diff --git a/.dprint.json b/.dprint.json index f2e243076599ba..422cd41e5ac258 100644 --- a/.dprint.json +++ b/.dprint.json @@ -22,6 +22,7 @@ "cli/bench/testdata/express-router.js", "cli/bench/testdata/npm/*", "cli/tsc/dts/*.d.ts", + "cli/tests/node_compat/test", "cli/tests/testdata/fmt/badly_formatted.json", "cli/tests/testdata/fmt/badly_formatted.md", "cli/tests/testdata/byte_order_mark.ts", diff --git a/Cargo.lock b/Cargo.lock index fd3153c69d1af9..23d442aed1b958 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -115,9 +115,9 @@ dependencies = [ [[package]] name = "android-activity" -version = "0.4.0" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4165a1aef703232031b40a6e8908c2f9e314d495f11aa7f98db75d39a497cc6a" +checksum = "7c77a0045eda8b888c76ea473c2b0515ba6f471d318f8927c5c72240937035a6" dependencies = [ "android-properties", "bitflags", @@ -187,7 +187,7 @@ dependencies = [ "proc-macro2 1.0.51", "quote 1.0.23", "swc_macros_common", - "syn 1.0.107", + "syn 1.0.109", ] [[package]] @@ -206,23 +206,24 @@ dependencies = [ [[package]] name = "async-stream" -version = "0.3.3" +version = "0.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dad5c83079eae9969be7fadefe640a1c566901f05ff91ab221de4b6f68d9507e" +checksum = "ad445822218ce64be7a341abfb0b1ea43b5c23aa83902542a4542e78309d8e5e" dependencies = [ "async-stream-impl", "futures-core", + "pin-project-lite", ] [[package]] name = "async-stream-impl" -version = "0.3.3" +version = "0.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "10f203db73a71dfa2fb6dd22763990fa26f3d2625a6da2da900d23b87d26be27" +checksum = "e4655ae1a7b0cdf149156f780c5bf3f1352bc53cbd9e0a361a7ef7b22947e965" dependencies = [ "proc-macro2 1.0.51", "quote 1.0.23", - "syn 1.0.107", + "syn 1.0.109", ] [[package]] @@ -233,7 +234,7 @@ checksum = "1cd7fce9ba8c3c042128ce72d8b2ddbf3a05747efb67ea0313c635e10bda47a2" dependencies = [ "proc-macro2 1.0.51", "quote 1.0.23", - "syn 1.0.107", + "syn 1.0.109", ] [[package]] @@ -256,7 +257,7 @@ dependencies = [ "proc-macro-error", "proc-macro2 1.0.51", "quote 1.0.23", - "syn 1.0.107", + "syn 1.0.109", ] [[package]] @@ -725,7 +726,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6d2301688392eb071b0bf1a37be05c469d3cc4dbbd95df672fe28ab021e6a096" dependencies = [ "quote 1.0.23", - "syn 1.0.107", + "syn 1.0.109", ] [[package]] @@ -771,9 +772,9 @@ dependencies = [ [[package]] name = "cxx" -version = "1.0.89" +version = "1.0.91" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bc831ee6a32dd495436e317595e639a587aa9907bef96fe6e6abc290ab6204e9" +checksum = "86d3488e7665a7a483b57e25bdd90d0aeb2bc7608c8d0346acf2ad3f1caf1d62" dependencies = [ "cc", "cxxbridge-flags", @@ -783,9 +784,9 @@ dependencies = [ [[package]] name = "cxx-build" -version = "1.0.89" +version = "1.0.91" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94331d54f1b1a8895cd81049f7eaaaef9d05a7dcb4d1fd08bf3ff0806246789d" +checksum = "48fcaf066a053a41a81dfb14d57d99738b767febb8b735c3016e469fac5da690" dependencies = [ "cc", "codespan-reporting", @@ -793,24 +794,24 @@ dependencies = [ "proc-macro2 1.0.51", "quote 1.0.23", "scratch", - "syn 1.0.107", + "syn 1.0.109", ] [[package]] name = "cxxbridge-flags" -version = "1.0.89" +version = "1.0.91" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "48dcd35ba14ca9b40d6e4b4b39961f23d835dbb8eed74565ded361d93e1feb8a" +checksum = "a2ef98b8b717a829ca5603af80e1f9e2e48013ab227b68ef37872ef84ee479bf" [[package]] name = "cxxbridge-macro" -version = "1.0.89" +version = "1.0.91" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81bbeb29798b407ccd82a3324ade1a7286e0d29851475990b612670f6f5124d2" +checksum = "086c685979a698443656e5cf7856c95c642295a38599f12fb1ff76fb28d19892" dependencies = [ "proc-macro2 1.0.51", "quote 1.0.23", - "syn 1.0.107", + "syn 1.0.109", ] [[package]] @@ -845,7 +846,7 @@ dependencies = [ "proc-macro2 1.0.51", "quote 1.0.23", "strsim", - "syn 1.0.107", + "syn 1.0.109", ] [[package]] @@ -856,7 +857,7 @@ checksum = "9c972679f83bdf9c42bd905396b6c3588a843a17f0f16dfcfa3e2c5d57441835" dependencies = [ "darling_core", "quote 1.0.23", - "syn 1.0.107", + "syn 1.0.109", ] [[package]] @@ -886,9 +887,9 @@ checksum = "8d7439c3735f405729d52c3fbbe4de140eaf938a1fe47d227c27f8254d4302a5" [[package]] name = "deno_ast" -version = "0.23.2" +version = "0.24.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e51afb5385ac30f59a1f4a80c986b7b4f02a1bf9da8bba5173aed80ab75ad8bf" +checksum = "76e007f9f03be5484596ea6bed86ffdc6357ba9660cb8da20845baf2ce079722" dependencies = [ "anyhow", "base64 0.13.1", @@ -918,9 +919,9 @@ dependencies = [ [[package]] name = "deno_bench_util" -version = "0.83.0" +version = "0.85.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5703056b5b8a64033e383b474baccea2df8addda8813d9ae3a1914974c9468a9" +checksum = "c2bece42b406eb8973c2a53951a9e5dfbb8fff721d7de31fa35b174b4fc076e7" dependencies = [ "bencher", "deno_core", @@ -930,9 +931,9 @@ dependencies = [ [[package]] name = "deno_broadcast_channel" -version = "0.83.0" +version = "0.85.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1eaddf316a5c63eabe962d8304fbd5f9fa6d360cc6972e72ee541b34b5cd2f88" +checksum = "66646a76b98b6ecfd28208585ae9b32eed49a322566c31e77f23d1efe41ce334" dependencies = [ "async-trait", "deno_core", @@ -942,9 +943,9 @@ dependencies = [ [[package]] name = "deno_cache" -version = "0.21.0" +version = "0.23.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fb8d1a44cb8c8d18eda0b95b1078f274a29089eb6dd21766a9ecf4ad6cbac685" +checksum = "93287fbe008c206ccdcb839713c6c3343083c82f97a3a8c6d0d1652e65d07acb" dependencies = [ "async-trait", "deno_core", @@ -956,18 +957,18 @@ dependencies = [ [[package]] name = "deno_console" -version = "0.89.0" +version = "0.91.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6cda6cf4635c2261951074023288f23756ac6852e7e63a6bd416a88f0e98c52e" +checksum = "5290ad8855794d107dd7faa325f3e0e6f7f9a161205203aa0c1658f9c8852425" dependencies = [ "deno_core", ] [[package]] name = "deno_core" -version = "0.171.0" +version = "0.173.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2dc41944f05dfeacfc2610e91f40ddcf246f3aeeac8ae4c26df46bfbf01a3902" +checksum = "e3f5ea1db875ff1eb020c04b900f75c810a9b66deec4a5437205b868bff9a7df" dependencies = [ "anyhow", "bytes", @@ -990,9 +991,9 @@ dependencies = [ [[package]] name = "deno_crypto" -version = "0.103.0" +version = "0.105.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4f4e2a590be03f643d1147e12e8bc2fb04671b9bd68da9c8857d7d0b11a0255c" +checksum = "065d18924326e14e217b366645c4a3c15680ce9f1b97d830e45bca0a7ce76581" dependencies = [ "aes", "aes-gcm", @@ -1027,9 +1028,9 @@ dependencies = [ [[package]] name = "deno_doc" -version = "0.53.0" +version = "0.57.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d792edabc692d89ab24cb4fa0f24cd8d71288669962e867b4634a28d04935bd" +checksum = "717302b02dd63e4e16a5e7f6339a74708a981b2addc5cbc944b9a4c74857120c" dependencies = [ "cfg-if", "deno_ast", @@ -1045,9 +1046,9 @@ dependencies = [ [[package]] name = "deno_emit" -version = "0.14.0" +version = "0.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9abbf7099beedb52d5d84cef071825f1abf75234d48c820b15dbd4e47576b64c" +checksum = "068712ed5abcae4c109c4e08d7b8f959fc974726c922a6c3c71aabb613249c92" dependencies = [ "anyhow", "base64 0.13.1", @@ -1060,9 +1061,9 @@ dependencies = [ [[package]] name = "deno_fetch" -version = "0.113.0" +version = "0.115.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "64c2ec54d6332b454cad9391e8b9c33edce79837ece8ffaca0f08ab957f78590" +checksum = "15422dcb563e7cec4adf7ead77811147b3eb9b9ee0e3100979f98297a7b6368b" dependencies = [ "bytes", "data-url", @@ -1079,24 +1080,26 @@ dependencies = [ [[package]] name = "deno_ffi" -version = "0.76.0" +version = "0.78.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e11cbb59638f05f3d4ffcb107a91491f70ddc86b93759be1f3858ffd4efd45f6" +checksum = "837b7d24fe8b6aa20dfb46981857e07ceeb24fc31827dbad95aa093241362a1a" dependencies = [ "deno_core", "dlopen", "dynasmrt", "libffi", "serde", + "serde-value", + "serde_json", "tokio", "winapi", ] [[package]] name = "deno_flash" -version = "0.25.0" +version = "0.27.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e8e6067e14bc4d904b53bd7a4f665eaa119ce31bc1bdb3913d41331de84f0a69" +checksum = "b03448283116a9ecc9537e730af4ccbbf6a74837a3f3b84a1cb2d5baf64c4126" dependencies = [ "deno_core", "deno_tls", @@ -1115,28 +1118,30 @@ dependencies = [ [[package]] name = "deno_graph" -version = "0.42.0" +version = "0.44.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f469b4a0694cb7e7fd512f23bc10d32398d7228cd2581a262109359c8e263940" +checksum = "1a2cb8c414852294d5fb23d265725bbf1fab9608296e04301440dba14fef71e5" dependencies = [ "anyhow", - "cfg-if", "data-url", "deno_ast", "futures", + "indexmap", + "monch", "once_cell", "parking_lot 0.12.1", "regex", "serde", "serde_json", + "thiserror", "url", ] [[package]] name = "deno_http" -version = "0.84.0" +version = "0.86.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "868bce9321850c1f2689846e8f031144efa5a7ae197d2839013c576c9b849167" +checksum = "af7598c56a295086870eedd85329de1806d5a5b7cf8ce048dbbbb68e4e04116f" dependencies = [ "async-compression", "base64 0.13.1", @@ -1160,9 +1165,9 @@ dependencies = [ [[package]] name = "deno_lint" -version = "0.37.0" +version = "0.40.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "82424506123f05de694106aa906a20cd1aabde637213187cfb3c6f0eba794e4a" +checksum = "663d65cb008cddda1d705cd397d5d80fd37716e708b4182ca076244e18132ace" dependencies = [ "anyhow", "deno_ast", @@ -1177,9 +1182,9 @@ dependencies = [ [[package]] name = "deno_lockfile" -version = "0.5.0" +version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49601a1de60fd2510ca3a2e9d5b58d367e4d523efb4241c63cf4185703291a36" +checksum = "7c9c1a71581ed67300959c7fdb03923d5f9bce773d553b4b55e850311fac1500" dependencies = [ "anyhow", "ring", @@ -1189,9 +1194,9 @@ dependencies = [ [[package]] name = "deno_napi" -version = "0.19.0" +version = "0.21.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c42ac68f4f95a5b786d76aacabfb0e0eb1817841159132b6ac72d6a6dba95429" +checksum = "97057fb0c11fe8bd016fb998082cdee3f831aa12a0d80c534a875c8ac4ec50db" dependencies = [ "deno_core", "libloading", @@ -1199,9 +1204,9 @@ dependencies = [ [[package]] name = "deno_net" -version = "0.81.0" +version = "0.83.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ed32765651e169918c9bb7f92d03b4b618401e8744d6a6ce6cc0d89ac4bda01" +checksum = "f0f6df34bcf15bf4e5ff0345e5a349c209fb5eb19189a39c4b1df09fc6a91450" dependencies = [ "deno_core", "deno_tls", @@ -1215,22 +1220,34 @@ dependencies = [ [[package]] name = "deno_node" -version = "0.26.0" +version = "0.28.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "31411684ae279034f4fdd1fb9d0f2207eeaa7a717fdf490c26b00a22775f08d7" +checksum = "a6b37e01ccc67c6c414064ba08d66071104b4340acc528a119ea5be73b086715" dependencies = [ "deno_core", + "digest 0.10.6", + "idna 0.3.0", + "indexmap", + "md-5", + "md4", "once_cell", "path-clean", + "rand", "regex", + "ripemd", + "rsa", "serde", + "sha-1 0.10.0", + "sha2", + "sha3", + "typenum", ] [[package]] name = "deno_ops" -version = "0.49.0" +version = "0.51.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4740bc5738ad07dc1f523a232a4079a995fa2ad11efd71e09e8e32bf28f21ee1" +checksum = "1d06332c139917f0fe6722e6c9bd3aac45bfdf3e306627e7eb681a42bad56497" dependencies = [ "once_cell", "pmutil", @@ -1238,14 +1255,14 @@ dependencies = [ "proc-macro2 1.0.51", "quote 1.0.23", "regex", - "syn 1.0.107", + "syn 1.0.109", ] [[package]] name = "deno_task_shell" -version = "0.8.2" +version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "532b383a071a05144c712614d62f08a2f9fad48dd62d6d457ed3884b049357da" +checksum = "a7068bd49521a7b22dc6df8937097a7ac285ea320cbd78582b4155d31f0d5049" dependencies = [ "anyhow", "futures", @@ -1258,9 +1275,9 @@ dependencies = [ [[package]] name = "deno_tls" -version = "0.76.0" +version = "0.78.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94b82b9b18941a42be4108f79f14e8b5a29067e27619293d710324e0dd77d5d8" +checksum = "46cac58432baf003b8f223c86118a54ffafe5d0a51c8196c0f42409b96e94c35" dependencies = [ "deno_core", "once_cell", @@ -1274,9 +1291,9 @@ dependencies = [ [[package]] name = "deno_url" -version = "0.89.0" +version = "0.91.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d1fe82b011d8b2af63c4587551536d951f47ffc3ba2a710e455b383d4f4b06ba" +checksum = "bf7e791aa935afc79258e31083f8c0c940f3fe063226d11122149197c41b47c5" dependencies = [ "deno_core", "serde", @@ -1286,9 +1303,9 @@ dependencies = [ [[package]] name = "deno_web" -version = "0.120.0" +version = "0.122.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7379502a7a333f573949558803e8bfe2e8fba3ef180cdbb4a882951803c87d20" +checksum = "c61f6d9f990fefe637d6ee6eb9de416b3c113dfef36d73946e37dbe0bfbcefcd" dependencies = [ "async-trait", "base64-simd", @@ -1302,18 +1319,18 @@ dependencies = [ [[package]] name = "deno_webidl" -version = "0.89.0" +version = "0.91.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d046c6ac75f22be851219f44824c42927345f51e0ae5fb825e8bf8ea658d8ee8" +checksum = "3bcf5027f4ab41174cb6ed30afb4a11c0c519db2287a00c6290b939f7ba83bfe" dependencies = [ "deno_core", ] [[package]] name = "deno_websocket" -version = "0.94.0" +version = "0.96.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "afe8ce87cc7da7b4b0575d5686cafbdb306cb33bf04f33ff6e99325c88f44933" +checksum = "fb839d2e6e574d87984687037e957c8699528130351043bdc6f392eb8005da8c" dependencies = [ "deno_core", "deno_tls", @@ -1327,9 +1344,9 @@ dependencies = [ [[package]] name = "deno_webstorage" -version = "0.84.0" +version = "0.86.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "33b25958fe8143a02c86971890b7fa08a888e6a8a201e4918ea49220c092c361" +checksum = "8cfa81bd857bece07e8c4fe771992f82dd3d050025b0d5fa3b1b0e12d2daca8c" dependencies = [ "deno_core", "deno_web", @@ -1339,8 +1356,9 @@ dependencies = [ [[package]] name = "denog" -version = "0.6.0" +version = "0.7.0" dependencies = [ + "async-trait", "atty", "base32", "base64 0.13.1", @@ -1398,7 +1416,6 @@ dependencies = [ "ring", "rustyline", "rustyline-derive", - "semver 1.0.14", "serde", "serde_json", "serde_repr", @@ -1408,6 +1425,7 @@ dependencies = [ "test_util", "text-size", "text_lines", + "thiserror", "tokio", "tokio-util", "tower-lsp", @@ -1424,9 +1442,10 @@ dependencies = [ [[package]] name = "denog_runtime" -version = "0.6.0" +version = "0.7.0" dependencies = [ "atty", + "deno_ast", "deno_broadcast_channel", "deno_cache", "deno_console", @@ -1476,7 +1495,7 @@ dependencies = [ [[package]] name = "denog_webgpu" -version = "0.6.0" +version = "0.7.0" dependencies = [ "deno_core", "raw-window-handle", @@ -1488,7 +1507,7 @@ dependencies = [ [[package]] name = "denog_wsi" -version = "0.6.0" +version = "0.7.0" dependencies = [ "deno_core", "denog_webgpu", @@ -1519,7 +1538,7 @@ dependencies = [ "proc-macro2 1.0.51", "quote 1.0.23", "rustc_version 0.4.0", - "syn 1.0.107", + "syn 1.0.109", ] [[package]] @@ -1657,9 +1676,9 @@ dependencies = [ [[package]] name = "dprint-plugin-typescript" -version = "0.81.1" +version = "0.83.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a5991c52c38d10079ecc8d4b4addcfcf77c125244027ce0934631ac22c6b6b36" +checksum = "0d1c293007fd6552e023431731f0d6fa0ed560d056ef4609e1c6dfcfbd734924" dependencies = [ "anyhow", "deno_ast", @@ -1670,9 +1689,9 @@ dependencies = [ [[package]] name = "dprint-swc-ext" -version = "0.6.0" +version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba1b7bac9133524358ec340b52b30a72df03c6252e327c22ad230637cc357306" +checksum = "0e2dc99247101e0132a17680c5afbba9a7334477b47738dd3431c13f5e2c9a84" dependencies = [ "bumpalo", "num-bigint", @@ -1702,7 +1721,7 @@ dependencies = [ "proc-macro-error", "proc-macro2 1.0.51", "quote 1.0.23", - "syn 1.0.107", + "syn 1.0.109", ] [[package]] @@ -1780,7 +1799,7 @@ dependencies = [ "heck", "proc-macro2 1.0.51", "quote 1.0.23", - "syn 1.0.107", + "syn 1.0.109", ] [[package]] @@ -1792,7 +1811,7 @@ dependencies = [ "pmutil", "proc-macro2 1.0.51", "swc_macros_common", - "syn 1.0.107", + "syn 1.0.109", ] [[package]] @@ -1850,9 +1869,9 @@ dependencies = [ [[package]] name = "eszip" -version = "0.33.0" +version = "0.37.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "98d6b4f119df606d3cdce5b251b326b6f5cc9a5c59054d701ca973c23f90f7b4" +checksum = "a1a9f2547787db6a0f5998a91dc54a3b5031190d11edcc62739473c0299bf36b" dependencies = [ "anyhow", "base64 0.13.1", @@ -1890,9 +1909,9 @@ dependencies = [ [[package]] name = "fastrand" -version = "1.8.0" +version = "1.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a7a407cfaa3385c4ae6b23e84623d48c2798d06e3e6a1878f7f59f17b3f86499" +checksum = "e51093e27b0797c359783294ca4f0a911c270184cb10f85783b118614a1501be" dependencies = [ "instant", ] @@ -1920,14 +1939,14 @@ dependencies = [ [[package]] name = "filetime" -version = "0.2.19" +version = "0.2.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4e884668cd0c7480504233e951174ddc3b382f7c2666e3b7310b5c4e7b0c37f9" +checksum = "8a3de6e8d11b22ff9edc6d916f890800597d60f8b2da1caf2955c274638d6412" dependencies = [ "cfg-if", "libc", "redox_syscall 0.2.16", - "windows-sys 0.42.0", + "windows-sys 0.45.0", ] [[package]] @@ -1944,7 +1963,7 @@ checksum = "479cde5eb168cf5a056dd98f311cbfab7494c216394e4fb9eba0336827a8db93" dependencies = [ "proc-macro2 1.0.51", "quote 1.0.23", - "syn 1.0.107", + "syn 1.0.109", ] [[package]] @@ -2007,7 +2026,7 @@ dependencies = [ "pmutil", "proc-macro2 1.0.51", "swc_macros_common", - "syn 1.0.107", + "syn 1.0.109", ] [[package]] @@ -2096,7 +2115,7 @@ checksum = "95a73af87da33b5acf53acfebdc339fe592ecf5357ac7c0a7734ab9d8c876a70" dependencies = [ "proc-macro2 1.0.51", "quote 1.0.23", - "syn 1.0.107", + "syn 1.0.109", ] [[package]] @@ -2210,9 +2229,9 @@ dependencies = [ [[package]] name = "glow" -version = "0.12.0" +version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8edf6019dff2d92ad27c1e3ff82ad50a0aea5b01370353cc928bfdc33e95925c" +checksum = "4e007a07a24de5ecae94160f141029e9a347282cfe25d1d58d85d845cf3130f1" dependencies = [ "js-sys", "slotmap", @@ -2611,15 +2630,15 @@ checksum = "30e22bd8629359895450b59ea7a776c850561b96a3b1d31321c1949d9e6c9146" [[package]] name = "is-macro" -version = "0.2.1" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1c068d4c6b922cd6284c609cfa6dec0e41615c9c5a1a4ba729a970d8daba05fb" +checksum = "8a7d079e129b77477a49c5c4f1cfe9ce6c2c909ef52520693e8e811a714c7b20" dependencies = [ "Inflector", "pmutil", "proc-macro2 1.0.51", "quote 1.0.23", - "syn 1.0.107", + "syn 1.0.109", ] [[package]] @@ -2680,6 +2699,15 @@ dependencies = [ "winapi", ] +[[package]] +name = "keccak" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3afef3b6eff9ce9d8ff9b3601125eec7f0c8cbac7abd14f355d053fa56c98768" +dependencies = [ + "cpufeatures", +] + [[package]] name = "khronos-egl" version = "4.1.0" @@ -2938,6 +2966,24 @@ version = "0.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2532096657941c2fea9c289d370a250971c689d4f143798ff67113ec042024a5" +[[package]] +name = "md-5" +version = "0.10.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6365506850d44bff6e2fbcb5176cf63650e48bd45ef2fe2665ae1570e0f4b9ca" +dependencies = [ + "digest 0.10.6", +] + +[[package]] +name = "md4" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7da5ac363534dce5fabf69949225e174fbf111a498bf0ff794c8ea1fba9f3dda" +dependencies = [ + "digest 0.10.6", +] + [[package]] name = "memchr" version = "2.5.0" @@ -2946,9 +2992,9 @@ checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" [[package]] name = "memmap2" -version = "0.5.8" +version = "0.5.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4b182332558b18d807c4ce1ca8ca983b34c3ee32765e47b3f0f69b90355cc1dc" +checksum = "83faa42c0a078c393f6b29d5db232d8be22776a891f8f56e5284faee4a20b327" dependencies = [ "libc", ] @@ -3008,14 +3054,14 @@ dependencies = [ [[package]] name = "mio" -version = "0.8.5" +version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5d732bc30207a6423068df043e3d02e0735b155ad7ce1a6f76fe2baa5b158de" +checksum = "5b9d9a46eff5b4ff64b45a9e316a6d1e0bc719ef429cbec4dc630684212bfdf9" dependencies = [ "libc", "log", "wasi 0.11.0+wasi-snapshot-preview1", - "windows-sys 0.42.0", + "windows-sys 0.45.0", ] [[package]] @@ -3056,15 +3102,15 @@ dependencies = [ [[package]] name = "napi_sym" -version = "0.19.0" +version = "0.21.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68fdb3a893e17454ffeee37ce62eecd41a63d8acc7b9c787a9f812dd6762a1a8" +checksum = "7da5214488838f7dae97398799e086a928597969e40a3bb84c90d3ea356700ec" dependencies = [ "proc-macro2 1.0.51", "quote 1.0.23", "serde", "serde_json", - "syn 1.0.107", + "syn 1.0.109", ] [[package]] @@ -3264,23 +3310,23 @@ dependencies = [ [[package]] name = "num_enum" -version = "0.5.9" +version = "0.5.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8d829733185c1ca374f17e52b762f24f535ec625d2cc1f070e34c8a9068f341b" +checksum = "1f646caf906c20226733ed5b1374287eb97e3c2a5c227ce668c1f2ce20ae57c9" dependencies = [ "num_enum_derive", ] [[package]] name = "num_enum_derive" -version = "0.5.9" +version = "0.5.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2be1598bf1c313dcdd12092e3f1920f463462525a21b7b4e11b4168353d0123e" +checksum = "dcbff9bc912032c62bf65ef1d5aea88983b420f4f839db1e9b0c281a25c9c799" dependencies = [ "proc-macro-crate", "proc-macro2 1.0.51", "quote 1.0.23", - "syn 1.0.107", + "syn 1.0.109", ] [[package]] @@ -3358,6 +3404,15 @@ dependencies = [ "web-sys", ] +[[package]] +name = "ordered-float" +version = "2.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7940cf2ca942593318d07fcf2596cdca60a85c9e7fab408a5e21a4f9dcd40d87" +dependencies = [ + "num-traits", +] + [[package]] name = "os_pipe" version = "1.0.1" @@ -3546,7 +3601,7 @@ dependencies = [ "proc-macro-hack", "proc-macro2 1.0.51", "quote 1.0.23", - "syn 1.0.107", + "syn 1.0.109", ] [[package]] @@ -3575,7 +3630,7 @@ checksum = "069bdb1e05adc7a8990dce9cc75370895fbe4e3d58b9b73bf1aee56359344a55" dependencies = [ "proc-macro2 1.0.51", "quote 1.0.23", - "syn 1.0.107", + "syn 1.0.109", ] [[package]] @@ -3626,7 +3681,7 @@ checksum = "3894e5d549cccbe44afecf72922f277f603cd4bb0219c8342631ef18fffbe004" dependencies = [ "proc-macro2 1.0.51", "quote 1.0.23", - "syn 1.0.107", + "syn 1.0.109", ] [[package]] @@ -3696,7 +3751,7 @@ dependencies = [ "proc-macro-error-attr", "proc-macro2 1.0.51", "quote 1.0.23", - "syn 1.0.107", + "syn 1.0.109", "version_check", ] @@ -3843,9 +3898,9 @@ dependencies = [ [[package]] name = "range-alloc" -version = "0.1.2" +version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "63e935c45e09cc6dcf00d2f0b2d630a58f4095320223d47fc68918722f0538b6" +checksum = "9c8a99fddc9f0ba0a85884b8d14e3592853e787d581ca1816c91349b10e4eeab" [[package]] name = "raw-window-handle" @@ -3959,12 +4014,6 @@ dependencies = [ "quick-error", ] -[[package]] -name = "retain_mut" -version = "0.1.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4389f1d5789befaf6029ebd9f7dac4af7f7e3d61b69d4f30e2ac02b57e7712b0" - [[package]] name = "rfc6979" version = "0.3.1" @@ -3991,6 +4040,15 @@ dependencies = [ "winapi", ] +[[package]] +name = "ripemd" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd124222d17ad93a644ed9d011a40f4fb64aa54275c08cc216524a9ea82fb09f" +dependencies = [ + "digest 0.10.6", +] + [[package]] name = "ron" version = "0.8.0" @@ -4144,7 +4202,7 @@ checksum = "107c3d5d7f370ac09efa62a78375f94d94b8a33c61d8c278b96683fb4dbf2d8d" dependencies = [ "proc-macro2 1.0.51", "quote 1.0.23", - "syn 1.0.107", + "syn 1.0.109", ] [[package]] @@ -4279,6 +4337,16 @@ dependencies = [ "serde_derive", ] +[[package]] +name = "serde-value" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3a1a3341211875ef120e117ea7fd5228530ae7e7036a779fdc9117be6b3282c" +dependencies = [ + "ordered-float", + "serde", +] + [[package]] name = "serde_bytes" version = "0.11.9" @@ -4296,14 +4364,14 @@ checksum = "af487d118eecd09402d70a5d72551860e788df87b464af30e5ea6a38c75c541e" dependencies = [ "proc-macro2 1.0.51", "quote 1.0.23", - "syn 1.0.107", + "syn 1.0.109", ] [[package]] name = "serde_json" -version = "1.0.92" +version = "1.0.93" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7434af0dc1cbd59268aa98b4c22c131c0584d2232f6fb166efb993e2832e896a" +checksum = "cad406b69c91885b5107daf2c29572f6c8cdb3c66826821e286c533490c0bc76" dependencies = [ "indexmap", "itoa", @@ -4319,7 +4387,7 @@ checksum = "1fe39d9fbb0ebf5eb2c7cb7e2a47e4f462fad1379f1166b8ae49ad9eae89a7ca" dependencies = [ "proc-macro2 1.0.51", "quote 1.0.23", - "syn 1.0.107", + "syn 1.0.109", ] [[package]] @@ -4336,9 +4404,9 @@ dependencies = [ [[package]] name = "serde_v8" -version = "0.82.0" +version = "0.84.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c060fd38f18c420e82ab21592ec1f088b39bccb6897b1dda394d63628e22158d" +checksum = "94c6b0ea68c2368070520883b5caac9b5bff17597cfac77aebc07280e8585f7f" dependencies = [ "bytes", "derive_more", @@ -4394,6 +4462,16 @@ dependencies = [ "digest 0.10.6", ] +[[package]] +name = "sha3" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bdf0c33fae925bdc080598b84bc15c55e7b9a4a43b3c704da051f977469691c9" +dependencies = [ + "digest 0.10.6", + "keccak", +] + [[package]] name = "shell-escape" version = "0.1.5" @@ -4402,9 +4480,9 @@ checksum = "45bb67a18fa91266cc7807181f62f9178a6873bfad7dc788c42e6430db40184f" [[package]] name = "signal-hook-registry" -version = "1.4.0" +version = "1.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e51e73328dc4ac0c7ccbda3a494dfa03df1de2f46018127f60c693f2648455b0" +checksum = "d8229b473baa5980ac72ef434c4415e70c4b5e71b423043adb4ba059f89c99a1" dependencies = [ "libc", ] @@ -4427,9 +4505,9 @@ checksum = "7bd3e3206899af3f8b12af284fafc038cc1dc2b41d1b89dd17297221c5d225de" [[package]] name = "slab" -version = "0.4.7" +version = "0.4.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4614a76b2a8be0058caa9dbbaf66d988527d86d003c11a94fbd335d7661edcef" +checksum = "6528351c9bc8ab22353f9d776db39a20288e8d6c37ef8cfe3317cf875eecfc2d" dependencies = [ "autocfg", ] @@ -4480,11 +4558,11 @@ dependencies = [ [[package]] name = "sourcemap" -version = "6.2.1" +version = "6.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aebe057d110ddba043708da3fb010bf562ff6e9d4d60c9ee92860527bcbeccd6" +checksum = "3562681c4e0890af6cd22f09a0eeed4afd855d5011cd5f9358c834b670932382" dependencies = [ - "base64 0.13.1", + "data-encoding", "if_chain", "rustc_version 0.2.3", "serde", @@ -4579,7 +4657,7 @@ dependencies = [ "proc-macro2 1.0.51", "quote 1.0.23", "swc_macros_common", - "syn 1.0.107", + "syn 1.0.109", ] [[package]] @@ -4596,9 +4674,9 @@ checksum = "6bdef32e8150c2a081110b42772ffe7d7c9032b606bc226c8260fd97e0976601" [[package]] name = "swc_atoms" -version = "0.4.32" +version = "0.4.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ad59af21529fcd3f4f8fa6b1ae399c2b183ec42c68347d76d68d6e5b657956e" +checksum = "731cf66bd8e11030f056f91f9d8af77f83ec4377ff04d1670778a57d1607402a" dependencies = [ "once_cell", "rustc-hash", @@ -4610,9 +4688,9 @@ dependencies = [ [[package]] name = "swc_bundler" -version = "0.193.30" +version = "0.199.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc9c11d11591e011e4131febcc366fb0401446a5ac71813de08437237be43668" +checksum = "bd8fba0fc5fd9deb528007c6bd6b0a326d17c114d702a1ec00863c2981bb03af" dependencies = [ "ahash", "anyhow", @@ -4624,7 +4702,6 @@ dependencies = [ "petgraph", "radix_fmt", "relative-path", - "retain_mut", "swc_atoms", "swc_common", "swc_ecma_ast", @@ -4642,9 +4719,9 @@ dependencies = [ [[package]] name = "swc_common" -version = "0.29.25" +version = "0.29.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "506321cad7393893018aac83a3b3bd25203883e8c47ab0864bb43195d43b22dd" +checksum = "a97e491d31418cd33fea58e9f893316fc04b30e2b5d0e750c066e2ba4907ae54" dependencies = [ "ahash", "ast_node", @@ -4690,14 +4767,14 @@ dependencies = [ "proc-macro2 1.0.51", "quote 1.0.23", "swc_macros_common", - "syn 1.0.107", + "syn 1.0.109", ] [[package]] name = "swc_ecma_ast" -version = "0.95.9" +version = "0.96.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3cc936f04c4e671ae5918b573a50945c5189d3dcdd57e4faddd47889717e1416" +checksum = "a887102d5595b557261aa4bde35f3d71906fba674d4b79cd5c59b4155b12ee2d" dependencies = [ "bitflags", "is-macro", @@ -4712,9 +4789,9 @@ dependencies = [ [[package]] name = "swc_ecma_codegen" -version = "0.128.15" +version = "0.129.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "121caf2dde74cbd143035a92cfd249be7744ee31622c4e66ee19a8249e3f6855" +checksum = "45f8f20522626a737753381bdf64ee53d568730f9f7e720d35960de97e5ff965" dependencies = [ "memchr", "num-bigint", @@ -4739,14 +4816,14 @@ dependencies = [ "proc-macro2 1.0.51", "quote 1.0.23", "swc_macros_common", - "syn 1.0.107", + "syn 1.0.109", ] [[package]] name = "swc_ecma_dep_graph" -version = "0.95.13" +version = "0.96.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42a9122bcff80fba41cadd123fc9136424755144de16dfabeee4031144d204a9" +checksum = "8fa847768c9ffb0b44085c881bc8931b3cb17f8eb0b81c65bf9068c0e9cbdc02" dependencies = [ "swc_atoms", "swc_common", @@ -4756,9 +4833,9 @@ dependencies = [ [[package]] name = "swc_ecma_loader" -version = "0.41.26" +version = "0.41.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42710b93ec010a5e0354cc86d621a3dd0243351d649d0c273c1887035a256151" +checksum = "c996baa947150d496c79fbd153d3df834e38d05c779abc4af987aded90e168a2" dependencies = [ "ahash", "anyhow", @@ -4770,9 +4847,9 @@ dependencies = [ [[package]] name = "swc_ecma_parser" -version = "0.123.13" +version = "0.124.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22225f792dcbcd3d3e77498d6e6fb86161cdd05ba4e24456361768dc41ee2948" +checksum = "9e75888eabf1ad8a8968e3befc7cd20c10e4721254d3344285bd5c3a42f58dc1" dependencies = [ "either", "enum_kind", @@ -4789,9 +4866,9 @@ dependencies = [ [[package]] name = "swc_ecma_transforms_base" -version = "0.112.19" +version = "0.116.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "44bc36990f42ceea1370426a2f3e923f43c4277342a8583edb4c4bef2f27e63d" +checksum = "5f5a212abba41897332f9ab3af8fe5d1a59f83d69e25eea119c27d9b53876688" dependencies = [ "better_scoped_tls", "bitflags", @@ -4811,9 +4888,9 @@ dependencies = [ [[package]] name = "swc_ecma_transforms_classes" -version = "0.101.19" +version = "0.105.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b247a889b92f088e5ecd66ccbdc5915a102d4d9f54823e9a93ec7344a1c080f" +checksum = "a2fd5a8eff1a7f16ec1b3ae50186debf3d3983effed6ed05d4e6db0ed7aadcac" dependencies = [ "swc_atoms", "swc_common", @@ -4833,14 +4910,14 @@ dependencies = [ "proc-macro2 1.0.51", "quote 1.0.23", "swc_macros_common", - "syn 1.0.107", + "syn 1.0.109", ] [[package]] name = "swc_ecma_transforms_optimization" -version = "0.168.21" +version = "0.172.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8d291756dcb423ea457c65ac463fbfd52d5917a7cda9ea4a097591afabe2ca5" +checksum = "db95141f068ca7cf391157c05bfb74c8b628174aa213b96c3a20a12ab3e07d14" dependencies = [ "ahash", "dashmap", @@ -4863,9 +4940,9 @@ dependencies = [ [[package]] name = "swc_ecma_transforms_proposal" -version = "0.145.20" +version = "0.149.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d1850fce438ac6d3f31a1e4bcf8e385df7fe6603cb4a09d3a281472b2b937518" +checksum = "a6fe11a20c7ced3c6b6149da330b8b4d8912fe02af6923aaac4d4479aa3dd3b6" dependencies = [ "either", "serde", @@ -4882,9 +4959,9 @@ dependencies = [ [[package]] name = "swc_ecma_transforms_react" -version = "0.156.20" +version = "0.160.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd4b1e06d0c517dbc308d6ba9004c1d8bd3e271f2bff445ac2226536e3893e67" +checksum = "b94e3884668e2e12684e4adf812dbd22d34b17da2d7637b1c9cffe393acad6c2" dependencies = [ "ahash", "base64 0.13.1", @@ -4908,9 +4985,9 @@ dependencies = [ [[package]] name = "swc_ecma_transforms_typescript" -version = "0.160.21" +version = "0.164.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "795677b92c36308ff444952aa1eb7ce041964f7f823dda69de406401b73e0d6e" +checksum = "9a885199b43798b46d8b26b4a986f899436f9f2cb37477eb12a183388a5c213f" dependencies = [ "serde", "swc_atoms", @@ -4924,9 +5001,9 @@ dependencies = [ [[package]] name = "swc_ecma_utils" -version = "0.106.15" +version = "0.107.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "20675f180e890897386295825bb6297640f7843282410545479dce02ac98b563" +checksum = "d773cf626c8d3be468a883879cda3727a2f1ea6169ccd0b5b8eb2d7afb5f367b" dependencies = [ "indexmap", "num_cpus", @@ -4942,9 +5019,9 @@ dependencies = [ [[package]] name = "swc_ecma_visit" -version = "0.81.9" +version = "0.82.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ebf5de90444c90b1905b7618800a7572fc757faa8c90cc1c6031d1f6ca179df" +checksum = "6b2ee0f4b61d6c426189d0d9da1333705ff3bc4513fb63633ca254595a700f75" dependencies = [ "num-bigint", "swc_atoms", @@ -4963,14 +5040,14 @@ dependencies = [ "pmutil", "proc-macro2 1.0.51", "quote 1.0.23", - "syn 1.0.107", + "syn 1.0.109", ] [[package]] name = "swc_fast_graph" -version = "0.17.26" +version = "0.17.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "06584f28662339e1972d164d263b3bfacdc13e1acb5fbe6d568c132a4693034b" +checksum = "42663a304e6b2aa28ff50da0c1cb49fc9d5e84a97d57d68d7eba385602deef95" dependencies = [ "ahash", "indexmap", @@ -4980,9 +5057,9 @@ dependencies = [ [[package]] name = "swc_graph_analyzer" -version = "0.18.28" +version = "0.18.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b052b885bcf22f52a0d279a88191f8df2787dca5105409998aa3890460c5e77" +checksum = "9b4fd7f98578fda786fed452c0a790c93ad6825abfe20989758afa1f13295273" dependencies = [ "ahash", "auto_impl", @@ -5000,7 +5077,7 @@ dependencies = [ "pmutil", "proc-macro2 1.0.51", "quote 1.0.23", - "syn 1.0.107", + "syn 1.0.109", ] [[package]] @@ -5024,7 +5101,7 @@ dependencies = [ "proc-macro2 1.0.51", "quote 1.0.23", "swc_macros_common", - "syn 1.0.107", + "syn 1.0.109", ] [[package]] @@ -5040,9 +5117,9 @@ dependencies = [ [[package]] name = "syn" -version = "1.0.107" +version = "1.0.109" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1f4064b5b16e03ae50984a5a8ed5d4f8803e6bc1fd170a3cda91a1be4b18e3f5" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" dependencies = [ "proc-macro2 1.0.51", "quote 1.0.23", @@ -5057,7 +5134,7 @@ checksum = "f36bdaa60a83aca3921b5259d5400cbf5e90fc51931376a9bd4a0eb79aa7210f" dependencies = [ "proc-macro2 1.0.51", "quote 1.0.23", - "syn 1.0.107", + "syn 1.0.109", "unicode-xid 0.2.4", ] @@ -5165,14 +5242,14 @@ checksum = "1fb327af4685e4d03fa8cbcf1716380da910eeb2bb8be417e7f9fd3fb164f36f" dependencies = [ "proc-macro2 1.0.51", "quote 1.0.23", - "syn 1.0.107", + "syn 1.0.109", ] [[package]] name = "time" -version = "0.3.17" +version = "0.3.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a561bf4617eebd33bca6434b988f39ed798e527f51a1e797d0ee4f61c0a38376" +checksum = "cd0cbfecb4d19b5ea75bb31ad904eb5b9fa13f21079c3b92017ebdf4999a5890" dependencies = [ "serde", "time-core", @@ -5226,9 +5303,9 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tokio" -version = "1.24.2" +version = "1.25.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "597a12a59981d9e3c38d216785b0c37399f6e415e8d0712047620f189371b0bb" +checksum = "c8e00990ebabbe4c14c08aca901caed183ecd5c09562a12c824bb53d3c3fd3af" dependencies = [ "autocfg", "bytes", @@ -5252,7 +5329,7 @@ checksum = "d266c00fde287f55d3f1c3e96c500c362a2b8c695076ec180f27918820bc6df8" dependencies = [ "proc-macro2 1.0.51", "quote 1.0.23", - "syn 1.0.107", + "syn 1.0.109", ] [[package]] @@ -5280,9 +5357,9 @@ dependencies = [ [[package]] name = "tokio-stream" -version = "0.1.11" +version = "0.1.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d660770404473ccd7bc9f8b28494a811bc18542b915c0855c51e8f419d5223ce" +checksum = "8fb52b74f05dbf495a8fba459fdc331812b96aa086d9eb78101fa0d4569c3313" dependencies = [ "futures-core", "pin-project-lite", @@ -5396,7 +5473,7 @@ checksum = "7ebd99eec668d0a450c177acbc4d05e0d0d13b1f8d3db13cd706c52cbec4ac04" dependencies = [ "proc-macro2 1.0.51", "quote 1.0.23", - "syn 1.0.107", + "syn 1.0.109", ] [[package]] @@ -5425,7 +5502,7 @@ checksum = "4017f8f45139870ca7e672686113917c71c7a6e02d4924eda67186083c03081a" dependencies = [ "proc-macro2 1.0.51", "quote 1.0.23", - "syn 1.0.107", + "syn 1.0.109", ] [[package]] @@ -5758,9 +5835,9 @@ dependencies = [ [[package]] name = "v8" -version = "0.60.1" +version = "0.63.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07fd5b3ed559897ff02c0f62bc0a5f300bfe79bb4c77a50031b8df771701c628" +checksum = "547e58962ac268fe0b1fbfb653ed341a08e3953994f7f7c978e46ec30afdf8f0" dependencies = [ "bitflags", "fslock", @@ -5867,7 +5944,7 @@ dependencies = [ "once_cell", "proc-macro2 1.0.51", "quote 1.0.23", - "syn 1.0.107", + "syn 1.0.109", "wasm-bindgen-shared", ] @@ -5901,7 +5978,7 @@ checksum = "2aff81306fcac3c7515ad4e177f521b5c9a15f2b08f4e32d823066102f35a5f6" dependencies = [ "proc-macro2 1.0.51", "quote 1.0.23", - "syn 1.0.107", + "syn 1.0.109", "wasm-bindgen-backend", "wasm-bindgen-shared", ] @@ -6029,9 +6106,9 @@ dependencies = [ [[package]] name = "wgpu-core" -version = "0.15.0" +version = "0.15.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be1f61be28e557a6ecb2506cac06c63fae3b6d302a006f38195a7a80995abeb9" +checksum = "7131408d940e335792645a98f03639573b0480e9e2e7cddbbab74f7c6d9f3fff" dependencies = [ "arrayvec", "bit-vec", @@ -6054,9 +6131,9 @@ dependencies = [ [[package]] name = "wgpu-hal" -version = "0.15.1" +version = "0.15.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "82e95792925fe3d58950b9a5c2a191caa145e2bc570e2d233f0d7320f6a8e814" +checksum = "37a7816f00690eca4540579ad861ee9b646d938b467ce83caa5ffb8b1d8180f6" dependencies = [ "android_system_properties", "arrayvec", @@ -6093,9 +6170,9 @@ dependencies = [ [[package]] name = "wgpu-types" -version = "0.15.0" +version = "0.15.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ecf8cfcbf98f94cc8bd5981544c687140cf9d3948e2ab83849367ead2cd737cf" +checksum = "a321d5436275e62be2d1ebb87991486fb3a561823656beb5410571500972cc65" dependencies = [ "bitflags", "js-sys", @@ -6354,7 +6431,7 @@ checksum = "44bf07cb3e50ea2003396695d58bf46bc9887a1f362260446fad6bc4e79bd36c" dependencies = [ "proc-macro2 1.0.51", "quote 1.0.23", - "syn 1.0.107", + "syn 1.0.109", "synstructure", ] @@ -6379,9 +6456,9 @@ dependencies = [ [[package]] name = "zstd-sys" -version = "2.0.6+zstd.1.5.2" +version = "2.0.7+zstd.1.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68a3f9792c0c3dc6c165840a75f47ae1f4da402c2d006881129579f6597e801b" +checksum = "94509c3ba2fe55294d752b79842c530ccfab760192521df74a081a78d2b3c7f5" dependencies = [ "cc", "libc", diff --git a/Cargo.toml b/Cargo.toml index f6b14d1f45f2ea..f75b4ce8cdf871 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -18,38 +18,38 @@ license = "MIT" repository = "https://github.com/denogdev/denog" [workspace.dependencies] -v8 = { version = "0.60.1", default-features = false } -deno_ast = { version = "0.23.2", features = ["transpiling"] } +v8 = { version = "0.63.0", default-features = false } +deno_ast = { version = "0.24.0", features = ["transpiling"] } -deno_core = { version = "0.171.0" } -deno_ops = { version = "0.49.0" } -serde_v8 = { version = "0.82.0" } -deno_runtime = { version = "0.6.0", path = "./runtime", package = "denog_runtime" } -napi_sym = { version = "0.19.0" } -deno_bench_util = { version = "0.83.0" } +deno_core = { version = "0.173.0" } +deno_ops = { version = "0.51.0" } +serde_v8 = { version = "0.84.0" } +deno_runtime = { version = "0.7.0", path = "./runtime", package = "denog_runtime" } +napi_sym = { version = "0.21.0" } +deno_bench_util = { version = "0.85.0" } test_util = { path = "./test_util" } -deno_lockfile = { version = "0.5.0" } +deno_lockfile = { version = "0.7.0" } # exts -deno_broadcast_channel = { version = "0.83.0" } -deno_cache = { version = "0.21.0" } -deno_console = { version = "0.89.0" } -deno_crypto = { version = "0.103.0" } -deno_fetch = { version = "0.113.0" } -deno_ffi = { version = "0.76.0" } -deno_flash = { version = "0.25.0" } -deno_http = { version = "0.84.0" } -deno_net = { version = "0.81.0" } -deno_node = { version = "0.26.0" } -deno_tls = { version = "0.76.0" } -deno_url = { version = "0.89.0" } -deno_web = { version = "0.120.0" } -deno_webgpu = { version = "0.6.0", path = "./ext/webgpu", package = "denog_webgpu" } -deno_webidl = { version = "0.89.0" } -deno_websocket = { version = "0.94.0" } -deno_webstorage = { version = "0.84.0" } -deno_napi = { version = "0.19.0" } -deno_wsi = { version = "0.6.0", path = "./ext/wsi", package = "denog_wsi" } +deno_broadcast_channel = { version = "0.85.0" } +deno_cache = { version = "0.23.0" } +deno_console = { version = "0.91.0" } +deno_crypto = { version = "0.105.0" } +deno_fetch = { version = "0.115.0" } +deno_ffi = { version = "0.78.0" } +deno_flash = { version = "0.27.0" } +deno_http = { version = "0.86.0" } +deno_net = { version = "0.83.0" } +deno_node = { version = "0.28.0" } +deno_tls = { version = "0.78.0" } +deno_url = { version = "0.91.0" } +deno_web = { version = "0.122.0" } +deno_webgpu = { version = "0.7.0", path = "./ext/webgpu", package = "denog_webgpu" } +deno_webidl = { version = "0.91.0" } +deno_websocket = { version = "0.96.0" } +deno_webstorage = { version = "0.86.0" } +deno_napi = { version = "0.21.0" } +deno_wsi = { version = "0.7.0", path = "./ext/wsi", package = "denog_wsi" } anyhow = "1.0.57" async-trait = "0.1.51" @@ -65,6 +65,7 @@ flate2 = "=1.0.24" futures = "0.3.21" http = "=0.2.8" hyper = "0.14.18" +indexmap = { version = "1.9.2", features = ["serde"] } libc = "0.2.126" log = "=0.4.17" lzzzz = "1.0" @@ -82,7 +83,6 @@ ring = "=0.16.20" rusqlite = { version = "=0.28.0", features = ["unlock_notify", "bundled"] } rustls = "0.20.5" rustls-pemfile = "1.0.0" -semver = "=1.0.14" serde = { version = "1.0.149", features = ["derive"] } serde_bytes = "0.11" serde_json = "1.0.85" @@ -91,7 +91,7 @@ sha2 = { version = "0.10.6", features = ["oid"] } smallvec = "1.8" socket2 = "0.4.7" tar = "=0.4.38" -tokio = { version = "=1.24.2", features = ["full"] } +tokio = { version = "=1.25.0", features = ["full"] } tokio-rustls = "0.23.3" tokio-tungstenite = "0.16.1" tokio-util = "=0.7.4" @@ -99,6 +99,9 @@ url = { version = "2.3.1", features = ["serde", "expose_internals"] } uuid = { version = "=1.1.2", features = ["v4"] } zstd = "=0.11.2" +# crypto +rsa = { version = "0.7.0", default-features = false, features = ["std", "pem"] } + # webgpu raw-window-handle = "0.5.0" wgpu-core = "0.15" @@ -124,6 +127,11 @@ incremental = true lto = true opt-level = 'z' # Optimize for size +# Build release with debug symbols: cargo build --profile=release-with-debug +[profile.release-with-debug] +inherits = "release" +debug = true + # NB: the `bench` and `release` profiles must remain EXACTLY the same. [profile.bench] codegen-units = 1 diff --git a/bench_util/Cargo.toml b/bench_util/Cargo.toml index 758a316015a886..e0e661c4db97d6 100644 --- a/bench_util/Cargo.toml +++ b/bench_util/Cargo.toml @@ -2,7 +2,7 @@ [package] name = "deno_bench_util" -version = "0.83.0" +version = "0.85.0" authors.workspace = true edition.workspace = true license.workspace = true diff --git a/bench_util/benches/utf8.rs b/bench_util/benches/utf8.rs index 2bbf439b679c4e..a8d6e20eb1c8cd 100644 --- a/bench_util/benches/utf8.rs +++ b/bench_util/benches/utf8.rs @@ -6,12 +6,15 @@ use deno_bench_util::bencher::benchmark_group; use deno_bench_util::bencher::Bencher; use deno_bench_util::BenchOptions; use deno_core::Extension; +use deno_core::ExtensionFileSource; +use deno_core::ExtensionFileSourceCode; fn setup() -> Vec { vec![Extension::builder("bench_setup") - .js(vec![( - "setup.js", - r#" + .js(vec![ExtensionFileSource { + specifier: "setup.js".to_string(), + code: ExtensionFileSourceCode::IncludedInBinary( + r#" const hello = "hello world\n"; const hello1k = hello.repeat(1e3); const hello1m = hello.repeat(1e6); @@ -19,7 +22,8 @@ fn setup() -> Vec { const hello1kEncoded = Deno.core.encode(hello1k); const hello1mEncoded = Deno.core.encode(hello1m); "#, - )]) + ), + }]) .build()] } diff --git a/bench_util/js_runtime.rs b/bench_util/js_runtime.rs index 7868c433ab4152..e45af2bdd03a29 100644 --- a/bench_util/js_runtime.rs +++ b/bench_util/js_runtime.rs @@ -10,6 +10,9 @@ use crate::profiling::is_profiling; pub fn create_js_runtime(setup: impl FnOnce() -> Vec) -> JsRuntime { JsRuntime::new(RuntimeOptions { extensions_with_js: setup(), + module_loader: Some(std::rc::Rc::new( + deno_core::InternalModuleLoader::default(), + )), ..Default::default() }) } @@ -101,9 +104,6 @@ pub fn bench_js_async_with( }; let looped = loop_code(inner_iters, src); let src = looped.as_ref(); - runtime - .execute_script("init", "Deno.core.initializeAsyncOps();") - .unwrap(); if is_profiling() { for _ in 0..opts.profiling_outer { tokio_runtime.block_on(inner_async(src, &mut runtime)); diff --git a/cli/Cargo.toml b/cli/Cargo.toml index 20218d77ed0f78..bfe2ed317fd0b8 100644 --- a/cli/Cargo.toml +++ b/cli/Cargo.toml @@ -3,7 +3,7 @@ [package] name = "denog" -version = "0.6.0" +version = "0.7.0" authors.workspace = true default-run = "denog" edition.workspace = true @@ -28,8 +28,8 @@ harness = false path = "./bench/lsp_bench_standalone.rs" [build-dependencies] -deno_runtime = { workspace = true, features = ["snapshot_from_snapshot"] } -deno_core.workspace = true +deno_runtime = { workspace = true, features = ["snapshot_from_snapshot", "include_js_files_for_snapshotting"] } +deno_core = { workspace = true, features = ["include_js_files_for_snapshotting"] } regex.workspace = true serde.workspace = true serde_json.workspace = true @@ -44,16 +44,17 @@ winres.workspace = true [dependencies] deno_ast = { workspace = true, features = ["bundler", "cjs", "codegen", "dep_graph", "module_specifier", "proposal", "react", "sourcemap", "transforms", "typescript", "view", "visit"] } -deno_core.workspace = true -deno_doc = "0.53.0" -deno_emit = "0.14.0" -deno_graph = "0.42.0" -deno_lint = { version = "0.37.0", features = ["docs"] } +deno_core = { workspace = true, features = ["include_js_files_for_snapshotting"] } +deno_doc = "0.57.0" +deno_emit = "0.16.0" +deno_graph = "=0.44.2" +deno_lint = { version = "0.40.0", features = ["docs"] } deno_lockfile.workspace = true -deno_runtime.workspace = true -deno_task_shell = "0.8.1" +deno_runtime = { workspace = true, features = ["dont_create_runtime_snapshot", "include_js_files_for_snapshotting"] } +deno_task_shell = "0.10.0" napi_sym.workspace = true +async-trait.workspace = true atty.workspace = true base32 = "=0.4.0" base64.workspace = true @@ -67,15 +68,15 @@ data-url.workspace = true dissimilar = "=1.0.4" dprint-plugin-json = "=0.17.0" dprint-plugin-markdown = "=0.15.2" -dprint-plugin-typescript = "=0.81.1" +dprint-plugin-typescript = "=0.83.0" encoding_rs.workspace = true env_logger = "=0.9.0" -eszip = "=0.33.0" +eszip = "=0.37.0" fancy-regex = "=0.10.0" flate2.workspace = true http.workspace = true import_map = "=0.15.0" -indexmap = "=1.9.2" +indexmap.workspace = true jsonc-parser = { version = "=0.21.0", features = ["serde"] } libc.workspace = true log = { workspace = true, features = ["serde"] } @@ -94,13 +95,13 @@ ring.workspace = true rustyline = { version = "=10.0.0", default-features = false, features = ["custom-bindings"] } rustyline-derive = "=0.7.0" secure_tempfile = { version = "=3.3.0", package = "tempfile" } # different name to discourage use in tests -semver.workspace = true serde.workspace = true serde_repr.workspace = true shell-escape = "=0.1.5" tar.workspace = true text-size = "=1.1.0" text_lines = "=0.6.0" +thiserror = "=1.0.38" tokio.workspace = true tokio-util.workspace = true tower-lsp = { version = "=0.17.0", features = ["proposed"] } diff --git a/cli/args/config_file.rs b/cli/args/config_file.rs index ce6bba4eacc44f..ede4af51a6c6cc 100644 --- a/cli/args/config_file.rs +++ b/cli/args/config_file.rs @@ -2,7 +2,6 @@ use crate::args::ConfigFlag; use crate::args::Flags; -use crate::args::TaskFlags; use crate::util::fs::canonicalize_path; use crate::util::path::specifier_parent; use crate::util::path::specifier_to_file_path; @@ -18,6 +17,8 @@ use deno_core::serde_json; use deno_core::serde_json::json; use deno_core::serde_json::Value; use deno_core::ModuleSpecifier; +use indexmap::IndexMap; +use std::borrow::Cow; use std::collections::BTreeMap; use std::collections::HashMap; use std::collections::HashSet; @@ -26,7 +27,7 @@ use std::path::Path; use std::path::PathBuf; pub type MaybeImportsResult = - Result)>>, AnyError>; + Result, AnyError>; #[derive(Hash)] pub struct JsxImportSourceConfig { @@ -483,10 +484,21 @@ pub struct ConfigFile { } impl ConfigFile { - pub fn discover(flags: &Flags) -> Result, AnyError> { + pub fn discover( + flags: &Flags, + cwd: &Path, + ) -> Result, AnyError> { match &flags.config_flag { ConfigFlag::Disabled => Ok(None), - ConfigFlag::Path(config_path) => Ok(Some(ConfigFile::read(config_path)?)), + ConfigFlag::Path(config_path) => { + let config_path = PathBuf::from(config_path); + let config_path = if config_path.is_absolute() { + config_path + } else { + cwd.join(config_path) + }; + Ok(Some(ConfigFile::read(&config_path)?)) + } ConfigFlag::Discover => { if let Some(config_path_args) = flags.config_path_args() { let mut checked = HashSet::new(); @@ -495,21 +507,8 @@ impl ConfigFile { return Ok(Some(cf)); } } - // attempt to resolve the config file from the task subcommand's - // `--cwd` when specified - if let crate::args::DenoSubcommand::Task(TaskFlags { - cwd: Some(path), - .. - }) = &flags.subcommand - { - let task_cwd = canonicalize_path(&PathBuf::from(path))?; - if let Some(path) = Self::discover_from(&task_cwd, &mut checked)? { - return Ok(Some(path)); - } - }; // From CWD walk up to root looking for deno.json or deno.jsonc - let cwd = std::env::current_dir()?; - Self::discover_from(&cwd, &mut checked) + Self::discover_from(cwd, &mut checked) } else { Ok(None) } @@ -524,6 +523,14 @@ impl ConfigFile { /// Filenames that Deno will recognize when discovering config. const CONFIG_FILE_NAMES: [&str; 2] = ["deno.json", "deno.jsonc"]; + // todo(dsherret): in the future, we should force all callers + // to provide a resolved path + let start = if start.is_absolute() { + Cow::Borrowed(start) + } else { + Cow::Owned(std::env::current_dir()?.join(start)) + }; + for ancestor in start.ancestors() { if checked.insert(ancestor.to_path_buf()) { for config_filename in CONFIG_FILE_NAMES { @@ -556,34 +563,29 @@ impl ConfigFile { Ok(None) } - pub fn read(path_ref: impl AsRef) -> Result { - let path = Path::new(path_ref.as_ref()); - let config_file = if path.is_absolute() { - path.to_path_buf() - } else { - std::env::current_dir()?.join(path_ref) - }; + pub fn read(config_path: &Path) -> Result { + debug_assert!(config_path.is_absolute()); // perf: Check if the config file exists before canonicalizing path. - if !config_file.exists() { + if !config_path.exists() { return Err( std::io::Error::new( std::io::ErrorKind::InvalidInput, format!( "Could not find the config file: {}", - config_file.to_string_lossy() + config_path.to_string_lossy() ), ) .into(), ); } - let config_path = canonicalize_path(&config_file).map_err(|_| { + let config_path = canonicalize_path(config_path).map_err(|_| { std::io::Error::new( std::io::ErrorKind::InvalidInput, format!( "Could not find the config file: {}", - config_file.to_string_lossy() + config_path.to_string_lossy() ), ) })?; @@ -746,9 +748,9 @@ impl ConfigFile { pub fn to_tasks_config( &self, - ) -> Result>, AnyError> { + ) -> Result>, AnyError> { if let Some(config) = self.json.tasks.clone() { - let tasks_config: BTreeMap = + let tasks_config: IndexMap = serde_json::from_value(config) .context("Failed to parse \"tasks\" configuration")?; Ok(Some(tasks_config)) @@ -765,7 +767,7 @@ impl ConfigFile { if let Some(value) = self.json.compiler_options.as_ref() { value } else { - return Ok(None); + return Ok(Vec::new()); }; let compiler_options: CompilerOptions = serde_json::from_value(compiler_options_value.clone())?; @@ -774,9 +776,9 @@ impl ConfigFile { } if !imports.is_empty() { let referrer = self.specifier.clone(); - Ok(Some(vec![(referrer, imports)])) + Ok(vec![deno_graph::ReferrerImports { referrer, imports }]) } else { - Ok(None) + Ok(Vec::new()) } } @@ -801,25 +803,22 @@ impl ConfigFile { pub fn resolve_tasks_config( &self, - ) -> Result, AnyError> { + ) -> Result, AnyError> { let maybe_tasks_config = self.to_tasks_config()?; - if let Some(tasks_config) = maybe_tasks_config { - for key in tasks_config.keys() { - if key.is_empty() { - bail!("Configuration file task names cannot be empty"); - } else if !key - .chars() - .all(|c| c.is_ascii_alphanumeric() || matches!(c, '_' | '-' | ':')) - { - bail!("Configuration file task names must only contain alpha-numeric characters, colons (:), underscores (_), or dashes (-). Task: {}", key); - } else if !key.chars().next().unwrap().is_ascii_alphabetic() { - bail!("Configuration file task names must start with an alphabetic character. Task: {}", key); - } + let tasks_config = maybe_tasks_config.unwrap_or_default(); + for key in tasks_config.keys() { + if key.is_empty() { + bail!("Configuration file task names cannot be empty"); + } else if !key + .chars() + .all(|c| c.is_ascii_alphanumeric() || matches!(c, '_' | '-' | ':')) + { + bail!("Configuration file task names must only contain alpha-numeric characters, colons (:), underscores (_), or dashes (-). Task: {}", key); + } else if !key.chars().next().unwrap().is_ascii_alphabetic() { + bail!("Configuration file task names must start with an alphabetic character. Task: {}", key); } - Ok(tasks_config) - } else { - bail!("No tasks found in configuration file") } + Ok(tasks_config) } pub fn to_lock_config(&self) -> Result, AnyError> { @@ -998,25 +997,17 @@ mod tests { use deno_core::serde_json::json; use pretty_assertions::assert_eq; - #[test] - fn read_config_file_relative() { - let config_file = - ConfigFile::read("tests/testdata/module_graph/tsconfig.json") - .expect("Failed to load config file"); - assert!(config_file.json.compiler_options.is_some()); - } - #[test] fn read_config_file_absolute() { let path = test_util::testdata_path().join("module_graph/tsconfig.json"); - let config_file = ConfigFile::read(path.to_str().unwrap()) - .expect("Failed to load config file"); + let config_file = ConfigFile::read(&path).unwrap(); assert!(config_file.json.compiler_options.is_some()); } #[test] fn include_config_path_on_error() { - let error = ConfigFile::read("404.json").err().unwrap(); + let path = test_util::testdata_path().join("404.json"); + let error = ConfigFile::read(&path).err().unwrap(); assert!(error.to_string().contains("404.json")); } @@ -1239,11 +1230,6 @@ mod tests { assert!(err.to_string().contains("Unable to parse config file")); } - #[test] - fn tasks_no_tasks() { - run_task_error_test(r#"{}"#, "No tasks found in configuration file"); - } - #[test] fn task_name_invalid_chars() { run_task_error_test( diff --git a/cli/args/flags.rs b/cli/args/flags.rs index 3222b0173347fe..6376e3c5c0dc41 100644 --- a/cli/args/flags.rs +++ b/cli/args/flags.rs @@ -20,6 +20,8 @@ use std::num::NonZeroUsize; use std::path::PathBuf; use std::str::FromStr; +use crate::util::fs::canonicalize_path; + use super::flags_allow_net; static LONG_VERSION: Lazy = Lazy::new(|| { @@ -55,6 +57,7 @@ pub struct FileFlags { pub struct BenchFlags { pub files: FileFlags, pub filter: Option, + pub json: bool, } #[derive(Clone, Debug, Eq, PartialEq)] @@ -193,7 +196,7 @@ impl RunFlags { #[derive(Clone, Debug, Eq, PartialEq)] pub struct TaskFlags { pub cwd: Option, - pub task: String, + pub task: Option, } #[derive(Clone, Debug, Default, Eq, PartialEq)] @@ -327,7 +330,7 @@ pub struct Flags { pub cached_only: bool, pub type_check_mode: TypeCheckMode, pub config_flag: ConfigFlag, - pub node_modules_dir: bool, + pub node_modules_dir: Option, pub coverage_dir: Option, pub enable_testing_features: bool, pub ignore: Vec, @@ -500,10 +503,56 @@ impl Flags { Some(vec![]) } } + Task(TaskFlags { + cwd: Some(path), .. + }) => { + // attempt to resolve the config file from the task subcommand's + // `--cwd` when specified + match canonicalize_path(&PathBuf::from(path)) { + Ok(path) => Some(vec![path]), + Err(_) => Some(vec![]), + } + } _ => Some(vec![]), } } + /// Extract path argument for `package.json` search paths. + /// If it returns Some(path), the `package.json` should be discovered + /// from the `path` dir. + /// If it returns None, the `package.json` file shouldn't be discovered at + /// all. + pub fn package_json_search_dir(&self) -> Option { + use DenoSubcommand::*; + + match &self.subcommand { + Run(RunFlags { script }) => { + let module_specifier = deno_core::resolve_url_or_path(script).ok()?; + if module_specifier.scheme() == "file" { + let p = module_specifier + .to_file_path() + .unwrap() + .parent()? + .to_owned(); + Some(p) + } else if module_specifier.scheme() == "npm" { + Some(std::env::current_dir().unwrap()) + } else { + None + } + } + Task(TaskFlags { cwd: Some(cwd), .. }) => { + deno_core::resolve_url_or_path(cwd) + .ok()? + .to_file_path() + .ok() + } + Task(_) | Check(_) | Coverage(_) | Cache(_) | Info(_) | Eval(_) + | Test(_) | Bench(_) => std::env::current_dir().ok(), + _ => None, + } + } + pub fn has_permission(&self) -> bool { self.allow_all || self.allow_hrtime @@ -543,6 +592,7 @@ static ENV_VARIABLES_HELP: &str = r#"ENVIRONMENT VARIABLES: DENO_DIR Set the cache directory DENO_INSTALL_ROOT Set deno install's output directory (defaults to $HOME/.deno/bin) + DENO_NO_PACKAGE_JSON Disables auto-resolution of package.json DENO_NO_PROMPT Set to disable permission prompts on access (alternative to passing --no-prompt on invocation) DENO_NO_UPDATE_CHECK Set to disable checking if a newer Deno version is @@ -708,6 +758,12 @@ fn clap_root(version: &str) -> Command { fn bench_subcommand<'a>() -> Command<'a> { runtime_args(Command::new("bench"), false, true, false) .trailing_var_arg(true) + .arg( + Arg::new("json") + .long("json") + .help("UNSTABLE: Output benchmark result in JSON format") + .takes_value(false), + ) .arg( Arg::new("ignore") .long("ignore") @@ -751,6 +807,7 @@ glob {*_,*.,}bench.{js,mjs,ts,mts,jsx,tsx}: fn bundle_subcommand<'a>() -> Command<'a> { compile_args(Command::new("bundle")) + .hide(true) .arg( Arg::new("source_file") .takes_value(true) @@ -1167,8 +1224,9 @@ Ignore formatting a file by adding an ignore comment at the top of the file: .arg(watch_arg(false)) .arg(no_clear_screen_arg()) .arg( - Arg::new("options-use-tabs") - .long("options-use-tabs") + Arg::new("use-tabs") + .long("use-tabs") + .alias("options-use-tabs") .takes_value(true) .min_values(0) .max_values(1) @@ -1177,32 +1235,33 @@ Ignore formatting a file by adding an ignore comment at the top of the file: .help("Use tabs instead of spaces for indentation. Defaults to false."), ) .arg( - Arg::new("options-line-width") - .long("options-line-width") + Arg::new("line-width") + .long("line-width") + .alias("options-line-width") .help("Define maximum line width. Defaults to 80.") .takes_value(true) .validator(|val: &str| match val.parse::() { Ok(_) => Ok(()), - Err(_) => { - Err("options-line-width should be a non zero integer".to_string()) - } + Err(_) => Err("line-width should be a non zero integer".to_string()), }), ) .arg( - Arg::new("options-indent-width") - .long("options-indent-width") + Arg::new("indent-width") + .long("indent-width") + .alias("options-indent-width") .help("Define indentation width. Defaults to 2.") .takes_value(true) .validator(|val: &str| match val.parse::() { Ok(_) => Ok(()), Err(_) => { - Err("options-indent-width should be a non zero integer".to_string()) + Err("indent-width should be a non zero integer".to_string()) } }), ) .arg( - Arg::new("options-single-quote") - .long("options-single-quote") + Arg::new("single-quote") + .long("single-quote") + .alias("options-single-quote") .min_values(0) .max_values(1) .takes_value(true) @@ -1211,15 +1270,17 @@ Ignore formatting a file by adding an ignore comment at the top of the file: .help("Use single quotes. Defaults to false."), ) .arg( - Arg::new("options-prose-wrap") - .long("options-prose-wrap") + Arg::new("prose-wrap") + .long("prose-wrap") + .alias("options-prose-wrap") .takes_value(true) .possible_values(["always", "never", "preserve"]) .help("Define how prose should be wrapped. Defaults to always."), ) .arg( - Arg::new("options-no-semicolons") - .long("options-no-semicolons") + Arg::new("no-semicolons") + .long("no-semicolons") + .alias("options-no-semicolons") .min_values(0) .max_values(1) .takes_value(true) @@ -1603,7 +1664,7 @@ fn test_subcommand<'a>() -> Command<'a> { .arg( Arg::new("doc") .long("doc") - .help("UNSTABLE: type-check code blocks") + .help("Type-check code blocks in JSDoc and Markdown") .takes_value(false), ) .arg( @@ -1638,7 +1699,7 @@ fn test_subcommand<'a>() -> Command<'a> { Arg::new("shuffle") .long("shuffle") .value_name("NUMBER") - .help("(UNSTABLE): Shuffle the order in which the tests are run") + .help("Shuffle the order in which the tests are run") .min_values(0) .max_values(1) .require_equals(true) @@ -1657,7 +1718,7 @@ fn test_subcommand<'a>() -> Command<'a> { .conflicts_with("inspect") .conflicts_with("inspect-wait") .conflicts_with("inspect-brk") - .help("UNSTABLE: Collect coverage profile data into DIR"), + .help("Collect coverage profile data into DIR"), ) .arg( Arg::new("parallel") @@ -2300,7 +2361,12 @@ fn no_npm_arg<'a>() -> Arg<'a> { fn local_npm_arg<'a>() -> Arg<'a> { Arg::new("node-modules-dir") .long("node-modules-dir") - .help("Creates a local node_modules folder") + .min_values(0) + .max_values(1) + .takes_value(true) + .require_equals(true) + .possible_values(["true", "false"]) + .help("Creates a local node_modules folder. This option is implicitly true when a package.json is auto-discovered.") } fn unsafely_ignore_certificate_errors_arg<'a>() -> Arg<'a> { @@ -2324,6 +2390,8 @@ fn bench_parse(flags: &mut Flags, matches: &clap::ArgMatches) { // interactive prompts, unless done by user code flags.no_prompt = true; + let json = matches.is_present("json"); + let ignore = match matches.values_of("ignore") { Some(f) => f.map(PathBuf::from).collect(), None => vec![], @@ -2358,6 +2426,7 @@ fn bench_parse(flags: &mut Flags, matches: &clap::ArgMatches) { flags.subcommand = DenoSubcommand::Bench(BenchFlags { files: FileFlags { include, ignore }, filter, + json, }); } @@ -2562,22 +2631,16 @@ fn fmt_parse(flags: &mut Flags, matches: &clap::ArgMatches) { }; let ext = matches.value_of("ext").unwrap().to_string(); - let use_tabs = optional_bool_parse(matches, "options-use-tabs"); - let line_width = if matches.is_present("options-line-width") { - Some( - matches - .value_of("options-line-width") - .unwrap() - .parse() - .unwrap(), - ) + let use_tabs = optional_bool_parse(matches, "use-tabs"); + let line_width = if matches.is_present("line-width") { + Some(matches.value_of("line-width").unwrap().parse().unwrap()) } else { None }; - let indent_width = if matches.is_present("options-indent-width") { + let indent_width = if matches.is_present("indent-width") { Some( matches - .value_of("options-indent-width") + .value_of("indent-width") .unwrap_or("true") .parse() .unwrap(), @@ -2585,11 +2648,9 @@ fn fmt_parse(flags: &mut Flags, matches: &clap::ArgMatches) { } else { None }; - let single_quote = optional_bool_parse(matches, "options-single-quote"); - let prose_wrap = matches - .value_of("options-prose-wrap") - .map(ToString::to_string); - let no_semicolons = optional_bool_parse(matches, "options-no-semicolons"); + let single_quote = optional_bool_parse(matches, "single-quote"); + let prose_wrap = matches.value_of("prose-wrap").map(ToString::to_string); + let no_semicolons = optional_bool_parse(matches, "no-semicolons"); flags.subcommand = DenoSubcommand::Fmt(FmtFlags { check: matches.is_present("check"), @@ -2759,7 +2820,7 @@ fn task_parse( let mut task_flags = TaskFlags { cwd: None, - task: String::new(), + task: None, }; if let Some(cwd) = matches.value_of("cwd") { @@ -2794,7 +2855,7 @@ fn task_parse( } if index < raw_args.len() { - task_flags.task = raw_args[index].to_string(); + task_flags.task = Some(raw_args[index].to_string()); index += 1; if index < raw_args.len() { @@ -3253,9 +3314,7 @@ fn no_npm_arg_parse(flags: &mut Flags, matches: &clap::ArgMatches) { } fn local_npm_args_parse(flags: &mut Flags, matches: &ArgMatches) { - if matches.is_present("node-modules-dir") { - flags.node_modules_dir = true; - } + flags.node_modules_dir = optional_bool_parse(matches, "node-modules-dir"); } fn inspect_arg_validate(val: &str) -> Result<(), String> { @@ -3821,15 +3880,15 @@ mod tests { let r = flags_from_vec(svec![ "deno", "fmt", - "--options-use-tabs", - "--options-line-width", + "--use-tabs", + "--line-width", "60", - "--options-indent-width", + "--indent-width", "4", - "--options-single-quote", - "--options-prose-wrap", + "--single-quote", + "--prose-wrap", "never", - "--options-no-semicolons", + "--no-semicolons", ]); assert_eq!( r.unwrap(), @@ -3856,9 +3915,9 @@ mod tests { let r = flags_from_vec(svec![ "deno", "fmt", - "--options-use-tabs=false", - "--options-single-quote=false", - "--options-no-semicolons=false", + "--use-tabs=false", + "--single-quote=false", + "--no-semicolons=false", ]); assert_eq!( r.unwrap(), @@ -5454,7 +5513,24 @@ mod tests { subcommand: DenoSubcommand::Run(RunFlags { script: "script.ts".to_string(), }), - node_modules_dir: true, + node_modules_dir: Some(true), + ..Flags::default() + } + ); + + let r = flags_from_vec(svec![ + "deno", + "run", + "--node-modules-dir=false", + "script.ts" + ]); + assert_eq!( + r.unwrap(), + Flags { + subcommand: DenoSubcommand::Run(RunFlags { + script: "script.ts".to_string(), + }), + node_modules_dir: Some(false), ..Flags::default() } ); @@ -6357,7 +6433,7 @@ mod tests { Flags { subcommand: DenoSubcommand::Task(TaskFlags { cwd: None, - task: "build".to_string(), + task: Some("build".to_string()), }), argv: svec!["hello", "world"], ..Flags::default() @@ -6370,7 +6446,7 @@ mod tests { Flags { subcommand: DenoSubcommand::Task(TaskFlags { cwd: None, - task: "build".to_string(), + task: Some("build".to_string()), }), ..Flags::default() } @@ -6382,7 +6458,7 @@ mod tests { Flags { subcommand: DenoSubcommand::Task(TaskFlags { cwd: Some("foo".to_string()), - task: "build".to_string(), + task: Some("build".to_string()), }), ..Flags::default() } @@ -6406,7 +6482,7 @@ mod tests { Flags { subcommand: DenoSubcommand::Task(TaskFlags { cwd: None, - task: "build".to_string(), + task: Some("build".to_string()), }), argv: svec!["--", "hello", "world"], config_flag: ConfigFlag::Path("deno.json".to_owned()), @@ -6422,7 +6498,7 @@ mod tests { Flags { subcommand: DenoSubcommand::Task(TaskFlags { cwd: Some("foo".to_string()), - task: "build".to_string(), + task: Some("build".to_string()), }), argv: svec!["--", "hello", "world"], ..Flags::default() @@ -6439,7 +6515,7 @@ mod tests { Flags { subcommand: DenoSubcommand::Task(TaskFlags { cwd: None, - task: "build".to_string(), + task: Some("build".to_string()), }), argv: svec!["--"], ..Flags::default() @@ -6455,7 +6531,7 @@ mod tests { Flags { subcommand: DenoSubcommand::Task(TaskFlags { cwd: None, - task: "build".to_string(), + task: Some("build".to_string()), }), argv: svec!["-1", "--test"], ..Flags::default() @@ -6471,7 +6547,7 @@ mod tests { Flags { subcommand: DenoSubcommand::Task(TaskFlags { cwd: None, - task: "build".to_string(), + task: Some("build".to_string()), }), argv: svec!["--test"], ..Flags::default() @@ -6489,7 +6565,7 @@ mod tests { Flags { subcommand: DenoSubcommand::Task(TaskFlags { cwd: None, - task: "build".to_string(), + task: Some("build".to_string()), }), unstable: true, log_level: Some(log::Level::Error), @@ -6506,7 +6582,7 @@ mod tests { Flags { subcommand: DenoSubcommand::Task(TaskFlags { cwd: None, - task: "".to_string(), + task: None, }), ..Flags::default() } @@ -6521,7 +6597,7 @@ mod tests { Flags { subcommand: DenoSubcommand::Task(TaskFlags { cwd: None, - task: "".to_string(), + task: None, }), config_flag: ConfigFlag::Path("deno.jsonc".to_string()), ..Flags::default() @@ -6537,7 +6613,7 @@ mod tests { Flags { subcommand: DenoSubcommand::Task(TaskFlags { cwd: None, - task: "".to_string(), + task: None, }), config_flag: ConfigFlag::Path("deno.jsonc".to_string()), ..Flags::default() @@ -6556,6 +6632,7 @@ mod tests { let r = flags_from_vec(svec![ "deno", "bench", + "--json", "--unstable", "--filter", "- foo", @@ -6573,6 +6650,7 @@ mod tests { Flags { subcommand: DenoSubcommand::Bench(BenchFlags { filter: Some("- foo".to_string()), + json: true, files: FileFlags { include: vec![PathBuf::from("dir1/"), PathBuf::from("dir2/")], ignore: vec![], @@ -6597,6 +6675,7 @@ mod tests { Flags { subcommand: DenoSubcommand::Bench(BenchFlags { filter: None, + json: false, files: FileFlags { include: vec![], ignore: vec![], diff --git a/cli/args/lockfile.rs b/cli/args/lockfile.rs index f9302743057ff8..1a3233c5a5d9e1 100644 --- a/cli/args/lockfile.rs +++ b/cli/args/lockfile.rs @@ -75,8 +75,8 @@ impl Into for NpmResolutionPackage { .collect(); NpmPackageLockfileInfo { - display_id: self.id.display(), - serialized_id: self.id.as_serialized(), + display_id: self.pkg_id.nv.to_string(), + serialized_id: self.pkg_id.as_serialized(), integrity: self.dist.integrity().to_string(), dependencies, } diff --git a/cli/args/mod.rs b/cli/args/mod.rs index d75f25d525831a..f198301b5c3118 100644 --- a/cli/args/mod.rs +++ b/cli/args/mod.rs @@ -5,9 +5,13 @@ mod flags; mod flags_allow_net; mod import_map; mod lockfile; +pub mod package_json; pub use self::import_map::resolve_import_map_from_specifier; use ::import_map::ImportMap; +use indexmap::IndexMap; + +use crate::npm::NpmResolutionSnapshot; pub use config_file::BenchConfig; pub use config_file::CompilerOptions; pub use config_file::ConfigFile; @@ -32,8 +36,11 @@ use deno_core::anyhow::Context; use deno_core::error::AnyError; use deno_core::normalize_path; use deno_core::parking_lot::Mutex; +use deno_core::serde_json; use deno_core::url::Url; +use deno_graph::npm::NpmPackageReq; use deno_runtime::colors; +use deno_runtime::deno_node::PackageJson; use deno_runtime::deno_tls::rustls; use deno_runtime::deno_tls::rustls::RootCertStore; use deno_runtime::deno_tls::rustls_native_certs::load_native_certs; @@ -41,17 +48,20 @@ use deno_runtime::deno_tls::rustls_pemfile; use deno_runtime::deno_tls::webpki_roots; use deno_runtime::inspector_server::InspectorServer; use deno_runtime::permissions::PermissionsOptions; +use once_cell::sync::Lazy; use std::collections::BTreeMap; use std::env; use std::io::BufReader; use std::io::Cursor; use std::net::SocketAddr; use std::num::NonZeroUsize; +use std::path::Path; use std::path::PathBuf; use std::sync::Arc; use crate::cache::DenoDir; use crate::file_fetcher::FileFetcher; +use crate::npm::NpmProcessState; use crate::util::fs::canonicalize_path_maybe_not_exists; use crate::version; @@ -105,6 +115,7 @@ impl CacheSetting { pub struct BenchOptions { pub files: FilesConfig, pub filter: Option, + pub json: bool, } impl BenchOptions { @@ -119,6 +130,7 @@ impl BenchOptions { Some(bench_flags.files), ), filter: bench_flags.filter, + json: bench_flags.json, }) } } @@ -373,6 +385,25 @@ fn resolve_lint_rules_options( } } +/// Discover `package.json` file. If `maybe_stop_at` is provided, we will stop +/// crawling up the directory tree at that path. +fn discover_package_json( + flags: &Flags, + maybe_stop_at: Option, +) -> Result, AnyError> { + // TODO(bartlomieju): discover for all subcommands, but print warnings that + // `package.json` is ignored in bundle/compile/etc. + + if let Some(package_json_dir) = flags.package_json_search_dir() { + let package_json_dir = + canonicalize_path_maybe_not_exists(&package_json_dir)?; + return package_json::discover_from(&package_json_dir, maybe_stop_at); + } + + log::debug!("No package.json file found"); + Ok(None) +} + /// Create and populate a root cert store based on the passed options and /// environment. pub fn get_root_cert_store( @@ -457,6 +488,18 @@ pub fn get_root_cert_store( Ok(root_cert_store) } +const RESOLUTION_STATE_ENV_VAR_NAME: &str = + "DENO_DONT_USE_INTERNAL_NODE_COMPAT_STATE"; + +static NPM_PROCESS_STATE: Lazy> = Lazy::new(|| { + let state = std::env::var(RESOLUTION_STATE_ENV_VAR_NAME).ok()?; + let state: NpmProcessState = serde_json::from_str(&state).ok()?; + // remove the environment variable so that sub processes + // that are spawned do not also use this. + std::env::remove_var(RESOLUTION_STATE_ENV_VAR_NAME); + Some(state) +}); + /// Overrides for the options below that when set will /// use these values over the values derived from the /// CLI flags or config file. @@ -471,7 +514,9 @@ pub struct CliOptions { // the source of the options is a detail the rest of the // application need not concern itself with, so keep these private flags: Flags, + maybe_node_modules_folder: Option, maybe_config_file: Option, + maybe_package_json: Option, maybe_lockfile: Option>>, overrides: CliOptionOverrides, } @@ -479,9 +524,11 @@ pub struct CliOptions { impl CliOptions { pub fn new( flags: Flags, + initial_cwd: PathBuf, maybe_config_file: Option, maybe_lockfile: Option, - ) -> Self { + maybe_package_json: Option, + ) -> Result { if let Some(insecure_allowlist) = flags.unsafely_ignore_certificate_errors.as_ref() { @@ -497,20 +544,59 @@ impl CliOptions { } let maybe_lockfile = maybe_lockfile.map(|l| Arc::new(Mutex::new(l))); + let maybe_node_modules_folder = resolve_local_node_modules_folder( + &initial_cwd, + &flags, + maybe_config_file.as_ref(), + maybe_package_json.as_ref(), + ) + .with_context(|| "Resolving node_modules folder.")?; - Self { + Ok(Self { + flags, maybe_config_file, maybe_lockfile, - flags, + maybe_package_json, + maybe_node_modules_folder, overrides: Default::default(), - } + }) } pub fn from_flags(flags: Flags) -> Result { - let maybe_config_file = ConfigFile::discover(&flags)?; + let initial_cwd = + std::env::current_dir().with_context(|| "Failed getting cwd.")?; + let maybe_config_file = ConfigFile::discover(&flags, &initial_cwd)?; + + let mut maybe_package_json = None; + if flags.config_flag == ConfigFlag::Disabled + || flags.no_npm + || has_flag_env_var("DENO_NO_PACKAGE_JSON") + { + log::debug!("package.json auto-discovery is disabled") + } else if let Some(config_file) = &maybe_config_file { + let specifier = config_file.specifier.clone(); + if specifier.scheme() == "file" { + let maybe_stop_at = specifier + .to_file_path() + .unwrap() + .parent() + .map(|p| p.to_path_buf()); + + maybe_package_json = discover_package_json(&flags, maybe_stop_at)?; + } + } else { + maybe_package_json = discover_package_json(&flags, None)?; + } + let maybe_lock_file = lockfile::discover(&flags, maybe_config_file.as_ref())?; - Ok(Self::new(flags, maybe_config_file, maybe_lock_file)) + Self::new( + flags, + initial_cwd, + maybe_config_file, + maybe_lock_file, + maybe_package_json, + ) } pub fn maybe_config_file_specifier(&self) -> Option { @@ -574,7 +660,7 @@ impl CliOptions { }; resolve_import_map_from_specifier( &import_map_specifier, - self.get_maybe_config_file().as_ref(), + self.maybe_config_file().as_ref(), file_fetcher, ) .await @@ -584,31 +670,41 @@ impl CliOptions { .map(Some) } + pub fn get_npm_resolution_snapshot(&self) -> Option { + if let Some(state) = &*NPM_PROCESS_STATE { + // TODO(bartlomieju): remove this clone + return Some(state.snapshot.clone()); + } + + None + } + + // If the main module should be treated as being in an npm package. + // This is triggered via a secret environment variable which is used + // for functionality like child_process.fork. Users should NOT depend + // on this functionality. + pub fn is_npm_main(&self) -> bool { + NPM_PROCESS_STATE.is_some() + } + /// Overrides the import map specifier to use. pub fn set_import_map_specifier(&mut self, path: Option) { self.overrides.import_map_specifier = Some(path); } - pub fn node_modules_dir(&self) -> bool { - self.flags.node_modules_dir + pub fn has_node_modules_dir(&self) -> bool { + self.maybe_node_modules_folder.is_some() } - /// Resolves the path to use for a local node_modules folder. - pub fn resolve_local_node_modules_folder( - &self, - ) -> Result, AnyError> { - let path = if !self.flags.node_modules_dir { - return Ok(None); - } else if let Some(config_path) = self - .maybe_config_file + pub fn node_modules_dir_path(&self) -> Option { + self.maybe_node_modules_folder.clone() + } + + pub fn node_modules_dir_specifier(&self) -> Option { + self + .maybe_node_modules_folder .as_ref() - .and_then(|c| c.specifier.to_file_path().ok()) - { - config_path.parent().unwrap().join("node_modules") - } else { - std::env::current_dir()?.join("node_modules") - }; - Ok(Some(canonicalize_path_maybe_not_exists(&path)?)) + .map(|path| ModuleSpecifier::from_directory_path(path).unwrap()) } pub fn resolve_root_cert_store(&self) -> Result { @@ -669,9 +765,11 @@ impl CliOptions { pub fn resolve_tasks_config( &self, - ) -> Result, AnyError> { + ) -> Result, AnyError> { if let Some(config_file) = &self.maybe_config_file { config_file.resolve_tasks_config() + } else if self.maybe_package_json.is_some() { + Ok(Default::default()) } else { bail!("No config file found") } @@ -690,23 +788,37 @@ impl CliOptions { /// Return any imports that should be brought into the scope of the module /// graph. pub fn to_maybe_imports(&self) -> MaybeImportsResult { - let mut imports = Vec::new(); if let Some(config_file) = &self.maybe_config_file { - if let Some(config_imports) = config_file.to_maybe_imports()? { - imports.extend(config_imports); - } - } - if imports.is_empty() { - Ok(None) + config_file.to_maybe_imports() } else { - Ok(Some(imports)) + Ok(Vec::new()) } } - pub fn get_maybe_config_file(&self) -> &Option { + pub fn maybe_config_file(&self) -> &Option { &self.maybe_config_file } + pub fn maybe_package_json(&self) -> &Option { + &self.maybe_package_json + } + + pub fn maybe_package_json_deps( + &self, + ) -> Result>, AnyError> { + if matches!( + self.flags.subcommand, + DenoSubcommand::Task(TaskFlags { task: None, .. }) + ) { + // don't have any package json dependencies for deno task with no args + Ok(None) + } else if let Some(package_json) = self.maybe_package_json() { + package_json::get_local_package_json_version_reqs(package_json).map(Some) + } else { + Ok(None) + } + } + pub fn resolve_fmt_options( &self, fmt_flags: FmtFlags, @@ -917,6 +1029,33 @@ impl CliOptions { } } +/// Resolves the path to use for a local node_modules folder. +fn resolve_local_node_modules_folder( + cwd: &Path, + flags: &Flags, + maybe_config_file: Option<&ConfigFile>, + maybe_package_json: Option<&PackageJson>, +) -> Result, AnyError> { + let path = if flags.node_modules_dir == Some(false) { + return Ok(None); + } else if let Some(state) = &*NPM_PROCESS_STATE { + return Ok(state.local_node_modules_path.as_ref().map(PathBuf::from)); + } else if let Some(package_json_path) = maybe_package_json.map(|c| &c.path) { + // always auto-discover the local_node_modules_folder when a package.json exists + package_json_path.parent().unwrap().join("node_modules") + } else if flags.node_modules_dir.is_none() { + return Ok(None); + } else if let Some(config_path) = maybe_config_file + .as_ref() + .and_then(|c| c.specifier.to_file_path().ok()) + { + config_path.parent().unwrap().join("node_modules") + } else { + cwd.join("node_modules") + }; + Ok(Some(canonicalize_path_maybe_not_exists(&path)?)) +} + fn resolve_import_map_specifier( maybe_import_map_path: Option<&str>, maybe_config_file: Option<&ConfigFile>, @@ -999,10 +1138,12 @@ fn resolve_files( /// Resolves the no_prompt value based on the cli flags and environment. pub fn resolve_no_prompt(flags: &Flags) -> bool { - flags.no_prompt || { - let value = env::var("DENO_NO_PROMPT"); - matches!(value.as_ref().map(|s| s.as_str()), Ok("1")) - } + flags.no_prompt || has_flag_env_var("DENO_NO_PROMPT") +} + +fn has_flag_env_var(name: &str) -> bool { + let value = env::var(name); + matches!(value.as_ref().map(|s| s.as_str()), Ok("1")) } #[cfg(test)] diff --git a/cli/args/package_json.rs b/cli/args/package_json.rs new file mode 100644 index 00000000000000..301d1b8bae71ae --- /dev/null +++ b/cli/args/package_json.rs @@ -0,0 +1,236 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. + +use std::collections::BTreeMap; +use std::collections::HashMap; +use std::path::Path; +use std::path::PathBuf; + +use deno_core::anyhow::anyhow; +use deno_core::anyhow::bail; +use deno_core::error::AnyError; +use deno_graph::npm::NpmPackageReq; +use deno_graph::semver::VersionReq; +use deno_runtime::deno_node::PackageJson; + +/// Gets the name and raw version constraint taking into account npm +/// package aliases. +pub fn parse_dep_entry_name_and_raw_version<'a>( + key: &'a str, + value: &'a str, +) -> Result<(&'a str, &'a str), AnyError> { + if let Some(package_and_version) = value.strip_prefix("npm:") { + if let Some((name, version)) = package_and_version.rsplit_once('@') { + Ok((name, version)) + } else { + bail!("could not find @ symbol in npm url '{}'", value); + } + } else { + Ok((key, value)) + } +} + +/// Gets an application level package.json's npm package requirements. +/// +/// Note that this function is not general purpose. It is specifically for +/// parsing the application level package.json that the user has control +/// over. This is a design limitation to allow mapping these dependency +/// entries to npm specifiers which can then be used in the resolver. +pub fn get_local_package_json_version_reqs( + package_json: &PackageJson, +) -> Result, AnyError> { + fn insert_deps( + deps: Option<&HashMap>, + result: &mut BTreeMap, + ) -> Result<(), AnyError> { + if let Some(deps) = deps { + for (key, value) in deps { + if value.starts_with("workspace:") + || value.starts_with("file:") + || value.starts_with("git:") + || value.starts_with("http:") + || value.starts_with("https:") + { + // skip these specifiers for now + continue; + } + let (name, version_req) = + parse_dep_entry_name_and_raw_version(key, value)?; + + let version_req = { + let result = VersionReq::parse_from_specifier(version_req); + match result { + Ok(version_req) => version_req, + Err(e) => { + let err = anyhow!("{:#}", e).context(concat!( + "Parsing version constraints in the application-level ", + "package.json is more strict at the moment" + )); + return Err(err); + } + } + }; + result.insert( + key.to_string(), + NpmPackageReq { + name: name.to_string(), + version_req: Some(version_req), + }, + ); + } + } + Ok(()) + } + + let deps = package_json.dependencies.as_ref(); + let dev_deps = package_json.dev_dependencies.as_ref(); + let mut result = BTreeMap::new(); + + // insert the dev dependencies first so the dependencies will + // take priority and overwrite any collisions + insert_deps(dev_deps, &mut result)?; + insert_deps(deps, &mut result)?; + + Ok(result) +} + +/// Attempts to discover the package.json file, maybe stopping when it +/// reaches the specified `maybe_stop_at` directory. +pub fn discover_from( + start: &Path, + maybe_stop_at: Option, +) -> Result, AnyError> { + const PACKAGE_JSON_NAME: &str = "package.json"; + + // note: ancestors() includes the `start` path + for ancestor in start.ancestors() { + let path = ancestor.join(PACKAGE_JSON_NAME); + + let source = match std::fs::read_to_string(&path) { + Ok(source) => source, + Err(err) if err.kind() == std::io::ErrorKind::NotFound => { + if let Some(stop_at) = maybe_stop_at.as_ref() { + if ancestor == stop_at { + break; + } + } + continue; + } + Err(err) => bail!( + "Error loading package.json at {}. {:#}", + path.display(), + err + ), + }; + + let package_json = PackageJson::load_from_string(path.clone(), source)?; + log::debug!("package.json file found at '{}'", path.display()); + return Ok(Some(package_json)); + } + + log::debug!("No package.json file found"); + Ok(None) +} + +#[cfg(test)] +mod test { + use pretty_assertions::assert_eq; + use std::path::PathBuf; + + use super::*; + + #[test] + fn test_parse_dep_entry_name_and_raw_version() { + let cases = [ + ("test", "^1.2", Ok(("test", "^1.2"))), + ("test", "1.x - 2.6", Ok(("test", "1.x - 2.6"))), + ("test", "npm:package@^1.2", Ok(("package", "^1.2"))), + ( + "test", + "npm:package", + Err("could not find @ symbol in npm url 'npm:package'"), + ), + ]; + for (key, value, expected_result) in cases { + let result = parse_dep_entry_name_and_raw_version(key, value); + match result { + Ok(result) => assert_eq!(result, expected_result.unwrap()), + Err(err) => assert_eq!(err.to_string(), expected_result.err().unwrap()), + } + } + } + + #[test] + fn test_get_local_package_json_version_reqs() { + let mut package_json = PackageJson::empty(PathBuf::from("/package.json")); + package_json.dependencies = Some(HashMap::from([ + ("test".to_string(), "^1.2".to_string()), + ("other".to_string(), "npm:package@~1.3".to_string()), + ])); + package_json.dev_dependencies = Some(HashMap::from([ + ("package_b".to_string(), "~2.2".to_string()), + // should be ignored + ("other".to_string(), "^3.2".to_string()), + ])); + let result = get_local_package_json_version_reqs(&package_json).unwrap(); + assert_eq!( + result, + BTreeMap::from([ + ( + "test".to_string(), + NpmPackageReq::from_str("test@^1.2").unwrap() + ), + ( + "other".to_string(), + NpmPackageReq::from_str("package@~1.3").unwrap() + ), + ( + "package_b".to_string(), + NpmPackageReq::from_str("package_b@~2.2").unwrap() + ) + ]) + ); + } + + #[test] + fn test_get_local_package_json_version_reqs_errors_non_npm_specifier() { + let mut package_json = PackageJson::empty(PathBuf::from("/package.json")); + package_json.dependencies = Some(HashMap::from([( + "test".to_string(), + "1.x - 1.3".to_string(), + )])); + let err = get_local_package_json_version_reqs(&package_json) + .err() + .unwrap(); + assert_eq!( + format!("{err:#}"), + concat!( + "Parsing version constraints in the application-level ", + "package.json is more strict at the moment: Invalid npm specifier ", + "version requirement. Unexpected character.\n", + " - 1.3\n", + " ~" + ) + ); + } + + #[test] + fn test_get_local_package_json_version_reqs_skips_certain_specifiers() { + let mut package_json = PackageJson::empty(PathBuf::from("/package.json")); + package_json.dependencies = Some(HashMap::from([ + ("test".to_string(), "1".to_string()), + ("work".to_string(), "workspace:1.1.1".to_string()), + ("file".to_string(), "file:something".to_string()), + ("git".to_string(), "git:something".to_string()), + ("http".to_string(), "http://something".to_string()), + ("https".to_string(), "https://something".to_string()), + ])); + let result = get_local_package_json_version_reqs(&package_json).unwrap(); + assert_eq!( + result, + BTreeMap::from([( + "test".to_string(), + NpmPackageReq::from_str("test@1").unwrap() + )]) + ); + } +} diff --git a/cli/build.rs b/cli/build.rs index 2104867b429550..e3511274c4d4d1 100644 --- a/cli/build.rs +++ b/cli/build.rs @@ -5,8 +5,11 @@ use std::env; use std::path::Path; use std::path::PathBuf; +use deno_core::include_js_files; use deno_core::snapshot_util::*; use deno_core::Extension; +use deno_core::ExtensionFileSource; +use deno_core::ExtensionFileSourceCode; use deno_runtime::deno_cache::SqliteBackedCache; use deno_runtime::permissions::PermissionsContainer; use deno_runtime::*; @@ -33,11 +36,7 @@ mod ts { specifier: String, } - pub fn create_compiler_snapshot( - snapshot_path: PathBuf, - files: Vec, - cwd: &Path, - ) { + pub fn create_compiler_snapshot(snapshot_path: PathBuf, cwd: &Path) { // libs that are being provided by op crates. let mut op_crate_libs = HashMap::new(); op_crate_libs.insert("deno.cache", deno_cache::get_declaration()); @@ -254,35 +253,52 @@ mod ts { } } + let tsc_extension = Extension::builder("deno_tsc") + .ops(vec![ + op_build_info::decl(), + op_cwd::decl(), + op_exists::decl(), + op_is_node_file::decl(), + op_load::decl(), + op_script_version::decl(), + ]) + .js(include_js_files! { + dir "tsc", + "00_typescript.js", + "99_main_compiler.js", + }) + .state(move |state| { + state.put(op_crate_libs.clone()); + state.put(build_libs.clone()); + state.put(path_dts.clone()); + + Ok(()) + }) + .build(); + create_snapshot(CreateSnapshotOptions { cargo_manifest_dir: env!("CARGO_MANIFEST_DIR"), snapshot_path, startup_snapshot: None, - extensions: vec![Extension::builder("deno_tsc") - .ops(vec![ - op_build_info::decl(), - op_cwd::decl(), - op_exists::decl(), - op_is_node_file::decl(), - op_load::decl(), - op_script_version::decl(), - ]) - .state(move |state| { - state.put(op_crate_libs.clone()); - state.put(build_libs.clone()); - state.put(path_dts.clone()); - - Ok(()) - }) - .build()], - extensions_with_js: vec![], - additional_files: files, + extensions: vec![], + extensions_with_js: vec![tsc_extension], + + // NOTE(bartlomieju): Compressing the TSC snapshot in debug build took + // ~45s on M1 MacBook Pro; without compression it took ~1s. + // Thus we're not not using compressed snapshot, trading off + // a lot of build time for some startup time in debug build. + #[cfg(debug_assertions)] + compression_cb: None, + + #[cfg(not(debug_assertions))] compression_cb: Some(Box::new(|vec, snapshot_slice| { + eprintln!("Compressing TSC snapshot..."); vec.extend_from_slice( &zstd::bulk::compress(snapshot_slice, 22) .expect("snapshot compression failed"), ); })), + snapshot_module_load_cb: None, }); } @@ -308,7 +324,7 @@ mod ts { } } -fn create_cli_snapshot(snapshot_path: PathBuf, files: Vec) { +fn create_cli_snapshot(snapshot_path: PathBuf) { let extensions: Vec = vec![ deno_webidl::init(), deno_console::init(), @@ -329,24 +345,42 @@ fn create_cli_snapshot(snapshot_path: PathBuf, files: Vec) { false, // No --unstable. ), deno_node::init::(None), // No --unstable. + deno_node::init_polyfill(), deno_ffi::init::(false), deno_net::init::( None, false, // No --unstable. None, ), - deno_napi::init::(false), + deno_napi::init::(), deno_http::init(), deno_flash::init::(false), // No --unstable deno_wsi::init(None), ]; + let mut esm_files = include_js_files!( + dir "js", + "40_testing.js", + ); + esm_files.push(ExtensionFileSource { + specifier: "runtime/js/99_main.js".to_string(), + code: ExtensionFileSourceCode::LoadedFromFsDuringSnapshot( + std::path::PathBuf::from(deno_runtime::js::PATH_FOR_99_MAIN_JS), + ), + }); + let extensions_with_js = vec![Extension::builder("cli") + // FIXME(bartlomieju): information about which extensions were + // already snapshotted is not preserved in the snapshot. This should be + // fixed, so we can reliably depend on that information. + // .dependencies(vec!["runtime"]) + .esm(esm_files) + .build()]; + create_snapshot(CreateSnapshotOptions { cargo_manifest_dir: env!("CARGO_MANIFEST_DIR"), snapshot_path, startup_snapshot: Some(deno_runtime::js::deno_isolate_init()), extensions, - extensions_with_js: vec![], - additional_files: files, + extensions_with_js, compression_cb: Some(Box::new(|vec, snapshot_slice| { lzzzz::lz4_hc::compress_to_vec( snapshot_slice, @@ -355,6 +389,7 @@ fn create_cli_snapshot(snapshot_path: PathBuf, files: Vec) { ) .expect("snapshot compression failed"); })), + snapshot_module_load_cb: None, }) } @@ -451,13 +486,10 @@ fn main() { let o = PathBuf::from(env::var_os("OUT_DIR").unwrap()); let compiler_snapshot_path = o.join("COMPILER_SNAPSHOT.bin"); - let js_files = get_js_files(env!("CARGO_MANIFEST_DIR"), "tsc"); - ts::create_compiler_snapshot(compiler_snapshot_path, js_files, &c); + ts::create_compiler_snapshot(compiler_snapshot_path, &c); let cli_snapshot_path = o.join("CLI_SNAPSHOT.bin"); - let mut js_files = get_js_files(env!("CARGO_MANIFEST_DIR"), "js"); - js_files.push(deno_runtime::js::get_99_main()); - create_cli_snapshot(cli_snapshot_path, js_files); + create_cli_snapshot(cli_snapshot_path); #[cfg(target_os = "windows")] { diff --git a/cli/cache/mod.rs b/cli/cache/mod.rs index c8fcaa22343965..a962538b72440c 100644 --- a/cli/cache/mod.rs +++ b/cli/cache/mod.rs @@ -2,7 +2,6 @@ use crate::errors::get_error_class_name; use crate::file_fetcher::FileFetcher; -use crate::npm; use deno_core::futures; use deno_core::futures::FutureExt; @@ -45,6 +44,8 @@ pub struct FetchCacher { dynamic_permissions: PermissionsContainer, file_fetcher: Arc, root_permissions: PermissionsContainer, + cache_info_enabled: bool, + maybe_local_node_modules_url: Option, } impl FetchCacher { @@ -53,19 +54,28 @@ impl FetchCacher { file_fetcher: Arc, root_permissions: PermissionsContainer, dynamic_permissions: PermissionsContainer, + maybe_local_node_modules_url: Option, ) -> Self { Self { emit_cache, dynamic_permissions, file_fetcher, root_permissions, + cache_info_enabled: false, + maybe_local_node_modules_url, } } + + /// The cache information takes a bit of time to fetch and it's + /// not always necessary. It should only be enabled for deno info. + pub fn enable_loading_cache_info(&mut self) { + self.cache_info_enabled = true; + } } impl Loader for FetchCacher { fn get_cache_info(&self, specifier: &ModuleSpecifier) -> Option { - if matches!(specifier.scheme(), "npm" | "node") { + if !self.cache_info_enabled { return None; } @@ -90,36 +100,22 @@ impl Loader for FetchCacher { specifier: &ModuleSpecifier, is_dynamic: bool, ) -> LoadFuture { - if specifier.scheme() == "npm" { - return Box::pin(futures::future::ready( - match npm::NpmPackageReference::from_specifier(specifier) { - Ok(_) => Ok(Some(deno_graph::source::LoadResponse::External { - specifier: specifier.clone(), - })), - Err(err) => Err(err), - }, - )); - } - - let specifier = - if let Some(module_name) = specifier.as_str().strip_prefix("node:") { - if module_name == "module" { - // the source code for "node:module" is built-in rather than - // being from deno_std like the other modules + if let Some(node_modules_url) = self.maybe_local_node_modules_url.as_ref() { + // The specifier might be in a completely different symlinked tree than + // what the resolved node_modules_url is in (ex. `/my-project-1/node_modules` + // symlinked to `/my-project-2/node_modules`), so first check if the path + // is in a node_modules dir to avoid needlessly canonicalizing, then compare + // against the canonicalized specifier. + if specifier.path().contains("/node_modules/") { + let specifier = + crate::node::resolve_specifier_into_node_modules(specifier); + if specifier.as_str().starts_with(node_modules_url.as_str()) { return Box::pin(futures::future::ready(Ok(Some( - deno_graph::source::LoadResponse::External { - specifier: specifier.clone(), - }, + LoadResponse::External { specifier }, )))); } - - match crate::node::resolve_builtin_node_module(module_name) { - Ok(specifier) => specifier, - Err(err) => return Box::pin(futures::future::ready(Err(err))), - } - } else { - specifier.clone() - }; + } + } let permissions = if is_dynamic { self.dynamic_permissions.clone() @@ -127,6 +123,7 @@ impl Loader for FetchCacher { self.root_permissions.clone() }; let file_fetcher = self.file_fetcher.clone(); + let specifier = specifier.clone(); async move { file_fetcher diff --git a/cli/cache/parsed_source.rs b/cli/cache/parsed_source.rs index 56b99f82b388b0..f6090a6a7a323c 100644 --- a/cli/cache/parsed_source.rs +++ b/cli/cache/parsed_source.rs @@ -76,19 +76,15 @@ impl ParsedSourceCache { } } - pub fn get_parsed_source_from_module( + pub fn get_parsed_source_from_esm_module( &self, - module: &deno_graph::Module, - ) -> Result, AnyError> { - if let Some(source) = &module.maybe_source { - Ok(Some(self.get_or_parse_module( - &module.specifier, - source.clone(), - module.media_type, - )?)) - } else { - Ok(None) - } + module: &deno_graph::EsmModule, + ) -> Result { + self.get_or_parse_module( + &module.specifier, + module.source.clone(), + module.media_type, + ) } /// Gets the matching `ParsedSource` from the cache @@ -192,7 +188,7 @@ impl ParsedSourceCacheModuleAnalyzer { let mut stmt = self.conn.prepare_cached(query)?; let mut rows = stmt.query(params![ &specifier.as_str(), - &media_type.to_string(), + serialize_media_type(media_type), &expected_source_hash, ])?; if let Some(row) = rows.next()? { @@ -219,7 +215,7 @@ impl ParsedSourceCacheModuleAnalyzer { let mut stmt = self.conn.prepare_cached(sql)?; stmt.execute(params![ specifier.as_str(), - &media_type.to_string(), + serialize_media_type(media_type), &source_hash, &serde_json::to_string(&module_info)?, ])?; @@ -227,6 +223,30 @@ impl ParsedSourceCacheModuleAnalyzer { } } +// todo(dsherret): change this to be stored as an integer next time +// the cache version is bumped +fn serialize_media_type(media_type: MediaType) -> &'static str { + use MediaType::*; + match media_type { + JavaScript => "1", + Jsx => "2", + Mjs => "3", + Cjs => "4", + TypeScript => "5", + Mts => "6", + Cts => "7", + Dts => "8", + Dmts => "9", + Dcts => "10", + Tsx => "11", + Json => "12", + Wasm => "13", + TsBuildInfo => "14", + SourceMap => "15", + Unknown => "16", + } +} + impl deno_graph::ModuleAnalyzer for ParsedSourceCacheModuleAnalyzer { fn analyze( &self, diff --git a/cli/deno_std.rs b/cli/deno_std.rs index 3ccf526d96593b..1805589cdddda2 100644 --- a/cli/deno_std.rs +++ b/cli/deno_std.rs @@ -5,7 +5,7 @@ use once_cell::sync::Lazy; // WARNING: Ensure this is the only deno_std version reference as this // is automatically updated by the version bump workflow. -static CURRENT_STD_URL_STR: &str = "https://deno.land/std@0.177.0/"; +static CURRENT_STD_URL_STR: &str = "https://deno.land/std@0.178.0/"; pub static CURRENT_STD_URL: Lazy = Lazy::new(|| Url::parse(CURRENT_STD_URL_STR).expect("invalid std url")); diff --git a/cli/errors.rs b/cli/errors.rs index 823d32da5833af..3ec17e8b29c6a5 100644 --- a/cli/errors.rs +++ b/cli/errors.rs @@ -25,15 +25,18 @@ fn get_diagnostic_class(_: &Diagnostic) -> &'static str { fn get_module_graph_error_class(err: &ModuleGraphError) -> &'static str { match err { - ModuleGraphError::LoadingErr(_, err) => get_error_class_name(err.as_ref()), + ModuleGraphError::LoadingErr(_, _, err) => { + get_error_class_name(err.as_ref()) + } ModuleGraphError::InvalidTypeAssertion { .. } => "SyntaxError", ModuleGraphError::ParseErr(_, diagnostic) => { get_diagnostic_class(diagnostic) } ModuleGraphError::ResolutionError(err) => get_resolution_error_class(err), - ModuleGraphError::UnsupportedMediaType(_, _) - | ModuleGraphError::UnsupportedImportAssertionType(_, _) => "TypeError", - ModuleGraphError::Missing(_) => "NotFound", + ModuleGraphError::UnsupportedMediaType { .. } + | ModuleGraphError::UnsupportedImportAssertionType { .. } => "TypeError", + ModuleGraphError::Missing(_, _) + | ModuleGraphError::MissingDynamic(_, _) => "NotFound", } } @@ -62,11 +65,13 @@ pub fn get_error_class_name(e: &AnyError) -> &'static str { .map(get_resolution_error_class) }) .unwrap_or_else(|| { - eprintln!( - "Error '{}' contains boxed error of unknown type:{}", - e, - e.chain().map(|e| format!("\n {e:?}")).collect::() - ); + if cfg!(debug) { + log::warn!( + "Error '{}' contains boxed error of unknown type:{}", + e, + e.chain().map(|e| format!("\n {e:?}")).collect::() + ); + } "Error" }) } diff --git a/cli/graph_util.rs b/cli/graph_util.rs index 16ee0145f87bd7..31645824d8bc1c 100644 --- a/cli/graph_util.rs +++ b/cli/graph_util.rs @@ -1,5 +1,6 @@ // Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. +use crate::args::CliOptions; use crate::args::Lockfile; use crate::args::TsConfigType; use crate::args::TsTypeLib; @@ -8,11 +9,9 @@ use crate::cache; use crate::cache::TypeCheckCache; use crate::colors; use crate::errors::get_error_class_name; -use crate::npm::resolve_graph_npm_info; -use crate::npm::NpmPackageReference; -use crate::npm::NpmPackageReq; +use crate::npm::NpmPackageResolver; use crate::proc_state::ProcState; -use crate::resolver::CliResolver; +use crate::resolver::CliGraphResolver; use crate::tools::check; use deno_core::anyhow::bail; @@ -20,487 +19,132 @@ use deno_core::error::custom_error; use deno_core::error::AnyError; use deno_core::parking_lot::RwLock; use deno_core::ModuleSpecifier; -use deno_graph::Dependency; -use deno_graph::GraphImport; -use deno_graph::MediaType; +use deno_graph::Module; use deno_graph::ModuleGraph; use deno_graph::ModuleGraphError; -use deno_graph::ModuleKind; -use deno_graph::Range; use deno_graph::ResolutionError; -use deno_graph::Resolved; use deno_graph::SpecifierError; use deno_runtime::permissions::PermissionsContainer; use import_map::ImportMapError; -use std::collections::BTreeMap; use std::collections::HashMap; use std::collections::HashSet; -use std::collections::VecDeque; use std::sync::Arc; - -#[derive(Debug, Clone)] -#[allow(clippy::large_enum_variant)] -pub enum ModuleEntry { - Module { - code: Arc, - dependencies: BTreeMap, - media_type: MediaType, - /// A set of type libs that the module has passed a type check with this - /// session. This would consist of window, worker or both. - checked_libs: HashSet, - maybe_types: Option, - }, - Error(ModuleGraphError), - Redirect(ModuleSpecifier), +use tokio::sync::Semaphore; +use tokio::sync::SemaphorePermit; + +#[derive(Clone, Copy)] +pub struct GraphValidOptions { + pub check_js: bool, + pub follow_type_only: bool, + pub is_vendoring: bool, } -/// Composes data from potentially many `ModuleGraph`s. -#[derive(Debug, Default)] -pub struct GraphData { - modules: HashMap, - /// Specifiers that are built-in or external. - external_specifiers: HashSet, - npm_packages: Vec, - has_node_builtin_specifier: bool, - /// Map of first known referrer locations for each module. Used to enhance - /// error messages. - referrer_map: HashMap>, - graph_imports: Vec, +/// Check if `roots` and their deps are available. Returns `Ok(())` if +/// so. Returns `Err(_)` if there is a known module graph or resolution +/// error statically reachable from `roots` and not a dynamic import. +pub fn graph_valid_with_cli_options( + graph: &ModuleGraph, + roots: &[ModuleSpecifier], + options: &CliOptions, +) -> Result<(), AnyError> { + graph_valid( + graph, + roots, + GraphValidOptions { + is_vendoring: false, + follow_type_only: options.type_check_mode() != TypeCheckMode::None, + check_js: options.check_js(), + }, + ) } -impl GraphData { - /// Store data from `graph` into `self`. - pub fn add_graph(&mut self, graph: &ModuleGraph) { - for graph_import in &graph.imports { - for dep in graph_import.dependencies.values() { - for resolved in [&dep.maybe_code, &dep.maybe_type] { - if let Resolved::Ok { - specifier, range, .. - } = resolved - { - let entry = self.referrer_map.entry(specifier.clone()); - entry.or_insert_with(|| range.clone()); - } - } - } - self.graph_imports.push(graph_import.clone()) - } - - let mut has_npm_specifier_in_graph = false; - - for (specifier, result) in graph.specifiers() { - if self.modules.contains_key(specifier) { - continue; - } - - if !self.has_node_builtin_specifier && specifier.scheme() == "node" { - self.has_node_builtin_specifier = true; - } - - if let Some(found) = graph.redirects.get(specifier) { - let module_entry = ModuleEntry::Redirect(found.clone()); - self.modules.insert(specifier.clone(), module_entry); - continue; - } - - match result { - Ok((_, module_kind, media_type)) => { - if module_kind == ModuleKind::External { - if !has_npm_specifier_in_graph - && NpmPackageReference::from_specifier(specifier).is_ok() - { - has_npm_specifier_in_graph = true; - } - self.external_specifiers.insert(specifier.clone()); - continue; // ignore npm and node specifiers - } +/// Check if `roots` and their deps are available. Returns `Ok(())` if +/// so. Returns `Err(_)` if there is a known module graph or resolution +/// error statically reachable from `roots`. +/// +/// It is preferable to use this over using deno_graph's API directly +/// because it will have enhanced error message information specifically +/// for the CLI. +pub fn graph_valid( + graph: &ModuleGraph, + roots: &[ModuleSpecifier], + options: GraphValidOptions, +) -> Result<(), AnyError> { + let mut errors = graph + .walk( + roots, + deno_graph::WalkOptions { + check_js: options.check_js, + follow_type_only: options.follow_type_only, + follow_dynamic: options.is_vendoring, + }, + ) + .errors() + .flat_map(|error| { + let is_root = match &error { + ModuleGraphError::ResolutionError(_) => false, + _ => roots.contains(error.specifier()), + }; + let mut message = if let ModuleGraphError::ResolutionError(err) = &error { + enhanced_resolution_error_message(err) + } else { + format!("{error}") + }; - let module = graph.get(specifier).unwrap(); - let code = match &module.maybe_source { - Some(source) => source.clone(), - None => continue, - }; - let maybe_types = module - .maybe_types_dependency - .as_ref() - .map(|(_, r)| r.clone()); - if let Some(Resolved::Ok { - specifier, range, .. - }) = &maybe_types - { - let specifier = graph.redirects.get(specifier).unwrap_or(specifier); - let entry = self.referrer_map.entry(specifier.clone()); - entry.or_insert_with(|| range.clone()); - } - for dep in module.dependencies.values() { - #[allow(clippy::manual_flatten)] - for resolved in [&dep.maybe_code, &dep.maybe_type] { - if let Resolved::Ok { - specifier, range, .. - } = resolved - { - let specifier = - graph.redirects.get(specifier).unwrap_or(specifier); - let entry = self.referrer_map.entry(specifier.clone()); - entry.or_insert_with(|| range.clone()); - } - } - } - let module_entry = ModuleEntry::Module { - code, - dependencies: module.dependencies.clone(), - media_type, - checked_libs: Default::default(), - maybe_types, - }; - self.modules.insert(specifier.clone(), module_entry); - } - Err(error) => { - let module_entry = ModuleEntry::Error(error.clone()); - self.modules.insert(specifier.clone(), module_entry); + if let Some(range) = error.maybe_range() { + if !is_root && !range.specifier.as_str().contains("/$deno$eval") { + message.push_str(&format!("\n at {range}")); } } - } - if has_npm_specifier_in_graph { - self - .npm_packages - .extend(resolve_graph_npm_info(graph).package_reqs); - } - } - - pub fn entries( - &self, - ) -> impl Iterator { - self.modules.iter() - } - - /// Gets if the graph had a "node:" specifier. - pub fn has_node_builtin_specifier(&self) -> bool { - self.has_node_builtin_specifier - } - - /// Gets the npm package requirements from all the encountered graphs - /// in the order that they should be resolved. - pub fn npm_package_reqs(&self) -> &Vec { - &self.npm_packages - } - - /// Walk dependencies from `roots` and return every encountered specifier. - /// Return `None` if any modules are not known. - pub fn walk<'a>( - &'a self, - roots: &[ModuleSpecifier], - follow_dynamic: bool, - follow_type_only: bool, - check_js: bool, - ) -> Option> { - let mut result = HashMap::<&'a ModuleSpecifier, &'a ModuleEntry>::new(); - let mut seen = HashSet::<&ModuleSpecifier>::new(); - let mut visiting = VecDeque::<&ModuleSpecifier>::new(); - for root in roots { - seen.insert(root); - visiting.push_back(root); - } - for (_, dep) in self.graph_imports.iter().flat_map(|i| &i.dependencies) { - let mut resolutions = vec![&dep.maybe_code]; - if follow_type_only { - resolutions.push(&dep.maybe_type); - } - #[allow(clippy::manual_flatten)] - for resolved in resolutions { - if let Resolved::Ok { specifier, .. } = resolved { - if !seen.contains(specifier) { - seen.insert(specifier); - visiting.push_front(specifier); - } - } - } - } - while let Some(specifier) = visiting.pop_front() { - let (specifier, entry) = match self.modules.get_key_value(specifier) { - Some(pair) => pair, - None => { - if self.external_specifiers.contains(specifier) { - continue; - } + if options.is_vendoring { + // warn about failing dynamic imports when vendoring, but don't fail completely + if matches!(error, ModuleGraphError::MissingDynamic(_, _)) { + log::warn!("Ignoring: {:#}", message); return None; } - }; - result.insert(specifier, entry); - match entry { - ModuleEntry::Module { - dependencies, - maybe_types, - media_type, - .. - } => { - let check_types = (check_js - || !matches!( - media_type, - MediaType::JavaScript - | MediaType::Mjs - | MediaType::Cjs - | MediaType::Jsx - )) - && follow_type_only; - if check_types { - if let Some(Resolved::Ok { specifier, .. }) = maybe_types { - if !seen.contains(specifier) { - seen.insert(specifier); - visiting.push_front(specifier); - } - } - } - for (dep_specifier, dep) in dependencies.iter().rev() { - // todo(dsherret): ideally there would be a way to skip external dependencies - // in the graph here rather than specifically npm package references - if NpmPackageReference::from_str(dep_specifier).is_ok() { - continue; - } - - if !dep.is_dynamic || follow_dynamic { - let mut resolutions = vec![&dep.maybe_code]; - if check_types { - resolutions.push(&dep.maybe_type); - } - #[allow(clippy::manual_flatten)] - for resolved in resolutions { - if let Resolved::Ok { specifier, .. } = resolved { - if !seen.contains(specifier) { - seen.insert(specifier); - visiting.push_front(specifier); - } - } - } - } - } - } - ModuleEntry::Error(_) => {} - ModuleEntry::Redirect(specifier) => { - if !seen.contains(specifier) { - seen.insert(specifier); - visiting.push_front(specifier); - } - } - } - } - Some(result) - } - - /// Clone part of `self`, containing only modules which are dependencies of - /// `roots`. Returns `None` if any roots are not known. - pub fn graph_segment(&self, roots: &[ModuleSpecifier]) -> Option { - let mut modules = HashMap::new(); - let mut referrer_map = HashMap::new(); - let entries = match self.walk(roots, true, true, true) { - Some(entries) => entries, - None => return None, - }; - for (specifier, module_entry) in entries { - modules.insert(specifier.clone(), module_entry.clone()); - if let Some(referrer) = self.referrer_map.get(specifier) { - referrer_map.insert(specifier.clone(), referrer.clone()); - } - } - Some(Self { - modules, - external_specifiers: self.external_specifiers.clone(), - has_node_builtin_specifier: self.has_node_builtin_specifier, - npm_packages: self.npm_packages.clone(), - referrer_map, - graph_imports: self.graph_imports.to_vec(), - }) - } - /// Check if `roots` and their deps are available. Returns `Some(Ok(()))` if - /// so. Returns `Some(Err(_))` if there is a known module graph or resolution - /// error statically reachable from `roots`. Returns `None` if any modules are - /// not known. - pub fn check( - &self, - roots: &[ModuleSpecifier], - follow_type_only: bool, - check_js: bool, - ) -> Option> { - let entries = match self.walk(roots, false, follow_type_only, check_js) { - Some(entries) => entries, - None => return None, - }; - for (specifier, module_entry) in entries { - match module_entry { - ModuleEntry::Module { - dependencies, - maybe_types, - media_type, - .. - } => { - let check_types = (check_js - || !matches!( - media_type, - MediaType::JavaScript - | MediaType::Mjs - | MediaType::Cjs - | MediaType::Jsx - )) - && follow_type_only; - if check_types { - if let Some(Resolved::Err(error)) = maybe_types { - let range = error.range(); - return Some(handle_check_error( - error.clone().into(), - Some(range), - )); - } - } - for (_, dep) in dependencies.iter() { - if !dep.is_dynamic { - let mut resolutions = vec![&dep.maybe_code]; - if check_types { - resolutions.push(&dep.maybe_type); - } - #[allow(clippy::manual_flatten)] - for resolved in resolutions { - if let Resolved::Err(error) = resolved { - let range = error.range(); - return Some(handle_check_error( - error.clone().into(), - Some(range), - )); - } - } - } + // ignore invalid downgrades and invalid local imports when vendoring + if let ModuleGraphError::ResolutionError(err) = &error { + if matches!( + err, + ResolutionError::InvalidDowngrade { .. } + | ResolutionError::InvalidLocalImport { .. } + ) { + return None; } } - ModuleEntry::Error(error) => { - let maybe_range = if roots.contains(specifier) { - None - } else { - self.referrer_map.get(specifier) - }; - return Some(handle_check_error( - error.clone().into(), - maybe_range.map(|r| &**r), - )); - } - _ => {} - } - } - Some(Ok(())) - } - - /// Mark `roots` and all of their dependencies as type checked under `lib`. - /// Assumes that all of those modules are known. - pub fn set_type_checked( - &mut self, - roots: &[ModuleSpecifier], - lib: TsTypeLib, - ) { - let specifiers: Vec = - match self.walk(roots, true, true, true) { - Some(entries) => entries.into_keys().cloned().collect(), - None => unreachable!("contains module not in graph data"), - }; - for specifier in specifiers { - if let ModuleEntry::Module { checked_libs, .. } = - self.modules.get_mut(&specifier).unwrap() - { - checked_libs.insert(lib); } - } - } - /// Check if `roots` are all marked as type checked under `lib`. - pub fn is_type_checked( - &self, - roots: &[ModuleSpecifier], - lib: &TsTypeLib, - ) -> bool { - roots.iter().all(|r| { - let found = self.follow_redirect(r); - match self.modules.get(&found) { - Some(ModuleEntry::Module { checked_libs, .. }) => { - checked_libs.contains(lib) - } - _ => false, - } - }) - } - - /// If `specifier` is known and a redirect, return the found specifier. - /// Otherwise return `specifier`. - pub fn follow_redirect( - &self, - specifier: &ModuleSpecifier, - ) -> ModuleSpecifier { - match self.modules.get(specifier) { - Some(ModuleEntry::Redirect(s)) => s.clone(), - _ => specifier.clone(), - } - } - - pub fn get<'a>( - &'a self, - specifier: &ModuleSpecifier, - ) -> Option<&'a ModuleEntry> { - self.modules.get(specifier) - } - - /// Get the dependencies of a module or graph import. - pub fn get_dependencies<'a>( - &'a self, - specifier: &ModuleSpecifier, - ) -> Option<&'a BTreeMap> { - let specifier = self.follow_redirect(specifier); - if let Some(ModuleEntry::Module { dependencies, .. }) = self.get(&specifier) - { - return Some(dependencies); - } - if let Some(graph_import) = - self.graph_imports.iter().find(|i| i.referrer == specifier) - { - return Some(&graph_import.dependencies); - } - None - } -} - -impl From<&ModuleGraph> for GraphData { - fn from(graph: &ModuleGraph) -> Self { - let mut graph_data = GraphData::default(); - graph_data.add_graph(graph); - graph_data + Some(custom_error(get_error_class_name(&error.into()), message)) + }); + if let Some(error) = errors.next() { + Err(error) + } else { + Ok(()) } } -/// Like `graph.valid()`, but enhanced with referrer info. -pub fn graph_valid( - graph: &ModuleGraph, - follow_type_only: bool, - check_js: bool, -) -> Result<(), AnyError> { - GraphData::from(graph) - .check(&graph.roots, follow_type_only, check_js) - .unwrap() -} - /// Checks the lockfile against the graph and and exits on errors. pub fn graph_lock_or_exit(graph: &ModuleGraph, lockfile: &mut Lockfile) { for module in graph.modules() { - if let Some(source) = &module.maybe_source { - if !lockfile.check_or_insert_remote(module.specifier.as_str(), source) { - let err = format!( - concat!( - "The source code is invalid, as it does not match the expected hash in the lock file.\n", - " Specifier: {}\n", - " Lock file: {}", - ), - module.specifier, - lockfile.filename.display(), - ); - log::error!("{} {}", colors::red("error:"), err); - std::process::exit(10); - } + let source = match module { + Module::Esm(module) => &module.source, + Module::Json(module) => &module.source, + Module::Node(_) | Module::Npm(_) | Module::External(_) => continue, + }; + if !lockfile.check_or_insert_remote(module.specifier().as_str(), source) { + let err = format!( + concat!( + "The source code is invalid, as it does not match the expected hash in the lock file.\n", + " Specifier: {}\n", + " Lock file: {}", + ), + module.specifier(), + lockfile.filename.display(), + ); + log::error!("{} {}", colors::red("error:"), err); + std::process::exit(10); } } } @@ -514,43 +158,39 @@ pub async fn create_graph_and_maybe_check( ps.file_fetcher.clone(), PermissionsContainer::allow_all(), PermissionsContainer::allow_all(), + ps.options.node_modules_dir_specifier(), ); let maybe_imports = ps.options.to_maybe_imports()?; - let maybe_cli_resolver = CliResolver::maybe_new( + let cli_resolver = CliGraphResolver::new( ps.options.to_maybe_jsx_import_source_config(), ps.maybe_import_map.clone(), + ps.options.no_npm(), + ps.npm_resolver.api().clone(), + ps.npm_resolver.resolution().clone(), + ps.package_json_deps_installer.clone(), ); - let maybe_graph_resolver = - maybe_cli_resolver.as_ref().map(|r| r.as_graph_resolver()); + let graph_resolver = cli_resolver.as_graph_resolver(); + let graph_npm_resolver = cli_resolver.as_graph_npm_resolver(); let analyzer = ps.parsed_source_cache.as_analyzer(); - let graph = Arc::new( - deno_graph::create_graph( - vec![root], - &mut cache, - deno_graph::GraphOptions { - is_dynamic: false, - imports: maybe_imports, - resolver: maybe_graph_resolver, - module_analyzer: Some(&*analyzer), - reporter: None, - }, - ) - .await, - ); - - let check_js = ps.options.check_js(); - let mut graph_data = GraphData::default(); - graph_data.add_graph(&graph); - graph_data - .check( - &graph.roots, - ps.options.type_check_mode() != TypeCheckMode::None, - check_js, - ) - .unwrap()?; - ps.npm_resolver - .add_package_reqs(graph_data.npm_package_reqs().clone()) - .await?; + let mut graph = ModuleGraph::default(); + build_graph_with_npm_resolution( + &mut graph, + &ps.npm_resolver, + vec![root], + &mut cache, + deno_graph::BuildOptions { + is_dynamic: false, + imports: maybe_imports, + resolver: Some(graph_resolver), + npm_resolver: Some(graph_npm_resolver), + module_analyzer: Some(&*analyzer), + reporter: None, + }, + ) + .await?; + + graph_valid_with_cli_options(&graph, &graph.roots, &ps.options)?; + let graph = Arc::new(graph); if let Some(lockfile) = &ps.lockfile { graph_lock_or_exit(&graph, &mut lockfile.lock()); } @@ -558,7 +198,7 @@ pub async fn create_graph_and_maybe_check( if ps.options.type_check_mode() != TypeCheckMode::None { // node built-in specifiers use the @types/node package to determine // types, so inject that now after the lockfile has been written - if graph_data.has_node_builtin_specifier() { + if graph.has_node_specifier { ps.npm_resolver .inject_synthetic_types_node_package() .await?; @@ -574,8 +214,7 @@ pub async fn create_graph_and_maybe_check( let maybe_config_specifier = ps.options.maybe_config_file_specifier(); let cache = TypeCheckCache::new(&ps.dir.type_checking_cache_db_file_path()); let check_result = check::check( - &graph.roots, - Arc::new(RwLock::new(graph_data)), + graph.clone(), &cache, &ps.npm_resolver, check::CheckOptions { @@ -596,42 +235,37 @@ pub async fn create_graph_and_maybe_check( Ok(graph) } -pub fn error_for_any_npm_specifier( - graph: &deno_graph::ModuleGraph, +pub async fn build_graph_with_npm_resolution<'a>( + graph: &mut ModuleGraph, + npm_resolver: &NpmPackageResolver, + roots: Vec, + loader: &mut dyn deno_graph::source::Loader, + options: deno_graph::BuildOptions<'a>, ) -> Result<(), AnyError> { - let first_npm_specifier = graph - .specifiers() - .filter_map(|(_, r)| match r { - Ok((specifier, kind, _)) if kind == deno_graph::ModuleKind::External => { - Some(specifier) - } - _ => None, - }) - .next(); - if let Some(npm_specifier) = first_npm_specifier { - bail!("npm specifiers have not yet been implemented for this sub command (https://github.com/denoland/deno/issues/15960). Found: {}", npm_specifier) - } else { - Ok(()) - } + graph.build(roots, loader, options).await; + + // resolve the dependencies of any pending dependencies + // that were inserted by building the graph + npm_resolver.resolve_pending().await?; + + Ok(()) } -fn handle_check_error( - error: AnyError, - maybe_range: Option<&deno_graph::Range>, +pub fn error_for_any_npm_specifier( + graph: &ModuleGraph, ) -> Result<(), AnyError> { - let mut message = if let Some(err) = error.downcast_ref::() { - enhanced_resolution_error_message(err) - } else { - format!("{error}") - }; - - if let Some(range) = maybe_range { - if !range.specifier.as_str().contains("$deno") { - message.push_str(&format!("\n at {range}")); + for module in graph.modules() { + match module { + Module::Npm(module) => { + bail!("npm specifiers have not yet been implemented for this sub command (https://github.com/denoland/deno/issues/15960). Found: {}", module.specifier) + } + Module::Node(module) => { + bail!("Node specifiers have not yet been implemented for this sub command (https://github.com/denoland/deno/issues/15960). Found: node:{}", module.module_name) + } + Module::Esm(_) | Module::Json(_) | Module::External(_) => {} } } - - Err(custom_error(get_error_class_name(&error), message)) + Ok(()) } /// Adds more explanatory information to a resolution error. @@ -677,6 +311,111 @@ fn get_resolution_error_bare_specifier( } } +#[derive(Default, Debug)] +struct GraphData { + graph: Arc, + checked_libs: HashMap>, +} + +/// Holds the `ModuleGraph` and what parts of it are type checked. +#[derive(Clone)] +pub struct ModuleGraphContainer { + update_semaphore: Arc, + graph_data: Arc>, +} + +impl Default for ModuleGraphContainer { + fn default() -> Self { + Self { + update_semaphore: Arc::new(Semaphore::new(1)), + graph_data: Default::default(), + } + } +} + +impl ModuleGraphContainer { + /// Acquires a permit to modify the module graph without other code + /// having the chance to modify it. In the meantime, other code may + /// still read from the existing module graph. + pub async fn acquire_update_permit(&self) -> ModuleGraphUpdatePermit { + let permit = self.update_semaphore.acquire().await.unwrap(); + ModuleGraphUpdatePermit { + permit, + graph_data: self.graph_data.clone(), + graph: (*self.graph_data.read().graph).clone(), + } + } + + pub fn graph(&self) -> Arc { + self.graph_data.read().graph.clone() + } + + /// Mark `roots` and all of their dependencies as type checked under `lib`. + /// Assumes that all of those modules are known. + pub fn set_type_checked(&self, roots: &[ModuleSpecifier], lib: TsTypeLib) { + // It's ok to analyze and update this while the module graph itself is + // being updated in a permit because the module graph update is always + // additive and this will be a subset of the original graph + let graph = self.graph(); + let entries = graph.walk( + roots, + deno_graph::WalkOptions { + check_js: true, + follow_dynamic: true, + follow_type_only: true, + }, + ); + + // now update + let mut data = self.graph_data.write(); + let checked_lib_set = data.checked_libs.entry(lib).or_default(); + for (specifier, _) in entries { + checked_lib_set.insert(specifier.clone()); + } + } + + /// Check if `roots` are all marked as type checked under `lib`. + pub fn is_type_checked( + &self, + roots: &[ModuleSpecifier], + lib: TsTypeLib, + ) -> bool { + let data = self.graph_data.read(); + match data.checked_libs.get(&lib) { + Some(checked_lib_set) => roots.iter().all(|r| { + let found = data.graph.resolve(r); + checked_lib_set.contains(&found) + }), + None => false, + } + } +} + +/// A permit for updating the module graph. When complete and +/// everything looks fine, calling `.commit()` will store the +/// new graph in the ModuleGraphContainer. +pub struct ModuleGraphUpdatePermit<'a> { + permit: SemaphorePermit<'a>, + graph_data: Arc>, + graph: ModuleGraph, +} + +impl<'a> ModuleGraphUpdatePermit<'a> { + /// Gets the module graph for mutation. + pub fn graph_mut(&mut self) -> &mut ModuleGraph { + &mut self.graph + } + + /// Saves the mutated module graph in the container + /// and returns an Arc to the new module graph. + pub fn commit(self) -> Arc { + let graph = Arc::new(self.graph); + self.graph_data.write().graph = graph.clone(); + drop(self.permit); // explicit drop for clarity + graph + } +} + #[cfg(test)] mod test { use std::sync::Arc; @@ -703,6 +442,7 @@ mod test { specifier: input.to_string(), range: Range { specifier, + text: "".to_string(), start: Position::zeroed(), end: Position::zeroed(), }, @@ -719,6 +459,7 @@ mod test { let err = ResolutionError::InvalidSpecifier { range: Range { specifier, + text: "".to_string(), start: Position::zeroed(), end: Position::zeroed(), }, diff --git a/cli/http_util.rs b/cli/http_util.rs index 225c49996b9321..9476d6a5f9d70b 100644 --- a/cli/http_util.rs +++ b/cli/http_util.rs @@ -300,7 +300,7 @@ impl HttpClient { .map(Some) } - async fn get_redirected_response( + pub async fn get_redirected_response( &self, url: U, ) -> Result { diff --git a/cli/js/40_testing.js b/cli/js/40_testing.js index 0693fba1a12f11..7ea52a5fcb79d0 100644 --- a/cli/js/40_testing.js +++ b/cli/js/40_testing.js @@ -1,899 +1,930 @@ // Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. -"use strict"; - -((window) => { - const core = window.Deno.core; - const ops = core.ops; - const { setExitHandler } = window.__bootstrap.os; - const { Console } = window.__bootstrap.console; - const { serializePermissions } = window.__bootstrap.permissions; - const { assert } = window.__bootstrap.infra; - const { - ArrayFrom, - ArrayPrototypeFilter, - ArrayPrototypeJoin, - ArrayPrototypeMap, - ArrayPrototypePush, - ArrayPrototypeShift, - ArrayPrototypeSort, - BigInt, - DateNow, - Error, - FunctionPrototype, - Map, - MapPrototypeGet, - MapPrototypeHas, - MapPrototypeSet, - MathCeil, - ObjectKeys, - ObjectPrototypeHasOwnProperty, - ObjectPrototypeIsPrototypeOf, - Promise, - SafeArrayIterator, - Set, - SymbolToStringTag, - TypeError, - } = window.__bootstrap.primordials; - - const opSanitizerDelayResolveQueue = []; - - // Even if every resource is closed by the end of a test, there can be a delay - // until the pending ops have all finished. This function returns a promise - // that resolves when it's (probably) fine to run the op sanitizer. - // - // This is implemented by adding a macrotask callback that runs after the - // timer macrotasks, so we can guarantee that a currently running interval - // will have an associated op. An additional `setTimeout` of 0 is needed - // before that, though, in order to give time for worker message ops to finish - // (since timeouts of 0 don't queue tasks in the timer queue immediately). - function opSanitizerDelay() { - return new Promise((resolve) => { - setTimeout(() => { - ArrayPrototypePush(opSanitizerDelayResolveQueue, resolve); - }, 0); - }); - } - - function handleOpSanitizerDelayMacrotask() { - ArrayPrototypeShift(opSanitizerDelayResolveQueue)?.(); - return opSanitizerDelayResolveQueue.length === 0; - } - - // An async operation to $0 was started in this test, but never completed. This is often caused by not $1. - // An async operation to $0 was started in this test, but never completed. Async operations should not complete in a test if they were not started in that test. - // deno-fmt-ignore - const OP_DETAILS = { - "op_blob_read_part": ["read from a Blob or File", "awaiting the result of a Blob or File read"], - "op_broadcast_recv": ["receive a message from a BroadcastChannel", "closing the BroadcastChannel"], - "op_broadcast_send": ["send a message to a BroadcastChannel", "closing the BroadcastChannel"], - "op_chmod_async": ["change the permissions of a file", "awaiting the result of a `Deno.chmod` call"], - "op_chown_async": ["change the owner of a file", "awaiting the result of a `Deno.chown` call"], - "op_copy_file_async": ["copy a file", "awaiting the result of a `Deno.copyFile` call"], - "op_crypto_decrypt": ["decrypt data", "awaiting the result of a `crypto.subtle.decrypt` call"], - "op_crypto_derive_bits": ["derive bits from a key", "awaiting the result of a `crypto.subtle.deriveBits` call"], - "op_crypto_encrypt": ["encrypt data", "awaiting the result of a `crypto.subtle.encrypt` call"], - "op_crypto_generate_key": ["generate a key", "awaiting the result of a `crypto.subtle.generateKey` call"], - "op_crypto_sign_key": ["sign data", "awaiting the result of a `crypto.subtle.sign` call"], - "op_crypto_subtle_digest": ["digest data", "awaiting the result of a `crypto.subtle.digest` call"], - "op_crypto_verify_key": ["verify data", "awaiting the result of a `crypto.subtle.verify` call"], - "op_net_recv_udp": ["receive a datagram message via UDP", "awaiting the result of `Deno.DatagramConn#receive` call, or not breaking out of a for await loop looping over a `Deno.DatagramConn`"], - "op_net_recv_unixpacket": ["receive a datagram message via Unixpacket", "awaiting the result of `Deno.DatagramConn#receive` call, or not breaking out of a for await loop looping over a `Deno.DatagramConn`"], - "op_net_send_udp": ["send a datagram message via UDP", "awaiting the result of `Deno.DatagramConn#send` call"], - "op_net_send_unixpacket": ["send a datagram message via Unixpacket", "awaiting the result of `Deno.DatagramConn#send` call"], - "op_dns_resolve": ["resolve a DNS name", "awaiting the result of a `Deno.resolveDns` call"], - "op_fdatasync_async": ["flush pending data operations for a file to disk", "awaiting the result of a `Deno.fdatasync` call"], - "op_fetch_send": ["send a HTTP request", "awaiting the result of a `fetch` call"], - "op_ffi_call_nonblocking": ["do a non blocking ffi call", "awaiting the returned promise"] , - "op_ffi_call_ptr_nonblocking": ["do a non blocking ffi call", "awaiting the returned promise"], - "op_flock_async": ["lock a file", "awaiting the result of a `Deno.flock` call"], - "op_fs_events_poll": ["get the next file system event", "breaking out of a for await loop looping over `Deno.FsEvents`"], - "op_fstat_async": ["get file metadata", "awaiting the result of a `Deno.File#fstat` call"], - "op_fsync_async": ["flush pending data operations for a file to disk", "awaiting the result of a `Deno.fsync` call"], - "op_ftruncate_async": ["truncate a file", "awaiting the result of a `Deno.ftruncate` call"], - "op_funlock_async": ["unlock a file", "awaiting the result of a `Deno.funlock` call"], - "op_futime_async": ["change file timestamps", "awaiting the result of a `Deno.futime` call"], - "op_http_accept": ["accept a HTTP request", "closing a `Deno.HttpConn`"], - "op_http_shutdown": ["shutdown a HTTP connection", "awaiting `Deno.HttpEvent#respondWith`"], - "op_http_upgrade_websocket": ["upgrade a HTTP connection to a WebSocket", "awaiting `Deno.HttpEvent#respondWith`"], - "op_http_write_headers": ["write HTTP response headers", "awaiting `Deno.HttpEvent#respondWith`"], - "op_http_write": ["write HTTP response body", "awaiting `Deno.HttpEvent#respondWith`"], - "op_link_async": ["create a hard link", "awaiting the result of a `Deno.link` call"], - "op_make_temp_dir_async": ["create a temporary directory", "awaiting the result of a `Deno.makeTempDir` call"], - "op_make_temp_file_async": ["create a temporary file", "awaiting the result of a `Deno.makeTempFile` call"], - "op_message_port_recv_message": ["receive a message from a MessagePort", "awaiting the result of not closing a `MessagePort`"], - "op_mkdir_async": ["create a directory", "awaiting the result of a `Deno.mkdir` call"], - "op_net_accept_tcp": ["accept a TCP stream", "closing a `Deno.Listener`"], - "op_net_accept_unix": ["accept a Unix stream", "closing a `Deno.Listener`"], - "op_net_connect_tcp": ["connect to a TCP server", "awaiting a `Deno.connect` call"], - "op_net_connect_unix": ["connect to a Unix server", "awaiting a `Deno.connect` call"], - "op_open_async": ["open a file", "awaiting the result of a `Deno.open` call"], - "op_read_dir_async": ["read a directory", "collecting all items in the async iterable returned from a `Deno.readDir` call"], - "op_read_link_async": ["read a symlink", "awaiting the result of a `Deno.readLink` call"], - "op_realpath_async": ["resolve a path", "awaiting the result of a `Deno.realpath` call"], - "op_remove_async": ["remove a file or directory", "awaiting the result of a `Deno.remove` call"], - "op_rename_async": ["rename a file or directory", "awaiting the result of a `Deno.rename` call"], - "op_run_status": ["get the status of a subprocess", "awaiting the result of a `Deno.Process#status` call"], - "op_seek_async": ["seek in a file", "awaiting the result of a `Deno.File#seek` call"], - "op_signal_poll": ["get the next signal", "un-registering a OS signal handler"], - "op_sleep": ["sleep for a duration", "cancelling a `setTimeout` or `setInterval` call"], - "op_stat_async": ["get file metadata", "awaiting the result of a `Deno.stat` call"], - "op_symlink_async": ["create a symlink", "awaiting the result of a `Deno.symlink` call"], - "op_net_accept_tls": ["accept a TLS stream", "closing a `Deno.TlsListener`"], - "op_net_connect_tls": ["connect to a TLS server", "awaiting a `Deno.connectTls` call"], - "op_tls_handshake": ["perform a TLS handshake", "awaiting a `Deno.TlsConn#handshake` call"], - "op_tls_start": ["start a TLS connection", "awaiting a `Deno.startTls` call"], - "op_truncate_async": ["truncate a file", "awaiting the result of a `Deno.truncate` call"], - "op_utime_async": ["change file timestamps", "awaiting the result of a `Deno.utime` call"], - "op_webgpu_buffer_get_map_async": ["map a WebGPU buffer", "awaiting the result of a `GPUBuffer#mapAsync` call"], - "op_webgpu_request_adapter": ["request a WebGPU adapter", "awaiting the result of a `navigator.gpu.requestAdapter` call"], - "op_webgpu_request_device": ["request a WebGPU device", "awaiting the result of a `GPUAdapter#requestDevice` call"], - "op_worker_recv_message": ["receive a message from a web worker", "terminating a `Worker`"], - "op_ws_close": ["close a WebSocket", "awaiting until the `close` event is emitted on a `WebSocket`, or the `WebSocketStream#closed` promise resolves"], - "op_ws_create": ["create a WebSocket", "awaiting until the `open` event is emitted on a `WebSocket`, or the result of a `WebSocketStream#connection` promise"], - "op_ws_next_event": ["receive the next message on a WebSocket", "closing a `WebSocket` or `WebSocketStream`"], - "op_ws_send": ["send a message on a WebSocket", "closing a `WebSocket` or `WebSocketStream`"], - }; - // Wrap test function in additional assertion that makes sure - // the test case does not leak async "ops" - ie. number of async - // completed ops after the test is the same as number of dispatched - // ops. Note that "unref" ops are ignored since in nature that are - // optional. - function assertOps(fn) { - /** @param desc {TestDescription | TestStepDescription} */ - return async function asyncOpSanitizer(desc) { - const pre = core.metrics(); - const preTraces = new Map(core.opCallTraces); - try { - await fn(desc); - } finally { - // Defer until next event loop turn - that way timeouts and intervals - // cleared can actually be removed from resource table, otherwise - // false positives may occur (https://github.com/denoland/deno/issues/4591) - await opSanitizerDelay(); - await opSanitizerDelay(); - } +const core = globalThis.Deno.core; +const ops = core.ops; +const internals = globalThis.__bootstrap.internals; +import { setExitHandler } from "internal:runtime/js/30_os.js"; +import { Console } from "internal:deno_console/02_console.js"; +import { serializePermissions } from "internal:runtime/js/10_permissions.js"; +import { assert } from "internal:deno_web/00_infra.js"; +const primordials = globalThis.__bootstrap.primordials; +const { + ArrayFrom, + ArrayPrototypeFilter, + ArrayPrototypeJoin, + ArrayPrototypeMap, + ArrayPrototypePush, + ArrayPrototypeShift, + ArrayPrototypeSort, + BigInt, + DateNow, + Error, + FunctionPrototype, + Map, + MapPrototypeGet, + MapPrototypeHas, + MapPrototypeSet, + MathCeil, + ObjectKeys, + ObjectPrototypeHasOwnProperty, + ObjectPrototypeIsPrototypeOf, + Promise, + SafeArrayIterator, + Set, + SymbolToStringTag, + TypeError, +} = primordials; + +const opSanitizerDelayResolveQueue = []; + +// Even if every resource is closed by the end of a test, there can be a delay +// until the pending ops have all finished. This function returns a promise +// that resolves when it's (probably) fine to run the op sanitizer. +// +// This is implemented by adding a macrotask callback that runs after the +// timer macrotasks, so we can guarantee that a currently running interval +// will have an associated op. An additional `setTimeout` of 0 is needed +// before that, though, in order to give time for worker message ops to finish +// (since timeouts of 0 don't queue tasks in the timer queue immediately). +function opSanitizerDelay() { + return new Promise((resolve) => { + setTimeout(() => { + ArrayPrototypePush(opSanitizerDelayResolveQueue, resolve); + }, 0); + }); +} + +function handleOpSanitizerDelayMacrotask() { + ArrayPrototypeShift(opSanitizerDelayResolveQueue)?.(); + return opSanitizerDelayResolveQueue.length === 0; +} + +// An async operation to $0 was started in this test, but never completed. This is often caused by not $1. +// An async operation to $0 was started in this test, but never completed. Async operations should not complete in a test if they were not started in that test. +// deno-fmt-ignore +const OP_DETAILS = { + "op_blob_read_part": ["read from a Blob or File", "awaiting the result of a Blob or File read"], + "op_broadcast_recv": ["receive a message from a BroadcastChannel", "closing the BroadcastChannel"], + "op_broadcast_send": ["send a message to a BroadcastChannel", "closing the BroadcastChannel"], + "op_chmod_async": ["change the permissions of a file", "awaiting the result of a `Deno.chmod` call"], + "op_chown_async": ["change the owner of a file", "awaiting the result of a `Deno.chown` call"], + "op_copy_file_async": ["copy a file", "awaiting the result of a `Deno.copyFile` call"], + "op_crypto_decrypt": ["decrypt data", "awaiting the result of a `crypto.subtle.decrypt` call"], + "op_crypto_derive_bits": ["derive bits from a key", "awaiting the result of a `crypto.subtle.deriveBits` call"], + "op_crypto_encrypt": ["encrypt data", "awaiting the result of a `crypto.subtle.encrypt` call"], + "op_crypto_generate_key": ["generate a key", "awaiting the result of a `crypto.subtle.generateKey` call"], + "op_crypto_sign_key": ["sign data", "awaiting the result of a `crypto.subtle.sign` call"], + "op_crypto_subtle_digest": ["digest data", "awaiting the result of a `crypto.subtle.digest` call"], + "op_crypto_verify_key": ["verify data", "awaiting the result of a `crypto.subtle.verify` call"], + "op_net_recv_udp": ["receive a datagram message via UDP", "awaiting the result of `Deno.DatagramConn#receive` call, or not breaking out of a for await loop looping over a `Deno.DatagramConn`"], + "op_net_recv_unixpacket": ["receive a datagram message via Unixpacket", "awaiting the result of `Deno.DatagramConn#receive` call, or not breaking out of a for await loop looping over a `Deno.DatagramConn`"], + "op_net_send_udp": ["send a datagram message via UDP", "awaiting the result of `Deno.DatagramConn#send` call"], + "op_net_send_unixpacket": ["send a datagram message via Unixpacket", "awaiting the result of `Deno.DatagramConn#send` call"], + "op_dns_resolve": ["resolve a DNS name", "awaiting the result of a `Deno.resolveDns` call"], + "op_fdatasync_async": ["flush pending data operations for a file to disk", "awaiting the result of a `Deno.fdatasync` call"], + "op_fetch_send": ["send a HTTP request", "awaiting the result of a `fetch` call"], + "op_ffi_call_nonblocking": ["do a non blocking ffi call", "awaiting the returned promise"] , + "op_ffi_call_ptr_nonblocking": ["do a non blocking ffi call", "awaiting the returned promise"], + "op_flock_async": ["lock a file", "awaiting the result of a `Deno.flock` call"], + "op_fs_events_poll": ["get the next file system event", "breaking out of a for await loop looping over `Deno.FsEvents`"], + "op_fstat_async": ["get file metadata", "awaiting the result of a `Deno.File#fstat` call"], + "op_fsync_async": ["flush pending data operations for a file to disk", "awaiting the result of a `Deno.fsync` call"], + "op_ftruncate_async": ["truncate a file", "awaiting the result of a `Deno.ftruncate` call"], + "op_funlock_async": ["unlock a file", "awaiting the result of a `Deno.funlock` call"], + "op_futime_async": ["change file timestamps", "awaiting the result of a `Deno.futime` call"], + "op_http_accept": ["accept a HTTP request", "closing a `Deno.HttpConn`"], + "op_http_shutdown": ["shutdown a HTTP connection", "awaiting `Deno.HttpEvent#respondWith`"], + "op_http_upgrade_websocket": ["upgrade a HTTP connection to a WebSocket", "awaiting `Deno.HttpEvent#respondWith`"], + "op_http_write_headers": ["write HTTP response headers", "awaiting `Deno.HttpEvent#respondWith`"], + "op_http_write": ["write HTTP response body", "awaiting `Deno.HttpEvent#respondWith`"], + "op_link_async": ["create a hard link", "awaiting the result of a `Deno.link` call"], + "op_make_temp_dir_async": ["create a temporary directory", "awaiting the result of a `Deno.makeTempDir` call"], + "op_make_temp_file_async": ["create a temporary file", "awaiting the result of a `Deno.makeTempFile` call"], + "op_message_port_recv_message": ["receive a message from a MessagePort", "awaiting the result of not closing a `MessagePort`"], + "op_mkdir_async": ["create a directory", "awaiting the result of a `Deno.mkdir` call"], + "op_net_accept_tcp": ["accept a TCP stream", "closing a `Deno.Listener`"], + "op_net_accept_unix": ["accept a Unix stream", "closing a `Deno.Listener`"], + "op_net_connect_tcp": ["connect to a TCP server", "awaiting a `Deno.connect` call"], + "op_net_connect_unix": ["connect to a Unix server", "awaiting a `Deno.connect` call"], + "op_open_async": ["open a file", "awaiting the result of a `Deno.open` call"], + "op_read_dir_async": ["read a directory", "collecting all items in the async iterable returned from a `Deno.readDir` call"], + "op_read_link_async": ["read a symlink", "awaiting the result of a `Deno.readLink` call"], + "op_realpath_async": ["resolve a path", "awaiting the result of a `Deno.realpath` call"], + "op_remove_async": ["remove a file or directory", "awaiting the result of a `Deno.remove` call"], + "op_rename_async": ["rename a file or directory", "awaiting the result of a `Deno.rename` call"], + "op_run_status": ["get the status of a subprocess", "awaiting the result of a `Deno.Process#status` call"], + "op_seek_async": ["seek in a file", "awaiting the result of a `Deno.File#seek` call"], + "op_signal_poll": ["get the next signal", "un-registering a OS signal handler"], + "op_sleep": ["sleep for a duration", "cancelling a `setTimeout` or `setInterval` call"], + "op_stat_async": ["get file metadata", "awaiting the result of a `Deno.stat` call"], + "op_symlink_async": ["create a symlink", "awaiting the result of a `Deno.symlink` call"], + "op_net_accept_tls": ["accept a TLS stream", "closing a `Deno.TlsListener`"], + "op_net_connect_tls": ["connect to a TLS server", "awaiting a `Deno.connectTls` call"], + "op_tls_handshake": ["perform a TLS handshake", "awaiting a `Deno.TlsConn#handshake` call"], + "op_tls_start": ["start a TLS connection", "awaiting a `Deno.startTls` call"], + "op_truncate_async": ["truncate a file", "awaiting the result of a `Deno.truncate` call"], + "op_utime_async": ["change file timestamps", "awaiting the result of a `Deno.utime` call"], + "op_webgpu_buffer_get_map_async": ["map a WebGPU buffer", "awaiting the result of a `GPUBuffer#mapAsync` call"], + "op_webgpu_request_adapter": ["request a WebGPU adapter", "awaiting the result of a `navigator.gpu.requestAdapter` call"], + "op_webgpu_request_device": ["request a WebGPU device", "awaiting the result of a `GPUAdapter#requestDevice` call"], + "op_worker_recv_message": ["receive a message from a web worker", "terminating a `Worker`"], + "op_ws_close": ["close a WebSocket", "awaiting until the `close` event is emitted on a `WebSocket`, or the `WebSocketStream#closed` promise resolves"], + "op_ws_create": ["create a WebSocket", "awaiting until the `open` event is emitted on a `WebSocket`, or the result of a `WebSocketStream#connection` promise"], + "op_ws_next_event": ["receive the next message on a WebSocket", "closing a `WebSocket` or `WebSocketStream`"], + "op_ws_send": ["send a message on a WebSocket", "closing a `WebSocket` or `WebSocketStream`"], +}; + +// Wrap test function in additional assertion that makes sure +// the test case does not leak async "ops" - ie. number of async +// completed ops after the test is the same as number of dispatched +// ops. Note that "unref" ops are ignored since in nature that are +// optional. +function assertOps(fn) { + /** @param desc {TestDescription | TestStepDescription} */ + return async function asyncOpSanitizer(desc) { + const pre = core.metrics(); + const preTraces = new Map(core.opCallTraces); + try { + await fn(desc); + } finally { + // Defer until next event loop turn - that way timeouts and intervals + // cleared can actually be removed from resource table, otherwise + // false positives may occur (https://github.com/denoland/deno/issues/4591) + await opSanitizerDelay(); + await opSanitizerDelay(); + } - if (shouldSkipSanitizers(desc)) return; + if (shouldSkipSanitizers(desc)) return; - const post = core.metrics(); - const postTraces = new Map(core.opCallTraces); + const post = core.metrics(); + const postTraces = new Map(core.opCallTraces); - // We're checking diff because one might spawn HTTP server in the background - // that will be a pending async op before test starts. - const dispatchedDiff = post.opsDispatchedAsync - pre.opsDispatchedAsync; - const completedDiff = post.opsCompletedAsync - pre.opsCompletedAsync; + // We're checking diff because one might spawn HTTP server in the background + // that will be a pending async op before test starts. + const dispatchedDiff = post.opsDispatchedAsync - pre.opsDispatchedAsync; + const completedDiff = post.opsCompletedAsync - pre.opsCompletedAsync; - if (dispatchedDiff === completedDiff) return; + if (dispatchedDiff === completedDiff) return; - const details = []; - for (const key in post.ops) { - if (!ObjectPrototypeHasOwnProperty(post.ops, key)) { - continue; + const details = []; + for (const key in post.ops) { + if (!ObjectPrototypeHasOwnProperty(post.ops, key)) { + continue; + } + const preOp = pre.ops[key] ?? + { opsDispatchedAsync: 0, opsCompletedAsync: 0 }; + const postOp = post.ops[key]; + const dispatchedDiff = postOp.opsDispatchedAsync - + preOp.opsDispatchedAsync; + const completedDiff = postOp.opsCompletedAsync - + preOp.opsCompletedAsync; + + if (dispatchedDiff > completedDiff) { + const [name, hint] = OP_DETAILS[key] || [key, null]; + const count = dispatchedDiff - completedDiff; + let message = `${count} async operation${ + count === 1 ? "" : "s" + } to ${name} ${ + count === 1 ? "was" : "were" + } started in this test, but never completed.`; + if (hint) { + message += ` This is often caused by not ${hint}.`; } - const preOp = pre.ops[key] ?? - { opsDispatchedAsync: 0, opsCompletedAsync: 0 }; - const postOp = post.ops[key]; - const dispatchedDiff = postOp.opsDispatchedAsync - - preOp.opsDispatchedAsync; - const completedDiff = postOp.opsCompletedAsync - - preOp.opsCompletedAsync; - - if (dispatchedDiff > completedDiff) { - const [name, hint] = OP_DETAILS[key] || [key, null]; - const count = dispatchedDiff - completedDiff; - let message = `${count} async operation${ - count === 1 ? "" : "s" - } to ${name} ${ + const traces = []; + for (const [id, { opName, stack }] of postTraces) { + if (opName !== key) continue; + if (MapPrototypeHas(preTraces, id)) continue; + ArrayPrototypePush(traces, stack); + } + if (traces.length === 1) { + message += " The operation was started here:\n"; + message += traces[0]; + } else if (traces.length > 1) { + message += " The operations were started here:\n"; + message += ArrayPrototypeJoin(traces, "\n\n"); + } + ArrayPrototypePush(details, message); + } else if (dispatchedDiff < completedDiff) { + const [name, hint] = OP_DETAILS[key] || [key, null]; + const count = completedDiff - dispatchedDiff; + ArrayPrototypePush( + details, + `${count} async operation${count === 1 ? "" : "s"} to ${name} ${ count === 1 ? "was" : "were" - } started in this test, but never completed.`; - if (hint) { - message += ` This is often caused by not ${hint}.`; - } - const traces = []; - for (const [id, { opName, stack }] of postTraces) { - if (opName !== key) continue; - if (MapPrototypeHas(preTraces, id)) continue; - ArrayPrototypePush(traces, stack); - } - if (traces.length === 1) { - message += " The operation was started here:\n"; - message += traces[0]; - } else if (traces.length > 1) { - message += " The operations were started here:\n"; - message += ArrayPrototypeJoin(traces, "\n\n"); - } - ArrayPrototypePush(details, message); - } else if (dispatchedDiff < completedDiff) { - const [name, hint] = OP_DETAILS[key] || [key, null]; - const count = completedDiff - dispatchedDiff; - ArrayPrototypePush( - details, - `${count} async operation${count === 1 ? "" : "s"} to ${name} ${ - count === 1 ? "was" : "were" - } started before this test, but ${ - count === 1 ? "was" : "were" - } completed during the test. Async operations should not complete in a test if they were not started in that test. + } started before this test, but ${ + count === 1 ? "was" : "were" + } completed during the test. Async operations should not complete in a test if they were not started in that test. ${hint ? `This is often caused by not ${hint}.` : ""}`, - ); - } + ); } + } - let msg = `Test case is leaking async ops. + let msg = `Test case is leaking async ops. - ${ArrayPrototypeJoin(details, "\n - ")}`; - if (!core.isOpCallTracingEnabled()) { - msg += - `\n\nTo get more details where ops were leaked, run again with --trace-ops flag.`; - } else { - msg += "\n"; - } - - throw assert(false, msg); - }; - } - - function prettyResourceNames(name) { - switch (name) { - case "fsFile": - return ["A file", "opened", "closed"]; - case "fetchRequest": - return ["A fetch request", "started", "finished"]; - case "fetchRequestBody": - return ["A fetch request body", "created", "closed"]; - case "fetchResponseBody": - return ["A fetch response body", "created", "consumed"]; - case "httpClient": - return ["An HTTP client", "created", "closed"]; - case "dynamicLibrary": - return ["A dynamic library", "loaded", "unloaded"]; - case "httpConn": - return ["An inbound HTTP connection", "accepted", "closed"]; - case "httpStream": - return ["An inbound HTTP request", "accepted", "closed"]; - case "tcpStream": - return ["A TCP connection", "opened/accepted", "closed"]; - case "unixStream": - return ["A Unix connection", "opened/accepted", "closed"]; - case "tlsStream": - return ["A TLS connection", "opened/accepted", "closed"]; - case "tlsListener": - return ["A TLS listener", "opened", "closed"]; - case "unixListener": - return ["A Unix listener", "opened", "closed"]; - case "unixDatagram": - return ["A Unix datagram", "opened", "closed"]; - case "tcpListener": - return ["A TCP listener", "opened", "closed"]; - case "udpSocket": - return ["A UDP socket", "opened", "closed"]; - case "timer": - return ["A timer", "started", "fired/cleared"]; - case "textDecoder": - return ["A text decoder", "created", "finished"]; - case "messagePort": - return ["A message port", "created", "closed"]; - case "webSocketStream": - return ["A WebSocket", "opened", "closed"]; - case "fsEvents": - return ["A file system watcher", "created", "closed"]; - case "childStdin": - return ["A child process stdin", "opened", "closed"]; - case "childStdout": - return ["A child process stdout", "opened", "closed"]; - case "childStderr": - return ["A child process stderr", "opened", "closed"]; - case "child": - return ["A child process", "started", "closed"]; - case "signal": - return ["A signal listener", "created", "fired/cleared"]; - case "stdin": - return ["The stdin pipe", "opened", "closed"]; - case "stdout": - return ["The stdout pipe", "opened", "closed"]; - case "stderr": - return ["The stderr pipe", "opened", "closed"]; - case "compression": - return ["A CompressionStream", "created", "closed"]; - default: - return [`A "${name}" resource`, "created", "cleaned up"]; + if (!core.isOpCallTracingEnabled()) { + msg += + `\n\nTo get more details where ops were leaked, run again with --trace-ops flag.`; + } else { + msg += "\n"; } - } - function resourceCloseHint(name) { - switch (name) { - case "fsFile": - return "Close the file handle by calling `file.close()`."; - case "fetchRequest": - return "Await the promise returned from `fetch()` or abort the fetch with an abort signal."; - case "fetchRequestBody": - return "Terminate the request body `ReadableStream` by closing or erroring it."; - case "fetchResponseBody": - return "Consume or close the response body `ReadableStream`, e.g `await resp.text()` or `await resp.body.cancel()`."; - case "httpClient": - return "Close the HTTP client by calling `httpClient.close()`."; - case "dynamicLibrary": - return "Unload the dynamic library by calling `dynamicLibrary.close()`."; - case "httpConn": - return "Close the inbound HTTP connection by calling `httpConn.close()`."; - case "httpStream": - return "Close the inbound HTTP request by responding with `e.respondWith()` or closing the HTTP connection."; - case "tcpStream": - return "Close the TCP connection by calling `tcpConn.close()`."; - case "unixStream": - return "Close the Unix socket connection by calling `unixConn.close()`."; - case "tlsStream": - return "Close the TLS connection by calling `tlsConn.close()`."; - case "tlsListener": - return "Close the TLS listener by calling `tlsListener.close()`."; - case "unixListener": - return "Close the Unix socket listener by calling `unixListener.close()`."; - case "unixDatagram": - return "Close the Unix datagram socket by calling `unixDatagram.close()`."; - case "tcpListener": - return "Close the TCP listener by calling `tcpListener.close()`."; - case "udpSocket": - return "Close the UDP socket by calling `udpSocket.close()`."; - case "timer": - return "Clear the timer by calling `clearInterval` or `clearTimeout`."; - case "textDecoder": - return "Close the text decoder by calling `textDecoder.decode('')` or `await textDecoderStream.readable.cancel()`."; - case "messagePort": - return "Close the message port by calling `messagePort.close()`."; - case "webSocketStream": - return "Close the WebSocket by calling `webSocket.close()`."; - case "fsEvents": - return "Close the file system watcher by calling `watcher.close()`."; - case "childStdin": - return "Close the child process stdin by calling `proc.stdin.close()`."; - case "childStdout": - return "Close the child process stdout by calling `proc.stdout.close()` or `await child.stdout.cancel()`."; - case "childStderr": - return "Close the child process stderr by calling `proc.stderr.close()` or `await child.stderr.cancel()`."; - case "child": - return "Close the child process by calling `proc.kill()` or `proc.close()`."; - case "signal": - return "Clear the signal listener by calling `Deno.removeSignalListener`."; - case "stdin": - return "Close the stdin pipe by calling `Deno.stdin.close()`."; - case "stdout": - return "Close the stdout pipe by calling `Deno.stdout.close()`."; - case "stderr": - return "Close the stderr pipe by calling `Deno.stderr.close()`."; - case "compression": - return "Close the compression stream by calling `await stream.writable.close()`."; - default: - return "Close the resource before the end of the test."; - } + throw assert(false, msg); + }; +} + +function prettyResourceNames(name) { + switch (name) { + case "fsFile": + return ["A file", "opened", "closed"]; + case "fetchRequest": + return ["A fetch request", "started", "finished"]; + case "fetchRequestBody": + return ["A fetch request body", "created", "closed"]; + case "fetchResponseBody": + return ["A fetch response body", "created", "consumed"]; + case "httpClient": + return ["An HTTP client", "created", "closed"]; + case "dynamicLibrary": + return ["A dynamic library", "loaded", "unloaded"]; + case "httpConn": + return ["An inbound HTTP connection", "accepted", "closed"]; + case "httpStream": + return ["An inbound HTTP request", "accepted", "closed"]; + case "tcpStream": + return ["A TCP connection", "opened/accepted", "closed"]; + case "unixStream": + return ["A Unix connection", "opened/accepted", "closed"]; + case "tlsStream": + return ["A TLS connection", "opened/accepted", "closed"]; + case "tlsListener": + return ["A TLS listener", "opened", "closed"]; + case "unixListener": + return ["A Unix listener", "opened", "closed"]; + case "unixDatagram": + return ["A Unix datagram", "opened", "closed"]; + case "tcpListener": + return ["A TCP listener", "opened", "closed"]; + case "udpSocket": + return ["A UDP socket", "opened", "closed"]; + case "timer": + return ["A timer", "started", "fired/cleared"]; + case "textDecoder": + return ["A text decoder", "created", "finished"]; + case "messagePort": + return ["A message port", "created", "closed"]; + case "webSocketStream": + return ["A WebSocket", "opened", "closed"]; + case "fsEvents": + return ["A file system watcher", "created", "closed"]; + case "childStdin": + return ["A child process stdin", "opened", "closed"]; + case "childStdout": + return ["A child process stdout", "opened", "closed"]; + case "childStderr": + return ["A child process stderr", "opened", "closed"]; + case "child": + return ["A child process", "started", "closed"]; + case "signal": + return ["A signal listener", "created", "fired/cleared"]; + case "stdin": + return ["The stdin pipe", "opened", "closed"]; + case "stdout": + return ["The stdout pipe", "opened", "closed"]; + case "stderr": + return ["The stderr pipe", "opened", "closed"]; + case "compression": + return ["A CompressionStream", "created", "closed"]; + default: + return [`A "${name}" resource`, "created", "cleaned up"]; + } +} + +function resourceCloseHint(name) { + switch (name) { + case "fsFile": + return "Close the file handle by calling `file.close()`."; + case "fetchRequest": + return "Await the promise returned from `fetch()` or abort the fetch with an abort signal."; + case "fetchRequestBody": + return "Terminate the request body `ReadableStream` by closing or erroring it."; + case "fetchResponseBody": + return "Consume or close the response body `ReadableStream`, e.g `await resp.text()` or `await resp.body.cancel()`."; + case "httpClient": + return "Close the HTTP client by calling `httpClient.close()`."; + case "dynamicLibrary": + return "Unload the dynamic library by calling `dynamicLibrary.close()`."; + case "httpConn": + return "Close the inbound HTTP connection by calling `httpConn.close()`."; + case "httpStream": + return "Close the inbound HTTP request by responding with `e.respondWith()` or closing the HTTP connection."; + case "tcpStream": + return "Close the TCP connection by calling `tcpConn.close()`."; + case "unixStream": + return "Close the Unix socket connection by calling `unixConn.close()`."; + case "tlsStream": + return "Close the TLS connection by calling `tlsConn.close()`."; + case "tlsListener": + return "Close the TLS listener by calling `tlsListener.close()`."; + case "unixListener": + return "Close the Unix socket listener by calling `unixListener.close()`."; + case "unixDatagram": + return "Close the Unix datagram socket by calling `unixDatagram.close()`."; + case "tcpListener": + return "Close the TCP listener by calling `tcpListener.close()`."; + case "udpSocket": + return "Close the UDP socket by calling `udpSocket.close()`."; + case "timer": + return "Clear the timer by calling `clearInterval` or `clearTimeout`."; + case "textDecoder": + return "Close the text decoder by calling `textDecoder.decode('')` or `await textDecoderStream.readable.cancel()`."; + case "messagePort": + return "Close the message port by calling `messagePort.close()`."; + case "webSocketStream": + return "Close the WebSocket by calling `webSocket.close()`."; + case "fsEvents": + return "Close the file system watcher by calling `watcher.close()`."; + case "childStdin": + return "Close the child process stdin by calling `proc.stdin.close()`."; + case "childStdout": + return "Close the child process stdout by calling `proc.stdout.close()` or `await child.stdout.cancel()`."; + case "childStderr": + return "Close the child process stderr by calling `proc.stderr.close()` or `await child.stderr.cancel()`."; + case "child": + return "Close the child process by calling `proc.kill()` or `proc.close()`."; + case "signal": + return "Clear the signal listener by calling `Deno.removeSignalListener`."; + case "stdin": + return "Close the stdin pipe by calling `Deno.stdin.close()`."; + case "stdout": + return "Close the stdout pipe by calling `Deno.stdout.close()`."; + case "stderr": + return "Close the stderr pipe by calling `Deno.stderr.close()`."; + case "compression": + return "Close the compression stream by calling `await stream.writable.close()`."; + default: + return "Close the resource before the end of the test."; } +} - // Wrap test function in additional assertion that makes sure - // the test case does not "leak" resources - ie. resource table after - // the test has exactly the same contents as before the test. - function assertResources(fn) { - /** @param desc {TestDescription | TestStepDescription} */ - return async function resourceSanitizer(desc) { - const pre = core.resources(); - await fn(desc); +// Wrap test function in additional assertion that makes sure +// the test case does not "leak" resources - ie. resource table after +// the test has exactly the same contents as before the test. +function assertResources(fn) { + /** @param desc {TestDescription | TestStepDescription} */ + return async function resourceSanitizer(desc) { + const pre = core.resources(); + await fn(desc); - if (shouldSkipSanitizers(desc)) { - return; - } + if (shouldSkipSanitizers(desc)) { + return; + } - const post = core.resources(); - - const allResources = new Set([ - ...new SafeArrayIterator(ObjectKeys(pre)), - ...new SafeArrayIterator(ObjectKeys(post)), - ]); - - const details = []; - for (const resource of allResources) { - const preResource = pre[resource]; - const postResource = post[resource]; - if (preResource === postResource) continue; - - if (preResource === undefined) { - const [name, action1, action2] = prettyResourceNames(postResource); - const hint = resourceCloseHint(postResource); - const detail = - `${name} (rid ${resource}) was ${action1} during the test, but not ${action2} during the test. ${hint}`; - ArrayPrototypePush(details, detail); - } else { - const [name, action1, action2] = prettyResourceNames(preResource); - const detail = - `${name} (rid ${resource}) was ${action1} before the test started, but was ${action2} during the test. Do not close resources in a test that were not created during that test.`; - ArrayPrototypePush(details, detail); - } + const post = core.resources(); + + const allResources = new Set([ + ...new SafeArrayIterator(ObjectKeys(pre)), + ...new SafeArrayIterator(ObjectKeys(post)), + ]); + + const details = []; + for (const resource of allResources) { + const preResource = pre[resource]; + const postResource = post[resource]; + if (preResource === postResource) continue; + + if (preResource === undefined) { + const [name, action1, action2] = prettyResourceNames(postResource); + const hint = resourceCloseHint(postResource); + const detail = + `${name} (rid ${resource}) was ${action1} during the test, but not ${action2} during the test. ${hint}`; + ArrayPrototypePush(details, detail); + } else { + const [name, action1, action2] = prettyResourceNames(preResource); + const detail = + `${name} (rid ${resource}) was ${action1} before the test started, but was ${action2} during the test. Do not close resources in a test that were not created during that test.`; + ArrayPrototypePush(details, detail); } + } - const message = `Test case is leaking ${details.length} resource${ - details.length === 1 ? "" : "s" - }: + const message = `Test case is leaking ${details.length} resource${ + details.length === 1 ? "" : "s" + }: - ${details.join("\n - ")} `; - assert(details.length === 0, message); - }; - } + assert(details.length === 0, message); + }; +} + +// Wrap test function in additional assertion that makes sure +// that the test case does not accidentally exit prematurely. +function assertExit(fn, isTest) { + return async function exitSanitizer(...params) { + setExitHandler((exitCode) => { + assert( + false, + `${ + isTest ? "Test case" : "Bench" + } attempted to exit with exit code: ${exitCode}`, + ); + }); - // Wrap test function in additional assertion that makes sure - // that the test case does not accidentally exit prematurely. - function assertExit(fn, isTest) { - return async function exitSanitizer(...params) { - setExitHandler((exitCode) => { - assert( - false, - `${ - isTest ? "Test case" : "Bench" - } attempted to exit with exit code: ${exitCode}`, - ); - }); + try { + await fn(...new SafeArrayIterator(params)); + } catch (err) { + throw err; + } finally { + setExitHandler(null); + } + }; +} - try { - await fn(...new SafeArrayIterator(params)); - } catch (err) { - throw err; - } finally { - setExitHandler(null); - } - }; - } +function assertTestStepScopes(fn) { + /** @param desc {TestDescription | TestStepDescription} */ + return async function testStepSanitizer(desc) { + preValidation(); + // only report waiting after pre-validation + if (canStreamReporting(desc) && "parent" in desc) { + stepReportWait(desc); + } + await fn(MapPrototypeGet(testStates, desc.id).context); + testStepPostValidation(desc); + + function preValidation() { + const runningStepDescs = getRunningStepDescs(); + const runningStepDescsWithSanitizers = ArrayPrototypeFilter( + runningStepDescs, + (d) => usesSanitizer(d), + ); - function assertTestStepScopes(fn) { - /** @param desc {TestDescription | TestStepDescription} */ - return async function testStepSanitizer(desc) { - preValidation(); - // only report waiting after pre-validation - if (canStreamReporting(desc) && "parent" in desc) { - stepReportWait(desc); - } - await fn(MapPrototypeGet(testStates, desc.id).context); - testStepPostValidation(desc); - - function preValidation() { - const runningStepDescs = getRunningStepDescs(); - const runningStepDescsWithSanitizers = ArrayPrototypeFilter( - runningStepDescs, - (d) => usesSanitizer(d), + if (runningStepDescsWithSanitizers.length > 0) { + throw new Error( + "Cannot start test step while another test step with sanitizers is running.\n" + + runningStepDescsWithSanitizers + .map((d) => ` * ${getFullName(d)}`) + .join("\n"), ); - - if (runningStepDescsWithSanitizers.length > 0) { - throw new Error( - "Cannot start test step while another test step with sanitizers is running.\n" + - runningStepDescsWithSanitizers - .map((d) => ` * ${getFullName(d)}`) - .join("\n"), - ); - } - - if (usesSanitizer(desc) && runningStepDescs.length > 0) { - throw new Error( - "Cannot start test step with sanitizers while another test step is running.\n" + - runningStepDescs.map((d) => ` * ${getFullName(d)}`).join("\n"), - ); - } - - function getRunningStepDescs() { - const results = []; - let childDesc = desc; - while (childDesc.parent != null) { - const state = MapPrototypeGet(testStates, childDesc.parent.id); - for (const siblingDesc of state.children) { - if (siblingDesc.id == childDesc.id) { - continue; - } - const siblingState = MapPrototypeGet(testStates, siblingDesc.id); - if (!siblingState.finalized) { - ArrayPrototypePush(results, siblingDesc); - } - } - childDesc = childDesc.parent; - } - return results; - } } - }; - } - function testStepPostValidation(desc) { - // check for any running steps - for (const childDesc of MapPrototypeGet(testStates, desc.id).children) { - if (MapPrototypeGet(testStates, childDesc.id).status == "pending") { + if (usesSanitizer(desc) && runningStepDescs.length > 0) { throw new Error( - "There were still test steps running after the current scope finished execution. Ensure all steps are awaited (ex. `await t.step(...)`).", + "Cannot start test step with sanitizers while another test step is running.\n" + + runningStepDescs.map((d) => ` * ${getFullName(d)}`).join("\n"), ); } - } - // check if an ancestor already completed - let currentDesc = desc.parent; - while (currentDesc != null) { - if (MapPrototypeGet(testStates, currentDesc.id).finalized) { - throw new Error( - "Parent scope completed before test step finished execution. Ensure all steps are awaited (ex. `await t.step(...)`).", - ); + function getRunningStepDescs() { + const results = []; + let childDesc = desc; + while (childDesc.parent != null) { + const state = MapPrototypeGet(testStates, childDesc.parent.id); + for (const siblingDesc of state.children) { + if (siblingDesc.id == childDesc.id) { + continue; + } + const siblingState = MapPrototypeGet(testStates, siblingDesc.id); + if (!siblingState.finalized) { + ArrayPrototypePush(results, siblingDesc); + } + } + childDesc = childDesc.parent; + } + return results; } - currentDesc = currentDesc.parent; + } + }; +} + +function testStepPostValidation(desc) { + // check for any running steps + for (const childDesc of MapPrototypeGet(testStates, desc.id).children) { + if (MapPrototypeGet(testStates, childDesc.id).status == "pending") { + throw new Error( + "There were still test steps running after the current scope finished execution. Ensure all steps are awaited (ex. `await t.step(...)`).", + ); } } - function pledgePermissions(permissions) { - return ops.op_pledge_test_permissions( - serializePermissions(permissions), - ); + // check if an ancestor already completed + let currentDesc = desc.parent; + while (currentDesc != null) { + if (MapPrototypeGet(testStates, currentDesc.id).finalized) { + throw new Error( + "Parent scope completed before test step finished execution. Ensure all steps are awaited (ex. `await t.step(...)`).", + ); + } + currentDesc = currentDesc.parent; } +} - function restorePermissions(token) { - ops.op_restore_test_permissions(token); - } +function pledgePermissions(permissions) { + return ops.op_pledge_test_permissions( + serializePermissions(permissions), + ); +} - function withPermissions(fn, permissions) { - return async function applyPermissions(...params) { - const token = pledgePermissions(permissions); +function restorePermissions(token) { + ops.op_restore_test_permissions(token); +} - try { - await fn(...new SafeArrayIterator(params)); - } finally { - restorePermissions(token); - } - }; - } +function withPermissions(fn, permissions) { + return async function applyPermissions(...params) { + const token = pledgePermissions(permissions); - /** - * @typedef {{ - * id: number, - * name: string, - * fn: TestFunction - * origin: string, - * location: TestLocation, - * filteredOut: boolean, - * ignore: boolean, - * only: boolean. - * sanitizeOps: boolean, - * sanitizeResources: boolean, - * sanitizeExit: boolean, - * permissions: PermissionOptions, - * }} TestDescription - * - * @typedef {{ - * id: number, - * name: string, - * fn: TestFunction - * origin: string, - * location: TestLocation, - * ignore: boolean, - * level: number, - * parent: TestDescription | TestStepDescription, - * rootId: number, - * rootName: String, - * sanitizeOps: boolean, - * sanitizeResources: boolean, - * sanitizeExit: boolean, - * }} TestStepDescription - * - * @typedef {{ - * context: TestContext, - * children: TestStepDescription[], - * finalized: boolean, - * }} TestState - * - * @typedef {{ - * context: TestContext, - * children: TestStepDescription[], - * finalized: boolean, - * status: "pending" | "ok" | ""failed" | ignored", - * error: unknown, - * elapsed: number | null, - * reportedWait: boolean, - * reportedResult: boolean, - * }} TestStepState - * - * @typedef {{ - * id: number, - * name: string, - * fn: BenchFunction - * origin: string, - * filteredOut: boolean, - * ignore: boolean, - * only: boolean. - * sanitizeExit: boolean, - * permissions: PermissionOptions, - * }} BenchDescription - */ - - /** @type {TestDescription[]} */ - const testDescs = []; - /** @type {Map} */ - const testStates = new Map(); - /** @type {BenchDescription[]} */ - const benchDescs = []; - let isTestSubcommand = false; - let isBenchSubcommand = false; - - // Main test function provided by Deno. - function test( - nameOrFnOrOptions, - optionsOrFn, - maybeFn, - ) { - if (!isTestSubcommand) { - return; + try { + await fn(...new SafeArrayIterator(params)); + } finally { + restorePermissions(token); } + }; +} + +/** + * @typedef {{ + * id: number, + * name: string, + * fn: TestFunction + * origin: string, + * location: TestLocation, + * filteredOut: boolean, + * ignore: boolean, + * only: boolean. + * sanitizeOps: boolean, + * sanitizeResources: boolean, + * sanitizeExit: boolean, + * permissions: PermissionOptions, + * }} TestDescription + * + * @typedef {{ + * id: number, + * name: string, + * fn: TestFunction + * origin: string, + * location: TestLocation, + * ignore: boolean, + * level: number, + * parent: TestDescription | TestStepDescription, + * rootId: number, + * rootName: String, + * sanitizeOps: boolean, + * sanitizeResources: boolean, + * sanitizeExit: boolean, + * }} TestStepDescription + * + * @typedef {{ + * context: TestContext, + * children: TestStepDescription[], + * finalized: boolean, + * }} TestState + * + * @typedef {{ + * context: TestContext, + * children: TestStepDescription[], + * finalized: boolean, + * status: "pending" | "ok" | ""failed" | ignored", + * error: unknown, + * elapsed: number | null, + * reportedWait: boolean, + * reportedResult: boolean, + * }} TestStepState + * + * @typedef {{ + * id: number, + * name: string, + * fn: BenchFunction + * origin: string, + * filteredOut: boolean, + * ignore: boolean, + * only: boolean. + * sanitizeExit: boolean, + * permissions: PermissionOptions, + * }} BenchDescription + */ + +/** @type {TestDescription[]} */ +const testDescs = []; +/** @type {Map} */ +const testStates = new Map(); +/** @type {BenchDescription[]} */ +const benchDescs = []; +let isTestSubcommand = false; +let isBenchSubcommand = false; + +// Main test function provided by Deno. +function test( + nameOrFnOrOptions, + optionsOrFn, + maybeFn, +) { + if (!isTestSubcommand) { + return; + } - let testDesc; - const defaults = { - ignore: false, - only: false, - sanitizeOps: true, - sanitizeResources: true, - sanitizeExit: true, - permissions: null, - }; + let testDesc; + const defaults = { + ignore: false, + only: false, + sanitizeOps: true, + sanitizeResources: true, + sanitizeExit: true, + permissions: null, + }; - if (typeof nameOrFnOrOptions === "string") { - if (!nameOrFnOrOptions) { - throw new TypeError("The test name can't be empty"); - } - if (typeof optionsOrFn === "function") { - testDesc = { fn: optionsOrFn, name: nameOrFnOrOptions, ...defaults }; - } else { - if (!maybeFn || typeof maybeFn !== "function") { - throw new TypeError("Missing test function"); - } - if (optionsOrFn.fn != undefined) { - throw new TypeError( - "Unexpected 'fn' field in options, test function is already provided as the third argument.", - ); - } - if (optionsOrFn.name != undefined) { - throw new TypeError( - "Unexpected 'name' field in options, test name is already provided as the first argument.", - ); - } - testDesc = { - ...defaults, - ...optionsOrFn, - fn: maybeFn, - name: nameOrFnOrOptions, - }; - } - } else if (typeof nameOrFnOrOptions === "function") { - if (!nameOrFnOrOptions.name) { - throw new TypeError("The test function must have a name"); + if (typeof nameOrFnOrOptions === "string") { + if (!nameOrFnOrOptions) { + throw new TypeError("The test name can't be empty"); + } + if (typeof optionsOrFn === "function") { + testDesc = { fn: optionsOrFn, name: nameOrFnOrOptions, ...defaults }; + } else { + if (!maybeFn || typeof maybeFn !== "function") { + throw new TypeError("Missing test function"); } - if (optionsOrFn != undefined) { - throw new TypeError("Unexpected second argument to Deno.test()"); + if (optionsOrFn.fn != undefined) { + throw new TypeError( + "Unexpected 'fn' field in options, test function is already provided as the third argument.", + ); } - if (maybeFn != undefined) { - throw new TypeError("Unexpected third argument to Deno.test()"); + if (optionsOrFn.name != undefined) { + throw new TypeError( + "Unexpected 'name' field in options, test name is already provided as the first argument.", + ); } testDesc = { ...defaults, - fn: nameOrFnOrOptions, - name: nameOrFnOrOptions.name, + ...optionsOrFn, + fn: maybeFn, + name: nameOrFnOrOptions, }; - } else { - let fn; - let name; - if (typeof optionsOrFn === "function") { - fn = optionsOrFn; - if (nameOrFnOrOptions.fn != undefined) { - throw new TypeError( - "Unexpected 'fn' field in options, test function is already provided as the second argument.", - ); - } - name = nameOrFnOrOptions.name ?? fn.name; - } else { - if ( - !nameOrFnOrOptions.fn || typeof nameOrFnOrOptions.fn !== "function" - ) { - throw new TypeError( - "Expected 'fn' field in the first argument to be a test function.", - ); - } - fn = nameOrFnOrOptions.fn; - name = nameOrFnOrOptions.name ?? fn.name; + } + } else if (typeof nameOrFnOrOptions === "function") { + if (!nameOrFnOrOptions.name) { + throw new TypeError("The test function must have a name"); + } + if (optionsOrFn != undefined) { + throw new TypeError("Unexpected second argument to Deno.test()"); + } + if (maybeFn != undefined) { + throw new TypeError("Unexpected third argument to Deno.test()"); + } + testDesc = { + ...defaults, + fn: nameOrFnOrOptions, + name: nameOrFnOrOptions.name, + }; + } else { + let fn; + let name; + if (typeof optionsOrFn === "function") { + fn = optionsOrFn; + if (nameOrFnOrOptions.fn != undefined) { + throw new TypeError( + "Unexpected 'fn' field in options, test function is already provided as the second argument.", + ); } - if (!name) { - throw new TypeError("The test name can't be empty"); + name = nameOrFnOrOptions.name ?? fn.name; + } else { + if ( + !nameOrFnOrOptions.fn || typeof nameOrFnOrOptions.fn !== "function" + ) { + throw new TypeError( + "Expected 'fn' field in the first argument to be a test function.", + ); } - testDesc = { ...defaults, ...nameOrFnOrOptions, fn, name }; + fn = nameOrFnOrOptions.fn; + name = nameOrFnOrOptions.name ?? fn.name; } - - // Delete this prop in case the user passed it. It's used to detect steps. - delete testDesc.parent; - testDesc.fn = wrapTestFnWithSanitizers(testDesc.fn, testDesc); - if (testDesc.permissions) { - testDesc.fn = withPermissions( - testDesc.fn, - testDesc.permissions, - ); + if (!name) { + throw new TypeError("The test name can't be empty"); } - testDesc.origin = getTestOrigin(); - const jsError = core.destructureError(new Error()); - testDesc.location = { - fileName: jsError.frames[1].fileName, - lineNumber: jsError.frames[1].lineNumber, - columnNumber: jsError.frames[1].columnNumber, - }; - - const { id, filteredOut } = ops.op_register_test(testDesc); - testDesc.id = id; - testDesc.filteredOut = filteredOut; + testDesc = { ...defaults, ...nameOrFnOrOptions, fn, name }; + } - ArrayPrototypePush(testDescs, testDesc); - MapPrototypeSet(testStates, testDesc.id, { - context: createTestContext(testDesc), - children: [], - finalized: false, - }); + // Delete this prop in case the user passed it. It's used to detect steps. + delete testDesc.parent; + testDesc.fn = wrapTestFnWithSanitizers(testDesc.fn, testDesc); + if (testDesc.permissions) { + testDesc.fn = withPermissions( + testDesc.fn, + testDesc.permissions, + ); } + testDesc.origin = getTestOrigin(); + const jsError = core.destructureError(new Error()); + testDesc.location = { + fileName: jsError.frames[1].fileName, + lineNumber: jsError.frames[1].lineNumber, + columnNumber: jsError.frames[1].columnNumber, + }; - // Main bench function provided by Deno. - function bench( - nameOrFnOrOptions, - optionsOrFn, - maybeFn, - ) { - if (!isBenchSubcommand) { - return; - } + const { id, filteredOut } = ops.op_register_test(testDesc); + testDesc.id = id; + testDesc.filteredOut = filteredOut; + + ArrayPrototypePush(testDescs, testDesc); + MapPrototypeSet(testStates, testDesc.id, { + context: createTestContext(testDesc), + children: [], + finalized: false, + }); +} + +// Main bench function provided by Deno. +function bench( + nameOrFnOrOptions, + optionsOrFn, + maybeFn, +) { + if (!isBenchSubcommand) { + return; + } - let benchDesc; - const defaults = { - ignore: false, - baseline: false, - only: false, - sanitizeExit: true, - permissions: null, - }; + let benchDesc; + const defaults = { + ignore: false, + baseline: false, + only: false, + sanitizeExit: true, + permissions: null, + }; - if (typeof nameOrFnOrOptions === "string") { - if (!nameOrFnOrOptions) { - throw new TypeError("The bench name can't be empty"); - } - if (typeof optionsOrFn === "function") { - benchDesc = { fn: optionsOrFn, name: nameOrFnOrOptions, ...defaults }; - } else { - if (!maybeFn || typeof maybeFn !== "function") { - throw new TypeError("Missing bench function"); - } - if (optionsOrFn.fn != undefined) { - throw new TypeError( - "Unexpected 'fn' field in options, bench function is already provided as the third argument.", - ); - } - if (optionsOrFn.name != undefined) { - throw new TypeError( - "Unexpected 'name' field in options, bench name is already provided as the first argument.", - ); - } - benchDesc = { - ...defaults, - ...optionsOrFn, - fn: maybeFn, - name: nameOrFnOrOptions, - }; - } - } else if (typeof nameOrFnOrOptions === "function") { - if (!nameOrFnOrOptions.name) { - throw new TypeError("The bench function must have a name"); + if (typeof nameOrFnOrOptions === "string") { + if (!nameOrFnOrOptions) { + throw new TypeError("The bench name can't be empty"); + } + if (typeof optionsOrFn === "function") { + benchDesc = { fn: optionsOrFn, name: nameOrFnOrOptions, ...defaults }; + } else { + if (!maybeFn || typeof maybeFn !== "function") { + throw new TypeError("Missing bench function"); } - if (optionsOrFn != undefined) { - throw new TypeError("Unexpected second argument to Deno.bench()"); + if (optionsOrFn.fn != undefined) { + throw new TypeError( + "Unexpected 'fn' field in options, bench function is already provided as the third argument.", + ); } - if (maybeFn != undefined) { - throw new TypeError("Unexpected third argument to Deno.bench()"); + if (optionsOrFn.name != undefined) { + throw new TypeError( + "Unexpected 'name' field in options, bench name is already provided as the first argument.", + ); } benchDesc = { ...defaults, - fn: nameOrFnOrOptions, - name: nameOrFnOrOptions.name, + ...optionsOrFn, + fn: maybeFn, + name: nameOrFnOrOptions, }; - } else { - let fn; - let name; - if (typeof optionsOrFn === "function") { - fn = optionsOrFn; - if (nameOrFnOrOptions.fn != undefined) { - throw new TypeError( - "Unexpected 'fn' field in options, bench function is already provided as the second argument.", - ); - } - name = nameOrFnOrOptions.name ?? fn.name; - } else { - if ( - !nameOrFnOrOptions.fn || typeof nameOrFnOrOptions.fn !== "function" - ) { - throw new TypeError( - "Expected 'fn' field in the first argument to be a bench function.", - ); - } - fn = nameOrFnOrOptions.fn; - name = nameOrFnOrOptions.name ?? fn.name; + } + } else if (typeof nameOrFnOrOptions === "function") { + if (!nameOrFnOrOptions.name) { + throw new TypeError("The bench function must have a name"); + } + if (optionsOrFn != undefined) { + throw new TypeError("Unexpected second argument to Deno.bench()"); + } + if (maybeFn != undefined) { + throw new TypeError("Unexpected third argument to Deno.bench()"); + } + benchDesc = { + ...defaults, + fn: nameOrFnOrOptions, + name: nameOrFnOrOptions.name, + }; + } else { + let fn; + let name; + if (typeof optionsOrFn === "function") { + fn = optionsOrFn; + if (nameOrFnOrOptions.fn != undefined) { + throw new TypeError( + "Unexpected 'fn' field in options, bench function is already provided as the second argument.", + ); } - if (!name) { - throw new TypeError("The bench name can't be empty"); + name = nameOrFnOrOptions.name ?? fn.name; + } else { + if ( + !nameOrFnOrOptions.fn || typeof nameOrFnOrOptions.fn !== "function" + ) { + throw new TypeError( + "Expected 'fn' field in the first argument to be a bench function.", + ); } - benchDesc = { ...defaults, ...nameOrFnOrOptions, fn, name }; + fn = nameOrFnOrOptions.fn; + name = nameOrFnOrOptions.name ?? fn.name; } + if (!name) { + throw new TypeError("The bench name can't be empty"); + } + benchDesc = { ...defaults, ...nameOrFnOrOptions, fn, name }; + } - benchDesc.origin = getBenchOrigin(); - const AsyncFunction = (async () => {}).constructor; - benchDesc.async = AsyncFunction === benchDesc.fn.constructor; + benchDesc.origin = getBenchOrigin(); + const AsyncFunction = (async () => {}).constructor; + benchDesc.async = AsyncFunction === benchDesc.fn.constructor; - const { id, filteredOut } = ops.op_register_bench(benchDesc); - benchDesc.id = id; - benchDesc.filteredOut = filteredOut; + const { id, filteredOut } = ops.op_register_bench(benchDesc); + benchDesc.id = id; + benchDesc.filteredOut = filteredOut; - ArrayPrototypePush(benchDescs, benchDesc); - } + ArrayPrototypePush(benchDescs, benchDesc); +} - async function runTest(desc) { - if (desc.ignore) { - return "ignored"; - } +async function runTest(desc) { + if (desc.ignore) { + return "ignored"; + } - try { - await desc.fn(desc); - const failCount = failedChildStepsCount(desc); - return failCount === 0 ? "ok" : { - "failed": core.destructureError( - new Error( - `${failCount} test step${failCount === 1 ? "" : "s"} failed.`, - ), + try { + await desc.fn(desc); + const failCount = failedChildStepsCount(desc); + return failCount === 0 ? "ok" : { + "failed": core.destructureError( + new Error( + `${failCount} test step${failCount === 1 ? "" : "s"} failed.`, ), - }; - } catch (error) { - return { - "failed": core.destructureError(error), - }; - } finally { - const state = MapPrototypeGet(testStates, desc.id); - state.finalized = true; - // ensure the children report their result - for (const childDesc of state.children) { - stepReportResult(childDesc); - } + ), + }; + } catch (error) { + return { + "failed": core.destructureError(error), + }; + } finally { + const state = MapPrototypeGet(testStates, desc.id); + state.finalized = true; + // ensure the children report their result + for (const childDesc of state.children) { + stepReportResult(childDesc); } } +} + +function compareMeasurements(a, b) { + if (a > b) return 1; + if (a < b) return -1; + + return 0; +} + +function benchStats(n, highPrecision, avg, min, max, all) { + return { + n, + min, + max, + p75: all[MathCeil(n * (75 / 100)) - 1], + p99: all[MathCeil(n * (99 / 100)) - 1], + p995: all[MathCeil(n * (99.5 / 100)) - 1], + p999: all[MathCeil(n * (99.9 / 100)) - 1], + avg: !highPrecision ? (avg / n) : MathCeil(avg / n), + }; +} + +async function benchMeasure(timeBudget, desc) { + const fn = desc.fn; + let n = 0; + let avg = 0; + let wavg = 0; + const all = []; + let min = Infinity; + let max = -Infinity; + const lowPrecisionThresholdInNs = 1e4; + + // warmup step + let c = 0; + let iterations = 20; + let budget = 10 * 1e6; + + if (!desc.async) { + while (budget > 0 || iterations-- > 0) { + const t1 = benchNow(); + + fn(); + const iterationTime = benchNow() - t1; + + c++; + wavg += iterationTime; + budget -= iterationTime; + } + } else { + while (budget > 0 || iterations-- > 0) { + const t1 = benchNow(); - function compareMeasurements(a, b) { - if (a > b) return 1; - if (a < b) return -1; + await fn(); + const iterationTime = benchNow() - t1; - return 0; + c++; + wavg += iterationTime; + budget -= iterationTime; + } } - function benchStats(n, highPrecision, avg, min, max, all) { - return { - n, - min, - max, - p75: all[MathCeil(n * (75 / 100)) - 1], - p99: all[MathCeil(n * (99 / 100)) - 1], - p995: all[MathCeil(n * (99.5 / 100)) - 1], - p999: all[MathCeil(n * (99.9 / 100)) - 1], - avg: !highPrecision ? (avg / n) : MathCeil(avg / n), - }; - } + wavg /= c; - async function benchMeasure(timeBudget, desc) { - const fn = desc.fn; - let n = 0; - let avg = 0; - let wavg = 0; - const all = []; - let min = Infinity; - let max = -Infinity; - const lowPrecisionThresholdInNs = 1e4; - - // warmup step - let c = 0; - let iterations = 20; - let budget = 10 * 1e6; + // measure step + if (wavg > lowPrecisionThresholdInNs) { + let iterations = 10; + let budget = timeBudget * 1e6; if (!desc.async) { while (budget > 0 || iterations-- > 0) { @@ -902,9 +933,12 @@ fn(); const iterationTime = benchNow() - t1; - c++; - wavg += iterationTime; + n++; + avg += iterationTime; budget -= iterationTime; + ArrayPrototypePush(all, iterationTime); + if (iterationTime < min) min = iterationTime; + if (iterationTime > max) max = iterationTime; } } else { while (budget > 0 || iterations-- > 0) { @@ -913,520 +947,483 @@ await fn(); const iterationTime = benchNow() - t1; - c++; - wavg += iterationTime; + n++; + avg += iterationTime; budget -= iterationTime; + ArrayPrototypePush(all, iterationTime); + if (iterationTime < min) min = iterationTime; + if (iterationTime > max) max = iterationTime; } } + } else { + let iterations = 10; + let budget = timeBudget * 1e6; - wavg /= c; - - // measure step - if (wavg > lowPrecisionThresholdInNs) { - let iterations = 10; - let budget = timeBudget * 1e6; - - if (!desc.async) { - while (budget > 0 || iterations-- > 0) { - const t1 = benchNow(); - - fn(); - const iterationTime = benchNow() - t1; - - n++; - avg += iterationTime; - budget -= iterationTime; - ArrayPrototypePush(all, iterationTime); - if (iterationTime < min) min = iterationTime; - if (iterationTime > max) max = iterationTime; - } - } else { - while (budget > 0 || iterations-- > 0) { - const t1 = benchNow(); - - await fn(); - const iterationTime = benchNow() - t1; - - n++; - avg += iterationTime; - budget -= iterationTime; - ArrayPrototypePush(all, iterationTime); - if (iterationTime < min) min = iterationTime; - if (iterationTime > max) max = iterationTime; - } + if (!desc.async) { + while (budget > 0 || iterations-- > 0) { + const t1 = benchNow(); + for (let c = 0; c < lowPrecisionThresholdInNs; c++) fn(); + const iterationTime = (benchNow() - t1) / lowPrecisionThresholdInNs; + + n++; + avg += iterationTime; + ArrayPrototypePush(all, iterationTime); + if (iterationTime < min) min = iterationTime; + if (iterationTime > max) max = iterationTime; + budget -= iterationTime * lowPrecisionThresholdInNs; } } else { - let iterations = 10; - let budget = timeBudget * 1e6; - - if (!desc.async) { - while (budget > 0 || iterations-- > 0) { - const t1 = benchNow(); - for (let c = 0; c < lowPrecisionThresholdInNs; c++) fn(); - const iterationTime = (benchNow() - t1) / lowPrecisionThresholdInNs; - - n++; - avg += iterationTime; - ArrayPrototypePush(all, iterationTime); - if (iterationTime < min) min = iterationTime; - if (iterationTime > max) max = iterationTime; - budget -= iterationTime * lowPrecisionThresholdInNs; - } - } else { - while (budget > 0 || iterations-- > 0) { - const t1 = benchNow(); - for (let c = 0; c < lowPrecisionThresholdInNs; c++) await fn(); - const iterationTime = (benchNow() - t1) / lowPrecisionThresholdInNs; - - n++; - avg += iterationTime; - ArrayPrototypePush(all, iterationTime); - if (iterationTime < min) min = iterationTime; - if (iterationTime > max) max = iterationTime; - budget -= iterationTime * lowPrecisionThresholdInNs; - } + while (budget > 0 || iterations-- > 0) { + const t1 = benchNow(); + for (let c = 0; c < lowPrecisionThresholdInNs; c++) await fn(); + const iterationTime = (benchNow() - t1) / lowPrecisionThresholdInNs; + + n++; + avg += iterationTime; + ArrayPrototypePush(all, iterationTime); + if (iterationTime < min) min = iterationTime; + if (iterationTime > max) max = iterationTime; + budget -= iterationTime * lowPrecisionThresholdInNs; } } - - all.sort(compareMeasurements); - return benchStats(n, wavg > lowPrecisionThresholdInNs, avg, min, max, all); } - async function runBench(desc) { - let token = null; + all.sort(compareMeasurements); + return benchStats(n, wavg > lowPrecisionThresholdInNs, avg, min, max, all); +} - try { - if (desc.permissions) { - token = pledgePermissions(desc.permissions); - } +async function runBench(desc) { + let token = null; - if (desc.sanitizeExit) { - setExitHandler((exitCode) => { - assert( - false, - `Bench attempted to exit with exit code: ${exitCode}`, - ); - }); - } - - const benchTimeInMs = 500; - const stats = await benchMeasure(benchTimeInMs, desc); + try { + if (desc.permissions) { + token = pledgePermissions(desc.permissions); + } - return { ok: stats }; - } catch (error) { - return { failed: core.destructureError(error) }; - } finally { - if (bench.sanitizeExit) setExitHandler(null); - if (token !== null) restorePermissions(token); + if (desc.sanitizeExit) { + setExitHandler((exitCode) => { + assert( + false, + `Bench attempted to exit with exit code: ${exitCode}`, + ); + }); } - } - let origin = null; + const benchTimeInMs = 500; + const stats = await benchMeasure(benchTimeInMs, desc); - function getTestOrigin() { - if (origin == null) { - origin = ops.op_get_test_origin(); - } - return origin; + return { ok: stats }; + } catch (error) { + return { failed: core.destructureError(error) }; + } finally { + if (bench.sanitizeExit) setExitHandler(null); + if (token !== null) restorePermissions(token); } +} - function getBenchOrigin() { - if (origin == null) { - origin = ops.op_get_bench_origin(); - } - return origin; - } +let origin = null; - function benchNow() { - return ops.op_bench_now(); +function getTestOrigin() { + if (origin == null) { + origin = ops.op_get_test_origin(); } + return origin; +} - function enableTest() { - isTestSubcommand = true; +function getBenchOrigin() { + if (origin == null) { + origin = ops.op_get_bench_origin(); } + return origin; +} + +function benchNow() { + return ops.op_bench_now(); +} + +function enableTest() { + isTestSubcommand = true; +} + +function enableBench() { + isBenchSubcommand = true; +} + +async function runTests({ + shuffle = null, +} = {}) { + core.setMacrotaskCallback(handleOpSanitizerDelayMacrotask); + + const origin = getTestOrigin(); + const only = ArrayPrototypeFilter(testDescs, (test) => test.only); + const filtered = ArrayPrototypeFilter( + only.length > 0 ? only : testDescs, + (desc) => !desc.filteredOut, + ); + + ops.op_dispatch_test_event({ + plan: { + origin, + total: filtered.length, + filteredOut: testDescs.length - filtered.length, + usedOnly: only.length > 0, + }, + }); + + if (shuffle !== null) { + // http://en.wikipedia.org/wiki/Linear_congruential_generator + // Use BigInt for everything because the random seed is u64. + const nextInt = function (state) { + const m = 0x80000000n; + const a = 1103515245n; + const c = 12345n; + + return function (max) { + return state = ((a * state + c) % m) % BigInt(max); + }; + }(BigInt(shuffle)); - function enableBench() { - isBenchSubcommand = true; + for (let i = filtered.length - 1; i > 0; i--) { + const j = nextInt(i); + [filtered[i], filtered[j]] = [filtered[j], filtered[i]]; + } } - async function runTests({ - shuffle = null, - } = {}) { - core.setMacrotaskCallback(handleOpSanitizerDelayMacrotask); - - const origin = getTestOrigin(); - const only = ArrayPrototypeFilter(testDescs, (test) => test.only); - const filtered = ArrayPrototypeFilter( - only.length > 0 ? only : testDescs, - (desc) => !desc.filteredOut, - ); - + for (const desc of filtered) { + if (ops.op_tests_should_stop()) { + break; + } + ops.op_dispatch_test_event({ wait: desc.id }); + const earlier = DateNow(); + const result = await runTest(desc); + const elapsed = DateNow() - earlier; ops.op_dispatch_test_event({ - plan: { - origin, - total: filtered.length, - filteredOut: testDescs.length - filtered.length, - usedOnly: only.length > 0, - }, + result: [desc.id, result, elapsed], }); - - if (shuffle !== null) { - // http://en.wikipedia.org/wiki/Linear_congruential_generator - // Use BigInt for everything because the random seed is u64. - const nextInt = function (state) { - const m = 0x80000000n; - const a = 1103515245n; - const c = 12345n; - - return function (max) { - return state = ((a * state + c) % m) % BigInt(max); - }; - }(BigInt(shuffle)); - - for (let i = filtered.length - 1; i > 0; i--) { - const j = nextInt(i); - [filtered[i], filtered[j]] = [filtered[j], filtered[i]]; - } - } - - for (const desc of filtered) { - if (ops.op_tests_should_stop()) { - break; - } - ops.op_dispatch_test_event({ wait: desc.id }); - const earlier = DateNow(); - const result = await runTest(desc); - const elapsed = DateNow() - earlier; - ops.op_dispatch_test_event({ - result: [desc.id, result, elapsed], - }); - } } +} - async function runBenchmarks() { - core.setMacrotaskCallback(handleOpSanitizerDelayMacrotask); +async function runBenchmarks() { + core.setMacrotaskCallback(handleOpSanitizerDelayMacrotask); - const origin = getBenchOrigin(); - const originalConsole = globalThis.console; + const origin = getBenchOrigin(); + const originalConsole = globalThis.console; - globalThis.console = new Console((s) => { - ops.op_dispatch_bench_event({ output: s }); - }); + globalThis.console = new Console((s) => { + ops.op_dispatch_bench_event({ output: s }); + }); - const only = ArrayPrototypeFilter(benchDescs, (bench) => bench.only); - const filtered = ArrayPrototypeFilter( - only.length > 0 ? only : benchDescs, - (desc) => !desc.filteredOut && !desc.ignore, - ); + const only = ArrayPrototypeFilter(benchDescs, (bench) => bench.only); + const filtered = ArrayPrototypeFilter( + only.length > 0 ? only : benchDescs, + (desc) => !desc.filteredOut && !desc.ignore, + ); - let groups = new Set(); - // make sure ungrouped benchmarks are placed above grouped - groups.add(undefined); + let groups = new Set(); + // make sure ungrouped benchmarks are placed above grouped + groups.add(undefined); - for (const desc of filtered) { - desc.group ||= undefined; - groups.add(desc.group); - } + for (const desc of filtered) { + desc.group ||= undefined; + groups.add(desc.group); + } - groups = ArrayFrom(groups); - ArrayPrototypeSort( - filtered, - (a, b) => groups.indexOf(a.group) - groups.indexOf(b.group), - ); + groups = ArrayFrom(groups); + ArrayPrototypeSort( + filtered, + (a, b) => groups.indexOf(a.group) - groups.indexOf(b.group), + ); + + ops.op_dispatch_bench_event({ + plan: { + origin, + total: filtered.length, + usedOnly: only.length > 0, + names: ArrayPrototypeMap(filtered, (desc) => desc.name), + }, + }); + for (const desc of filtered) { + desc.baseline = !!desc.baseline; + ops.op_dispatch_bench_event({ wait: desc.id }); ops.op_dispatch_bench_event({ - plan: { - origin, - total: filtered.length, - usedOnly: only.length > 0, - names: ArrayPrototypeMap(filtered, (desc) => desc.name), - }, + result: [desc.id, await runBench(desc)], }); - - for (const desc of filtered) { - desc.baseline = !!desc.baseline; - ops.op_dispatch_bench_event({ wait: desc.id }); - ops.op_dispatch_bench_event({ - result: [desc.id, await runBench(desc)], - }); - } - - globalThis.console = originalConsole; } - function getFullName(desc) { - if ("parent" in desc) { - return `${desc.parent.name} > ${desc.name}`; - } - return desc.name; - } + globalThis.console = originalConsole; +} - function usesSanitizer(desc) { - return desc.sanitizeResources || desc.sanitizeOps || desc.sanitizeExit; +function getFullName(desc) { + if ("parent" in desc) { + return `${desc.parent.name} > ${desc.name}`; } + return desc.name; +} - function canStreamReporting(desc) { - let currentDesc = desc; - while (currentDesc != null) { - if (!usesSanitizer(currentDesc)) { - return false; - } - currentDesc = currentDesc.parent; - } - for (const childDesc of MapPrototypeGet(testStates, desc.id).children) { - const state = MapPrototypeGet(testStates, childDesc.id); - if (!usesSanitizer(childDesc) && !state.finalized) { - return false; - } - } - return true; - } +function usesSanitizer(desc) { + return desc.sanitizeResources || desc.sanitizeOps || desc.sanitizeExit; +} - function stepReportWait(desc) { - const state = MapPrototypeGet(testStates, desc.id); - if (state.reportedWait) { - return; +function canStreamReporting(desc) { + let currentDesc = desc; + while (currentDesc != null) { + if (!usesSanitizer(currentDesc)) { + return false; } - ops.op_dispatch_test_event({ stepWait: desc.id }); - state.reportedWait = true; + currentDesc = currentDesc.parent; } - - function stepReportResult(desc) { - const state = MapPrototypeGet(testStates, desc.id); - if (state.reportedResult) { - return; - } - stepReportWait(desc); - for (const childDesc of state.children) { - stepReportResult(childDesc); - } - let result; - if (state.status == "pending" || state.status == "failed") { - result = { - [state.status]: state.error && core.destructureError(state.error), - }; - } else { - result = state.status; + for (const childDesc of MapPrototypeGet(testStates, desc.id).children) { + const state = MapPrototypeGet(testStates, childDesc.id); + if (!usesSanitizer(childDesc) && !state.finalized) { + return false; } - ops.op_dispatch_test_event({ - stepResult: [desc.id, result, state.elapsed], - }); - state.reportedResult = true; } + return true; +} - function failedChildStepsCount(desc) { - return ArrayPrototypeFilter( - MapPrototypeGet(testStates, desc.id).children, - (d) => MapPrototypeGet(testStates, d.id).status === "failed", - ).length; +function stepReportWait(desc) { + const state = MapPrototypeGet(testStates, desc.id); + if (state.reportedWait) { + return; } - - /** If a test validation error already occurred then don't bother checking - * the sanitizers as that will create extra noise. - */ - function shouldSkipSanitizers(desc) { - try { - testStepPostValidation(desc); - return false; - } catch { - return true; - } + ops.op_dispatch_test_event({ stepWait: desc.id }); + state.reportedWait = true; +} + +function stepReportResult(desc) { + const state = MapPrototypeGet(testStates, desc.id); + if (state.reportedResult) { + return; + } + stepReportWait(desc); + for (const childDesc of state.children) { + stepReportResult(childDesc); + } + let result; + if (state.status == "pending" || state.status == "failed") { + result = { + [state.status]: state.error && core.destructureError(state.error), + }; + } else { + result = state.status; + } + ops.op_dispatch_test_event({ + stepResult: [desc.id, result, state.elapsed], + }); + state.reportedResult = true; +} + +function failedChildStepsCount(desc) { + return ArrayPrototypeFilter( + MapPrototypeGet(testStates, desc.id).children, + (d) => MapPrototypeGet(testStates, d.id).status === "failed", + ).length; +} + +/** If a test validation error already occurred then don't bother checking + * the sanitizers as that will create extra noise. + */ +function shouldSkipSanitizers(desc) { + try { + testStepPostValidation(desc); + return false; + } catch { + return true; + } +} + +/** @param desc {TestDescription | TestStepDescription} */ +function createTestContext(desc) { + let parent; + let level; + let rootId; + let rootName; + if ("parent" in desc) { + parent = MapPrototypeGet(testStates, desc.parent.id).context; + level = desc.level; + rootId = desc.rootId; + rootName = desc.rootName; + } else { + parent = undefined; + level = 0; + rootId = desc.id; + rootName = desc.name; } + return { + [SymbolToStringTag]: "TestContext", + /** + * The current test name. + */ + name: desc.name, + /** + * Parent test context. + */ + parent, + /** + * File Uri of the test code. + */ + origin: desc.origin, + /** + * @param nameOrFnOrOptions {string | TestStepDefinition | ((t: TestContext) => void | Promise)} + * @param maybeFn {((t: TestContext) => void | Promise) | undefined} + */ + async step(nameOrFnOrOptions, maybeFn) { + if (MapPrototypeGet(testStates, desc.id).finalized) { + throw new Error( + "Cannot run test step after parent scope has finished execution. " + + "Ensure any `.step(...)` calls are executed before their parent scope completes execution.", + ); + } - /** @param desc {TestDescription | TestStepDescription} */ - function createTestContext(desc) { - let parent; - let level; - let rootId; - let rootName; - if ("parent" in desc) { - parent = MapPrototypeGet(testStates, desc.parent.id).context; - level = desc.level; - rootId = desc.rootId; - rootName = desc.rootName; - } else { - parent = undefined; - level = 0; - rootId = desc.id; - rootName = desc.name; - } - return { - [SymbolToStringTag]: "TestContext", - /** - * The current test name. - */ - name: desc.name, - /** - * Parent test context. - */ - parent, - /** - * File Uri of the test code. - */ - origin: desc.origin, - /** - * @param nameOrFnOrOptions {string | TestStepDefinition | ((t: TestContext) => void | Promise)} - * @param maybeFn {((t: TestContext) => void | Promise) | undefined} - */ - async step(nameOrFnOrOptions, maybeFn) { - if (MapPrototypeGet(testStates, desc.id).finalized) { - throw new Error( - "Cannot run test step after parent scope has finished execution. " + - "Ensure any `.step(...)` calls are executed before their parent scope completes execution.", - ); + let stepDesc; + if (typeof nameOrFnOrOptions === "string") { + if (!(ObjectPrototypeIsPrototypeOf(FunctionPrototype, maybeFn))) { + throw new TypeError("Expected function for second argument."); } - - let stepDesc; - if (typeof nameOrFnOrOptions === "string") { - if (!(ObjectPrototypeIsPrototypeOf(FunctionPrototype, maybeFn))) { - throw new TypeError("Expected function for second argument."); - } - stepDesc = { - name: nameOrFnOrOptions, - fn: maybeFn, - }; - } else if (typeof nameOrFnOrOptions === "function") { - if (!nameOrFnOrOptions.name) { - throw new TypeError("The step function must have a name."); - } - if (maybeFn != undefined) { - throw new TypeError( - "Unexpected second argument to TestContext.step()", - ); - } - stepDesc = { - name: nameOrFnOrOptions.name, - fn: nameOrFnOrOptions, - }; - } else if (typeof nameOrFnOrOptions === "object") { - stepDesc = nameOrFnOrOptions; - } else { + stepDesc = { + name: nameOrFnOrOptions, + fn: maybeFn, + }; + } else if (typeof nameOrFnOrOptions === "function") { + if (!nameOrFnOrOptions.name) { + throw new TypeError("The step function must have a name."); + } + if (maybeFn != undefined) { throw new TypeError( - "Expected a test definition or name and function.", + "Unexpected second argument to TestContext.step()", ); } - stepDesc.ignore ??= false; - stepDesc.sanitizeOps ??= desc.sanitizeOps; - stepDesc.sanitizeResources ??= desc.sanitizeResources; - stepDesc.sanitizeExit ??= desc.sanitizeExit; - stepDesc.origin = getTestOrigin(); - const jsError = core.destructureError(new Error()); - stepDesc.location = { - fileName: jsError.frames[1].fileName, - lineNumber: jsError.frames[1].lineNumber, - columnNumber: jsError.frames[1].columnNumber, - }; - stepDesc.level = level + 1; - stepDesc.parent = desc; - stepDesc.rootId = rootId; - stepDesc.rootName = rootName; - const { id } = ops.op_register_test_step(stepDesc); - stepDesc.id = id; - const state = { - context: createTestContext(stepDesc), - children: [], - finalized: false, - status: "pending", - error: null, - elapsed: null, - reportedWait: false, - reportedResult: false, + stepDesc = { + name: nameOrFnOrOptions.name, + fn: nameOrFnOrOptions, }; - MapPrototypeSet(testStates, stepDesc.id, state); - ArrayPrototypePush( - MapPrototypeGet(testStates, stepDesc.parent.id).children, - stepDesc, + } else if (typeof nameOrFnOrOptions === "object") { + stepDesc = nameOrFnOrOptions; + } else { + throw new TypeError( + "Expected a test definition or name and function.", ); + } + stepDesc.ignore ??= false; + stepDesc.sanitizeOps ??= desc.sanitizeOps; + stepDesc.sanitizeResources ??= desc.sanitizeResources; + stepDesc.sanitizeExit ??= desc.sanitizeExit; + stepDesc.origin = getTestOrigin(); + const jsError = core.destructureError(new Error()); + stepDesc.location = { + fileName: jsError.frames[1].fileName, + lineNumber: jsError.frames[1].lineNumber, + columnNumber: jsError.frames[1].columnNumber, + }; + stepDesc.level = level + 1; + stepDesc.parent = desc; + stepDesc.rootId = rootId; + stepDesc.rootName = rootName; + const { id } = ops.op_register_test_step(stepDesc); + stepDesc.id = id; + const state = { + context: createTestContext(stepDesc), + children: [], + finalized: false, + status: "pending", + error: null, + elapsed: null, + reportedWait: false, + reportedResult: false, + }; + MapPrototypeSet(testStates, stepDesc.id, state); + ArrayPrototypePush( + MapPrototypeGet(testStates, stepDesc.parent.id).children, + stepDesc, + ); - try { - if (stepDesc.ignore) { - state.status = "ignored"; - state.finalized = true; - if (canStreamReporting(stepDesc)) { - stepReportResult(stepDesc); - } - return false; + try { + if (stepDesc.ignore) { + state.status = "ignored"; + state.finalized = true; + if (canStreamReporting(stepDesc)) { + stepReportResult(stepDesc); } + return false; + } - const testFn = wrapTestFnWithSanitizers(stepDesc.fn, stepDesc); - const start = DateNow(); + const testFn = wrapTestFnWithSanitizers(stepDesc.fn, stepDesc); + const start = DateNow(); - try { - await testFn(stepDesc); + try { + await testFn(stepDesc); - if (failedChildStepsCount(stepDesc) > 0) { - state.status = "failed"; - } else { - state.status = "ok"; - } - } catch (error) { - state.error = error; + if (failedChildStepsCount(stepDesc) > 0) { state.status = "failed"; + } else { + state.status = "ok"; } + } catch (error) { + state.error = error; + state.status = "failed"; + } - state.elapsed = DateNow() - start; + state.elapsed = DateNow() - start; - if (MapPrototypeGet(testStates, stepDesc.parent.id).finalized) { - // always point this test out as one that was still running - // if the parent step finalized - state.status = "pending"; - } + if (MapPrototypeGet(testStates, stepDesc.parent.id).finalized) { + // always point this test out as one that was still running + // if the parent step finalized + state.status = "pending"; + } - state.finalized = true; + state.finalized = true; - if (state.reportedWait && canStreamReporting(stepDesc)) { - stepReportResult(stepDesc); - } + if (state.reportedWait && canStreamReporting(stepDesc)) { + stepReportResult(stepDesc); + } - return state.status === "ok"; - } finally { - if (canStreamReporting(stepDesc.parent)) { - const parentState = MapPrototypeGet(testStates, stepDesc.parent.id); - // flush any buffered steps - for (const childDesc of parentState.children) { - stepReportResult(childDesc); - } + return state.status === "ok"; + } finally { + if (canStreamReporting(stepDesc.parent)) { + const parentState = MapPrototypeGet(testStates, stepDesc.parent.id); + // flush any buffered steps + for (const childDesc of parentState.children) { + stepReportResult(childDesc); } } - }, - }; - } - - /** - * @template T {Function} - * @param testFn {T} - * @param opts {{ - * sanitizeOps: boolean, - * sanitizeResources: boolean, - * sanitizeExit: boolean, - * }} - * @returns {T} - */ - function wrapTestFnWithSanitizers(testFn, opts) { - testFn = assertTestStepScopes(testFn); - - if (opts.sanitizeOps) { - testFn = assertOps(testFn); - } - if (opts.sanitizeResources) { - testFn = assertResources(testFn); - } - if (opts.sanitizeExit) { - testFn = assertExit(testFn, true); - } - return testFn; - } - - window.__bootstrap.internals = { - ...window.__bootstrap.internals ?? {}, - testing: { - runTests, - runBenchmarks, - enableTest, - enableBench, + } }, }; - - window.__bootstrap.denoNs.bench = bench; - window.__bootstrap.denoNs.test = test; -})(this); +} + +/** + * @template T {Function} + * @param testFn {T} + * @param opts {{ + * sanitizeOps: boolean, + * sanitizeResources: boolean, + * sanitizeExit: boolean, + * }} + * @returns {T} + */ +function wrapTestFnWithSanitizers(testFn, opts) { + testFn = assertTestStepScopes(testFn); + + if (opts.sanitizeOps) { + testFn = assertOps(testFn); + } + if (opts.sanitizeResources) { + testFn = assertResources(testFn); + } + if (opts.sanitizeExit) { + testFn = assertExit(testFn, true); + } + return testFn; +} + +internals.testing = { + runTests, + runBenchmarks, + enableTest, + enableBench, +}; + +import { denoNs } from "internal:runtime/js/90_deno_ns.js"; +denoNs.bench = bench; +denoNs.test = test; diff --git a/cli/lsp/cache.rs b/cli/lsp/cache.rs index bad93217674a4d..f047e5fd4ec6eb 100644 --- a/cli/lsp/cache.rs +++ b/cli/lsp/cache.rs @@ -84,7 +84,7 @@ impl CacheMetadata { } fn refresh(&self, specifier: &ModuleSpecifier) -> Option { - if specifier.scheme() == "file" || specifier.scheme() == "npm" { + if matches!(specifier.scheme(), "file" | "npm" | "node") { return None; } let cache_filename = self.cache.get_cache_filename(specifier)?; diff --git a/cli/lsp/completions.rs b/cli/lsp/completions.rs index b89aec6c982396..0c27500e2a1639 100644 --- a/cli/lsp/completions.rs +++ b/cli/lsp/completions.rs @@ -681,6 +681,7 @@ mod tests { &text_info, &Range { specifier: ModuleSpecifier::parse("https://deno.land").unwrap(), + text: "".to_string(), start: deno_graph::Position { line: 0, character: 0, @@ -705,6 +706,7 @@ mod tests { &text_info, &Range { specifier: ModuleSpecifier::parse("https://deno.land").unwrap(), + text: "".to_string(), start: deno_graph::Position { line: 0, character: 0, diff --git a/cli/lsp/diagnostics.rs b/cli/lsp/diagnostics.rs index 0a6f3312697ff0..f938ba6f450967 100644 --- a/cli/lsp/diagnostics.rs +++ b/cli/lsp/diagnostics.rs @@ -16,7 +16,6 @@ use crate::args::LintOptions; use crate::graph_util; use crate::graph_util::enhanced_resolution_error_message; use crate::node; -use crate::npm::NpmPackageReference; use crate::tools::lint::get_configured_rules; use deno_ast::MediaType; @@ -27,8 +26,9 @@ use deno_core::serde::Deserialize; use deno_core::serde_json; use deno_core::serde_json::json; use deno_core::ModuleSpecifier; +use deno_graph::npm::NpmPackageReqReference; +use deno_graph::Resolution; use deno_graph::ResolutionError; -use deno_graph::Resolved; use deno_graph::SpecifierError; use deno_lint::rules::LintRule; use deno_runtime::tokio_util::create_basic_runtime; @@ -614,7 +614,7 @@ pub enum DenoDiagnostic { /// A data module was not found in the cache. NoCacheData(ModuleSpecifier), /// A remote npm package reference was not found in the cache. - NoCacheNpm(NpmPackageReference, ModuleSpecifier), + NoCacheNpm(NpmPackageReqReference, ModuleSpecifier), /// A local module was not found on the local file system. NoLocal(ModuleSpecifier), /// The specifier resolved to a remote specifier that was redirected to @@ -852,18 +852,17 @@ impl DenoDiagnostic { } } -fn diagnose_resolved( +fn diagnose_resolution( diagnostics: &mut Vec, snapshot: &language_server::StateSnapshot, - resolved: &deno_graph::Resolved, + resolution: &Resolution, is_dynamic: bool, maybe_assert_type: Option<&str>, ) { - match resolved { - Resolved::Ok { - specifier, range, .. - } => { - let range = documents::to_lsp_range(range); + match resolution { + Resolution::Ok(resolved) => { + let specifier = &resolved.specifier; + let range = documents::to_lsp_range(&resolved.range); // If the module is a remote module and has a `X-Deno-Warning` header, we // want a warning diagnostic with that message. if let Some(metadata) = snapshot.cache_metadata.get(specifier) { @@ -906,12 +905,14 @@ fn diagnose_resolved( .push(DenoDiagnostic::NoAssertType.to_lsp_diagnostic(&range)), } } - } else if let Ok(pkg_ref) = NpmPackageReference::from_specifier(specifier) + } else if let Ok(pkg_ref) = + NpmPackageReqReference::from_specifier(specifier) { if let Some(npm_resolver) = &snapshot.maybe_npm_resolver { // show diagnostics for npm package references that aren't cached if npm_resolver - .resolve_package_folder_from_deno_module(&pkg_ref.req) + .resolution() + .resolve_pkg_id_from_pkg_req(&pkg_ref.req) .is_err() { diagnostics.push( @@ -930,9 +931,10 @@ fn diagnose_resolved( } else if let Some(npm_resolver) = &snapshot.maybe_npm_resolver { // check that a @types/node package exists in the resolver let types_node_ref = - NpmPackageReference::from_str("npm:@types/node").unwrap(); + NpmPackageReqReference::from_str("npm:@types/node").unwrap(); if npm_resolver - .resolve_package_folder_from_deno_module(&types_node_ref.req) + .resolution() + .resolve_pkg_id_from_pkg_req(&types_node_ref.req) .is_err() { diagnostics.push( @@ -959,8 +961,8 @@ fn diagnose_resolved( } // The specifier resolution resulted in an error, so we want to issue a // diagnostic for that. - Resolved::Err(err) => diagnostics.push( - DenoDiagnostic::ResolutionError(err.clone()) + Resolution::Err(err) => diagnostics.push( + DenoDiagnostic::ResolutionError(*err.clone()) .to_lsp_diagnostic(&documents::to_lsp_range(err.range())), ), _ => (), @@ -984,31 +986,28 @@ fn diagnose_dependency( } if let Some(import_map) = &snapshot.maybe_import_map { - if let Resolved::Ok { - specifier, range, .. - } = &dependency.maybe_code - { - if let Some(to) = import_map.lookup(specifier, referrer) { + if let Resolution::Ok(resolved) = &dependency.maybe_code { + if let Some(to) = import_map.lookup(&resolved.specifier, referrer) { if dependency_key != to { diagnostics.push( DenoDiagnostic::ImportMapRemap { from: dependency_key.to_string(), to, } - .to_lsp_diagnostic(&documents::to_lsp_range(range)), + .to_lsp_diagnostic(&documents::to_lsp_range(&resolved.range)), ); } } } } - diagnose_resolved( + diagnose_resolution( diagnostics, snapshot, &dependency.maybe_code, dependency.is_dynamic, dependency.maybe_assert_type.as_deref(), ); - diagnose_resolved( + diagnose_resolution( diagnostics, snapshot, &dependency.maybe_type, @@ -1064,6 +1063,7 @@ mod tests { use crate::lsp::documents::Documents; use crate::lsp::documents::LanguageId; use crate::lsp::language_server::StateSnapshot; + use pretty_assertions::assert_eq; use std::path::Path; use std::path::PathBuf; use std::sync::Arc; diff --git a/cli/lsp/documents.rs b/cli/lsp/documents.rs index c32efe89c4ae45..0acfdbe1fe3c5a 100644 --- a/cli/lsp/documents.rs +++ b/cli/lsp/documents.rs @@ -5,6 +5,7 @@ use super::text::LineIndex; use super::tsc; use super::tsc::AssetDocument; +use crate::args::package_json; use crate::args::ConfigFile; use crate::args::JsxImportSourceConfig; use crate::cache::CachedUrlMetadata; @@ -13,13 +14,15 @@ use crate::cache::HttpCache; use crate::file_fetcher::get_source_from_bytes; use crate::file_fetcher::map_content_type; use crate::file_fetcher::SUPPORTED_SCHEMES; +use crate::lsp::logging::lsp_log; use crate::node; use crate::node::node_resolve_npm_reference; use crate::node::NodeResolution; -use crate::npm::NpmPackageReference; -use crate::npm::NpmPackageReq; use crate::npm::NpmPackageResolver; -use crate::resolver::CliResolver; +use crate::npm::NpmRegistryApi; +use crate::npm::NpmResolution; +use crate::npm::PackageJsonDepsInstaller; +use crate::resolver::CliGraphResolver; use crate::util::path::specifier_to_file_path; use crate::util::text_encoding; @@ -32,10 +35,14 @@ use deno_core::futures::future; use deno_core::parking_lot::Mutex; use deno_core::url; use deno_core::ModuleSpecifier; +use deno_graph::npm::NpmPackageReq; +use deno_graph::npm::NpmPackageReqReference; use deno_graph::GraphImport; -use deno_graph::Resolved; +use deno_graph::Resolution; use deno_runtime::deno_node::NodeResolutionMode; +use deno_runtime::deno_node::PackageJson; use deno_runtime::permissions::PermissionsContainer; +use indexmap::IndexMap; use once_cell::sync::Lazy; use std::collections::BTreeMap; use std::collections::HashMap; @@ -242,8 +249,8 @@ impl AssetOrDocument { #[derive(Debug, Default)] struct DocumentDependencies { - deps: BTreeMap, - maybe_types_dependency: Option<(String, Resolved)>, + deps: IndexMap, + maybe_types_dependency: Option, } impl DocumentDependencies { @@ -255,7 +262,7 @@ impl DocumentDependencies { } } - pub fn from_module(module: &deno_graph::Module) -> Self { + pub fn from_module(module: &deno_graph::EsmModule) -> Self { Self { deps: module.dependencies.clone(), maybe_types_dependency: module.maybe_types_dependency.clone(), @@ -263,7 +270,7 @@ impl DocumentDependencies { } } -type ModuleResult = Result; +type ModuleResult = Result; type ParsedSourceResult = Result; #[derive(Debug)] @@ -293,7 +300,7 @@ impl Document { fs_version: String, maybe_headers: Option>, text_info: SourceTextInfo, - maybe_resolver: Option<&dyn deno_graph::source::Resolver>, + resolver: &dyn deno_graph::source::Resolver, ) -> Self { // we only ever do `Document::new` on on disk resources that are supposed to // be diagnosable, unlike `Document::open`, so it is safe to unconditionally @@ -302,7 +309,7 @@ impl Document { &specifier, text_info.clone(), maybe_headers.as_ref(), - maybe_resolver, + resolver, ); let dependencies = Arc::new(DocumentDependencies::from_maybe_module(&maybe_module)); @@ -324,7 +331,7 @@ impl Document { fn maybe_with_new_resolver( &self, - maybe_resolver: Option<&dyn deno_graph::source::Resolver>, + resolver: &dyn deno_graph::source::Resolver, ) -> Option { let parsed_source_result = match &self.0.maybe_parsed_source { Some(parsed_source_result) => parsed_source_result.clone(), @@ -334,7 +341,7 @@ impl Document { &self.0.specifier, &parsed_source_result, self.0.maybe_headers.as_ref(), - maybe_resolver, + resolver, )); let dependencies = Arc::new(DocumentDependencies::from_maybe_module(&maybe_module)); @@ -360,7 +367,7 @@ impl Document { version: i32, language_id: LanguageId, content: Arc, - maybe_resolver: Option<&dyn deno_graph::source::Resolver>, + resolver: &dyn deno_graph::source::Resolver, ) -> Self { let maybe_headers = language_id.as_headers(); let text_info = SourceTextInfo::new(content); @@ -369,7 +376,7 @@ impl Document { &specifier, text_info.clone(), maybe_headers, - maybe_resolver, + resolver, ) } else { (None, None) @@ -396,7 +403,7 @@ impl Document { &self, version: i32, changes: Vec, - maybe_resolver: Option<&dyn deno_graph::source::Resolver>, + resolver: &dyn deno_graph::source::Resolver, ) -> Result { let mut content = self.0.text_info.text_str().to_string(); let mut line_index = self.0.line_index.clone(); @@ -431,7 +438,7 @@ impl Document { &self.0.specifier, text_info.clone(), maybe_headers, - maybe_resolver, + resolver, ) } else { (None, None) @@ -508,13 +515,12 @@ impl Document { self.0.maybe_lsp_version.is_some() } - pub fn maybe_types_dependency(&self) -> deno_graph::Resolved { - if let Some((_, maybe_dep)) = - self.0.dependencies.maybe_types_dependency.as_ref() + pub fn maybe_types_dependency(&self) -> Resolution { + if let Some(types_dep) = self.0.dependencies.maybe_types_dependency.as_ref() { - maybe_dep.clone() + types_dep.dependency.clone() } else { - deno_graph::Resolved::None + Resolution::None } } @@ -543,9 +549,7 @@ impl Document { self.0.maybe_lsp_version } - fn maybe_module( - &self, - ) -> Option<&Result> { + fn maybe_esm_module(&self) -> Option<&ModuleResult> { self.0.maybe_module.as_ref() } @@ -573,7 +577,7 @@ impl Document { } } - pub fn dependencies(&self) -> &BTreeMap { + pub fn dependencies(&self) -> &IndexMap { &self.0.dependencies.deps } @@ -584,7 +588,7 @@ impl Document { &self, position: &lsp::Position, ) -> Option<(String, deno_graph::Dependency, deno_graph::Range)> { - let module = self.maybe_module()?.as_ref().ok()?; + let module = self.maybe_esm_module()?.as_ref().ok()?; let position = deno_graph::Position { line: position.line as usize, character: position.character as usize, @@ -597,20 +601,23 @@ impl Document { } } -pub fn to_hover_text(result: &Resolved) -> String { +pub fn to_hover_text(result: &Resolution) -> String { match result { - Resolved::Ok { specifier, .. } => match specifier.scheme() { - "data" => "_(a data url)_".to_string(), - "blob" => "_(a blob url)_".to_string(), - _ => format!( - "{}​{}", - &specifier[..url::Position::AfterScheme], - &specifier[url::Position::AfterScheme..], - ) - .replace('@', "​@"), - }, - Resolved::Err(_) => "_[errored]_".to_string(), - Resolved::None => "_[missing]_".to_string(), + Resolution::Ok(resolved) => { + let specifier = &resolved.specifier; + match specifier.scheme() { + "data" => "_(a data url)_".to_string(), + "blob" => "_(a blob url)_".to_string(), + _ => format!( + "{}​{}", + &specifier[..url::Position::AfterScheme], + &specifier[url::Position::AfterScheme..], + ) + .replace('@', "​@"), + } + } + Resolution::Err(_) => "_[errored]_".to_string(), + Resolution::None => "_[missing]_".to_string(), } } @@ -713,7 +720,7 @@ impl FileSystemDocuments { pub fn get( &mut self, cache: &HttpCache, - maybe_resolver: Option<&dyn deno_graph::source::Resolver>, + resolver: &dyn deno_graph::source::Resolver, specifier: &ModuleSpecifier, ) -> Option { let fs_version = get_document_path(cache, specifier) @@ -721,7 +728,7 @@ impl FileSystemDocuments { let file_system_doc = self.docs.get(specifier); if file_system_doc.map(|d| d.fs_version().to_string()) != fs_version { // attempt to update the file on the file system - self.refresh_document(cache, maybe_resolver, specifier) + self.refresh_document(cache, resolver, specifier) } else { file_system_doc.cloned() } @@ -732,7 +739,7 @@ impl FileSystemDocuments { fn refresh_document( &mut self, cache: &HttpCache, - maybe_resolver: Option<&dyn deno_graph::source::Resolver>, + resolver: &dyn deno_graph::source::Resolver, specifier: &ModuleSpecifier, ) -> Option { let path = get_document_path(cache, specifier)?; @@ -747,7 +754,7 @@ impl FileSystemDocuments { fs_version, None, SourceTextInfo::from_string(content), - maybe_resolver, + resolver, ) } else { let cache_filename = cache.get_cache_filename(specifier)?; @@ -761,7 +768,7 @@ impl FileSystemDocuments { fs_version, maybe_headers, SourceTextInfo::from_string(content), - maybe_resolver, + resolver, ) }; self.dirty = true; @@ -771,10 +778,10 @@ impl FileSystemDocuments { pub fn refresh_dependencies( &mut self, - maybe_resolver: Option<&dyn deno_graph::source::Resolver>, + resolver: &dyn deno_graph::source::Resolver, ) { for doc in self.docs.values_mut() { - if let Some(new_doc) = doc.maybe_with_new_resolver(maybe_resolver) { + if let Some(new_doc) = doc.maybe_with_new_resolver(resolver) { *doc = new_doc; } } @@ -815,9 +822,11 @@ pub struct Documents { imports: Arc>, /// A resolver that takes into account currently loaded import map and JSX /// settings. - maybe_resolver: Option, - /// The npm package requirements. - npm_reqs: Arc>, + resolver: CliGraphResolver, + /// The npm package requirements found in a package.json file. + npm_package_json_reqs: Arc>, + /// The npm package requirements found in npm specifiers. + npm_specifier_reqs: Arc>, /// Gets if any document had a node: specifier such that a @types/node package /// should be injected. has_injected_types_node_package: bool, @@ -835,8 +844,9 @@ impl Documents { file_system_docs: Default::default(), resolver_config_hash: 0, imports: Default::default(), - maybe_resolver: None, - npm_reqs: Default::default(), + resolver: CliGraphResolver::default(), + npm_package_json_reqs: Default::default(), + npm_specifier_reqs: Default::default(), has_injected_types_node_package: false, specifier_resolver: Arc::new(SpecifierResolver::new(location)), } @@ -853,13 +863,13 @@ impl Documents { language_id: LanguageId, content: Arc, ) -> Document { - let maybe_resolver = self.get_maybe_resolver(); + let resolver = self.get_resolver(); let document = Document::open( specifier.clone(), version, language_id, content, - maybe_resolver, + resolver, ); let mut file_system_docs = self.file_system_docs.lock(); file_system_docs.docs.remove(&specifier); @@ -894,7 +904,7 @@ impl Documents { Ok, )?; self.dirty = true; - let doc = doc.with_change(version, changes, self.get_maybe_resolver())?; + let doc = doc.with_change(version, changes, self.get_resolver())?; self.open_docs.insert(doc.specifier().clone(), doc.clone()); Ok(doc) } @@ -927,12 +937,7 @@ impl Documents { specifier: &str, referrer: &ModuleSpecifier, ) -> bool { - let maybe_resolver = self.get_maybe_resolver(); - let maybe_specifier = if let Some(resolver) = maybe_resolver { - resolver.resolve(specifier, referrer).ok() - } else { - deno_core::resolve_import(specifier, referrer.as_str()).ok() - }; + let maybe_specifier = self.get_resolver().resolve(specifier, referrer).ok(); if let Some(import_specifier) = maybe_specifier { self.exists(&import_specifier) } else { @@ -973,9 +978,15 @@ impl Documents { } /// Returns a collection of npm package requirements. - pub fn npm_package_reqs(&mut self) -> HashSet { + pub fn npm_package_reqs(&mut self) -> Vec { self.calculate_dependents_if_dirty(); - (*self.npm_reqs).clone() + let mut reqs = Vec::with_capacity( + self.npm_package_json_reqs.len() + self.npm_specifier_reqs.len(), + ); + // resolve the package.json reqs first, then the npm specifiers + reqs.extend(self.npm_package_json_reqs.iter().cloned()); + reqs.extend(self.npm_specifier_reqs.iter().cloned()); + reqs } /// Returns if a @types/node package was injected into the npm @@ -991,7 +1002,7 @@ impl Documents { Some(document.clone()) } else { let mut file_system_docs = self.file_system_docs.lock(); - file_system_docs.get(&self.cache, self.get_maybe_resolver(), &specifier) + file_system_docs.get(&self.cache, self.get_resolver(), &specifier) } } @@ -1094,30 +1105,22 @@ impl Documents { } else if let Some(dep) = dependencies.as_ref().and_then(|d| d.deps.get(&specifier)) { - if let Resolved::Ok { specifier, .. } = &dep.maybe_type { + if let Some(specifier) = dep.maybe_type.maybe_specifier() { results.push(self.resolve_dependency(specifier, maybe_npm_resolver)); - } else if let Resolved::Ok { specifier, .. } = &dep.maybe_code { + } else if let Some(specifier) = dep.maybe_code.maybe_specifier() { results.push(self.resolve_dependency(specifier, maybe_npm_resolver)); } else { results.push(None); } - } else if let Some(Resolved::Ok { specifier, .. }) = - self.resolve_imports_dependency(&specifier) + } else if let Some(specifier) = self + .resolve_imports_dependency(&specifier) + .and_then(|r| r.maybe_specifier()) { results.push(self.resolve_dependency(specifier, maybe_npm_resolver)); - } else if let Ok(npm_ref) = NpmPackageReference::from_str(&specifier) { - results.push(maybe_npm_resolver.map(|npm_resolver| { - NodeResolution::into_specifier_and_media_type( - node_resolve_npm_reference( - &npm_ref, - NodeResolutionMode::Types, - npm_resolver, - &mut PermissionsContainer::allow_all(), - ) - .ok() - .flatten(), - ) - })); + } else if let Ok(npm_req_ref) = + NpmPackageReqReference::from_str(&specifier) + { + results.push(node_resolve_npm_req_ref(npm_req_ref, maybe_npm_resolver)); } else { results.push(None); } @@ -1161,10 +1164,14 @@ impl Documents { &mut self, maybe_import_map: Option>, maybe_config_file: Option<&ConfigFile>, + maybe_package_json: Option<&PackageJson>, + npm_registry_api: NpmRegistryApi, + npm_resolution: NpmResolution, ) { fn calculate_resolver_config_hash( maybe_import_map: Option<&import_map::ImportMap>, maybe_jsx_config: Option<&JsxImportSourceConfig>, + maybe_package_json_deps: Option<&BTreeMap>, ) -> u64 { let mut hasher = FastInsecureHasher::default(); if let Some(import_map) = maybe_import_map { @@ -1174,30 +1181,64 @@ impl Documents { if let Some(jsx_config) = maybe_jsx_config { hasher.write_hashable(&jsx_config); } + if let Some(deps) = maybe_package_json_deps { + hasher.write_hashable(&deps); + } hasher.finish() } + let maybe_package_json_deps = maybe_package_json.and_then(|package_json| { + match package_json::get_local_package_json_version_reqs(package_json) { + Ok(deps) => Some(deps), + Err(err) => { + lsp_log!("Error parsing package.json deps: {err:#}"); + None + } + } + }); let maybe_jsx_config = maybe_config_file.and_then(|cf| cf.to_maybe_jsx_import_source_config()); let new_resolver_config_hash = calculate_resolver_config_hash( maybe_import_map.as_deref(), maybe_jsx_config.as_ref(), + maybe_package_json_deps.as_ref(), + ); + self.npm_package_json_reqs = Arc::new({ + match &maybe_package_json_deps { + Some(deps) => { + let mut reqs = deps.values().cloned().collect::>(); + reqs.sort(); + reqs + } + None => Vec::new(), + } + }); + let deps_installer = PackageJsonDepsInstaller::new( + npm_registry_api.clone(), + npm_resolution.clone(), + maybe_package_json_deps, + ); + self.resolver = CliGraphResolver::new( + maybe_jsx_config, + maybe_import_map, + false, + npm_registry_api, + npm_resolution, + deps_installer, ); - self.maybe_resolver = - CliResolver::maybe_new(maybe_jsx_config, maybe_import_map); self.imports = Arc::new( - if let Some(Ok(Some(imports))) = + if let Some(Ok(imports)) = maybe_config_file.map(|cf| cf.to_maybe_imports()) { imports .into_iter() - .map(|(referrer, dependencies)| { + .map(|import| { let graph_import = GraphImport::new( - referrer.clone(), - dependencies, - self.get_maybe_resolver(), + &import.referrer, + import.imports, + Some(self.get_resolver()), ); - (referrer, graph_import) + (import.referrer, graph_import) }) .collect() } else { @@ -1215,17 +1256,13 @@ impl Documents { } fn refresh_dependencies(&mut self) { - let maybe_resolver = - self.maybe_resolver.as_ref().map(|r| r.as_graph_resolver()); + let resolver = self.resolver.as_graph_resolver(); for doc in self.open_docs.values_mut() { - if let Some(new_doc) = doc.maybe_with_new_resolver(maybe_resolver) { + if let Some(new_doc) = doc.maybe_with_new_resolver(resolver) { *doc = new_doc; } } - self - .file_system_docs - .lock() - .refresh_dependencies(maybe_resolver); + self.file_system_docs.lock().refresh_dependencies(resolver); } /// Iterate through the documents, building a map where the key is a unique @@ -1248,7 +1285,7 @@ impl Documents { // perf: ensure this is not added to unless this specifier has never // been analyzed in order to not cause an extra file system lookup self.pending_specifiers.push_back(dep.clone()); - if let Ok(reference) = NpmPackageReference::from_specifier(dep) { + if let Ok(reference) = NpmPackageReqReference::from_specifier(dep) { self.npm_reqs.insert(reference.req); } } @@ -1274,10 +1311,8 @@ impl Documents { self.add(dep, specifier); } } - if let Resolved::Ok { specifier: dep, .. } = - doc.maybe_types_dependency() - { - self.add(&dep, specifier); + if let Some(dep) = doc.maybe_types_dependency().maybe_specifier() { + self.add(dep, specifier); } } } @@ -1294,10 +1329,9 @@ impl Documents { doc_analyzer.analyze_doc(specifier, doc); } - let maybe_resolver = self.get_maybe_resolver(); + let resolver = self.get_resolver(); while let Some(specifier) = doc_analyzer.pending_specifiers.pop_front() { - if let Some(doc) = - file_system_docs.get(&self.cache, maybe_resolver, &specifier) + if let Some(doc) = file_system_docs.get(&self.cache, resolver, &specifier) { doc_analyzer.analyze_doc(&specifier, &doc); } @@ -1315,13 +1349,17 @@ impl Documents { } self.dependents_map = Arc::new(doc_analyzer.dependents_map); - self.npm_reqs = Arc::new(npm_reqs); + self.npm_specifier_reqs = Arc::new({ + let mut reqs = npm_reqs.into_iter().collect::>(); + reqs.sort(); + reqs + }); self.dirty = false; file_system_docs.dirty = false; } - fn get_maybe_resolver(&self) -> Option<&dyn deno_graph::source::Resolver> { - self.maybe_resolver.as_ref().map(|r| r.as_graph_resolver()) + fn get_resolver(&self) -> &dyn deno_graph::source::Resolver { + self.resolver.as_graph_resolver() } fn resolve_dependency( @@ -1329,29 +1367,17 @@ impl Documents { specifier: &ModuleSpecifier, maybe_npm_resolver: Option<&NpmPackageResolver>, ) -> Option<(ModuleSpecifier, MediaType)> { - if let Ok(npm_ref) = NpmPackageReference::from_specifier(specifier) { - return maybe_npm_resolver.map(|npm_resolver| { - NodeResolution::into_specifier_and_media_type( - node_resolve_npm_reference( - &npm_ref, - NodeResolutionMode::Types, - npm_resolver, - &mut PermissionsContainer::allow_all(), - ) - .ok() - .flatten(), - ) - }); + if let Ok(npm_ref) = NpmPackageReqReference::from_specifier(specifier) { + return node_resolve_npm_req_ref(npm_ref, maybe_npm_resolver); } let doc = self.get(specifier)?; - let maybe_module = doc.maybe_module().and_then(|r| r.as_ref().ok()); - let maybe_types_dependency = maybe_module.and_then(|m| { - m.maybe_types_dependency - .as_ref() - .map(|(_, resolved)| resolved.clone()) - }); - if let Some(Resolved::Ok { specifier, .. }) = maybe_types_dependency { - self.resolve_dependency(&specifier, maybe_npm_resolver) + let maybe_module = doc.maybe_esm_module().and_then(|r| r.as_ref().ok()); + let maybe_types_dependency = maybe_module + .and_then(|m| m.maybe_types_dependency.as_ref().map(|d| &d.dependency)); + if let Some(specifier) = + maybe_types_dependency.and_then(|d| d.maybe_specifier()) + { + self.resolve_dependency(specifier, maybe_npm_resolver) } else { let media_type = doc.media_type(); Some((specifier.clone(), media_type)) @@ -1361,10 +1387,7 @@ impl Documents { /// Iterate through any "imported" modules, checking to see if a dependency /// is available. This is used to provide "global" imports like the JSX import /// source. - fn resolve_imports_dependency( - &self, - specifier: &str, - ) -> Option<&deno_graph::Resolved> { + fn resolve_imports_dependency(&self, specifier: &str) -> Option<&Resolution> { for graph_imports in self.imports.values() { let maybe_dep = graph_imports.dependencies.get(specifier); if maybe_dep.is_some() { @@ -1375,6 +1398,30 @@ impl Documents { } } +fn node_resolve_npm_req_ref( + npm_req_ref: NpmPackageReqReference, + maybe_npm_resolver: Option<&NpmPackageResolver>, +) -> Option<(ModuleSpecifier, MediaType)> { + maybe_npm_resolver.map(|npm_resolver| { + NodeResolution::into_specifier_and_media_type( + npm_resolver + .resolution() + .pkg_req_ref_to_nv_ref(npm_req_ref) + .ok() + .and_then(|pkg_id_ref| { + node_resolve_npm_reference( + &pkg_id_ref, + NodeResolutionMode::Types, + npm_resolver, + &mut PermissionsContainer::allow_all(), + ) + .ok() + .flatten() + }), + ) + }) +} + /// Loader that will look at the open documents. pub struct OpenDocumentsGraphLoader<'a> { pub inner_loader: &'a mut dyn deno_graph::source::Loader, @@ -1406,15 +1453,11 @@ fn parse_and_analyze_module( specifier: &ModuleSpecifier, text_info: SourceTextInfo, maybe_headers: Option<&HashMap>, - maybe_resolver: Option<&dyn deno_graph::source::Resolver>, + resolver: &dyn deno_graph::source::Resolver, ) -> (Option, Option) { let parsed_source_result = parse_source(specifier, text_info, maybe_headers); - let module_result = analyze_module( - specifier, - &parsed_source_result, - maybe_headers, - maybe_resolver, - ); + let module_result = + analyze_module(specifier, &parsed_source_result, maybe_headers, resolver); (Some(parsed_source_result), Some(module_result)) } @@ -1437,15 +1480,14 @@ fn analyze_module( specifier: &ModuleSpecifier, parsed_source_result: &ParsedSourceResult, maybe_headers: Option<&HashMap>, - maybe_resolver: Option<&dyn deno_graph::source::Resolver>, + resolver: &dyn deno_graph::source::Resolver, ) -> ModuleResult { match parsed_source_result { Ok(parsed_source) => Ok(deno_graph::parse_module_from_ast( specifier, - deno_graph::ModuleKind::Esm, maybe_headers, parsed_source, - maybe_resolver, + Some(resolver), )), Err(err) => Err(deno_graph::ModuleGraphError::ParseErr( specifier.clone(), @@ -1456,6 +1498,8 @@ fn analyze_module( #[cfg(test)] mod tests { + use crate::npm::NpmResolution; + use super::*; use import_map::ImportMap; use test_util::TempDir; @@ -1556,6 +1600,10 @@ console.log(b, "hello deno"); #[test] fn test_documents_refresh_dependencies_config_change() { + let npm_registry_api = NpmRegistryApi::new_uninitialized(); + let npm_resolution = + NpmResolution::new(npm_registry_api.clone(), None, None); + // it should never happen that a user of this API causes this to happen, // but we'll guard against it anyway let temp_dir = TempDir::new(); @@ -1585,7 +1633,13 @@ console.log(b, "hello deno"); .append("test".to_string(), "./file2.ts".to_string()) .unwrap(); - documents.update_config(Some(Arc::new(import_map)), None); + documents.update_config( + Some(Arc::new(import_map)), + None, + None, + npm_registry_api.clone(), + npm_resolution.clone(), + ); // open the document let document = documents.open( @@ -1618,7 +1672,13 @@ console.log(b, "hello deno"); .append("test".to_string(), "./file3.ts".to_string()) .unwrap(); - documents.update_config(Some(Arc::new(import_map)), None); + documents.update_config( + Some(Arc::new(import_map)), + None, + None, + npm_registry_api, + npm_resolution, + ); // check the document's dependencies let document = documents.get(&file1_specifier).unwrap(); diff --git a/cli/lsp/language_server.rs b/cli/lsp/language_server.rs index 1cc9f9ca243af6..b3d89c71c8f902 100644 --- a/cli/lsp/language_server.rs +++ b/cli/lsp/language_server.rs @@ -3,18 +3,21 @@ use deno_ast::MediaType; use deno_core::anyhow::anyhow; +use deno_core::anyhow::Context; use deno_core::error::AnyError; use deno_core::resolve_url; use deno_core::serde_json; use deno_core::serde_json::json; use deno_core::serde_json::Value; use deno_core::ModuleSpecifier; +use deno_runtime::deno_node::PackageJson; use deno_runtime::deno_web::BlobStore; use import_map::ImportMap; use log::error; use log::warn; use serde_json::from_value; use std::collections::HashMap; +use std::collections::HashSet; use std::env; use std::fmt::Write as _; use std::path::PathBuf; @@ -58,6 +61,7 @@ use super::tsc::AssetsSnapshot; use super::tsc::TsServer; use super::urls; use crate::args::get_root_cert_store; +use crate::args::package_json; use crate::args::resolve_import_map_from_specifier; use crate::args::CaData; use crate::args::CacheSetting; @@ -70,11 +74,11 @@ use crate::args::TsConfig; use crate::cache::DenoDir; use crate::cache::HttpCache; use crate::file_fetcher::FileFetcher; -use crate::graph_util::graph_valid; +use crate::graph_util; use crate::http_util::HttpClient; use crate::npm::NpmCache; use crate::npm::NpmPackageResolver; -use crate::npm::RealNpmRegistryApi; +use crate::npm::NpmRegistryApi; use crate::proc_state::ProcState; use crate::tools::fmt::format_file; use crate::tools::fmt::format_parsed_source; @@ -130,6 +134,8 @@ pub struct Inner { maybe_import_map: Option>, /// The URL for the import map which is used to determine relative imports. maybe_import_map_uri: Option, + /// An optional package.json configuration file. + maybe_package_json: Option, /// Configuration for formatter which has been taken from specified config file. fmt_options: FmtOptions, /// An optional configuration for linter which has been taken from specified config file. @@ -174,19 +180,41 @@ impl LanguageServer { inner_loader: &mut inner_loader, open_docs: &open_docs, }; - let graph = ps.create_graph_with_loader(roots, &mut loader).await?; - graph_valid(&graph, true, false)?; + let graph = ps + .create_graph_with_loader(roots.clone(), &mut loader) + .await?; + graph_util::graph_valid( + &graph, + &roots, + graph_util::GraphValidOptions { + is_vendoring: false, + follow_type_only: true, + check_js: false, + }, + )?; Ok(()) } match params.map(serde_json::from_value) { Some(Ok(params)) => { // do as much as possible in a read, then do a write outside - let result = { + let maybe_cache_result = { let inner = self.0.read().await; // ensure dropped - inner.prepare_cache(params)? + match inner.prepare_cache(params) { + Ok(maybe_cache_result) => maybe_cache_result, + Err(err) => { + self + .0 + .read() + .await + .client + .show_message(MessageType::WARNING, err) + .await; + return Err(LspError::internal_error()); + } + } }; - if let Some(result) = result { + if let Some(result) = maybe_cache_result { let cli_options = result.cli_options; let roots = result.roots; let open_docs = result.open_docs; @@ -295,7 +323,7 @@ fn create_lsp_npm_resolver( dir: &DenoDir, http_client: HttpClient, ) -> NpmPackageResolver { - let registry_url = RealNpmRegistryApi::default_url(); + let registry_url = NpmRegistryApi::default_url(); let progress_bar = ProgressBar::new(ProgressBarStyle::TextOnly); let npm_cache = NpmCache::from_deno_dir( dir, @@ -307,13 +335,13 @@ fn create_lsp_npm_resolver( http_client.clone(), progress_bar.clone(), ); - let api = RealNpmRegistryApi::new( - registry_url, + let api = NpmRegistryApi::new( + registry_url.clone(), npm_cache.clone(), http_client, progress_bar, ); - NpmPackageResolver::new(npm_cache, api, false, None) + NpmPackageResolver::new(npm_cache, api) } impl Inner { @@ -354,6 +382,7 @@ impl Inner { maybe_config_file: None, maybe_import_map: None, maybe_import_map_uri: None, + maybe_package_json: None, fmt_options: Default::default(), lint_options: Default::default(), maybe_testing_server: None, @@ -434,8 +463,6 @@ impl Inner { Ok(navigation_tree) } - /// Returns a tuple with parsed `ConfigFile` and `Url` pointing to that file. - /// If there's no config file specified in settings returns `None`. fn get_config_file(&self) -> Result, AnyError> { let workspace_settings = self.config.get_workspace_settings(); let maybe_config = workspace_settings.config; @@ -479,6 +506,28 @@ impl Inner { } } + fn get_package_json( + &self, + maybe_config_file: Option<&ConfigFile>, + ) -> Result, AnyError> { + // It is possible that root_uri is not set, for example when having a single + // file open and not a workspace. In those situations we can't + // automatically discover the configuration + if let Some(root_uri) = &self.config.root_uri { + let root_path = specifier_to_file_path(root_uri)?; + let maybe_package_json = package_json::discover_from( + &root_path, + maybe_config_file.and_then(|f| f.specifier.to_file_path().ok()), + )?; + Ok(maybe_package_json.map(|c| { + lsp_log!(" Auto-resolved package.json: \"{}\"", c.specifier()); + c + })) + } else { + Ok(None) + } + } + fn is_diagnosable(&self, specifier: &ModuleSpecifier) -> bool { if specifier.scheme() == "asset" { matches!( @@ -792,6 +841,15 @@ impl Inner { Ok(()) } + /// Updates the package.json. Always ensure this is done after updating + /// the configuration file as the resolution of this depends on that. + fn update_package_json(&mut self) -> Result<(), AnyError> { + self.maybe_package_json = None; + self.maybe_package_json = + self.get_package_json(self.maybe_config_file.as_ref())?; + Ok(()) + } + async fn update_tsconfig(&mut self) -> Result<(), AnyError> { let mark = self.performance.mark("update_tsconfig", None::<()>); let mut tsconfig = TsConfig::new(json!({ @@ -901,6 +959,9 @@ impl Inner { if let Err(err) = self.update_config_file() { self.client.show_message(MessageType::WARNING, err).await; } + if let Err(err) = self.update_package_json() { + self.client.show_message(MessageType::WARNING, err).await; + } if let Err(err) = self.update_tsconfig().await { self.client.show_message(MessageType::WARNING, err).await; } @@ -928,6 +989,9 @@ impl Inner { self.documents.update_config( self.maybe_import_map.clone(), self.maybe_config_file.as_ref(), + self.maybe_package_json.as_ref(), + self.npm_resolver.api().clone(), + self.npm_resolver.resolution().clone(), ); self.assets.intitialize(self.snapshot()).await; @@ -1105,6 +1169,9 @@ impl Inner { if let Err(err) = self.update_config_file() { self.client.show_message(MessageType::WARNING, err).await; } + if let Err(err) = self.update_package_json() { + self.client.show_message(MessageType::WARNING, err).await; + } if let Err(err) = self.update_import_map().await { self.client.show_message(MessageType::WARNING, err).await; } @@ -1115,6 +1182,9 @@ impl Inner { self.documents.update_config( self.maybe_import_map.clone(), self.maybe_config_file.as_ref(), + self.maybe_package_json.as_ref(), + self.npm_resolver.api().clone(), + self.npm_resolver.resolution().clone(), ); self.send_diagnostics_update(); @@ -1129,15 +1199,15 @@ impl Inner { .performance .mark("did_change_watched_files", Some(¶ms)); let mut touched = false; - let changes: Vec = params + let changes: HashSet = params .changes .iter() .map(|f| self.url_map.normalize_url(&f.uri)) .collect(); - // if the current tsconfig has changed, we need to reload it + // if the current deno.json has changed, we need to reload it if let Some(config_file) = &self.maybe_config_file { - if changes.iter().any(|uri| config_file.specifier == *uri) { + if changes.contains(&config_file.specifier) { if let Err(err) = self.update_config_file() { self.client.show_message(MessageType::WARNING, err).await; } @@ -1147,10 +1217,19 @@ impl Inner { touched = true; } } - // if the current import map, or config file has changed, we need to reload + if let Some(package_json) = &self.maybe_package_json { + // always update the package json if the deno config changes + if touched || changes.contains(&package_json.specifier()) { + if let Err(err) = self.update_package_json() { + self.client.show_message(MessageType::WARNING, err).await; + } + touched = true; + } + } + // if the current import map, or config file has changed, we need to // reload the import map if let Some(import_map_uri) = &self.maybe_import_map_uri { - if changes.iter().any(|uri| import_map_uri == uri) || touched { + if touched || changes.contains(import_map_uri) { if let Err(err) = self.update_import_map().await { self.client.show_message(MessageType::WARNING, err).await; } @@ -1161,6 +1240,9 @@ impl Inner { self.documents.update_config( self.maybe_import_map.clone(), self.maybe_config_file.as_ref(), + self.maybe_package_json.as_ref(), + self.npm_resolver.api().clone(), + self.npm_resolver.resolution().clone(), ); self.refresh_npm_specifiers().await; self.diagnostics_server.invalidate_all(); @@ -2978,7 +3060,7 @@ impl Inner { fn prepare_cache( &self, params: lsp_custom::CacheParams, - ) -> LspResult> { + ) -> Result, AnyError> { let referrer = self.url_map.normalize_url(¶ms.referrer.uri); if !self.is_diagnosable(&referrer) { return Ok(None); @@ -3006,10 +3088,13 @@ impl Inner { unstable: true, ..Default::default() }, + std::env::current_dir().with_context(|| "Failed getting cwd.")?, self.maybe_config_file.clone(), // TODO(#16510): add support for lockfile None, - ); + // TODO(bartlomieju): handle package.json dependencies here + None, + )?; cli_options.set_import_map_specifier(self.maybe_import_map_uri.clone()); let open_docs = self.documents.documents(true, true); diff --git a/cli/main.rs b/cli/main.rs index 179d5a8a906373..891e8bbe5b5608 100644 --- a/cli/main.rs +++ b/cli/main.rs @@ -19,7 +19,6 @@ mod npm; mod ops; mod proc_state; mod resolver; -mod semver; mod standalone; mod tools; mod tsc; @@ -31,7 +30,7 @@ use crate::args::flags_from_vec; use crate::args::DenoSubcommand; use crate::args::Flags; use crate::proc_state::ProcState; -use crate::resolver::CliResolver; +use crate::resolver::CliGraphResolver; use crate::util::display; use crate::util::v8::get_v8_flags_from_env; use crate::util::v8::init_v8_flags; diff --git a/cli/module_loader.rs b/cli/module_loader.rs index d2e103ec88b92c..4ddb297a594117 100644 --- a/cli/module_loader.rs +++ b/cli/module_loader.rs @@ -2,7 +2,6 @@ use crate::args::TsTypeLib; use crate::emit::emit_parsed_source; -use crate::graph_util::ModuleEntry; use crate::node; use crate::proc_state::ProcState; use crate::util::text_encoding::code_without_source_map; @@ -22,6 +21,8 @@ use deno_core::ModuleType; use deno_core::OpState; use deno_core::ResolutionKind; use deno_core::SourceMapGetter; +use deno_graph::EsmModule; +use deno_graph::JsonModule; use deno_runtime::permissions::PermissionsContainer; use std::cell::RefCell; use std::pin::Pin; @@ -78,25 +79,34 @@ impl CliModuleLoader { specifier: &ModuleSpecifier, maybe_referrer: Option, ) -> Result { - if specifier.as_str() == "node:module" { - return Ok(ModuleCodeSource { - code: deno_runtime::deno_node::MODULE_ES_SHIM.to_string(), - found_url: specifier.to_owned(), - media_type: MediaType::JavaScript, - }); + if specifier.scheme() == "node" { + unreachable!(); // Node built-in modules should be handled internally. } - let graph_data = self.ps.graph_data.read(); - let found_url = graph_data.follow_redirect(specifier); - match graph_data.get(&found_url) { - Some(ModuleEntry::Module { - code, media_type, .. - }) => { + + let graph = self.ps.graph(); + match graph.get(specifier) { + Some(deno_graph::Module::Json(JsonModule { + source, + media_type, + specifier, + .. + })) => Ok(ModuleCodeSource { + code: source.to_string(), + found_url: specifier.clone(), + media_type: *media_type, + }), + Some(deno_graph::Module::Esm(EsmModule { + source, + media_type, + specifier, + .. + })) => { let code = match media_type { MediaType::JavaScript | MediaType::Unknown | MediaType::Cjs | MediaType::Mjs - | MediaType::Json => code.to_string(), + | MediaType::Json => source.to_string(), MediaType::Dts | MediaType::Dcts | MediaType::Dmts => "".to_string(), MediaType::TypeScript | MediaType::Mts @@ -107,15 +117,15 @@ impl CliModuleLoader { emit_parsed_source( &self.ps.emit_cache, &self.ps.parsed_source_cache, - &found_url, + specifier, *media_type, - code, + source, &self.ps.emit_options, self.ps.emit_options_hash, )? } MediaType::TsBuildInfo | MediaType::Wasm | MediaType::SourceMap => { - panic!("Unexpected media type {media_type} for {found_url}") + panic!("Unexpected media type {media_type} for {specifier}") } }; @@ -124,7 +134,7 @@ impl CliModuleLoader { Ok(ModuleCodeSource { code, - found_url, + found_url: specifier.clone(), media_type: *media_type, }) } @@ -295,10 +305,10 @@ impl SourceMapGetter for CliModuleLoader { file_name: &str, line_number: usize, ) -> Option { - let graph_data = self.ps.graph_data.read(); - let specifier = graph_data.follow_redirect(&resolve_url(file_name).ok()?); - let code = match graph_data.get(&specifier) { - Some(ModuleEntry::Module { code, .. }) => code, + let graph = self.ps.graph(); + let code = match graph.get(&resolve_url(file_name).ok()?) { + Some(deno_graph::Module::Esm(module)) => &module.source, + Some(deno_graph::Module::Json(module)) => &module.source, _ => return None, }; // Do NOT use .lines(): it skips the terminating empty line. diff --git a/cli/napi/js_native_api.rs b/cli/napi/js_native_api.rs index 4dc249fff20260..e79307714d811a 100644 --- a/cli/napi/js_native_api.rs +++ b/cli/napi/js_native_api.rs @@ -1423,27 +1423,70 @@ fn napi_define_properties( properties: *const napi_property_descriptor, ) -> Result { let env: &mut Env = env_ptr.as_mut().ok_or(Error::InvalidArg)?; + if property_count > 0 { + check_arg!(env, properties); + } + let scope = &mut env.scope(); - let object = transmute::>(obj); + + let object: v8::Local = napi_value_unchecked(obj) + .try_into() + .map_err(|_| Error::ObjectExpected)?; + let properties = std::slice::from_raw_parts(properties, property_count); for property in properties { let name = if !property.utf8name.is_null() { - let name_str = CStr::from_ptr(property.utf8name); - let name_str = name_str.to_str().unwrap(); - v8::String::new(scope, name_str).unwrap() + let name_str = CStr::from_ptr(property.utf8name).to_str().unwrap(); + v8::String::new(scope, name_str) + .ok_or(Error::GenericFailure)? + .into() } else { - transmute::>(property.name) + let property_value = napi_value_unchecked(property.name); + v8::Local::::try_from(property_value) + .map_err(|_| Error::NameExpected)? }; - let method_ptr = property.method; + if property.getter.is_some() || property.setter.is_some() { + let local_getter: v8::Local = if property.getter.is_some() { + create_function(env_ptr, None, property.getter, property.data).into() + } else { + v8::undefined(scope).into() + }; + let local_setter: v8::Local = if property.setter.is_some() { + create_function(env_ptr, None, property.setter, property.data).into() + } else { + v8::undefined(scope).into() + }; + + let mut desc = + v8::PropertyDescriptor::new_from_get_set(local_getter, local_setter); + desc.set_enumerable(property.attributes & napi_enumerable != 0); + desc.set_configurable(property.attributes & napi_configurable != 0); - if method_ptr.is_some() { - let function: v8::Local = { + let define_maybe = object.define_property(scope, name, &desc); + return_status_if_false!( + env_ptr, + !define_maybe.unwrap_or(false), + napi_invalid_arg + ); + } else if property.method.is_some() { + let value: v8::Local = { let function = create_function(env_ptr, None, property.method, property.data); function.into() }; - object.set(scope, name.into(), function).unwrap(); + return_status_if_false!( + env_ptr, + object.set(scope, name.into(), value).is_some(), + napi_invalid_arg + ); + } else { + let value = napi_value_unchecked(property.value); + return_status_if_false!( + env_ptr, + object.set(scope, name.into(), value).is_some(), + napi_invalid_arg + ); } } diff --git a/cli/napi/sym/Cargo.toml b/cli/napi/sym/Cargo.toml index 8f19ea68bc8375..f5ab0b91f33e74 100644 --- a/cli/napi/sym/Cargo.toml +++ b/cli/napi/sym/Cargo.toml @@ -2,7 +2,7 @@ [package] name = "napi_sym" -version = "0.19.0" +version = "0.21.0" authors.workspace = true edition.workspace = true license.workspace = true diff --git a/cli/node/mod.rs b/cli/node/mod.rs index 9ab593304aee32..e45694fd6946b7 100644 --- a/cli/node/mod.rs +++ b/cli/node/mod.rs @@ -13,11 +13,13 @@ use deno_core::anyhow::bail; use deno_core::anyhow::Context; use deno_core::error::generic_error; use deno_core::error::AnyError; -use deno_core::located_script_name; use deno_core::serde_json::Value; use deno_core::url::Url; -use deno_core::JsRuntime; +use deno_graph::npm::NpmPackageNv; +use deno_graph::npm::NpmPackageNvReference; +use deno_runtime::deno_node; use deno_runtime::deno_node::errors; +use deno_runtime::deno_node::find_builtin_node_module; use deno_runtime::deno_node::get_closest_package_json; use deno_runtime::deno_node::legacy_main_resolve; use deno_runtime::deno_node::package_exports_resolve; @@ -25,25 +27,20 @@ use deno_runtime::deno_node::package_imports_resolve; use deno_runtime::deno_node::package_resolve; use deno_runtime::deno_node::path_to_declaration_path; use deno_runtime::deno_node::NodeModuleKind; -use deno_runtime::deno_node::NodeModulePolyfill; use deno_runtime::deno_node::NodePermissions; use deno_runtime::deno_node::NodeResolutionMode; use deno_runtime::deno_node::PackageJson; use deno_runtime::deno_node::PathClean; use deno_runtime::deno_node::RequireNpmResolver; use deno_runtime::deno_node::DEFAULT_CONDITIONS; -use deno_runtime::deno_node::NODE_GLOBAL_THIS_NAME; -use deno_runtime::deno_node::SUPPORTED_BUILTIN_NODE_MODULES; use deno_runtime::permissions::PermissionsContainer; use once_cell::sync::Lazy; use regex::Regex; use crate::cache::NodeAnalysisCache; -use crate::deno_std::CURRENT_STD_URL; use crate::file_fetcher::FileFetcher; -use crate::npm::NpmPackageReference; -use crate::npm::NpmPackageReq; use crate::npm::NpmPackageResolver; +use crate::util::fs::canonicalize_path_maybe_not_exists; mod analyze; @@ -108,45 +105,14 @@ impl NodeResolution { } } -static NODE_COMPAT_URL: Lazy = Lazy::new(|| { - if let Ok(url_str) = std::env::var("DENO_NODE_COMPAT_URL") { - let url = Url::parse(&url_str).expect( - "Malformed DENO_NODE_COMPAT_URL value, make sure it's a file URL ending with a slash" - ); - return url; - } - - CURRENT_STD_URL.clone() -}); - -pub static MODULE_ALL_URL: Lazy = - Lazy::new(|| NODE_COMPAT_URL.join("node/module_all.ts").unwrap()); - -fn find_builtin_node_module(specifier: &str) -> Option<&NodeModulePolyfill> { - SUPPORTED_BUILTIN_NODE_MODULES - .iter() - .find(|m| m.name == specifier) -} - -fn is_builtin_node_module(specifier: &str) -> bool { - find_builtin_node_module(specifier).is_some() -} - -pub fn resolve_builtin_node_module(specifier: &str) -> Result { - // NOTE(bartlomieju): `module` is special, because we don't want to use - // `deno_std/node/module.ts`, but instead use a special shim that we - // provide in `ext/node`. - if specifier == "module" { - return Ok(Url::parse("node:module").unwrap()); - } - - if let Some(module) = find_builtin_node_module(specifier) { - let module_url = NODE_COMPAT_URL.join(module.specifier).unwrap(); - return Ok(module_url); +// TODO(bartlomieju): seems super wasteful to parse specified each time +pub fn resolve_builtin_node_module(module_name: &str) -> Result { + if let Some(module) = find_builtin_node_module(module_name) { + return Ok(ModuleSpecifier::parse(module.specifier).unwrap()); } Err(generic_error(format!( - "Unknown built-in \"node:\" module: {specifier}" + "Unknown built-in \"node:\" module: {module_name}" ))) } @@ -200,49 +166,6 @@ static RESERVED_WORDS: Lazy> = Lazy::new(|| { ]) }); -pub async fn initialize_runtime( - js_runtime: &mut JsRuntime, - uses_local_node_modules_dir: bool, -) -> Result<(), AnyError> { - let source_code = &format!( - r#"(async function loadBuiltinNodeModules(moduleAllUrl, nodeGlobalThisName, usesLocalNodeModulesDir) {{ - const moduleAll = await import(moduleAllUrl); - Deno[Deno.internal].node.initialize(moduleAll.default, nodeGlobalThisName); - if (usesLocalNodeModulesDir) {{ - Deno[Deno.internal].require.setUsesLocalNodeModulesDir(); - }} - }})('{}', '{}', {});"#, - MODULE_ALL_URL.as_str(), - NODE_GLOBAL_THIS_NAME.as_str(), - uses_local_node_modules_dir, - ); - - let value = - js_runtime.execute_script(&located_script_name!(), source_code)?; - js_runtime.resolve_value(value).await?; - Ok(()) -} - -pub async fn initialize_binary_command( - js_runtime: &mut JsRuntime, - binary_name: &str, -) -> Result<(), AnyError> { - // overwrite what's done in deno_std in order to set the binary arg name - let source_code = &format!( - r#"(async function initializeBinaryCommand(binaryName) {{ - const process = Deno[Deno.internal].node.globalThis.process; - Object.defineProperty(process.argv, "0", {{ - get: () => binaryName, - }}); - }})('{binary_name}');"#, - ); - - let value = - js_runtime.execute_script(&located_script_name!(), source_code)?; - js_runtime.resolve_value(value).await?; - Ok(()) -} - /// This function is an implementation of `defaultResolve` in /// `lib/internal/modules/esm/resolve.js` from Node. pub fn node_resolve( @@ -255,7 +178,7 @@ pub fn node_resolve( // Note: if we are here, then the referrer is an esm module // TODO(bartlomieju): skipped "policy" part as we don't plan to support it - if is_builtin_node_module(specifier) { + if deno_node::is_builtin_node_module(specifier) { return Ok(Some(NodeResolution::BuiltIn(specifier.to_string()))); } @@ -270,7 +193,7 @@ pub fn node_resolve( let split_specifier = url.as_str().split(':'); let specifier = split_specifier.skip(1).collect::(); - if is_builtin_node_module(&specifier) { + if deno_node::is_builtin_node_module(&specifier) { return Ok(Some(NodeResolution::BuiltIn(specifier))); } } @@ -319,13 +242,13 @@ pub fn node_resolve( } pub fn node_resolve_npm_reference( - reference: &NpmPackageReference, + reference: &NpmPackageNvReference, mode: NodeResolutionMode, npm_resolver: &NpmPackageResolver, permissions: &mut dyn NodePermissions, ) -> Result, AnyError> { let package_folder = - npm_resolver.resolve_package_folder_from_deno_module(&reference.req)?; + npm_resolver.resolve_package_folder_from_deno_module(&reference.nv)?; let node_module_kind = NodeModuleKind::Esm; let maybe_resolved_path = package_config_resolve( &reference @@ -363,25 +286,68 @@ pub fn node_resolve_npm_reference( Ok(Some(resolve_response)) } +/// Resolves a specifier that is pointing into a node_modules folder. +/// +/// Note: This should be called whenever getting the specifier from +/// a Module::External(module) reference because that module might +/// not be fully resolved at the time deno_graph is analyzing it +/// because the node_modules folder might not exist at that time. +pub fn resolve_specifier_into_node_modules( + specifier: &ModuleSpecifier, +) -> ModuleSpecifier { + specifier + .to_file_path() + .ok() + // this path might not exist at the time the graph is being created + // because the node_modules folder might not yet exist + .and_then(|path| canonicalize_path_maybe_not_exists(&path).ok()) + .and_then(|path| ModuleSpecifier::from_file_path(path).ok()) + .unwrap_or_else(|| specifier.clone()) +} + +pub fn node_resolve_binary_commands( + pkg_nv: &NpmPackageNv, + npm_resolver: &NpmPackageResolver, +) -> Result, AnyError> { + let package_folder = + npm_resolver.resolve_package_folder_from_deno_module(pkg_nv)?; + let package_json_path = package_folder.join("package.json"); + let package_json = PackageJson::load( + npm_resolver, + &mut PermissionsContainer::allow_all(), + package_json_path, + )?; + + Ok(match package_json.bin { + Some(Value::String(_)) => vec![pkg_nv.name.to_string()], + Some(Value::Object(o)) => { + o.into_iter().map(|(key, _)| key).collect::>() + } + _ => Vec::new(), + }) +} + pub fn node_resolve_binary_export( - pkg_req: &NpmPackageReq, + pkg_nv: &NpmPackageNv, bin_name: Option<&str>, npm_resolver: &NpmPackageResolver, - permissions: &mut dyn NodePermissions, ) -> Result { let package_folder = - npm_resolver.resolve_package_folder_from_deno_module(pkg_req)?; + npm_resolver.resolve_package_folder_from_deno_module(pkg_nv)?; let package_json_path = package_folder.join("package.json"); - let package_json = - PackageJson::load(npm_resolver, permissions, package_json_path)?; + let package_json = PackageJson::load( + npm_resolver, + &mut PermissionsContainer::allow_all(), + package_json_path, + )?; let bin = match &package_json.bin { Some(bin) => bin, None => bail!( "package '{}' did not have a bin property in its package.json", - &pkg_req.name, + &pkg_nv.name, ), }; - let bin_entry = resolve_bin_entry_value(pkg_req, bin_name, bin)?; + let bin_entry = resolve_bin_entry_value(pkg_nv, bin_name, bin)?; let url = ModuleSpecifier::from_file_path(package_folder.join(bin_entry)).unwrap(); @@ -392,13 +358,13 @@ pub fn node_resolve_binary_export( } fn resolve_bin_entry_value<'a>( - pkg_req: &NpmPackageReq, + pkg_nv: &NpmPackageNv, bin_name: Option<&str>, bin: &'a Value, ) -> Result<&'a str, AnyError> { let bin_entry = match bin { Value::String(_) => { - if bin_name.is_some() && bin_name.unwrap() != pkg_req.name { + if bin_name.is_some() && bin_name.unwrap() != pkg_nv.name { None } else { Some(bin) @@ -410,10 +376,10 @@ fn resolve_bin_entry_value<'a>( } else if o.len() == 1 || o.len() > 1 && o.values().all(|v| v == o.values().next().unwrap()) { o.values().next() } else { - o.get(&pkg_req.name) + o.get(&pkg_nv.name) } }, - _ => bail!("package '{}' did not have a bin property with a string or object value in its package.json", pkg_req.name), + _ => bail!("package '{}' did not have a bin property with a string or object value in its package.json", pkg_nv), }; let bin_entry = match bin_entry { Some(e) => e, @@ -423,14 +389,14 @@ fn resolve_bin_entry_value<'a>( .map(|o| { o.keys() .into_iter() - .map(|k| format!(" * npm:{pkg_req}/{k}")) + .map(|k| format!(" * npm:{pkg_nv}/{k}")) .collect::>() }) .unwrap_or_default(); bail!( "package '{}' did not have a bin entry for '{}' in its package.json{}", - pkg_req.name, - bin_name.unwrap_or(&pkg_req.name), + pkg_nv, + bin_name.unwrap_or(&pkg_nv.name), if keys.is_empty() { "".to_string() } else { @@ -443,37 +409,11 @@ fn resolve_bin_entry_value<'a>( Value::String(s) => Ok(s), _ => bail!( "package '{}' had a non-string sub property of bin in its package.json", - pkg_req.name, + pkg_nv, ), } } -pub fn load_cjs_module_from_ext_node( - js_runtime: &mut JsRuntime, - module: &str, - main: bool, - inspect_brk: bool, -) -> Result<(), AnyError> { - fn escape_for_single_quote_string(text: &str) -> String { - text.replace('\\', r"\\").replace('\'', r"\'") - } - - let source_code = &format!( - r#"(function loadCjsModule(module, inspectBrk) {{ - if (inspectBrk) {{ - Deno[Deno.internal].require.setInspectBrk(); - }} - Deno[Deno.internal].require.Module._load(module, null, {main}); - }})('{module}', {inspect_brk});"#, - main = main, - module = escape_for_single_quote_string(module), - inspect_brk = inspect_brk, - ); - - js_runtime.execute_script(&located_script_name!(), source_code)?; - Ok(()) -} - fn package_config_resolve( package_subpath: &str, package_dir: &Path, @@ -1086,7 +1026,7 @@ mod tests { }); assert_eq!( resolve_bin_entry_value( - &NpmPackageReq::from_str("test").unwrap(), + &NpmPackageNv::from_str("test@1.1.1").unwrap(), Some("bin1"), &value ) @@ -1097,7 +1037,7 @@ mod tests { // should resolve the value with the same name when not specified assert_eq!( resolve_bin_entry_value( - &NpmPackageReq::from_str("test").unwrap(), + &NpmPackageNv::from_str("test@1.1.1").unwrap(), None, &value ) @@ -1108,7 +1048,7 @@ mod tests { // should not resolve when specified value does not exist assert_eq!( resolve_bin_entry_value( - &NpmPackageReq::from_str("test").unwrap(), + &NpmPackageNv::from_str("test@1.1.1").unwrap(), Some("other"), &value ) @@ -1116,19 +1056,19 @@ mod tests { .unwrap() .to_string(), concat!( - "package 'test' did not have a bin entry for 'other' in its package.json\n", + "package 'test@1.1.1' did not have a bin entry for 'other' in its package.json\n", "\n", "Possibilities:\n", - " * npm:test/bin1\n", - " * npm:test/bin2\n", - " * npm:test/test" + " * npm:test@1.1.1/bin1\n", + " * npm:test@1.1.1/bin2\n", + " * npm:test@1.1.1/test" ) ); // should not resolve when default value can't be determined assert_eq!( resolve_bin_entry_value( - &NpmPackageReq::from_str("asdf@1.2").unwrap(), + &NpmPackageNv::from_str("asdf@1.2.3").unwrap(), None, &value ) @@ -1136,12 +1076,12 @@ mod tests { .unwrap() .to_string(), concat!( - "package 'asdf' did not have a bin entry for 'asdf' in its package.json\n", + "package 'asdf@1.2.3' did not have a bin entry for 'asdf' in its package.json\n", "\n", "Possibilities:\n", - " * npm:asdf@1.2/bin1\n", - " * npm:asdf@1.2/bin2\n", - " * npm:asdf@1.2/test" + " * npm:asdf@1.2.3/bin1\n", + " * npm:asdf@1.2.3/bin2\n", + " * npm:asdf@1.2.3/test" ) ); @@ -1152,7 +1092,7 @@ mod tests { }); assert_eq!( resolve_bin_entry_value( - &NpmPackageReq::from_str("test").unwrap(), + &NpmPackageNv::from_str("test@1.2.3").unwrap(), None, &value ) @@ -1164,14 +1104,14 @@ mod tests { let value = json!("./value"); assert_eq!( resolve_bin_entry_value( - &NpmPackageReq::from_str("test").unwrap(), + &NpmPackageNv::from_str("test@1.2.3").unwrap(), Some("path"), &value ) .err() .unwrap() .to_string(), - "package 'test' did not have a bin entry for 'path' in its package.json" + "package 'test@1.2.3' did not have a bin entry for 'path' in its package.json" ); } } diff --git a/cli/npm/cache.rs b/cli/npm/cache.rs index 88897592619689..1bc2f8487233c3 100644 --- a/cli/npm/cache.rs +++ b/cli/npm/cache.rs @@ -13,11 +13,12 @@ use deno_core::error::custom_error; use deno_core::error::AnyError; use deno_core::parking_lot::Mutex; use deno_core::url::Url; +use deno_graph::npm::NpmPackageNv; +use deno_graph::semver::Version; use crate::args::CacheSetting; use crate::cache::DenoDir; use crate::http_util::HttpClient; -use crate::semver::Version; use crate::util::fs::canonicalize_path; use crate::util::fs::hard_link_dir_recursive; use crate::util::path::root_url_to_safe_local_dirname; @@ -35,7 +36,7 @@ pub fn should_sync_download() -> bool { const NPM_PACKAGE_SYNC_LOCK_FILENAME: &str = ".deno_sync_lock"; pub fn with_folder_sync_lock( - package: (&str, &Version), + package: &NpmPackageNv, output_folder: &Path, action: impl FnOnce() -> Result<(), AnyError>, ) -> Result<(), AnyError> { @@ -87,14 +88,13 @@ pub fn with_folder_sync_lock( if remove_err.kind() != std::io::ErrorKind::NotFound { bail!( concat!( - "Failed setting up package cache directory for {}@{}, then ", + "Failed setting up package cache directory for {}, then ", "failed cleaning it up.\n\nOriginal error:\n\n{}\n\n", "Remove error:\n\n{}\n\nPlease manually ", "delete this folder or you will run into issues using this ", "package in the future:\n\n{}" ), - package.0, - package.1, + package, err, remove_err, output_folder.display(), @@ -107,8 +107,7 @@ pub fn with_folder_sync_lock( } pub struct NpmPackageCacheFolderId { - pub name: String, - pub version: Version, + pub nv: NpmPackageNv, /// Peer dependency resolution may require us to have duplicate copies /// of the same package. pub copy_index: usize, @@ -117,8 +116,7 @@ pub struct NpmPackageCacheFolderId { impl NpmPackageCacheFolderId { pub fn with_no_count(&self) -> Self { Self { - name: self.name.clone(), - version: self.version.clone(), + nv: self.nv.clone(), copy_index: 0, } } @@ -126,7 +124,7 @@ impl NpmPackageCacheFolderId { impl std::fmt::Display for NpmPackageCacheFolderId { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "{}@{}", self.name, self.version)?; + write!(f, "{}", self.nv)?; if self.copy_index > 0 { write!(f, "_{}", self.copy_index)?; } @@ -181,33 +179,32 @@ impl ReadonlyNpmCache { Self::new(dir.npm_folder_path()) } + pub fn root_dir_url(&self) -> &Url { + &self.root_dir_url + } + pub fn package_folder_for_id( &self, - id: &NpmPackageCacheFolderId, + folder_id: &NpmPackageCacheFolderId, registry_url: &Url, ) -> PathBuf { - if id.copy_index == 0 { - self.package_folder_for_name_and_version( - &id.name, - &id.version, - registry_url, - ) + if folder_id.copy_index == 0 { + self.package_folder_for_name_and_version(&folder_id.nv, registry_url) } else { self - .package_name_folder(&id.name, registry_url) - .join(format!("{}_{}", id.version, id.copy_index)) + .package_name_folder(&folder_id.nv.name, registry_url) + .join(format!("{}_{}", folder_id.nv.version, folder_id.copy_index)) } } pub fn package_folder_for_name_and_version( &self, - name: &str, - version: &Version, + package: &NpmPackageNv, registry_url: &Url, ) -> PathBuf { self - .package_name_folder(name, registry_url) - .join(version.to_string()) + .package_name_folder(&package.name, registry_url) + .join(package.version.to_string()) } pub fn package_name_folder(&self, name: &str, registry_url: &Url) -> PathBuf { @@ -304,8 +301,10 @@ impl ReadonlyNpmCache { (version_part, 0) }; Some(NpmPackageCacheFolderId { - name, - version: Version::parse_from_npm(version).ok()?, + nv: NpmPackageNv { + name, + version: Version::parse_from_npm(version).ok()?, + }, copy_index, }) } @@ -323,7 +322,7 @@ pub struct NpmCache { http_client: HttpClient, progress_bar: ProgressBar, /// ensures a package is only downloaded once per run - previously_reloaded_packages: Arc>>, + previously_reloaded_packages: Arc>>, } impl NpmCache { @@ -350,6 +349,10 @@ impl NpmCache { &self.cache_setting } + pub fn root_dir_url(&self) -> &Url { + self.readonly.root_dir_url() + } + /// Checks if the cache should be used for the provided name and version. /// NOTE: Subsequent calls for the same package will always return `true` /// to ensure a package is only downloaded once per run of the CLI. This @@ -357,40 +360,36 @@ impl NpmCache { /// and imports a dynamic import that imports the same package again for example. fn should_use_global_cache_for_package( &self, - package: (&str, &Version), + package: &NpmPackageNv, ) -> bool { - self.cache_setting.should_use_for_npm_package(package.0) + self.cache_setting.should_use_for_npm_package(&package.name) || !self .previously_reloaded_packages .lock() - .insert(format!("{}@{}", package.0, package.1)) + .insert(package.clone()) } pub async fn ensure_package( &self, - package: (&str, &Version), + package: &NpmPackageNv, dist: &NpmPackageVersionDistInfo, registry_url: &Url, ) -> Result<(), AnyError> { self .ensure_package_inner(package, dist, registry_url) .await - .with_context(|| { - format!("Failed caching npm package '{}@{}'.", package.0, package.1) - }) + .with_context(|| format!("Failed caching npm package '{package}'.")) } async fn ensure_package_inner( &self, - package: (&str, &Version), + package: &NpmPackageNv, dist: &NpmPackageVersionDistInfo, registry_url: &Url, ) -> Result<(), AnyError> { - let package_folder = self.readonly.package_folder_for_name_and_version( - package.0, - package.1, - registry_url, - ); + let package_folder = self + .readonly + .package_folder_for_name_and_version(package, registry_url); if self.should_use_global_cache_for_package(package) && package_folder.exists() // if this file exists, then the package didn't successfully extract @@ -403,7 +402,7 @@ impl NpmCache { "NotCached", format!( "An npm specifier not found in cache: \"{}\", --cached-only is specified.", - &package.0 + &package.name ) ) ); @@ -430,29 +429,28 @@ impl NpmCache { /// from exists before this is called. pub fn ensure_copy_package( &self, - id: &NpmPackageCacheFolderId, + folder_id: &NpmPackageCacheFolderId, registry_url: &Url, ) -> Result<(), AnyError> { - assert_ne!(id.copy_index, 0); - let package_folder = self.readonly.package_folder_for_id(id, registry_url); + assert_ne!(folder_id.copy_index, 0); + let package_folder = + self.readonly.package_folder_for_id(folder_id, registry_url); if package_folder.exists() // if this file exists, then the package didn't successfully extract // the first time, or another process is currently extracting the zip file && !package_folder.join(NPM_PACKAGE_SYNC_LOCK_FILENAME).exists() - && self.cache_setting.should_use_for_npm_package(&id.name) + && self.cache_setting.should_use_for_npm_package(&folder_id.nv.name) { return Ok(()); } let original_package_folder = self .readonly - .package_folder_for_name_and_version(&id.name, &id.version, registry_url); - with_folder_sync_lock( - (id.name.as_str(), &id.version), - &package_folder, - || hard_link_dir_recursive(&original_package_folder, &package_folder), - )?; + .package_folder_for_name_and_version(&folder_id.nv, registry_url); + with_folder_sync_lock(&folder_id.nv, &package_folder, || { + hard_link_dir_recursive(&original_package_folder, &package_folder) + })?; Ok(()) } @@ -466,15 +464,12 @@ impl NpmCache { pub fn package_folder_for_name_and_version( &self, - name: &str, - version: &Version, + package: &NpmPackageNv, registry_url: &Url, ) -> PathBuf { - self.readonly.package_folder_for_name_and_version( - name, - version, - registry_url, - ) + self + .readonly + .package_folder_for_name_and_version(package, registry_url) } pub fn package_name_folder(&self, name: &str, registry_url: &Url) -> PathBuf { @@ -514,10 +509,11 @@ pub fn mixed_case_package_name_decode(name: &str) -> Option { #[cfg(test)] mod test { use deno_core::url::Url; + use deno_graph::npm::NpmPackageNv; + use deno_graph::semver::Version; use super::ReadonlyNpmCache; use crate::npm::cache::NpmPackageCacheFolderId; - use crate::semver::Version; #[test] fn should_get_package_folder() { @@ -529,8 +525,10 @@ mod test { assert_eq!( cache.package_folder_for_id( &NpmPackageCacheFolderId { - name: "json".to_string(), - version: Version::parse_from_npm("1.2.5").unwrap(), + nv: NpmPackageNv { + name: "json".to_string(), + version: Version::parse_from_npm("1.2.5").unwrap(), + }, copy_index: 0, }, ®istry_url, @@ -544,8 +542,10 @@ mod test { assert_eq!( cache.package_folder_for_id( &NpmPackageCacheFolderId { - name: "json".to_string(), - version: Version::parse_from_npm("1.2.5").unwrap(), + nv: NpmPackageNv { + name: "json".to_string(), + version: Version::parse_from_npm("1.2.5").unwrap(), + }, copy_index: 1, }, ®istry_url, @@ -559,8 +559,10 @@ mod test { assert_eq!( cache.package_folder_for_id( &NpmPackageCacheFolderId { - name: "JSON".to_string(), - version: Version::parse_from_npm("2.1.5").unwrap(), + nv: NpmPackageNv { + name: "JSON".to_string(), + version: Version::parse_from_npm("2.1.5").unwrap(), + }, copy_index: 0, }, ®istry_url, @@ -574,8 +576,10 @@ mod test { assert_eq!( cache.package_folder_for_id( &NpmPackageCacheFolderId { - name: "@types/JSON".to_string(), - version: Version::parse_from_npm("2.1.5").unwrap(), + nv: NpmPackageNv { + name: "@types/JSON".to_string(), + version: Version::parse_from_npm("2.1.5").unwrap(), + }, copy_index: 0, }, ®istry_url, diff --git a/cli/npm/installer.rs b/cli/npm/installer.rs new file mode 100644 index 00000000000000..149126cd508fff --- /dev/null +++ b/cli/npm/installer.rs @@ -0,0 +1,88 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. + +use std::collections::BTreeMap; +use std::sync::atomic::AtomicBool; +use std::sync::Arc; + +use deno_core::error::AnyError; +use deno_graph::npm::NpmPackageReq; + +use super::NpmRegistryApi; +use super::NpmResolution; + +#[derive(Debug)] +struct PackageJsonDepsInstallerInner { + has_installed: AtomicBool, + npm_registry_api: NpmRegistryApi, + npm_resolution: NpmResolution, + package_deps: BTreeMap, +} + +/// Holds and controls installing dependencies from package.json. +#[derive(Debug, Clone, Default)] +pub struct PackageJsonDepsInstaller(Option>); + +impl PackageJsonDepsInstaller { + pub fn new( + npm_registry_api: NpmRegistryApi, + npm_resolution: NpmResolution, + deps: Option>, + ) -> Self { + Self(deps.map(|package_deps| { + Arc::new(PackageJsonDepsInstallerInner { + has_installed: AtomicBool::new(false), + npm_registry_api, + npm_resolution, + package_deps, + }) + })) + } + + pub fn package_deps(&self) -> Option<&BTreeMap> { + self.0.as_ref().map(|inner| &inner.package_deps) + } + + /// Gets if the package.json has the specified package name. + pub fn has_package_name(&self, name: &str) -> bool { + if let Some(package_deps) = self.package_deps() { + // ensure this looks at the package name and not the + // bare specifiers (do not look at the keys!) + package_deps.values().any(|v| v.name == name) + } else { + false + } + } + + /// Installs the top level dependencies in the package.json file + /// without going through and resolving the descendant dependencies yet. + pub async fn ensure_top_level_install(&self) -> Result<(), AnyError> { + use std::sync::atomic::Ordering; + let inner = match &self.0 { + Some(inner) => inner, + None => return Ok(()), + }; + + if inner.has_installed.swap(true, Ordering::SeqCst) { + return Ok(()); // already installed by something else + } + + let mut package_reqs = + inner.package_deps.values().cloned().collect::>(); + package_reqs.sort(); // deterministic resolution + + inner + .npm_registry_api + .cache_in_parallel( + package_reqs.iter().map(|req| req.name.clone()).collect(), + ) + .await?; + + for package_req in package_reqs { + inner + .npm_resolution + .resolve_package_req_as_pending(&package_req)?; + } + + Ok(()) + } +} diff --git a/cli/npm/mod.rs b/cli/npm/mod.rs index b9a4f493aaff38..ea18f8866ec7dc 100644 --- a/cli/npm/mod.rs +++ b/cli/npm/mod.rs @@ -1,20 +1,23 @@ // Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. mod cache; +mod installer; mod registry; mod resolution; mod resolvers; mod tarball; +pub use cache::should_sync_download; pub use cache::NpmCache; +pub use installer::PackageJsonDepsInstaller; #[cfg(test)] pub use registry::NpmPackageVersionDistInfo; pub use registry::NpmRegistryApi; -pub use registry::RealNpmRegistryApi; -pub use resolution::resolve_graph_npm_info; +#[cfg(test)] +pub use registry::TestNpmRegistryApiInner; pub use resolution::NpmPackageId; -pub use resolution::NpmPackageReference; -pub use resolution::NpmPackageReq; +pub use resolution::NpmResolution; pub use resolution::NpmResolutionPackage; pub use resolution::NpmResolutionSnapshot; pub use resolvers::NpmPackageResolver; +pub use resolvers::NpmProcessState; diff --git a/cli/npm/registry.rs b/cli/npm/registry.rs index fea6996aba2bf6..75760c1714f13f 100644 --- a/cli/npm/registry.rs +++ b/cli/npm/registry.rs @@ -9,27 +9,29 @@ use std::io::ErrorKind; use std::path::PathBuf; use std::sync::Arc; +use async_trait::async_trait; use deno_core::anyhow::bail; use deno_core::anyhow::Context; use deno_core::error::custom_error; use deno_core::error::AnyError; -use deno_core::futures::future::BoxFuture; -use deno_core::futures::FutureExt; +use deno_core::futures; use deno_core::parking_lot::Mutex; use deno_core::serde::Deserialize; use deno_core::serde_json; use deno_core::url::Url; -use deno_runtime::colors; +use deno_graph::npm::NpmPackageNv; +use deno_graph::semver::VersionReq; +use once_cell::sync::Lazy; use serde::Serialize; +use crate::args::package_json::parse_dep_entry_name_and_raw_version; use crate::args::CacheSetting; use crate::cache::CACHE_PERM; use crate::http_util::HttpClient; -use crate::semver::Version; -use crate::semver::VersionReq; use crate::util::fs::atomic_write_file; use crate::util::progress_bar::ProgressBar; +use super::cache::should_sync_download; use super::cache::NpmCache; // npm registry docs: https://github.com/npm/registry/blob/master/docs/REGISTRY-API.md @@ -42,7 +44,7 @@ pub struct NpmPackageInfo { pub dist_tags: HashMap, } -#[derive(Debug, Eq, PartialEq)] +#[derive(Debug, Clone, Eq, PartialEq)] pub enum NpmDependencyEntryKind { Dep, Peer, @@ -55,7 +57,7 @@ impl NpmDependencyEntryKind { } } -#[derive(Debug, Eq, PartialEq)] +#[derive(Debug, Clone, Eq, PartialEq)] pub struct NpmDependencyEntry { pub kind: NpmDependencyEntryKind, pub bare_specifier: String, @@ -87,17 +89,25 @@ impl Ord for NpmDependencyEntry { } } -#[derive(Debug, Default, Deserialize, Serialize, Clone)] +#[derive(Debug, Default, Deserialize, Serialize, Clone, PartialEq, Eq)] pub struct NpmPeerDependencyMeta { #[serde(default)] optional: bool, } -#[derive(Debug, Default, Deserialize, Serialize, Clone)] +#[derive(Debug, Deserialize, Serialize, Clone, PartialEq, Eq)] +#[serde(untagged)] +pub enum NpmPackageVersionBinEntry { + String(String), + Map(HashMap), +} + +#[derive(Debug, Default, Deserialize, Serialize, Clone, PartialEq, Eq)] #[serde(rename_all = "camelCase")] pub struct NpmPackageVersionInfo { pub version: String, pub dist: NpmPackageVersionDistInfo, + pub bin: Option, // Bare specifier to version (ex. `"typescript": "^3.0.1") or possibly // package and version (ex. `"typescript-3.0.1": "npm:typescript@3.0.1"`). #[serde(default)] @@ -113,30 +123,19 @@ impl NpmPackageVersionInfo { &self, ) -> Result, AnyError> { fn parse_dep_entry( - entry: (&String, &String), + (key, value): (&String, &String), kind: NpmDependencyEntryKind, ) -> Result { - let bare_specifier = entry.0.clone(); let (name, version_req) = - if let Some(package_and_version) = entry.1.strip_prefix("npm:") { - if let Some((name, version)) = package_and_version.rsplit_once('@') { - (name.to_string(), version.to_string()) - } else { - bail!("could not find @ symbol in npm url '{}'", entry.1); - } - } else { - (entry.0.clone(), entry.1.clone()) - }; + parse_dep_entry_name_and_raw_version(key, value)?; let version_req = - VersionReq::parse_from_npm(&version_req).with_context(|| { - format!( - "error parsing version requirement for dependency: {bare_specifier}@{version_req}" - ) + VersionReq::parse_from_npm(version_req).with_context(|| { + format!("error parsing version requirement for dependency: {key}@{version_req}") })?; Ok(NpmDependencyEntry { kind, - bare_specifier, - name, + bare_specifier: key.to_string(), + name: name.to_string(), version_req, peer_dep_version_req: None, }) @@ -191,83 +190,30 @@ impl NpmPackageVersionDistInfo { } } -pub trait NpmRegistryApi: Clone + Sync + Send + 'static { - fn maybe_package_info( - &self, - name: &str, - ) -> BoxFuture<'static, Result>, AnyError>>; - - fn package_info( - &self, - name: &str, - ) -> BoxFuture<'static, Result, AnyError>> { - let api = self.clone(); - let name = name.to_string(); - async move { - let maybe_package_info = api.maybe_package_info(&name).await?; - match maybe_package_info { - Some(package_info) => Ok(package_info), - None => bail!("npm package '{}' does not exist", name), +static NPM_REGISTRY_DEFAULT_URL: Lazy = Lazy::new(|| { + let env_var_name = "NPM_CONFIG_REGISTRY"; + if let Ok(registry_url) = std::env::var(env_var_name) { + // ensure there is a trailing slash for the directory + let registry_url = format!("{}/", registry_url.trim_end_matches('/')); + match Url::parse(®istry_url) { + Ok(url) => { + return url; + } + Err(err) => { + log::debug!("Invalid {} environment variable: {:#}", env_var_name, err,); } } - .boxed() } - fn package_version_info( - &self, - name: &str, - version: &Version, - ) -> BoxFuture<'static, Result, AnyError>> { - let api = self.clone(); - let name = name.to_string(); - let version = version.to_string(); - async move { - let package_info = api.package_info(&name).await?; - Ok(package_info.versions.get(&version).cloned()) - } - .boxed() - } + Url::parse("https://registry.npmjs.org").unwrap() +}); - /// Clears the internal memory cache. - fn clear_memory_cache(&self); -} - -#[derive(Clone)] -pub struct RealNpmRegistryApi(Arc); - -impl RealNpmRegistryApi { - pub fn default_url() -> Url { - // todo(dsherret): remove DENO_NPM_REGISTRY in the future (maybe May 2023) - let env_var_names = ["NPM_CONFIG_REGISTRY", "DENO_NPM_REGISTRY"]; - for env_var_name in env_var_names { - if let Ok(registry_url) = std::env::var(env_var_name) { - // ensure there is a trailing slash for the directory - let registry_url = format!("{}/", registry_url.trim_end_matches('/')); - match Url::parse(®istry_url) { - Ok(url) => { - if env_var_name == "DENO_NPM_REGISTRY" { - log::warn!( - "{}", - colors::yellow(concat!( - "DENO_NPM_REGISTRY was intended for internal testing purposes only. ", - "Please update to NPM_CONFIG_REGISTRY instead.", - )), - ); - } - return url; - } - Err(err) => { - log::debug!( - "Invalid {} environment variable: {:#}", - env_var_name, - err, - ); - } - } - } - } +#[derive(Clone, Debug)] +pub struct NpmRegistryApi(Arc); - Url::parse("https://registry.npmjs.org").unwrap() +impl NpmRegistryApi { + pub fn default_url() -> &'static Url { + &NPM_REGISTRY_DEFAULT_URL } pub fn new( @@ -286,26 +232,124 @@ impl RealNpmRegistryApi { })) } + /// Creates an npm registry API that will be uninitialized + /// and error for every request. This is useful for tests + /// or for initializing the LSP. + pub fn new_uninitialized() -> Self { + Self(Arc::new(NullNpmRegistryApiInner)) + } + + #[cfg(test)] + pub fn new_for_test(api: TestNpmRegistryApiInner) -> NpmRegistryApi { + Self(Arc::new(api)) + } + + pub async fn package_info( + &self, + name: &str, + ) -> Result, AnyError> { + let maybe_package_info = self.0.maybe_package_info(name).await?; + match maybe_package_info { + Some(package_info) => Ok(package_info), + None => bail!("npm package '{}' does not exist", name), + } + } + + pub async fn package_version_info( + &self, + nv: &NpmPackageNv, + ) -> Result, AnyError> { + let package_info = self.package_info(&nv.name).await?; + Ok(package_info.versions.get(&nv.version.to_string()).cloned()) + } + + /// Caches all the package information in memory in parallel. + pub async fn cache_in_parallel( + &self, + package_names: Vec, + ) -> Result<(), AnyError> { + let mut unresolved_tasks = Vec::with_capacity(package_names.len()); + + // cache the package info up front in parallel + if should_sync_download() { + // for deterministic test output + let mut ordered_names = package_names; + ordered_names.sort(); + for name in ordered_names { + self.package_info(&name).await?; + } + } else { + for name in package_names { + let api = self.clone(); + unresolved_tasks.push(tokio::task::spawn(async move { + // This is ok to call because api will internally cache + // the package information in memory. + api.package_info(&name).await + })); + } + }; + + for result in futures::future::join_all(unresolved_tasks).await { + result??; // surface the first error + } + + Ok(()) + } + + /// Clears the internal memory cache. + pub fn clear_memory_cache(&self) { + self.0.clear_memory_cache(); + } + + pub fn get_cached_package_info( + &self, + name: &str, + ) -> Option> { + self.0.get_cached_package_info(name) + } + pub fn base_url(&self) -> &Url { - &self.0.base_url + self.0.base_url() } } -impl NpmRegistryApi for RealNpmRegistryApi { - fn maybe_package_info( +#[async_trait] +trait NpmRegistryApiInner: std::fmt::Debug + Sync + Send + 'static { + async fn maybe_package_info( + &self, + name: &str, + ) -> Result>, AnyError>; + + fn clear_memory_cache(&self); + + fn get_cached_package_info(&self, name: &str) -> Option>; + + fn base_url(&self) -> &Url; +} + +#[async_trait] +impl NpmRegistryApiInner for RealNpmRegistryApiInner { + fn base_url(&self) -> &Url { + &self.base_url + } + + async fn maybe_package_info( &self, name: &str, - ) -> BoxFuture<'static, Result>, AnyError>> { - let api = self.clone(); - let name = name.to_string(); - async move { api.0.maybe_package_info(&name).await }.boxed() + ) -> Result>, AnyError> { + self.maybe_package_info(name).await } fn clear_memory_cache(&self) { - self.0.mem_cache.lock().clear(); + self.mem_cache.lock().clear(); + } + + fn get_cached_package_info(&self, name: &str) -> Option> { + self.mem_cache.lock().get(name).cloned().flatten() } } +#[derive(Debug)] struct RealNpmRegistryApiInner { base_url: Url, cache: NpmCache, @@ -339,7 +383,11 @@ impl RealNpmRegistryApiInner { .load_package_info_from_registry(name) .await .with_context(|| { - format!("Error getting response at {}", self.get_package_url(name)) + format!( + "Error getting response at {} for package \"{}\"", + self.get_package_url(name), + name + ) })?; } let maybe_package_info = maybe_package_info.map(Arc::new); @@ -467,16 +515,44 @@ impl RealNpmRegistryApiInner { } } +#[derive(Debug)] +struct NullNpmRegistryApiInner; + +#[async_trait] +impl NpmRegistryApiInner for NullNpmRegistryApiInner { + async fn maybe_package_info( + &self, + _name: &str, + ) -> Result>, AnyError> { + Err(deno_core::anyhow::anyhow!( + "Deno bug. Please report. Registry API was not initialized." + )) + } + + fn clear_memory_cache(&self) {} + + fn get_cached_package_info( + &self, + _name: &str, + ) -> Option> { + None + } + + fn base_url(&self) -> &Url { + NpmRegistryApi::default_url() + } +} + /// Note: This test struct is not thread safe for setup /// purposes. Construct everything on the same thread. #[cfg(test)] -#[derive(Clone, Default)] -pub struct TestNpmRegistryApi { +#[derive(Clone, Default, Debug)] +pub struct TestNpmRegistryApiInner { package_infos: Arc>>, } #[cfg(test)] -impl TestNpmRegistryApi { +impl TestNpmRegistryApiInner { pub fn add_package_info(&self, name: &str, info: NpmPackageInfo) { let previous = self.package_infos.lock().insert(name.to_string(), info); assert!(previous.is_none()); @@ -560,16 +636,83 @@ impl TestNpmRegistryApi { } #[cfg(test)] -impl NpmRegistryApi for TestNpmRegistryApi { - fn maybe_package_info( +#[async_trait] +impl NpmRegistryApiInner for TestNpmRegistryApiInner { + async fn maybe_package_info( &self, name: &str, - ) -> BoxFuture<'static, Result>, AnyError>> { + ) -> Result>, AnyError> { let result = self.package_infos.lock().get(name).cloned(); - Box::pin(deno_core::futures::future::ready(Ok(result.map(Arc::new)))) + Ok(result.map(Arc::new)) } fn clear_memory_cache(&self) { // do nothing for the test api } + + fn get_cached_package_info( + &self, + _name: &str, + ) -> Option> { + None + } + + fn base_url(&self) -> &Url { + NpmRegistryApi::default_url() + } +} + +#[cfg(test)] +mod test { + use std::collections::HashMap; + + use deno_core::serde_json; + + use crate::npm::registry::NpmPackageVersionBinEntry; + use crate::npm::NpmPackageVersionDistInfo; + + use super::NpmPackageVersionInfo; + + #[test] + fn deserializes_minimal_pkg_info() { + let text = r#"{ "version": "1.0.0", "dist": { "tarball": "value", "shasum": "test" } }"#; + let info: NpmPackageVersionInfo = serde_json::from_str(text).unwrap(); + assert_eq!( + info, + NpmPackageVersionInfo { + version: "1.0.0".to_string(), + dist: NpmPackageVersionDistInfo { + tarball: "value".to_string(), + shasum: "test".to_string(), + integrity: None, + }, + bin: None, + dependencies: Default::default(), + peer_dependencies: Default::default(), + peer_dependencies_meta: Default::default() + } + ); + } + + #[test] + fn deserializes_bin_entry() { + // string + let text = r#"{ "version": "1.0.0", "bin": "bin-value", "dist": { "tarball": "value", "shasum": "test" } }"#; + let info: NpmPackageVersionInfo = serde_json::from_str(text).unwrap(); + assert_eq!( + info.bin, + Some(NpmPackageVersionBinEntry::String("bin-value".to_string())) + ); + + // map + let text = r#"{ "version": "1.0.0", "bin": { "a": "a-value", "b": "b-value" }, "dist": { "tarball": "value", "shasum": "test" } }"#; + let info: NpmPackageVersionInfo = serde_json::from_str(text).unwrap(); + assert_eq!( + info.bin, + Some(NpmPackageVersionBinEntry::Map(HashMap::from([ + ("a".to_string(), "a-value".to_string()), + ("b".to_string(), "b-value".to_string()), + ]))) + ); + } } diff --git a/cli/npm/resolution/common.rs b/cli/npm/resolution/common.rs new file mode 100644 index 00000000000000..b733d8eebdcc79 --- /dev/null +++ b/cli/npm/resolution/common.rs @@ -0,0 +1,241 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. + +use deno_core::anyhow::bail; +use deno_core::error::AnyError; +use deno_graph::semver::Version; +use deno_graph::semver::VersionReq; +use once_cell::sync::Lazy; + +use super::NpmPackageId; +use crate::npm::registry::NpmPackageInfo; +use crate::npm::registry::NpmPackageVersionInfo; + +pub static LATEST_VERSION_REQ: Lazy = + Lazy::new(|| VersionReq::parse_from_specifier("latest").unwrap()); + +pub fn resolve_best_package_version_and_info<'info, 'version>( + version_req: &VersionReq, + package_info: &'info NpmPackageInfo, + existing_versions: impl Iterator, +) -> Result, AnyError> { + if let Some(version) = resolve_best_from_existing_versions( + version_req, + package_info, + existing_versions, + )? { + match package_info.versions.get(&version.to_string()) { + Some(version_info) => Ok(VersionAndInfo { + version, + info: version_info, + }), + None => { + bail!( + "could not find version '{}' for '{}'", + version, + &package_info.name + ) + } + } + } else { + // get the information + get_resolved_package_version_and_info(version_req, package_info, None) + } +} + +#[derive(Clone)] +pub struct VersionAndInfo<'a> { + pub version: Version, + pub info: &'a NpmPackageVersionInfo, +} + +fn get_resolved_package_version_and_info<'a>( + version_req: &VersionReq, + info: &'a NpmPackageInfo, + parent: Option<&NpmPackageId>, +) -> Result, AnyError> { + if let Some(tag) = version_req.tag() { + tag_to_version_info(info, tag, parent) + } else { + let mut maybe_best_version: Option = None; + for version_info in info.versions.values() { + let version = Version::parse_from_npm(&version_info.version)?; + if version_req.matches(&version) { + let is_best_version = maybe_best_version + .as_ref() + .map(|best_version| best_version.version.cmp(&version).is_lt()) + .unwrap_or(true); + if is_best_version { + maybe_best_version = Some(VersionAndInfo { + version, + info: version_info, + }); + } + } + } + + match maybe_best_version { + Some(v) => Ok(v), + // If the package isn't found, it likely means that the user needs to use + // `--reload` to get the latest npm package information. Although it seems + // like we could make this smart by fetching the latest information for + // this package here, we really need a full restart. There could be very + // interesting bugs that occur if this package's version was resolved by + // something previous using the old information, then now being smart here + // causes a new fetch of the package information, meaning this time the + // previous resolution of this package's version resolved to an older + // version, but next time to a different version because it has new information. + None => bail!( + concat!( + "Could not find npm package '{}' matching {}{}. ", + "Try retrieving the latest npm package information by running with --reload", + ), + info.name, + version_req.version_text(), + match parent { + Some(resolved_id) => format!(" as specified in {}", resolved_id.nv), + None => String::new(), + } + ), + } + } +} + +pub fn version_req_satisfies( + version_req: &VersionReq, + version: &Version, + package_info: &NpmPackageInfo, + parent: Option<&NpmPackageId>, +) -> Result { + match version_req.tag() { + Some(tag) => { + let tag_version = tag_to_version_info(package_info, tag, parent)?.version; + Ok(tag_version == *version) + } + None => Ok(version_req.matches(version)), + } +} + +fn resolve_best_from_existing_versions<'a>( + version_req: &VersionReq, + package_info: &NpmPackageInfo, + existing_versions: impl Iterator, +) -> Result, AnyError> { + let mut maybe_best_version: Option<&Version> = None; + for version in existing_versions { + if version_req_satisfies(version_req, version, package_info, None)? { + let is_best_version = maybe_best_version + .as_ref() + .map(|best_version| (*best_version).cmp(version).is_lt()) + .unwrap_or(true); + if is_best_version { + maybe_best_version = Some(version); + } + } + } + Ok(maybe_best_version.cloned()) +} + +fn tag_to_version_info<'a>( + info: &'a NpmPackageInfo, + tag: &str, + parent: Option<&NpmPackageId>, +) -> Result, AnyError> { + // For when someone just specifies @types/node, we want to pull in a + // "known good" version of @types/node that works well with Deno and + // not necessarily the latest version. For example, we might only be + // compatible with Node vX, but then Node vY is published so we wouldn't + // want to pull that in. + // Note: If the user doesn't want this behavior, then they can specify an + // explicit version. + if tag == "latest" && info.name == "@types/node" { + return get_resolved_package_version_and_info( + &VersionReq::parse_from_npm("18.0.0 - 18.11.18").unwrap(), + info, + parent, + ); + } + + if let Some(version) = info.dist_tags.get(tag) { + match info.versions.get(version) { + Some(info) => Ok(VersionAndInfo { + version: Version::parse_from_npm(version)?, + info, + }), + None => { + bail!( + "Could not find version '{}' referenced in dist-tag '{}'.", + version, + tag, + ) + } + } + } else { + bail!("Could not find dist-tag '{}'.", tag) + } +} + +#[cfg(test)] +mod test { + use std::collections::HashMap; + + use deno_graph::npm::NpmPackageReqReference; + + use super::*; + + #[test] + fn test_get_resolved_package_version_and_info() { + // dist tag where version doesn't exist + let package_ref = NpmPackageReqReference::from_str("npm:test").unwrap(); + let package_info = NpmPackageInfo { + name: "test".to_string(), + versions: HashMap::new(), + dist_tags: HashMap::from([( + "latest".to_string(), + "1.0.0-alpha".to_string(), + )]), + }; + let result = get_resolved_package_version_and_info( + package_ref + .req + .version_req + .as_ref() + .unwrap_or(&*LATEST_VERSION_REQ), + &package_info, + None, + ); + assert_eq!( + result.err().unwrap().to_string(), + "Could not find version '1.0.0-alpha' referenced in dist-tag 'latest'." + ); + + // dist tag where version is a pre-release + let package_ref = NpmPackageReqReference::from_str("npm:test").unwrap(); + let package_info = NpmPackageInfo { + name: "test".to_string(), + versions: HashMap::from([ + ("0.1.0".to_string(), NpmPackageVersionInfo::default()), + ( + "1.0.0-alpha".to_string(), + NpmPackageVersionInfo { + version: "0.1.0-alpha".to_string(), + ..Default::default() + }, + ), + ]), + dist_tags: HashMap::from([( + "latest".to_string(), + "1.0.0-alpha".to_string(), + )]), + }; + let result = get_resolved_package_version_and_info( + package_ref + .req + .version_req + .as_ref() + .unwrap_or(&*LATEST_VERSION_REQ), + &package_info, + None, + ); + assert_eq!(result.unwrap().version.to_string(), "1.0.0-alpha"); + } +} diff --git a/cli/npm/resolution/graph.rs b/cli/npm/resolution/graph.rs index 20f192fbf80bb4..87579dad32482d 100644 --- a/cli/npm/resolution/graph.rs +++ b/cli/npm/resolution/graph.rs @@ -1,198 +1,358 @@ // Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. -use std::borrow::Cow; +use std::collections::hash_map::DefaultHasher; use std::collections::BTreeMap; -use std::collections::BTreeSet; use std::collections::HashMap; +use std::collections::HashSet; use std::collections::VecDeque; +use std::hash::Hash; +use std::hash::Hasher; use std::sync::Arc; use deno_core::anyhow::bail; use deno_core::anyhow::Context; use deno_core::error::AnyError; -use deno_core::futures; use deno_core::parking_lot::Mutex; -use deno_core::parking_lot::MutexGuard; +use deno_graph::npm::NpmPackageNv; +use deno_graph::npm::NpmPackageReq; +use deno_graph::semver::VersionReq; use log::debug; -use once_cell::sync::Lazy; -use crate::npm::cache::should_sync_download; use crate::npm::registry::NpmDependencyEntry; use crate::npm::registry::NpmDependencyEntryKind; use crate::npm::registry::NpmPackageInfo; use crate::npm::registry::NpmPackageVersionInfo; +use crate::npm::resolution::common::resolve_best_package_version_and_info; +use crate::npm::resolution::snapshot::SnapshotPackageCopyIndexResolver; use crate::npm::NpmRegistryApi; -use crate::semver::Version; -use crate::semver::VersionReq; +use super::common::version_req_satisfies; +use super::common::LATEST_VERSION_REQ; use super::snapshot::NpmResolutionSnapshot; -use super::snapshot::SnapshotPackageCopyIndexResolver; use super::NpmPackageId; -use super::NpmPackageReq; use super::NpmResolutionPackage; -pub static LATEST_VERSION_REQ: Lazy = - Lazy::new(|| VersionReq::parse_from_specifier("latest").unwrap()); - -/// A memory efficient path of visited name and versions in the graph -/// which is used to detect cycles. -/// -/// note(dsherret): although this is definitely more memory efficient -/// than a HashSet, I haven't done any tests about whether this is -/// faster in practice. -#[derive(Default, Clone)] -struct VisitedVersionsPath { - previous_node: Option>, - visited_version_key: String, +/// A unique identifier to a node in the graph. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Ord, PartialOrd)] +struct NodeId(u32); + +/// A resolved package in the resolution graph. +#[derive(Debug)] +struct Node { + /// The specifier to child relationship in the graph. The specifier is + /// the key in an npm package's dependencies map (ex. "express"). We + /// use a BTreeMap for some determinism when creating the snapshot. + /// + /// Note: We don't want to store the children as a `NodeRef` because + /// multiple paths might visit through the children and we don't want + /// to share those references with those paths. + pub children: BTreeMap, + /// Whether the node has demonstrated to have no peer dependencies in its + /// descendants. If this is true then we can skip analyzing this node + /// again when we encounter it another time in the dependency tree, which + /// is much faster. + pub no_peers: bool, } -impl VisitedVersionsPath { - pub fn new(id: &NpmPackageId) -> Arc { - Arc::new(Self { - previous_node: None, - visited_version_key: Self::id_to_key(id), - }) +#[derive(Clone)] +enum ResolvedIdPeerDep { + /// This is a reference to the parent instead of the child because we only have a + /// node reference to the parent, since we've traversed it, but the child node may + /// change from under it. + ParentReference { + parent: GraphPathNodeOrRoot, + child_pkg_nv: NpmPackageNv, + }, + /// A node that was created during snapshotting and is not being used in any path. + SnapshotNodeId(NodeId), +} + +impl ResolvedIdPeerDep { + pub fn current_state_hash(&self) -> u64 { + let mut hasher = DefaultHasher::new(); + self.current_state_hash_with_hasher(&mut hasher); + hasher.finish() } - pub fn with_parent( - self: &Arc, - parent: &NodeParent, - ) -> Option> { - match parent { - NodeParent::Node(id) => self.with_id(id), - NodeParent::Req => Some(self.clone()), + pub fn current_state_hash_with_hasher(&self, hasher: &mut DefaultHasher) { + match self { + ResolvedIdPeerDep::ParentReference { + parent, + child_pkg_nv, + } => { + match parent { + GraphPathNodeOrRoot::Root(root) => root.hash(hasher), + GraphPathNodeOrRoot::Node(node) => node.node_id().hash(hasher), + } + child_pkg_nv.hash(hasher); + } + ResolvedIdPeerDep::SnapshotNodeId(node_id) => { + node_id.hash(hasher); + } } } +} - pub fn with_id( - self: &Arc, - id: &NpmPackageId, - ) -> Option> { - if self.has_visited(id) { - None - } else { - Some(Arc::new(Self { - previous_node: Some(self.clone()), - visited_version_key: Self::id_to_key(id), - })) +/// A pending resolved identifier used in the graph. At the end of resolution, these +/// will become fully resolved to an `NpmPackageId`. +#[derive(Clone)] +struct ResolvedId { + nv: NpmPackageNv, + peer_dependencies: Vec, +} + +impl ResolvedId { + /// Gets a hash of the resolved identifier at this current moment in time. + /// + /// WARNING: A resolved identifier references a value that could change in + /// the future, so this should be used with that in mind. + pub fn current_state_hash(&self) -> u64 { + let mut hasher = DefaultHasher::new(); + self.nv.hash(&mut hasher); + for dep in &self.peer_dependencies { + dep.current_state_hash_with_hasher(&mut hasher); } + hasher.finish() } - pub fn has_visited(self: &Arc, id: &NpmPackageId) -> bool { - let mut maybe_next_node = Some(self); - let key = Self::id_to_key(id); - while let Some(next_node) = maybe_next_node { - if next_node.visited_version_key == key { - return true; + pub fn push_peer_dep(&mut self, peer_dep: ResolvedIdPeerDep) { + let new_hash = peer_dep.current_state_hash(); + for dep in &self.peer_dependencies { + if new_hash == dep.current_state_hash() { + return; } - maybe_next_node = next_node.previous_node.as_ref(); } - false + self.peer_dependencies.push(peer_dep); + } +} + +/// Mappings of node identifiers to resolved identifiers. Each node has exactly +/// one resolved identifier. +#[derive(Default)] +struct ResolvedNodeIds { + node_to_resolved_id: HashMap, + resolved_to_node_id: HashMap, +} + +impl ResolvedNodeIds { + pub fn set(&mut self, node_id: NodeId, resolved_id: ResolvedId) { + let resolved_id_hash = resolved_id.current_state_hash(); + if let Some((_, old_resolved_id_key)) = self + .node_to_resolved_id + .insert(node_id, (resolved_id, resolved_id_hash)) + { + // ensure the old resolved id key is removed as it might be stale + self.resolved_to_node_id.remove(&old_resolved_id_key); + } + self.resolved_to_node_id.insert(resolved_id_hash, node_id); + } + + pub fn get(&self, node_id: NodeId) -> Option<&ResolvedId> { + self.node_to_resolved_id.get(&node_id).map(|(id, _)| id) + } + + pub fn get_node_id(&self, resolved_id: &ResolvedId) -> Option { + self + .resolved_to_node_id + .get(&resolved_id.current_state_hash()) + .copied() + } +} + +// todo(dsherret): for some reason the lsp errors when using an Rc> here +// instead of an Arc>. We should investigate and fix. + +/// A pointer to a specific node in a graph path. The underlying node id +/// may change as peer dependencies are created. +#[derive(Clone, Debug)] +struct NodeIdRef(Arc>); + +impl NodeIdRef { + pub fn new(node_id: NodeId) -> Self { + NodeIdRef(Arc::new(Mutex::new(node_id))) + } + + pub fn change(&self, node_id: NodeId) { + *self.0.lock() = node_id; } - fn id_to_key(id: &NpmPackageId) -> String { - format!("{}@{}", id.name, id.version) + pub fn get(&self) -> NodeId { + *self.0.lock() } } -/// A memory efficient path of the visited specifiers in the tree. -#[derive(Default, Clone)] -struct GraphSpecifierPath { - previous_node: Option>, +#[derive(Clone)] +enum GraphPathNodeOrRoot { + Node(Arc), + Root(NpmPackageNv), +} + +/// Path through the graph that represents a traversal through the graph doing +/// the dependency resolution. The graph tries to share duplicate package +/// information and we try to avoid traversing parts of the graph that we know +/// are resolved. +#[derive(Clone)] +struct GraphPath { + previous_node: Option, + node_id_ref: NodeIdRef, + // todo(dsherret): I think we might be able to get rid of specifier and + // node version here, but I added them for extra protection for the time being. specifier: String, + nv: NpmPackageNv, } -impl GraphSpecifierPath { - pub fn new(specifier: String) -> Arc { +impl GraphPath { + pub fn for_root(node_id: NodeId, nv: NpmPackageNv) -> Arc { Arc::new(Self { - previous_node: None, - specifier, + previous_node: Some(GraphPathNodeOrRoot::Root(nv.clone())), + node_id_ref: NodeIdRef::new(node_id), + // use an empty specifier + specifier: "".to_string(), + nv, }) } - pub fn with_specifier(self: &Arc, specifier: String) -> Arc { - Arc::new(Self { - previous_node: Some(self.clone()), - specifier, - }) + pub fn node_id(&self) -> NodeId { + self.node_id_ref.get() } - pub fn pop(&self) -> Option<&Arc> { - self.previous_node.as_ref() + pub fn specifier(&self) -> &str { + &self.specifier } -} -#[derive(Debug, Clone, PartialEq, Eq, Hash, Ord, PartialOrd)] -enum NodeParent { - /// These are top of the graph npm package requirements - /// as specified in Deno code. - Req, - /// A reference to another node, which is a resolved package. - Node(NpmPackageId), -} + pub fn change_id(&self, node_id: NodeId) { + self.node_id_ref.change(node_id) + } -/// A resolved package in the resolution graph. -#[derive(Debug)] -struct Node { - pub id: NpmPackageId, - /// If the node was forgotten due to having no parents. - pub forgotten: bool, - // Use BTreeMap and BTreeSet in order to create determinism - // when going up and down the tree - pub parents: BTreeMap>, - pub children: BTreeMap, - pub deps: Arc>, - /// Whether the node has demonstrated to have no peer dependencies in its - /// descendants. If this is true then we can skip analyzing this node - /// again when we encounter it another time in the dependency tree, which - /// is much faster. - pub no_peers: bool, -} + pub fn with_id( + self: &Arc, + node_id: NodeId, + specifier: &str, + nv: NpmPackageNv, + ) -> Option> { + if self.has_visited(&nv) { + None + } else { + Some(Arc::new(Self { + previous_node: Some(GraphPathNodeOrRoot::Node(self.clone())), + node_id_ref: NodeIdRef::new(node_id), + specifier: specifier.to_string(), + nv, + })) + } + } + + /// Each time an identifier is added, we do a check to ensure + /// that we haven't previously visited this node. I suspect this + /// might be a little slow since it has to go up through the ancestors, + /// so some optimizations could be made here in the future. + pub fn has_visited(self: &Arc, nv: &NpmPackageNv) -> bool { + if self.nv == *nv { + return true; + } + let mut maybe_next_node = self.previous_node.as_ref(); + while let Some(GraphPathNodeOrRoot::Node(next_node)) = maybe_next_node { + // we've visited this before, so stop + if next_node.nv == *nv { + return true; + } + maybe_next_node = next_node.previous_node.as_ref(); + } + false + } -impl Node { - pub fn add_parent(&mut self, specifier: String, parent: NodeParent) { - self.parents.entry(specifier).or_default().insert(parent); + pub fn ancestors(&self) -> GraphPathAncestorIterator { + GraphPathAncestorIterator { + next: self.previous_node.as_ref(), + } } +} - pub fn remove_parent(&mut self, specifier: &str, parent: &NodeParent) { - if let Some(parents) = self.parents.get_mut(specifier) { - parents.remove(parent); - if parents.is_empty() { - self.parents.remove(specifier); +struct GraphPathAncestorIterator<'a> { + next: Option<&'a GraphPathNodeOrRoot>, +} + +impl<'a> Iterator for GraphPathAncestorIterator<'a> { + type Item = &'a GraphPathNodeOrRoot; + fn next(&mut self) -> Option { + if let Some(next) = self.next.take() { + if let GraphPathNodeOrRoot::Node(node) = next { + self.next = node.previous_node.as_ref(); } + Some(next) + } else { + None } } } -#[derive(Debug, Default)] +#[derive(Default)] pub struct Graph { - package_reqs: HashMap, - packages_by_name: HashMap>, - // Ideally this value would be Rc>, but we need to use a Mutex - // because the lsp requires Send and this code is executed in the lsp. - // Would be nice if the lsp wasn't Send. - packages: HashMap>>, + /// Each requirement is mapped to a specific name and version. + package_reqs: HashMap, + /// Then each name and version is mapped to an exact node id. + /// Note: Uses a BTreeMap in order to create some determinism + /// when creating the snapshot. + root_packages: BTreeMap, + nodes_by_package_name: HashMap>, + nodes: HashMap, + resolved_node_ids: ResolvedNodeIds, // This will be set when creating from a snapshot, then // inform the final snapshot creation. packages_to_copy_index: HashMap, + /// Packages that the resolver should resolve first. + pending_unresolved_packages: Vec, } impl Graph { - pub fn from_snapshot(snapshot: NpmResolutionSnapshot) -> Self { - fn fill_for_id( + pub fn from_snapshot( + snapshot: NpmResolutionSnapshot, + ) -> Result { + fn get_or_create_graph_node( graph: &mut Graph, - id: &NpmPackageId, + resolved_id: &NpmPackageId, packages: &HashMap, - ) -> Arc> { - let resolution = packages.get(id).unwrap(); - let (created, node) = graph.get_or_create_for_id(id); - if created { - for (name, child_id) in &resolution.dependencies { - let child_node = fill_for_id(graph, child_id, packages); - graph.set_child_parent_node(name, &child_node, id); - } + created_package_ids: &mut HashMap, + ) -> Result { + if let Some(id) = created_package_ids.get(resolved_id) { + return Ok(*id); + } + + let node_id = graph.create_node(&resolved_id.nv); + created_package_ids.insert(resolved_id.clone(), node_id); + + let peer_dep_ids = resolved_id + .peer_dependencies + .iter() + .map(|peer_dep| { + Ok(ResolvedIdPeerDep::SnapshotNodeId(get_or_create_graph_node( + graph, + peer_dep, + packages, + created_package_ids, + )?)) + }) + .collect::, AnyError>>()?; + let graph_resolved_id = ResolvedId { + nv: resolved_id.nv.clone(), + peer_dependencies: peer_dep_ids, + }; + graph.resolved_node_ids.set(node_id, graph_resolved_id); + let resolution = match packages.get(resolved_id) { + Some(resolved_id) => resolved_id, + // maybe the user messed around with the lockfile + None => bail!("not found package: {}", resolved_id.as_serialized()), + }; + for (name, child_id) in &resolution.dependencies { + let child_node_id = get_or_create_graph_node( + graph, + child_id, + packages, + created_package_ids, + )?; + graph.set_child_parent_node(name, child_node_id, node_id); } - node + Ok(node_id) } let mut graph = Self { @@ -203,274 +363,398 @@ impl Graph { .iter() .map(|(id, p)| (id.clone(), p.copy_index)) .collect(), + package_reqs: snapshot.package_reqs, + pending_unresolved_packages: snapshot.pending_unresolved_packages, ..Default::default() }; - for (package_req, id) in &snapshot.package_reqs { - let node = fill_for_id(&mut graph, id, &snapshot.packages); - let package_req_text = package_req.to_string(); - (*node) - .lock() - .add_parent(package_req_text.clone(), NodeParent::Req); - graph.package_reqs.insert(package_req_text, id.clone()); + let mut created_package_ids = + HashMap::with_capacity(snapshot.packages.len()); + for (id, resolved_id) in snapshot.root_packages { + let node_id = get_or_create_graph_node( + &mut graph, + &resolved_id, + &snapshot.packages, + &mut created_package_ids, + )?; + graph.root_packages.insert(id, node_id); } - graph + Ok(graph) } - pub fn has_package_req(&self, req: &NpmPackageReq) -> bool { - self.package_reqs.contains_key(&req.to_string()) + pub fn take_pending_unresolved(&mut self) -> Vec { + std::mem::take(&mut self.pending_unresolved_packages) } - fn get_or_create_for_id( - &mut self, - id: &NpmPackageId, - ) -> (bool, Arc>) { - if let Some(node) = self.packages.get(id) { - (false, node.clone()) - } else { - let node = Arc::new(Mutex::new(Node { - id: id.clone(), - forgotten: false, - parents: Default::default(), - children: Default::default(), - deps: Default::default(), - no_peers: false, - })); - self - .packages_by_name - .entry(id.name.clone()) - .or_default() - .push(id.clone()); - self.packages.insert(id.clone(), node.clone()); - (true, node) - } + pub fn has_package_req(&self, req: &NpmPackageReq) -> bool { + self.package_reqs.contains_key(req) } - fn borrow_node(&self, id: &NpmPackageId) -> MutexGuard { - (**self.packages.get(id).unwrap_or_else(|| { - panic!("could not find id {} in the tree", id.as_serialized()) - })) - .lock() + pub fn has_root_package(&self, id: &NpmPackageNv) -> bool { + self.root_packages.contains_key(id) } - fn forget_orphan(&mut self, node_id: &NpmPackageId) { - if let Some(node) = self.packages.remove(node_id) { - let mut node = (*node).lock(); - node.forgotten = true; - assert_eq!(node.parents.len(), 0); - - // Remove the id from the list of packages by name. - let packages_with_name = - self.packages_by_name.get_mut(&node.id.name).unwrap(); - let remove_index = packages_with_name - .iter() - .position(|id| id == &node.id) - .unwrap(); - packages_with_name.remove(remove_index); + fn get_npm_pkg_id(&self, node_id: NodeId) -> NpmPackageId { + let resolved_id = self.resolved_node_ids.get(node_id).unwrap(); + self.get_npm_pkg_id_from_resolved_id(resolved_id, HashSet::new()) + } - let parent = NodeParent::Node(node.id.clone()); - for (specifier, child_id) in &node.children { - let mut child = self.borrow_node(child_id); - child.remove_parent(specifier, &parent); - if child.parents.is_empty() { - drop(child); // stop borrowing from self - self.forget_orphan(child_id); + fn get_npm_pkg_id_from_resolved_id( + &self, + resolved_id: &ResolvedId, + seen: HashSet, + ) -> NpmPackageId { + if resolved_id.peer_dependencies.is_empty() { + NpmPackageId { + nv: resolved_id.nv.clone(), + peer_dependencies: Vec::new(), + } + } else { + let mut npm_pkg_id = NpmPackageId { + nv: resolved_id.nv.clone(), + peer_dependencies: Vec::with_capacity( + resolved_id.peer_dependencies.len(), + ), + }; + let mut seen_children_resolved_ids = + HashSet::with_capacity(resolved_id.peer_dependencies.len()); + for peer_dep in &resolved_id.peer_dependencies { + let maybe_node_and_resolved_id = match peer_dep { + ResolvedIdPeerDep::SnapshotNodeId(node_id) => self + .resolved_node_ids + .get(*node_id) + .map(|resolved_id| (*node_id, resolved_id)), + ResolvedIdPeerDep::ParentReference { + parent, + child_pkg_nv: child_nv, + } => match &parent { + GraphPathNodeOrRoot::Root(_) => { + self.root_packages.get(child_nv).and_then(|node_id| { + self + .resolved_node_ids + .get(*node_id) + .map(|resolved_id| (*node_id, resolved_id)) + }) + } + GraphPathNodeOrRoot::Node(parent_path) => { + self.nodes.get(&parent_path.node_id()).and_then(|parent| { + parent + .children + .values() + .filter_map(|child_id| { + let child_id = *child_id; + self + .resolved_node_ids + .get(child_id) + .map(|resolved_id| (child_id, resolved_id)) + }) + .find(|(_, resolved_id)| resolved_id.nv == *child_nv) + }) + } + }, + }; + // this should always be set + debug_assert!(maybe_node_and_resolved_id.is_some()); + if let Some((child_id, child_resolved_id)) = maybe_node_and_resolved_id + { + let mut new_seen = seen.clone(); + if new_seen.insert(child_id) { + let child_peer = self.get_npm_pkg_id_from_resolved_id( + child_resolved_id, + new_seen.clone(), + ); + + // This condition prevents a name showing up in the peer_dependencies + // list that matches the current name. Checking just the name and + // version should be sufficient because the rest of the peer dependency + // resolutions should be the same + let is_pkg_same = child_peer.nv == npm_pkg_id.nv; + if !is_pkg_same + && seen_children_resolved_ids.insert(child_peer.clone()) + { + npm_pkg_id.peer_dependencies.push(child_peer); + } + } } } + npm_pkg_id } } - fn set_child_parent( + fn get_or_create_for_id( &mut self, - specifier: &str, - child: &Mutex, - parent: &NodeParent, - ) { - match parent { - NodeParent::Node(parent_id) => { - self.set_child_parent_node(specifier, child, parent_id); - } - NodeParent::Req => { - let mut node = (*child).lock(); - node.add_parent(specifier.to_string(), parent.clone()); - self - .package_reqs - .insert(specifier.to_string(), node.id.clone()); - } + resolved_id: &ResolvedId, + ) -> (bool, NodeId) { + if let Some(node_id) = self.resolved_node_ids.get_node_id(resolved_id) { + return (false, node_id); } + + let node_id = self.create_node(&resolved_id.nv); + self.resolved_node_ids.set(node_id, resolved_id.clone()); + (true, node_id) } - fn set_child_parent_node( - &mut self, - specifier: &str, - child: &Mutex, - parent_id: &NpmPackageId, - ) { - let mut child = (*child).lock(); - assert_ne!(child.id, *parent_id); - let mut parent = (**self.packages.get(parent_id).unwrap_or_else(|| { - panic!( - "could not find {} in list of packages when setting child {}", - parent_id.as_serialized(), - child.id.as_serialized() - ) - })) - .lock(); - parent - .children - .insert(specifier.to_string(), child.id.clone()); - child - .add_parent(specifier.to_string(), NodeParent::Node(parent.id.clone())); + fn create_node(&mut self, pkg_nv: &NpmPackageNv) -> NodeId { + let node_id = NodeId(self.nodes.len() as u32); + let node = Node { + children: Default::default(), + no_peers: false, + }; + + self + .nodes_by_package_name + .entry(pkg_nv.name.clone()) + .or_default() + .push(node_id); + self.nodes.insert(node_id, node); + + node_id + } + + fn borrow_node_mut(&mut self, node_id: NodeId) -> &mut Node { + self.nodes.get_mut(&node_id).unwrap() } - fn remove_child_parent( + fn set_child_parent_node( &mut self, specifier: &str, - child_id: &NpmPackageId, - parent: &NodeParent, + child_id: NodeId, + parent_id: NodeId, ) { - match parent { - NodeParent::Node(parent_id) => { - let mut node = self.borrow_node(parent_id); - if let Some(removed_child_id) = node.children.remove(specifier) { - assert_eq!(removed_child_id, *child_id); - } - } - NodeParent::Req => { - if let Some(removed_child_id) = self.package_reqs.remove(specifier) { - assert_eq!(removed_child_id, *child_id); - } - } - } - self.borrow_node(child_id).remove_parent(specifier, parent); + assert_ne!(child_id, parent_id); + let parent = self.borrow_node_mut(parent_id); + parent.children.insert(specifier.to_string(), child_id); } pub async fn into_snapshot( self, - api: &impl NpmRegistryApi, + api: &NpmRegistryApi, ) -> Result { + let packages_to_resolved_id = self + .nodes + .keys() + .map(|node_id| (*node_id, self.get_npm_pkg_id(*node_id))) + .collect::>(); let mut copy_index_resolver = SnapshotPackageCopyIndexResolver::from_map_with_capacity( self.packages_to_copy_index, - self.packages.len(), + self.nodes.len(), ); - - // Iterate through the packages vector in each packages_by_name in order - // to set the copy index as this will be deterministic rather than - // iterating over the hashmap below. - for packages in self.packages_by_name.values() { - if packages.len() > 1 { - for id in packages { - copy_index_resolver.resolve(id); - } + let mut packages = HashMap::with_capacity(self.nodes.len()); + let mut traversed_node_ids = HashSet::with_capacity(self.nodes.len()); + let mut pending = VecDeque::new(); + for root_id in self.root_packages.values() { + if traversed_node_ids.insert(*root_id) { + pending.push_back(*root_id); } } - - let mut packages = HashMap::with_capacity(self.packages.len()); - for (id, node) in self.packages { + while let Some(node_id) = pending.pop_front() { + let node = self.nodes.get(&node_id).unwrap(); + let resolved_id = packages_to_resolved_id.get(&node_id).unwrap(); + // todo(dsherret): grab this from the dep entry cache, which should have it let dist = api - .package_version_info(&id.name, &id.version) + .package_version_info(&resolved_id.nv) .await? - .unwrap() + .unwrap_or_else(|| panic!("missing: {:?}", resolved_id.nv)) .dist; - let node = node.lock(); packages.insert( - id.clone(), + (*resolved_id).clone(), NpmResolutionPackage { - copy_index: copy_index_resolver.resolve(&id), - id, + copy_index: copy_index_resolver.resolve(resolved_id), + pkg_id: (*resolved_id).clone(), dist, dependencies: node .children .iter() - .map(|(key, value)| (key.clone(), value.clone())) + .map(|(key, value)| { + ( + key.clone(), + packages_to_resolved_id + .get(value) + .unwrap_or_else(|| { + panic!("{node_id:?} -- missing child: {value:?}") + }) + .clone(), + ) + }) .collect(), }, ); + for child_id in node.children.values() { + if traversed_node_ids.insert(*child_id) { + pending.push_back(*child_id); + } + } } Ok(NpmResolutionSnapshot { - package_reqs: self - .package_reqs + root_packages: self + .root_packages .into_iter() - .map(|(specifier, id)| { - (NpmPackageReq::from_str(&specifier).unwrap(), id) + .map(|(id, node_id)| { + (id, packages_to_resolved_id.get(&node_id).unwrap().clone()) + }) + .collect(), + packages_by_name: self + .nodes_by_package_name + .into_iter() + .map(|(name, ids)| { + let mut ids = ids + .into_iter() + .filter(|id| traversed_node_ids.contains(id)) + .map(|id| packages_to_resolved_id.get(&id).unwrap().clone()) + .collect::>(); + ids.sort(); + ids.dedup(); + (name, ids) }) .collect(), - packages_by_name: self.packages_by_name, packages, + package_reqs: self.package_reqs, + pending_unresolved_packages: self.pending_unresolved_packages, }) } + + // Debugging methods + + #[cfg(debug_assertions)] + #[allow(unused)] + fn output_path(&self, path: &Arc) { + eprintln!("-----------"); + self.output_node(path.node_id(), false); + for path in path.ancestors() { + match path { + GraphPathNodeOrRoot::Node(node) => { + self.output_node(node.node_id(), false) + } + GraphPathNodeOrRoot::Root(pkg_id) => { + let node_id = self.root_packages.get(pkg_id).unwrap(); + eprintln!( + "Root: {} ({}: {})", + pkg_id, + node_id.0, + self.get_npm_pkg_id(*node_id).as_serialized() + ) + } + } + } + eprintln!("-----------"); + } + + #[cfg(debug_assertions)] + #[allow(unused)] + fn output_node(&self, node_id: NodeId, show_children: bool) { + eprintln!( + "{:>4}: {}", + node_id.0, + self.get_npm_pkg_id(node_id).as_serialized() + ); + + if show_children { + let node = self.nodes.get(&node_id).unwrap(); + eprintln!(" Children:"); + for (specifier, child_id) in &node.children { + eprintln!(" {}: {}", specifier, child_id.0); + } + } + } + + #[cfg(debug_assertions)] + #[allow(unused)] + pub fn output_nodes(&self) { + eprintln!("~~~"); + let mut node_ids = self + .resolved_node_ids + .node_to_resolved_id + .keys() + .copied() + .collect::>(); + node_ids.sort_by(|a, b| a.0.cmp(&b.0)); + for node_id in node_ids { + self.output_node(node_id, true); + } + eprintln!("~~~"); + } +} + +#[derive(Default)] +struct DepEntryCache(HashMap>>); + +impl DepEntryCache { + pub fn store( + &mut self, + nv: NpmPackageNv, + version_info: &NpmPackageVersionInfo, + ) -> Result>, AnyError> { + debug_assert!(!self.0.contains_key(&nv)); // we should not be re-inserting + let mut deps = version_info + .dependencies_as_entries() + .with_context(|| format!("npm package: {nv}"))?; + // Ensure name alphabetical and then version descending + // so these are resolved in that order + deps.sort(); + let deps = Arc::new(deps); + self.0.insert(nv, deps.clone()); + Ok(deps) + } + + pub fn get( + &self, + id: &NpmPackageNv, + ) -> Option<&Arc>> { + self.0.get(id) + } +} + +struct UnresolvedOptionalPeer { + specifier: String, + graph_path: Arc, } -pub struct GraphDependencyResolver<'a, TNpmRegistryApi: NpmRegistryApi> { +pub struct GraphDependencyResolver<'a> { graph: &'a mut Graph, - api: &'a TNpmRegistryApi, - pending_unresolved_nodes: - VecDeque<(Arc, Arc>)>, + api: &'a NpmRegistryApi, + pending_unresolved_nodes: VecDeque>, + unresolved_optional_peers: HashMap>, + dep_entry_cache: DepEntryCache, } -impl<'a, TNpmRegistryApi: NpmRegistryApi> - GraphDependencyResolver<'a, TNpmRegistryApi> -{ - pub fn new(graph: &'a mut Graph, api: &'a TNpmRegistryApi) -> Self { +impl<'a> GraphDependencyResolver<'a> { + pub fn new(graph: &'a mut Graph, api: &'a NpmRegistryApi) -> Self { Self { graph, api, pending_unresolved_nodes: Default::default(), + unresolved_optional_peers: Default::default(), + dep_entry_cache: Default::default(), } } - fn resolve_best_package_version_and_info<'info>( - &self, - version_req: &VersionReq, - package_info: &'info NpmPackageInfo, - ) -> Result, AnyError> { - if let Some(version) = - self.resolve_best_package_version(package_info, version_req)? - { - match package_info.versions.get(&version.to_string()) { - Some(version_info) => Ok(VersionAndInfo { - version, - info: version_info, - }), - None => { - bail!( - "could not find version '{}' for '{}'", - version, - &package_info.name - ) - } - } - } else { - // get the information - get_resolved_package_version_and_info(version_req, package_info, None) - } - } - - fn resolve_best_package_version( - &self, + pub fn add_root_package( + &mut self, + package_nv: &NpmPackageNv, package_info: &NpmPackageInfo, - version_req: &VersionReq, - ) -> Result, AnyError> { - let mut maybe_best_version: Option<&Version> = None; - if let Some(ids) = self.graph.packages_by_name.get(&package_info.name) { - for version in ids.iter().map(|id| &id.version) { - if version_req_satisfies(version_req, version, package_info, None)? { - let is_best_version = maybe_best_version - .as_ref() - .map(|best_version| (*best_version).cmp(version).is_lt()) - .unwrap_or(true); - if is_best_version { - maybe_best_version = Some(version); - } - } - } + ) -> Result<(), AnyError> { + if self.graph.root_packages.contains_key(package_nv) { + return Ok(()); // already added } - Ok(maybe_best_version.cloned()) - } - pub fn has_package_req(&self, req: &NpmPackageReq) -> bool { - self.graph.has_package_req(req) + // todo(dsherret): using a version requirement here is a temporary hack + // to reuse code in a large refactor. We should resolve the node directly + // from the package name and version + let version_req = + VersionReq::parse_from_specifier(&format!("{}", package_nv.version)) + .unwrap(); + let (pkg_id, node_id) = self.resolve_node_from_info( + &package_nv.name, + &version_req, + package_info, + None, + )?; + self.graph.root_packages.insert(pkg_id.clone(), node_id); + self + .pending_unresolved_nodes + .push_back(GraphPath::for_root(node_id, pkg_id)); + Ok(()) } pub fn add_package_req( @@ -478,7 +762,11 @@ impl<'a, TNpmRegistryApi: NpmRegistryApi> package_req: &NpmPackageReq, package_info: &NpmPackageInfo, ) -> Result<(), AnyError> { - let (_, node) = self.resolve_node_from_info( + if self.graph.package_reqs.contains_key(package_req) { + return Ok(()); // already added + } + + let (pkg_id, node_id) = self.resolve_node_from_info( &package_req.name, package_req .version_req @@ -487,12 +775,14 @@ impl<'a, TNpmRegistryApi: NpmRegistryApi> package_info, None, )?; - self.graph.set_child_parent( - &package_req.to_string(), - &node, - &NodeParent::Req, - ); - self.try_add_pending_unresolved_node(None, &node); + self + .graph + .package_reqs + .insert(package_req.clone(), pkg_id.clone()); + self.graph.root_packages.insert(pkg_id.clone(), node_id); + self + .pending_unresolved_nodes + .push_back(GraphPath::for_root(node_id, pkg_id)); Ok(()) } @@ -500,63 +790,55 @@ impl<'a, TNpmRegistryApi: NpmRegistryApi> &mut self, entry: &NpmDependencyEntry, package_info: &NpmPackageInfo, - parent_id: &NpmPackageId, - visited_versions: &Arc, - ) -> Result>, AnyError> { - let (id, node) = self.resolve_node_from_info( + graph_path: &Arc, + ) -> Result { + debug_assert_eq!(entry.kind, NpmDependencyEntryKind::Dep); + let parent_id = graph_path.node_id(); + let (_, node_id) = self.resolve_node_from_info( &entry.name, - match entry.kind { - NpmDependencyEntryKind::Dep => &entry.version_req, - // when resolving a peer dependency as a dependency, it should - // use the "dependencies" entry version requirement if it exists - NpmDependencyEntryKind::Peer | NpmDependencyEntryKind::OptionalPeer => { - entry - .peer_dep_version_req - .as_ref() - .unwrap_or(&entry.version_req) - } - }, + &entry.version_req, package_info, Some(parent_id), )?; // Some packages may resolves to themselves as a dependency. If this occurs, // just ignore adding these as dependencies because this is likely a mistake // in the package. - if id != *parent_id { - self.graph.set_child_parent( + if node_id != parent_id { + self.graph.set_child_parent_node( + &entry.bare_specifier, + node_id, + parent_id, + ); + self.try_add_pending_unresolved_node( + graph_path, + node_id, &entry.bare_specifier, - &node, - &NodeParent::Node(parent_id.clone()), ); - self.try_add_pending_unresolved_node(Some(visited_versions), &node); } - Ok(node) + Ok(node_id) } fn try_add_pending_unresolved_node( &mut self, - maybe_previous_visited_versions: Option<&Arc>, - node: &Arc>, + path: &Arc, + node_id: NodeId, + specifier: &str, ) { - let node_id = { - let node = node.lock(); - if node.no_peers { - return; // skip, no need to analyze this again - } - node.id.clone() - }; - let visited_versions = match maybe_previous_visited_versions { - Some(previous_visited_versions) => { - match previous_visited_versions.with_id(&node_id) { - Some(visited_versions) => visited_versions, - None => return, // circular, don't visit this node - } - } - None => VisitedVersionsPath::new(&node_id), + if self.graph.nodes.get(&node_id).unwrap().no_peers { + return; // skip, no need to analyze this again + } + let node_nv = self + .graph + .resolved_node_ids + .get(node_id) + .unwrap() + .nv + .clone(); + let new_path = match path.with_id(node_id, specifier, node_nv) { + Some(visited_versions) => visited_versions, + None => return, // circular, don't visit this node }; - self - .pending_unresolved_nodes - .push_back((visited_versions, node.clone())); + self.pending_unresolved_nodes.push_back(new_path); } fn resolve_node_from_info( @@ -564,123 +846,187 @@ impl<'a, TNpmRegistryApi: NpmRegistryApi> pkg_req_name: &str, version_req: &VersionReq, package_info: &NpmPackageInfo, - parent_id: Option<&NpmPackageId>, - ) -> Result<(NpmPackageId, Arc>), AnyError> { - let version_and_info = - self.resolve_best_package_version_and_info(version_req, package_info)?; - let id = NpmPackageId { - name: package_info.name.to_string(), - version: version_and_info.version.clone(), + parent_id: Option, + ) -> Result<(NpmPackageNv, NodeId), AnyError> { + let version_and_info = resolve_best_package_version_and_info( + version_req, + package_info, + self + .graph + .nodes_by_package_name + .entry(package_info.name.clone()) + .or_default() + .iter() + .map(|node_id| { + &self + .graph + .resolved_node_ids + .get(*node_id) + .unwrap() + .nv + .version + }), + )?; + let resolved_id = ResolvedId { + nv: NpmPackageNv { + name: package_info.name.to_string(), + version: version_and_info.version.clone(), + }, peer_dependencies: Vec::new(), }; - debug!( - "{} - Resolved {}@{} to {}", + let (_, node_id) = self.graph.get_or_create_for_id(&resolved_id); + let pkg_id = resolved_id.nv; + + let has_deps = if let Some(deps) = self.dep_entry_cache.get(&pkg_id) { + !deps.is_empty() + } else { + let deps = self + .dep_entry_cache + .store(pkg_id.clone(), version_and_info.info)?; + !deps.is_empty() + }; + + if !has_deps { + // ensure this is set if not, as it's an optimization + let mut node = self.graph.borrow_node_mut(node_id); + node.no_peers = true; + } + + debug!( + "{} - Resolved {}@{} to {}", match parent_id { - Some(id) => id.as_serialized(), + Some(parent_id) => self.graph.get_npm_pkg_id(parent_id).as_serialized(), None => "".to_string(), }, pkg_req_name, version_req.version_text(), - id.as_serialized(), - ); - let (created, node) = self.graph.get_or_create_for_id(&id); - if created { - let mut node = (*node).lock(); - let mut deps = version_and_info - .info - .dependencies_as_entries() - .with_context(|| format!("npm package: {}", id.display()))?; - // Ensure name alphabetical and then version descending - // so these are resolved in that order - deps.sort(); - node.deps = Arc::new(deps); - node.no_peers = node.deps.is_empty(); - } + pkg_id.to_string(), + ); - Ok((id, node)) + Ok((pkg_id, node_id)) } pub async fn resolve_pending(&mut self) -> Result<(), AnyError> { while !self.pending_unresolved_nodes.is_empty() { // now go down through the dependencies by tree depth - while let Some((visited_versions, parent_node)) = - self.pending_unresolved_nodes.pop_front() - { - let (mut parent_id, deps, existing_children) = { - let parent_node = parent_node.lock(); - if parent_node.forgotten || parent_node.no_peers { - // todo(dsherret): we should try to reproduce this forgotten scenario and write a test + while let Some(graph_path) = self.pending_unresolved_nodes.pop_front() { + let (pkg_id, deps) = { + let node_id = graph_path.node_id(); + if self.graph.nodes.get(&node_id).unwrap().no_peers { + // We can skip as there's no reason to analyze this graph segment further + // Note that we don't need to count parent references here because that's + // only necessary for graph segments that could potentially have peer + // dependencies within them. continue; } - ( - parent_node.id.clone(), - parent_node.deps.clone(), - parent_node.children.clone(), - ) + let pkg_nv = self + .graph + .resolved_node_ids + .get(node_id) + .unwrap() + .nv + .clone(); + let deps = if let Some(deps) = self.dep_entry_cache.get(&pkg_nv) { + deps.clone() + } else { + // the api should have this in the cache at this point, so no need to parallelize + match self.api.package_version_info(&pkg_nv).await? { + Some(version_info) => { + self.dep_entry_cache.store(pkg_nv.clone(), &version_info)? + } + None => { + bail!("Could not find version information for {}", pkg_nv) + } + } + }; + + (pkg_nv, deps) }; // cache all the dependencies' registry infos in parallel if should - if !should_sync_download() { - let handles = deps - .iter() - .map(|dep| { - let name = dep.name.clone(); - let api = self.api.clone(); - tokio::task::spawn(async move { - // it's ok to call this without storing the result, because - // NpmRegistryApi will cache the package info in memory - api.package_info(&name).await - }) - }) - .collect::>(); - let results = futures::future::join_all(handles).await; - for result in results { - result??; // surface the first error - } - } + self + .api + .cache_in_parallel({ + deps.iter().map(|dep| dep.name.clone()).collect() + }) + .await?; // resolve the dependencies let mut found_peer = false; + for dep in deps.iter() { let package_info = self.api.package_info(&dep.name).await?; match dep.kind { NpmDependencyEntryKind::Dep => { - let node = self.analyze_dependency( - dep, - &package_info, - &parent_id, - &visited_versions, - )?; + // todo(dsherret): look into skipping dependency analysis if + // it was done previously again + let child_id = + self.analyze_dependency(dep, &package_info, &graph_path)?; + if !found_peer { - found_peer = !node.lock().no_peers; + found_peer = !self.graph.borrow_node_mut(child_id).no_peers; } } NpmDependencyEntryKind::Peer | NpmDependencyEntryKind::OptionalPeer => { found_peer = true; - let maybe_new_parent_id = self.resolve_peer_dep( + // we need to re-evaluate peer dependencies every time and can't + // skip over them because they might be evaluated differently based + // on the current path + let maybe_new_id = self.resolve_peer_dep( &dep.bare_specifier, - &parent_id, dep, &package_info, - &visited_versions, - existing_children.get(&dep.bare_specifier), + &graph_path, )?; - if let Some(new_parent_id) = maybe_new_parent_id { - assert_eq!( - (&new_parent_id.name, &new_parent_id.version), - (&parent_id.name, &parent_id.version) - ); - parent_id = new_parent_id; + + // For optional dependencies, we want to resolve them if any future + // same parent version resolves them. So when not resolved, store them to be + // potentially resolved later. + // + // Note: This is not a good solution, but will probably work ok in most + // scenarios. We can work on improving this in the future. We probably + // want to resolve future optional peers to the same dependency for example. + if dep.kind == NpmDependencyEntryKind::OptionalPeer { + match maybe_new_id { + Some(new_id) => { + if let Some(unresolved_optional_peers) = + self.unresolved_optional_peers.remove(&pkg_id) + { + for optional_peer in unresolved_optional_peers { + let peer_parent = GraphPathNodeOrRoot::Node( + optional_peer.graph_path.clone(), + ); + self.set_new_peer_dep( + vec![&optional_peer.graph_path], + peer_parent, + &optional_peer.specifier, + new_id, + ); + } + } + } + None => { + // store this for later if it's resolved for this version + self + .unresolved_optional_peers + .entry(pkg_id.clone()) + .or_default() + .push(UnresolvedOptionalPeer { + specifier: dep.bare_specifier.clone(), + graph_path: graph_path.clone(), + }); + } + } } } } } if !found_peer { - self.graph.borrow_node(&parent_id).no_peers = true; + self.graph.borrow_node_mut(graph_path.node_id()).no_peers = true; } } } @@ -690,496 +1036,284 @@ impl<'a, TNpmRegistryApi: NpmRegistryApi> fn resolve_peer_dep( &mut self, specifier: &str, - parent_id: &NpmPackageId, peer_dep: &NpmDependencyEntry, peer_package_info: &NpmPackageInfo, - visited_ancestor_versions: &Arc, - existing_dep_id: Option<&NpmPackageId>, - ) -> Result, AnyError> { - fn find_matching_child<'a>( - peer_dep: &NpmDependencyEntry, - peer_package_info: &NpmPackageInfo, - children: impl Iterator, - ) -> Result, AnyError> { - for child_id in children { - if child_id.name == peer_dep.name - && version_req_satisfies( - &peer_dep.version_req, - &child_id.version, - peer_package_info, - None, - )? - { - return Ok(Some(child_id.clone())); - } - } - Ok(None) - } + ancestor_path: &Arc, + ) -> Result, AnyError> { + debug_assert!(matches!( + peer_dep.kind, + NpmDependencyEntryKind::Peer | NpmDependencyEntryKind::OptionalPeer + )); - // Peer dependencies are resolved based on its ancestors' siblings. - // If not found, then it resolves based on the version requirement if non-optional. - let mut pending_ancestors = VecDeque::new(); // go up the tree by depth - let path = GraphSpecifierPath::new(specifier.to_string()); - let visited_versions = VisitedVersionsPath::new(parent_id); + let mut path = vec![ancestor_path]; - // skip over the current node - for (specifier, grand_parents) in - self.graph.borrow_node(parent_id).parents.clone() + // the current dependency might have had the peer dependency + // in another bare specifier slot... if so resolve it to that { - let path = path.with_specifier(specifier); - for grand_parent in grand_parents { - if let Some(visited_versions) = - visited_versions.with_parent(&grand_parent) - { - pending_ancestors.push_back(( - grand_parent, - path.clone(), - visited_versions, - )); - } + let maybe_peer_dep = self.find_peer_dep_in_node( + ancestor_path, + peer_dep, + peer_package_info, + )?; + + if let Some((peer_parent, peer_dep_id)) = maybe_peer_dep { + // this will always have an ancestor because we're not at the root + self.set_new_peer_dep(path, peer_parent, specifier, peer_dep_id); + return Ok(Some(peer_dep_id)); } } - while let Some((ancestor, path, visited_versions)) = - pending_ancestors.pop_front() - { - match &ancestor { - NodeParent::Node(ancestor_node_id) => { - let maybe_peer_dep_id = if ancestor_node_id.name == peer_dep.name - && version_req_satisfies( - &peer_dep.version_req, - &ancestor_node_id.version, - peer_package_info, - None, - )? { - Some(ancestor_node_id.clone()) - } else { - let ancestor = self.graph.borrow_node(ancestor_node_id); - for (specifier, parents) in &ancestor.parents { - let new_path = path.with_specifier(specifier.clone()); - for parent in parents { - if let Some(visited_versions) = - visited_versions.with_parent(parent) - { - pending_ancestors.push_back(( - parent.clone(), - new_path.clone(), - visited_versions, - )); - } - } - } - find_matching_child( - peer_dep, - peer_package_info, - ancestor.children.values(), - )? - }; - if let Some(peer_dep_id) = maybe_peer_dep_id { - if existing_dep_id == Some(&peer_dep_id) { - return Ok(None); // do nothing, there's already an existing child dep id for this - } - - // handle optional dependency that's never been set - if existing_dep_id.is_none() && peer_dep.kind.is_optional() { - self.set_previously_unresolved_optional_dependency( - &peer_dep_id, - parent_id, - peer_dep, - visited_ancestor_versions, - ); - return Ok(None); - } - - let parents = - self.graph.borrow_node(ancestor_node_id).parents.clone(); - return Ok(Some(self.set_new_peer_dep( - parents, - ancestor_node_id, - &peer_dep_id, - &path, - visited_ancestor_versions, - ))); + // Peer dependencies are resolved based on its ancestors' siblings. + // If not found, then it resolves based on the version requirement if non-optional. + for ancestor_node in ancestor_path.ancestors() { + match ancestor_node { + GraphPathNodeOrRoot::Node(ancestor_graph_path_node) => { + path.push(ancestor_graph_path_node); + let maybe_peer_dep = self.find_peer_dep_in_node( + ancestor_graph_path_node, + peer_dep, + peer_package_info, + )?; + if let Some((parent, peer_dep_id)) = maybe_peer_dep { + // this will always have an ancestor because we're not at the root + self.set_new_peer_dep(path, parent, specifier, peer_dep_id); + return Ok(Some(peer_dep_id)); } } - NodeParent::Req => { + GraphPathNodeOrRoot::Root(root_pkg_id) => { // in this case, the parent is the root so the children are all the package requirements if let Some(child_id) = find_matching_child( peer_dep, peer_package_info, - self.graph.package_reqs.values(), + self + .graph + .root_packages + .iter() + .map(|(pkg_id, id)| (*id, pkg_id)), )? { - if existing_dep_id == Some(&child_id) { - return Ok(None); // do nothing, there's already an existing child dep id for this - } - - // handle optional dependency that's never been set - if existing_dep_id.is_none() && peer_dep.kind.is_optional() { - self.set_previously_unresolved_optional_dependency( - &child_id, - parent_id, - peer_dep, - visited_ancestor_versions, - ); - return Ok(None); - } - - let specifier = path.specifier.to_string(); - let path = path.pop().unwrap(); // go back down one level from the package requirement - let old_id = - self.graph.package_reqs.get(&specifier).unwrap().clone(); - return Ok(Some(self.set_new_peer_dep( - BTreeMap::from([(specifier, BTreeSet::from([NodeParent::Req]))]), - &old_id, - &child_id, - path, - visited_ancestor_versions, - ))); + let peer_parent = GraphPathNodeOrRoot::Root(root_pkg_id.clone()); + self.set_new_peer_dep(path, peer_parent, specifier, child_id); + return Ok(Some(child_id)); } } } } // We didn't find anything by searching the ancestor siblings, so we need - // to resolve based on the package info and will treat this just like any - // other dependency when not optional - if !peer_dep.kind.is_optional() - // prefer the existing dep id if it exists - && existing_dep_id.is_none() - { - self.analyze_dependency( - peer_dep, + // to resolve based on the package info + if !peer_dep.kind.is_optional() { + let parent_id = ancestor_path.node_id(); + let (_, node_id) = self.resolve_node_from_info( + &peer_dep.name, + peer_dep + .peer_dep_version_req + .as_ref() + .unwrap_or(&peer_dep.version_req), peer_package_info, - parent_id, - visited_ancestor_versions, + Some(parent_id), )?; + let peer_parent = GraphPathNodeOrRoot::Node(ancestor_path.clone()); + self.set_new_peer_dep( + vec![ancestor_path], + peer_parent, + specifier, + node_id, + ); + Ok(Some(node_id)) + } else { + Ok(None) } - - Ok(None) } - /// Optional peer dependencies that have never been set before are - /// simply added to the existing peer dependency instead of affecting - /// the entire sub tree. - fn set_previously_unresolved_optional_dependency( - &mut self, - peer_dep_id: &NpmPackageId, - parent_id: &NpmPackageId, + fn find_peer_dep_in_node( + &self, + path: &Arc, peer_dep: &NpmDependencyEntry, - visited_ancestor_versions: &Arc, - ) { - let (_, node) = self.graph.get_or_create_for_id(peer_dep_id); - self.graph.set_child_parent( - &peer_dep.bare_specifier, - &node, - &NodeParent::Node(parent_id.clone()), - ); - self - .try_add_pending_unresolved_node(Some(visited_ancestor_versions), &node); + peer_package_info: &NpmPackageInfo, + ) -> Result, AnyError> { + let node_id = path.node_id(); + let resolved_node_id = self.graph.resolved_node_ids.get(node_id).unwrap(); + // check if this node itself is a match for + // the peer dependency and if so use that + if resolved_node_id.nv.name == peer_dep.name + && version_req_satisfies( + &peer_dep.version_req, + &resolved_node_id.nv.version, + peer_package_info, + None, + )? + { + let parent = path.previous_node.as_ref().unwrap().clone(); + Ok(Some((parent, node_id))) + } else { + let node = self.graph.nodes.get(&node_id).unwrap(); + let children = node.children.values().map(|child_node_id| { + let child_node_id = *child_node_id; + ( + child_node_id, + &self.graph.resolved_node_ids.get(child_node_id).unwrap().nv, + ) + }); + find_matching_child(peer_dep, peer_package_info, children).map( + |maybe_child_id| { + maybe_child_id.map(|child_id| { + let parent = GraphPathNodeOrRoot::Node(path.clone()); + (parent, child_id) + }) + }, + ) + } } fn set_new_peer_dep( &mut self, - previous_parents: BTreeMap>, - node_id: &NpmPackageId, - peer_dep_id: &NpmPackageId, - path: &Arc, - visited_ancestor_versions: &Arc, - ) -> NpmPackageId { - let peer_dep_id = Cow::Borrowed(peer_dep_id); - let old_id = node_id; - let (new_id, mut old_node_children) = - if old_id.peer_dependencies.contains(&peer_dep_id) - || *old_id == *peer_dep_id - { - // the parent has already resolved to using this peer dependency - // via some other path or the parent is the peer dependency, - // so we don't need to update its ids, but instead only make a link to it - ( - old_id.clone(), - self.graph.borrow_node(old_id).children.clone(), - ) - } else { - let mut new_id = old_id.clone(); - new_id.peer_dependencies.push(peer_dep_id.as_ref().clone()); - - // remove the previous parents from the old node - let old_node_children = { - for (specifier, parents) in &previous_parents { - for parent in parents { - self.graph.remove_child_parent(specifier, old_id, parent); - } - } - let old_node = self.graph.borrow_node(old_id); - old_node.children.clone() - }; + // path from the node above the resolved dep to just above the peer dep + path: Vec<&Arc>, + peer_dep_parent: GraphPathNodeOrRoot, + peer_dep_specifier: &str, + mut peer_dep_id: NodeId, + ) { + debug_assert!(!path.is_empty()); + let peer_dep_pkg_id = self + .graph + .resolved_node_ids + .get(peer_dep_id) + .unwrap() + .nv + .clone(); + + let peer_dep = ResolvedIdPeerDep::ParentReference { + parent: peer_dep_parent, + child_pkg_nv: peer_dep_pkg_id, + }; + for graph_path_node in path.iter().rev() { + let old_node_id = graph_path_node.node_id(); + let old_resolved_id = self + .graph + .resolved_node_ids + .get(old_node_id) + .unwrap() + .clone(); - let (_, new_node) = self.graph.get_or_create_for_id(&new_id); + let mut new_resolved_id = old_resolved_id.clone(); + new_resolved_id.push_peer_dep(peer_dep.clone()); + let (created, new_node_id) = + self.graph.get_or_create_for_id(&new_resolved_id); - // update the previous parent to point to the new node - // and this node to point at those parents - for (specifier, parents) in previous_parents { - for parent in parents { - self.graph.set_child_parent(&specifier, &new_node, &parent); - } - } + // this will occur when the peer dependency is in an ancestor + if old_node_id == peer_dep_id { + peer_dep_id = new_node_id; + } - // now add the previous children to this node - let new_id_as_parent = NodeParent::Node(new_id.clone()); - for (specifier, child_id) in &old_node_children { - let child = self.graph.packages.get(child_id).unwrap().clone(); + if created { + let old_children = + self.graph.borrow_node_mut(old_node_id).children.clone(); + // copy over the old children to this new one + for (specifier, child_id) in &old_children { self .graph - .set_child_parent(specifier, &child, &new_id_as_parent); - } - (new_id, old_node_children) - }; - - // this is the parent id found at the bottom of the path - let mut bottom_parent_id = new_id.clone(); - - // continue going down the path - let next_specifier = &path.specifier; - if let Some(path) = path.pop() { - let next_node_id = old_node_children.get(next_specifier).unwrap(); - bottom_parent_id = self.set_new_peer_dep( - BTreeMap::from([( - next_specifier.to_string(), - BTreeSet::from([NodeParent::Node(new_id.clone())]), - )]), - next_node_id, - &peer_dep_id, - path, - visited_ancestor_versions, - ); - } else { - // this means we're at the peer dependency now - debug!( - "Resolved peer dependency for {} in {} to {}", - next_specifier, - &new_id.as_serialized(), - &peer_dep_id.as_serialized(), - ); - - // handle this node having a previous child due to another peer dependency - if let Some(child_id) = old_node_children.remove(next_specifier) { - if let Some(node) = self.graph.packages.get(&child_id) { - let is_orphan = { - let mut node = node.lock(); - node - .remove_parent(next_specifier, &NodeParent::Node(new_id.clone())); - node.parents.is_empty() - }; - if is_orphan { - self.graph.forget_orphan(&child_id); - } + .set_child_parent_node(specifier, *child_id, new_node_id); } } - let node = self.graph.get_or_create_for_id(&peer_dep_id).1; - self.try_add_pending_unresolved_node( - Some(visited_ancestor_versions), - &node, - ); - self - .graph - .set_child_parent_node(next_specifier, &node, &new_id); - } - - // forget the old node at this point if it has no parents - if new_id != *old_id { - let old_node = self.graph.borrow_node(old_id); - if old_node.parents.is_empty() { - drop(old_node); // stop borrowing - self.graph.forget_orphan(old_id); - } - } - - bottom_parent_id - } -} - -#[derive(Clone)] -struct VersionAndInfo<'a> { - version: Version, - info: &'a NpmPackageVersionInfo, -} + debug_assert_eq!(graph_path_node.node_id(), old_node_id); + graph_path_node.change_id(new_node_id); -fn get_resolved_package_version_and_info<'a>( - version_req: &VersionReq, - info: &'a NpmPackageInfo, - parent: Option<&NpmPackageId>, -) -> Result, AnyError> { - if let Some(tag) = version_req.tag() { - tag_to_version_info(info, tag, parent) - } else { - let mut maybe_best_version: Option = None; - for version_info in info.versions.values() { - let version = Version::parse_from_npm(&version_info.version)?; - if version_req.matches(&version) { - let is_best_version = maybe_best_version - .as_ref() - .map(|best_version| best_version.version.cmp(&version).is_lt()) - .unwrap_or(true); - if is_best_version { - maybe_best_version = Some(VersionAndInfo { - version, - info: version_info, - }); + // update the previous parent to have this as its child + match graph_path_node.previous_node.as_ref().unwrap() { + GraphPathNodeOrRoot::Root(pkg_id) => { + self.graph.root_packages.insert(pkg_id.clone(), new_node_id); + } + GraphPathNodeOrRoot::Node(parent_node_path) => { + let parent_node_id = parent_node_path.node_id(); + let parent_node = self.graph.borrow_node_mut(parent_node_id); + parent_node + .children + .insert(graph_path_node.specifier().to_string(), new_node_id); } } } - match maybe_best_version { - Some(v) => Ok(v), - // If the package isn't found, it likely means that the user needs to use - // `--reload` to get the latest npm package information. Although it seems - // like we could make this smart by fetching the latest information for - // this package here, we really need a full restart. There could be very - // interesting bugs that occur if this package's version was resolved by - // something previous using the old information, then now being smart here - // causes a new fetch of the package information, meaning this time the - // previous resolution of this package's version resolved to an older - // version, but next time to a different version because it has new information. - None => bail!( - concat!( - "Could not find npm package '{}' matching {}{}. ", - "Try retrieving the latest npm package information by running with --reload", - ), - info.name, - version_req.version_text(), - match parent { - Some(id) => format!(" as specified in {}", id.display()), - None => String::new(), - } - ), - } - } -} + // now set the peer dependency + let bottom_node = path.first().unwrap(); + let parent_node_id = bottom_node.node_id(); + self.graph.set_child_parent_node( + peer_dep_specifier, + peer_dep_id, + parent_node_id, + ); -fn version_req_satisfies( - version_req: &VersionReq, - version: &Version, - package_info: &NpmPackageInfo, - parent: Option<&NpmPackageId>, -) -> Result { - match version_req.tag() { - Some(tag) => { - let tag_version = tag_to_version_info(package_info, tag, parent)?.version; - Ok(tag_version == *version) - } - None => Ok(version_req.matches(version)), + // mark the peer dependency to be analyzed + self.try_add_pending_unresolved_node( + bottom_node, + peer_dep_id, + peer_dep_specifier, + ); + + debug!( + "Resolved peer dependency for {} in {} to {}", + peer_dep_specifier, + &self.graph.get_npm_pkg_id(parent_node_id).as_serialized(), + &self.graph.get_npm_pkg_id(peer_dep_id).as_serialized(), + ); } } -fn tag_to_version_info<'a>( - info: &'a NpmPackageInfo, - tag: &str, - parent: Option<&NpmPackageId>, -) -> Result, AnyError> { - // For when someone just specifies @types/node, we want to pull in a - // "known good" version of @types/node that works well with Deno and - // not necessarily the latest version. For example, we might only be - // compatible with Node vX, but then Node vY is published so we wouldn't - // want to pull that in. - // Note: If the user doesn't want this behavior, then they can specify an - // explicit version. - if tag == "latest" && info.name == "@types/node" { - return get_resolved_package_version_and_info( - &VersionReq::parse_from_npm("18.0.0 - 18.11.18").unwrap(), - info, - parent, - ); - } - - if let Some(version) = info.dist_tags.get(tag) { - match info.versions.get(version) { - Some(info) => Ok(VersionAndInfo { - version: Version::parse_from_npm(version)?, - info, - }), - None => { - bail!( - "Could not find version '{}' referenced in dist-tag '{}'.", - version, - tag, - ) - } +fn find_matching_child<'a>( + peer_dep: &NpmDependencyEntry, + peer_package_info: &NpmPackageInfo, + children: impl Iterator, +) -> Result, AnyError> { + for (child_id, pkg_id) in children { + if pkg_id.name == peer_dep.name + && version_req_satisfies( + &peer_dep.version_req, + &pkg_id.version, + peer_package_info, + None, + )? + { + return Ok(Some(child_id)); } - } else { - bail!("Could not find dist-tag '{}'.", tag) } + Ok(None) } #[cfg(test)] mod test { + use deno_graph::npm::NpmPackageReqReference; use pretty_assertions::assert_eq; - use crate::npm::registry::TestNpmRegistryApi; - use crate::npm::NpmPackageReference; + use crate::npm::registry::TestNpmRegistryApiInner; use super::*; #[test] - fn test_get_resolved_package_version_and_info() { - // dist tag where version doesn't exist - let package_ref = NpmPackageReference::from_str("npm:test").unwrap(); - let package_info = NpmPackageInfo { - name: "test".to_string(), - versions: HashMap::new(), - dist_tags: HashMap::from([( - "latest".to_string(), - "1.0.0-alpha".to_string(), - )]), + fn resolved_id_tests() { + let mut ids = ResolvedNodeIds::default(); + let node_id = NodeId(0); + let resolved_id = ResolvedId { + nv: NpmPackageNv::from_str("package@1.1.1").unwrap(), + peer_dependencies: Vec::new(), }; - let result = get_resolved_package_version_and_info( - package_ref - .req - .version_req - .as_ref() - .unwrap_or(&*LATEST_VERSION_REQ), - &package_info, - None, - ); - assert_eq!( - result.err().unwrap().to_string(), - "Could not find version '1.0.0-alpha' referenced in dist-tag 'latest'." - ); + ids.set(node_id, resolved_id.clone()); + assert!(ids.get(node_id).is_some()); + assert!(ids.get(NodeId(1)).is_none()); + assert_eq!(ids.get_node_id(&resolved_id), Some(node_id)); - // dist tag where version is a pre-release - let package_ref = NpmPackageReference::from_str("npm:test").unwrap(); - let package_info = NpmPackageInfo { - name: "test".to_string(), - versions: HashMap::from([ - ("0.1.0".to_string(), NpmPackageVersionInfo::default()), - ( - "1.0.0-alpha".to_string(), - NpmPackageVersionInfo { - version: "0.1.0-alpha".to_string(), - ..Default::default() - }, - ), - ]), - dist_tags: HashMap::from([( - "latest".to_string(), - "1.0.0-alpha".to_string(), - )]), + let resolved_id_new = ResolvedId { + nv: NpmPackageNv::from_str("package@1.1.2").unwrap(), + peer_dependencies: Vec::new(), }; - let result = get_resolved_package_version_and_info( - package_ref - .req - .version_req - .as_ref() - .unwrap_or(&*LATEST_VERSION_REQ), - &package_info, - None, - ); - assert_eq!(result.unwrap().version.to_string(), "1.0.0-alpha"); + ids.set(node_id, resolved_id_new.clone()); + assert_eq!(ids.get_node_id(&resolved_id), None); // stale entry should have been removed + assert!(ids.get(node_id).is_some()); + assert_eq!(ids.get_node_id(&resolved_id_new), Some(node_id)); } #[tokio::test] async fn resolve_deps_no_peer() { - let api = TestNpmRegistryApi::default(); + let api = TestNpmRegistryApiInner::default(); api.ensure_package_version("package-a", "1.0.0"); api.ensure_package_version("package-b", "2.0.0"); api.ensure_package_version("package-c", "0.1.0"); @@ -1196,7 +1330,7 @@ mod test { packages, vec![ NpmResolutionPackage { - id: NpmPackageId::from_serialized("package-a@1.0.0").unwrap(), + pkg_id: NpmPackageId::from_serialized("package-a@1.0.0").unwrap(), copy_index: 0, dependencies: HashMap::from([ ( @@ -1211,13 +1345,13 @@ mod test { dist: Default::default(), }, NpmResolutionPackage { - id: NpmPackageId::from_serialized("package-b@2.0.0").unwrap(), + pkg_id: NpmPackageId::from_serialized("package-b@2.0.0").unwrap(), copy_index: 0, dist: Default::default(), dependencies: Default::default(), }, NpmResolutionPackage { - id: NpmPackageId::from_serialized("package-c@0.1.0").unwrap(), + pkg_id: NpmPackageId::from_serialized("package-c@0.1.0").unwrap(), copy_index: 0, dist: Default::default(), dependencies: HashMap::from([( @@ -1226,7 +1360,7 @@ mod test { )]) }, NpmResolutionPackage { - id: NpmPackageId::from_serialized("package-d@3.2.1").unwrap(), + pkg_id: NpmPackageId::from_serialized("package-d@3.2.1").unwrap(), copy_index: 0, dist: Default::default(), dependencies: Default::default(), @@ -1241,7 +1375,7 @@ mod test { #[tokio::test] async fn resolve_deps_circular() { - let api = TestNpmRegistryApi::default(); + let api = TestNpmRegistryApiInner::default(); api.ensure_package_version("package-a", "1.0.0"); api.ensure_package_version("package-b", "2.0.0"); api.add_dependency(("package-a", "1.0.0"), ("package-b", "*")); @@ -1253,7 +1387,7 @@ mod test { packages, vec![ NpmResolutionPackage { - id: NpmPackageId::from_serialized("package-a@1.0.0").unwrap(), + pkg_id: NpmPackageId::from_serialized("package-a@1.0.0").unwrap(), copy_index: 0, dependencies: HashMap::from([( "package-b".to_string(), @@ -1262,7 +1396,7 @@ mod test { dist: Default::default(), }, NpmResolutionPackage { - id: NpmPackageId::from_serialized("package-b@2.0.0").unwrap(), + pkg_id: NpmPackageId::from_serialized("package-b@2.0.0").unwrap(), copy_index: 0, dependencies: HashMap::from([( "package-a".to_string(), @@ -1279,203 +1413,242 @@ mod test { } #[tokio::test] - async fn resolve_with_peer_deps_top_tree() { - let api = TestNpmRegistryApi::default(); + async fn peer_deps_simple_top_tree() { + let api = TestNpmRegistryApiInner::default(); api.ensure_package_version("package-a", "1.0.0"); - api.ensure_package_version("package-b", "2.0.0"); - api.ensure_package_version("package-c", "3.0.0"); - api.ensure_package_version("package-peer", "4.0.0"); - api.ensure_package_version("package-peer", "4.1.0"); - api.add_dependency(("package-a", "1.0.0"), ("package-b", "^2")); - api.add_dependency(("package-a", "1.0.0"), ("package-c", "^3")); - api.add_peer_dependency(("package-b", "2.0.0"), ("package-peer", "4")); - api.add_peer_dependency(("package-c", "3.0.0"), ("package-peer", "*")); + api.ensure_package_version("package-b", "1.0.0"); + api.ensure_package_version("package-peer", "1.0.0"); + api.add_dependency(("package-a", "1.0.0"), ("package-b", "1")); + api.add_peer_dependency(("package-b", "1.0.0"), ("package-peer", "*")); let (packages, package_reqs) = run_resolver_and_get_output( api, - // the peer dependency is specified here at the top of the tree - // so it should resolve to 4.0.0 instead of 4.1.0 - vec!["npm:package-a@1", "npm:package-peer@4.0.0"], + vec!["npm:package-a@1.0", "npm:package-peer@1.0"], ) .await; assert_eq!( packages, vec![ NpmResolutionPackage { - id: NpmPackageId::from_serialized( - "package-a@1.0.0_package-peer@4.0.0" + pkg_id: NpmPackageId::from_serialized( + "package-a@1.0.0_package-peer@1.0.0" ) .unwrap(), copy_index: 0, - dependencies: HashMap::from([ - ( - "package-b".to_string(), - NpmPackageId::from_serialized( - "package-b@2.0.0_package-peer@4.0.0" - ) - .unwrap(), - ), - ( - "package-c".to_string(), - NpmPackageId::from_serialized( - "package-c@3.0.0_package-peer@4.0.0" - ) + dependencies: HashMap::from([( + "package-b".to_string(), + NpmPackageId::from_serialized("package-b@1.0.0_package-peer@1.0.0") .unwrap(), - ), - ]), + )]), dist: Default::default(), }, NpmResolutionPackage { - id: NpmPackageId::from_serialized( - "package-b@2.0.0_package-peer@4.0.0" + pkg_id: NpmPackageId::from_serialized( + "package-b@1.0.0_package-peer@1.0.0" ) .unwrap(), copy_index: 0, - dist: Default::default(), dependencies: HashMap::from([( "package-peer".to_string(), - NpmPackageId::from_serialized("package-peer@4.0.0").unwrap(), - )]) - }, - NpmResolutionPackage { - id: NpmPackageId::from_serialized( - "package-c@3.0.0_package-peer@4.0.0" - ) - .unwrap(), - copy_index: 0, + NpmPackageId::from_serialized("package-peer@1.0.0").unwrap(), + )]), dist: Default::default(), - dependencies: HashMap::from([( - "package-peer".to_string(), - NpmPackageId::from_serialized("package-peer@4.0.0").unwrap(), - )]) }, NpmResolutionPackage { - id: NpmPackageId::from_serialized("package-peer@4.0.0").unwrap(), + pkg_id: NpmPackageId::from_serialized("package-peer@1.0.0").unwrap(), copy_index: 0, - dist: Default::default(), dependencies: Default::default(), - }, + dist: Default::default(), + } ] ); assert_eq!( package_reqs, vec![ ( - "package-a@1".to_string(), - "package-a@1.0.0_package-peer@4.0.0".to_string() + "package-a@1.0".to_string(), + "package-a@1.0.0_package-peer@1.0.0".to_string() ), ( - "package-peer@4.0.0".to_string(), - "package-peer@4.0.0".to_string() + "package-peer@1.0".to_string(), + "package-peer@1.0.0".to_string() ) ] ); } #[tokio::test] - async fn resolve_with_peer_deps_ancestor_sibling_not_top_tree() { - let api = TestNpmRegistryApi::default(); - api.ensure_package_version("package-0", "1.1.1"); + async fn peer_deps_simple_root_pkg_children() { + let api = TestNpmRegistryApiInner::default(); + api.ensure_package_version("package-0", "1.0.0"); api.ensure_package_version("package-a", "1.0.0"); - api.ensure_package_version("package-b", "2.0.0"); - api.ensure_package_version("package-c", "3.0.0"); - api.ensure_package_version("package-peer", "4.0.0"); - api.ensure_package_version("package-peer", "4.1.0"); - api.add_dependency(("package-0", "1.1.1"), ("package-a", "1")); - api.add_dependency(("package-a", "1.0.0"), ("package-b", "^2")); - api.add_dependency(("package-a", "1.0.0"), ("package-c", "^3")); - // the peer dependency is specified here as a sibling of "a" and "b" - // so it should resolve to 4.0.0 instead of 4.1.0 - api.add_dependency(("package-a", "1.0.0"), ("package-peer", "4.0.0")); - api.add_peer_dependency(("package-b", "2.0.0"), ("package-peer", "4")); - api.add_peer_dependency(("package-c", "3.0.0"), ("package-peer", "*")); + api.ensure_package_version("package-b", "1.0.0"); + api.ensure_package_version("package-peer", "1.0.0"); + api.add_dependency(("package-0", "1.0.0"), ("package-a", "1")); + api.add_dependency(("package-0", "1.0.0"), ("package-peer", "1")); + api.add_dependency(("package-a", "1.0.0"), ("package-b", "1")); + api.add_peer_dependency(("package-b", "1.0.0"), ("package-peer", "*")); let (packages, package_reqs) = - run_resolver_and_get_output(api, vec!["npm:package-0@1.1.1"]).await; + run_resolver_and_get_output(api, vec!["npm:package-0@1.0"]).await; assert_eq!( packages, vec![ NpmResolutionPackage { - id: NpmPackageId::from_serialized("package-0@1.1.1").unwrap(), - copy_index: 0, - dependencies: HashMap::from([( - "package-a".to_string(), - NpmPackageId::from_serialized("package-a@1.0.0_package-peer@4.0.0") - .unwrap(), - ),]), - dist: Default::default(), - }, - NpmResolutionPackage { - id: NpmPackageId::from_serialized( - "package-a@1.0.0_package-peer@4.0.0" + pkg_id: NpmPackageId::from_serialized( + "package-0@1.0.0_package-peer@1.0.0" ) .unwrap(), copy_index: 0, dependencies: HashMap::from([ ( - "package-b".to_string(), + "package-a".to_string(), NpmPackageId::from_serialized( - "package-b@2.0.0_package-peer@4.0.0" - ) - .unwrap(), - ), - ( - "package-c".to_string(), - NpmPackageId::from_serialized( - "package-c@3.0.0_package-peer@4.0.0" + "package-a@1.0.0_package-peer@1.0.0" ) .unwrap(), ), ( "package-peer".to_string(), - NpmPackageId::from_serialized("package-peer@4.0.0").unwrap(), - ), + NpmPackageId::from_serialized("package-peer@1.0.0").unwrap(), + ) ]), dist: Default::default(), }, NpmResolutionPackage { - id: NpmPackageId::from_serialized( - "package-b@2.0.0_package-peer@4.0.0" + pkg_id: NpmPackageId::from_serialized( + "package-a@1.0.0_package-peer@1.0.0" ) .unwrap(), copy_index: 0, + dependencies: HashMap::from([( + "package-b".to_string(), + NpmPackageId::from_serialized("package-b@1.0.0_package-peer@1.0.0") + .unwrap(), + )]), dist: Default::default(), + }, + NpmResolutionPackage { + pkg_id: NpmPackageId::from_serialized( + "package-b@1.0.0_package-peer@1.0.0" + ) + .unwrap(), + copy_index: 0, dependencies: HashMap::from([( "package-peer".to_string(), - NpmPackageId::from_serialized("package-peer@4.0.0").unwrap(), - )]) + NpmPackageId::from_serialized("package-peer@1.0.0").unwrap(), + )]), + dist: Default::default(), }, NpmResolutionPackage { - id: NpmPackageId::from_serialized( - "package-c@3.0.0_package-peer@4.0.0" + pkg_id: NpmPackageId::from_serialized("package-peer@1.0.0").unwrap(), + copy_index: 0, + dependencies: Default::default(), + dist: Default::default(), + } + ] + ); + assert_eq!( + package_reqs, + vec![( + "package-0@1.0".to_string(), + "package-0@1.0.0_package-peer@1.0.0".to_string() + ),] + ); + } + + #[tokio::test] + async fn peer_deps_simple_deeper() { + let api = TestNpmRegistryApiInner::default(); + api.ensure_package_version("package-0", "1.0.0"); + api.ensure_package_version("package-1", "1.0.0"); + api.ensure_package_version("package-a", "1.0.0"); + api.ensure_package_version("package-b", "1.0.0"); + api.ensure_package_version("package-peer", "1.0.0"); + api.add_dependency(("package-0", "1.0.0"), ("package-1", "1")); + api.add_dependency(("package-1", "1.0.0"), ("package-a", "1")); + api.add_dependency(("package-1", "1.0.0"), ("package-peer", "1")); + api.add_dependency(("package-a", "1.0.0"), ("package-b", "1")); + api.add_peer_dependency(("package-b", "1.0.0"), ("package-peer", "*")); + + let (packages, package_reqs) = + run_resolver_and_get_output(api, vec!["npm:package-0@1.0"]).await; + assert_eq!( + packages, + vec![ + NpmResolutionPackage { + pkg_id: NpmPackageId::from_serialized("package-0@1.0.0").unwrap(), + copy_index: 0, + dependencies: HashMap::from([( + "package-1".to_string(), + NpmPackageId::from_serialized("package-1@1.0.0_package-peer@1.0.0") + .unwrap(), + )]), + dist: Default::default(), + }, + NpmResolutionPackage { + pkg_id: NpmPackageId::from_serialized( + "package-1@1.0.0_package-peer@1.0.0" + ) + .unwrap(), + copy_index: 0, + dependencies: HashMap::from([ + ( + "package-a".to_string(), + NpmPackageId::from_serialized( + "package-a@1.0.0_package-peer@1.0.0" + ) + .unwrap(), + ), + ( + "package-peer".to_string(), + NpmPackageId::from_serialized("package-peer@1.0.0").unwrap(), + ) + ]), + dist: Default::default(), + }, + NpmResolutionPackage { + pkg_id: NpmPackageId::from_serialized( + "package-a@1.0.0_package-peer@1.0.0" ) .unwrap(), copy_index: 0, + dependencies: HashMap::from([( + "package-b".to_string(), + NpmPackageId::from_serialized("package-b@1.0.0_package-peer@1.0.0") + .unwrap(), + )]), dist: Default::default(), + }, + NpmResolutionPackage { + pkg_id: NpmPackageId::from_serialized( + "package-b@1.0.0_package-peer@1.0.0" + ) + .unwrap(), + copy_index: 0, dependencies: HashMap::from([( "package-peer".to_string(), - NpmPackageId::from_serialized("package-peer@4.0.0").unwrap(), - )]) + NpmPackageId::from_serialized("package-peer@1.0.0").unwrap(), + )]), + dist: Default::default(), }, NpmResolutionPackage { - id: NpmPackageId::from_serialized("package-peer@4.0.0").unwrap(), + pkg_id: NpmPackageId::from_serialized("package-peer@1.0.0").unwrap(), copy_index: 0, - dist: Default::default(), dependencies: Default::default(), - }, + dist: Default::default(), + } ] ); assert_eq!( package_reqs, - vec![("package-0@1.1.1".to_string(), "package-0@1.1.1".to_string())] + vec![("package-0@1.0".to_string(), "package-0@1.0.0".to_string()),] ); } #[tokio::test] - async fn resolve_with_peer_deps_auto_resolved() { - // in this case, the peer dependency is not found in the tree - // so it's auto-resolved based on the registry - let api = TestNpmRegistryApi::default(); + async fn resolve_with_peer_deps_top_tree() { + let api = TestNpmRegistryApiInner::default(); api.ensure_package_version("package-a", "1.0.0"); api.ensure_package_version("package-b", "2.0.0"); api.ensure_package_version("package-c", "3.0.0"); @@ -1486,46 +1659,66 @@ mod test { api.add_peer_dependency(("package-b", "2.0.0"), ("package-peer", "4")); api.add_peer_dependency(("package-c", "3.0.0"), ("package-peer", "*")); - let (packages, package_reqs) = - run_resolver_and_get_output(api, vec!["npm:package-a@1"]).await; + let (packages, package_reqs) = run_resolver_and_get_output( + api, + // the peer dependency is specified here at the top of the tree + // so it should resolve to 4.0.0 instead of 4.1.0 + vec!["npm:package-a@1", "npm:package-peer@4.0.0"], + ) + .await; assert_eq!( packages, vec![ NpmResolutionPackage { - id: NpmPackageId::from_serialized("package-a@1.0.0").unwrap(), + pkg_id: NpmPackageId::from_serialized( + "package-a@1.0.0_package-peer@4.0.0" + ) + .unwrap(), copy_index: 0, dependencies: HashMap::from([ ( "package-b".to_string(), - NpmPackageId::from_serialized("package-b@2.0.0").unwrap(), + NpmPackageId::from_serialized( + "package-b@2.0.0_package-peer@4.0.0" + ) + .unwrap(), ), ( "package-c".to_string(), - NpmPackageId::from_serialized("package-c@3.0.0").unwrap(), + NpmPackageId::from_serialized( + "package-c@3.0.0_package-peer@4.0.0" + ) + .unwrap(), ), ]), dist: Default::default(), }, NpmResolutionPackage { - id: NpmPackageId::from_serialized("package-b@2.0.0").unwrap(), + pkg_id: NpmPackageId::from_serialized( + "package-b@2.0.0_package-peer@4.0.0" + ) + .unwrap(), copy_index: 0, dist: Default::default(), dependencies: HashMap::from([( "package-peer".to_string(), - NpmPackageId::from_serialized("package-peer@4.1.0").unwrap(), + NpmPackageId::from_serialized("package-peer@4.0.0").unwrap(), )]) }, NpmResolutionPackage { - id: NpmPackageId::from_serialized("package-c@3.0.0").unwrap(), + pkg_id: NpmPackageId::from_serialized( + "package-c@3.0.0_package-peer@4.0.0" + ) + .unwrap(), copy_index: 0, dist: Default::default(), dependencies: HashMap::from([( "package-peer".to_string(), - NpmPackageId::from_serialized("package-peer@4.1.0").unwrap(), + NpmPackageId::from_serialized("package-peer@4.0.0").unwrap(), )]) }, NpmResolutionPackage { - id: NpmPackageId::from_serialized("package-peer@4.1.0").unwrap(), + pkg_id: NpmPackageId::from_serialized("package-peer@4.0.0").unwrap(), copy_index: 0, dist: Default::default(), dependencies: Default::default(), @@ -1534,74 +1727,123 @@ mod test { ); assert_eq!( package_reqs, - vec![("package-a@1".to_string(), "package-a@1.0.0".to_string())] + vec![ + ( + "package-a@1".to_string(), + "package-a@1.0.0_package-peer@4.0.0".to_string() + ), + ( + "package-peer@4.0.0".to_string(), + "package-peer@4.0.0".to_string() + ) + ] ); } #[tokio::test] - async fn resolve_with_optional_peer_dep_not_resolved() { - // in this case, the peer dependency is not found in the tree - // so it's auto-resolved based on the registry - let api = TestNpmRegistryApi::default(); + async fn resolve_with_peer_deps_ancestor_sibling_not_top_tree() { + let api = TestNpmRegistryApiInner::default(); + api.ensure_package_version("package-0", "1.1.1"); api.ensure_package_version("package-a", "1.0.0"); api.ensure_package_version("package-b", "2.0.0"); api.ensure_package_version("package-c", "3.0.0"); api.ensure_package_version("package-peer", "4.0.0"); api.ensure_package_version("package-peer", "4.1.0"); + api.add_dependency(("package-0", "1.1.1"), ("package-a", "1")); api.add_dependency(("package-a", "1.0.0"), ("package-b", "^2")); api.add_dependency(("package-a", "1.0.0"), ("package-c", "^3")); - api.add_optional_peer_dependency( - ("package-b", "2.0.0"), - ("package-peer", "4"), - ); - api.add_optional_peer_dependency( - ("package-c", "3.0.0"), - ("package-peer", "*"), - ); + // the peer dependency is specified here as a sibling of "a" and "b" + // so it should resolve to 4.0.0 instead of 4.1.0 + api.add_dependency(("package-a", "1.0.0"), ("package-peer", "4.0.0")); + api.add_peer_dependency(("package-b", "2.0.0"), ("package-peer", "4")); + api.add_peer_dependency(("package-c", "3.0.0"), ("package-peer", "*")); let (packages, package_reqs) = - run_resolver_and_get_output(api, vec!["npm:package-a@1"]).await; + run_resolver_and_get_output(api, vec!["npm:package-0@1.1.1"]).await; assert_eq!( packages, vec![ NpmResolutionPackage { - id: NpmPackageId::from_serialized("package-a@1.0.0").unwrap(), + pkg_id: NpmPackageId::from_serialized("package-0@1.1.1").unwrap(), + copy_index: 0, + dependencies: HashMap::from([( + "package-a".to_string(), + NpmPackageId::from_serialized("package-a@1.0.0_package-peer@4.0.0") + .unwrap(), + ),]), + dist: Default::default(), + }, + NpmResolutionPackage { + pkg_id: NpmPackageId::from_serialized( + "package-a@1.0.0_package-peer@4.0.0" + ) + .unwrap(), copy_index: 0, dependencies: HashMap::from([ ( "package-b".to_string(), - NpmPackageId::from_serialized("package-b@2.0.0").unwrap(), + NpmPackageId::from_serialized( + "package-b@2.0.0_package-peer@4.0.0" + ) + .unwrap(), ), ( "package-c".to_string(), - NpmPackageId::from_serialized("package-c@3.0.0").unwrap(), + NpmPackageId::from_serialized( + "package-c@3.0.0_package-peer@4.0.0" + ) + .unwrap(), + ), + ( + "package-peer".to_string(), + NpmPackageId::from_serialized("package-peer@4.0.0").unwrap(), ), ]), dist: Default::default(), }, NpmResolutionPackage { - id: NpmPackageId::from_serialized("package-b@2.0.0").unwrap(), + pkg_id: NpmPackageId::from_serialized( + "package-b@2.0.0_package-peer@4.0.0" + ) + .unwrap(), copy_index: 0, dist: Default::default(), - dependencies: HashMap::new(), + dependencies: HashMap::from([( + "package-peer".to_string(), + NpmPackageId::from_serialized("package-peer@4.0.0").unwrap(), + )]) }, NpmResolutionPackage { - id: NpmPackageId::from_serialized("package-c@3.0.0").unwrap(), + pkg_id: NpmPackageId::from_serialized( + "package-c@3.0.0_package-peer@4.0.0" + ) + .unwrap(), copy_index: 0, dist: Default::default(), - dependencies: HashMap::new(), + dependencies: HashMap::from([( + "package-peer".to_string(), + NpmPackageId::from_serialized("package-peer@4.0.0").unwrap(), + )]) + }, + NpmResolutionPackage { + pkg_id: NpmPackageId::from_serialized("package-peer@4.0.0").unwrap(), + copy_index: 0, + dist: Default::default(), + dependencies: Default::default(), }, ] ); assert_eq!( package_reqs, - vec![("package-a@1".to_string(), "package-a@1.0.0".to_string())] + vec![("package-0@1.1.1".to_string(), "package-0@1.1.1".to_string())] ); } #[tokio::test] - async fn resolve_with_optional_peer_found() { - let api = TestNpmRegistryApi::default(); + async fn resolve_with_peer_deps_auto_resolved() { + // in this case, the peer dependency is not found in the tree + // so it's auto-resolved based on the registry + let api = TestNpmRegistryApiInner::default(); api.ensure_package_version("package-a", "1.0.0"); api.ensure_package_version("package-b", "2.0.0"); api.ensure_package_version("package-c", "3.0.0"); @@ -1609,58 +1851,61 @@ mod test { api.ensure_package_version("package-peer", "4.1.0"); api.add_dependency(("package-a", "1.0.0"), ("package-b", "^2")); api.add_dependency(("package-a", "1.0.0"), ("package-c", "^3")); - api.add_optional_peer_dependency( - ("package-b", "2.0.0"), - ("package-peer", "4"), - ); - api.add_optional_peer_dependency( - ("package-c", "3.0.0"), - ("package-peer", "*"), - ); + api.add_peer_dependency(("package-b", "2.0.0"), ("package-peer", "4")); + api.add_peer_dependency(("package-c", "3.0.0"), ("package-peer", "*")); - let (packages, package_reqs) = run_resolver_and_get_output( - api, - vec!["npm:package-a@1", "npm:package-peer@4.0.0"], - ) - .await; + let (packages, package_reqs) = + run_resolver_and_get_output(api, vec!["npm:package-a@1"]).await; assert_eq!( packages, vec![ NpmResolutionPackage { - id: NpmPackageId::from_serialized("package-a@1.0.0").unwrap(), + pkg_id: NpmPackageId::from_serialized("package-a@1.0.0").unwrap(), copy_index: 0, dependencies: HashMap::from([ ( "package-b".to_string(), - NpmPackageId::from_serialized("package-b@2.0.0").unwrap(), + NpmPackageId::from_serialized( + "package-b@2.0.0_package-peer@4.1.0" + ) + .unwrap(), ), ( "package-c".to_string(), - NpmPackageId::from_serialized("package-c@3.0.0").unwrap(), + NpmPackageId::from_serialized( + "package-c@3.0.0_package-peer@4.1.0" + ) + .unwrap(), ), ]), dist: Default::default(), }, NpmResolutionPackage { - id: NpmPackageId::from_serialized("package-b@2.0.0").unwrap(), + pkg_id: NpmPackageId::from_serialized( + "package-b@2.0.0_package-peer@4.1.0" + ) + .unwrap(), copy_index: 0, dist: Default::default(), dependencies: HashMap::from([( "package-peer".to_string(), - NpmPackageId::from_serialized("package-peer@4.0.0").unwrap(), + NpmPackageId::from_serialized("package-peer@4.1.0").unwrap(), )]) }, NpmResolutionPackage { - id: NpmPackageId::from_serialized("package-c@3.0.0").unwrap(), + pkg_id: NpmPackageId::from_serialized( + "package-c@3.0.0_package-peer@4.1.0" + ) + .unwrap(), copy_index: 0, dist: Default::default(), dependencies: HashMap::from([( "package-peer".to_string(), - NpmPackageId::from_serialized("package-peer@4.0.0").unwrap(), + NpmPackageId::from_serialized("package-peer@4.1.0").unwrap(), )]) }, NpmResolutionPackage { - id: NpmPackageId::from_serialized("package-peer@4.0.0").unwrap(), + pkg_id: NpmPackageId::from_serialized("package-peer@4.1.0").unwrap(), copy_index: 0, dist: Default::default(), dependencies: Default::default(), @@ -1669,23 +1914,176 @@ mod test { ); assert_eq!( package_reqs, - vec![ - ("package-a@1".to_string(), "package-a@1.0.0".to_string()), - ( - "package-peer@4.0.0".to_string(), - "package-peer@4.0.0".to_string() - ) - ] + vec![("package-a@1".to_string(), "package-a@1.0.0".to_string())] ); } #[tokio::test] - async fn resolve_optional_peer_first_not_resolved_second_resolved_scenario1() - { - // When resolving a dependency a second time and it has an optional - // peer dependency that wasn't previously resolved, it should resolve all the + async fn resolve_with_optional_peer_dep_not_resolved() { + // in this case, the peer dependency is not found in the tree + // so it's auto-resolved based on the registry + let api = TestNpmRegistryApiInner::default(); + api.ensure_package_version("package-a", "1.0.0"); + api.ensure_package_version("package-b", "2.0.0"); + api.ensure_package_version("package-c", "3.0.0"); + api.ensure_package_version("package-peer", "4.0.0"); + api.ensure_package_version("package-peer", "4.1.0"); + api.add_dependency(("package-a", "1.0.0"), ("package-b", "^2")); + api.add_dependency(("package-a", "1.0.0"), ("package-c", "^3")); + api.add_optional_peer_dependency( + ("package-b", "2.0.0"), + ("package-peer", "4"), + ); + api.add_optional_peer_dependency( + ("package-c", "3.0.0"), + ("package-peer", "*"), + ); + + let (packages, package_reqs) = + run_resolver_and_get_output(api, vec!["npm:package-a@1"]).await; + assert_eq!( + packages, + vec![ + NpmResolutionPackage { + pkg_id: NpmPackageId::from_serialized("package-a@1.0.0").unwrap(), + copy_index: 0, + dependencies: HashMap::from([ + ( + "package-b".to_string(), + NpmPackageId::from_serialized("package-b@2.0.0").unwrap(), + ), + ( + "package-c".to_string(), + NpmPackageId::from_serialized("package-c@3.0.0").unwrap(), + ), + ]), + dist: Default::default(), + }, + NpmResolutionPackage { + pkg_id: NpmPackageId::from_serialized("package-b@2.0.0").unwrap(), + copy_index: 0, + dist: Default::default(), + dependencies: HashMap::new(), + }, + NpmResolutionPackage { + pkg_id: NpmPackageId::from_serialized("package-c@3.0.0").unwrap(), + copy_index: 0, + dist: Default::default(), + dependencies: HashMap::new(), + }, + ] + ); + assert_eq!( + package_reqs, + vec![("package-a@1".to_string(), "package-a@1.0.0".to_string())] + ); + } + + #[tokio::test] + async fn resolve_with_optional_peer_found() { + let api = TestNpmRegistryApiInner::default(); + api.ensure_package_version("package-a", "1.0.0"); + api.ensure_package_version("package-b", "2.0.0"); + api.ensure_package_version("package-c", "3.0.0"); + api.ensure_package_version("package-peer", "4.0.0"); + api.ensure_package_version("package-peer", "4.1.0"); + api.add_dependency(("package-a", "1.0.0"), ("package-b", "^2")); + api.add_dependency(("package-a", "1.0.0"), ("package-c", "^3")); + api.add_optional_peer_dependency( + ("package-b", "2.0.0"), + ("package-peer", "4"), + ); + api.add_optional_peer_dependency( + ("package-c", "3.0.0"), + ("package-peer", "*"), + ); + + let (packages, package_reqs) = run_resolver_and_get_output( + api, + vec!["npm:package-a@1", "npm:package-peer@4.0.0"], + ) + .await; + assert_eq!( + packages, + vec![ + NpmResolutionPackage { + pkg_id: NpmPackageId::from_serialized( + "package-a@1.0.0_package-peer@4.0.0" + ) + .unwrap(), + copy_index: 0, + dependencies: HashMap::from([ + ( + "package-b".to_string(), + NpmPackageId::from_serialized( + "package-b@2.0.0_package-peer@4.0.0" + ) + .unwrap(), + ), + ( + "package-c".to_string(), + NpmPackageId::from_serialized( + "package-c@3.0.0_package-peer@4.0.0" + ) + .unwrap(), + ), + ]), + dist: Default::default(), + }, + NpmResolutionPackage { + pkg_id: NpmPackageId::from_serialized( + "package-b@2.0.0_package-peer@4.0.0" + ) + .unwrap(), + copy_index: 0, + dist: Default::default(), + dependencies: HashMap::from([( + "package-peer".to_string(), + NpmPackageId::from_serialized("package-peer@4.0.0").unwrap(), + )]) + }, + NpmResolutionPackage { + pkg_id: NpmPackageId::from_serialized( + "package-c@3.0.0_package-peer@4.0.0" + ) + .unwrap(), + copy_index: 0, + dist: Default::default(), + dependencies: HashMap::from([( + "package-peer".to_string(), + NpmPackageId::from_serialized("package-peer@4.0.0").unwrap(), + )]) + }, + NpmResolutionPackage { + pkg_id: NpmPackageId::from_serialized("package-peer@4.0.0").unwrap(), + copy_index: 0, + dist: Default::default(), + dependencies: Default::default(), + }, + ] + ); + assert_eq!( + package_reqs, + vec![ + ( + "package-a@1".to_string(), + "package-a@1.0.0_package-peer@4.0.0".to_string() + ), + ( + "package-peer@4.0.0".to_string(), + "package-peer@4.0.0".to_string() + ) + ] + ); + } + + #[tokio::test] + async fn resolve_optional_peer_first_not_resolved_second_resolved_scenario1() + { + // When resolving a dependency a second time and it has an optional + // peer dependency that wasn't previously resolved, it should resolve all the // previous versions to the new one - let api = TestNpmRegistryApi::default(); + let api = TestNpmRegistryApiInner::default(); api.ensure_package_version("package-a", "1.0.0"); api.ensure_package_version("package-b", "1.0.0"); api.ensure_package_version("package-peer", "1.0.0"); @@ -1705,12 +2103,18 @@ mod test { packages, vec![ NpmResolutionPackage { - id: NpmPackageId::from_serialized("package-a@1.0.0").unwrap(), + pkg_id: NpmPackageId::from_serialized( + "package-a@1.0.0_package-peer@1.0.0" + ) + .unwrap(), copy_index: 0, dependencies: HashMap::from([ ( "package-b".to_string(), - NpmPackageId::from_serialized("package-b@1.0.0").unwrap(), + NpmPackageId::from_serialized( + "package-b@1.0.0_package-peer@1.0.0" + ) + .unwrap(), ), ( "package-peer".to_string(), @@ -1720,7 +2124,10 @@ mod test { dist: Default::default(), }, NpmResolutionPackage { - id: NpmPackageId::from_serialized("package-b@1.0.0").unwrap(), + pkg_id: NpmPackageId::from_serialized( + "package-b@1.0.0_package-peer@1.0.0" + ) + .unwrap(), copy_index: 0, dist: Default::default(), dependencies: HashMap::from([( @@ -1729,7 +2136,7 @@ mod test { )]), }, NpmResolutionPackage { - id: NpmPackageId::from_serialized("package-peer@1.0.0").unwrap(), + pkg_id: NpmPackageId::from_serialized("package-peer@1.0.0").unwrap(), copy_index: 0, dist: Default::default(), dependencies: HashMap::new(), @@ -1739,8 +2146,14 @@ mod test { assert_eq!( package_reqs, vec![ - ("package-a@1".to_string(), "package-a@1.0.0".to_string()), - ("package-b@1".to_string(), "package-b@1.0.0".to_string()) + ( + "package-a@1".to_string(), + "package-a@1.0.0_package-peer@1.0.0".to_string() + ), + ( + "package-b@1".to_string(), + "package-b@1.0.0_package-peer@1.0.0".to_string() + ) ] ); } @@ -1748,7 +2161,7 @@ mod test { #[tokio::test] async fn resolve_optional_peer_first_not_resolved_second_resolved_scenario2() { - let api = TestNpmRegistryApi::default(); + let api = TestNpmRegistryApiInner::default(); api.ensure_package_version("package-a", "1.0.0"); api.ensure_package_version("package-b", "1.0.0"); api.ensure_package_version("package-peer", "2.0.0"); @@ -1768,7 +2181,10 @@ mod test { packages, vec![ NpmResolutionPackage { - id: NpmPackageId::from_serialized("package-a@1.0.0").unwrap(), + pkg_id: NpmPackageId::from_serialized( + "package-a@1.0.0_package-peer@2.0.0" + ) + .unwrap(), copy_index: 0, dependencies: HashMap::from([( "package-peer".to_string(), @@ -1777,13 +2193,19 @@ mod test { dist: Default::default(), }, NpmResolutionPackage { - id: NpmPackageId::from_serialized("package-b@1.0.0").unwrap(), + pkg_id: NpmPackageId::from_serialized( + "package-b@1.0.0_package-peer@2.0.0" + ) + .unwrap(), copy_index: 0, dist: Default::default(), dependencies: HashMap::from([ ( "package-a".to_string(), - NpmPackageId::from_serialized("package-a@1.0.0").unwrap(), + NpmPackageId::from_serialized( + "package-a@1.0.0_package-peer@2.0.0" + ) + .unwrap(), ), ( "package-peer".to_string(), @@ -1792,7 +2214,7 @@ mod test { ]), }, NpmResolutionPackage { - id: NpmPackageId::from_serialized("package-peer@2.0.0").unwrap(), + pkg_id: NpmPackageId::from_serialized("package-peer@2.0.0").unwrap(), copy_index: 0, dist: Default::default(), dependencies: HashMap::new(), @@ -1802,15 +2224,21 @@ mod test { assert_eq!( package_reqs, vec![ - ("package-a@1".to_string(), "package-a@1.0.0".to_string()), - ("package-b@1".to_string(), "package-b@1.0.0".to_string()) + ( + "package-a@1".to_string(), + "package-a@1.0.0_package-peer@2.0.0".to_string() + ), + ( + "package-b@1".to_string(), + "package-b@1.0.0_package-peer@2.0.0".to_string() + ) ] ); } #[tokio::test] async fn resolve_optional_dep_npm_req_top() { - let api = TestNpmRegistryApi::default(); + let api = TestNpmRegistryApiInner::default(); api.ensure_package_version("package-a", "1.0.0"); api.ensure_package_version("package-peer", "1.0.0"); api.add_optional_peer_dependency( @@ -1827,7 +2255,10 @@ mod test { packages, vec![ NpmResolutionPackage { - id: NpmPackageId::from_serialized("package-a@1.0.0").unwrap(), + pkg_id: NpmPackageId::from_serialized( + "package-a@1.0.0_package-peer@1.0.0" + ) + .unwrap(), copy_index: 0, dependencies: HashMap::from([( "package-peer".to_string(), @@ -1836,7 +2267,7 @@ mod test { dist: Default::default(), }, NpmResolutionPackage { - id: NpmPackageId::from_serialized("package-peer@1.0.0").unwrap(), + pkg_id: NpmPackageId::from_serialized("package-peer@1.0.0").unwrap(), copy_index: 0, dist: Default::default(), dependencies: HashMap::new(), @@ -1846,7 +2277,10 @@ mod test { assert_eq!( package_reqs, vec![ - ("package-a@1".to_string(), "package-a@1.0.0".to_string()), + ( + "package-a@1".to_string(), + "package-a@1.0.0_package-peer@1.0.0".to_string() + ), ( "package-peer@1".to_string(), "package-peer@1.0.0".to_string() @@ -1857,7 +2291,7 @@ mod test { #[tokio::test] async fn resolve_optional_dep_different_resolution_second_time() { - let api = TestNpmRegistryApi::default(); + let api = TestNpmRegistryApiInner::default(); api.ensure_package_version("package-a", "1.0.0"); api.ensure_package_version("package-b", "1.0.0"); api.ensure_package_version("package-peer", "1.0.0"); @@ -1882,7 +2316,10 @@ mod test { packages, vec![ NpmResolutionPackage { - id: NpmPackageId::from_serialized("package-a@1.0.0").unwrap(), + pkg_id: NpmPackageId::from_serialized( + "package-a@1.0.0_package-peer@1.0.0" + ) + .unwrap(), copy_index: 0, dependencies: HashMap::from([( "package-peer".to_string(), @@ -1891,7 +2328,7 @@ mod test { dist: Default::default(), }, NpmResolutionPackage { - id: NpmPackageId::from_serialized( + pkg_id: NpmPackageId::from_serialized( "package-a@1.0.0_package-peer@2.0.0" ) .unwrap(), @@ -1903,7 +2340,7 @@ mod test { dist: Default::default(), }, NpmResolutionPackage { - id: NpmPackageId::from_serialized( + pkg_id: NpmPackageId::from_serialized( "package-b@1.0.0_package-peer@2.0.0" ) .unwrap(), @@ -1924,13 +2361,13 @@ mod test { ]), }, NpmResolutionPackage { - id: NpmPackageId::from_serialized("package-peer@1.0.0").unwrap(), + pkg_id: NpmPackageId::from_serialized("package-peer@1.0.0").unwrap(), copy_index: 0, dist: Default::default(), dependencies: HashMap::new(), }, NpmResolutionPackage { - id: NpmPackageId::from_serialized("package-peer@2.0.0").unwrap(), + pkg_id: NpmPackageId::from_serialized("package-peer@2.0.0").unwrap(), copy_index: 0, dist: Default::default(), dependencies: HashMap::new(), @@ -1940,7 +2377,10 @@ mod test { assert_eq!( package_reqs, vec![ - ("package-a@1".to_string(), "package-a@1.0.0".to_string()), + ( + "package-a@1".to_string(), + "package-a@1.0.0_package-peer@1.0.0".to_string() + ), ( "package-b@1".to_string(), "package-b@1.0.0_package-peer@2.0.0".to_string() @@ -1952,10 +2392,61 @@ mod test { ] ); } + #[tokio::test] + async fn resolve_peer_dep_other_specifier_slot() { + let api = TestNpmRegistryApiInner::default(); + api.ensure_package_version("package-a", "1.0.0"); + api.ensure_package_version("package-peer", "2.0.0"); + // bit of an edge case... probably nobody has ever done this + api.add_dependency( + ("package-a", "1.0.0"), + ("package-peer2", "npm:package-peer@2"), + ); + api.add_peer_dependency(("package-a", "1.0.0"), ("package-peer", "2")); + + let (packages, package_reqs) = + run_resolver_and_get_output(api, vec!["npm:package-a@1"]).await; + assert_eq!( + packages, + vec![ + NpmResolutionPackage { + pkg_id: NpmPackageId::from_serialized( + "package-a@1.0.0_package-peer@2.0.0" + ) + .unwrap(), + copy_index: 0, + dependencies: HashMap::from([ + ( + "package-peer".to_string(), + NpmPackageId::from_serialized("package-peer@2.0.0").unwrap(), + ), + ( + "package-peer2".to_string(), + NpmPackageId::from_serialized("package-peer@2.0.0").unwrap(), + ), + ]), + dist: Default::default(), + }, + NpmResolutionPackage { + pkg_id: NpmPackageId::from_serialized("package-peer@2.0.0").unwrap(), + copy_index: 0, + dist: Default::default(), + dependencies: Default::default(), + }, + ] + ); + assert_eq!( + package_reqs, + vec![( + "package-a@1".to_string(), + "package-a@1.0.0_package-peer@2.0.0".to_string() + ),] + ); + } #[tokio::test] async fn resolve_nested_peer_deps_auto_resolved() { - let api = TestNpmRegistryApi::default(); + let api = TestNpmRegistryApiInner::default(); api.ensure_package_version("package-0", "1.0.0"); api.ensure_package_version("package-peer-a", "2.0.0"); api.ensure_package_version("package-peer-b", "3.0.0"); @@ -1971,16 +2462,25 @@ mod test { packages, vec![ NpmResolutionPackage { - id: NpmPackageId::from_serialized("package-0@1.0.0").unwrap(), + pkg_id: NpmPackageId::from_serialized( + "package-0@1.0.0_package-peer-a@2.0.0__package-peer-b@3.0.0" + ) + .unwrap(), copy_index: 0, dependencies: HashMap::from([( "package-peer-a".to_string(), - NpmPackageId::from_serialized("package-peer-a@2.0.0").unwrap(), + NpmPackageId::from_serialized( + "package-peer-a@2.0.0_package-peer-b@3.0.0" + ) + .unwrap(), )]), dist: Default::default(), }, NpmResolutionPackage { - id: NpmPackageId::from_serialized("package-peer-a@2.0.0").unwrap(), + pkg_id: NpmPackageId::from_serialized( + "package-peer-a@2.0.0_package-peer-b@3.0.0" + ) + .unwrap(), copy_index: 0, dependencies: HashMap::from([( "package-peer-b".to_string(), @@ -1989,7 +2489,8 @@ mod test { dist: Default::default(), }, NpmResolutionPackage { - id: NpmPackageId::from_serialized("package-peer-b@3.0.0").unwrap(), + pkg_id: NpmPackageId::from_serialized("package-peer-b@3.0.0") + .unwrap(), copy_index: 0, dependencies: HashMap::new(), dist: Default::default(), @@ -1998,13 +2499,17 @@ mod test { ); assert_eq!( package_reqs, - vec![("package-0@1.0".to_string(), "package-0@1.0.0".to_string())] + vec![( + "package-0@1.0".to_string(), + "package-0@1.0.0_package-peer-a@2.0.0__package-peer-b@3.0.0" + .to_string() + )] ); } #[tokio::test] async fn resolve_nested_peer_deps_ancestor_sibling_deps() { - let api = TestNpmRegistryApi::default(); + let api = TestNpmRegistryApiInner::default(); api.ensure_package_version("package-0", "1.0.0"); api.ensure_package_version("package-peer-a", "2.0.0"); api.ensure_package_version("package-peer-b", "3.0.0"); @@ -2028,8 +2533,8 @@ mod test { packages, vec![ NpmResolutionPackage { - id: NpmPackageId::from_serialized( - "package-0@1.0.0_package-peer-a@2.0.0_package-peer-b@3.0.0" + pkg_id: NpmPackageId::from_serialized( + "package-0@1.0.0_package-peer-a@2.0.0__package-peer-b@3.0.0_package-peer-b@3.0.0" ) .unwrap(), copy_index: 0, @@ -2043,25 +2548,28 @@ mod test { ), ( "package-peer-b".to_string(), - NpmPackageId::from_serialized("package-peer-b@3.0.0").unwrap(), + NpmPackageId::from_serialized("package-peer-b@3.0.0") + .unwrap(), ) ]), dist: Default::default(), }, NpmResolutionPackage { - id: NpmPackageId::from_serialized( + pkg_id: NpmPackageId::from_serialized( "package-peer-a@2.0.0_package-peer-b@3.0.0" ) .unwrap(), copy_index: 0, dependencies: HashMap::from([( "package-peer-b".to_string(), - NpmPackageId::from_serialized("package-peer-b@3.0.0").unwrap(), + NpmPackageId::from_serialized("package-peer-b@3.0.0") + .unwrap(), )]), dist: Default::default(), }, NpmResolutionPackage { - id: NpmPackageId::from_serialized("package-peer-b@3.0.0").unwrap(), + pkg_id: NpmPackageId::from_serialized("package-peer-b@3.0.0") + .unwrap(), copy_index: 0, dependencies: HashMap::new(), dist: Default::default(), @@ -2073,7 +2581,7 @@ mod test { vec![ ( "package-0@1.0".to_string(), - "package-0@1.0.0_package-peer-a@2.0.0_package-peer-b@3.0.0" + "package-0@1.0.0_package-peer-a@2.0.0__package-peer-b@3.0.0_package-peer-b@3.0.0" .to_string() ), ( @@ -2090,7 +2598,7 @@ mod test { #[tokio::test] async fn resolve_with_peer_deps_multiple() { - let api = TestNpmRegistryApi::default(); + let api = TestNpmRegistryApiInner::default(); api.ensure_package_version("package-0", "1.1.1"); api.ensure_package_version("package-a", "1.0.0"); api.ensure_package_version("package-b", "2.0.0"); @@ -2110,7 +2618,7 @@ mod test { api.add_peer_dependency(("package-b", "2.0.0"), ("package-peer-a", "4")); api.add_peer_dependency( ("package-b", "2.0.0"), - ("package-peer-c", "=6.2.0"), + ("package-peer-c", "=6.2.0"), // will be auto-resolved ); api.add_peer_dependency(("package-c", "3.0.0"), ("package-peer-a", "*")); api.add_peer_dependency( @@ -2127,20 +2635,21 @@ mod test { packages, vec![ NpmResolutionPackage { - id: NpmPackageId::from_serialized("package-0@1.1.1").unwrap(), + pkg_id: NpmPackageId::from_serialized("package-0@1.1.1") + .unwrap(), copy_index: 0, dependencies: HashMap::from([( "package-a".to_string(), NpmPackageId::from_serialized( - "package-a@1.0.0_package-peer-a@4.0.0" + "package-a@1.0.0_package-peer-a@4.0.0__package-peer-b@5.4.1" ) .unwrap(), ),]), dist: Default::default(), }, NpmResolutionPackage { - id: NpmPackageId::from_serialized( - "package-a@1.0.0_package-peer-a@4.0.0" + pkg_id: NpmPackageId::from_serialized( + "package-a@1.0.0_package-peer-a@4.0.0__package-peer-b@5.4.1" ) .unwrap(), copy_index: 0, @@ -2148,14 +2657,14 @@ mod test { ( "package-b".to_string(), NpmPackageId::from_serialized( - "package-b@2.0.0_package-peer-a@4.0.0" + "package-b@2.0.0_package-peer-a@4.0.0__package-peer-b@5.4.1_package-peer-c@6.2.0" ) .unwrap(), ), ( "package-c".to_string(), NpmPackageId::from_serialized( - "package-c@3.0.0_package-peer-a@4.0.0" + "package-c@3.0.0_package-peer-a@4.0.0__package-peer-b@5.4.1" ) .unwrap(), ), @@ -2165,14 +2674,17 @@ mod test { ), ( "package-peer-a".to_string(), - NpmPackageId::from_serialized("package-peer-a@4.0.0").unwrap(), + NpmPackageId::from_serialized( + "package-peer-a@4.0.0_package-peer-b@5.4.1" + ) + .unwrap(), ), ]), dist: Default::default(), }, NpmResolutionPackage { - id: NpmPackageId::from_serialized( - "package-b@2.0.0_package-peer-a@4.0.0" + pkg_id: NpmPackageId::from_serialized( + "package-b@2.0.0_package-peer-a@4.0.0__package-peer-b@5.4.1_package-peer-c@6.2.0" ) .unwrap(), copy_index: 0, @@ -2180,55 +2692,64 @@ mod test { dependencies: HashMap::from([ ( "package-peer-a".to_string(), - NpmPackageId::from_serialized("package-peer-a@4.0.0").unwrap(), + NpmPackageId::from_serialized("package-peer-a@4.0.0_package-peer-b@5.4.1") + .unwrap(), ), ( "package-peer-c".to_string(), - NpmPackageId::from_serialized("package-peer-c@6.2.0").unwrap(), + NpmPackageId::from_serialized("package-peer-c@6.2.0") + .unwrap(), ) ]) }, NpmResolutionPackage { - id: NpmPackageId::from_serialized( - "package-c@3.0.0_package-peer-a@4.0.0" + pkg_id: NpmPackageId::from_serialized( + "package-c@3.0.0_package-peer-a@4.0.0__package-peer-b@5.4.1" ) .unwrap(), copy_index: 0, dist: Default::default(), dependencies: HashMap::from([( "package-peer-a".to_string(), - NpmPackageId::from_serialized("package-peer-a@4.0.0").unwrap(), + NpmPackageId::from_serialized("package-peer-a@4.0.0_package-peer-b@5.4.1") + .unwrap(), )]) }, NpmResolutionPackage { - id: NpmPackageId::from_serialized("package-d@3.5.0").unwrap(), + pkg_id: NpmPackageId::from_serialized("package-d@3.5.0") + .unwrap(), copy_index: 0, dependencies: HashMap::from([]), dist: Default::default(), }, NpmResolutionPackage { - id: NpmPackageId::from_serialized("package-e@3.6.0").unwrap(), + pkg_id: NpmPackageId::from_serialized("package-e@3.6.0") + .unwrap(), copy_index: 0, dependencies: HashMap::from([]), dist: Default::default(), }, NpmResolutionPackage { - id: NpmPackageId::from_serialized("package-peer-a@4.0.0").unwrap(), + pkg_id: NpmPackageId::from_serialized("package-peer-a@4.0.0_package-peer-b@5.4.1") + .unwrap(), copy_index: 0, dist: Default::default(), dependencies: HashMap::from([( "package-peer-b".to_string(), - NpmPackageId::from_serialized("package-peer-b@5.4.1").unwrap(), + NpmPackageId::from_serialized("package-peer-b@5.4.1") + .unwrap(), )]) }, NpmResolutionPackage { - id: NpmPackageId::from_serialized("package-peer-b@5.4.1").unwrap(), + pkg_id: NpmPackageId::from_serialized("package-peer-b@5.4.1") + .unwrap(), copy_index: 0, dist: Default::default(), dependencies: Default::default(), }, NpmResolutionPackage { - id: NpmPackageId::from_serialized("package-peer-c@6.2.0").unwrap(), + pkg_id: NpmPackageId::from_serialized("package-peer-c@6.2.0") + .unwrap(), copy_index: 0, dist: Default::default(), dependencies: Default::default(), @@ -2246,7 +2767,7 @@ mod test { #[tokio::test] async fn resolve_peer_deps_circular() { - let api = TestNpmRegistryApi::default(); + let api = TestNpmRegistryApiInner::default(); api.ensure_package_version("package-a", "1.0.0"); api.ensure_package_version("package-b", "2.0.0"); api.add_dependency(("package-a", "1.0.0"), ("package-b", "*")); @@ -2258,7 +2779,7 @@ mod test { packages, vec![ NpmResolutionPackage { - id: NpmPackageId::from_serialized("package-a@1.0.0").unwrap(), + pkg_id: NpmPackageId::from_serialized("package-a@1.0.0").unwrap(), copy_index: 0, dependencies: HashMap::from([( "package-b".to_string(), @@ -2268,8 +2789,10 @@ mod test { dist: Default::default(), }, NpmResolutionPackage { - id: NpmPackageId::from_serialized("package-b@2.0.0_package-a@1.0.0") - .unwrap(), + pkg_id: NpmPackageId::from_serialized( + "package-b@2.0.0_package-a@1.0.0" + ) + .unwrap(), copy_index: 0, dependencies: HashMap::from([( "package-a".to_string(), @@ -2289,7 +2812,7 @@ mod test { async fn resolve_peer_deps_multiple_copies() { // repeat this a few times to have a higher probability of surfacing indeterminism for _ in 0..3 { - let api = TestNpmRegistryApi::default(); + let api = TestNpmRegistryApiInner::default(); api.ensure_package_version("package-a", "1.0.0"); api.ensure_package_version("package-b", "2.0.0"); api.ensure_package_version("package-dep", "3.0.0"); @@ -2310,7 +2833,7 @@ mod test { packages, vec![ NpmResolutionPackage { - id: NpmPackageId::from_serialized( + pkg_id: NpmPackageId::from_serialized( "package-a@1.0.0_package-peer@4.0.0" ) .unwrap(), @@ -2331,7 +2854,7 @@ mod test { dist: Default::default(), }, NpmResolutionPackage { - id: NpmPackageId::from_serialized( + pkg_id: NpmPackageId::from_serialized( "package-b@2.0.0_package-peer@5.0.0" ) .unwrap(), @@ -2352,7 +2875,7 @@ mod test { dist: Default::default(), }, NpmResolutionPackage { - id: NpmPackageId::from_serialized( + pkg_id: NpmPackageId::from_serialized( "package-dep@3.0.0_package-peer@4.0.0" ) .unwrap(), @@ -2364,7 +2887,7 @@ mod test { dist: Default::default(), }, NpmResolutionPackage { - id: NpmPackageId::from_serialized( + pkg_id: NpmPackageId::from_serialized( "package-dep@3.0.0_package-peer@5.0.0" ) .unwrap(), @@ -2376,13 +2899,15 @@ mod test { dist: Default::default(), }, NpmResolutionPackage { - id: NpmPackageId::from_serialized("package-peer@4.0.0").unwrap(), + pkg_id: NpmPackageId::from_serialized("package-peer@4.0.0") + .unwrap(), copy_index: 0, dependencies: HashMap::new(), dist: Default::default(), }, NpmResolutionPackage { - id: NpmPackageId::from_serialized("package-peer@5.0.0").unwrap(), + pkg_id: NpmPackageId::from_serialized("package-peer@5.0.0") + .unwrap(), copy_index: 0, dependencies: HashMap::new(), dist: Default::default(), @@ -2407,7 +2932,7 @@ mod test { #[tokio::test] async fn resolve_dep_with_peer_deps_dep_then_peer() { - let api = TestNpmRegistryApi::default(); + let api = TestNpmRegistryApiInner::default(); api.ensure_package_version("package-a", "1.0.0"); api.ensure_package_version("package-b", "1.0.0"); api.ensure_package_version("package-c", "1.0.0"); @@ -2426,14 +2951,18 @@ mod test { packages, vec![ NpmResolutionPackage { - id: NpmPackageId::from_serialized("package-a@1.0.0_package-b@1.0.0") - .unwrap(), + pkg_id: NpmPackageId::from_serialized( + "package-a@1.0.0_package-b@1.0.0__package-peer@1.0.0" + ) + .unwrap(), copy_index: 0, dependencies: HashMap::from([ ( "package-c".to_string(), - NpmPackageId::from_serialized("package-c@1.0.0_package-b@1.0.0") - .unwrap(), + NpmPackageId::from_serialized( + "package-c@1.0.0_package-b@1.0.0__package-peer@1.0.0" + ) + .unwrap(), ), ( "package-peer".to_string(), @@ -2443,7 +2972,10 @@ mod test { dist: Default::default(), }, NpmResolutionPackage { - id: NpmPackageId::from_serialized("package-b@1.0.0").unwrap(), + pkg_id: NpmPackageId::from_serialized( + "package-b@1.0.0_package-peer@1.0.0" + ) + .unwrap(), copy_index: 0, dependencies: HashMap::from([( "package-peer".to_string(), @@ -2452,17 +2984,20 @@ mod test { dist: Default::default(), }, NpmResolutionPackage { - id: NpmPackageId::from_serialized("package-c@1.0.0_package-b@1.0.0") - .unwrap(), + pkg_id: NpmPackageId::from_serialized( + "package-c@1.0.0_package-b@1.0.0__package-peer@1.0.0" + ) + .unwrap(), copy_index: 0, dependencies: HashMap::from([( "package-b".to_string(), - NpmPackageId::from_serialized("package-b@1.0.0").unwrap(), + NpmPackageId::from_serialized("package-b@1.0.0_package-peer@1.0.0") + .unwrap(), )]), dist: Default::default(), }, NpmResolutionPackage { - id: NpmPackageId::from_serialized("package-peer@1.0.0").unwrap(), + pkg_id: NpmPackageId::from_serialized("package-peer@1.0.0").unwrap(), copy_index: 0, dependencies: HashMap::from([]), dist: Default::default(), @@ -2474,25 +3009,28 @@ mod test { vec![ ( "package-a@1.0".to_string(), - "package-a@1.0.0_package-b@1.0.0".to_string() + "package-a@1.0.0_package-b@1.0.0__package-peer@1.0.0".to_string() ), - ("package-b@1.0".to_string(), "package-b@1.0.0".to_string()) + ( + "package-b@1.0".to_string(), + "package-b@1.0.0_package-peer@1.0.0".to_string() + ) ] ); } #[tokio::test] - async fn resolve_dep_with_peer_deps_dep_then_different_peer() { - let api = TestNpmRegistryApi::default(); + async fn resolve_dep_with_peer_deps_then_other_dep_with_different_peer() { + let api = TestNpmRegistryApiInner::default(); api.ensure_package_version("package-a", "1.0.0"); api.ensure_package_version("package-b", "1.0.0"); api.ensure_package_version("package-c", "1.0.0"); api.ensure_package_version("package-peer", "1.1.0"); api.ensure_package_version("package-peer", "1.2.0"); - api.add_peer_dependency(("package-a", "1.0.0"), ("package-peer", "*")); // should select 1.2.0 + api.add_peer_dependency(("package-a", "1.0.0"), ("package-peer", "*")); // should select 1.2.0, then 1.1.0 api.add_dependency(("package-b", "1.0.0"), ("package-c", "1")); api.add_dependency(("package-b", "1.0.0"), ("package-peer", "=1.1.0")); - api.add_peer_dependency(("package-c", "1.0.0"), ("package-a", "1")); + api.add_dependency(("package-c", "1.0.0"), ("package-a", "1")); let (packages, package_reqs) = run_resolver_and_get_output( api, @@ -2503,29 +3041,32 @@ mod test { packages, vec![ NpmResolutionPackage { - id: NpmPackageId::from_serialized("package-a@1.0.0").unwrap(), - copy_index: 0, + pkg_id: NpmPackageId::from_serialized( + "package-a@1.0.0_package-peer@1.1.0" + ) + .unwrap(), + copy_index: 1, dependencies: HashMap::from([( "package-peer".to_string(), - NpmPackageId::from_serialized("package-peer@1.2.0").unwrap(), + NpmPackageId::from_serialized("package-peer@1.1.0").unwrap(), )]), dist: Default::default(), }, NpmResolutionPackage { - id: NpmPackageId::from_serialized( - "package-a@1.0.0_package-peer@1.1.0" + pkg_id: NpmPackageId::from_serialized( + "package-a@1.0.0_package-peer@1.2.0" ) .unwrap(), - copy_index: 1, + copy_index: 0, dependencies: HashMap::from([( "package-peer".to_string(), - NpmPackageId::from_serialized("package-peer@1.1.0").unwrap(), + NpmPackageId::from_serialized("package-peer@1.2.0").unwrap(), )]), dist: Default::default(), }, NpmResolutionPackage { - id: NpmPackageId::from_serialized( - "package-b@1.0.0_package-a@1.0.0_package-peer@1.1.0" + pkg_id: NpmPackageId::from_serialized( + "package-b@1.0.0_package-peer@1.1.0" ) .unwrap(), copy_index: 0, @@ -2533,7 +3074,7 @@ mod test { ( "package-c".to_string(), NpmPackageId::from_serialized( - "package-c@1.0.0_package-a@1.0.0_package-peer@1.1.0" + "package-c@1.0.0_package-peer@1.1.0" ) .unwrap(), ), @@ -2545,8 +3086,8 @@ mod test { dist: Default::default(), }, NpmResolutionPackage { - id: NpmPackageId::from_serialized( - "package-c@1.0.0_package-a@1.0.0_package-peer@1.1.0" + pkg_id: NpmPackageId::from_serialized( + "package-c@1.0.0_package-peer@1.1.0" ) .unwrap(), copy_index: 0, @@ -2558,13 +3099,13 @@ mod test { dist: Default::default(), }, NpmResolutionPackage { - id: NpmPackageId::from_serialized("package-peer@1.1.0").unwrap(), + pkg_id: NpmPackageId::from_serialized("package-peer@1.1.0").unwrap(), copy_index: 0, dependencies: HashMap::from([]), dist: Default::default(), }, NpmResolutionPackage { - id: NpmPackageId::from_serialized("package-peer@1.2.0").unwrap(), + pkg_id: NpmPackageId::from_serialized("package-peer@1.2.0").unwrap(), copy_index: 0, dependencies: HashMap::from([]), dist: Default::default(), @@ -2574,10 +3115,13 @@ mod test { assert_eq!( package_reqs, vec![ - ("package-a@1.0".to_string(), "package-a@1.0.0".to_string()), + ( + "package-a@1.0".to_string(), + "package-a@1.0.0_package-peer@1.2.0".to_string() + ), ( "package-b@1.0".to_string(), - "package-b@1.0.0_package-a@1.0.0_package-peer@1.1.0".to_string() + "package-b@1.0.0_package-peer@1.1.0".to_string() ) ] ); @@ -2585,7 +3129,7 @@ mod test { #[tokio::test] async fn resolve_dep_and_peer_dist_tag() { - let api = TestNpmRegistryApi::default(); + let api = TestNpmRegistryApiInner::default(); api.ensure_package_version("package-a", "1.0.0"); api.ensure_package_version("package-b", "2.0.0"); api.ensure_package_version("package-b", "3.0.0"); @@ -2607,8 +3151,10 @@ mod test { packages, vec![ NpmResolutionPackage { - id: NpmPackageId::from_serialized("package-a@1.0.0_package-d@1.0.0") - .unwrap(), + pkg_id: NpmPackageId::from_serialized( + "package-a@1.0.0_package-d@1.0.0" + ) + .unwrap(), copy_index: 0, dependencies: HashMap::from([ ( @@ -2632,14 +3178,16 @@ mod test { dist: Default::default(), }, NpmResolutionPackage { - id: NpmPackageId::from_serialized("package-b@2.0.0").unwrap(), + pkg_id: NpmPackageId::from_serialized("package-b@2.0.0").unwrap(), copy_index: 0, dependencies: HashMap::new(), dist: Default::default(), }, NpmResolutionPackage { - id: NpmPackageId::from_serialized("package-c@1.0.0_package-d@1.0.0") - .unwrap(), + pkg_id: NpmPackageId::from_serialized( + "package-c@1.0.0_package-d@1.0.0" + ) + .unwrap(), copy_index: 0, dependencies: HashMap::from([( "package-d".to_string(), @@ -2648,13 +3196,13 @@ mod test { dist: Default::default(), }, NpmResolutionPackage { - id: NpmPackageId::from_serialized("package-d@1.0.0").unwrap(), + pkg_id: NpmPackageId::from_serialized("package-d@1.0.0").unwrap(), copy_index: 0, dependencies: HashMap::new(), dist: Default::default(), }, NpmResolutionPackage { - id: NpmPackageId::from_serialized("package-e@1.0.0").unwrap(), + pkg_id: NpmPackageId::from_serialized("package-e@1.0.0").unwrap(), copy_index: 0, dependencies: HashMap::from([( "package-b".to_string(), @@ -2675,7 +3223,7 @@ mod test { #[tokio::test] async fn package_has_self_as_dependency() { - let api = TestNpmRegistryApi::default(); + let api = TestNpmRegistryApiInner::default(); api.ensure_package_version("package-a", "1.0.0"); api.add_dependency(("package-a", "1.0.0"), ("package-a", "1")); @@ -2684,7 +3232,7 @@ mod test { assert_eq!( packages, vec![NpmResolutionPackage { - id: NpmPackageId::from_serialized("package-a@1.0.0").unwrap(), + pkg_id: NpmPackageId::from_serialized("package-a@1.0.0").unwrap(), copy_index: 0, // in this case, we just ignore that the package did this dependencies: Default::default(), @@ -2699,7 +3247,7 @@ mod test { #[tokio::test] async fn package_has_self_but_different_version_as_dependency() { - let api = TestNpmRegistryApi::default(); + let api = TestNpmRegistryApiInner::default(); api.ensure_package_version("package-a", "1.0.0"); api.ensure_package_version("package-a", "0.5.0"); api.add_dependency(("package-a", "1.0.0"), ("package-a", "^0.5")); @@ -2710,13 +3258,13 @@ mod test { packages, vec![ NpmResolutionPackage { - id: NpmPackageId::from_serialized("package-a@0.5.0").unwrap(), + pkg_id: NpmPackageId::from_serialized("package-a@0.5.0").unwrap(), copy_index: 0, dependencies: Default::default(), dist: Default::default(), }, NpmResolutionPackage { - id: NpmPackageId::from_serialized("package-a@1.0.0").unwrap(), + pkg_id: NpmPackageId::from_serialized("package-a@1.0.0").unwrap(), copy_index: 0, dependencies: HashMap::from([( "package-a".to_string(), @@ -2732,15 +3280,350 @@ mod test { ); } + #[tokio::test] + async fn grand_child_package_has_self_as_peer_dependency_root() { + let api = TestNpmRegistryApiInner::default(); + api.ensure_package_version("package-a", "1.0.0"); + api.ensure_package_version("package-b", "2.0.0"); + api.add_dependency(("package-a", "1.0.0"), ("package-b", "2")); + api.add_peer_dependency(("package-b", "2.0.0"), ("package-a", "*")); + + let (packages, package_reqs) = + run_resolver_and_get_output(api, vec!["npm:package-a@1.0"]).await; + assert_eq!( + packages, + vec![ + NpmResolutionPackage { + pkg_id: NpmPackageId::from_serialized("package-a@1.0.0").unwrap(), + copy_index: 0, + dependencies: HashMap::from([( + "package-b".to_string(), + NpmPackageId::from_serialized("package-b@2.0.0_package-a@1.0.0") + .unwrap(), + )]), + dist: Default::default(), + }, + NpmResolutionPackage { + pkg_id: NpmPackageId::from_serialized( + "package-b@2.0.0_package-a@1.0.0" + ) + .unwrap(), + copy_index: 0, + dependencies: HashMap::from([( + "package-a".to_string(), + NpmPackageId::from_serialized("package-a@1.0.0").unwrap(), + )]), + dist: Default::default(), + } + ] + ); + assert_eq!( + package_reqs, + vec![("package-a@1.0".to_string(), "package-a@1.0.0".to_string())] + ); + } + + #[tokio::test] + async fn grand_child_package_has_self_as_peer_dependency_under_root() { + let api = TestNpmRegistryApiInner::default(); + api.ensure_package_version("package-0", "1.0.0"); + api.ensure_package_version("package-a", "1.0.0"); + api.ensure_package_version("package-b", "2.0.0"); + api.add_dependency(("package-0", "1.0.0"), ("package-a", "*")); + api.add_dependency(("package-a", "1.0.0"), ("package-b", "2")); + api.add_peer_dependency(("package-b", "2.0.0"), ("package-a", "*")); + + let (packages, package_reqs) = + run_resolver_and_get_output(api, vec!["npm:package-0@1.0"]).await; + assert_eq!( + packages, + vec![ + NpmResolutionPackage { + pkg_id: NpmPackageId::from_serialized("package-0@1.0.0").unwrap(), + copy_index: 0, + dependencies: HashMap::from([( + "package-a".to_string(), + NpmPackageId::from_serialized("package-a@1.0.0").unwrap(), + )]), + dist: Default::default(), + }, + NpmResolutionPackage { + pkg_id: NpmPackageId::from_serialized("package-a@1.0.0").unwrap(), + copy_index: 0, + dependencies: HashMap::from([( + "package-b".to_string(), + NpmPackageId::from_serialized("package-b@2.0.0_package-a@1.0.0") + .unwrap(), + )]), + dist: Default::default(), + }, + NpmResolutionPackage { + pkg_id: NpmPackageId::from_serialized( + "package-b@2.0.0_package-a@1.0.0" + ) + .unwrap(), + copy_index: 0, + dependencies: HashMap::from([( + "package-a".to_string(), + NpmPackageId::from_serialized("package-a@1.0.0").unwrap(), + )]), + dist: Default::default(), + } + ] + ); + assert_eq!( + package_reqs, + vec![("package-0@1.0".to_string(), "package-0@1.0.0".to_string())] + ); + } + + #[tokio::test] + async fn nested_deps_same_peer_dep_ancestor() { + let api = TestNpmRegistryApiInner::default(); + api.ensure_package_version("package-0", "1.0.0"); + api.ensure_package_version("package-1", "1.0.0"); + api.ensure_package_version("package-a", "1.0.0"); + api.ensure_package_version("package-b", "1.0.0"); + api.ensure_package_version("package-c", "1.0.0"); + api.ensure_package_version("package-d", "1.0.0"); + api.add_dependency(("package-0", "1.0.0"), ("package-a", "1")); + api.add_dependency(("package-0", "1.0.0"), ("package-1", "1")); + api.add_dependency(("package-1", "1.0.0"), ("package-a", "1")); + api.add_dependency(("package-a", "1.0.0"), ("package-b", "1")); + api.add_dependency(("package-b", "1.0.0"), ("package-c", "1")); + api.add_dependency(("package-c", "1.0.0"), ("package-d", "1")); + api.add_peer_dependency(("package-b", "1.0.0"), ("package-a", "*")); + api.add_peer_dependency(("package-c", "1.0.0"), ("package-a", "*")); + api.add_peer_dependency(("package-d", "1.0.0"), ("package-a", "*")); + api.add_peer_dependency(("package-b", "1.0.0"), ("package-0", "*")); + api.add_peer_dependency(("package-c", "1.0.0"), ("package-0", "*")); + api.add_peer_dependency(("package-d", "1.0.0"), ("package-0", "*")); + + let (packages, package_reqs) = + run_resolver_and_get_output(api, vec!["npm:package-0@1.0"]).await; + assert_eq!( + packages, + vec![ + NpmResolutionPackage { + pkg_id: NpmPackageId::from_serialized("package-0@1.0.0").unwrap(), + copy_index: 0, + dependencies: HashMap::from([( + "package-a".to_string(), + NpmPackageId::from_serialized("package-a@1.0.0_package-0@1.0.0").unwrap(), + ), ( + "package-1".to_string(), + NpmPackageId::from_serialized("package-1@1.0.0_package-0@1.0.0").unwrap(), + )]), + dist: Default::default(), + }, + NpmResolutionPackage { + pkg_id: NpmPackageId::from_serialized("package-1@1.0.0_package-0@1.0.0").unwrap(), + copy_index: 0, + dependencies: HashMap::from([( + "package-a".to_string(), + NpmPackageId::from_serialized("package-a@1.0.0_package-0@1.0.0").unwrap(), + )]), + dist: Default::default(), + }, + NpmResolutionPackage { + pkg_id: NpmPackageId::from_serialized("package-a@1.0.0_package-0@1.0.0").unwrap(), + copy_index: 0, + dependencies: HashMap::from([( + "package-b".to_string(), + NpmPackageId::from_serialized("package-b@1.0.0_package-0@1.0.0_package-a@1.0.0__package-0@1.0.0") + .unwrap(), + )]), + dist: Default::default(), + }, + NpmResolutionPackage { + pkg_id: NpmPackageId::from_serialized( + "package-b@1.0.0_package-0@1.0.0_package-a@1.0.0__package-0@1.0.0" + ) + .unwrap(), + copy_index: 0, + dependencies: HashMap::from([ + ( + "package-0".to_string(), + NpmPackageId::from_serialized("package-0@1.0.0").unwrap(), + ), + ( + "package-a".to_string(), + NpmPackageId::from_serialized("package-a@1.0.0_package-0@1.0.0").unwrap(), + ), + ( + "package-c".to_string(), + NpmPackageId::from_serialized("package-c@1.0.0_package-0@1.0.0_package-a@1.0.0__package-0@1.0.0") + .unwrap(), + ) + ]), + dist: Default::default(), + }, + NpmResolutionPackage { + pkg_id: NpmPackageId::from_serialized( + "package-c@1.0.0_package-0@1.0.0_package-a@1.0.0__package-0@1.0.0" + ) + .unwrap(), + copy_index: 0, + dependencies: HashMap::from([ + ( + "package-0".to_string(), + NpmPackageId::from_serialized("package-0@1.0.0").unwrap(), + ), + ( + "package-a".to_string(), + NpmPackageId::from_serialized("package-a@1.0.0_package-0@1.0.0").unwrap(), + ), + ( + "package-d".to_string(), + NpmPackageId::from_serialized("package-d@1.0.0_package-0@1.0.0_package-a@1.0.0__package-0@1.0.0") + .unwrap(), + ) + ]), + dist: Default::default(), + }, + NpmResolutionPackage { + pkg_id: NpmPackageId::from_serialized( + "package-d@1.0.0_package-0@1.0.0_package-a@1.0.0__package-0@1.0.0" + ) + .unwrap(), + copy_index: 0, + dependencies: HashMap::from([ + ( + "package-0".to_string(), + NpmPackageId::from_serialized("package-0@1.0.0").unwrap(), + ), + ( + "package-a".to_string(), + NpmPackageId::from_serialized("package-a@1.0.0_package-0@1.0.0").unwrap(), + ) + ]), + dist: Default::default(), + } + ] + ); + assert_eq!( + package_reqs, + vec![("package-0@1.0".to_string(), "package-0@1.0.0".to_string())] + ); + } + + #[tokio::test] + async fn peer_dep_resolved_then_resolved_deeper() { + let api = TestNpmRegistryApiInner::default(); + api.ensure_package_version("package-0", "1.0.0"); + api.ensure_package_version("package-1", "1.0.0"); + api.ensure_package_version("package-a", "1.0.0"); + api.ensure_package_version("package-b", "1.0.0"); + api.ensure_package_version("package-peer", "1.0.0"); + api.add_dependency(("package-0", "1.0.0"), ("package-a", "1")); + api.add_dependency(("package-0", "1.0.0"), ("package-1", "1")); + api.add_dependency(("package-1", "1.0.0"), ("package-a", "1")); + api.add_dependency(("package-a", "1.0.0"), ("package-b", "1")); + api.add_peer_dependency(("package-b", "1.0.0"), ("package-peer", "*")); + + let (packages, package_reqs) = run_resolver_and_get_output( + api, + vec!["npm:package-0@1.0", "npm:package-peer@1.0"], + ) + .await; + assert_eq!( + packages, + vec![ + NpmResolutionPackage { + pkg_id: NpmPackageId::from_serialized( + "package-0@1.0.0_package-peer@1.0.0" + ) + .unwrap(), + copy_index: 0, + dependencies: HashMap::from([ + ( + "package-1".to_string(), + NpmPackageId::from_serialized( + "package-1@1.0.0_package-peer@1.0.0" + ) + .unwrap(), + ), + ( + "package-a".to_string(), + NpmPackageId::from_serialized( + "package-a@1.0.0_package-peer@1.0.0" + ) + .unwrap(), + ) + ]), + dist: Default::default(), + }, + NpmResolutionPackage { + pkg_id: NpmPackageId::from_serialized( + "package-1@1.0.0_package-peer@1.0.0" + ) + .unwrap(), + copy_index: 0, + dependencies: HashMap::from([( + "package-a".to_string(), + NpmPackageId::from_serialized("package-a@1.0.0_package-peer@1.0.0") + .unwrap(), + )]), + dist: Default::default(), + }, + NpmResolutionPackage { + pkg_id: NpmPackageId::from_serialized( + "package-a@1.0.0_package-peer@1.0.0" + ) + .unwrap(), + copy_index: 0, + dependencies: HashMap::from([( + "package-b".to_string(), + NpmPackageId::from_serialized("package-b@1.0.0_package-peer@1.0.0") + .unwrap(), + )]), + dist: Default::default(), + }, + NpmResolutionPackage { + pkg_id: NpmPackageId::from_serialized( + "package-b@1.0.0_package-peer@1.0.0" + ) + .unwrap(), + copy_index: 0, + dependencies: HashMap::from([( + "package-peer".to_string(), + NpmPackageId::from_serialized("package-peer@1.0.0").unwrap(), + )]), + dist: Default::default(), + }, + NpmResolutionPackage { + pkg_id: NpmPackageId::from_serialized("package-peer@1.0.0").unwrap(), + copy_index: 0, + dependencies: Default::default(), + dist: Default::default(), + } + ] + ); + assert_eq!( + package_reqs, + vec![ + ( + "package-0@1.0".to_string(), + "package-0@1.0.0_package-peer@1.0.0".to_string() + ), + ( + "package-peer@1.0".to_string(), + "package-peer@1.0.0".to_string() + ) + ] + ); + } + async fn run_resolver_and_get_output( - api: TestNpmRegistryApi, + api: TestNpmRegistryApiInner, reqs: Vec<&str>, ) -> (Vec, Vec<(String, String)>) { let mut graph = Graph::default(); + let api = NpmRegistryApi::new_for_test(api); let mut resolver = GraphDependencyResolver::new(&mut graph, &api); for req in reqs { - let req = NpmPackageReference::from_str(req).unwrap().req; + let req = NpmPackageReqReference::from_str(req).unwrap().req; resolver .add_package_req(&req, &api.package_info(&req.name).await.unwrap()) .unwrap(); @@ -2748,14 +3631,43 @@ mod test { resolver.resolve_pending().await.unwrap(); let snapshot = graph.into_snapshot(&api).await.unwrap(); + + { + let new_snapshot = Graph::from_snapshot(snapshot.clone()) + .unwrap() + .into_snapshot(&api) + .await + .unwrap(); + assert_eq!( + snapshot, new_snapshot, + "recreated snapshot should be the same" + ); + // create one again from the new snapshot + let new_snapshot2 = Graph::from_snapshot(new_snapshot.clone()) + .unwrap() + .into_snapshot(&api) + .await + .unwrap(); + assert_eq!( + snapshot, new_snapshot2, + "second recreated snapshot should be the same" + ); + } + let mut packages = snapshot.all_packages(); - packages.sort_by(|a, b| a.id.cmp(&b.id)); + packages.sort_by(|a, b| a.pkg_id.cmp(&b.pkg_id)); let mut package_reqs = snapshot .package_reqs .into_iter() - .map(|(a, b)| (a.to_string(), b.as_serialized())) + .map(|(a, b)| { + ( + a.to_string(), + snapshot.root_packages.get(&b).unwrap().as_serialized(), + ) + }) .collect::>(); package_reqs.sort_by(|a, b| a.0.to_string().cmp(&b.0.to_string())); + (packages, package_reqs) } } diff --git a/cli/npm/resolution/mod.rs b/cli/npm/resolution/mod.rs index 990ad8d0652220..f43f3c5cb077b4 100644 --- a/cli/npm/resolution/mod.rs +++ b/cli/npm/resolution/mod.rs @@ -1,57 +1,66 @@ // Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. +use std::cmp::Ordering; +use std::collections::BTreeMap; use std::collections::HashMap; use std::collections::HashSet; +use std::sync::Arc; use deno_core::anyhow::Context; use deno_core::error::AnyError; -use deno_core::futures; +use deno_core::parking_lot::Mutex; use deno_core::parking_lot::RwLock; +use deno_graph::npm::NpmPackageNv; +use deno_graph::npm::NpmPackageNvReference; +use deno_graph::npm::NpmPackageReq; +use deno_graph::npm::NpmPackageReqReference; +use deno_graph::semver::Version; +use log::debug; use serde::Deserialize; use serde::Serialize; +use thiserror::Error; use crate::args::Lockfile; -use crate::semver::Version; +use crate::npm::resolution::common::LATEST_VERSION_REQ; +use self::common::resolve_best_package_version_and_info; use self::graph::GraphDependencyResolver; use self::snapshot::NpmPackagesPartitioned; -use super::cache::should_sync_download; use super::cache::NpmPackageCacheFolderId; use super::registry::NpmPackageVersionDistInfo; -use super::registry::RealNpmRegistryApi; -use super::NpmRegistryApi; +use super::registry::NpmRegistryApi; +mod common; mod graph; -mod reference; mod snapshot; -mod specifier; use graph::Graph; -pub use reference::NpmPackageReference; -pub use reference::NpmPackageReq; pub use snapshot::NpmResolutionSnapshot; -pub use specifier::resolve_graph_npm_info; -#[derive( - Debug, Clone, PartialOrd, Ord, PartialEq, Eq, Hash, Serialize, Deserialize, -)] +#[derive(Debug, Error)] +#[error("Invalid npm package id '{text}'. {message}")] +pub struct NpmPackageNodeIdDeserializationError { + message: String, + text: String, +} + +/// A resolved unique identifier for an npm package. This contains +/// the resolved name, version, and peer dependency resolution identifiers. +#[derive(Clone, PartialEq, Eq, Hash, Serialize, Deserialize)] pub struct NpmPackageId { - pub name: String, - pub version: Version, + pub nv: NpmPackageNv, pub peer_dependencies: Vec, } -impl NpmPackageId { - #[allow(unused)] - pub fn scope(&self) -> Option<&str> { - if self.name.starts_with('@') && self.name.contains('/') { - self.name.split('/').next() - } else { - None - } +// Custom debug implementation for more concise test output +impl std::fmt::Debug for NpmPackageId { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{}", self.as_serialized()) } +} +impl NpmPackageId { pub fn as_serialized(&self) -> String { self.as_serialized_with_level(0) } @@ -61,11 +70,11 @@ impl NpmPackageId { let mut result = format!( "{}@{}", if level == 0 { - self.name.to_string() + self.nv.name.to_string() } else { - self.name.replace('/', "+") + self.nv.name.replace('/', "+") }, - self.version + self.nv.version ); for peer in &self.peer_dependencies { // unfortunately we can't do something like `_3` when @@ -77,7 +86,9 @@ impl NpmPackageId { result } - pub fn from_serialized(id: &str) -> Result { + pub fn from_serialized( + id: &str, + ) -> Result { use monch::*; fn parse_name(input: &str) -> ParseResult<&str> { @@ -155,28 +166,40 @@ impl NpmPackageId { Ok(( input, NpmPackageId { - name, - version, + nv: NpmPackageNv { name, version }, peer_dependencies, }, )) } } - with_failure_handling(parse_id_at_level(0))(id) - .with_context(|| format!("Invalid npm package id '{id}'.")) + with_failure_handling(parse_id_at_level(0))(id).map_err(|err| { + NpmPackageNodeIdDeserializationError { + message: format!("{err:#}"), + text: id.to_string(), + } + }) } +} - pub fn display(&self) -> String { - // Don't implement std::fmt::Display because we don't - // want this to be used by accident in certain scenarios. - format!("{}@{}", self.name, self.version) +impl Ord for NpmPackageId { + fn cmp(&self, other: &Self) -> Ordering { + match self.nv.cmp(&other.nv) { + Ordering::Equal => self.peer_dependencies.cmp(&other.peer_dependencies), + ordering => ordering, + } } } -#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] +impl PartialOrd for NpmPackageId { + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.cmp(other)) + } +} + +#[derive(Clone, Serialize, Deserialize, PartialEq, Eq)] pub struct NpmResolutionPackage { - pub id: NpmPackageId, + pub pkg_id: NpmPackageId, /// The peer dependency resolution can differ for the same /// package (name and version) depending on where it is in /// the resolution tree. This copy index indicates which @@ -188,25 +211,43 @@ pub struct NpmResolutionPackage { pub dependencies: HashMap, } +impl std::fmt::Debug for NpmResolutionPackage { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + // custom debug implementation for deterministic output in the tests + f.debug_struct("NpmResolutionPackage") + .field("pkg_id", &self.pkg_id) + .field("copy_index", &self.copy_index) + .field("dist", &self.dist) + .field( + "dependencies", + &self.dependencies.iter().collect::>(), + ) + .finish() + } +} + impl NpmResolutionPackage { pub fn get_package_cache_folder_id(&self) -> NpmPackageCacheFolderId { NpmPackageCacheFolderId { - name: self.id.name.clone(), - version: self.id.version.clone(), + nv: self.pkg_id.nv.clone(), copy_index: self.copy_index, } } } -pub struct NpmResolution { - api: RealNpmRegistryApi, +#[derive(Clone)] +pub struct NpmResolution(Arc); + +struct NpmResolutionInner { + api: NpmRegistryApi, snapshot: RwLock, update_semaphore: tokio::sync::Semaphore, + maybe_lockfile: Option>>, } impl std::fmt::Debug for NpmResolution { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - let snapshot = self.snapshot.read(); + let snapshot = self.0.snapshot.read(); f.debug_struct("NpmResolution") .field("snapshot", &snapshot) .finish() @@ -215,127 +256,101 @@ impl std::fmt::Debug for NpmResolution { impl NpmResolution { pub fn new( - api: RealNpmRegistryApi, + api: NpmRegistryApi, initial_snapshot: Option, + maybe_lockfile: Option>>, ) -> Self { - Self { + Self(Arc::new(NpmResolutionInner { api, snapshot: RwLock::new(initial_snapshot.unwrap_or_default()), update_semaphore: tokio::sync::Semaphore::new(1), - } + maybe_lockfile, + })) } pub async fn add_package_reqs( &self, package_reqs: Vec, ) -> Result<(), AnyError> { - // only allow one thread in here at a time - let _permit = self.update_semaphore.acquire().await?; - let snapshot = self.snapshot.read().clone(); - - let snapshot = self - .add_package_reqs_to_snapshot(package_reqs, snapshot) - .await?; + let inner = &self.0; - *self.snapshot.write() = snapshot; + // only allow one thread in here at a time + let _permit = inner.update_semaphore.acquire().await?; + let snapshot = inner.snapshot.read().clone(); + + let snapshot = add_package_reqs_to_snapshot( + &inner.api, + package_reqs, + snapshot, + self.0.maybe_lockfile.clone(), + ) + .await?; + + *inner.snapshot.write() = snapshot; Ok(()) } pub async fn set_package_reqs( &self, - package_reqs: HashSet, + package_reqs: Vec, ) -> Result<(), AnyError> { + let inner = &self.0; // only allow one thread in here at a time - let _permit = self.update_semaphore.acquire().await?; - let snapshot = self.snapshot.read().clone(); + let _permit = inner.update_semaphore.acquire().await?; + let snapshot = inner.snapshot.read().clone(); + let reqs_set = package_reqs.iter().collect::>(); let has_removed_package = !snapshot .package_reqs .keys() - .all(|req| package_reqs.contains(req)); + .all(|req| reqs_set.contains(req)); // if any packages were removed, we need to completely recreate the npm resolution snapshot let snapshot = if has_removed_package { NpmResolutionSnapshot::default() } else { snapshot }; - let snapshot = self - .add_package_reqs_to_snapshot( - package_reqs.into_iter().collect(), - snapshot, - ) - .await?; + let snapshot = add_package_reqs_to_snapshot( + &inner.api, + package_reqs, + snapshot, + self.0.maybe_lockfile.clone(), + ) + .await?; - *self.snapshot.write() = snapshot; + *inner.snapshot.write() = snapshot; Ok(()) } - async fn add_package_reqs_to_snapshot( - &self, - package_reqs: Vec, - snapshot: NpmResolutionSnapshot, - ) -> Result { - // convert the snapshot to a traversable graph - let mut graph = Graph::from_snapshot(snapshot); - - // go over the top level package names first, then down the - // tree one level at a time through all the branches - let mut unresolved_tasks = Vec::with_capacity(package_reqs.len()); - let mut resolving_package_names = - HashSet::with_capacity(package_reqs.len()); - for package_req in &package_reqs { - if graph.has_package_req(package_req) { - // skip analyzing this package, as there's already a matching top level package - continue; - } - if !resolving_package_names.insert(package_req.name.clone()) { - continue; // already resolving - } - - // cache the package info up front in parallel - if should_sync_download() { - // for deterministic test output - self.api.package_info(&package_req.name).await?; - } else { - let api = self.api.clone(); - let package_name = package_req.name.clone(); - unresolved_tasks.push(tokio::task::spawn(async move { - // This is ok to call because api will internally cache - // the package information in memory. - api.package_info(&package_name).await - })); - }; - } - - for result in futures::future::join_all(unresolved_tasks).await { - result??; // surface the first error - } + pub async fn resolve_pending(&self) -> Result<(), AnyError> { + let inner = &self.0; + // only allow one thread in here at a time + let _permit = inner.update_semaphore.acquire().await?; + let snapshot = inner.snapshot.read().clone(); - let mut resolver = GraphDependencyResolver::new(&mut graph, &self.api); + let snapshot = add_package_reqs_to_snapshot( + &inner.api, + Vec::new(), + snapshot, + self.0.maybe_lockfile.clone(), + ) + .await?; - // These package_reqs should already be sorted in the order they should - // be resolved in. - for package_req in package_reqs { - // avoid loading the info if this is already in the graph - if !resolver.has_package_req(&package_req) { - let info = self.api.package_info(&package_req.name).await?; - resolver.add_package_req(&package_req, &info)?; - } - } + *inner.snapshot.write() = snapshot; - resolver.resolve_pending().await?; - - let result = graph.into_snapshot(&self.api).await; - self.api.clear_memory_cache(); - result + Ok(()) } - pub fn resolve_package_from_id( + pub fn pkg_req_ref_to_nv_ref( &self, - id: &NpmPackageId, - ) -> Option { - self.snapshot.read().package_from_id(id).cloned() + req_ref: NpmPackageReqReference, + ) -> Result { + let node_id = self.resolve_pkg_id_from_pkg_req(&req_ref.req)?; + Ok(NpmPackageNvReference { + nv: node_id.nv, + sub_path: req_ref.sub_path, + }) } pub fn resolve_package_cache_folder_id_from_id( @@ -343,6 +358,7 @@ impl NpmResolution { id: &NpmPackageId, ) -> Option { self + .0 .snapshot .read() .package_from_id(id) @@ -355,6 +371,7 @@ impl NpmResolution { referrer: &NpmPackageCacheFolderId, ) -> Result { self + .0 .snapshot .read() .resolve_package_from_package(name, referrer) @@ -362,35 +379,101 @@ impl NpmResolution { } /// Resolve a node package from a deno module. - pub fn resolve_package_from_deno_module( + pub fn resolve_pkg_id_from_pkg_req( &self, - package: &NpmPackageReq, - ) -> Result { + req: &NpmPackageReq, + ) -> Result { self + .0 .snapshot .read() - .resolve_package_from_deno_module(package) - .cloned() + .resolve_pkg_from_pkg_req(req) + .map(|pkg| pkg.pkg_id.clone()) + } + + pub fn resolve_pkg_id_from_deno_module( + &self, + id: &NpmPackageNv, + ) -> Result { + self + .0 + .snapshot + .read() + .resolve_package_from_deno_module(id) + .map(|pkg| pkg.pkg_id.clone()) + } + + /// Resolves a package requirement for deno graph. This should only be + /// called by deno_graph's NpmResolver or for resolving packages in + /// a package.json + pub fn resolve_package_req_as_pending( + &self, + pkg_req: &NpmPackageReq, + ) -> Result { + let inner = &self.0; + // we should always have this because it should have been cached before here + let package_info = + inner.api.get_cached_package_info(&pkg_req.name).unwrap(); + + let mut snapshot = inner.snapshot.write(); + let version_req = + pkg_req.version_req.as_ref().unwrap_or(&*LATEST_VERSION_REQ); + let version_and_info = + match snapshot.packages_by_name.get(&package_info.name) { + Some(existing_versions) => resolve_best_package_version_and_info( + version_req, + &package_info, + existing_versions.iter().map(|p| &p.nv.version), + )?, + None => resolve_best_package_version_and_info( + version_req, + &package_info, + Vec::new().iter(), + )?, + }; + let id = NpmPackageNv { + name: package_info.name.to_string(), + version: version_and_info.version, + }; + debug!( + "Resolved {}@{} to {}", + pkg_req.name, + version_req.version_text(), + id.to_string(), + ); + snapshot.package_reqs.insert(pkg_req.clone(), id.clone()); + let packages_with_name = snapshot + .packages_by_name + .entry(package_info.name.clone()) + .or_default(); + if !packages_with_name.iter().any(|p| p.nv == id) { + packages_with_name.push(NpmPackageId { + nv: id.clone(), + peer_dependencies: Vec::new(), + }); + } + snapshot.pending_unresolved_packages.push(id.clone()); + Ok(id) } pub fn all_packages_partitioned(&self) -> NpmPackagesPartitioned { - self.snapshot.read().all_packages_partitioned() + self.0.snapshot.read().all_packages_partitioned() } pub fn has_packages(&self) -> bool { - !self.snapshot.read().packages.is_empty() + !self.0.snapshot.read().packages.is_empty() } pub fn snapshot(&self) -> NpmResolutionSnapshot { - self.snapshot.read().clone() + self.0.snapshot.read().clone() } pub fn lock(&self, lockfile: &mut Lockfile) -> Result<(), AnyError> { - let snapshot = self.snapshot.read(); - for (package_req, package_id) in snapshot.package_reqs.iter() { + let snapshot = self.0.snapshot.read(); + for (package_req, nv) in snapshot.package_reqs.iter() { lockfile.insert_npm_specifier( package_req.to_string(), - package_id.as_serialized(), + snapshot.root_packages.get(nv).unwrap().as_serialized(), ); } for package in snapshot.all_packages() { @@ -400,43 +483,148 @@ impl NpmResolution { } } +async fn add_package_reqs_to_snapshot( + api: &NpmRegistryApi, + package_reqs: Vec, + snapshot: NpmResolutionSnapshot, + maybe_lockfile: Option>>, +) -> Result { + if snapshot.pending_unresolved_packages.is_empty() + && package_reqs + .iter() + .all(|req| snapshot.package_reqs.contains_key(req)) + { + return Ok(snapshot); // already up to date + } + + // convert the snapshot to a traversable graph + let mut graph = Graph::from_snapshot(snapshot).with_context(|| { + deno_core::anyhow::anyhow!( + "Failed creating npm state. Try recreating your lockfile." + ) + })?; + let pending_unresolved = graph.take_pending_unresolved(); + + // avoid loading the info if this is already in the graph + let package_reqs = package_reqs + .into_iter() + .filter(|r| !graph.has_package_req(r)) + .collect::>(); + let pending_unresolved = pending_unresolved + .into_iter() + .filter(|p| !graph.has_root_package(p)) + .collect::>(); + + // cache the packages in parallel + api + .cache_in_parallel( + package_reqs + .iter() + .map(|req| req.name.clone()) + .chain(pending_unresolved.iter().map(|id| id.name.clone())) + .collect::>() + .into_iter() + .collect::>(), + ) + .await?; + + // go over the top level package names first (npm package reqs and pending unresolved), + // then down the tree one level at a time through all the branches + let mut resolver = GraphDependencyResolver::new(&mut graph, api); + + // The package reqs and ids should already be sorted + // in the order they should be resolved in. + for package_req in package_reqs { + let info = api.package_info(&package_req.name).await?; + resolver.add_package_req(&package_req, &info)?; + } + + for pkg_id in pending_unresolved { + let info = api.package_info(&pkg_id.name).await?; + resolver.add_root_package(&pkg_id, &info)?; + } + + resolver.resolve_pending().await?; + + let result = graph.into_snapshot(api).await; + api.clear_memory_cache(); + + if let Some(lockfile_mutex) = maybe_lockfile { + let mut lockfile = lockfile_mutex.lock(); + match result { + Ok(snapshot) => { + for (package_req, nv) in snapshot.package_reqs.iter() { + lockfile.insert_npm_specifier( + package_req.to_string(), + snapshot.root_packages.get(nv).unwrap().as_serialized(), + ); + } + for package in snapshot.all_packages() { + lockfile.check_or_insert_npm_package(package.into())?; + } + Ok(snapshot) + } + Err(err) => Err(err), + } + } else { + result + } +} + #[cfg(test)] -mod tests { - use super::*; +mod test { + use deno_graph::npm::NpmPackageNv; + use deno_graph::semver::Version; + + use super::NpmPackageId; #[test] fn serialize_npm_package_id() { let id = NpmPackageId { - name: "pkg-a".to_string(), - version: Version::parse_from_npm("1.2.3").unwrap(), + nv: NpmPackageNv { + name: "pkg-a".to_string(), + version: Version::parse_from_npm("1.2.3").unwrap(), + }, peer_dependencies: vec![ NpmPackageId { - name: "pkg-b".to_string(), - version: Version::parse_from_npm("3.2.1").unwrap(), + nv: NpmPackageNv { + name: "pkg-b".to_string(), + version: Version::parse_from_npm("3.2.1").unwrap(), + }, peer_dependencies: vec![ NpmPackageId { - name: "pkg-c".to_string(), - version: Version::parse_from_npm("1.3.2").unwrap(), + nv: NpmPackageNv { + name: "pkg-c".to_string(), + version: Version::parse_from_npm("1.3.2").unwrap(), + }, peer_dependencies: vec![], }, NpmPackageId { - name: "pkg-d".to_string(), - version: Version::parse_from_npm("2.3.4").unwrap(), + nv: NpmPackageNv { + name: "pkg-d".to_string(), + version: Version::parse_from_npm("2.3.4").unwrap(), + }, peer_dependencies: vec![], }, ], }, NpmPackageId { - name: "pkg-e".to_string(), - version: Version::parse_from_npm("2.3.1").unwrap(), - peer_dependencies: vec![NpmPackageId { - name: "pkg-f".to_string(), + nv: NpmPackageNv { + name: "pkg-e".to_string(), version: Version::parse_from_npm("2.3.1").unwrap(), + }, + peer_dependencies: vec![NpmPackageId { + nv: NpmPackageNv { + name: "pkg-f".to_string(), + version: Version::parse_from_npm("2.3.1").unwrap(), + }, peer_dependencies: vec![], }], }, ], }; + + // this shouldn't change because it's used in the lockfile let serialized = id.as_serialized(); assert_eq!(serialized, "pkg-a@1.2.3_pkg-b@3.2.1__pkg-c@1.3.2__pkg-d@2.3.4_pkg-e@2.3.1__pkg-f@2.3.1"); assert_eq!(NpmPackageId::from_serialized(&serialized).unwrap(), id); diff --git a/cli/npm/resolution/reference.rs b/cli/npm/resolution/reference.rs deleted file mode 100644 index 2d34bcc34d668d..00000000000000 --- a/cli/npm/resolution/reference.rs +++ /dev/null @@ -1,298 +0,0 @@ -// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. - -use deno_ast::ModuleSpecifier; -use deno_core::anyhow::bail; -use deno_core::anyhow::Context; -use deno_core::error::generic_error; -use deno_core::error::AnyError; -use serde::Deserialize; -use serde::Serialize; - -use crate::semver::VersionReq; - -/// A reference to an npm package's name, version constraint, and potential sub path. -/// -/// This contains all the information found in an npm specifier. -#[derive(Clone, Debug, Default, PartialEq, Eq)] -pub struct NpmPackageReference { - pub req: NpmPackageReq, - pub sub_path: Option, -} - -impl NpmPackageReference { - pub fn from_specifier( - specifier: &ModuleSpecifier, - ) -> Result { - Self::from_str(specifier.as_str()) - } - - pub fn from_str(specifier: &str) -> Result { - let original_text = specifier; - let specifier = match specifier.strip_prefix("npm:") { - Some(s) => { - // Strip leading slash, which might come from import map - s.strip_prefix('/').unwrap_or(s) - } - None => { - // don't allocate a string here and instead use a static string - // because this is hit a lot when a url is not an npm specifier - return Err(generic_error("Not an npm specifier")); - } - }; - let parts = specifier.split('/').collect::>(); - let name_part_len = if specifier.starts_with('@') { 2 } else { 1 }; - if parts.len() < name_part_len { - return Err(generic_error(format!("Not a valid package: {specifier}"))); - } - let name_parts = &parts[0..name_part_len]; - let req = match NpmPackageReq::parse_from_parts(name_parts) { - Ok(pkg_req) => pkg_req, - Err(err) => { - return Err(generic_error(format!( - "Invalid npm specifier '{original_text}'. {err:#}" - ))) - } - }; - let sub_path = if parts.len() == name_parts.len() { - None - } else { - let sub_path = parts[name_part_len..].join("/"); - if sub_path.is_empty() { - None - } else { - Some(sub_path) - } - }; - - if let Some(sub_path) = &sub_path { - if let Some(at_index) = sub_path.rfind('@') { - let (new_sub_path, version) = sub_path.split_at(at_index); - let msg = format!( - "Invalid package specifier 'npm:{req}/{sub_path}'. Did you mean to write 'npm:{req}{version}/{new_sub_path}'?" - ); - return Err(generic_error(msg)); - } - } - - Ok(NpmPackageReference { req, sub_path }) - } -} - -impl std::fmt::Display for NpmPackageReference { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - if let Some(sub_path) = &self.sub_path { - write!(f, "npm:{}/{}", self.req, sub_path) - } else { - write!(f, "npm:{}", self.req) - } - } -} - -/// The name and version constraint component of an `NpmPackageReference`. -#[derive( - Clone, Debug, Default, PartialEq, Eq, Hash, Serialize, Deserialize, -)] -pub struct NpmPackageReq { - pub name: String, - pub version_req: Option, -} - -impl std::fmt::Display for NpmPackageReq { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match &self.version_req { - Some(req) => write!(f, "{}@{}", self.name, req), - None => write!(f, "{}", self.name), - } - } -} - -impl NpmPackageReq { - pub fn from_str(text: &str) -> Result { - let parts = text.split('/').collect::>(); - match NpmPackageReq::parse_from_parts(&parts) { - Ok(req) => Ok(req), - Err(err) => { - let msg = format!("Invalid npm package requirement '{text}'. {err:#}"); - Err(generic_error(msg)) - } - } - } - - fn parse_from_parts(name_parts: &[&str]) -> Result { - assert!(!name_parts.is_empty()); // this should be provided the result of a string split - let last_name_part = &name_parts[name_parts.len() - 1]; - let (name, version_req) = if let Some(at_index) = last_name_part.rfind('@') - { - let version = &last_name_part[at_index + 1..]; - let last_name_part = &last_name_part[..at_index]; - let version_req = VersionReq::parse_from_specifier(version) - .with_context(|| "Invalid version requirement.")?; - let name = if name_parts.len() == 1 { - last_name_part.to_string() - } else { - format!("{}/{}", name_parts[0], last_name_part) - }; - (name, Some(version_req)) - } else { - (name_parts.join("/"), None) - }; - if name.is_empty() { - bail!("Did not contain a package name.") - } - Ok(Self { name, version_req }) - } -} - -#[cfg(test)] -mod tests { - use pretty_assertions::assert_eq; - - use super::*; - - #[test] - fn parse_npm_package_ref() { - assert_eq!( - NpmPackageReference::from_str("npm:@package/test").unwrap(), - NpmPackageReference { - req: NpmPackageReq { - name: "@package/test".to_string(), - version_req: None, - }, - sub_path: None, - } - ); - - assert_eq!( - NpmPackageReference::from_str("npm:@package/test@1").unwrap(), - NpmPackageReference { - req: NpmPackageReq { - name: "@package/test".to_string(), - version_req: Some(VersionReq::parse_from_specifier("1").unwrap()), - }, - sub_path: None, - } - ); - - assert_eq!( - NpmPackageReference::from_str("npm:@package/test@~1.1/sub_path").unwrap(), - NpmPackageReference { - req: NpmPackageReq { - name: "@package/test".to_string(), - version_req: Some(VersionReq::parse_from_specifier("~1.1").unwrap()), - }, - sub_path: Some("sub_path".to_string()), - } - ); - - assert_eq!( - NpmPackageReference::from_str("npm:@package/test/sub_path").unwrap(), - NpmPackageReference { - req: NpmPackageReq { - name: "@package/test".to_string(), - version_req: None, - }, - sub_path: Some("sub_path".to_string()), - } - ); - - assert_eq!( - NpmPackageReference::from_str("npm:test").unwrap(), - NpmPackageReference { - req: NpmPackageReq { - name: "test".to_string(), - version_req: None, - }, - sub_path: None, - } - ); - - assert_eq!( - NpmPackageReference::from_str("npm:test@^1.2").unwrap(), - NpmPackageReference { - req: NpmPackageReq { - name: "test".to_string(), - version_req: Some(VersionReq::parse_from_specifier("^1.2").unwrap()), - }, - sub_path: None, - } - ); - - assert_eq!( - NpmPackageReference::from_str("npm:test@~1.1/sub_path").unwrap(), - NpmPackageReference { - req: NpmPackageReq { - name: "test".to_string(), - version_req: Some(VersionReq::parse_from_specifier("~1.1").unwrap()), - }, - sub_path: Some("sub_path".to_string()), - } - ); - - assert_eq!( - NpmPackageReference::from_str("npm:@package/test/sub_path").unwrap(), - NpmPackageReference { - req: NpmPackageReq { - name: "@package/test".to_string(), - version_req: None, - }, - sub_path: Some("sub_path".to_string()), - } - ); - - assert_eq!( - NpmPackageReference::from_str("npm:@package") - .err() - .unwrap() - .to_string(), - "Not a valid package: @package" - ); - - // should parse leading slash - assert_eq!( - NpmPackageReference::from_str("npm:/@package/test/sub_path").unwrap(), - NpmPackageReference { - req: NpmPackageReq { - name: "@package/test".to_string(), - version_req: None, - }, - sub_path: Some("sub_path".to_string()), - } - ); - assert_eq!( - NpmPackageReference::from_str("npm:/test").unwrap(), - NpmPackageReference { - req: NpmPackageReq { - name: "test".to_string(), - version_req: None, - }, - sub_path: None, - } - ); - assert_eq!( - NpmPackageReference::from_str("npm:/test/").unwrap(), - NpmPackageReference { - req: NpmPackageReq { - name: "test".to_string(), - version_req: None, - }, - sub_path: None, - } - ); - - // should error for no name - assert_eq!( - NpmPackageReference::from_str("npm:/") - .err() - .unwrap() - .to_string(), - "Invalid npm specifier 'npm:/'. Did not contain a package name." - ); - assert_eq!( - NpmPackageReference::from_str("npm://test") - .err() - .unwrap() - .to_string(), - "Invalid npm specifier 'npm://test'. Did not contain a package name." - ); - } -} diff --git a/cli/npm/resolution/snapshot.rs b/cli/npm/resolution/snapshot.rs index 934320a1daa521..e986294eca772f 100644 --- a/cli/npm/resolution/snapshot.rs +++ b/cli/npm/resolution/snapshot.rs @@ -1,5 +1,6 @@ // Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. +use std::collections::BTreeMap; use std::collections::HashMap; use std::collections::HashSet; use std::sync::Arc; @@ -8,21 +9,19 @@ use deno_core::anyhow::anyhow; use deno_core::anyhow::bail; use deno_core::anyhow::Context; use deno_core::error::AnyError; -use deno_core::futures; use deno_core::parking_lot::Mutex; +use deno_graph::npm::NpmPackageNv; +use deno_graph::npm::NpmPackageReq; +use deno_graph::semver::VersionReq; use serde::Deserialize; use serde::Serialize; use crate::args::Lockfile; -use crate::npm::cache::should_sync_download; use crate::npm::cache::NpmPackageCacheFolderId; use crate::npm::registry::NpmPackageVersionDistInfo; use crate::npm::registry::NpmRegistryApi; -use crate::npm::registry::RealNpmRegistryApi; -use crate::semver::VersionReq; use super::NpmPackageId; -use super::NpmPackageReq; use super::NpmResolutionPackage; /// Packages partitioned by if they are "copy" packages or not. @@ -44,13 +43,48 @@ impl NpmPackagesPartitioned { } } -#[derive(Debug, Clone, Default, Serialize, Deserialize)] +#[derive(Clone, Default, Serialize, Deserialize, PartialEq, Eq)] pub struct NpmResolutionSnapshot { + /// The unique package requirements map to a single npm package name and version. #[serde(with = "map_to_vec")] - pub(super) package_reqs: HashMap, + pub(super) package_reqs: HashMap, + // Each root level npm package name and version maps to an exact npm package node id. + #[serde(with = "map_to_vec")] + pub(super) root_packages: HashMap, pub(super) packages_by_name: HashMap>, #[serde(with = "map_to_vec")] pub(super) packages: HashMap, + /// Ordered list based on resolution of packages whose dependencies + /// have not yet been resolved + pub(super) pending_unresolved_packages: Vec, +} + +impl std::fmt::Debug for NpmResolutionSnapshot { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + // do a custom debug implementation that creates deterministic output for the tests + f.debug_struct("NpmResolutionSnapshot") + .field( + "package_reqs", + &self.package_reqs.iter().collect::>(), + ) + .field( + "root_packages", + &self.root_packages.iter().collect::>(), + ) + .field( + "packages_by_name", + &self.packages_by_name.iter().collect::>(), + ) + .field( + "packages", + &self.packages.iter().collect::>(), + ) + .field( + "pending_unresolved_packages", + &self.pending_unresolved_packages, + ) + .finish() + } } // This is done so the maps with non-string keys get serialized and deserialized as vectors. @@ -93,25 +127,30 @@ mod map_to_vec { } impl NpmResolutionSnapshot { - /// Resolve a node package from a deno module. - pub fn resolve_package_from_deno_module( + /// Resolve a package from a package requirement. + pub fn resolve_pkg_from_pkg_req( &self, req: &NpmPackageReq, ) -> Result<&NpmResolutionPackage, AnyError> { match self.package_reqs.get(req) { - Some(id) => Ok(self.packages.get(id).unwrap()), + Some(id) => self.resolve_package_from_deno_module(id), None => bail!("could not find npm package directory for '{}'", req), } } + /// Resolve a package from a deno module. + pub fn resolve_package_from_deno_module( + &self, + id: &NpmPackageNv, + ) -> Result<&NpmResolutionPackage, AnyError> { + match self.root_packages.get(id) { + Some(id) => Ok(self.packages.get(id).unwrap()), + None => bail!("could not find npm package directory for '{}'", id), + } + } + pub fn top_level_packages(&self) -> Vec { - self - .package_reqs - .values() - .cloned() - .collect::>() - .into_iter() - .collect::>() + self.root_packages.values().cloned().collect::>() } pub fn package_from_id( @@ -129,13 +168,13 @@ impl NpmResolutionSnapshot { // todo(dsherret): do we need an additional hashmap to get this quickly? let referrer_package = self .packages_by_name - .get(&referrer.name) + .get(&referrer.nv.name) .and_then(|packages| { packages .iter() - .filter(|p| p.version == referrer.version) - .filter_map(|id| { - let package = self.packages.get(id)?; + .filter(|p| p.nv.version == referrer.nv.version) + .filter_map(|node_id| { + let package = self.packages.get(node_id)?; if package.copy_index == referrer.copy_index { Some(package) } else { @@ -153,7 +192,7 @@ impl NpmResolutionSnapshot { return Ok(self.packages.get(id).unwrap()); } - if referrer_package.id.name == name { + if referrer_package.pkg_id.nv.name == name { return Ok(referrer_package); } @@ -202,15 +241,15 @@ impl NpmResolutionSnapshot { // todo(dsherret): this is not exactly correct because some ids // will be better than others due to peer dependencies let mut maybe_best_id: Option<&NpmPackageId> = None; - if let Some(ids) = self.packages_by_name.get(name) { - for id in ids { - if version_req.matches(&id.version) { + if let Some(node_ids) = self.packages_by_name.get(name) { + for node_id in node_ids.iter() { + if version_req.matches(&node_id.nv.version) { let is_best_version = maybe_best_id .as_ref() - .map(|best_id| best_id.version.cmp(&id.version).is_lt()) + .map(|best_id| best_id.nv.version.cmp(&node_id.nv.version).is_lt()) .unwrap_or(true); if is_best_version { - maybe_best_id = Some(id); + maybe_best_id = Some(node_id); } } } @@ -220,9 +259,10 @@ impl NpmResolutionSnapshot { pub async fn from_lockfile( lockfile: Arc>, - api: &RealNpmRegistryApi, + api: &NpmRegistryApi, ) -> Result { - let mut package_reqs: HashMap; + let mut package_reqs: HashMap; + let mut root_packages: HashMap; let mut packages_by_name: HashMap>; let mut packages: HashMap; let mut copy_index_resolver: SnapshotPackageCopyIndexResolver; @@ -233,6 +273,8 @@ impl NpmResolutionSnapshot { // pre-allocate collections package_reqs = HashMap::with_capacity(lockfile.content.npm.specifiers.len()); + root_packages = + HashMap::with_capacity(lockfile.content.npm.specifiers.len()); let packages_len = lockfile.content.npm.packages.len(); packages = HashMap::with_capacity(packages_len); packages_by_name = HashMap::with_capacity(packages_len); // close enough @@ -245,7 +287,8 @@ impl NpmResolutionSnapshot { let package_req = NpmPackageReq::from_str(key) .with_context(|| format!("Unable to parse npm specifier: {key}"))?; let package_id = NpmPackageId::from_serialized(value)?; - package_reqs.insert(package_req, package_id.clone()); + package_reqs.insert(package_req, package_id.nv.clone()); + root_packages.insert(package_id.nv.clone(), package_id.clone()); verify_ids.insert(package_id.clone()); } @@ -257,7 +300,7 @@ impl NpmResolutionSnapshot { let mut dependencies = HashMap::default(); packages_by_name - .entry(package_id.name.to_string()) + .entry(package_id.nv.name.to_string()) .or_default() .push(package_id.clone()); @@ -268,7 +311,7 @@ impl NpmResolutionSnapshot { } let package = NpmResolutionPackage { - id: package_id.clone(), + pkg_id: package_id.clone(), copy_index: copy_index_resolver.resolve(&package_id), // temporary dummy value dist: NpmPackageVersionDistInfo::default(), @@ -288,40 +331,20 @@ impl NpmResolutionSnapshot { } } - let mut unresolved_tasks = Vec::with_capacity(packages_by_name.len()); - - // cache the package names in parallel in the registry api - // unless synchronous download should occur - if should_sync_download() { - let mut package_names = packages_by_name.keys().collect::>(); - package_names.sort(); - for package_name in package_names { - api.package_info(package_name).await?; - } - } else { - for package_name in packages_by_name.keys() { - let package_name = package_name.clone(); - let api = api.clone(); - unresolved_tasks.push(tokio::task::spawn(async move { - api.package_info(&package_name).await?; - Result::<_, AnyError>::Ok(()) - })); - } - } - for result in futures::future::join_all(unresolved_tasks).await { - result??; - } + api + .cache_in_parallel(packages_by_name.keys().cloned().collect()) + .await?; // ensure the dist is set for each package for package in packages.values_mut() { // this will read from the memory cache now let version_info = match api - .package_version_info(&package.id.name, &package.id.version) + .package_version_info(&package.pkg_id.nv) .await? { Some(version_info) => version_info, None => { - bail!("could not find '{}' specified in the lockfile. Maybe try again with --reload", package.id.display()); + bail!("could not find '{}' specified in the lockfile. Maybe try again with --reload", package.pkg_id.nv); } }; package.dist = version_info.dist; @@ -329,15 +352,17 @@ impl NpmResolutionSnapshot { Ok(Self { package_reqs, + root_packages, packages_by_name, packages, + pending_unresolved_packages: Default::default(), }) } } pub struct SnapshotPackageCopyIndexResolver { packages_to_copy_index: HashMap, - package_name_version_to_copy_count: HashMap<(String, String), usize>, + package_name_version_to_copy_count: HashMap, } impl SnapshotPackageCopyIndexResolver { @@ -358,9 +383,9 @@ impl SnapshotPackageCopyIndexResolver { packages_to_copy_index.reserve(capacity - packages_to_copy_index.len()); } - for (id, index) in &packages_to_copy_index { + for (node_id, index) in &packages_to_copy_index { let entry = package_name_version_to_copy_count - .entry((id.name.to_string(), id.version.to_string())) + .entry(node_id.nv.clone()) .or_insert(0); if *entry < *index { *entry = *index; @@ -372,18 +397,18 @@ impl SnapshotPackageCopyIndexResolver { } } - pub fn resolve(&mut self, id: &NpmPackageId) -> usize { - if let Some(index) = self.packages_to_copy_index.get(id) { + pub fn resolve(&mut self, node_id: &NpmPackageId) -> usize { + if let Some(index) = self.packages_to_copy_index.get(node_id) { *index } else { let index = *self .package_name_version_to_copy_count - .entry((id.name.to_string(), id.version.to_string())) + .entry(node_id.nv.clone()) .and_modify(|count| { *count += 1; }) .or_insert(0); - self.packages_to_copy_index.insert(id.clone(), index); + self.packages_to_copy_index.insert(node_id.clone(), index); index } } diff --git a/cli/npm/resolution/specifier.rs b/cli/npm/resolution/specifier.rs deleted file mode 100644 index 78d313412b4c22..00000000000000 --- a/cli/npm/resolution/specifier.rs +++ /dev/null @@ -1,733 +0,0 @@ -// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. - -use std::cmp::Ordering; -use std::collections::HashMap; -use std::collections::HashSet; -use std::collections::VecDeque; - -use deno_ast::ModuleSpecifier; -use deno_graph::ModuleGraph; -use deno_graph::Resolved; - -use crate::semver::VersionReq; - -use super::NpmPackageReference; -use super::NpmPackageReq; - -pub struct GraphNpmInfo { - /// The order of these package requirements is the order they - /// should be resolved in. - pub package_reqs: Vec, - /// Gets if the graph had a built-in node specifier (ex. `node:fs`). - pub has_node_builtin_specifier: bool, -} - -/// Resolves npm specific information from the graph. -/// -/// This function will analyze the module graph for parent-most folder -/// specifiers of all modules, then group npm specifiers together as found in -/// those descendant modules and return them in the order found spreading out -/// from the root of the graph. -/// -/// For example, given the following module graph: -/// -/// file:///dev/local_module_a/mod.ts -/// ├── npm:package-a@1 -/// ├─┬ https://deno.land/x/module_d/mod.ts -/// │ └─┬ https://deno.land/x/module_d/other.ts -/// │ └── npm:package-a@3 -/// ├─┬ file:///dev/local_module_a/other.ts -/// │ └── npm:package-b@2 -/// ├─┬ file:///dev/local_module_b/mod.ts -/// │ └── npm:package-b@2 -/// └─┬ https://deno.land/x/module_a/mod.ts -/// ├── npm:package-a@4 -/// ├── npm:package-c@5 -/// ├─┬ https://deno.land/x/module_c/sub_folder/mod.ts -/// │ ├── https://deno.land/x/module_c/mod.ts -/// │ ├─┬ https://deno.land/x/module_d/sub_folder/mod.ts -/// │ │ └── npm:package-other@2 -/// │ └── npm:package-c@5 -/// └── https://deno.land/x/module_b/mod.ts -/// -/// The graph above would be grouped down to the topmost specifier folders like -/// so and npm specifiers under each path would be resolved for that group -/// prioritizing file specifiers and sorting by end folder name alphabetically: -/// -/// file:///dev/local_module_a/ -/// ├── file:///dev/local_module_b/ -/// ├─┬ https://deno.land/x/module_a/ -/// │ ├── https://deno.land/x/module_b/ -/// │ └─┬ https://deno.land/x/module_c/ -/// │ └── https://deno.land/x/module_d/ -/// └── https://deno.land/x/module_d/ -/// -/// Then it would resolve the npm specifiers in each of those groups according -/// to that tree going by tree depth. -pub fn resolve_graph_npm_info(graph: &ModuleGraph) -> GraphNpmInfo { - fn collect_specifiers<'a>( - graph: &'a ModuleGraph, - module: &'a deno_graph::Module, - ) -> Vec<&'a ModuleSpecifier> { - let mut specifiers = Vec::with_capacity(module.dependencies.len() * 2 + 1); - let maybe_types = module.maybe_types_dependency.as_ref().map(|(_, r)| r); - if let Some(Resolved::Ok { specifier, .. }) = &maybe_types { - specifiers.push(specifier); - } - for dep in module.dependencies.values() { - #[allow(clippy::manual_flatten)] - for resolved in [&dep.maybe_code, &dep.maybe_type] { - if let Resolved::Ok { specifier, .. } = resolved { - specifiers.push(specifier); - } - } - } - - // flatten any data urls into this list of specifiers - for i in (0..specifiers.len()).rev() { - if specifiers[i].scheme() == "data" { - let data_specifier = specifiers.swap_remove(i); - if let Some(module) = graph.get(data_specifier) { - specifiers.extend(collect_specifiers(graph, module)); - } - } - } - - specifiers - } - - fn analyze_module( - module: &deno_graph::Module, - graph: &ModuleGraph, - specifier_graph: &mut SpecifierTree, - seen: &mut HashSet, - has_node_builtin_specifier: &mut bool, - ) { - if !seen.insert(module.specifier.clone()) { - return; // already visited - } - - let parent_specifier = get_folder_path_specifier(&module.specifier); - let leaf = specifier_graph.get_leaf(&parent_specifier); - - let specifiers = collect_specifiers(graph, module); - - // fill this leaf's information - for specifier in &specifiers { - if let Ok(npm_ref) = NpmPackageReference::from_specifier(specifier) { - leaf.reqs.insert(npm_ref.req); - } else if !specifier.as_str().starts_with(parent_specifier.as_str()) { - leaf - .dependencies - .insert(get_folder_path_specifier(specifier)); - } - - if !*has_node_builtin_specifier && specifier.scheme() == "node" { - *has_node_builtin_specifier = true; - } - } - - // now visit all the dependencies - for specifier in &specifiers { - if let Some(module) = graph.get(specifier) { - analyze_module( - module, - graph, - specifier_graph, - seen, - has_node_builtin_specifier, - ); - } - } - } - - let root_specifiers = graph - .roots - .iter() - .map(|url| graph.resolve(url)) - .collect::>(); - let mut seen = HashSet::new(); - let mut specifier_graph = SpecifierTree::default(); - let mut has_node_builtin_specifier = false; - for root in &root_specifiers { - if let Some(module) = graph.get(root) { - analyze_module( - module, - graph, - &mut specifier_graph, - &mut seen, - &mut has_node_builtin_specifier, - ); - } - } - - let mut seen = HashSet::new(); - let mut pending_specifiers = VecDeque::new(); - let mut result = Vec::new(); - - for specifier in &root_specifiers { - match NpmPackageReference::from_specifier(specifier) { - Ok(npm_ref) => result.push(npm_ref.req), - Err(_) => { - pending_specifiers.push_back(get_folder_path_specifier(specifier)) - } - } - } - - while let Some(specifier) = pending_specifiers.pop_front() { - let leaf = specifier_graph.get_leaf(&specifier); - if !seen.insert(leaf.specifier.clone()) { - continue; // already seen - } - - let reqs = std::mem::take(&mut leaf.reqs); - let mut reqs = reqs.into_iter().collect::>(); - reqs.sort_by(cmp_package_req); - result.extend(reqs); - - let mut deps = std::mem::take(&mut leaf.dependencies) - .into_iter() - .collect::>(); - deps.sort_by(cmp_folder_specifiers); - - for dep in deps { - pending_specifiers.push_back(dep); - } - } - - GraphNpmInfo { - has_node_builtin_specifier, - package_reqs: result, - } -} - -fn get_folder_path_specifier(specifier: &ModuleSpecifier) -> ModuleSpecifier { - let mut specifier = specifier.clone(); - specifier.set_query(None); - specifier.set_fragment(None); - if !specifier.path().ends_with('/') { - // remove the last path part, but keep the trailing slash - let mut path_parts = specifier.path().split('/').collect::>(); - let path_parts_len = path_parts.len(); // make borrow checker happy for some reason - if path_parts_len > 0 { - path_parts[path_parts_len - 1] = ""; - } - specifier.set_path(&path_parts.join("/")); - } - specifier -} - -#[derive(Debug)] -enum SpecifierTreeNode { - Parent(SpecifierTreeParentNode), - Leaf(SpecifierTreeLeafNode), -} - -impl SpecifierTreeNode { - pub fn mut_to_leaf(&mut self) { - if let SpecifierTreeNode::Parent(node) = self { - let node = std::mem::replace( - node, - SpecifierTreeParentNode { - specifier: node.specifier.clone(), - dependencies: Default::default(), - }, - ); - *self = SpecifierTreeNode::Leaf(node.into_leaf()); - } - } -} - -#[derive(Debug)] -struct SpecifierTreeParentNode { - specifier: ModuleSpecifier, - dependencies: HashMap, -} - -impl SpecifierTreeParentNode { - pub fn into_leaf(self) -> SpecifierTreeLeafNode { - fn fill_new_leaf( - deps: HashMap, - new_leaf: &mut SpecifierTreeLeafNode, - ) { - for node in deps.into_values() { - match node { - SpecifierTreeNode::Parent(node) => { - fill_new_leaf(node.dependencies, new_leaf) - } - SpecifierTreeNode::Leaf(leaf) => { - for dep in leaf.dependencies { - // don't insert if the dependency is found within the new leaf - if !dep.as_str().starts_with(new_leaf.specifier.as_str()) { - new_leaf.dependencies.insert(dep); - } - } - new_leaf.reqs.extend(leaf.reqs); - } - } - } - } - - let mut new_leaf = SpecifierTreeLeafNode { - specifier: self.specifier, - reqs: Default::default(), - dependencies: Default::default(), - }; - fill_new_leaf(self.dependencies, &mut new_leaf); - new_leaf - } -} - -#[derive(Debug)] -struct SpecifierTreeLeafNode { - specifier: ModuleSpecifier, - reqs: HashSet, - dependencies: HashSet, -} - -#[derive(Default)] -struct SpecifierTree { - root_nodes: HashMap, -} - -impl SpecifierTree { - pub fn get_leaf( - &mut self, - specifier: &ModuleSpecifier, - ) -> &mut SpecifierTreeLeafNode { - let root_specifier = { - let mut specifier = specifier.clone(); - specifier.set_path(""); - specifier - }; - let root_node = self - .root_nodes - .entry(root_specifier.clone()) - .or_insert_with(|| { - SpecifierTreeNode::Parent(SpecifierTreeParentNode { - specifier: root_specifier.clone(), - dependencies: Default::default(), - }) - }); - let mut current_node = root_node; - if !matches!(specifier.path(), "" | "/") { - let mut current_parts = Vec::new(); - let path = specifier.path(); - for part in path[1..path.len() - 1].split('/') { - current_parts.push(part); - match current_node { - SpecifierTreeNode::Leaf(leaf) => return leaf, - SpecifierTreeNode::Parent(node) => { - current_node = node - .dependencies - .entry(part.to_string()) - .or_insert_with(|| { - SpecifierTreeNode::Parent(SpecifierTreeParentNode { - specifier: { - let mut specifier = root_specifier.clone(); - specifier.set_path(¤t_parts.join("/")); - specifier - }, - dependencies: Default::default(), - }) - }); - } - } - } - } - current_node.mut_to_leaf(); - match current_node { - SpecifierTreeNode::Leaf(leaf) => leaf, - _ => unreachable!(), - } - } -} - -// prefer file: specifiers, then sort by folder name, then by specifier -fn cmp_folder_specifiers(a: &ModuleSpecifier, b: &ModuleSpecifier) -> Ordering { - fn order_folder_name(path_a: &str, path_b: &str) -> Option { - let path_a = path_a.trim_end_matches('/'); - let path_b = path_b.trim_end_matches('/'); - match path_a.rfind('/') { - Some(a_index) => match path_b.rfind('/') { - Some(b_index) => match path_a[a_index..].cmp(&path_b[b_index..]) { - Ordering::Equal => None, - ordering => Some(ordering), - }, - None => None, - }, - None => None, - } - } - - fn order_specifiers(a: &ModuleSpecifier, b: &ModuleSpecifier) -> Ordering { - match order_folder_name(a.path(), b.path()) { - Some(ordering) => ordering, - None => a.as_str().cmp(b.as_str()), // fallback to just comparing the entire url - } - } - - if a.scheme() == "file" { - if b.scheme() == "file" { - order_specifiers(a, b) - } else { - Ordering::Less - } - } else if b.scheme() == "file" { - Ordering::Greater - } else { - order_specifiers(a, b) - } -} - -// Sort the package requirements alphabetically then the version -// requirement in a way that will lead to the least number of -// duplicate packages (so sort None last since it's `*`), but -// mostly to create some determinism around how these are resolved. -fn cmp_package_req(a: &NpmPackageReq, b: &NpmPackageReq) -> Ordering { - fn cmp_specifier_version_req(a: &VersionReq, b: &VersionReq) -> Ordering { - match a.tag() { - Some(a_tag) => match b.tag() { - Some(b_tag) => b_tag.cmp(a_tag), // sort descending - None => Ordering::Less, // prefer a since tag - }, - None => { - match b.tag() { - Some(_) => Ordering::Greater, // prefer b since tag - None => { - // At this point, just sort by text descending. - // We could maybe be a bit smarter here in the future. - b.to_string().cmp(&a.to_string()) - } - } - } - } - } - - match a.name.cmp(&b.name) { - Ordering::Equal => { - match &b.version_req { - Some(b_req) => { - match &a.version_req { - Some(a_req) => cmp_specifier_version_req(a_req, b_req), - None => Ordering::Greater, // prefer b, since a is * - } - } - None => Ordering::Less, // prefer a, since b is * - } - } - ordering => ordering, - } -} - -#[cfg(test)] -mod tests { - use pretty_assertions::assert_eq; - - use super::*; - - #[test] - fn sorting_folder_specifiers() { - fn cmp(a: &str, b: &str) -> Ordering { - let a = ModuleSpecifier::parse(a).unwrap(); - let b = ModuleSpecifier::parse(b).unwrap(); - cmp_folder_specifiers(&a, &b) - } - - // prefer file urls - assert_eq!( - cmp("file:///test/", "https://deno.land/x/module/"), - Ordering::Less - ); - assert_eq!( - cmp("https://deno.land/x/module/", "file:///test/"), - Ordering::Greater - ); - - // sort by folder name - assert_eq!( - cmp( - "https://deno.land/x/module_a/", - "https://deno.land/x/module_b/" - ), - Ordering::Less - ); - assert_eq!( - cmp( - "https://deno.land/x/module_b/", - "https://deno.land/x/module_a/" - ), - Ordering::Greater - ); - assert_eq!( - cmp( - "https://deno.land/x/module_a/", - "https://deno.land/std/module_b/" - ), - Ordering::Less - ); - assert_eq!( - cmp( - "https://deno.land/std/module_b/", - "https://deno.land/x/module_a/" - ), - Ordering::Greater - ); - - // by specifier, since folder names match - assert_eq!( - cmp( - "https://deno.land/std/module_a/", - "https://deno.land/x/module_a/" - ), - Ordering::Less - ); - } - - #[test] - fn sorting_package_reqs() { - fn cmp_req(a: &str, b: &str) -> Ordering { - let a = NpmPackageReq::from_str(a).unwrap(); - let b = NpmPackageReq::from_str(b).unwrap(); - cmp_package_req(&a, &b) - } - - // sort by name - assert_eq!(cmp_req("a", "b@1"), Ordering::Less); - assert_eq!(cmp_req("b@1", "a"), Ordering::Greater); - // prefer non-wildcard - assert_eq!(cmp_req("a", "a@1"), Ordering::Greater); - assert_eq!(cmp_req("a@1", "a"), Ordering::Less); - // prefer tag - assert_eq!(cmp_req("a@tag", "a"), Ordering::Less); - assert_eq!(cmp_req("a", "a@tag"), Ordering::Greater); - // sort tag descending - assert_eq!(cmp_req("a@latest-v1", "a@latest-v2"), Ordering::Greater); - assert_eq!(cmp_req("a@latest-v2", "a@latest-v1"), Ordering::Less); - // sort version req descending - assert_eq!(cmp_req("a@1", "a@2"), Ordering::Greater); - assert_eq!(cmp_req("a@2", "a@1"), Ordering::Less); - } - - #[test] - fn test_get_folder_path_specifier() { - fn get(a: &str) -> String { - get_folder_path_specifier(&ModuleSpecifier::parse(a).unwrap()).to_string() - } - - assert_eq!(get("https://deno.land/"), "https://deno.land/"); - assert_eq!(get("https://deno.land"), "https://deno.land/"); - assert_eq!(get("https://deno.land/test"), "https://deno.land/"); - assert_eq!(get("https://deno.land/test/"), "https://deno.land/test/"); - assert_eq!( - get("https://deno.land/test/other"), - "https://deno.land/test/" - ); - assert_eq!( - get("https://deno.land/test/other/"), - "https://deno.land/test/other/" - ); - assert_eq!( - get("https://deno.land/test/other/test?test#other"), - "https://deno.land/test/other/" - ); - } - - #[tokio::test] - async fn test_resolve_npm_package_reqs() { - let mut loader = deno_graph::source::MemoryLoader::new( - vec![ - ( - "file:///dev/local_module_a/mod.ts".to_string(), - deno_graph::source::Source::Module { - specifier: "file:///dev/local_module_a/mod.ts".to_string(), - content: concat!( - "import 'https://deno.land/x/module_d/mod.ts';", - "import 'file:///dev/local_module_a/other.ts';", - "import 'file:///dev/local_module_b/mod.ts';", - "import 'https://deno.land/x/module_a/mod.ts';", - "import 'npm:package-a@local_module_a';", - "import 'https://deno.land/x/module_e/';", - ) - .to_string(), - maybe_headers: None, - }, - ), - ( - "file:///dev/local_module_a/other.ts".to_string(), - deno_graph::source::Source::Module { - specifier: "file:///dev/local_module_a/other.ts".to_string(), - content: "import 'npm:package-b@local_module_a';".to_string(), - maybe_headers: None, - }, - ), - ( - "file:///dev/local_module_b/mod.ts".to_string(), - deno_graph::source::Source::Module { - specifier: "file:///dev/local_module_b/mod.ts".to_string(), - content: concat!( - "export * from 'npm:package-b@local_module_b';", - "import * as test from 'data:application/typescript,export%20*%20from%20%22npm:package-data%40local_module_b%22;';", - ).to_string(), - maybe_headers: None, - }, - ), - ( - "https://deno.land/x/module_d/mod.ts".to_string(), - deno_graph::source::Source::Module { - specifier: "https://deno.land/x/module_d/mod.ts".to_string(), - content: concat!( - "import './other.ts';", - "import 'npm:package-a@module_d';", - ) - .to_string(), - maybe_headers: None, - }, - ), - ( - "https://deno.land/x/module_d/other.ts".to_string(), - deno_graph::source::Source::Module { - specifier: "https://deno.land/x/module_d/other.ts".to_string(), - content: "import 'npm:package-c@module_d'".to_string(), - maybe_headers: None, - }, - ), - ( - "https://deno.land/x/module_a/mod.ts".to_string(), - deno_graph::source::Source::Module { - specifier: "https://deno.land/x/module_a/mod.ts".to_string(), - content: concat!( - "import 'npm:package-a@module_a';", - "import 'npm:package-b@module_a';", - "import '../module_c/sub/sub/mod.ts';", - "import '../module_b/mod.ts';", - ) - .to_string(), - maybe_headers: None, - }, - ), - ( - "https://deno.land/x/module_b/mod.ts".to_string(), - deno_graph::source::Source::Module { - specifier: "https://deno.land/x/module_b/mod.ts".to_string(), - content: "import 'npm:package-a@module_b'".to_string(), - maybe_headers: None, - }, - ), - ( - "https://deno.land/x/module_c/sub/sub/mod.ts".to_string(), - deno_graph::source::Source::Module { - specifier: "https://deno.land/x/module_c/sub/sub/mod.ts" - .to_string(), - content: concat!( - "import 'npm:package-a@module_c';", - "import '../../mod.ts';", - ) - .to_string(), - maybe_headers: None, - }, - ), - ( - "https://deno.land/x/module_c/mod.ts".to_string(), - deno_graph::source::Source::Module { - specifier: "https://deno.land/x/module_c/mod.ts".to_string(), - content: concat!( - "import 'npm:package-b@module_c';", - "import '../module_d/sub_folder/mod.ts';", - ) - .to_string(), - maybe_headers: None, - }, - ), - ( - "https://deno.land/x/module_d/sub_folder/mod.ts".to_string(), - deno_graph::source::Source::Module { - specifier: "https://deno.land/x/module_d/sub_folder/mod.ts" - .to_string(), - content: "import 'npm:package-b@module_d';".to_string(), - maybe_headers: None, - }, - ), - ( - // ensure a module at a directory is treated as being at a directory - "https://deno.land/x/module_e/".to_string(), - deno_graph::source::Source::Module { - specifier: "https://deno.land/x/module_e/" - .to_string(), - content: "import 'npm:package-a@module_e';".to_string(), - maybe_headers: Some(vec![( - "content-type".to_string(), - "application/javascript".to_string(), - )]), - }, - ), - // redirect module - ( - "https://deno.land/x/module_redirect/mod.ts".to_string(), - deno_graph::source::Source::Module { - specifier: "https://deno.land/x/module_redirect@0.0.1/mod.ts".to_string(), - content: concat!( - "import 'npm:package-a@module_redirect';", - // try another redirect here - "import 'https://deno.land/x/module_redirect/other.ts';", - ).to_string(), - maybe_headers: None, - } - ), - ( - "https://deno.land/x/module_redirect/other.ts".to_string(), - deno_graph::source::Source::Module { - specifier: "https://deno.land/x/module_redirect@0.0.1/other.ts".to_string(), - content: "import 'npm:package-b@module_redirect';".to_string(), - maybe_headers: None, - } - ), - ], - Vec::new(), - ); - let analyzer = deno_graph::CapturingModuleAnalyzer::default(); - let graph = deno_graph::create_graph( - vec![ - ModuleSpecifier::parse("file:///dev/local_module_a/mod.ts").unwrap(), - // test redirect at root - ModuleSpecifier::parse("https://deno.land/x/module_redirect/mod.ts") - .unwrap(), - ], - &mut loader, - deno_graph::GraphOptions { - is_dynamic: false, - imports: None, - resolver: None, - module_analyzer: Some(&analyzer), - reporter: None, - }, - ) - .await; - let reqs = resolve_graph_npm_info(&graph) - .package_reqs - .into_iter() - .map(|r| r.to_string()) - .collect::>(); - - assert_eq!( - reqs, - vec![ - "package-a@local_module_a", - "package-b@local_module_a", - "package-a@module_redirect", - "package-b@module_redirect", - "package-b@local_module_b", - "package-data@local_module_b", - "package-a@module_a", - "package-b@module_a", - "package-a@module_d", - "package-b@module_d", - "package-c@module_d", - "package-a@module_e", - "package-a@module_b", - "package-a@module_c", - "package-b@module_c", - ] - ); - } -} diff --git a/cli/npm/resolvers/common.rs b/cli/npm/resolvers/common.rs index 7fe9c3fa49b236..3f1c2a7047e251 100644 --- a/cli/npm/resolvers/common.rs +++ b/cli/npm/resolvers/common.rs @@ -1,30 +1,31 @@ // Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. -use std::collections::HashSet; use std::io::ErrorKind; use std::path::Path; use std::path::PathBuf; +use async_trait::async_trait; use deno_ast::ModuleSpecifier; use deno_core::error::AnyError; use deno_core::futures; -use deno_core::futures::future::BoxFuture; use deno_core::url::Url; use deno_runtime::deno_node::NodePermissions; use deno_runtime::deno_node::NodeResolutionMode; -use crate::args::Lockfile; use crate::npm::cache::should_sync_download; -use crate::npm::resolution::NpmResolutionSnapshot; use crate::npm::NpmCache; use crate::npm::NpmPackageId; -use crate::npm::NpmPackageReq; use crate::npm::NpmResolutionPackage; -pub trait InnerNpmPackageResolver: Send + Sync { +/// Part of the resolution that interacts with the file system. +#[async_trait] +pub trait NpmPackageFsResolver: Send + Sync { + /// Specifier for the root directory. + fn root_dir_url(&self) -> &Url; + fn resolve_package_folder_from_deno_module( &self, - pkg_req: &NpmPackageReq, + id: &NpmPackageId, ) -> Result; fn resolve_package_folder_from_package( @@ -41,29 +42,13 @@ pub trait InnerNpmPackageResolver: Send + Sync { fn package_size(&self, package_id: &NpmPackageId) -> Result; - fn has_packages(&self) -> bool; - - fn add_package_reqs( - &self, - packages: Vec, - ) -> BoxFuture<'static, Result<(), AnyError>>; - - fn set_package_reqs( - &self, - packages: HashSet, - ) -> BoxFuture<'static, Result<(), AnyError>>; - - fn cache_packages(&self) -> BoxFuture<'static, Result<(), AnyError>>; + async fn cache_packages(&self) -> Result<(), AnyError>; fn ensure_read_permission( &self, permissions: &mut dyn NodePermissions, path: &Path, ) -> Result<(), AnyError>; - - fn snapshot(&self) -> NpmResolutionSnapshot; - - fn lock(&self, lockfile: &mut Lockfile) -> Result<(), AnyError>; } /// Caches all the packages in parallel. @@ -76,7 +61,7 @@ pub async fn cache_packages( if sync_download { // we're running the tests not with --quiet // and we want the output to be deterministic - packages.sort_by(|a, b| a.id.cmp(&b.id)); + packages.sort_by(|a, b| a.pkg_id.cmp(&b.pkg_id)); } let mut handles = Vec::with_capacity(packages.len()); @@ -86,11 +71,7 @@ pub async fn cache_packages( let registry_url = registry_url.clone(); let handle = tokio::task::spawn(async move { cache - .ensure_package( - (package.id.name.as_str(), &package.id.version), - &package.dist, - ®istry_url, - ) + .ensure_package(&package.pkg_id.nv, &package.dist, ®istry_url) .await }); if sync_download { diff --git a/cli/npm/resolvers/global.rs b/cli/npm/resolvers/global.rs index 41a1329ec22f94..87ad92675a5450 100644 --- a/cli/npm/resolvers/global.rs +++ b/cli/npm/resolvers/global.rs @@ -2,51 +2,41 @@ //! Code for global npm cache resolution. -use std::collections::HashSet; use std::path::Path; use std::path::PathBuf; -use std::sync::Arc; +use async_trait::async_trait; use deno_ast::ModuleSpecifier; use deno_core::error::AnyError; -use deno_core::futures::future::BoxFuture; -use deno_core::futures::FutureExt; use deno_core::url::Url; use deno_runtime::deno_node::NodePermissions; use deno_runtime::deno_node::NodeResolutionMode; -use crate::args::Lockfile; use crate::npm::cache::NpmPackageCacheFolderId; use crate::npm::resolution::NpmResolution; -use crate::npm::resolution::NpmResolutionSnapshot; use crate::npm::resolvers::common::cache_packages; use crate::npm::NpmCache; use crate::npm::NpmPackageId; -use crate::npm::NpmPackageReq; use crate::npm::NpmResolutionPackage; -use crate::npm::RealNpmRegistryApi; use super::common::ensure_registry_read_permission; use super::common::types_package_name; -use super::common::InnerNpmPackageResolver; +use super::common::NpmPackageFsResolver; /// Resolves packages from the global npm cache. #[derive(Debug, Clone)] pub struct GlobalNpmPackageResolver { cache: NpmCache, - resolution: Arc, + resolution: NpmResolution, registry_url: Url, } impl GlobalNpmPackageResolver { pub fn new( cache: NpmCache, - api: RealNpmRegistryApi, - initial_snapshot: Option, + registry_url: Url, + resolution: NpmResolution, ) -> Self { - let registry_url = api.base_url().to_owned(); - let resolution = Arc::new(NpmResolution::new(api, initial_snapshot)); - Self { cache, resolution, @@ -76,13 +66,17 @@ impl GlobalNpmPackageResolver { } } -impl InnerNpmPackageResolver for GlobalNpmPackageResolver { +#[async_trait] +impl NpmPackageFsResolver for GlobalNpmPackageResolver { + fn root_dir_url(&self) -> &Url { + self.cache.root_dir_url() + } + fn resolve_package_folder_from_deno_module( &self, - pkg_req: &NpmPackageReq, + id: &NpmPackageId, ) -> Result { - let pkg = self.resolution.resolve_package_from_deno_module(pkg_req)?; - Ok(self.package_folder(&pkg.id)) + Ok(self.package_folder(id)) } fn resolve_package_folder_from_package( @@ -107,7 +101,7 @@ impl InnerNpmPackageResolver for GlobalNpmPackageResolver { .resolution .resolve_package_from_package(name, &referrer_pkg_id)? }; - Ok(self.package_folder(&pkg.id)) + Ok(self.package_folder(&pkg.pkg_id)) } fn resolve_package_folder_from_specifier( @@ -125,34 +119,13 @@ impl InnerNpmPackageResolver for GlobalNpmPackageResolver { ) } - fn package_size(&self, package_id: &NpmPackageId) -> Result { - let package_folder = self.package_folder(package_id); + fn package_size(&self, id: &NpmPackageId) -> Result { + let package_folder = self.package_folder(id); Ok(crate::util::fs::dir_size(&package_folder)?) } - fn has_packages(&self) -> bool { - self.resolution.has_packages() - } - - fn add_package_reqs( - &self, - packages: Vec, - ) -> BoxFuture<'static, Result<(), AnyError>> { - let resolver = self.clone(); - async move { resolver.resolution.add_package_reqs(packages).await }.boxed() - } - - fn set_package_reqs( - &self, - packages: HashSet, - ) -> BoxFuture<'static, Result<(), AnyError>> { - let resolver = self.clone(); - async move { resolver.resolution.set_package_reqs(packages).await }.boxed() - } - - fn cache_packages(&self) -> BoxFuture<'static, Result<(), AnyError>> { - let resolver = self.clone(); - async move { cache_packages_in_resolver(&resolver).await }.boxed() + async fn cache_packages(&self) -> Result<(), AnyError> { + cache_packages_in_resolver(self).await } fn ensure_read_permission( @@ -163,14 +136,6 @@ impl InnerNpmPackageResolver for GlobalNpmPackageResolver { let registry_path = self.cache.registry_folder(&self.registry_url); ensure_registry_read_permission(permissions, ®istry_path, path) } - - fn snapshot(&self) -> NpmResolutionSnapshot { - self.resolution.snapshot() - } - - fn lock(&self, lockfile: &mut Lockfile) -> Result<(), AnyError> { - self.resolution.lock(lockfile) - } } async fn cache_packages_in_resolver( diff --git a/cli/npm/resolvers/local.rs b/cli/npm/resolvers/local.rs index 7c1f38b471ec75..bf5b8529c4cde3 100644 --- a/cli/npm/resolvers/local.rs +++ b/cli/npm/resolvers/local.rs @@ -8,15 +8,13 @@ use std::collections::VecDeque; use std::fs; use std::path::Path; use std::path::PathBuf; -use std::sync::Arc; use crate::util::fs::symlink_dir; +use async_trait::async_trait; use deno_ast::ModuleSpecifier; use deno_core::anyhow::bail; use deno_core::anyhow::Context; use deno_core::error::AnyError; -use deno_core::futures::future::BoxFuture; -use deno_core::futures::FutureExt; use deno_core::url::Url; use deno_runtime::deno_core::futures; use deno_runtime::deno_node::NodePermissions; @@ -24,7 +22,6 @@ use deno_runtime::deno_node::NodeResolutionMode; use deno_runtime::deno_node::PackageJson; use tokio::task::JoinHandle; -use crate::args::Lockfile; use crate::npm::cache::mixed_case_package_name_encode; use crate::npm::cache::should_sync_download; use crate::npm::cache::NpmPackageCacheFolderId; @@ -32,45 +29,37 @@ use crate::npm::resolution::NpmResolution; use crate::npm::resolution::NpmResolutionSnapshot; use crate::npm::NpmCache; use crate::npm::NpmPackageId; -use crate::npm::NpmPackageReq; -use crate::npm::NpmResolutionPackage; -use crate::npm::RealNpmRegistryApi; use crate::util::fs::copy_dir_recursive; use crate::util::fs::hard_link_dir_recursive; use super::common::ensure_registry_read_permission; use super::common::types_package_name; -use super::common::InnerNpmPackageResolver; +use super::common::NpmPackageFsResolver; /// Resolver that creates a local node_modules directory /// and resolves packages from it. #[derive(Debug, Clone)] pub struct LocalNpmPackageResolver { cache: NpmCache, - resolution: Arc, + resolution: NpmResolution, registry_url: Url, root_node_modules_path: PathBuf, - root_node_modules_specifier: ModuleSpecifier, + root_node_modules_url: Url, } impl LocalNpmPackageResolver { pub fn new( cache: NpmCache, - api: RealNpmRegistryApi, + registry_url: Url, node_modules_folder: PathBuf, - initial_snapshot: Option, + resolution: NpmResolution, ) -> Self { - let registry_url = api.base_url().to_owned(); - let resolution = Arc::new(NpmResolution::new(api, initial_snapshot)); - Self { cache, resolution, registry_url, - root_node_modules_specifier: ModuleSpecifier::from_directory_path( - &node_modules_folder, - ) - .unwrap(), + root_node_modules_url: Url::from_directory_path(&node_modules_folder) + .unwrap(), root_node_modules_path: node_modules_folder, } } @@ -101,8 +90,7 @@ impl LocalNpmPackageResolver { &self, specifier: &ModuleSpecifier, ) -> Option { - let relative_url = - self.root_node_modules_specifier.make_relative(specifier)?; + let relative_url = self.root_node_modules_url.make_relative(specifier)?; if relative_url.starts_with("../") { return None; } @@ -112,41 +100,38 @@ impl LocalNpmPackageResolver { fn get_package_id_folder( &self, - package_id: &NpmPackageId, + id: &NpmPackageId, ) -> Result { - match self.resolution.resolve_package_from_id(package_id) { - Some(package) => Ok(self.get_package_id_folder_from_package(&package)), + match self.resolution.resolve_package_cache_folder_id_from_id(id) { + // package is stored at: + // node_modules/.deno//node_modules/ + Some(cache_folder_id) => Ok( + self + .root_node_modules_path + .join(".deno") + .join(get_package_folder_id_folder_name(&cache_folder_id)) + .join("node_modules") + .join(&cache_folder_id.nv.name), + ), None => bail!( "Could not find package information for '{}'", - package_id.as_serialized() + id.as_serialized() ), } } +} - fn get_package_id_folder_from_package( - &self, - package: &NpmResolutionPackage, - ) -> PathBuf { - // package is stored at: - // node_modules/.deno//node_modules/ - self - .root_node_modules_path - .join(".deno") - .join(get_package_folder_id_folder_name( - &package.get_package_cache_folder_id(), - )) - .join("node_modules") - .join(&package.id.name) +#[async_trait] +impl NpmPackageFsResolver for LocalNpmPackageResolver { + fn root_dir_url(&self) -> &Url { + &self.root_node_modules_url } -} -impl InnerNpmPackageResolver for LocalNpmPackageResolver { fn resolve_package_folder_from_deno_module( &self, - pkg_req: &NpmPackageReq, + node_id: &NpmPackageId, ) -> Result { - let package = self.resolution.resolve_package_from_deno_module(pkg_req)?; - Ok(self.get_package_id_folder_from_package(&package)) + self.get_package_id_folder(node_id) } fn resolve_package_folder_from_package( @@ -203,47 +188,15 @@ impl InnerNpmPackageResolver for LocalNpmPackageResolver { Ok(package_root_path) } - fn package_size(&self, package_id: &NpmPackageId) -> Result { - let package_folder_path = self.get_package_id_folder(package_id)?; + fn package_size(&self, id: &NpmPackageId) -> Result { + let package_folder_path = self.get_package_id_folder(id)?; Ok(crate::util::fs::dir_size(&package_folder_path)?) } - fn has_packages(&self) -> bool { - self.resolution.has_packages() - } - - fn add_package_reqs( - &self, - packages: Vec, - ) -> BoxFuture<'static, Result<(), AnyError>> { - let resolver = self.clone(); - async move { - resolver.resolution.add_package_reqs(packages).await?; - Ok(()) - } - .boxed() - } - - fn set_package_reqs( - &self, - packages: HashSet, - ) -> BoxFuture<'static, Result<(), AnyError>> { - let resolver = self.clone(); - async move { - resolver.resolution.set_package_reqs(packages).await?; - Ok(()) - } - .boxed() - } - - fn cache_packages(&self) -> BoxFuture<'static, Result<(), AnyError>> { - let resolver = self.clone(); - async move { - sync_resolver_with_fs(&resolver).await?; - Ok(()) - } - .boxed() + async fn cache_packages(&self) -> Result<(), AnyError> { + sync_resolver_with_fs(self).await?; + Ok(()) } fn ensure_read_permission( @@ -257,14 +210,6 @@ impl InnerNpmPackageResolver for LocalNpmPackageResolver { path, ) } - - fn snapshot(&self) -> NpmResolutionSnapshot { - self.resolution.snapshot() - } - - fn lock(&self, lockfile: &mut Lockfile) -> Result<(), AnyError> { - self.resolution.lock(lockfile) - } } async fn sync_resolver_with_fs( @@ -300,7 +245,9 @@ async fn sync_resolution_with_fs( if sync_download { // we're running the tests not with --quiet // and we want the output to be deterministic - package_partitions.packages.sort_by(|a, b| a.id.cmp(&b.id)); + package_partitions + .packages + .sort_by(|a, b| a.pkg_id.cmp(&b.pkg_id)); } let mut handles: Vec>> = Vec::with_capacity(package_partitions.packages.len()); @@ -311,7 +258,7 @@ async fn sync_resolution_with_fs( let initialized_file = folder_path.join(".initialized"); if !cache .cache_setting() - .should_use_for_npm_package(&package.id.name) + .should_use_for_npm_package(&package.pkg_id.nv.name) || !initialized_file.exists() { let cache = cache.clone(); @@ -319,20 +266,15 @@ async fn sync_resolution_with_fs( let package = package.clone(); let handle = tokio::task::spawn(async move { cache - .ensure_package( - (&package.id.name, &package.id.version), - &package.dist, - ®istry_url, - ) + .ensure_package(&package.pkg_id.nv, &package.dist, ®istry_url) .await?; let sub_node_modules = folder_path.join("node_modules"); let package_path = - join_package_name(&sub_node_modules, &package.id.name); + join_package_name(&sub_node_modules, &package.pkg_id.nv.name); fs::create_dir_all(&package_path) .with_context(|| format!("Creating '{}'", folder_path.display()))?; let cache_folder = cache.package_folder_for_name_and_version( - &package.id.name, - &package.id.version, + &package.pkg_id.nv, ®istry_url, ); // for now copy, but in the future consider hard linking @@ -362,7 +304,8 @@ async fn sync_resolution_with_fs( let initialized_file = destination_path.join(".initialized"); if !initialized_file.exists() { let sub_node_modules = destination_path.join("node_modules"); - let package_path = join_package_name(&sub_node_modules, &package.id.name); + let package_path = + join_package_name(&sub_node_modules, &package.pkg_id.nv.name); fs::create_dir_all(&package_path).with_context(|| { format!("Creating '{}'", destination_path.display()) })?; @@ -372,7 +315,7 @@ async fn sync_resolution_with_fs( &package_cache_folder_id.with_no_count(), )) .join("node_modules"), - &package.id.name, + &package.pkg_id.nv.name, ); hard_link_dir_recursive(&source_path, &package_path)?; // write out a file that indicates this folder has been initialized @@ -403,7 +346,7 @@ async fn sync_resolution_with_fs( &deno_local_registry_dir .join(dep_folder_name) .join("node_modules"), - &dep_id.name, + &dep_id.nv.name, ); symlink_package_dir( &dep_folder_path, @@ -424,22 +367,22 @@ async fn sync_resolution_with_fs( .into_iter() .map(|id| (id, true)), ); - while let Some((package_id, is_top_level)) = pending_packages.pop_front() { - let root_folder_name = if found_names.insert(package_id.name.clone()) { - package_id.name.clone() + while let Some((id, is_top_level)) = pending_packages.pop_front() { + let root_folder_name = if found_names.insert(id.nv.name.clone()) { + id.nv.name.clone() } else if is_top_level { - package_id.display() + id.nv.to_string() } else { continue; // skip, already handled }; - let package = snapshot.package_from_id(&package_id).unwrap(); + let package = snapshot.package_from_id(&id).unwrap(); let local_registry_package_path = join_package_name( &deno_local_registry_dir .join(get_package_folder_id_folder_name( &package.get_package_cache_folder_id(), )) .join("node_modules"), - &package_id.name, + &id.nv.name, ); symlink_package_dir( @@ -454,18 +397,21 @@ async fn sync_resolution_with_fs( Ok(()) } -fn get_package_folder_id_folder_name(id: &NpmPackageCacheFolderId) -> String { - let copy_str = if id.copy_index == 0 { +fn get_package_folder_id_folder_name( + folder_id: &NpmPackageCacheFolderId, +) -> String { + let copy_str = if folder_id.copy_index == 0 { "".to_string() } else { - format!("_{}", id.copy_index) + format!("_{}", folder_id.copy_index) }; - let name = if id.name.to_lowercase() == id.name { - Cow::Borrowed(&id.name) + let nv = &folder_id.nv; + let name = if nv.name.to_lowercase() == nv.name { + Cow::Borrowed(&nv.name) } else { - Cow::Owned(format!("_{}", mixed_case_package_name_encode(&id.name))) + Cow::Owned(format!("_{}", mixed_case_package_name_encode(&nv.name))) }; - format!("{}@{}{}", name, id.version, copy_str).replace('/', "+") + format!("{}@{}{}", name, nv.version, copy_str).replace('/', "+") } fn symlink_package_dir( diff --git a/cli/npm/resolvers/mod.rs b/cli/npm/resolvers/mod.rs index 9ea14061ecbc3e..0027698c0ab56e 100644 --- a/cli/npm/resolvers/mod.rs +++ b/cli/npm/resolvers/mod.rs @@ -7,19 +7,18 @@ mod local; use deno_ast::ModuleSpecifier; use deno_core::anyhow::bail; use deno_core::anyhow::Context; -use deno_core::error::custom_error; use deno_core::error::AnyError; use deno_core::parking_lot::Mutex; use deno_core::serde_json; +use deno_graph::npm::NpmPackageNv; +use deno_graph::npm::NpmPackageReq; use deno_runtime::deno_node::NodePermissions; use deno_runtime::deno_node::NodeResolutionMode; use deno_runtime::deno_node::PathClean; use deno_runtime::deno_node::RequireNpmResolver; use global::GlobalNpmPackageResolver; -use once_cell::sync::Lazy; use serde::Deserialize; use serde::Serialize; -use std::collections::HashSet; use std::path::Path; use std::path::PathBuf; use std::sync::Arc; @@ -27,53 +26,27 @@ use std::sync::Arc; use crate::args::Lockfile; use crate::util::fs::canonicalize_path_maybe_not_exists; -use self::common::InnerNpmPackageResolver; +use self::common::NpmPackageFsResolver; use self::local::LocalNpmPackageResolver; +use super::resolution::NpmResolution; use super::NpmCache; use super::NpmPackageId; -use super::NpmPackageReq; +use super::NpmRegistryApi; use super::NpmResolutionSnapshot; -use super::RealNpmRegistryApi; - -const RESOLUTION_STATE_ENV_VAR_NAME: &str = - "DENO_DONT_USE_INTERNAL_NODE_COMPAT_STATE"; - -static IS_NPM_MAIN: Lazy = - Lazy::new(|| std::env::var(RESOLUTION_STATE_ENV_VAR_NAME).is_ok()); /// State provided to the process via an environment variable. -#[derive(Debug, Serialize, Deserialize)] -struct NpmProcessState { - snapshot: NpmResolutionSnapshot, - local_node_modules_path: Option, -} - -impl NpmProcessState { - pub fn was_set() -> bool { - *IS_NPM_MAIN - } - - pub fn take() -> Option { - // initialize the lazy before we remove the env var below - if !Self::was_set() { - return None; - } - - let state = std::env::var(RESOLUTION_STATE_ENV_VAR_NAME).ok()?; - let state = serde_json::from_str(&state).ok()?; - // remove the environment variable so that sub processes - // that are spawned do not also use this. - std::env::remove_var(RESOLUTION_STATE_ENV_VAR_NAME); - Some(state) - } +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct NpmProcessState { + pub snapshot: NpmResolutionSnapshot, + pub local_node_modules_path: Option, } #[derive(Clone)] pub struct NpmPackageResolver { - no_npm: bool, - inner: Arc, + fs_resolver: Arc, local_node_modules_path: Option, - api: RealNpmRegistryApi, + api: NpmRegistryApi, + resolution: NpmResolution, cache: NpmCache, maybe_lockfile: Option>>, } @@ -81,110 +54,118 @@ pub struct NpmPackageResolver { impl std::fmt::Debug for NpmPackageResolver { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { f.debug_struct("NpmPackageResolver") - .field("no_npm", &self.no_npm) - .field("inner", &"") + .field("fs_resolver", &"") .field("local_node_modules_path", &self.local_node_modules_path) + .field("api", &"") + .field("resolution", &"") + .field("cache", &"") + .field("maybe_lockfile", &"") .finish() } } impl NpmPackageResolver { - pub fn new( - cache: NpmCache, - api: RealNpmRegistryApi, - no_npm: bool, - local_node_modules_path: Option, - ) -> Self { - Self::new_inner(cache, api, no_npm, local_node_modules_path, None, None) + pub fn new(cache: NpmCache, api: NpmRegistryApi) -> Self { + Self::new_inner(cache, api, None, None, None) } pub async fn new_with_maybe_lockfile( cache: NpmCache, - api: RealNpmRegistryApi, - no_npm: bool, + api: NpmRegistryApi, local_node_modules_path: Option, + initial_snapshot: Option, maybe_lockfile: Option>>, ) -> Result { - let maybe_snapshot = if let Some(lockfile) = &maybe_lockfile { - if lockfile.lock().overwrite { - None - } else { - Some( - NpmResolutionSnapshot::from_lockfile(lockfile.clone(), &api) - .await - .with_context(|| { - format!( - "failed reading lockfile '{}'", - lockfile.lock().filename.display() - ) - })?, - ) + let mut initial_snapshot = initial_snapshot; + + if initial_snapshot.is_none() { + if let Some(lockfile) = &maybe_lockfile { + if !lockfile.lock().overwrite { + initial_snapshot = Some( + NpmResolutionSnapshot::from_lockfile(lockfile.clone(), &api) + .await + .with_context(|| { + format!( + "failed reading lockfile '{}'", + lockfile.lock().filename.display() + ) + })?, + ) + } } - } else { - None - }; + } + Ok(Self::new_inner( cache, api, - no_npm, local_node_modules_path, - maybe_snapshot, + initial_snapshot, maybe_lockfile, )) } fn new_inner( cache: NpmCache, - api: RealNpmRegistryApi, - no_npm: bool, + api: NpmRegistryApi, local_node_modules_path: Option, - initial_snapshot: Option, + maybe_snapshot: Option, maybe_lockfile: Option>>, ) -> Self { - let process_npm_state = NpmProcessState::take(); - let local_node_modules_path = local_node_modules_path.or_else(|| { - process_npm_state - .as_ref() - .and_then(|s| s.local_node_modules_path.as_ref().map(PathBuf::from)) - }); - let maybe_snapshot = - initial_snapshot.or_else(|| process_npm_state.map(|s| s.snapshot)); - let inner: Arc = match &local_node_modules_path - { - Some(node_modules_folder) => Arc::new(LocalNpmPackageResolver::new( - cache.clone(), - api.clone(), - node_modules_folder.clone(), - maybe_snapshot, - )), - None => Arc::new(GlobalNpmPackageResolver::new( - cache.clone(), - api.clone(), - maybe_snapshot, - )), - }; + let registry_url = api.base_url().to_owned(); + let resolution = + NpmResolution::new(api.clone(), maybe_snapshot, maybe_lockfile.clone()); + let fs_resolver: Arc = + match &local_node_modules_path { + Some(node_modules_folder) => Arc::new(LocalNpmPackageResolver::new( + cache.clone(), + registry_url, + node_modules_folder.clone(), + resolution.clone(), + )), + None => Arc::new(GlobalNpmPackageResolver::new( + cache.clone(), + registry_url, + resolution.clone(), + )), + }; Self { - no_npm, - inner, + fs_resolver, local_node_modules_path, api, + resolution, cache, maybe_lockfile, } } + pub fn api(&self) -> &NpmRegistryApi { + &self.api + } + + pub fn resolution(&self) -> &NpmResolution { + &self.resolution + } + /// Resolves an npm package folder path from a Deno module. pub fn resolve_package_folder_from_deno_module( &self, - pkg_req: &NpmPackageReq, + pkg_nv: &NpmPackageNv, + ) -> Result { + let pkg_id = self.resolution.resolve_pkg_id_from_deno_module(pkg_nv)?; + self.resolve_pkg_folder_from_deno_module_at_pkg_id(&pkg_id) + } + + fn resolve_pkg_folder_from_deno_module_at_pkg_id( + &self, + pkg_id: &NpmPackageId, ) -> Result { let path = self - .inner - .resolve_package_folder_from_deno_module(pkg_req)?; + .fs_resolver + .resolve_package_folder_from_deno_module(pkg_id)?; let path = canonicalize_path_maybe_not_exists(&path)?; log::debug!( "Resolved package folder of {} to {}", - pkg_req, + pkg_id.as_serialized(), path.display() ); Ok(path) @@ -198,7 +179,7 @@ impl NpmPackageResolver { mode: NodeResolutionMode, ) -> Result { let path = self - .inner + .fs_resolver .resolve_package_folder_from_package(name, referrer, mode)?; log::debug!("Resolved {} from {} to {}", name, referrer, path.display()); Ok(path) @@ -212,7 +193,7 @@ impl NpmPackageResolver { specifier: &ModuleSpecifier, ) -> Result { let path = self - .inner + .fs_resolver .resolve_package_folder_from_specifier(specifier)?; log::debug!( "Resolved package folder of {} to {}", @@ -227,19 +208,19 @@ impl NpmPackageResolver { &self, package_id: &NpmPackageId, ) -> Result { - self.inner.package_size(package_id) + self.fs_resolver.package_size(package_id) } /// Gets if the provided specifier is in an npm package. pub fn in_npm_package(&self, specifier: &ModuleSpecifier) -> bool { - self - .resolve_package_folder_from_specifier(specifier) - .is_ok() + let root_dir_url = self.fs_resolver.root_dir_url(); + debug_assert!(root_dir_url.as_str().ends_with('/')); + specifier.as_ref().starts_with(root_dir_url.as_str()) } /// If the resolver has resolved any npm packages. pub fn has_packages(&self) -> bool { - self.inner.has_packages() + self.resolution.has_packages() } /// Adds package requirements to the resolver and ensures everything is setup. @@ -251,24 +232,8 @@ impl NpmPackageResolver { return Ok(()); } - if self.no_npm { - let fmt_reqs = packages - .iter() - .collect::>() // prevent duplicates - .iter() - .map(|p| format!("\"{p}\"")) - .collect::>() - .join(", "); - return Err(custom_error( - "NoNpm", - format!( - "Following npm specifiers were requested: {fmt_reqs}; but --no-npm is specified." - ), - )); - } - - self.inner.add_package_reqs(packages).await?; - self.inner.cache_packages().await?; + self.resolution.add_package_reqs(packages).await?; + self.fs_resolver.cache_packages().await?; // If there's a lock file, update it with all discovered npm packages if let Some(lockfile_mutex) = &self.maybe_lockfile { @@ -284,23 +249,15 @@ impl NpmPackageResolver { /// This will retrieve and resolve package information, but not cache any package files. pub async fn set_package_reqs( &self, - packages: HashSet, + packages: Vec, ) -> Result<(), AnyError> { - self.inner.set_package_reqs(packages).await - } - - // If the main module should be treated as being in an npm package. - // This is triggered via a secret environment variable which is used - // for functionality like child_process.fork. Users should NOT depend - // on this functionality. - pub fn is_npm_main(&self) -> bool { - NpmProcessState::was_set() + self.resolution.set_package_reqs(packages).await } /// Gets the state of npm for the process. pub fn get_npm_process_state(&self) -> String { serde_json::to_string(&NpmProcessState { - snapshot: self.inner.snapshot(), + snapshot: self.snapshot(), local_node_modules_path: self .local_node_modules_path .as_ref() @@ -314,7 +271,6 @@ impl NpmPackageResolver { Self::new_inner( self.cache.clone(), self.api.clone(), - self.no_npm, self.local_node_modules_path.clone(), Some(self.snapshot()), None, @@ -322,11 +278,11 @@ impl NpmPackageResolver { } pub fn snapshot(&self) -> NpmResolutionSnapshot { - self.inner.snapshot() + self.resolution.snapshot() } pub fn lock(&self, lockfile: &mut Lockfile) -> Result<(), AnyError> { - self.inner.lock(lockfile) + self.resolution.lock(lockfile) } pub async fn inject_synthetic_types_node_package( @@ -334,11 +290,17 @@ impl NpmPackageResolver { ) -> Result<(), AnyError> { // add and ensure this isn't added to the lockfile self - .inner + .resolution .add_package_reqs(vec![NpmPackageReq::from_str("@types/node").unwrap()]) .await?; - self.inner.cache_packages().await?; + self.fs_resolver.cache_packages().await?; + + Ok(()) + } + pub async fn resolve_pending(&self) -> Result<(), AnyError> { + self.resolution.resolve_pending().await?; + self.fs_resolver.cache_packages().await?; Ok(()) } } @@ -378,7 +340,7 @@ impl RequireNpmResolver for NpmPackageResolver { permissions: &mut dyn NodePermissions, path: &Path, ) -> Result<(), AnyError> { - self.inner.ensure_read_permission(permissions, path) + self.fs_resolver.ensure_read_permission(permissions, path) } } diff --git a/cli/npm/tarball.rs b/cli/npm/tarball.rs index 3abf4f12f42fde..1f804a9aa06bb7 100644 --- a/cli/npm/tarball.rs +++ b/cli/npm/tarball.rs @@ -7,16 +7,16 @@ use std::path::PathBuf; use deno_core::anyhow::bail; use deno_core::error::AnyError; +use deno_graph::npm::NpmPackageNv; use flate2::read::GzDecoder; use tar::Archive; use tar::EntryType; use super::cache::with_folder_sync_lock; use super::registry::NpmPackageVersionDistInfo; -use crate::semver::Version; pub fn verify_and_extract_tarball( - package: (&str, &Version), + package: &NpmPackageNv, data: &[u8], dist_info: &NpmPackageVersionDistInfo, output_folder: &Path, @@ -29,7 +29,7 @@ pub fn verify_and_extract_tarball( } fn verify_tarball_integrity( - package: (&str, &Version), + package: &NpmPackageNv, data: &[u8], npm_integrity: &str, ) -> Result<(), AnyError> { @@ -40,18 +40,16 @@ fn verify_tarball_integrity( "sha512" => &ring::digest::SHA512, "sha1" => &ring::digest::SHA1_FOR_LEGACY_USE_ONLY, hash_kind => bail!( - "Not implemented hash function for {}@{}: {}", - package.0, - package.1, + "Not implemented hash function for {}: {}", + package, hash_kind ), }; (algo, checksum.to_lowercase()) } None => bail!( - "Not implemented integrity kind for {}@{}: {}", - package.0, - package.1, + "Not implemented integrity kind for {}: {}", + package, npm_integrity ), }; @@ -62,9 +60,8 @@ fn verify_tarball_integrity( let tarball_checksum = base64::encode(digest.as_ref()).to_lowercase(); if tarball_checksum != expected_checksum { bail!( - "Tarball checksum did not match what was provided by npm registry for {}@{}.\n\nExpected: {}\nActual: {}", - package.0, - package.1, + "Tarball checksum did not match what was provided by npm registry for {}.\n\nExpected: {}\nActual: {}", + package, expected_checksum, tarball_checksum, ) @@ -119,29 +116,32 @@ fn extract_tarball(data: &[u8], output_folder: &Path) -> Result<(), AnyError> { #[cfg(test)] mod test { + use deno_graph::semver::Version; + use super::*; #[test] pub fn test_verify_tarball() { - let package_name = "package".to_string(); - let package_version = Version::parse_from_npm("1.0.0").unwrap(); - let package = (package_name.as_str(), &package_version); + let package = NpmPackageNv { + name: "package".to_string(), + version: Version::parse_from_npm("1.0.0").unwrap(), + }; let actual_checksum = "z4phnx7vul3xvchq1m2ab9yg5aulvxxcg/spidns6c5h0ne8xyxysp+dgnkhfuwvy7kxvudbeoglodj6+sfapg=="; assert_eq!( - verify_tarball_integrity(package, &Vec::new(), "test") + verify_tarball_integrity(&package, &Vec::new(), "test") .unwrap_err() .to_string(), "Not implemented integrity kind for package@1.0.0: test", ); assert_eq!( - verify_tarball_integrity(package, &Vec::new(), "notimplemented-test") + verify_tarball_integrity(&package, &Vec::new(), "notimplemented-test") .unwrap_err() .to_string(), "Not implemented hash function for package@1.0.0: notimplemented", ); assert_eq!( - verify_tarball_integrity(package, &Vec::new(), "sha1-test") + verify_tarball_integrity(&package, &Vec::new(), "sha1-test") .unwrap_err() .to_string(), concat!( @@ -150,13 +150,13 @@ mod test { ), ); assert_eq!( - verify_tarball_integrity(package, &Vec::new(), "sha512-test") + verify_tarball_integrity(&package, &Vec::new(), "sha512-test") .unwrap_err() .to_string(), format!("Tarball checksum did not match what was provided by npm registry for package@1.0.0.\n\nExpected: test\nActual: {actual_checksum}"), ); assert!(verify_tarball_integrity( - package, + &package, &Vec::new(), &format!("sha512-{actual_checksum}") ) diff --git a/cli/proc_state.rs b/cli/proc_state.rs index c481a4307d75ae..4c61b9a6b22371 100644 --- a/cli/proc_state.rs +++ b/cli/proc_state.rs @@ -17,18 +17,18 @@ use crate::cache::ParsedSourceCache; use crate::cache::TypeCheckCache; use crate::emit::emit_parsed_source; use crate::file_fetcher::FileFetcher; +use crate::graph_util::build_graph_with_npm_resolution; use crate::graph_util::graph_lock_or_exit; -use crate::graph_util::GraphData; -use crate::graph_util::ModuleEntry; +use crate::graph_util::graph_valid_with_cli_options; +use crate::graph_util::ModuleGraphContainer; use crate::http_util::HttpClient; use crate::node; use crate::node::NodeResolution; -use crate::npm::resolve_graph_npm_info; use crate::npm::NpmCache; -use crate::npm::NpmPackageReference; use crate::npm::NpmPackageResolver; -use crate::npm::RealNpmRegistryApi; -use crate::resolver::CliResolver; +use crate::npm::NpmRegistryApi; +use crate::npm::PackageJsonDepsInstaller; +use crate::resolver::CliGraphResolver; use crate::tools::check; use crate::util::progress_bar::ProgressBar; use crate::util::progress_bar::ProgressBarStyle; @@ -39,19 +39,17 @@ use deno_core::anyhow::Context; use deno_core::error::custom_error; use deno_core::error::generic_error; use deno_core::error::AnyError; -use deno_core::futures; use deno_core::parking_lot::Mutex; -use deno_core::parking_lot::RwLock; use deno_core::resolve_url_or_path; use deno_core::CompiledWasmModuleStore; use deno_core::ModuleSpecifier; use deno_core::SharedArrayBufferStore; -use deno_graph::create_graph; -use deno_graph::source::CacheInfo; -use deno_graph::source::LoadFuture; +use deno_graph::npm::NpmPackageReqReference; use deno_graph::source::Loader; use deno_graph::source::Resolver; -use deno_graph::Resolved; +use deno_graph::Module; +use deno_graph::ModuleGraph; +use deno_graph::Resolution; use deno_runtime::deno_broadcast_channel::InMemoryBroadcastChannel; use deno_runtime::deno_node::NodeResolutionMode; use deno_runtime::deno_tls::rustls::RootCertStore; @@ -60,11 +58,10 @@ use deno_runtime::inspector_server::InspectorServer; use deno_runtime::permissions::PermissionsContainer; use import_map::ImportMap; use log::warn; +use std::borrow::Cow; use std::collections::HashSet; use std::ops::Deref; use std::path::PathBuf; -use std::sync::atomic::AtomicBool; -use std::sync::atomic::Ordering; use std::sync::Arc; /// This structure represents state of single "deno" program. @@ -81,7 +78,7 @@ pub struct Inner { pub emit_cache: EmitCache, pub emit_options: deno_ast::EmitOptions, pub emit_options_hash: u64, - pub graph_data: Arc>, + graph_container: ModuleGraphContainer, pub lockfile: Option>>, pub maybe_import_map: Option>, pub maybe_inspector_server: Option>, @@ -91,14 +88,14 @@ pub struct Inner { pub shared_array_buffer_store: SharedArrayBufferStore, pub compiled_wasm_module_store: CompiledWasmModuleStore, pub parsed_source_cache: ParsedSourceCache, - pub maybe_resolver: Option>, + pub resolver: Arc, maybe_file_watcher_reporter: Option, pub node_analysis_cache: NodeAnalysisCache, pub npm_cache: NpmCache, pub npm_resolver: NpmPackageResolver, + pub package_json_deps_installer: PackageJsonDepsInstaller, pub cjs_resolutions: Mutex>, progress_bar: ProgressBar, - node_std_graph_prepared: AtomicBool, } impl Deref for ProcState { @@ -143,7 +140,7 @@ impl ProcState { emit_options: self.emit_options.clone(), file_fetcher: self.file_fetcher.clone(), http_client: self.http_client.clone(), - graph_data: Default::default(), + graph_container: Default::default(), lockfile: self.lockfile.clone(), maybe_import_map: self.maybe_import_map.clone(), maybe_inspector_server: self.maybe_inspector_server.clone(), @@ -153,14 +150,14 @@ impl ProcState { shared_array_buffer_store: Default::default(), compiled_wasm_module_store: Default::default(), parsed_source_cache: self.parsed_source_cache.reset_for_file_watcher(), - maybe_resolver: self.maybe_resolver.clone(), + resolver: self.resolver.clone(), maybe_file_watcher_reporter: self.maybe_file_watcher_reporter.clone(), node_analysis_cache: self.node_analysis_cache.clone(), npm_cache: self.npm_cache.clone(), npm_resolver: self.npm_resolver.clone(), + package_json_deps_installer: self.package_json_deps_installer.clone(), cjs_resolutions: Default::default(), progress_bar: self.progress_bar.clone(), - node_std_graph_prepared: AtomicBool::new(false), }); self.init_watcher(); } @@ -213,6 +210,32 @@ impl ProcState { let lockfile = cli_options.maybe_lock_file(); + let registry_url = NpmRegistryApi::default_url().to_owned(); + let npm_cache = NpmCache::from_deno_dir( + &dir, + cli_options.cache_setting(), + http_client.clone(), + progress_bar.clone(), + ); + let api = NpmRegistryApi::new( + registry_url, + npm_cache.clone(), + http_client.clone(), + progress_bar.clone(), + ); + let npm_resolver = NpmPackageResolver::new_with_maybe_lockfile( + npm_cache.clone(), + api, + cli_options.node_modules_dir_path(), + cli_options.get_npm_resolution_snapshot(), + lockfile.as_ref().cloned(), + ) + .await?; + let package_json_deps_installer = PackageJsonDepsInstaller::new( + npm_resolver.api().clone(), + npm_resolver.resolution().clone(), + cli_options.maybe_package_json_deps()?, + ); let maybe_import_map = cli_options .resolve_import_map(&file_fetcher) .await? @@ -220,11 +243,14 @@ impl ProcState { let maybe_inspector_server = cli_options.resolve_inspector_server().map(Arc::new); - let maybe_cli_resolver = CliResolver::maybe_new( + let resolver = Arc::new(CliGraphResolver::new( cli_options.to_maybe_jsx_import_source_config(), maybe_import_map.clone(), - ); - let maybe_resolver = maybe_cli_resolver.map(Arc::new); + cli_options.no_npm(), + npm_resolver.api().clone(), + npm_resolver.resolution().clone(), + package_json_deps_installer.clone(), + )); let maybe_file_watcher_reporter = maybe_sender.map(|sender| FileWatcherReporter { @@ -240,29 +266,12 @@ impl ProcState { let emit_cache = EmitCache::new(dir.gen_cache.clone()); let parsed_source_cache = ParsedSourceCache::new(Some(dir.dep_analysis_db_file_path())); - let registry_url = RealNpmRegistryApi::default_url(); let npm_cache = NpmCache::from_deno_dir( &dir, cli_options.cache_setting(), http_client.clone(), progress_bar.clone(), ); - let api = RealNpmRegistryApi::new( - registry_url, - npm_cache.clone(), - http_client.clone(), - progress_bar.clone(), - ); - let npm_resolver = NpmPackageResolver::new_with_maybe_lockfile( - npm_cache.clone(), - api, - cli_options.no_npm(), - cli_options - .resolve_local_node_modules_folder() - .with_context(|| "Resolving local node_modules folder.")?, - lockfile.as_ref().cloned(), - ) - .await?; let node_analysis_cache = NodeAnalysisCache::new(Some(dir.node_analysis_db_file_path())); @@ -277,7 +286,7 @@ impl ProcState { emit_options, file_fetcher: Arc::new(file_fetcher), http_client, - graph_data: Default::default(), + graph_container: Default::default(), lockfile, maybe_import_map, maybe_inspector_server, @@ -287,14 +296,14 @@ impl ProcState { shared_array_buffer_store, compiled_wasm_module_store, parsed_source_cache, - maybe_resolver, + resolver, maybe_file_watcher_reporter, node_analysis_cache, npm_cache, npm_resolver, + package_json_deps_installer, cjs_resolutions: Default::default(), progress_bar, - node_std_graph_prepared: AtomicBool::new(false), }))) } @@ -314,70 +323,16 @@ impl ProcState { log::debug!("Preparing module load."); let _pb_clear_guard = self.progress_bar.clear_guard(); - let has_root_npm_specifier = roots.iter().any(|r| { - r.scheme() == "npm" && NpmPackageReference::from_specifier(r).is_ok() - }); - - if !has_root_npm_specifier { - let graph_data = self.graph_data.read(); - if self.options.type_check_mode() == TypeCheckMode::None - || graph_data.is_type_checked(&roots, &lib) - { - if let Some(result) = graph_data.check( - &roots, - self.options.type_check_mode() != TypeCheckMode::None, - false, - ) { - // TODO(bartlomieju): this is strange... ideally there should be only - // one codepath in `prepare_module_load` so we don't forget things - // like writing a lockfile. Figure a way to refactor this function. - if let Some(ref lockfile) = self.lockfile { - let g = lockfile.lock(); - g.write()?; - } - return result; - } - } - } let mut cache = cache::FetchCacher::new( self.emit_cache.clone(), self.file_fetcher.clone(), root_permissions, dynamic_permissions, + self.options.node_modules_dir_specifier(), ); let maybe_imports = self.options.to_maybe_imports()?; - let maybe_resolver = - self.maybe_resolver.as_ref().map(|r| r.as_graph_resolver()); - - struct ProcStateLoader<'a> { - inner: &'a mut cache::FetchCacher, - graph_data: Arc>, - } - impl Loader for ProcStateLoader<'_> { - fn get_cache_info( - &self, - specifier: &ModuleSpecifier, - ) -> Option { - self.inner.get_cache_info(specifier) - } - fn load( - &mut self, - specifier: &ModuleSpecifier, - is_dynamic: bool, - ) -> LoadFuture { - let graph_data = self.graph_data.read(); - let found_specifier = graph_data.follow_redirect(specifier); - match graph_data.get(&found_specifier) { - Some(_) => Box::pin(futures::future::ready(Err(anyhow!("")))), - _ => self.inner.load(specifier, is_dynamic), - } - } - } - let mut loader = ProcStateLoader { - inner: &mut cache, - graph_data: self.graph_data.clone(), - }; - + let graph_resolver = self.resolver.as_graph_resolver(); + let graph_npm_resolver = self.resolver.as_graph_npm_resolver(); let maybe_file_watcher_reporter: Option<&dyn deno_graph::source::Reporter> = if let Some(reporter) = &self.maybe_file_watcher_reporter { Some(reporter) @@ -386,55 +341,43 @@ impl ProcState { }; let analyzer = self.parsed_source_cache.as_analyzer(); + log::debug!("Creating module graph."); - let graph = create_graph( + let mut graph_update_permit = + self.graph_container.acquire_update_permit().await; + let graph = graph_update_permit.graph_mut(); + + // Determine any modules that have already been emitted this session and + // should be skipped. + let reload_exclusions: HashSet = + graph.specifiers().map(|(s, _)| s.clone()).collect(); + + build_graph_with_npm_resolution( + graph, + &self.npm_resolver, roots.clone(), - &mut loader, - deno_graph::GraphOptions { + &mut cache, + deno_graph::BuildOptions { is_dynamic, imports: maybe_imports, - resolver: maybe_resolver, + resolver: Some(graph_resolver), + npm_resolver: Some(graph_npm_resolver), module_analyzer: Some(&*analyzer), reporter: maybe_file_watcher_reporter, }, ) - .await; + .await?; // If there is a lockfile, validate the integrity of all the modules. if let Some(lockfile) = &self.lockfile { - graph_lock_or_exit(&graph, &mut lockfile.lock()); + graph_lock_or_exit(graph, &mut lockfile.lock()); } - // Determine any modules that have already been emitted this session and - // should be skipped. - let reload_exclusions: HashSet = { - let graph_data = self.graph_data.read(); - graph_data.entries().map(|(s, _)| s).cloned().collect() - }; - - let (npm_package_reqs, has_node_builtin_specifier) = { - let mut graph_data = self.graph_data.write(); - graph_data.add_graph(&graph); - let check_js = self.options.check_js(); - graph_data - .check( - &roots, - self.options.type_check_mode() != TypeCheckMode::None, - check_js, - ) - .unwrap()?; - ( - graph_data.npm_package_reqs().clone(), - graph_data.has_node_builtin_specifier(), - ) - }; - - if !npm_package_reqs.is_empty() { - self.npm_resolver.add_package_reqs(npm_package_reqs).await?; - self.prepare_node_std_graph().await?; - } + graph_valid_with_cli_options(graph, &roots, &self.options)?; + // save the graph and get a reference to the new graph + let graph = graph_update_permit.commit(); - if has_node_builtin_specifier + if graph.has_node_specifier && self.options.type_check_mode() != TypeCheckMode::None { self @@ -446,10 +389,12 @@ impl ProcState { drop(_pb_clear_guard); // type check if necessary - let is_std_node = roots.len() == 1 && roots[0] == *node::MODULE_ALL_URL; - if self.options.type_check_mode() != TypeCheckMode::None && !is_std_node { + if self.options.type_check_mode() != TypeCheckMode::None + && !self.graph_container.is_type_checked(&roots, lib) + { log::debug!("Type checking."); let maybe_config_specifier = self.options.maybe_config_file_specifier(); + let graph = Arc::new(graph.segment(&roots)); let options = check::CheckOptions { type_check_mode: self.options.type_check_mode(), debug: self.options.log_level() == Some(log::Level::Debug), @@ -464,25 +409,15 @@ impl ProcState { }; let check_cache = TypeCheckCache::new(&self.dir.type_checking_cache_db_file_path()); - let graph_data = self.graph_data.clone(); - let check_result = check::check( - &roots, - graph_data, - &check_cache, - &self.npm_resolver, - options, - )?; + let check_result = + check::check(graph, &check_cache, &self.npm_resolver, options)?; + self.graph_container.set_type_checked(&roots, lib); if !check_result.diagnostics.is_empty() { return Err(anyhow!(check_result.diagnostics)); } log::debug!("{}", check_result.stats); } - if self.options.type_check_mode() != TypeCheckMode::None { - let mut graph_data = self.graph_data.write(); - graph_data.set_type_checked(&roots, lib); - } - // any updates to the lockfile should be updated now if let Some(ref lockfile) = self.lockfile { let g = lockfile.lock(); @@ -517,20 +452,6 @@ impl ProcState { .await } - /// Add the builtin node modules to the graph data. - pub async fn prepare_node_std_graph(&self) -> Result<(), AnyError> { - if self.node_std_graph_prepared.load(Ordering::Relaxed) { - return Ok(()); - } - - let node_std_graph = self - .create_graph(vec![node::MODULE_ALL_URL.clone()]) - .await?; - self.graph_data.write().add_graph(&node_std_graph); - self.node_std_graph_prepared.store(true, Ordering::Relaxed); - Ok(()) - } - fn handle_node_resolve_result( &self, result: Result, AnyError>, @@ -570,47 +491,47 @@ impl ProcState { }); } - let graph_data = self.graph_data.read(); - let found_referrer = graph_data.follow_redirect(&referrer); - let maybe_resolved = match graph_data.get(&found_referrer) { - Some(ModuleEntry::Module { dependencies, .. }) => { - dependencies.get(specifier).map(|d| &d.maybe_code) + let graph = self.graph_container.graph(); + let maybe_resolved = match graph.get(&referrer) { + Some(Module::Esm(module)) => { + module.dependencies.get(specifier).map(|d| &d.maybe_code) } _ => None, }; match maybe_resolved { - Some(Resolved::Ok { specifier, .. }) => { - if let Ok(reference) = NpmPackageReference::from_specifier(specifier) - { - if !self.options.unstable() - && matches!(found_referrer.scheme(), "http" | "https") - { - return Err(custom_error( - "NotSupported", - format!("importing npm specifiers in remote modules requires the --unstable flag (referrer: {found_referrer})"), - )); - } + Some(Resolution::Ok(resolved)) => { + let specifier = &resolved.specifier; - return self + return match graph.get(specifier) { + Some(Module::Npm(module)) => self .handle_node_resolve_result(node::node_resolve_npm_reference( - &reference, + &module.nv_reference, NodeResolutionMode::Execution, &self.npm_resolver, permissions, )) - .with_context(|| format!("Could not resolve '{reference}'.")); - } else { - return Ok(specifier.clone()); - } + .with_context(|| { + format!("Could not resolve '{}'.", module.nv_reference) + }), + Some(Module::Node(module)) => { + node::resolve_builtin_node_module(&module.module_name) + } + Some(Module::Esm(module)) => Ok(module.specifier.clone()), + Some(Module::Json(module)) => Ok(module.specifier.clone()), + Some(Module::External(module)) => { + Ok(node::resolve_specifier_into_node_modules(&module.specifier)) + } + None => Ok(specifier.clone()), + }; } - Some(Resolved::Err(err)) => { + Some(Resolution::Err(err)) => { return Err(custom_error( "TypeError", format!("{}\n", err.to_string_with_range()), )) } - Some(Resolved::None) | None => {} + Some(Resolution::None) | None => {} } } @@ -631,14 +552,22 @@ impl ProcState { // FIXME(bartlomieju): this is another hack way to provide NPM specifier // support in REPL. This should be fixed. + let resolution = self.resolver.resolve(specifier, &referrer); + if is_repl { - let specifier = self - .maybe_resolver + let specifier = resolution .as_ref() - .and_then(|resolver| resolver.resolve(specifier, &referrer).ok()) - .or_else(|| ModuleSpecifier::parse(specifier).ok()); + .ok() + .map(Cow::Borrowed) + .or_else(|| ModuleSpecifier::parse(specifier).ok().map(Cow::Owned)); if let Some(specifier) = specifier { - if let Ok(reference) = NpmPackageReference::from_specifier(&specifier) { + if let Ok(reference) = + NpmPackageReqReference::from_specifier(&specifier) + { + let reference = self + .npm_resolver + .resolution() + .pkg_req_ref_to_nv_ref(reference)?; return self .handle_node_resolve_result(node::node_resolve_npm_reference( &reference, @@ -651,23 +580,15 @@ impl ProcState { } } - if let Some(resolver) = &self.maybe_resolver { - resolver.resolve(specifier, &referrer) - } else { - deno_core::resolve_import(specifier, referrer.as_str()) - .map_err(|err| err.into()) - } + resolution } pub fn cache_module_emits(&self) -> Result<(), AnyError> { - let graph_data = self.graph_data.read(); - for (specifier, entry) in graph_data.entries() { - if let ModuleEntry::Module { - code, media_type, .. - } = entry - { + let graph = self.graph(); + for module in graph.modules() { + if let Module::Esm(module) = module { let is_emittable = matches!( - media_type, + module.media_type, MediaType::TypeScript | MediaType::Mts | MediaType::Cts @@ -678,9 +599,9 @@ impl ProcState { emit_parsed_source( &self.emit_cache, &self.parsed_source_cache, - specifier, - *media_type, - code, + &module.specifier, + module.media_type, + &module.source, &self.emit_options, self.emit_options_hash, )?; @@ -697,6 +618,7 @@ impl ProcState { self.file_fetcher.clone(), PermissionsContainer::allow_all(), PermissionsContainer::allow_all(), + self.options.node_modules_dir_specifier(), ) } @@ -715,36 +637,36 @@ impl ProcState { ) -> Result { let maybe_imports = self.options.to_maybe_imports()?; - let maybe_cli_resolver = CliResolver::maybe_new( + let cli_resolver = CliGraphResolver::new( self.options.to_maybe_jsx_import_source_config(), self.maybe_import_map.clone(), + self.options.no_npm(), + self.npm_resolver.api().clone(), + self.npm_resolver.resolution().clone(), + self.package_json_deps_installer.clone(), ); - let maybe_graph_resolver = - maybe_cli_resolver.as_ref().map(|r| r.as_graph_resolver()); + let graph_resolver = cli_resolver.as_graph_resolver(); + let graph_npm_resolver = cli_resolver.as_graph_npm_resolver(); let analyzer = self.parsed_source_cache.as_analyzer(); - let graph = create_graph( + let mut graph = ModuleGraph::default(); + build_graph_with_npm_resolution( + &mut graph, + &self.npm_resolver, roots, loader, - deno_graph::GraphOptions { + deno_graph::BuildOptions { is_dynamic: false, imports: maybe_imports, - resolver: maybe_graph_resolver, + resolver: Some(graph_resolver), + npm_resolver: Some(graph_npm_resolver), module_analyzer: Some(&*analyzer), reporter: None, }, ) - .await; + .await?; - // add the found npm package requirements to the npm resolver and cache them - let graph_npm_info = resolve_graph_npm_info(&graph); - if !graph_npm_info.package_reqs.is_empty() { - self - .npm_resolver - .add_package_reqs(graph_npm_info.package_reqs) - .await?; - } - if graph_npm_info.has_node_builtin_specifier + if graph.has_node_specifier && self.options.type_check_mode() != TypeCheckMode::None { self @@ -755,6 +677,10 @@ impl ProcState { Ok(graph) } + + pub fn graph(&self) -> Arc { + self.graph_container.graph() + } } #[derive(Clone, Debug)] diff --git a/cli/resolver.rs b/cli/resolver.rs index 817b5d3b080267..e3d2eb37d61308 100644 --- a/cli/resolver.rs +++ b/cli/resolver.rs @@ -1,53 +1,103 @@ // Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. +use deno_core::anyhow::bail; use deno_core::error::AnyError; -use deno_core::resolve_import; +use deno_core::futures::future; +use deno_core::futures::future::LocalBoxFuture; +use deno_core::futures::FutureExt; use deno_core::ModuleSpecifier; +use deno_graph::npm::NpmPackageNv; +use deno_graph::npm::NpmPackageReq; +use deno_graph::source::NpmResolver; use deno_graph::source::Resolver; +use deno_graph::source::UnknownBuiltInNodeModuleError; use deno_graph::source::DEFAULT_JSX_IMPORT_SOURCE_MODULE; +use deno_runtime::deno_node::is_builtin_node_module; use import_map::ImportMap; +use std::collections::BTreeMap; use std::sync::Arc; use crate::args::JsxImportSourceConfig; +use crate::npm::NpmRegistryApi; +use crate::npm::NpmResolution; +use crate::npm::PackageJsonDepsInstaller; /// A resolver that takes care of resolution, taking into account loaded /// import map, JSX settings. -#[derive(Debug, Clone, Default)] -pub struct CliResolver { +#[derive(Debug, Clone)] +pub struct CliGraphResolver { maybe_import_map: Option>, maybe_default_jsx_import_source: Option, maybe_jsx_import_source_module: Option, + no_npm: bool, + npm_registry_api: NpmRegistryApi, + npm_resolution: NpmResolution, + package_json_deps_installer: PackageJsonDepsInstaller, + sync_download_semaphore: Option>, } -impl CliResolver { - pub fn maybe_new( +impl Default for CliGraphResolver { + fn default() -> Self { + // This is not ideal, but necessary for the LSP. In the future, we should + // refactor the LSP and force this to be initialized. + let npm_registry_api = NpmRegistryApi::new_uninitialized(); + let npm_resolution = + NpmResolution::new(npm_registry_api.clone(), None, None); + Self { + maybe_import_map: Default::default(), + maybe_default_jsx_import_source: Default::default(), + maybe_jsx_import_source_module: Default::default(), + no_npm: false, + npm_registry_api, + npm_resolution, + package_json_deps_installer: Default::default(), + sync_download_semaphore: Self::create_sync_download_semaphore(), + } + } +} + +impl CliGraphResolver { + pub fn new( maybe_jsx_import_source_config: Option, maybe_import_map: Option>, - ) -> Option { - if maybe_jsx_import_source_config.is_some() || maybe_import_map.is_some() { - Some(Self { - maybe_import_map, - maybe_default_jsx_import_source: maybe_jsx_import_source_config - .as_ref() - .and_then(|c| c.default_specifier.clone()), - maybe_jsx_import_source_module: maybe_jsx_import_source_config - .map(|c| c.module), - }) + no_npm: bool, + npm_registry_api: NpmRegistryApi, + npm_resolution: NpmResolution, + package_json_deps_installer: PackageJsonDepsInstaller, + ) -> Self { + Self { + maybe_import_map, + maybe_default_jsx_import_source: maybe_jsx_import_source_config + .as_ref() + .and_then(|c| c.default_specifier.clone()), + maybe_jsx_import_source_module: maybe_jsx_import_source_config + .map(|c| c.module), + no_npm, + npm_registry_api, + npm_resolution, + package_json_deps_installer, + sync_download_semaphore: Self::create_sync_download_semaphore(), + } + } + + fn create_sync_download_semaphore() -> Option> { + if crate::npm::should_sync_download() { + Some(Arc::new(tokio::sync::Semaphore::new(1))) } else { None } } - pub fn with_import_map(import_map: Arc) -> Self { - Self::maybe_new(None, Some(import_map)).unwrap() + pub fn as_graph_resolver(&self) -> &dyn Resolver { + self } - pub fn as_graph_resolver(&self) -> &dyn Resolver { + pub fn as_graph_npm_resolver(&self) -> &dyn NpmResolver { self } } -impl Resolver for CliResolver { +impl Resolver for CliGraphResolver { fn default_jsx_import_source(&self) -> Option { self.maybe_default_jsx_import_source.clone() } @@ -64,12 +114,186 @@ impl Resolver for CliResolver { specifier: &str, referrer: &ModuleSpecifier, ) -> Result { - if let Some(import_map) = &self.maybe_import_map { - import_map - .resolve(specifier, referrer) - .map_err(|err| err.into()) + // attempt to resolve with the import map first + let maybe_import_map_err = match self + .maybe_import_map + .as_ref() + .map(|import_map| import_map.resolve(specifier, referrer)) + { + Some(Ok(value)) => return Ok(value), + Some(Err(err)) => Some(err), + None => None, + }; + + // then with package.json + if let Some(deps) = self.package_json_deps_installer.package_deps().as_ref() + { + if let Some(specifier) = resolve_package_json_dep(specifier, deps)? { + return Ok(specifier); + } + } + + // otherwise, surface the import map error or try resolving when has no import map + if let Some(err) = maybe_import_map_err { + Err(err.into()) + } else { + deno_graph::resolve_import(specifier, referrer).map_err(|err| err.into()) + } + } +} + +fn resolve_package_json_dep( + specifier: &str, + deps: &BTreeMap, +) -> Result, deno_core::url::ParseError> { + for (bare_specifier, req) in deps { + if specifier.starts_with(bare_specifier) { + if specifier.len() == bare_specifier.len() { + return ModuleSpecifier::parse(&format!("npm:{req}")).map(Some); + } + let path = &specifier[bare_specifier.len()..]; + if path.starts_with('/') { + return ModuleSpecifier::parse(&format!("npm:/{req}{path}")).map(Some); + } + } + } + + Ok(None) +} + +impl NpmResolver for CliGraphResolver { + fn resolve_builtin_node_module( + &self, + specifier: &ModuleSpecifier, + ) -> Result, UnknownBuiltInNodeModuleError> { + if specifier.scheme() != "node" { + return Ok(None); + } + + let module_name = specifier.path().to_string(); + if is_builtin_node_module(&module_name) { + Ok(Some(module_name)) } else { - resolve_import(specifier, referrer.as_str()).map_err(|err| err.into()) + Err(UnknownBuiltInNodeModuleError { module_name }) + } + } + + fn load_and_cache_npm_package_info( + &self, + package_name: &str, + ) -> LocalBoxFuture<'static, Result<(), String>> { + if self.no_npm { + // return it succeeded and error at the import site below + return Box::pin(future::ready(Ok(()))); + } + // this will internally cache the package information + let package_name = package_name.to_string(); + let api = self.npm_registry_api.clone(); + let deps_installer = self.package_json_deps_installer.clone(); + let maybe_sync_download_semaphore = self.sync_download_semaphore.clone(); + async move { + let permit = if let Some(semaphore) = &maybe_sync_download_semaphore { + Some(semaphore.acquire().await.unwrap()) + } else { + None + }; + + // trigger an npm install if the package name matches + // a package in the package.json + // + // todo(dsherret): ideally this would only download if a bare + // specifiy matched in the package.json, but deno_graph only + // calls this once per package name and we might resolve an + // npm specifier first which calls this, then a bare specifier + // second and that would cause this not to occur. + if deps_installer.has_package_name(&package_name) { + deps_installer + .ensure_top_level_install() + .await + .map_err(|err| format!("{err:#}"))?; + } + + let result = api + .package_info(&package_name) + .await + .map(|_| ()) + .map_err(|err| format!("{err:#}")); + drop(permit); + result + } + .boxed() + } + + fn resolve_npm( + &self, + package_req: &NpmPackageReq, + ) -> Result { + if self.no_npm { + bail!("npm specifiers were requested; but --no-npm is specified") + } + self + .npm_resolution + .resolve_package_req_as_pending(package_req) + } +} + +#[cfg(test)] +mod test { + use super::*; + + #[test] + fn test_resolve_package_json_dep() { + fn resolve( + specifier: &str, + deps: &BTreeMap, + ) -> Result, String> { + resolve_package_json_dep(specifier, deps) + .map(|s| s.map(|s| s.to_string())) + .map_err(|err| err.to_string()) } + + let deps = BTreeMap::from([ + ( + "package".to_string(), + NpmPackageReq::from_str("package@1.0").unwrap(), + ), + ( + "package-alias".to_string(), + NpmPackageReq::from_str("package@^1.2").unwrap(), + ), + ( + "@deno/test".to_string(), + NpmPackageReq::from_str("@deno/test@~0.2").unwrap(), + ), + ]); + + assert_eq!( + resolve("package", &deps).unwrap(), + Some("npm:package@1.0".to_string()), + ); + assert_eq!( + resolve("package/some_path.ts", &deps).unwrap(), + Some("npm:/package@1.0/some_path.ts".to_string()), + ); + + assert_eq!( + resolve("@deno/test", &deps).unwrap(), + Some("npm:@deno/test@~0.2".to_string()), + ); + assert_eq!( + resolve("@deno/test/some_path.ts", &deps).unwrap(), + Some("npm:/@deno/test@~0.2/some_path.ts".to_string()), + ); + // matches the start, but doesn't have the same length or a path + assert_eq!(resolve("@deno/testing", &deps).unwrap(), None,); + + // alias + assert_eq!( + resolve("package-alias", &deps).unwrap(), + Some("npm:package@^1.2".to_string()), + ); + + // non-existent bare specifier + assert_eq!(resolve("non-existent", &deps).unwrap(), None); } } diff --git a/cli/semver/mod.rs b/cli/semver/mod.rs deleted file mode 100644 index 4fedddf5e02fb8..00000000000000 --- a/cli/semver/mod.rs +++ /dev/null @@ -1,200 +0,0 @@ -// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. - -use std::cmp::Ordering; -use std::fmt; - -use deno_core::error::AnyError; -use serde::Deserialize; -use serde::Serialize; - -mod npm; -mod range; -mod specifier; - -use self::npm::parse_npm_version_req; -pub use self::range::Partial; -pub use self::range::VersionBoundKind; -pub use self::range::VersionRange; -pub use self::range::VersionRangeSet; -pub use self::range::XRange; -use self::specifier::parse_version_req_from_specifier; - -#[derive( - Clone, Debug, PartialEq, Eq, Default, Hash, Serialize, Deserialize, -)] -pub struct Version { - pub major: u64, - pub minor: u64, - pub patch: u64, - pub pre: Vec, - pub build: Vec, -} - -impl Version { - pub fn parse_from_npm(text: &str) -> Result { - npm::parse_npm_version(text) - } -} - -impl fmt::Display for Version { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "{}.{}.{}", self.major, self.minor, self.patch)?; - if !self.pre.is_empty() { - write!(f, "-")?; - for (i, part) in self.pre.iter().enumerate() { - if i > 0 { - write!(f, ".")?; - } - write!(f, "{part}")?; - } - } - if !self.build.is_empty() { - write!(f, "+")?; - for (i, part) in self.build.iter().enumerate() { - if i > 0 { - write!(f, ".")?; - } - write!(f, "{part}")?; - } - } - Ok(()) - } -} - -impl std::cmp::PartialOrd for Version { - fn partial_cmp(&self, other: &Self) -> Option { - Some(self.cmp(other)) - } -} - -impl std::cmp::Ord for Version { - fn cmp(&self, other: &Self) -> Ordering { - let cmp_result = self.major.cmp(&other.major); - if cmp_result != Ordering::Equal { - return cmp_result; - } - - let cmp_result = self.minor.cmp(&other.minor); - if cmp_result != Ordering::Equal { - return cmp_result; - } - - let cmp_result = self.patch.cmp(&other.patch); - if cmp_result != Ordering::Equal { - return cmp_result; - } - - // only compare the pre-release and not the build as node-semver does - if self.pre.is_empty() && other.pre.is_empty() { - Ordering::Equal - } else if !self.pre.is_empty() && other.pre.is_empty() { - Ordering::Less - } else if self.pre.is_empty() && !other.pre.is_empty() { - Ordering::Greater - } else { - let mut i = 0; - loop { - let a = self.pre.get(i); - let b = other.pre.get(i); - if a.is_none() && b.is_none() { - return Ordering::Equal; - } - - // https://github.com/npm/node-semver/blob/4907647d169948a53156502867ed679268063a9f/internal/identifiers.js - let a = match a { - Some(a) => a, - None => return Ordering::Less, - }; - let b = match b { - Some(b) => b, - None => return Ordering::Greater, - }; - - // prefer numbers - if let Ok(a_num) = a.parse::() { - if let Ok(b_num) = b.parse::() { - return a_num.cmp(&b_num); - } else { - return Ordering::Less; - } - } else if b.parse::().is_ok() { - return Ordering::Greater; - } - - let cmp_result = a.cmp(b); - if cmp_result != Ordering::Equal { - return cmp_result; - } - i += 1; - } - } - } -} - -pub(super) fn is_valid_tag(value: &str) -> bool { - // we use the same rules as npm tags - npm::is_valid_npm_tag(value) -} - -#[derive(Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)] -pub enum RangeSetOrTag { - RangeSet(VersionRangeSet), - Tag(String), -} - -/// A version requirement found in an npm package's dependencies. -#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)] -pub struct VersionReq { - raw_text: String, - inner: RangeSetOrTag, -} - -impl VersionReq { - /// Creates a version requirement without examining the raw text. - pub fn from_raw_text_and_inner( - raw_text: String, - inner: RangeSetOrTag, - ) -> Self { - Self { raw_text, inner } - } - - pub fn parse_from_specifier(specifier: &str) -> Result { - parse_version_req_from_specifier(specifier) - } - - pub fn parse_from_npm(text: &str) -> Result { - parse_npm_version_req(text) - } - - #[cfg(test)] - pub fn inner(&self) -> &RangeSetOrTag { - &self.inner - } - - pub fn tag(&self) -> Option<&str> { - match &self.inner { - RangeSetOrTag::RangeSet(_) => None, - RangeSetOrTag::Tag(tag) => Some(tag.as_str()), - } - } - - pub fn matches(&self, version: &Version) -> bool { - match &self.inner { - RangeSetOrTag::RangeSet(range_set) => range_set.satisfies(version), - RangeSetOrTag::Tag(_) => panic!( - "programming error: cannot use matches with a tag: {}", - self.raw_text - ), - } - } - - pub fn version_text(&self) -> &str { - &self.raw_text - } -} - -impl fmt::Display for VersionReq { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "{}", &self.raw_text) - } -} diff --git a/cli/semver/npm.rs b/cli/semver/npm.rs deleted file mode 100644 index d95861b2c0ba02..00000000000000 --- a/cli/semver/npm.rs +++ /dev/null @@ -1,985 +0,0 @@ -// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. - -use deno_core::anyhow::Context; -use deno_core::error::AnyError; -use monch::*; - -use super::Partial; -use super::RangeSetOrTag; -use super::Version; -use super::VersionBoundKind; -use super::VersionRange; -use super::VersionRangeSet; -use super::VersionReq; -use super::XRange; - -pub fn is_valid_npm_tag(value: &str) -> bool { - // a valid tag is anything that doesn't get url encoded - // https://github.com/npm/npm-package-arg/blob/103c0fda8ed8185733919c7c6c73937cfb2baf3a/lib/npa.js#L399-L401 - value - .chars() - .all(|c| c.is_alphanumeric() || matches!(c, '-' | '_' | '.' | '~')) -} - -// A lot of the below is a re-implementation of parts of https://github.com/npm/node-semver -// which is Copyright (c) Isaac Z. Schlueter and Contributors (ISC License) - -pub fn parse_npm_version(text: &str) -> Result { - let text = text.trim(); - with_failure_handling(|input| { - let (input, _) = maybe(ch('='))(input)?; // skip leading = - let (input, _) = skip_whitespace(input)?; - let (input, _) = maybe(ch('v'))(input)?; // skip leading v - let (input, _) = skip_whitespace(input)?; - let (input, major) = nr(input)?; - let (input, _) = ch('.')(input)?; - let (input, minor) = nr(input)?; - let (input, _) = ch('.')(input)?; - let (input, patch) = nr(input)?; - let (input, q) = maybe(qualifier)(input)?; - let q = q.unwrap_or_default(); - - Ok(( - input, - Version { - major, - minor, - patch, - pre: q.pre, - build: q.build, - }, - )) - })(text) - .with_context(|| format!("Invalid npm version '{text}'.")) -} - -pub fn parse_npm_version_req(text: &str) -> Result { - let text = text.trim(); - with_failure_handling(|input| { - map(inner, |inner| { - VersionReq::from_raw_text_and_inner(input.to_string(), inner) - })(input) - })(text) - .with_context(|| format!("Invalid npm version requirement '{text}'.")) -} - -// https://github.com/npm/node-semver/tree/4907647d169948a53156502867ed679268063a9f#range-grammar -// range-set ::= range ( logical-or range ) * -// logical-or ::= ( ' ' ) * '||' ( ' ' ) * -// range ::= hyphen | simple ( ' ' simple ) * | '' -// hyphen ::= partial ' - ' partial -// simple ::= primitive | partial | tilde | caret -// primitive ::= ( '<' | '>' | '>=' | '<=' | '=' ) partial -// partial ::= xr ( '.' xr ( '.' xr qualifier ? )? )? -// xr ::= 'x' | 'X' | '*' | nr -// nr ::= '0' | ['1'-'9'] ( ['0'-'9'] ) * -// tilde ::= '~' partial -// caret ::= '^' partial -// qualifier ::= ( '-' pre )? ( '+' build )? -// pre ::= parts -// build ::= parts -// parts ::= part ( '.' part ) * -// part ::= nr | [-0-9A-Za-z]+ - -// range-set ::= range ( logical-or range ) * -fn inner(input: &str) -> ParseResult { - if input.is_empty() { - return Ok(( - input, - RangeSetOrTag::RangeSet(VersionRangeSet(vec![VersionRange::all()])), - )); - } - - let (input, mut ranges) = - separated_list(range_or_invalid, logical_or)(input)?; - - if ranges.len() == 1 { - match ranges.remove(0) { - RangeOrInvalid::Invalid(invalid) => { - if is_valid_npm_tag(invalid.text) { - return Ok((input, RangeSetOrTag::Tag(invalid.text.to_string()))); - } else { - return Err(invalid.failure); - } - } - RangeOrInvalid::Range(range) => { - // add it back - ranges.push(RangeOrInvalid::Range(range)); - } - } - } - - let ranges = ranges - .into_iter() - .filter_map(|r| r.into_range()) - .collect::>(); - Ok((input, RangeSetOrTag::RangeSet(VersionRangeSet(ranges)))) -} - -enum RangeOrInvalid<'a> { - Range(VersionRange), - Invalid(InvalidRange<'a>), -} - -impl<'a> RangeOrInvalid<'a> { - pub fn into_range(self) -> Option { - match self { - RangeOrInvalid::Range(r) => { - if r.is_none() { - None - } else { - Some(r) - } - } - RangeOrInvalid::Invalid(_) => None, - } - } -} - -struct InvalidRange<'a> { - failure: ParseError<'a>, - text: &'a str, -} - -fn range_or_invalid(input: &str) -> ParseResult { - let range_result = - map_res(map(range, RangeOrInvalid::Range), |result| match result { - Ok((input, range)) => { - let is_end = input.is_empty() || logical_or(input).is_ok(); - if is_end { - Ok((input, range)) - } else { - ParseError::backtrace() - } - } - Err(err) => Err(err), - })(input); - match range_result { - Ok(result) => Ok(result), - Err(failure) => { - let (input, text) = invalid_range(input)?; - Ok(( - input, - RangeOrInvalid::Invalid(InvalidRange { failure, text }), - )) - } - } -} - -fn invalid_range(input: &str) -> ParseResult<&str> { - let end_index = input.find("||").unwrap_or(input.len()); - let text = input[..end_index].trim(); - Ok((&input[end_index..], text)) -} - -// range ::= hyphen | simple ( ' ' simple ) * | '' -fn range(input: &str) -> ParseResult { - or( - map(hyphen, |hyphen| VersionRange { - start: hyphen.start.as_lower_bound(), - end: hyphen.end.as_upper_bound(), - }), - map(separated_list(simple, whitespace), |ranges| { - let mut final_range = VersionRange::all(); - for range in ranges { - final_range = final_range.clamp(&range); - } - final_range - }), - )(input) -} - -#[derive(Debug, Clone)] -struct Hyphen { - start: Partial, - end: Partial, -} - -// hyphen ::= partial ' - ' partial -fn hyphen(input: &str) -> ParseResult { - let (input, first) = partial(input)?; - let (input, _) = whitespace(input)?; - let (input, _) = tag("-")(input)?; - let (input, _) = whitespace(input)?; - let (input, second) = partial(input)?; - Ok(( - input, - Hyphen { - start: first, - end: second, - }, - )) -} - -// logical-or ::= ( ' ' ) * '||' ( ' ' ) * -fn logical_or(input: &str) -> ParseResult<&str> { - delimited(skip_whitespace, tag("||"), skip_whitespace)(input) -} - -fn skip_whitespace_or_v(input: &str) -> ParseResult<()> { - map( - pair(skip_whitespace, pair(maybe(ch('v')), skip_whitespace)), - |_| (), - )(input) -} - -// simple ::= primitive | partial | tilde | caret -fn simple(input: &str) -> ParseResult { - or4( - map(preceded(tilde, partial), |partial| { - partial.as_tilde_version_range() - }), - map(preceded(caret, partial), |partial| { - partial.as_caret_version_range() - }), - map(primitive, |primitive| { - let partial = primitive.partial; - match primitive.kind { - PrimitiveKind::Equal => partial.as_equal_range(), - PrimitiveKind::GreaterThan => { - partial.as_greater_than(VersionBoundKind::Exclusive) - } - PrimitiveKind::GreaterThanOrEqual => { - partial.as_greater_than(VersionBoundKind::Inclusive) - } - PrimitiveKind::LessThan => { - partial.as_less_than(VersionBoundKind::Exclusive) - } - PrimitiveKind::LessThanOrEqual => { - partial.as_less_than(VersionBoundKind::Inclusive) - } - } - }), - map(partial, |partial| partial.as_equal_range()), - )(input) -} - -fn tilde(input: &str) -> ParseResult<()> { - fn raw_tilde(input: &str) -> ParseResult<()> { - map(pair(or(tag("~>"), tag("~")), skip_whitespace_or_v), |_| ())(input) - } - - or( - preceded(terminated(primitive_kind, whitespace), raw_tilde), - raw_tilde, - )(input) -} - -fn caret(input: &str) -> ParseResult<()> { - fn raw_caret(input: &str) -> ParseResult<()> { - map(pair(ch('^'), skip_whitespace_or_v), |_| ())(input) - } - - or( - preceded(terminated(primitive_kind, whitespace), raw_caret), - raw_caret, - )(input) -} - -#[derive(Debug, Clone, Copy)] -enum PrimitiveKind { - GreaterThan, - LessThan, - GreaterThanOrEqual, - LessThanOrEqual, - Equal, -} - -#[derive(Debug, Clone)] -struct Primitive { - kind: PrimitiveKind, - partial: Partial, -} - -fn primitive(input: &str) -> ParseResult { - let (input, kind) = primitive_kind(input)?; - let (input, _) = skip_whitespace(input)?; - let (input, partial) = partial(input)?; - Ok((input, Primitive { kind, partial })) -} - -fn primitive_kind(input: &str) -> ParseResult { - or5( - map(tag(">="), |_| PrimitiveKind::GreaterThanOrEqual), - map(tag("<="), |_| PrimitiveKind::LessThanOrEqual), - map(ch('<'), |_| PrimitiveKind::LessThan), - map(ch('>'), |_| PrimitiveKind::GreaterThan), - map(ch('='), |_| PrimitiveKind::Equal), - )(input) -} - -// partial ::= xr ( '.' xr ( '.' xr qualifier ? )? )? -fn partial(input: &str) -> ParseResult { - let (input, _) = maybe(ch('v'))(input)?; // skip leading v - let (input, major) = xr()(input)?; - let (input, maybe_minor) = maybe(preceded(ch('.'), xr()))(input)?; - let (input, maybe_patch) = if maybe_minor.is_some() { - maybe(preceded(ch('.'), xr()))(input)? - } else { - (input, None) - }; - let (input, qual) = if maybe_patch.is_some() { - maybe(qualifier)(input)? - } else { - (input, None) - }; - let qual = qual.unwrap_or_default(); - Ok(( - input, - Partial { - major, - minor: maybe_minor.unwrap_or(XRange::Wildcard), - patch: maybe_patch.unwrap_or(XRange::Wildcard), - pre: qual.pre, - build: qual.build, - }, - )) -} - -// xr ::= 'x' | 'X' | '*' | nr -fn xr<'a>() -> impl Fn(&'a str) -> ParseResult<'a, XRange> { - or( - map(or3(tag("x"), tag("X"), tag("*")), |_| XRange::Wildcard), - map(nr, XRange::Val), - ) -} - -// nr ::= '0' | ['1'-'9'] ( ['0'-'9'] ) * -fn nr(input: &str) -> ParseResult { - // we do loose parsing to support people doing stuff like 01.02.03 - let (input, result) = - if_not_empty(substring(skip_while(|c| c.is_ascii_digit())))(input)?; - let val = match result.parse::() { - Ok(val) => val, - Err(err) => { - return ParseError::fail( - input, - format!("Error parsing '{result}' to u64.\n\n{err:#}"), - ) - } - }; - Ok((input, val)) -} - -#[derive(Debug, Clone, Default)] -struct Qualifier { - pre: Vec, - build: Vec, -} - -// qualifier ::= ( '-' pre )? ( '+' build )? -fn qualifier(input: &str) -> ParseResult { - let (input, pre_parts) = maybe(pre)(input)?; - let (input, build_parts) = maybe(build)(input)?; - Ok(( - input, - Qualifier { - pre: pre_parts.unwrap_or_default(), - build: build_parts.unwrap_or_default(), - }, - )) -} - -// pre ::= parts -fn pre(input: &str) -> ParseResult> { - preceded(maybe(ch('-')), parts)(input) -} - -// build ::= parts -fn build(input: &str) -> ParseResult> { - preceded(ch('+'), parts)(input) -} - -// parts ::= part ( '.' part ) * -fn parts(input: &str) -> ParseResult> { - if_not_empty(map(separated_list(part, ch('.')), |text| { - text.into_iter().map(ToOwned::to_owned).collect() - }))(input) -} - -// part ::= nr | [-0-9A-Za-z]+ -fn part(input: &str) -> ParseResult<&str> { - // nr is in the other set, so don't bother checking for it - if_true( - take_while(|c| c.is_ascii_alphanumeric() || c == '-'), - |result| !result.is_empty(), - )(input) -} - -#[cfg(test)] -mod tests { - use pretty_assertions::assert_eq; - use std::cmp::Ordering; - - use super::*; - - struct NpmVersionReqTester(VersionReq); - - impl NpmVersionReqTester { - fn new(text: &str) -> Self { - Self(parse_npm_version_req(text).unwrap()) - } - - fn matches(&self, version: &str) -> bool { - self.0.matches(&parse_npm_version(version).unwrap()) - } - } - - #[test] - pub fn npm_version_req_with_v() { - assert!(parse_npm_version_req("v1.0.0").is_ok()); - } - - #[test] - pub fn npm_version_req_exact() { - let tester = NpmVersionReqTester::new("2.1.2"); - assert!(!tester.matches("2.1.1")); - assert!(tester.matches("2.1.2")); - assert!(!tester.matches("2.1.3")); - - let tester = NpmVersionReqTester::new("2.1.2 || 2.1.5"); - assert!(!tester.matches("2.1.1")); - assert!(tester.matches("2.1.2")); - assert!(!tester.matches("2.1.3")); - assert!(!tester.matches("2.1.4")); - assert!(tester.matches("2.1.5")); - assert!(!tester.matches("2.1.6")); - } - - #[test] - pub fn npm_version_req_minor() { - let tester = NpmVersionReqTester::new("1.1"); - assert!(!tester.matches("1.0.0")); - assert!(tester.matches("1.1.0")); - assert!(tester.matches("1.1.1")); - assert!(!tester.matches("1.2.0")); - assert!(!tester.matches("1.2.1")); - } - - #[test] - pub fn npm_version_req_ranges() { - let tester = NpmVersionReqTester::new( - ">= 2.1.2 < 3.0.0 || 5.x || ignored-invalid-range || $#$%^#$^#$^%@#$%SDF|||", - ); - assert!(!tester.matches("2.1.1")); - assert!(tester.matches("2.1.2")); - assert!(tester.matches("2.9.9")); - assert!(!tester.matches("3.0.0")); - assert!(tester.matches("5.0.0")); - assert!(tester.matches("5.1.0")); - assert!(!tester.matches("6.1.0")); - } - - #[test] - pub fn npm_version_req_with_tag() { - let req = parse_npm_version_req("latest").unwrap(); - assert_eq!(req.tag(), Some("latest")); - } - - macro_rules! assert_cmp { - ($a:expr, $b:expr, $expected:expr) => { - assert_eq!( - $a.cmp(&$b), - $expected, - "expected {} to be {:?} {}", - $a, - $expected, - $b - ); - }; - } - - macro_rules! test_compare { - ($a:expr, $b:expr, $expected:expr) => { - let a = parse_npm_version($a).unwrap(); - let b = parse_npm_version($b).unwrap(); - assert_cmp!(a, b, $expected); - }; - } - - #[test] - fn version_compare() { - test_compare!("1.2.3", "2.3.4", Ordering::Less); - test_compare!("1.2.3", "1.2.4", Ordering::Less); - test_compare!("1.2.3", "1.2.3", Ordering::Equal); - test_compare!("1.2.3", "1.2.2", Ordering::Greater); - test_compare!("1.2.3", "1.1.5", Ordering::Greater); - } - - #[test] - fn version_compare_equal() { - // https://github.com/npm/node-semver/blob/bce42589d33e1a99454530a8fd52c7178e2b11c1/test/fixtures/equality.js - let fixtures = &[ - ("1.2.3", "v1.2.3"), - ("1.2.3", "=1.2.3"), - ("1.2.3", "v 1.2.3"), - ("1.2.3", "= 1.2.3"), - ("1.2.3", " v1.2.3"), - ("1.2.3", " =1.2.3"), - ("1.2.3", " v 1.2.3"), - ("1.2.3", " = 1.2.3"), - ("1.2.3-0", "v1.2.3-0"), - ("1.2.3-0", "=1.2.3-0"), - ("1.2.3-0", "v 1.2.3-0"), - ("1.2.3-0", "= 1.2.3-0"), - ("1.2.3-0", " v1.2.3-0"), - ("1.2.3-0", " =1.2.3-0"), - ("1.2.3-0", " v 1.2.3-0"), - ("1.2.3-0", " = 1.2.3-0"), - ("1.2.3-1", "v1.2.3-1"), - ("1.2.3-1", "=1.2.3-1"), - ("1.2.3-1", "v 1.2.3-1"), - ("1.2.3-1", "= 1.2.3-1"), - ("1.2.3-1", " v1.2.3-1"), - ("1.2.3-1", " =1.2.3-1"), - ("1.2.3-1", " v 1.2.3-1"), - ("1.2.3-1", " = 1.2.3-1"), - ("1.2.3-beta", "v1.2.3-beta"), - ("1.2.3-beta", "=1.2.3-beta"), - ("1.2.3-beta", "v 1.2.3-beta"), - ("1.2.3-beta", "= 1.2.3-beta"), - ("1.2.3-beta", " v1.2.3-beta"), - ("1.2.3-beta", " =1.2.3-beta"), - ("1.2.3-beta", " v 1.2.3-beta"), - ("1.2.3-beta", " = 1.2.3-beta"), - ("1.2.3-beta+build", " = 1.2.3-beta+otherbuild"), - ("1.2.3+build", " = 1.2.3+otherbuild"), - ("1.2.3-beta+build", "1.2.3-beta+otherbuild"), - ("1.2.3+build", "1.2.3+otherbuild"), - (" v1.2.3+build", "1.2.3+otherbuild"), - ]; - for (a, b) in fixtures { - test_compare!(a, b, Ordering::Equal); - } - } - - #[test] - fn version_comparisons_test() { - // https://github.com/npm/node-semver/blob/bce42589d33e1a99454530a8fd52c7178e2b11c1/test/fixtures/comparisons.js - let fixtures = &[ - ("0.0.0", "0.0.0-foo"), - ("0.0.1", "0.0.0"), - ("1.0.0", "0.9.9"), - ("0.10.0", "0.9.0"), - ("0.99.0", "0.10.0"), - ("2.0.0", "1.2.3"), - ("v0.0.0", "0.0.0-foo"), - ("v0.0.1", "0.0.0"), - ("v1.0.0", "0.9.9"), - ("v0.10.0", "0.9.0"), - ("v0.99.0", "0.10.0"), - ("v2.0.0", "1.2.3"), - ("0.0.0", "v0.0.0-foo"), - ("0.0.1", "v0.0.0"), - ("1.0.0", "v0.9.9"), - ("0.10.0", "v0.9.0"), - ("0.99.0", "v0.10.0"), - ("2.0.0", "v1.2.3"), - ("1.2.3", "1.2.3-asdf"), - ("1.2.3", "1.2.3-4"), - ("1.2.3", "1.2.3-4-foo"), - ("1.2.3-5-foo", "1.2.3-5"), - ("1.2.3-5", "1.2.3-4"), - ("1.2.3-5-foo", "1.2.3-5-Foo"), - ("3.0.0", "2.7.2+asdf"), - ("1.2.3-a.10", "1.2.3-a.5"), - ("1.2.3-a.b", "1.2.3-a.5"), - ("1.2.3-a.b", "1.2.3-a"), - ("1.2.3-a.b.c.10.d.5", "1.2.3-a.b.c.5.d.100"), - ("1.2.3-r2", "1.2.3-r100"), - ("1.2.3-r100", "1.2.3-R2"), - ]; - for (a, b) in fixtures { - let a = parse_npm_version(a).unwrap(); - let b = parse_npm_version(b).unwrap(); - assert_cmp!(a, b, Ordering::Greater); - assert_cmp!(b, a, Ordering::Less); - assert_cmp!(a, a, Ordering::Equal); - assert_cmp!(b, b, Ordering::Equal); - } - } - - #[test] - fn range_parse() { - // https://github.com/npm/node-semver/blob/4907647d169948a53156502867ed679268063a9f/test/fixtures/range-parse.js - let fixtures = &[ - ("1.0.0 - 2.0.0", ">=1.0.0 <=2.0.0"), - ("1 - 2", ">=1.0.0 <3.0.0-0"), - ("1.0 - 2.0", ">=1.0.0 <2.1.0-0"), - ("1.0.0", "1.0.0"), - (">=*", "*"), - ("", "*"), - ("*", "*"), - ("*", "*"), - (">=1.0.0", ">=1.0.0"), - (">1.0.0", ">1.0.0"), - ("<=2.0.0", "<=2.0.0"), - ("1", ">=1.0.0 <2.0.0-0"), - ("<=2.0.0", "<=2.0.0"), - ("<=2.0.0", "<=2.0.0"), - ("<2.0.0", "<2.0.0"), - ("<2.0.0", "<2.0.0"), - (">= 1.0.0", ">=1.0.0"), - (">= 1.0.0", ">=1.0.0"), - (">= 1.0.0", ">=1.0.0"), - ("> 1.0.0", ">1.0.0"), - ("> 1.0.0", ">1.0.0"), - ("<= 2.0.0", "<=2.0.0"), - ("<= 2.0.0", "<=2.0.0"), - ("<= 2.0.0", "<=2.0.0"), - ("< 2.0.0", "<2.0.0"), - ("<\t2.0.0", "<2.0.0"), - (">=0.1.97", ">=0.1.97"), - (">=0.1.97", ">=0.1.97"), - ("0.1.20 || 1.2.4", "0.1.20||1.2.4"), - (">=0.2.3 || <0.0.1", ">=0.2.3||<0.0.1"), - (">=0.2.3 || <0.0.1", ">=0.2.3||<0.0.1"), - (">=0.2.3 || <0.0.1", ">=0.2.3||<0.0.1"), - ("||", "*"), - ("2.x.x", ">=2.0.0 <3.0.0-0"), - ("1.2.x", ">=1.2.0 <1.3.0-0"), - ("1.2.x || 2.x", ">=1.2.0 <1.3.0-0||>=2.0.0 <3.0.0-0"), - ("1.2.x || 2.x", ">=1.2.0 <1.3.0-0||>=2.0.0 <3.0.0-0"), - ("x", "*"), - ("2.*.*", ">=2.0.0 <3.0.0-0"), - ("1.2.*", ">=1.2.0 <1.3.0-0"), - ("1.2.* || 2.*", ">=1.2.0 <1.3.0-0||>=2.0.0 <3.0.0-0"), - ("*", "*"), - ("2", ">=2.0.0 <3.0.0-0"), - ("2.3", ">=2.3.0 <2.4.0-0"), - ("~2.4", ">=2.4.0 <2.5.0-0"), - ("~2.4", ">=2.4.0 <2.5.0-0"), - ("~>3.2.1", ">=3.2.1 <3.3.0-0"), - ("~1", ">=1.0.0 <2.0.0-0"), - ("~>1", ">=1.0.0 <2.0.0-0"), - ("~> 1", ">=1.0.0 <2.0.0-0"), - ("~1.0", ">=1.0.0 <1.1.0-0"), - ("~ 1.0", ">=1.0.0 <1.1.0-0"), - ("^0", "<1.0.0-0"), - ("^ 1", ">=1.0.0 <2.0.0-0"), - ("^0.1", ">=0.1.0 <0.2.0-0"), - ("^1.0", ">=1.0.0 <2.0.0-0"), - ("^1.2", ">=1.2.0 <2.0.0-0"), - ("^0.0.1", ">=0.0.1 <0.0.2-0"), - ("^0.0.1-beta", ">=0.0.1-beta <0.0.2-0"), - ("^0.1.2", ">=0.1.2 <0.2.0-0"), - ("^1.2.3", ">=1.2.3 <2.0.0-0"), - ("^1.2.3-beta.4", ">=1.2.3-beta.4 <2.0.0-0"), - ("<1", "<1.0.0-0"), - ("< 1", "<1.0.0-0"), - (">=1", ">=1.0.0"), - (">= 1", ">=1.0.0"), - ("<1.2", "<1.2.0-0"), - ("< 1.2", "<1.2.0-0"), - ("1", ">=1.0.0 <2.0.0-0"), - ("^ 1.2 ^ 1", ">=1.2.0 <2.0.0-0 >=1.0.0"), - ("1.2 - 3.4.5", ">=1.2.0 <=3.4.5"), - ("1.2.3 - 3.4", ">=1.2.3 <3.5.0-0"), - ("1.2 - 3.4", ">=1.2.0 <3.5.0-0"), - (">1", ">=2.0.0"), - (">1.2", ">=1.3.0"), - (">X", "<0.0.0-0"), - ("* 2.x", "<0.0.0-0"), - (">x 2.x || * || 01.02.03", ">1.2.3"), - ("~1.2.3beta", ">=1.2.3-beta <1.3.0-0"), - (">=09090", ">=9090.0.0"), - ]; - for (range_text, expected) in fixtures { - let range = parse_npm_version_req(range_text).unwrap(); - let expected_range = parse_npm_version_req(expected).unwrap(); - assert_eq!( - range.inner(), - expected_range.inner(), - "failed for {} and {}", - range_text, - expected - ); - } - } - - #[test] - fn range_satisfies() { - // https://github.com/npm/node-semver/blob/4907647d169948a53156502867ed679268063a9f/test/fixtures/range-include.js - let fixtures = &[ - ("1.0.0 - 2.0.0", "1.2.3"), - ("^1.2.3+build", "1.2.3"), - ("^1.2.3+build", "1.3.0"), - ("1.2.3-pre+asdf - 2.4.3-pre+asdf", "1.2.3"), - ("1.2.3pre+asdf - 2.4.3-pre+asdf", "1.2.3"), - ("1.2.3-pre+asdf - 2.4.3pre+asdf", "1.2.3"), - ("1.2.3pre+asdf - 2.4.3pre+asdf", "1.2.3"), - ("1.2.3-pre+asdf - 2.4.3-pre+asdf", "1.2.3-pre.2"), - ("1.2.3-pre+asdf - 2.4.3-pre+asdf", "2.4.3-alpha"), - ("1.2.3+asdf - 2.4.3+asdf", "1.2.3"), - ("1.0.0", "1.0.0"), - (">=*", "0.2.4"), - ("", "1.0.0"), - ("*", "1.2.3"), - ("*", "v1.2.3"), - (">=1.0.0", "1.0.0"), - (">=1.0.0", "1.0.1"), - (">=1.0.0", "1.1.0"), - (">1.0.0", "1.0.1"), - (">1.0.0", "1.1.0"), - ("<=2.0.0", "2.0.0"), - ("<=2.0.0", "1.9999.9999"), - ("<=2.0.0", "0.2.9"), - ("<2.0.0", "1.9999.9999"), - ("<2.0.0", "0.2.9"), - (">= 1.0.0", "1.0.0"), - (">= 1.0.0", "1.0.1"), - (">= 1.0.0", "1.1.0"), - ("> 1.0.0", "1.0.1"), - ("> 1.0.0", "1.1.0"), - ("<= 2.0.0", "2.0.0"), - ("<= 2.0.0", "1.9999.9999"), - ("<= 2.0.0", "0.2.9"), - ("< 2.0.0", "1.9999.9999"), - ("<\t2.0.0", "0.2.9"), - (">=0.1.97", "v0.1.97"), - (">=0.1.97", "0.1.97"), - ("0.1.20 || 1.2.4", "1.2.4"), - (">=0.2.3 || <0.0.1", "0.0.0"), - (">=0.2.3 || <0.0.1", "0.2.3"), - (">=0.2.3 || <0.0.1", "0.2.4"), - ("||", "1.3.4"), - ("2.x.x", "2.1.3"), - ("1.2.x", "1.2.3"), - ("1.2.x || 2.x", "2.1.3"), - ("1.2.x || 2.x", "1.2.3"), - ("x", "1.2.3"), - ("2.*.*", "2.1.3"), - ("1.2.*", "1.2.3"), - ("1.2.* || 2.*", "2.1.3"), - ("1.2.* || 2.*", "1.2.3"), - ("*", "1.2.3"), - ("2", "2.1.2"), - ("2.3", "2.3.1"), - ("~0.0.1", "0.0.1"), - ("~0.0.1", "0.0.2"), - ("~x", "0.0.9"), // >=2.4.0 <2.5.0 - ("~2", "2.0.9"), // >=2.4.0 <2.5.0 - ("~2.4", "2.4.0"), // >=2.4.0 <2.5.0 - ("~2.4", "2.4.5"), - ("~>3.2.1", "3.2.2"), // >=3.2.1 <3.3.0, - ("~1", "1.2.3"), // >=1.0.0 <2.0.0 - ("~>1", "1.2.3"), - ("~> 1", "1.2.3"), - ("~1.0", "1.0.2"), // >=1.0.0 <1.1.0, - ("~ 1.0", "1.0.2"), - ("~ 1.0.3", "1.0.12"), - ("~ 1.0.3alpha", "1.0.12"), - (">=1", "1.0.0"), - (">= 1", "1.0.0"), - ("<1.2", "1.1.1"), - ("< 1.2", "1.1.1"), - ("~v0.5.4-pre", "0.5.5"), - ("~v0.5.4-pre", "0.5.4"), - ("=0.7.x", "0.7.2"), - ("<=0.7.x", "0.7.2"), - (">=0.7.x", "0.7.2"), - ("<=0.7.x", "0.6.2"), - ("~1.2.1 >=1.2.3", "1.2.3"), - ("~1.2.1 =1.2.3", "1.2.3"), - ("~1.2.1 1.2.3", "1.2.3"), - ("~1.2.1 >=1.2.3 1.2.3", "1.2.3"), - ("~1.2.1 1.2.3 >=1.2.3", "1.2.3"), - ("~1.2.1 1.2.3", "1.2.3"), - (">=1.2.1 1.2.3", "1.2.3"), - ("1.2.3 >=1.2.1", "1.2.3"), - (">=1.2.3 >=1.2.1", "1.2.3"), - (">=1.2.1 >=1.2.3", "1.2.3"), - (">=1.2", "1.2.8"), - ("^1.2.3", "1.8.1"), - ("^0.1.2", "0.1.2"), - ("^0.1", "0.1.2"), - ("^0.0.1", "0.0.1"), - ("^1.2", "1.4.2"), - ("^1.2 ^1", "1.4.2"), - ("^1.2.3-alpha", "1.2.3-pre"), - ("^1.2.0-alpha", "1.2.0-pre"), - ("^0.0.1-alpha", "0.0.1-beta"), - ("^0.0.1-alpha", "0.0.1"), - ("^0.1.1-alpha", "0.1.1-beta"), - ("^x", "1.2.3"), - ("x - 1.0.0", "0.9.7"), - ("x - 1.x", "0.9.7"), - ("1.0.0 - x", "1.9.7"), - ("1.x - x", "1.9.7"), - ("<=7.x", "7.9.9"), - // additional tests - ("1.0.0-alpha.13", "1.0.0-alpha.13"), - ]; - for (req_text, version_text) in fixtures { - let req = parse_npm_version_req(req_text).unwrap(); - let version = parse_npm_version(version_text).unwrap(); - assert!( - req.matches(&version), - "Checking {req_text} satisfies {version_text}" - ); - } - } - - #[test] - fn range_not_satisfies() { - let fixtures = &[ - ("1.0.0 - 2.0.0", "2.2.3"), - ("1.2.3+asdf - 2.4.3+asdf", "1.2.3-pre.2"), - ("1.2.3+asdf - 2.4.3+asdf", "2.4.3-alpha"), - ("^1.2.3+build", "2.0.0"), - ("^1.2.3+build", "1.2.0"), - ("^1.2.3", "1.2.3-pre"), - ("^1.2", "1.2.0-pre"), - (">1.2", "1.3.0-beta"), - ("<=1.2.3", "1.2.3-beta"), - ("^1.2.3", "1.2.3-beta"), - ("=0.7.x", "0.7.0-asdf"), - (">=0.7.x", "0.7.0-asdf"), - ("<=0.7.x", "0.7.0-asdf"), - ("1", "1.0.0beta"), - ("<1", "1.0.0beta"), - ("< 1", "1.0.0beta"), - ("1.0.0", "1.0.1"), - (">=1.0.0", "0.0.0"), - (">=1.0.0", "0.0.1"), - (">=1.0.0", "0.1.0"), - (">1.0.0", "0.0.1"), - (">1.0.0", "0.1.0"), - ("<=2.0.0", "3.0.0"), - ("<=2.0.0", "2.9999.9999"), - ("<=2.0.0", "2.2.9"), - ("<2.0.0", "2.9999.9999"), - ("<2.0.0", "2.2.9"), - (">=0.1.97", "v0.1.93"), - (">=0.1.97", "0.1.93"), - ("0.1.20 || 1.2.4", "1.2.3"), - (">=0.2.3 || <0.0.1", "0.0.3"), - (">=0.2.3 || <0.0.1", "0.2.2"), - ("2.x.x", "1.1.3"), - ("2.x.x", "3.1.3"), - ("1.2.x", "1.3.3"), - ("1.2.x || 2.x", "3.1.3"), - ("1.2.x || 2.x", "1.1.3"), - ("2.*.*", "1.1.3"), - ("2.*.*", "3.1.3"), - ("1.2.*", "1.3.3"), - ("1.2.* || 2.*", "3.1.3"), - ("1.2.* || 2.*", "1.1.3"), - ("2", "1.1.2"), - ("2.3", "2.4.1"), - ("~0.0.1", "0.1.0-alpha"), - ("~0.0.1", "0.1.0"), - ("~2.4", "2.5.0"), // >=2.4.0 <2.5.0 - ("~2.4", "2.3.9"), - ("~>3.2.1", "3.3.2"), // >=3.2.1 <3.3.0 - ("~>3.2.1", "3.2.0"), // >=3.2.1 <3.3.0 - ("~1", "0.2.3"), // >=1.0.0 <2.0.0 - ("~>1", "2.2.3"), - ("~1.0", "1.1.0"), // >=1.0.0 <1.1.0 - ("<1", "1.0.0"), - (">=1.2", "1.1.1"), - ("1", "2.0.0beta"), - ("~v0.5.4-beta", "0.5.4-alpha"), - ("=0.7.x", "0.8.2"), - (">=0.7.x", "0.6.2"), - ("<0.7.x", "0.7.2"), - ("<1.2.3", "1.2.3-beta"), - ("=1.2.3", "1.2.3-beta"), - (">1.2", "1.2.8"), - ("^0.0.1", "0.0.2-alpha"), - ("^0.0.1", "0.0.2"), - ("^1.2.3", "2.0.0-alpha"), - ("^1.2.3", "1.2.2"), - ("^1.2", "1.1.9"), - ("*", "v1.2.3-foo"), - ("^1.0.0", "2.0.0-rc1"), - ("1 - 2", "2.0.0-pre"), - ("1 - 2", "1.0.0-pre"), - ("1.0 - 2", "1.0.0-pre"), - ("1.1.x", "1.0.0-a"), - ("1.1.x", "1.1.0-a"), - ("1.1.x", "1.2.0-a"), - ("1.x", "1.0.0-a"), - ("1.x", "1.1.0-a"), - ("1.x", "1.2.0-a"), - (">=1.0.0 <1.1.0", "1.1.0"), - (">=1.0.0 <1.1.0", "1.1.0-pre"), - (">=1.0.0 <1.1.0-pre", "1.1.0-pre"), - ]; - - for (req_text, version_text) in fixtures { - let req = parse_npm_version_req(req_text).unwrap(); - let version = parse_npm_version(version_text).unwrap(); - assert!( - !req.matches(&version), - "Checking {req_text} not satisfies {version_text}" - ); - } - } - - #[test] - fn range_primitive_kind_beside_caret_or_tilde_with_whitespace() { - // node semver should have enforced strictness, but it didn't - // and so we end up with a system that acts this way - let fixtures = &[ - (">= ^1.2.3", "1.2.3", true), - (">= ^1.2.3", "1.2.4", true), - (">= ^1.2.3", "1.9.3", true), - (">= ^1.2.3", "2.0.0", false), - (">= ^1.2.3", "1.2.2", false), - // this is considered the same as the above by node semver - ("> ^1.2.3", "1.2.3", true), - ("> ^1.2.3", "1.2.4", true), - ("> ^1.2.3", "1.9.3", true), - ("> ^1.2.3", "2.0.0", false), - ("> ^1.2.3", "1.2.2", false), - // this is also considered the same - ("< ^1.2.3", "1.2.3", true), - ("< ^1.2.3", "1.2.4", true), - ("< ^1.2.3", "1.9.3", true), - ("< ^1.2.3", "2.0.0", false), - ("< ^1.2.3", "1.2.2", false), - // same with this - ("<= ^1.2.3", "1.2.3", true), - ("<= ^1.2.3", "1.2.4", true), - ("<= ^1.2.3", "1.9.3", true), - ("<= ^1.2.3", "2.0.0", false), - ("<= ^1.2.3", "1.2.2", false), - // now try a ~, which should work the same as above, but for ~ - ("<= ~1.2.3", "1.2.3", true), - ("<= ~1.2.3", "1.2.4", true), - ("<= ~1.2.3", "1.9.3", false), - ("<= ~1.2.3", "2.0.0", false), - ("<= ~1.2.3", "1.2.2", false), - ]; - - for (req_text, version_text, satisfies) in fixtures { - let req = parse_npm_version_req(req_text).unwrap(); - let version = parse_npm_version(version_text).unwrap(); - assert_eq!( - req.matches(&version), - *satisfies, - "Checking {} {} satisfies {}", - req_text, - if *satisfies { "true" } else { "false" }, - version_text - ); - } - } - - #[test] - fn range_primitive_kind_beside_caret_or_tilde_no_whitespace() { - let fixtures = &[ - ">=^1.2.3", ">^1.2.3", "<^1.2.3", "<=^1.2.3", ">=~1.2.3", ">~1.2.3", - "<~1.2.3", "<=~1.2.3", - ]; - - for req_text in fixtures { - // when it has no space, this is considered invalid - // by node semver so we should error - assert!(parse_npm_version_req(req_text).is_err()); - } - } -} diff --git a/cli/semver/range.rs b/cli/semver/range.rs deleted file mode 100644 index ab202b60e17f5f..00000000000000 --- a/cli/semver/range.rs +++ /dev/null @@ -1,509 +0,0 @@ -// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. - -use std::cmp::Ordering; - -use serde::Deserialize; -use serde::Serialize; - -use super::Version; - -/// Collection of ranges. -#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)] -pub struct VersionRangeSet(pub Vec); - -impl VersionRangeSet { - pub fn satisfies(&self, version: &Version) -> bool { - self.0.iter().any(|r| r.satisfies(version)) - } -} - -#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)] -pub enum RangeBound { - Version(VersionBound), - Unbounded, // matches everything -} - -impl RangeBound { - pub fn inclusive(version: Version) -> Self { - Self::version(VersionBoundKind::Inclusive, version) - } - - pub fn exclusive(version: Version) -> Self { - Self::version(VersionBoundKind::Exclusive, version) - } - - pub fn version(kind: VersionBoundKind, version: Version) -> Self { - Self::Version(VersionBound::new(kind, version)) - } - - pub fn clamp_start(&self, other: &RangeBound) -> RangeBound { - match &self { - RangeBound::Unbounded => other.clone(), - RangeBound::Version(self_bound) => RangeBound::Version(match &other { - RangeBound::Unbounded => self_bound.clone(), - RangeBound::Version(other_bound) => { - match self_bound.version.cmp(&other_bound.version) { - Ordering::Greater => self_bound.clone(), - Ordering::Less => other_bound.clone(), - Ordering::Equal => match self_bound.kind { - VersionBoundKind::Exclusive => self_bound.clone(), - VersionBoundKind::Inclusive => other_bound.clone(), - }, - } - } - }), - } - } - - pub fn clamp_end(&self, other: &RangeBound) -> RangeBound { - match &self { - RangeBound::Unbounded => other.clone(), - RangeBound::Version(self_bound) => { - RangeBound::Version(match other { - RangeBound::Unbounded => self_bound.clone(), - RangeBound::Version(other_bound) => { - match self_bound.version.cmp(&other_bound.version) { - // difference with above is the next two lines are switched - Ordering::Greater => other_bound.clone(), - Ordering::Less => self_bound.clone(), - Ordering::Equal => match self_bound.kind { - VersionBoundKind::Exclusive => self_bound.clone(), - VersionBoundKind::Inclusive => other_bound.clone(), - }, - } - } - }) - } - } - } - - pub fn has_pre_with_exact_major_minor_patch( - &self, - version: &Version, - ) -> bool { - if let RangeBound::Version(self_version) = &self { - if !self_version.version.pre.is_empty() - && self_version.version.major == version.major - && self_version.version.minor == version.minor - && self_version.version.patch == version.patch - { - return true; - } - } - false - } -} - -#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)] -pub enum VersionBoundKind { - Inclusive, - Exclusive, -} - -#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)] -pub struct VersionBound { - pub kind: VersionBoundKind, - pub version: Version, -} - -impl VersionBound { - pub fn new(kind: VersionBoundKind, version: Version) -> Self { - Self { kind, version } - } -} - -#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)] -pub struct VersionRange { - pub start: RangeBound, - pub end: RangeBound, -} - -impl VersionRange { - pub fn all() -> VersionRange { - VersionRange { - start: RangeBound::Version(VersionBound { - kind: VersionBoundKind::Inclusive, - version: Version::default(), - }), - end: RangeBound::Unbounded, - } - } - - pub fn none() -> VersionRange { - VersionRange { - start: RangeBound::Version(VersionBound { - kind: VersionBoundKind::Inclusive, - version: Version::default(), - }), - end: RangeBound::Version(VersionBound { - kind: VersionBoundKind::Exclusive, - version: Version::default(), - }), - } - } - - /// If this range won't match anything. - pub fn is_none(&self) -> bool { - if let RangeBound::Version(end) = &self.end { - end.kind == VersionBoundKind::Exclusive - && end.version.major == 0 - && end.version.minor == 0 - && end.version.patch == 0 - } else { - false - } - } - - pub fn satisfies(&self, version: &Version) -> bool { - let satisfies = self.min_satisfies(version) && self.max_satisfies(version); - if satisfies && !version.pre.is_empty() { - // check either side of the range has a pre and same version - self.start.has_pre_with_exact_major_minor_patch(version) - || self.end.has_pre_with_exact_major_minor_patch(version) - } else { - satisfies - } - } - - fn min_satisfies(&self, version: &Version) -> bool { - match &self.start { - RangeBound::Unbounded => true, - RangeBound::Version(bound) => match version.cmp(&bound.version) { - Ordering::Less => false, - Ordering::Equal => bound.kind == VersionBoundKind::Inclusive, - Ordering::Greater => true, - }, - } - } - - fn max_satisfies(&self, version: &Version) -> bool { - match &self.end { - RangeBound::Unbounded => true, - RangeBound::Version(bound) => match version.cmp(&bound.version) { - Ordering::Less => true, - Ordering::Equal => bound.kind == VersionBoundKind::Inclusive, - Ordering::Greater => false, - }, - } - } - - pub fn clamp(&self, range: &VersionRange) -> VersionRange { - let start = self.start.clamp_start(&range.start); - let end = self.end.clamp_end(&range.end); - // clamp the start range to the end when greater - let start = start.clamp_end(&end); - VersionRange { start, end } - } -} - -/// A range that could be a wildcard or number value. -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub enum XRange { - Wildcard, - Val(u64), -} - -/// A partial version. -#[derive(Debug, Clone)] -pub struct Partial { - pub major: XRange, - pub minor: XRange, - pub patch: XRange, - pub pre: Vec, - pub build: Vec, -} - -impl Partial { - pub fn as_tilde_version_range(&self) -> VersionRange { - // tilde ranges allow patch-level changes - let end = match self.major { - XRange::Wildcard => return VersionRange::all(), - XRange::Val(major) => match self.minor { - XRange::Wildcard => Version { - major: major + 1, - minor: 0, - patch: 0, - pre: Vec::new(), - build: Vec::new(), - }, - XRange::Val(minor) => Version { - major, - minor: minor + 1, - patch: 0, - pre: Vec::new(), - build: Vec::new(), - }, - }, - }; - VersionRange { - start: self.as_lower_bound(), - end: RangeBound::exclusive(end), - } - } - - pub fn as_caret_version_range(&self) -> VersionRange { - // partial ranges allow patch and minor updates, except when - // leading parts are < 1 in which case it will only bump the - // first non-zero or patch part - let end = match self.major { - XRange::Wildcard => return VersionRange::all(), - XRange::Val(major) => { - let next_major = Version { - major: major + 1, - ..Default::default() - }; - if major > 0 { - next_major - } else { - match self.minor { - XRange::Wildcard => next_major, - XRange::Val(minor) => { - let next_minor = Version { - minor: minor + 1, - ..Default::default() - }; - if minor > 0 { - next_minor - } else { - match self.patch { - XRange::Wildcard => next_minor, - XRange::Val(patch) => Version { - patch: patch + 1, - ..Default::default() - }, - } - } - } - } - } - } - }; - VersionRange { - start: self.as_lower_bound(), - end: RangeBound::Version(VersionBound { - kind: VersionBoundKind::Exclusive, - version: end, - }), - } - } - - pub fn as_lower_bound(&self) -> RangeBound { - RangeBound::inclusive(Version { - major: match self.major { - XRange::Val(val) => val, - XRange::Wildcard => 0, - }, - minor: match self.minor { - XRange::Val(val) => val, - XRange::Wildcard => 0, - }, - patch: match self.patch { - XRange::Val(val) => val, - XRange::Wildcard => 0, - }, - pre: self.pre.clone(), - build: self.build.clone(), - }) - } - - pub fn as_upper_bound(&self) -> RangeBound { - let mut end = Version::default(); - let mut kind = VersionBoundKind::Inclusive; - match self.patch { - XRange::Wildcard => { - end.minor += 1; - kind = VersionBoundKind::Exclusive; - } - XRange::Val(val) => { - end.patch = val; - } - } - match self.minor { - XRange::Wildcard => { - end.minor = 0; - end.major += 1; - kind = VersionBoundKind::Exclusive; - } - XRange::Val(val) => { - end.minor += val; - } - } - match self.major { - XRange::Wildcard => { - return RangeBound::Unbounded; - } - XRange::Val(val) => { - end.major += val; - } - } - - if kind == VersionBoundKind::Inclusive { - end.pre = self.pre.clone(); - } - - RangeBound::version(kind, end) - } - - pub fn as_equal_range(&self) -> VersionRange { - let major = match self.major { - XRange::Wildcard => { - return self.as_greater_range(VersionBoundKind::Inclusive) - } - XRange::Val(val) => val, - }; - let minor = match self.minor { - XRange::Wildcard => { - return self.as_greater_range(VersionBoundKind::Inclusive) - } - XRange::Val(val) => val, - }; - let patch = match self.patch { - XRange::Wildcard => { - return self.as_greater_range(VersionBoundKind::Inclusive) - } - XRange::Val(val) => val, - }; - let version = Version { - major, - minor, - patch, - pre: self.pre.clone(), - build: self.build.clone(), - }; - VersionRange { - start: RangeBound::inclusive(version.clone()), - end: RangeBound::inclusive(version), - } - } - - pub fn as_greater_than( - &self, - mut start_kind: VersionBoundKind, - ) -> VersionRange { - let major = match self.major { - XRange::Wildcard => match start_kind { - VersionBoundKind::Inclusive => return VersionRange::all(), - VersionBoundKind::Exclusive => return VersionRange::none(), - }, - XRange::Val(major) => major, - }; - let mut start = Version::default(); - - if start_kind == VersionBoundKind::Inclusive { - start.pre = self.pre.clone(); - } - - start.major = major; - match self.minor { - XRange::Wildcard => { - if start_kind == VersionBoundKind::Exclusive { - start_kind = VersionBoundKind::Inclusive; - start.major += 1; - } - } - XRange::Val(minor) => { - start.minor = minor; - } - } - match self.patch { - XRange::Wildcard => { - if start_kind == VersionBoundKind::Exclusive { - start_kind = VersionBoundKind::Inclusive; - start.minor += 1; - } - } - XRange::Val(patch) => { - start.patch = patch; - } - } - - VersionRange { - start: RangeBound::version(start_kind, start), - end: RangeBound::Unbounded, - } - } - - pub fn as_less_than(&self, mut end_kind: VersionBoundKind) -> VersionRange { - let major = match self.major { - XRange::Wildcard => match end_kind { - VersionBoundKind::Inclusive => return VersionRange::all(), - VersionBoundKind::Exclusive => return VersionRange::none(), - }, - XRange::Val(major) => major, - }; - let mut end = Version { - major, - ..Default::default() - }; - match self.minor { - XRange::Wildcard => { - if end_kind == VersionBoundKind::Inclusive { - end.major += 1; - } - end_kind = VersionBoundKind::Exclusive; - } - XRange::Val(minor) => { - end.minor = minor; - } - } - match self.patch { - XRange::Wildcard => { - if end_kind == VersionBoundKind::Inclusive { - end.minor += 1; - } - end_kind = VersionBoundKind::Exclusive; - } - XRange::Val(patch) => { - end.patch = patch; - } - } - if end_kind == VersionBoundKind::Inclusive { - end.pre = self.pre.clone(); - } - VersionRange { - start: RangeBound::Unbounded, - end: RangeBound::version(end_kind, end), - } - } - - pub fn as_greater_range(&self, start_kind: VersionBoundKind) -> VersionRange { - let major = match self.major { - XRange::Wildcard => return VersionRange::all(), - XRange::Val(major) => major, - }; - let mut start = Version::default(); - let mut end = Version::default(); - start.major = major; - end.major = major; - match self.patch { - XRange::Wildcard => { - if self.minor != XRange::Wildcard { - end.minor += 1; - } - } - XRange::Val(patch) => { - start.patch = patch; - end.patch = patch; - } - } - match self.minor { - XRange::Wildcard => { - end.major += 1; - } - XRange::Val(minor) => { - start.minor = minor; - end.minor += minor; - } - } - let end_kind = if start_kind == VersionBoundKind::Inclusive && start == end - { - VersionBoundKind::Inclusive - } else { - VersionBoundKind::Exclusive - }; - VersionRange { - start: RangeBound::version(start_kind, start), - end: RangeBound::version(end_kind, end), - } - } -} diff --git a/cli/semver/specifier.rs b/cli/semver/specifier.rs deleted file mode 100644 index 8edb4cddd25e1d..00000000000000 --- a/cli/semver/specifier.rs +++ /dev/null @@ -1,265 +0,0 @@ -// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. - -use deno_core::anyhow::Context; -use deno_core::error::AnyError; -use monch::*; - -use super::range::Partial; -use super::range::VersionRange; -use super::range::VersionRangeSet; -use super::range::XRange; -use super::RangeSetOrTag; -use super::VersionReq; - -use super::is_valid_tag; - -pub fn parse_version_req_from_specifier( - text: &str, -) -> Result { - with_failure_handling(|input| { - map_res(version_range, |result| { - let (new_input, range_result) = match result { - Ok((input, range)) => (input, Ok(range)), - // use an empty string because we'll consider it a tag - Err(err) => ("", Err(err)), - }; - Ok(( - new_input, - VersionReq::from_raw_text_and_inner( - input.to_string(), - match range_result { - Ok(range) => RangeSetOrTag::RangeSet(VersionRangeSet(vec![range])), - Err(err) => { - if !is_valid_tag(input) { - return Err(err); - } else { - RangeSetOrTag::Tag(input.to_string()) - } - } - }, - ), - )) - })(input) - })(text) - .with_context(|| { - format!("Invalid npm specifier version requirement '{text}'.") - }) -} - -// Note: Although the code below looks very similar to what's used for -// parsing npm version requirements, the code here is more strict -// in order to not allow for people to get ridiculous when using -// npm specifiers. -// -// A lot of the code below is adapted from https://github.com/npm/node-semver -// which is Copyright (c) Isaac Z. Schlueter and Contributors (ISC License) - -// version_range ::= partial | tilde | caret -fn version_range(input: &str) -> ParseResult { - or3( - map(preceded(ch('~'), partial), |partial| { - partial.as_tilde_version_range() - }), - map(preceded(ch('^'), partial), |partial| { - partial.as_caret_version_range() - }), - map(partial, |partial| partial.as_equal_range()), - )(input) -} - -// partial ::= xr ( '.' xr ( '.' xr qualifier ? )? )? -fn partial(input: &str) -> ParseResult { - let (input, major) = xr()(input)?; - let (input, maybe_minor) = maybe(preceded(ch('.'), xr()))(input)?; - let (input, maybe_patch) = if maybe_minor.is_some() { - maybe(preceded(ch('.'), xr()))(input)? - } else { - (input, None) - }; - let (input, qual) = if maybe_patch.is_some() { - maybe(qualifier)(input)? - } else { - (input, None) - }; - let qual = qual.unwrap_or_default(); - Ok(( - input, - Partial { - major, - minor: maybe_minor.unwrap_or(XRange::Wildcard), - patch: maybe_patch.unwrap_or(XRange::Wildcard), - pre: qual.pre, - build: qual.build, - }, - )) -} - -// xr ::= 'x' | 'X' | '*' | nr -fn xr<'a>() -> impl Fn(&'a str) -> ParseResult<'a, XRange> { - or( - map(or3(tag("x"), tag("X"), tag("*")), |_| XRange::Wildcard), - map(nr, XRange::Val), - ) -} - -// nr ::= '0' | ['1'-'9'] ( ['0'-'9'] ) * -fn nr(input: &str) -> ParseResult { - or(map(tag("0"), |_| 0), move |input| { - let (input, result) = if_not_empty(substring(pair( - if_true(next_char, |c| c.is_ascii_digit() && *c != '0'), - skip_while(|c| c.is_ascii_digit()), - )))(input)?; - let val = match result.parse::() { - Ok(val) => val, - Err(err) => { - return ParseError::fail( - input, - format!("Error parsing '{result}' to u64.\n\n{err:#}"), - ) - } - }; - Ok((input, val)) - })(input) -} - -#[derive(Debug, Clone, Default)] -struct Qualifier { - pre: Vec, - build: Vec, -} - -// qualifier ::= ( '-' pre )? ( '+' build )? -fn qualifier(input: &str) -> ParseResult { - let (input, pre_parts) = maybe(pre)(input)?; - let (input, build_parts) = maybe(build)(input)?; - Ok(( - input, - Qualifier { - pre: pre_parts.unwrap_or_default(), - build: build_parts.unwrap_or_default(), - }, - )) -} - -// pre ::= parts -fn pre(input: &str) -> ParseResult> { - preceded(ch('-'), parts)(input) -} - -// build ::= parts -fn build(input: &str) -> ParseResult> { - preceded(ch('+'), parts)(input) -} - -// parts ::= part ( '.' part ) * -fn parts(input: &str) -> ParseResult> { - if_not_empty(map(separated_list(part, ch('.')), |text| { - text.into_iter().map(ToOwned::to_owned).collect() - }))(input) -} - -// part ::= nr | [-0-9A-Za-z]+ -fn part(input: &str) -> ParseResult<&str> { - // nr is in the other set, so don't bother checking for it - if_true( - take_while(|c| c.is_ascii_alphanumeric() || c == '-'), - |result| !result.is_empty(), - )(input) -} - -#[cfg(test)] -mod tests { - use super::super::Version; - use super::*; - - struct VersionReqTester(VersionReq); - - impl VersionReqTester { - fn new(text: &str) -> Self { - Self(parse_version_req_from_specifier(text).unwrap()) - } - - fn matches(&self, version: &str) -> bool { - self.0.matches(&Version::parse_from_npm(version).unwrap()) - } - } - - #[test] - fn version_req_exact() { - let tester = VersionReqTester::new("1.0.1"); - assert!(!tester.matches("1.0.0")); - assert!(tester.matches("1.0.1")); - assert!(!tester.matches("1.0.2")); - assert!(!tester.matches("1.1.1")); - - // pre-release - let tester = VersionReqTester::new("1.0.0-alpha.13"); - assert!(tester.matches("1.0.0-alpha.13")); - } - - #[test] - fn version_req_minor() { - let tester = VersionReqTester::new("1.1"); - assert!(!tester.matches("1.0.0")); - assert!(tester.matches("1.1.0")); - assert!(tester.matches("1.1.1")); - assert!(!tester.matches("1.2.0")); - assert!(!tester.matches("1.2.1")); - } - - #[test] - fn version_req_caret() { - let tester = VersionReqTester::new("^1.1.1"); - assert!(!tester.matches("1.1.0")); - assert!(tester.matches("1.1.1")); - assert!(tester.matches("1.1.2")); - assert!(tester.matches("1.2.0")); - assert!(!tester.matches("2.0.0")); - - let tester = VersionReqTester::new("^0.1.1"); - assert!(!tester.matches("0.0.0")); - assert!(!tester.matches("0.1.0")); - assert!(tester.matches("0.1.1")); - assert!(tester.matches("0.1.2")); - assert!(!tester.matches("0.2.0")); - assert!(!tester.matches("1.0.0")); - - let tester = VersionReqTester::new("^0.0.1"); - assert!(!tester.matches("0.0.0")); - assert!(tester.matches("0.0.1")); - assert!(!tester.matches("0.0.2")); - assert!(!tester.matches("0.1.0")); - assert!(!tester.matches("1.0.0")); - } - - #[test] - fn version_req_tilde() { - let tester = VersionReqTester::new("~1.1.1"); - assert!(!tester.matches("1.1.0")); - assert!(tester.matches("1.1.1")); - assert!(tester.matches("1.1.2")); - assert!(!tester.matches("1.2.0")); - assert!(!tester.matches("2.0.0")); - - let tester = VersionReqTester::new("~0.1.1"); - assert!(!tester.matches("0.0.0")); - assert!(!tester.matches("0.1.0")); - assert!(tester.matches("0.1.1")); - assert!(tester.matches("0.1.2")); - assert!(!tester.matches("0.2.0")); - assert!(!tester.matches("1.0.0")); - - let tester = VersionReqTester::new("~0.0.1"); - assert!(!tester.matches("0.0.0")); - assert!(tester.matches("0.0.1")); - assert!(tester.matches("0.0.2")); // for some reason this matches, but not with ^ - assert!(!tester.matches("0.1.0")); - assert!(!tester.matches("1.0.0")); - } - - #[test] - fn parses_tag() { - let latest_tag = VersionReq::parse_from_specifier("latest").unwrap(); - assert_eq!(latest_tag.tag().unwrap(), "latest"); - } -} diff --git a/cli/standalone.rs b/cli/standalone.rs index 680a0b589dc2b7..6c9af2c15b3876 100644 --- a/cli/standalone.rs +++ b/cli/standalone.rs @@ -9,7 +9,7 @@ use crate::ops; use crate::proc_state::ProcState; use crate::util::v8::construct_v8_flags; use crate::version; -use crate::CliResolver; +use crate::CliGraphResolver; use deno_core::anyhow::Context; use deno_core::error::type_error; use deno_core::error::AnyError; @@ -128,7 +128,7 @@ fn u64_from_bytes(arr: &[u8]) -> Result { struct EmbeddedModuleLoader { eszip: eszip::EszipV2, - maybe_import_map_resolver: Option, + maybe_import_map_resolver: Option, } impl ModuleLoader for EmbeddedModuleLoader { @@ -236,9 +236,16 @@ pub async fn run( eszip, maybe_import_map_resolver: metadata.maybe_import_map.map( |(base, source)| { - CliResolver::with_import_map(Arc::new( - parse_from_json(&base, &source).unwrap().import_map, - )) + CliGraphResolver::new( + None, + Some(Arc::new( + parse_from_json(&base, &source).unwrap().import_map, + )), + false, + ps.npm_resolver.api().clone(), + ps.npm_resolver.resolution().clone(), + ps.package_json_deps_installer.clone(), + ) }, ), }); diff --git a/cli/tests/integration/bench_tests.rs b/cli/tests/integration/bench_tests.rs index 7953bef343919b..5a010ec6293034 100644 --- a/cli/tests/integration/bench_tests.rs +++ b/cli/tests/integration/bench_tests.rs @@ -2,6 +2,7 @@ use deno_core::url::Url; use test_util as util; +use util::env_vars_for_npm_tests; itest!(overloads { args: "bench bench/overloads.ts", @@ -178,6 +179,12 @@ itest!(bench_with_malformed_config { output: "bench/collect_with_malformed_config.out", }); +itest!(json_output { + args: "bench --json bench/pass.ts", + exit_code: 0, + output: "bench/pass.json.out", +}); + #[test] fn recursive_permissions_pledge() { let output = util::deno_cmd() @@ -210,3 +217,13 @@ fn file_protocol() { }) .run(); } + +itest!(package_json_basic { + args: "bench", + output: "package_json/basic/main.bench.out", + envs: env_vars_for_npm_tests(), + http_server: true, + cwd: Some("package_json/basic"), + copy_temp_dir: Some("package_json/basic"), + exit_code: 0, +}); diff --git a/cli/tests/integration/cache_tests.rs b/cli/tests/integration/cache_tests.rs index ae4dc001ad9b29..c80f7f5c85c4f6 100644 --- a/cli/tests/integration/cache_tests.rs +++ b/cli/tests/integration/cache_tests.rs @@ -1,5 +1,7 @@ // Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. +use test_util::env_vars_for_npm_tests; + itest!(_036_import_map_fetch { args: "cache --quiet --reload --import-map=import_maps/import_map.json import_maps/test.ts", @@ -95,3 +97,13 @@ itest!(json_import { // should not error args: "cache --quiet cache/json_import/main.ts", }); + +itest!(package_json_basic { + args: "cache main.ts", + output: "package_json/basic/main.cache.out", + envs: env_vars_for_npm_tests(), + http_server: true, + cwd: Some("package_json/basic"), + copy_temp_dir: Some("package_json/basic"), + exit_code: 0, +}); diff --git a/cli/tests/integration/check_tests.rs b/cli/tests/integration/check_tests.rs index 66433f81dadd14..1273fbdce3dd34 100644 --- a/cli/tests/integration/check_tests.rs +++ b/cli/tests/integration/check_tests.rs @@ -3,6 +3,8 @@ use std::process::Command; use std::process::Stdio; use test_util as util; +use util::env_vars_for_npm_tests; +use util::env_vars_for_npm_tests_no_sync_download; use util::TempDir; itest!(_095_check_with_bare_import { @@ -229,3 +231,33 @@ fn ts_no_recheck_on_redirect() { assert!(std::str::from_utf8(&output.stderr).unwrap().is_empty()); } + +itest!(package_json_basic { + args: "check main.ts", + output: "package_json/basic/main.check.out", + envs: env_vars_for_npm_tests(), + http_server: true, + cwd: Some("package_json/basic"), + copy_temp_dir: Some("package_json/basic"), + exit_code: 0, +}); + +itest!(package_json_fail_check { + args: "check --quiet fail_check.ts", + output: "package_json/basic/fail_check.check.out", + envs: env_vars_for_npm_tests_no_sync_download(), + http_server: true, + cwd: Some("package_json/basic"), + copy_temp_dir: Some("package_json/basic"), + exit_code: 1, +}); + +itest!(package_json_with_deno_json { + args: "check --quiet main.ts", + output: "package_json/deno_json/main.check.out", + cwd: Some("package_json/deno_json/"), + copy_temp_dir: Some("package_json/deno_json/"), + envs: env_vars_for_npm_tests_no_sync_download(), + http_server: true, + exit_code: 1, +}); diff --git a/cli/tests/integration/info_tests.rs b/cli/tests/integration/info_tests.rs index 6c75deea6bdb2d..704aaa7afc1e96 100644 --- a/cli/tests/integration/info_tests.rs +++ b/cli/tests/integration/info_tests.rs @@ -2,6 +2,7 @@ use test_util as util; use test_util::TempDir; +use util::env_vars_for_npm_tests_no_sync_download; #[test] fn info_with_compiled_source() { @@ -127,3 +128,13 @@ itest!(with_config_override { args: "info info/with_config/test.ts --config info/with_config/deno-override.json --import-map info/with_config/import_map.json", output: "info/with_config/with_config.out", }); + +itest!(package_json_basic { + args: "info --quiet main.ts", + output: "package_json/basic/main.info.out", + envs: env_vars_for_npm_tests_no_sync_download(), + http_server: true, + cwd: Some("package_json/basic"), + copy_temp_dir: Some("package_json/basic"), + exit_code: 0, +}); diff --git a/cli/tests/integration/inspector_tests.rs b/cli/tests/integration/inspector_tests.rs index bfc3a63e0922be..18b4d8ef90666f 100644 --- a/cli/tests/integration/inspector_tests.rs +++ b/cli/tests/integration/inspector_tests.rs @@ -1121,6 +1121,10 @@ async fn inspector_profile() { tester.child.wait().unwrap(); } +// TODO(bartlomieju): this test became flaky on CI after wiring up "ext/node" +// compatibility layer. Can't reproduce this problem locally for either Mac M1 +// or Linux. Ignoring for now to unblock further integration of "ext/node". +#[ignore] #[tokio::test] async fn inspector_break_on_first_line_npm_esm() { let _server = http_server(); @@ -1187,6 +1191,10 @@ async fn inspector_break_on_first_line_npm_esm() { tester.child.wait().unwrap(); } +// TODO(bartlomieju): this test became flaky on CI after wiring up "ext/node" +// compatibility layer. Can't reproduce this problem locally for either Mac M1 +// or Linux. Ignoring for now to unblock further integration of "ext/node". +#[ignore] #[tokio::test] async fn inspector_break_on_first_line_npm_cjs() { let _server = http_server(); @@ -1252,6 +1260,10 @@ async fn inspector_break_on_first_line_npm_cjs() { tester.child.wait().unwrap(); } +// TODO(bartlomieju): this test became flaky on CI after wiring up "ext/node" +// compatibility layer. Can't reproduce this problem locally for either Mac M1 +// or Linux. Ignoring for now to unblock further integration of "ext/node". +#[ignore] #[tokio::test] async fn inspector_error_with_npm_import() { let script = util::testdata_path().join("inspector/error_with_npm_import.js"); diff --git a/cli/tests/integration/mod.rs b/cli/tests/integration/mod.rs index 054a4e14ad8d65..d102b486d634b2 100644 --- a/cli/tests/integration/mod.rs +++ b/cli/tests/integration/mod.rs @@ -70,6 +70,10 @@ mod js_unit_tests; mod lint; #[path = "lsp_tests.rs"] mod lsp; +#[path = "node_compat_tests.rs"] +mod node_compat_tests; +#[path = "node_unit_tests.rs"] +mod node_unit_tests; #[path = "npm_tests.rs"] mod npm; #[path = "repl_tests.rs"] diff --git a/cli/tests/integration/node_compat_tests.rs b/cli/tests/integration/node_compat_tests.rs new file mode 100644 index 00000000000000..7617b703799f4a --- /dev/null +++ b/cli/tests/integration/node_compat_tests.rs @@ -0,0 +1,25 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. + +use test_util as util; + +#[test] +fn node_compat_tests() { + let mut deno = util::deno_cmd() + .current_dir(util::root_path()) + .arg("test") + .arg("--unstable") + .arg("--import-map") + .arg( + util::tests_path() + .join("node_compat") + .join("import_map.json"), + ) + .arg("-A") + .arg(util::tests_path().join("node_compat")) + .spawn() + .expect("failed to spawn script"); + + let status = deno.wait().expect("failed to wait for the child process"); + assert_eq!(Some(0), status.code()); + assert!(status.success()); +} diff --git a/cli/tests/integration/node_unit_tests.rs b/cli/tests/integration/node_unit_tests.rs new file mode 100644 index 00000000000000..d2a6f6ec8ead3e --- /dev/null +++ b/cli/tests/integration/node_unit_tests.rs @@ -0,0 +1,25 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. + +use test_util as util; + +#[test] +fn node_unit_tests() { + let _g = util::http_server(); + + let mut deno = util::deno_cmd() + .current_dir(util::root_path()) + .arg("test") + .arg("--unstable") + // TODO(kt3k): This option is required to pass tls_test.ts, + // but this shouldn't be necessary. tls.connect currently doesn't + // pass hostname option correctly and it causes cert errors. + .arg("--unsafely-ignore-certificate-errors") + .arg("-A") + .arg(util::tests_path().join("unit_node")) + .spawn() + .expect("failed to spawn script"); + + let status = deno.wait().expect("failed to wait for the child process"); + assert_eq!(Some(0), status.code()); + assert!(status.success()); +} diff --git a/cli/tests/integration/npm_tests.rs b/cli/tests/integration/npm_tests.rs index 107bab6137ad86..6a66db35fe3238 100644 --- a/cli/tests/integration/npm_tests.rs +++ b/cli/tests/integration/npm_tests.rs @@ -217,11 +217,11 @@ itest!(sub_paths { }); itest!(remote_npm_specifier { - args: "run --quiet npm/remote_npm_specifier/main.ts", + args: "run --quiet -A npm/remote_npm_specifier/main.ts", output: "npm/remote_npm_specifier/main.out", envs: env_vars_for_npm_tests(), http_server: true, - exit_code: 1, + exit_code: 0, }); itest!(tarball_with_global_header { @@ -581,7 +581,7 @@ fn no_npm_after_first_run() { let stdout = String::from_utf8_lossy(&output.stdout); assert_contains!( stderr, - "Following npm specifiers were requested: \"chalk@5\"; but --no-npm is specified." + "error: npm specifiers were requested; but --no-npm is specified\n at file:///" ); assert!(stdout.is_empty()); assert!(!output.status.success()); @@ -623,7 +623,7 @@ fn no_npm_after_first_run() { let stdout = String::from_utf8_lossy(&output.stdout); assert_contains!( stderr, - "Following npm specifiers were requested: \"chalk@5\"; but --no-npm is specified." + "error: npm specifiers were requested; but --no-npm is specified\n at file:///" ); assert!(stdout.is_empty()); assert!(!output.status.success()); @@ -660,6 +660,13 @@ itest!(deno_run_cowsay { http_server: true, }); +itest!(deno_run_cowsay_with_node_modules_dir { + args: "run -A --quiet --node-modules-dir npm:cowsay@1.5.0 Hello", + output: "npm/deno_run_cowsay.out", + envs: env_vars_for_npm_tests_no_sync_download(), + http_server: true, +}); + itest!(deno_run_cowsay_explicit { args: "run -A --quiet npm:cowsay@1.5.0/cowsay Hello", output: "npm/deno_run_cowsay.out", @@ -820,7 +827,7 @@ fn ensure_registry_files_local() { itest!(compile_errors { args: "compile -A --quiet npm/cached_only/main.ts", - output_str: Some("error: npm specifiers have not yet been implemented for this sub command (https://github.com/denoland/deno/issues/15960). Found: npm:chalk@5\n"), + output_str: Some("error: npm specifiers have not yet been implemented for this sub command (https://github.com/denoland/deno/issues/15960). Found: npm:chalk@5.0.1\n"), exit_code: 1, envs: env_vars_for_npm_tests(), http_server: true, @@ -828,7 +835,7 @@ itest!(compile_errors { itest!(bundle_errors { args: "bundle --quiet npm/esm/main.js", - output_str: Some("error: npm specifiers have not yet been implemented for this sub command (https://github.com/denoland/deno/issues/15960). Found: npm:chalk@5\n"), + output_str: Some("error: npm specifiers have not yet been implemented for this sub command (https://github.com/denoland/deno/issues/15960). Found: npm:chalk@5.0.1\n"), exit_code: 1, envs: env_vars_for_npm_tests(), http_server: true, @@ -1558,3 +1565,23 @@ itest!(create_require { envs: env_vars_for_npm_tests(), http_server: true, }); + +itest!(node_modules_import_run { + args: "run --quiet main.ts", + output: "npm/node_modules_import/main.out", + http_server: true, + copy_temp_dir: Some("npm/node_modules_import/"), + cwd: Some("npm/node_modules_import/"), + envs: env_vars_for_npm_tests(), + exit_code: 0, +}); + +itest!(node_modules_import_check { + args: "check --quiet main.ts", + output: "npm/node_modules_import/main_check.out", + envs: env_vars_for_npm_tests(), + http_server: true, + cwd: Some("npm/node_modules_import/"), + copy_temp_dir: Some("npm/node_modules_import/"), + exit_code: 1, +}); diff --git a/cli/tests/integration/repl_tests.rs b/cli/tests/integration/repl_tests.rs index 43e601739263e1..5e361084288f73 100644 --- a/cli/tests/integration/repl_tests.rs +++ b/cli/tests/integration/repl_tests.rs @@ -6,6 +6,7 @@ use test_util::assert_ends_with; use test_util::assert_not_contains; use util::TempDir; +#[ignore] #[test] fn pty_multiline() { util::with_pty(&["repl"], |mut console| { diff --git a/cli/tests/integration/run_tests.rs b/cli/tests/integration/run_tests.rs index 923232f9d441e4..b60efc94d7bb76 100644 --- a/cli/tests/integration/run_tests.rs +++ b/cli/tests/integration/run_tests.rs @@ -545,6 +545,12 @@ itest!(dynamic_import_already_rejected { output: "run/dynamic_import_already_rejected/main.out", }); +itest!(dynamic_import_concurrent_non_statically_analyzable { + args: "run --allow-read --allow-net --quiet run/dynamic_import_concurrent_non_statically_analyzable/main.ts", + output: "run/dynamic_import_concurrent_non_statically_analyzable/main.out", + http_server: true, +}); + itest!(no_check_imports_not_used_as_values { args: "run --config run/no_check_imports_not_used_as_values/preserve_imports.tsconfig.json --no-check run/no_check_imports_not_used_as_values/main.ts", output: "run/no_check_imports_not_used_as_values/main.out", @@ -591,6 +597,62 @@ fn _090_run_permissions_request_sync() { ]); } +#[test] +fn permissions_prompt_allow_all() { + let args = "run --quiet run/permissions_prompt_allow_all.ts"; + use util::PtyData::*; + util::test_pty2(args, vec![ + // "run" permissions + Output("┌ ⚠️ Deno requests run access to \"FOO\".\r\n├ Requested by `Deno.permissions.query()` API\r\n ├ Run again with --allow-run to bypass this prompt.\r\n└ Allow? [y/n/A] (y = yes, allow; n = no, deny; A = allow all run permissions) >"), + Input("a\n"), + Output("✅ Granted all run access.\r\n"), + // "read" permissions + Output("┌ ⚠️ Deno requests read access to \"FOO\".\r\n├ Requested by `Deno.permissions.query()` API\r\n ├ Run again with --allow-read to bypass this prompt.\r\n└ Allow? [y/n/A] (y = yes, allow; n = no, deny; A = allow all read permissions) >"), + Input("a\n"), + Output("✅ Granted all read access.\r\n"), + // "write" permissions + Output("┌ ⚠️ Deno requests write access to \"FOO\".\r\n├ Requested by `Deno.permissions.query()` API\r\n ├ Run again with --allow-write to bypass this prompt.\r\n└ Allow? [y/n/A] (y = yes, allow; n = no, deny; A = allow all write permissions) >"), + Input("a\n"), + Output("✅ Granted all write access.\r\n"), + // "net" permissions + Output("┌ ⚠️ Deno requests net access to \"FOO\".\r\n├ Requested by `Deno.permissions.query()` API\r\n ├ Run again with --allow-net to bypass this prompt.\r\n└ Allow? [y/n/A] (y = yes, allow; n = no, deny; A = allow all net permissions) >"), + Input("a\n"), + Output("✅ Granted all net access.\r\n"), + // "env" permissions + Output("┌ ⚠️ Deno requests env access to \"FOO\".\r\n├ Requested by `Deno.permissions.query()` API\r\n ├ Run again with --allow-env to bypass this prompt.\r\n└ Allow? [y/n/A] (y = yes, allow; n = no, deny; A = allow all env permissions) >"), + Input("a\n"), + Output("✅ Granted all env access.\r\n"), + // "sys" permissions + Output("┌ ⚠️ Deno requests sys access to \"loadavg\".\r\n├ Requested by `Deno.permissions.query()` API\r\n ├ Run again with --allow-sys to bypass this prompt.\r\n└ Allow? [y/n/A] (y = yes, allow; n = no, deny; A = allow all sys permissions) >"), + Input("a\n"), + Output("✅ Granted all sys access.\r\n"), + // "ffi" permissions + Output("┌ ⚠️ Deno requests ffi access to \"FOO\".\r\n├ Requested by `Deno.permissions.query()` API\r\n ├ Run again with --allow-ffi to bypass this prompt.\r\n└ Allow? [y/n/A] (y = yes, allow; n = no, deny; A = allow all ffi permissions) >"), + Input("a\n"), + Output("✅ Granted all ffi access.\r\n") + ]); +} + +#[test] +fn permissions_prompt_allow_all_2() { + let args = "run --quiet run/permissions_prompt_allow_all_2.ts"; + use util::PtyData::*; + util::test_pty2(args, vec![ + // "env" permissions + Output("┌ ⚠️ Deno requests env access to \"FOO\".\r\n├ Run again with --allow-env to bypass this prompt.\r\n└ Allow? [y/n/A] (y = yes, allow; n = no, deny; A = allow all env permissions) >"), + Input("d\n"), + Output("✅ Granted all env access.\r\n"), + // "sys" permissions + Output("┌ ⚠️ Deno requests sys access to \"FOO\".\r\n├ Run again with --allow-sys to bypass this prompt.\r\n└ Allow? [y/n/A] (y = yes, allow; n = no, deny; A = allow all sys permissions) >"), + Input("d\n"), + Output("✅ Granted all sys access.\r\n"), + // "read" permissions + Output("┌ ⚠️ Deno requests read access to \"FOO\".\r\n├ Run again with --allow-read to bypass this prompt.\r\n└ Allow? [y/n/A] (y = yes, allow; n = no, deny; A = allow all read permissions) >"), + Input("d\n"), + Output("✅ Granted all read access.\r\n"), + ]); +} + itest!(_091_use_define_for_class_fields { args: "run --check run/091_use_define_for_class_fields.ts", output: "run/091_use_define_for_class_fields.ts.out", @@ -2693,6 +2755,87 @@ itest!(config_not_auto_discovered_for_remote_script { http_server: true, }); +itest!(package_json_auto_discovered_for_local_script_arg { + args: "run -L debug -A no_deno_json/main.ts", + output: "run/with_package_json/no_deno_json/main.out", + // notice this is not in no_deno_json + cwd: Some("run/with_package_json/"), + // prevent creating a node_modules dir in the code directory + copy_temp_dir: Some("run/with_package_json/"), + envs: env_vars_for_npm_tests_no_sync_download(), + http_server: true, +}); + +// In this case we shouldn't discover `package.json` file, because it's in a +// directory that is above the directory containing `deno.json` file. +itest!( + package_json_auto_discovered_for_local_script_arg_with_stop { + args: "run -L debug with_stop/some/nested/dir/main.ts", + output: "run/with_package_json/with_stop/main.out", + cwd: Some("run/with_package_json/"), + copy_temp_dir: Some("run/with_package_json/"), + envs: env_vars_for_npm_tests_no_sync_download(), + http_server: true, + exit_code: 1, + } +); + +itest!(package_json_not_auto_discovered_no_config { + args: "run -L debug -A --no-config noconfig.ts", + output: "run/with_package_json/no_deno_json/noconfig.out", + cwd: Some("run/with_package_json/no_deno_json/"), +}); + +itest!(package_json_not_auto_discovered_no_npm { + args: "run -L debug -A --no-npm noconfig.ts", + output: "run/with_package_json/no_deno_json/noconfig.out", + cwd: Some("run/with_package_json/no_deno_json/"), +}); + +itest!(package_json_not_auto_discovered_env_var { + args: "run -L debug -A noconfig.ts", + output: "run/with_package_json/no_deno_json/noconfig.out", + cwd: Some("run/with_package_json/no_deno_json/"), + envs: vec![("DENO_NO_PACKAGE_JSON".to_string(), "1".to_string())], +}); + +itest!( + package_json_auto_discovered_node_modules_relative_package_json { + args: "run -A main.js", + output: "run/with_package_json/no_deno_json/sub_dir/main.out", + cwd: Some("run/with_package_json/no_deno_json/sub_dir"), + copy_temp_dir: Some("run/with_package_json/"), + envs: env_vars_for_npm_tests_no_sync_download(), + http_server: true, + } +); + +itest!(package_json_auto_discovered_for_npm_binary { + args: "run -L debug -A npm:@denotest/bin/cli-esm this is a test", + output: "run/with_package_json/npm_binary/main.out", + cwd: Some("run/with_package_json/npm_binary/"), + copy_temp_dir: Some("run/with_package_json/"), + envs: env_vars_for_npm_tests_no_sync_download(), + http_server: true, +}); + +itest!(package_json_auto_discovered_no_package_json_imports { + // this should not use --quiet because we should ensure no package.json install occurs + args: "run -A no_package_json_imports.ts", + output: "run/with_package_json/no_deno_json/no_package_json_imports.out", + cwd: Some("run/with_package_json/no_deno_json"), + copy_temp_dir: Some("run/with_package_json/no_deno_json"), +}); + +itest!(package_json_with_deno_json { + args: "run --quiet -A main.ts", + output: "package_json/deno_json/main.out", + cwd: Some("package_json/deno_json/"), + copy_temp_dir: Some("package_json/deno_json/"), + envs: env_vars_for_npm_tests_no_sync_download(), + http_server: true, +}); + itest!(wasm_streaming_panic_test { args: "run run/wasm_streaming_panic_test.js", output: "run/wasm_streaming_panic_test.js.out", @@ -2796,6 +2939,30 @@ itest!(unstable_ffi_15 { exit_code: 70, }); +itest!(unstable_ffi_16 { + args: "run run/ffi/unstable_ffi_16.js", + output: "run/ffi/unstable_ffi_16.js.out", + exit_code: 70, +}); + +itest!(unstable_ffi_17 { + args: "run run/ffi/unstable_ffi_17.js", + output: "run/ffi/unstable_ffi_17.js.out", + exit_code: 70, +}); + +itest!(unstable_ffi_18 { + args: "run run/ffi/unstable_ffi_18.js", + output: "run/ffi/unstable_ffi_18.js.out", + exit_code: 70, +}); + +itest!(unstable_ffi_19 { + args: "run run/ffi/unstable_ffi_19.js", + output: "run/ffi/unstable_ffi_19.js.out", + exit_code: 70, +}); + itest!(future_check2 { args: "run --check run/future_check.ts", output: "run/future_check2.out", @@ -3379,6 +3546,9 @@ async fn test_resolve_dns() { .unwrap(); let err = String::from_utf8_lossy(&output.stderr); let out = String::from_utf8_lossy(&output.stdout); + if !output.status.success() { + eprintln!("stderr: {err}"); + } assert!(output.status.success()); assert!(err.starts_with("Check file")); @@ -3788,6 +3958,7 @@ itest!(permission_args_quiet { }); // Regression test for https://github.com/denoland/deno/issues/16772 +#[ignore] #[test] fn file_fetcher_preserves_permissions() { let _guard = util::http_server(); @@ -3804,6 +3975,7 @@ fn file_fetcher_preserves_permissions() { }); } +#[ignore] #[test] fn stdio_streams_are_locked_in_permission_prompt() { let _guard = util::http_server(); @@ -3825,14 +3997,14 @@ fn stdio_streams_are_locked_in_permission_prompt() { } itest!(node_builtin_modules_ts { - args: "run --quiet run/node_builtin_modules/mod.ts", + args: "run --quiet --allow-read run/node_builtin_modules/mod.ts hello there", output: "run/node_builtin_modules/mod.ts.out", envs: env_vars_for_npm_tests_no_sync_download(), exit_code: 0, }); itest!(node_builtin_modules_js { - args: "run --quiet run/node_builtin_modules/mod.js", + args: "run --quiet --allow-read run/node_builtin_modules/mod.js hello there", output: "run/node_builtin_modules/mod.js.out", envs: env_vars_for_npm_tests_no_sync_download(), exit_code: 0, @@ -3844,3 +4016,15 @@ itest!(node_prefix_missing { envs: env_vars_for_npm_tests_no_sync_download(), exit_code: 1, }); + +itest!(internal_import { + args: "run run/internal_import.ts", + output: "run/internal_import.ts.out", + exit_code: 1, +}); + +itest!(internal_dynamic_import { + args: "run run/internal_dynamic_import.ts", + output: "run/internal_dynamic_import.ts.out", + exit_code: 1, +}); diff --git a/cli/tests/integration/task_tests.rs b/cli/tests/integration/task_tests.rs index 47c3b6b6b02f02..3dce90a0cb84ee 100644 --- a/cli/tests/integration/task_tests.rs +++ b/cli/tests/integration/task_tests.rs @@ -3,30 +3,32 @@ // Most of the tests for this are in deno_task_shell. // These tests are intended to only test integration. +use test_util::env_vars_for_npm_tests; + itest!(task_no_args { - args: "task -q --config task/deno.json", - output: "task/task_no_args.out", + args: "task -q --config task/deno_json/deno.json", + output: "task/deno_json/task_no_args.out", envs: vec![("NO_COLOR".to_string(), "1".to_string())], exit_code: 1, }); itest!(task_cwd { - args: "task -q --config task/deno.json --cwd .. echo_cwd", - output: "task/task_cwd.out", + args: "task -q --config task/deno_json/deno.json --cwd .. echo_cwd", + output: "task/deno_json/task_cwd.out", envs: vec![("NO_COLOR".to_string(), "1".to_string())], exit_code: 0, }); itest!(task_init_cwd { - args: "task -q --config task/deno.json --cwd .. echo_init_cwd", - output: "task/task_init_cwd.out", + args: "task -q --config task/deno_json/deno.json --cwd .. echo_init_cwd", + output: "task/deno_json/task_init_cwd.out", envs: vec![("NO_COLOR".to_string(), "1".to_string())], exit_code: 0, }); itest!(task_init_cwd_already_set { - args: "task -q --config task/deno.json echo_init_cwd", - output: "task/task_init_cwd_already_set.out", + args: "task -q --config task/deno_json/deno.json echo_init_cwd", + output: "task/deno_json/task_init_cwd_already_set.out", envs: vec![ ("NO_COLOR".to_string(), "1".to_string()), ("INIT_CWD".to_string(), "HELLO".to_string()) @@ -35,15 +37,15 @@ itest!(task_init_cwd_already_set { }); itest!(task_cwd_resolves_config_from_specified_dir { - args: "task -q --cwd task", - output: "task/task_no_args.out", + args: "task -q --cwd task/deno_json", + output: "task/deno_json/task_no_args.out", envs: vec![("NO_COLOR".to_string(), "1".to_string())], exit_code: 1, }); itest!(task_non_existent { - args: "task --config task/deno.json non_existent", - output: "task/task_non_existent.out", + args: "task --config task/deno_json/deno.json non_existent", + output: "task/deno_json/task_non_existent.out", envs: vec![("NO_COLOR".to_string(), "1".to_string())], exit_code: 1, }); @@ -51,27 +53,27 @@ itest!(task_non_existent { #[test] fn task_emoji() { // this bug only appears when using a pty/tty - let args = "task --config task/deno.json echo_emoji"; + let args = "task --config task/deno_json/deno.json echo_emoji"; use test_util::PtyData::*; test_util::test_pty2(args, vec![Output("Task echo_emoji echo 🔥\r\n🔥")]); } itest!(task_boolean_logic { - args: "task -q --config task/deno.json boolean_logic", - output: "task/task_boolean_logic.out", + args: "task -q --config task/deno_json/deno.json boolean_logic", + output: "task/deno_json/task_boolean_logic.out", envs: vec![("NO_COLOR".to_string(), "1".to_string())], }); itest!(task_exit_code_5 { - args: "task --config task/deno.json exit_code_5", - output: "task/task_exit_code_5.out", + args: "task --config task/deno_json/deno.json exit_code_5", + output: "task/deno_json/task_exit_code_5.out", envs: vec![("NO_COLOR".to_string(), "1".to_string())], exit_code: 5, }); itest!(task_additional_args { - args: "task -q --config task/deno.json echo 2", - output: "task/task_additional_args.out", + args: "task -q --config task/deno_json/deno.json echo 2", + output: "task/deno_json/task_additional_args.out", envs: vec![("NO_COLOR".to_string(), "1".to_string())], }); @@ -80,11 +82,11 @@ itest!(task_additional_args_no_shell_expansion { "task", "-q", "--config", - "task/deno.json", + "task/deno_json/deno.json", "echo", "$(echo 5)" ], - output: "task/task_additional_args_no_shell_expansion.out", + output: "task/deno_json/task_additional_args_no_shell_expansion.out", envs: vec![("NO_COLOR".to_string(), "1".to_string())], }); @@ -93,11 +95,11 @@ itest!(task_additional_args_nested_strings { "task", "-q", "--config", - "task/deno.json", + "task/deno_json/deno.json", "echo", "string \"quoted string\"" ], - output: "task/task_additional_args_nested_strings.out", + output: "task/deno_json/task_additional_args_nested_strings.out", envs: vec![("NO_COLOR".to_string(), "1".to_string())], }); @@ -106,25 +108,124 @@ itest!(task_additional_args_no_logic { "task", "-q", "--config", - "task/deno.json", + "task/deno_json/deno.json", "echo", "||", "echo", "5" ], - output: "task/task_additional_args_no_logic.out", + output: "task/deno_json/task_additional_args_no_logic.out", envs: vec![("NO_COLOR".to_string(), "1".to_string())], }); itest!(task_deno_exe_no_env { - args_vec: vec!["task", "-q", "--config", "task/deno.json", "deno_echo"], - output: "task/task_deno_exe_no_env.out", + args_vec: vec![ + "task", + "-q", + "--config", + "task/deno_json/deno.json", + "deno_echo" + ], + output: "task/deno_json/task_deno_exe_no_env.out", envs: vec![("NO_COLOR".to_string(), "1".to_string())], env_clear: true, }); itest!(task_piped_stdin { - args_vec: vec!["task", "-q", "--config", "task/deno.json", "piped"], - output: "task/task_piped_stdin.out", + args_vec: vec![ + "task", + "-q", + "--config", + "task/deno_json/deno.json", + "piped" + ], + output: "task/deno_json/task_piped_stdin.out", + envs: vec![("NO_COLOR".to_string(), "1".to_string())], +}); + +itest!(task_package_json_no_arg { + args: "task", + cwd: Some("task/package_json/"), + output: "task/package_json/no_args.out", + envs: vec![("NO_COLOR".to_string(), "1".to_string())], + exit_code: 1, +}); + +itest!(task_package_json_echo { + args: "task --quiet echo", + cwd: Some("task/package_json/"), + output: "task/package_json/echo.out", + // use a temp dir because the node_modules folder will be created + copy_temp_dir: Some("task/package_json/"), + envs: env_vars_for_npm_tests(), + exit_code: 0, + http_server: true, +}); + +itest!(task_package_json_npm_bin { + args: "task bin extra", + cwd: Some("task/package_json/"), + output: "task/package_json/bin.out", + copy_temp_dir: Some("task/package_json/"), + envs: env_vars_for_npm_tests(), + exit_code: 0, + http_server: true, +}); + +itest!(task_both_no_arg { + args: "task", + cwd: Some("task/both/"), + output: "task/both/no_args.out", envs: vec![("NO_COLOR".to_string(), "1".to_string())], + exit_code: 1, +}); + +itest!(task_both_deno_json_selected { + args: "task other", + cwd: Some("task/both/"), + output: "task/both/deno_selected.out", + copy_temp_dir: Some("task/both/"), + envs: env_vars_for_npm_tests(), + exit_code: 0, + http_server: true, +}); + +itest!(task_both_package_json_selected { + args: "task bin asdf", + cwd: Some("task/both/"), + output: "task/both/package_json_selected.out", + copy_temp_dir: Some("task/both/"), + envs: env_vars_for_npm_tests(), + exit_code: 0, + http_server: true, +}); + +itest!(task_both_prefers_deno { + args: "task output some text", + cwd: Some("task/both/"), + output: "task/both/prefers_deno.out", + copy_temp_dir: Some("task/both/"), + envs: env_vars_for_npm_tests(), + exit_code: 0, + http_server: true, +}); + +itest!(task_npx_non_existent { + args: "task non-existent", + cwd: Some("task/npx/"), + output: "task/npx/non_existent.out", + copy_temp_dir: Some("task/npx/"), + envs: env_vars_for_npm_tests(), + exit_code: 1, + http_server: true, +}); + +itest!(task_npx_on_own { + args: "task on-own", + cwd: Some("task/npx/"), + output: "task/npx/on_own.out", + copy_temp_dir: Some("task/npx/"), + envs: env_vars_for_npm_tests(), + exit_code: 1, + http_server: true, }); diff --git a/cli/tests/integration/test_tests.rs b/cli/tests/integration/test_tests.rs index efe50ac16a10f3..8b318e8e14f6e5 100644 --- a/cli/tests/integration/test_tests.rs +++ b/cli/tests/integration/test_tests.rs @@ -2,6 +2,7 @@ use deno_core::url::Url; use test_util as util; +use util::env_vars_for_npm_tests; #[test] fn no_color() { @@ -452,3 +453,13 @@ itest!(parallel_output { output: "test/parallel_output.out", exit_code: 1, }); + +itest!(package_json_basic { + args: "test", + output: "package_json/basic/main.test.out", + envs: env_vars_for_npm_tests(), + http_server: true, + cwd: Some("package_json/basic"), + copy_temp_dir: Some("package_json/basic"), + exit_code: 0, +}); diff --git a/cli/tests/integration/vendor_tests.rs b/cli/tests/integration/vendor_tests.rs index cd2f7f12e23b97..1f159fe802271d 100644 --- a/cli/tests/integration/vendor_tests.rs +++ b/cli/tests/integration/vendor_tests.rs @@ -478,6 +478,14 @@ fn dynamic_non_analyzable_import() { assert!(output.status.success()); } +itest!(dynamic_non_existent { + args: "vendor http://localhost:4545/vendor/dynamic_non_existent.ts", + temp_cwd: true, + exit_code: 0, + http_server: true, + output: "vendor/dynamic_non_existent.ts.out", +}); + #[test] fn update_existing_config_test() { let _server = http_server(); diff --git a/cli/tests/integration/watcher_tests.rs b/cli/tests/integration/watcher_tests.rs index 5ba16cd5e4104f..0c9b8c29f0ea73 100644 --- a/cli/tests/integration/watcher_tests.rs +++ b/cli/tests/integration/watcher_tests.rs @@ -402,6 +402,8 @@ fn bundle_js_watch() { let (_stdout_lines, mut stderr_lines) = child_lines(&mut deno); + assert_contains!(stderr_lines.next().unwrap(), "Warning"); + assert_contains!(stderr_lines.next().unwrap(), "deno_emit"); assert_contains!(stderr_lines.next().unwrap(), "Check"); let next_line = stderr_lines.next().unwrap(); assert_contains!(&next_line, "Bundle started"); @@ -455,8 +457,9 @@ fn bundle_watch_not_exit() { .unwrap(); let (_stdout_lines, mut stderr_lines) = child_lines(&mut deno); - let next_line = stderr_lines.next().unwrap(); - assert_contains!(&next_line, "Bundle started"); + assert_contains!(stderr_lines.next().unwrap(), "Warning"); + assert_contains!(stderr_lines.next().unwrap(), "deno_emit"); + assert_contains!(stderr_lines.next().unwrap(), "Bundle started"); assert_contains!(stderr_lines.next().unwrap(), "error:"); assert_eq!(stderr_lines.next().unwrap(), ""); assert_eq!(stderr_lines.next().unwrap(), " syntax error ^^"); @@ -1174,7 +1177,7 @@ fn run_watch_dynamic_imports() { .spawn() .unwrap(); let (mut stdout_lines, mut stderr_lines) = child_lines(&mut child); - + assert_contains!(stderr_lines.next().unwrap(), "No package.json file found"); assert_contains!(stderr_lines.next().unwrap(), "Process started"); wait_contains( diff --git a/cli/tests/node_compat/common.ts b/cli/tests/node_compat/common.ts new file mode 100644 index 00000000000000..72f44e51013949 --- /dev/null +++ b/cli/tests/node_compat/common.ts @@ -0,0 +1,54 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. +import { join } from "std/path/mod.ts"; + +/** + * The test suite matches the folders inside the `test` folder inside the + * node repo + * + * Each test suite contains a list of files (which can be paths + * or a regex to match) that will be pulled from the node repo + */ +type TestSuites = Record; + +interface Config { + nodeVersion: string; + /** Ignored files won't regenerated by the update script */ + ignore: TestSuites; + /** + * The files that will be run by the test suite + * + * The files to be generated with the update script must be listed here as well, + * but they won't be regenerated if they are listed in the `ignore` configuration + */ + tests: TestSuites; + windowsIgnore: TestSuites; + darwinIgnore: TestSuites; +} + +export const config: Config = JSON.parse( + await Deno.readTextFile(new URL("./config.json", import.meta.url)), +); + +export const ignoreList = Object.entries(config.ignore).reduce( + (total: RegExp[], [suite, paths]) => { + paths.forEach((path) => total.push(new RegExp(join(suite, path)))); + return total; + }, + [], +); + +export function getPathsFromTestSuites(suites: TestSuites): string[] { + const testPaths: string[] = []; + for (const [dir, paths] of Object.entries(suites)) { + if ( + ["parallel", "internet", "pummel", "sequential", "pseudo-tty"].includes( + dir, + ) + ) { + for (const path of paths) { + testPaths.push(join(dir, path)); + } + } + } + return testPaths; +} diff --git a/cli/tests/node_compat/config.json b/cli/tests/node_compat/config.json new file mode 100644 index 00000000000000..b133fe8b29ab61 --- /dev/null +++ b/cli/tests/node_compat/config.json @@ -0,0 +1,847 @@ +{ + "nodeVersion": "18.12.1", + "ignore": { + "common": ["index.js", "internet.js", "tmpdir.js"], + "fixtures": [ + "child-process-spawn-node.js", + "echo.js", + "elipses.txt", + "empty.txt", + "exit.js", + "print-chars.js", + "x.txt" + ], + "internet": [ + "test-dns-any.js", + "test-dns-ipv4.js", + "test-dns-ipv6.js", + "test-dns.js" + ], + "parallel": [ + "test-assert.js", + "test-buffer-alloc.js", + "test-buffer-arraybuffer.js", + "test-buffer-bytelength.js", + "test-buffer-from.js", + "test-buffer-includes.js", + "test-buffer-indexof.js", + "test-child-process-exec-abortcontroller-promisified.js", + "test-child-process-exec-encoding.js", + "test-child-process-exec-kill-throws.js", + "test-child-process-exec-maxbuf.js", + "test-child-process-exec-std-encoding.js", + "test-child-process-exec-timeout-expire.js", + "test-child-process-exec-timeout-kill.js", + "test-child-process-exec-timeout-not-expired.js", + "test-child-process-execFile-promisified-abortController.js", + "test-child-process-execfile.js", + "test-child-process-execsync-maxbuf.js", + "test-child-process-exit-code.js", + "test-child-process-ipc.js", + "test-child-process-spawnsync-env.js", + "test-child-process-stdio-inherit.js", + "test-child-process-stdout-flush-exit.js", + "test-child-process-stdout-flush.js", + "test-console-instance.js", + "test-crypto-hmac.js", + "test-dgram-custom-lookup.js", + "test-dgram-ipv6only.js", + "test-dgram-send-cb-quelches-error.js", + "test-dgram-socket-buffer-size.js", + "test-dgram-udp6-link-local-address.js", + "test-dns-lookup.js", + "test-dns-resolveany.js", + "test-dns.js", + "test-event-emitter-max-listeners.js", + "test-event-emitter-no-error-provided-to-error-event.js", + "test-event-emitter-prepend.js", + "test-events-once.js", + "test-fs-append-file.js", + "test-fs-chmod-mask.js", + "test-fs-chmod.js", + "test-fs-mkdir.js", + "test-fs-open.js", + "test-fs-opendir.js", + "test-fs-rmdir-recursive.js", + "test-fs-write-file.js", + "test-fs-write.js", + "test-net-better-error-messages-path.js", + "test-net-connect-buffer.js", + "test-net-connect-buffer2.js", + "test-net-end-close.js", + "test-net-listen-invalid-port.js", + "test-net-server-call-listen-multiple-times.js", + "test-net-server-listen-path.js", + "test-net-server-try-ports.js", + "test-net-socket-timeout.js", + "test-net-write-arguments.js", + "test-os.js", + "test-path-resolve.js", + "test-path.js", + "test-querystring.js", + "test-readline-interface.js", + "test-stdin-from-file-spawn.js", + "test-url-urltooptions.js", + "test-util-format.js", + "test-util-inspect-namespace.js", + "test-util-inspect-proxy.js", + "test-util-inspect.js", + "test-util-isDeepStrictEqual.js", + "test-util-promisify.js", + "test-util-types.js", + "test-util.js", + "test-webcrypto-sign-verify.js", + "test-whatwg-url-properties.js", + "test-zlib-convenience-methods.js", + "test-zlib-empty-buffer.js", + "test-zlib-invalid-input.js", + "test-zlib-random-byte-pipes.js", + "test-zlib-write-after-flush.js", + "test-zlib-zero-byte.js", + "test-zlib-zero-windowBits.js" + ], + "pummel": [ + "test-net-bytes-per-incoming-chunk-overhead.js", + "test-net-write-callbacks.js" + ], + "sequential": [ + "test-child-process-exit.js" + ] + }, + "tests": { + "common": [ + "child_process.js", + "countdown.js", + "dns.js", + "duplexpair.js", + "fixtures.js", + "hijackstdio", + "index.mjs", + "internet.js" + ], + "fixtures": [ + "GH-1899-output.js", + "a.js", + "child-process-spawn-node.js", + "child_process_should_emit_error.js", + "echo.js", + "elipses.txt", + "empty.txt", + "exit.js", + "loop.js", + "print-chars.js", + "x.txt" + ], + "fixtures/keys": [ + "agent1-cert.pem", + "agent1-key.pem" + ], + "internet": [ + "TODO:test-dgram-connect.js", + "test-dns-any.js", + "test-dns-idna2008.js", + "test-dns-ipv4.js", + "test-dns-ipv6.js", + "test-dns-lookup.js", + "test-dns-promises-resolve.js", + "test-dns-regress-6244.js", + "test-dns-setserver-in-callback-of-resolve4.js", + "test-dns.js" + ], + "parallel": [ + "test-assert-async.js", + "test-assert-fail.js", + "test-assert-strict-exists.js", + "test-assert.js", + "test-bad-unicode.js", + "test-btoa-atob.js", + "TODO:test-buffer-alloc.js", + "test-buffer-arraybuffer.js", + "test-buffer-ascii.js", + "test-buffer-badhex.js", + "test-buffer-bigint64.js", + "test-buffer-bytelength.js", + "test-buffer-compare-offset.js", + "test-buffer-concat.js", + "test-buffer-constants.js", + "test-buffer-copy.js", + "test-buffer-equals.js", + "test-buffer-failed-alloc-typed-arrays.js", + "test-buffer-fakes.js", + "test-buffer-from.js", + "test-buffer-includes.js", + "TODO:test-buffer-indexof.js", + "test-buffer-inheritance.js", + "test-buffer-isencoding.js", + "test-buffer-iterator.js", + "test-buffer-new.js", + "test-buffer-no-negative-allocation.js", + "test-buffer-nopendingdep-map.js", + "test-buffer-of-no-deprecation.js", + "test-buffer-over-max-length.js", + "test-buffer-parent-property.js", + "test-buffer-read.js", + "test-buffer-readdouble.js", + "test-buffer-readfloat.js", + "test-buffer-readint.js", + "test-buffer-readuint.js", + "test-buffer-safe-unsafe.js", + "TODO:test-buffer-sharedarraybuffer.js", + "test-buffer-slice.js", + "test-buffer-slow.js", + "test-buffer-swap.js", + "test-buffer-tojson.js", + "test-buffer-tostring-range.js", + "test-buffer-tostring-rangeerror.js", + "test-buffer-tostring.js", + "TODO:test-buffer-write.js", + "test-buffer-writedouble.js", + "test-buffer-writefloat.js", + "test-buffer-writeint.js", + "test-buffer-writeuint.js", + "test-buffer-zero-fill-cli.js", + "test-buffer-zero-fill-reset.js", + "test-buffer-zero-fill.js", + "test-child-process-can-write-to-stdout.js", + "test-child-process-default-options.js", + "test-child-process-double-pipe.js", + "test-child-process-exec-abortcontroller-promisified.js", + "test-child-process-exec-cwd.js", + "TODO:test-child-process-exec-encoding.js", + "test-child-process-exec-env.js", + "test-child-process-exec-error.js", + "test-child-process-exec-kill-throws.js", + "test-child-process-exec-maxbuf.js", + "TODO:test-child-process-exec-std-encoding.js", + "test-child-process-exec-stdout-stderr-data-string.js", + "test-child-process-exec-timeout-expire.js", + "test-child-process-exec-timeout-kill.js", + "TODO:test-child-process-exec-timeout-not-expired.js", + "test-child-process-execFile-promisified-abortController.js", + "test-child-process-execfile-maxbuf.js", + "TODO:test-child-process-execfile.js", + "test-child-process-execfilesync-maxbuf.js", + "test-child-process-execsync-maxbuf.js", + "TODO:test-child-process-exit-code.js", + "test-child-process-flush-stdio.js", + "TODO:test-child-process-ipc.js", + "test-child-process-kill.js", + "test-child-process-set-blocking.js", + "test-child-process-spawn-args.js", + "test-child-process-spawn-event.js", + "test-child-process-spawnsync-args.js", + "TODO:test-child-process-spawnsync-env.js", + "test-child-process-spawnsync-maxbuf.js", + "test-child-process-spawnsync-validation-errors.js", + "test-child-process-spawnsync.js", + "TODO:test-child-process-stdio-inherit.js", + "TODO:test-child-process-stdout-flush-exit.js", + "TODO:test-child-process-stdout-flush.js", + "test-client-request-destroy.js", + "test-console-async-write-error.js", + "test-console-group.js", + "TODO:test-console-instance.js", + "test-console-log-stdio-broken-dest.js", + "test-console-log-throw-primitive.js", + "test-console-no-swallow-stack-overflow.js", + "test-console-sync-write-error.js", + "test-console-table.js", + "test-console-tty-colors.js", + "test-crypto-hmac.js", + "TODO:test-dgram-address.js", + "TODO:test-dgram-bind-default-address.js", + "TODO:test-dgram-bind.js", + "TODO:test-dgram-bytes-length.js", + "test-dgram-close-during-bind.js", + "TODO:test-dgram-close-in-listening.js", + "TODO:test-dgram-close-is-not-callback.js", + "test-dgram-close-signal.js", + "TODO:test-dgram-close.js", + "TODO:test-dgram-connect-send-callback-buffer-length.js", + "TODO:test-dgram-connect-send-callback-buffer.js", + "TODO:test-dgram-connect-send-callback-multi-buffer.js", + "TODO:test-dgram-connect-send-default-host.js", + "TODO:test-dgram-connect-send-empty-array.js", + "TODO:test-dgram-connect-send-empty-buffer.js", + "TODO:test-dgram-connect-send-empty-packet.js", + "TODO:test-dgram-connect-send-multi-buffer-copy.js", + "TODO:test-dgram-connect-send-multi-string-array.js", + "TODO:test-dgram-connect.js", + "TODO:test-dgram-createSocket-type.js", + "TODO:test-dgram-custom-lookup.js", + "TODO:test-dgram-error-message-address.js", + "TODO:test-dgram-implicit-bind.js", + "TODO:test-dgram-ipv6only.js", + "TODO:test-dgram-listen-after-bind.js", + "TODO:test-dgram-msgsize.js", + "TODO:test-dgram-oob-buffer.js", + "TODO:test-dgram-recv-error.js", + "TODO:test-dgram-send-bad-arguments.js", + "TODO:test-dgram-send-callback-buffer-empty-address.js", + "TODO:test-dgram-send-callback-buffer-length-empty-address.js", + "TODO:test-dgram-send-callback-buffer-length.js", + "TODO:test-dgram-send-callback-buffer.js", + "TODO:test-dgram-send-callback-multi-buffer-empty-address.js", + "TODO:test-dgram-send-callback-multi-buffer.js", + "TODO:test-dgram-send-callback-recursive.js", + "TODO:test-dgram-send-cb-quelches-error.js", + "TODO:test-dgram-send-default-host.js", + "TODO:test-dgram-send-empty-array.js", + "TODO:test-dgram-send-empty-buffer.js", + "TODO:test-dgram-send-empty-packet.js", + "TODO:test-dgram-send-error.js", + "TODO:test-dgram-send-invalid-msg-type.js", + "TODO:test-dgram-send-multi-buffer-copy.js", + "TODO:test-dgram-send-multi-string-array.js", + "TODO:test-dgram-socket-buffer-size.js", + "TODO:test-dgram-udp4.js", + "TODO:test-dgram-udp6-link-local-address.js", + "TODO:test-dgram-udp6-send-default-host.js", + "test-diagnostics-channel-has-subscribers.js", + "TODO:test-diagnostics-channel-net.js", + "test-diagnostics-channel-object-channel-pub-sub.js", + "test-diagnostics-channel-pub-sub.js", + "test-diagnostics-channel-symbol-named.js", + "test-diagnostics-channel-udp.js", + "test-dns-lookup.js", + "test-dns-memory-error.js", + "TODO:test-dns-multi-channel.js", + "test-dns-promises-exists.js", + "TODO:test-dns-resolveany.js", + "test-dns-resolvens-typeerror.js", + "test-dns-setservers-type-check.js", + "TODO:test-dns.js", + "test-eval-strict-referenceerror.js", + "test-eval.js", + "test-event-emitter-add-listeners.js", + "TODO:test-event-emitter-check-listener-leaks.js", + "test-event-emitter-emit-context.js", + "test-event-emitter-error-monitor.js", + "test-event-emitter-errors.js", + "test-event-emitter-get-max-listeners.js", + "test-event-emitter-invalid-listener.js", + "test-event-emitter-listener-count.js", + "test-event-emitter-listeners-side-effects.js", + "test-event-emitter-listeners.js", + "TODO:test-event-emitter-max-listeners-warning-for-null.js", + "TODO:test-event-emitter-max-listeners-warning-for-symbol.js", + "TODO:test-event-emitter-max-listeners-warning.js", + "test-event-emitter-max-listeners.js", + "test-event-emitter-method-names.js", + "test-event-emitter-modify-in-emit.js", + "test-event-emitter-no-error-provided-to-error-event.js", + "test-event-emitter-num-args.js", + "test-event-emitter-once.js", + "test-event-emitter-prepend.js", + "test-event-emitter-remove-all-listeners.js", + "test-event-emitter-remove-listeners.js", + "test-event-emitter-set-max-listeners-side-effects.js", + "test-event-emitter-special-event-names.js", + "test-event-emitter-subclass.js", + "test-event-emitter-symbols.js", + "test-events-list.js", + "test-events-on-async-iterator.js", + "test-events-once.js", + "test-events-uncaught-exception-stack.js", + "test-eventtarget-brandcheck.js", + "test-exception-handler.js", + "test-exception-handler2.js", + "test-file-read-noexist.js", + "test-file-write-stream.js", + "test-file-write-stream2.js", + "test-file-write-stream3.js", + "test-file-write-stream4.js", + "test-fs-access.js", + "test-fs-append-file-sync.js", + "test-fs-append-file.js", + "test-fs-chmod-mask.js", + "test-fs-chmod.js", + "test-fs-chown-type-check.js", + "test-fs-copyfile.js", + "test-fs-empty-readStream.js", + "test-fs-mkdir.js", + "test-fs-open-flags.js", + "test-fs-open-mode-mask.js", + "test-fs-open-no-close.js", + "test-fs-open-numeric-flags.js", + "test-fs-open.js", + "test-fs-opendir.js", + "test-fs-read-stream-autoClose.js", + "test-fs-read-stream-concurrent-reads.js", + "test-fs-read-stream-double-close.js", + "test-fs-read-stream-encoding.js", + "test-fs-read-stream-fd.js", + "test-fs-read-stream-inherit.js", + "test-fs-read-stream-patch-open.js", + "test-fs-read-stream-resume.js", + "test-fs-read-stream-throw-type-error.js", + "test-fs-read-stream.js", + "test-fs-read-type.js", + "test-fs-read-zero-length.js", + "test-fs-read.js", + "test-fs-readdir-stack-overflow.js", + "test-fs-readdir.js", + "test-fs-readfile-empty.js", + "test-fs-realpath-native.js", + "TODO:test-fs-rm.js", + "test-fs-rmdir-recursive-sync-warns-not-found.js", + "test-fs-rmdir-recursive-sync-warns-on-file.js", + "test-fs-rmdir-recursive-throws-not-found.js", + "test-fs-rmdir-recursive-throws-on-file.js", + "test-fs-rmdir-recursive-warns-not-found.js", + "test-fs-rmdir-recursive-warns-on-file.js", + "test-fs-rmdir-recursive.js", + "test-fs-rmdir-type-check.js", + "TODO:test-fs-watch.js", + "test-fs-watchfile.js", + "test-fs-write-buffer.js", + "test-fs-write-file-buffer.js", + "test-fs-write-file-invalid-path.js", + "test-fs-write-file-sync.js", + "test-fs-write-file.js", + "test-fs-write-no-fd.js", + "test-fs-write-stream-autoclose-option.js", + "test-fs-write-stream-close-without-callback.js", + "test-fs-write-stream-double-close.js", + "test-fs-write-stream-end.js", + "test-fs-write-stream-fs.js", + "test-fs-write-stream-throw-type-error.js", + "test-fs-write-stream.js", + "test-fs-write-sync.js", + "test-fs-write.js", + "test-fs-writev-sync.js", + "TODO:test-fs-writev.js", + "test-handle-wrap-close-abort.js", + "test-http-agent-getname.js", + "test-http-client-get-url.js", + "test-http-client-read-in-error.js", + "test-http-outgoing-buffer.js", + "TODO:test-http-outgoing-destroy.js", + "TODO:test-http-outgoing-finish-writable.js", + "test-http-outgoing-internal-headernames-getter.js", + "test-http-outgoing-internal-headernames-setter.js", + "test-http-outgoing-internal-headers.js", + "test-http-outgoing-message-inheritance.js", + "test-http-outgoing-renderHeaders.js", + "test-http-outgoing-settimeout.js", + "test-http-url.parse-auth.js", + "test-http-url.parse-path.js", + "test-http-url.parse-post.js", + "test-http-url.parse-search.js", + "test-net-access-byteswritten.js", + "TODO:test-net-after-close.js", + "TODO:test-net-allow-half-open.js", + "test-net-better-error-messages-listen-path.js", + "TODO:test-net-better-error-messages-listen.js", + "test-net-better-error-messages-path.js", + "test-net-better-error-messages-port-hostname.js", + "TODO:test-net-bind-twice.js", + "TODO:test-net-buffersize.js", + "TODO:test-net-bytes-written-large.js", + "TODO:test-net-can-reset-timeout.js", + "test-net-connect-after-destroy.js", + "TODO:test-net-connect-buffer.js", + "TODO:test-net-connect-buffer2.js", + "TODO:test-net-connect-call-socket-connect.js", + "test-net-connect-destroy.js", + "test-net-connect-immediate-destroy.js", + "test-net-connect-immediate-finish.js", + "test-net-connect-no-arg.js", + "TODO:test-net-connect-options-ipv6.js", + "TODO:test-net-connect-options-port.js", + "TODO:test-net-dns-custom-lookup.js", + "test-net-dns-error.js", + "TODO:test-net-dns-lookup-skip.js", + "TODO:test-net-dns-lookup.js", + "test-net-during-close.js", + "TODO:test-net-eaddrinuse.js", + "test-net-end-close.js", + "TODO:test-net-end-destroyed.js", + "test-net-end-without-connect.js", + "test-net-isip.js", + "test-net-isipv4.js", + "test-net-isipv6.js", + "TODO:test-net-listen-after-destroying-stdin.js", + "test-net-listen-close-server-callback-is-not-function.js", + "test-net-listen-close-server.js", + "TODO:test-net-listen-error.js", + "test-net-listen-invalid-port.js", + "test-net-listening.js", + "TODO:test-net-local-address-port.js", + "test-net-localerror.js", + "test-net-options-lookup.js", + "TODO:test-net-pause-resume-connecting.js", + "TODO:test-net-persistent-ref-unref.js", + "test-net-pipe-connect-errors.js", + "TODO:test-net-remote-address-port.js", + "TODO:test-net-server-call-listen-multiple-times.js", + "TODO:test-net-server-capture-rejection.js", + "TODO:test-net-server-close.js", + "test-net-server-listen-options-signal.js", + "test-net-server-listen-options.js", + "test-net-server-listen-path.js", + "test-net-server-listen-remove-callback.js", + "TODO:test-net-server-max-connections.js", + "test-net-server-options.js", + "TODO:test-net-server-pause-on-connect.js", + "TODO:test-net-server-try-ports.js", + "test-net-server-unref-persistent.js", + "test-net-server-unref.js", + "TODO:test-net-socket-close-after-end.js", + "TODO:test-net-socket-connect-without-cb.js", + "TODO:test-net-socket-connecting.js", + "TODO:test-net-socket-destroy-send.js", + "test-net-socket-destroy-twice.js", + "TODO:test-net-socket-end-before-connect.js", + "TODO:test-net-socket-end-callback.js", + "test-net-socket-no-halfopen-enforcer.js", + "TODO:test-net-socket-ready-without-cb.js", + "TODO:test-net-socket-timeout.js", + "TODO:test-net-socket-write-after-close.js", + "TODO:test-net-socket-write-error.js", + "TODO:test-net-sync-cork.js", + "test-net-timeout-no-handle.js", + "TODO:test-net-writable.js", + "TODO:test-net-write-after-end-nt.js", + "test-net-write-arguments.js", + "TODO:test-net-write-fully-async-buffer.js", + "TODO:test-net-write-fully-async-hex-string.js", + "TODO:test-net-write-slow.js", + "test-next-tick-doesnt-hang.js", + "test-next-tick-fixed-queue-regression.js", + "test-next-tick-intentional-starvation.js", + "test-next-tick-ordering.js", + "test-next-tick-ordering2.js", + "test-next-tick-when-exiting.js", + "test-next-tick.js", + "test-nodeeventtarget.js", + "TODO:test-os.js", + "test-outgoing-message-destroy.js", + "test-outgoing-message-pipe.js", + "test-path-basename.js", + "test-path-dirname.js", + "test-path-extname.js", + "test-path-isabsolute.js", + "test-path-join.js", + "test-path-makelong.js", + "test-path-normalize.js", + "test-path-parse-format.js", + "test-path-posix-exists.js", + "test-path-relative.js", + "test-path-resolve.js", + "test-path-win32-exists.js", + "test-path-zero-length-strings.js", + "test-path.js", + "test-process-beforeexit.js", + "test-process-binding-internalbinding-allowlist.js", + "test-process-env-allowed-flags.js", + "test-process-exit-from-before-exit.js", + "test-process-exit-handler.js", + "test-process-exit-recursive.js", + "test-process-exit.js", + "test-process-kill-pid.js", + "test-process-uptime.js", + "test-promise-unhandled-silent.js", + "test-promise-unhandled-throw-handler.js", + "TODO:test-punycode.js", + "test-querystring-escape.js", + "test-querystring-maxKeys-non-finite.js", + "test-querystring-multichar-separator.js", + "test-querystring.js", + "TODO:test-readline-csi.js", + "test-readline-emit-keypress-events.js", + "test-readline-interface-escapecodetimeout.js", + "TODO:test-readline-interface.js", + "test-readline-keys.js", + "test-readline-position.js", + "TODO:test-readline-promises-csi.mjs", + "TODO:test-readline-promises-interface.js", + "test-readline-reopen.js", + "test-readline-set-raw-mode.js", + "test-readline-undefined-columns.js", + "test-readline.js", + "test-stdin-from-file-spawn.js", + "test-stream-add-abort-signal.js", + "test-stream-aliases-legacy.js", + "TODO:test-stream-asIndexedPairs.mjs", + "test-stream-auto-destroy.js", + "test-stream-await-drain-writers-in-synchronously-recursion-write.js", + "test-stream-backpressure.js", + "test-stream-big-packet.js", + "test-stream-big-push.js", + "test-stream-buffer-list.js", + "TODO:test-stream-catch-rejections.js", + "test-stream-construct.js", + "test-stream-destroy-event-order.js", + "test-stream-duplex-destroy.js", + "test-stream-duplex-end.js", + "test-stream-duplex-from.js", + "test-stream-duplex-props.js", + "test-stream-duplex-readable-end.js", + "test-stream-duplex-writable-finished.js", + "test-stream-duplex.js", + "test-stream-end-paused.js", + "test-stream-error-once.js", + "test-stream-events-prepend.js", + "test-stream-inheritance.js", + "test-stream-ispaused.js", + "test-stream-objectmode-undefined.js", + "test-stream-once-readable-pipe.js", + "test-stream-pipe-after-end.js", + "test-stream-pipe-await-drain-manual-resume.js", + "test-stream-pipe-await-drain-push-while-write.js", + "test-stream-pipe-await-drain.js", + "test-stream-pipe-cleanup-pause.js", + "test-stream-pipe-cleanup.js", + "test-stream-pipe-error-handling.js", + "test-stream-pipe-event.js", + "test-stream-pipe-flow-after-unpipe.js", + "test-stream-pipe-flow.js", + "test-stream-pipe-manual-resume.js", + "test-stream-pipe-multiple-pipes.js", + "test-stream-pipe-needDrain.js", + "test-stream-pipe-same-destination-twice.js", + "test-stream-pipe-unpipe-streams.js", + "test-stream-pipe-without-listenerCount.js", + "test-stream-pipeline-async-iterator.js", + "test-stream-pipeline-queued-end-in-destroy.js", + "test-stream-pipeline-with-empty-string.js", + "test-stream-push-strings.js", + "test-stream-readable-aborted.js", + "test-stream-readable-add-chunk-during-data.js", + "test-stream-readable-constructor-set-methods.js", + "test-stream-readable-data.js", + "test-stream-readable-destroy.js", + "test-stream-readable-didRead.js", + "test-stream-readable-emit-readable-short-stream.js", + "test-stream-readable-emittedReadable.js", + "test-stream-readable-end-destroyed.js", + "test-stream-readable-ended.js", + "test-stream-readable-error-end.js", + "test-stream-readable-event.js", + "test-stream-readable-flow-recursion.js", + "test-stream-readable-hwm-0-async.js", + "test-stream-readable-hwm-0-no-flow-data.js", + "test-stream-readable-hwm-0.js", + "test-stream-readable-infinite-read.js", + "test-stream-readable-invalid-chunk.js", + "test-stream-readable-needReadable.js", + "test-stream-readable-next-no-null.js", + "test-stream-readable-no-unneeded-readable.js", + "test-stream-readable-object-multi-push-async.js", + "test-stream-readable-pause-and-resume.js", + "test-stream-readable-readable-then-resume.js", + "test-stream-readable-readable.js", + "test-stream-readable-reading-readingMore.js", + "test-stream-readable-resume-hwm.js", + "test-stream-readable-resumeScheduled.js", + "test-stream-readable-setEncoding-existing-buffers.js", + "test-stream-readable-setEncoding-null.js", + "test-stream-readable-unshift.js", + "test-stream-readable-with-unimplemented-_read.js", + "test-stream-readableListening-state.js", + "TODO:test-stream-some-find-every.mjs", + "test-stream-transform-callback-twice.js", + "test-stream-transform-constructor-set-methods.js", + "test-stream-transform-destroy.js", + "test-stream-transform-final-sync.js", + "test-stream-transform-final.js", + "test-stream-transform-flush-data.js", + "test-stream-transform-objectmode-falsey-value.js", + "test-stream-transform-split-highwatermark.js", + "test-stream-transform-split-objectmode.js", + "test-stream-uint8array.js", + "test-stream-unpipe-event.js", + "test-stream-unshift-empty-chunk.js", + "test-stream-unshift-read-race.js", + "test-stream-writable-change-default-encoding.js", + "test-stream-writable-clear-buffer.js", + "test-stream-writable-constructor-set-methods.js", + "test-stream-writable-decoded-encoding.js", + "test-stream-writable-destroy.js", + "test-stream-writable-end-cb-error.js", + "test-stream-writable-end-multiple.js", + "test-stream-writable-ended-state.js", + "test-stream-writable-finish-destroyed.js", + "test-stream-writable-finished-state.js", + "test-stream-writable-finished.js", + "test-stream-writable-invalid-chunk.js", + "test-stream-writable-needdrain-state.js", + "test-stream-writable-null.js", + "test-stream-writable-properties.js", + "test-stream-writable-writable.js", + "test-stream-writable-write-cb-error.js", + "test-stream-writable-write-cb-twice.js", + "test-stream-writable-write-error.js", + "test-stream-writable-write-writev-finish.js", + "test-stream-writableState-ending.js", + "test-stream-writableState-uncorked-bufferedRequestCount.js", + "test-stream-write-destroy.js", + "test-stream-write-drain.js", + "test-stream-write-final.js", + "test-stream-writev.js", + "test-stream2-base64-single-char-read-end.js", + "test-stream2-basic.js", + "test-stream2-compatibility.js", + "test-stream2-decode-partial.js", + "test-stream2-finish-pipe.js", + "test-stream2-large-read-stall.js", + "test-stream2-objects.js", + "test-stream2-pipe-error-handling.js", + "test-stream2-pipe-error-once-listener.js", + "test-stream2-push.js", + "test-stream2-read-sync-stack.js", + "test-stream2-readable-empty-buffer-no-eof.js", + "test-stream2-readable-from-list.js", + "test-stream2-readable-legacy-drain.js", + "test-stream2-readable-non-empty-end.js", + "test-stream2-readable-wrap-destroy.js", + "test-stream2-readable-wrap-empty.js", + "test-stream2-readable-wrap-error.js", + "test-stream2-readable-wrap.js", + "test-stream2-set-encoding.js", + "test-stream2-transform.js", + "test-stream2-unpipe-drain.js", + "test-stream2-unpipe-leak.js", + "test-stream2-writable.js", + "test-stream3-cork-end.js", + "test-stream3-cork-uncork.js", + "test-stream3-pause-then-read.js", + "test-streams-highwatermark.js", + "test-timers-api-refs.js", + "test-timers-args.js", + "test-timers-clear-null-does-not-throw-error.js", + "test-timers-clear-object-does-not-throw-error.js", + "test-timers-clear-timeout-interval-equivalent.js", + "test-timers-clearImmediate.js", + "test-timers-interval-throw.js", + "test-timers-non-integer-delay.js", + "test-timers-refresh.js", + "test-timers-same-timeout-wrong-list-deleted.js", + "test-timers-timeout-with-non-integer.js", + "test-timers-uncaught-exception.js", + "test-timers-unref-throw-then-ref.js", + "test-timers-user-call.js", + "test-timers-zero-timeout.js", + "TODO:test-tls-connect-simple.js", + "test-url-domain-ascii-unicode.js", + "test-url-fileurltopath.js", + "test-url-format-invalid-input.js", + "test-url-format-whatwg.js", + "test-url-format.js", + "test-url-parse-format.js", + "test-url-parse-invalid-input.js", + "test-url-parse-query.js", + "test-url-pathtofileurl.js", + "test-url-relative.js", + "test-url-urltooptions.js", + "test-util-deprecate-invalid-code.js", + "test-util-deprecate.js", + "test-util-format.js", + "test-util-inherits.js", + "test-util-inspect-long-running.js", + "test-util-inspect-namespace.js", + "test-util-inspect-proxy.js", + "test-util-inspect.js", + "test-util-isDeepStrictEqual.js", + "test-util-promisify.js", + "test-util-types-exists.js", + "TODO:test-util-types.js", + "test-util.js", + "test-vm-static-this.js", + "test-webcrypto-sign-verify.js", + "test-whatwg-encoding-custom-api-basics.js", + "test-whatwg-encoding-custom-fatal-streaming.js", + "test-whatwg-encoding-custom-textdecoder-fatal.js", + "test-whatwg-encoding-custom-textdecoder-ignorebom.js", + "test-whatwg-encoding-custom-textdecoder-streaming.js", + "test-whatwg-encoding-custom-textdecoder-utf16-surrogates.js", + "test-whatwg-events-add-event-listener-options-passive.js", + "test-whatwg-events-add-event-listener-options-signal.js", + "test-whatwg-events-customevent.js", + "test-whatwg-url-custom-deepequal.js", + "test-whatwg-url-custom-domainto.js", + "test-whatwg-url-custom-global.js", + "test-whatwg-url-custom-href-side-effect.js", + "test-whatwg-url-custom-inspect.js", + "test-whatwg-url-custom-parsing.js", + "test-whatwg-url-custom-setters.js", + "test-whatwg-url-custom-tostringtag.js", + "test-whatwg-url-override-hostname.js", + "test-whatwg-url-properties.js", + "test-whatwg-url-toascii.js", + "test-zlib-close-after-error.js", + "test-zlib-close-after-write.js", + "test-zlib-convenience-methods.js", + "test-zlib-deflate-raw-inherits.js", + "test-zlib-destroy-pipe.js", + "test-zlib-empty-buffer.js", + "test-zlib-from-string.js", + "test-zlib-invalid-input.js", + "test-zlib-no-stream.js", + "test-zlib-random-byte-pipes.js", + "test-zlib-sync-no-event.js", + "test-zlib-truncated.js", + "test-zlib-unzip-one-byte-chunks.js", + "test-zlib-write-after-end.js", + "test-zlib-write-after-flush.js", + "test-zlib-zero-byte.js", + "test-zlib-zero-windowBits.js" + ], + "pseudo-tty": [ + "console-dumb-tty.js", + "console_colors.js", + "no_dropped_stdio.js", + "no_interleaved_stdio.js", + "test-tty-color-support-warning-2.js", + "test-tty-color-support-warning.js", + "test-tty-stdin-end.js", + "test-tty-stdout-end.js" + ], + "pummel": [ + "TODO:test-net-bytes-per-incoming-chunk-overhead.js", + "TODO:test-net-pingpong-delay.js", + "TODO:test-net-write-callbacks.js" + ], + "sequential": [ + "test-child-process-exit.js" + ] + }, + "windowsIgnore": { + "parallel": [ + "test-child-process-exec-abortcontroller-promisified.js", + "test-console-log-throw-primitive.js", + "test-console-no-swallow-stack-overflow.js", + "test-console-sync-write-error.js", + "test-dns.js", + "test-dns-resolveany.js", + "test-fs-access.js", + "test-fs-mkdir.js", + "test-fs-open-mode-mask.js", + "test-fs-readdir-stack-overflow.js", + "test-fs-rm.js", + "test-fs-watchfile.js", + "test-fs-write-file-invalid-path.js", + "test-fs-write-file-sync.js", + "test-fs-write-file.js", + "test-http-client-get-url.js", + "test-http-client-reject-cr-no-lf.js", + "test-net-allow-half-open.js", + "test-net-better-error-messages-listen-path.js", + "test-net-better-error-messages-path.js", + "test-net-pipe-connect-errors.js", + "test-net-server-listen-path.js", + "test-net-socket-close-after-end.js", + "test-util-inspect-long-running.js", + "test-util-inspect.js" + ] + }, + "darwinIgnore": { + "parallel": [ + "test-net-allow-half-open.js", + "test-net-socket-close-after-end.js" + ] + }, + "suitesFolder": "test", + "versionsFolder": "versions" +} diff --git a/cli/tests/node_compat/import_map.json b/cli/tests/node_compat/import_map.json new file mode 100644 index 00000000000000..ccdef1e007543f --- /dev/null +++ b/cli/tests/node_compat/import_map.json @@ -0,0 +1,5 @@ +{ + "imports": { + "std/": "../../../test_util/std/" + } +} diff --git a/cli/tests/node_compat/runner.ts b/cli/tests/node_compat/runner.ts new file mode 100644 index 00000000000000..f12cc69b02906f --- /dev/null +++ b/cli/tests/node_compat/runner.ts @@ -0,0 +1,7 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. +import { createRequire } from "node:module"; +const file = Deno.args[0]; +if (!file) { + throw new Error("No file provided"); +} +createRequire(import.meta.url)(file); diff --git a/cli/tests/node_compat/test.ts b/cli/tests/node_compat/test.ts new file mode 100644 index 00000000000000..4029596bde36c0 --- /dev/null +++ b/cli/tests/node_compat/test.ts @@ -0,0 +1,117 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. +import { magenta } from "std/fmt/colors.ts"; +import { dirname, fromFileUrl, join } from "std/path/mod.ts"; +import { fail } from "std/testing/asserts.ts"; +import { config, getPathsFromTestSuites } from "./common.ts"; + +// If the test case is invoked like +// deno test -A cli/tests/node_compat/test.ts -- +// Use the test-names as filters +const filters = Deno.args; + +/** + * This script will run the test files specified in the configuration file + * + * Each test file will be run independently and wait until completion, if an abnormal + * code for the test is reported, the test suite will fail immediately + */ + +const toolsPath = dirname(fromFileUrl(import.meta.url)); +const stdRootUrl = new URL("../../", import.meta.url).href; +const testPaths = getPathsFromTestSuites(config.tests); +const cwd = new URL(".", import.meta.url); +const importMap = "import_map.json"; +const windowsIgnorePaths = new Set( + getPathsFromTestSuites(config.windowsIgnore), +); +const darwinIgnorePaths = new Set( + getPathsFromTestSuites(config.darwinIgnore), +); + +const decoder = new TextDecoder(); + +for await (const path of testPaths) { + // If filter patterns are given and any pattern doesn't match + // to the file path, then skip the case + if ( + filters.length > 0 && + filters.every((pattern) => !path.includes(pattern)) + ) { + continue; + } + const isTodo = path.includes("TODO"); + const ignore = + (Deno.build.os === "windows" && windowsIgnorePaths.has(path)) || + (Deno.build.os === "darwin" && darwinIgnorePaths.has(path)) || isTodo; + Deno.test({ + name: `Node.js compatibility "${path}"`, + ignore, + fn: async () => { + const testCase = join(toolsPath, "test", path); + + const v8Flags = ["--stack-size=4000"]; + const testSource = await Deno.readTextFile(testCase); + // TODO(kt3k): Parse `Flags` directive correctly + if (testSource.includes("Flags: --expose_externalize_string")) { + v8Flags.push("--expose-externalize-string"); + } + + const args = [ + "run", + "-A", + "--quiet", + "--unstable", + //"--unsafely-ignore-certificate-errors", + "--v8-flags=" + v8Flags.join(), + testCase.endsWith(".mjs") ? "--import-map=" + importMap : "runner.ts", + testCase, + ]; + + // Pipe stdout in order to output each test result as Deno.test output + // That way the tests will respect the `--quiet` option when provided + const command = new Deno.Command(Deno.execPath(), { + args, + env: { + DENO_NODE_COMPAT_URL: stdRootUrl, + }, + cwd, + }); + const { code, stdout, stderr } = await command.output(); + + if (stdout.length) console.log(decoder.decode(stdout)); + + if (code !== 0) { + console.log(`Error: "${path}" failed`); + console.log( + "You can repeat only this test with the command:", + magenta( + `./target/debug/deno test -A --import-map cli/tests/node_compat/import_map.json cli/tests/node_compat/test.ts -- ${path}`, + ), + ); + fail(decoder.decode(stderr)); + } + }, + }); +} + +function checkConfigTestFilesOrder(testFileLists: Array) { + for (let testFileList of testFileLists) { + testFileList = testFileList.filter((name) => !name.startsWith("TODO:")); + const sortedTestList = JSON.parse(JSON.stringify(testFileList)); + sortedTestList.sort(); + if (JSON.stringify(testFileList) !== JSON.stringify(sortedTestList)) { + throw new Error( + `File names in \`config.json\` are not correct order.`, + ); + } + } +} + +if (filters.length === 0) { + Deno.test("checkConfigTestFilesOrder", function () { + checkConfigTestFilesOrder([ + ...Object.keys(config.ignore).map((suite) => config.ignore[suite]), + ...Object.keys(config.tests).map((suite) => config.tests[suite]), + ]); + }); +} diff --git a/cli/tests/node_compat/test/common/child_process.js b/cli/tests/node_compat/test/common/child_process.js new file mode 100644 index 00000000000000..4b553ea84bf41b --- /dev/null +++ b/cli/tests/node_compat/test/common/child_process.js @@ -0,0 +1,56 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +'use strict'; + +const assert = require('assert'); +const common = require('./'); + +// Workaround for Windows Server 2008R2 +// When CMD is used to launch a process and CMD is killed too quickly, the +// process can stay behind running in suspended state, never completing. +function cleanupStaleProcess(filename) { + if (!common.isWindows) { + return; + } + process.once('beforeExit', () => { + const basename = filename.replace(/.*[/\\]/g, ''); + try { + require('child_process') + .execFileSync(`${process.env.SystemRoot}\\System32\\wbem\\WMIC.exe`, [ + 'process', + 'where', + `commandline like '%${basename}%child'`, + 'delete', + '/nointeractive', + ]); + } catch { + // Ignore failures, there might not be any stale process to clean up. + } + }); +} + +// This should keep the child process running long enough to expire +// the timeout. +const kExpiringChildRunTime = common.platformTimeout(20 * 1000); +const kExpiringParentTimer = 1; +assert(kExpiringChildRunTime > kExpiringParentTimer); + +function logAfterTime(time) { + setTimeout(() => { + // The following console statements are part of the test. + console.log('child stdout'); + console.error('child stderr'); + }, time); +} + +module.exports = { + cleanupStaleProcess, + logAfterTime, + kExpiringChildRunTime, + kExpiringParentTimer +}; diff --git a/cli/tests/node_compat/test/common/countdown.js b/cli/tests/node_compat/test/common/countdown.js new file mode 100644 index 00000000000000..635a24374d3906 --- /dev/null +++ b/cli/tests/node_compat/test/common/countdown.js @@ -0,0 +1,35 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +'use strict'; + +const assert = require('assert'); +const kLimit = Symbol('limit'); +const kCallback = Symbol('callback'); +const common = require('./'); + +class Countdown { + constructor(limit, cb) { + assert.strictEqual(typeof limit, 'number'); + assert.strictEqual(typeof cb, 'function'); + this[kLimit] = limit; + this[kCallback] = common.mustCall(cb); + } + + dec() { + assert(this[kLimit] > 0, 'Countdown expired'); + if (--this[kLimit] === 0) + this[kCallback](); + return this[kLimit]; + } + + get remaining() { + return this[kLimit]; + } +} + +module.exports = Countdown; diff --git a/cli/tests/node_compat/test/common/dns.js b/cli/tests/node_compat/test/common/dns.js new file mode 100644 index 00000000000000..d89769298b24ea --- /dev/null +++ b/cli/tests/node_compat/test/common/dns.js @@ -0,0 +1,327 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +'use strict'; + +const assert = require('assert'); +const os = require('os'); + +const types = { + A: 1, + AAAA: 28, + NS: 2, + CNAME: 5, + SOA: 6, + PTR: 12, + MX: 15, + TXT: 16, + ANY: 255, + CAA: 257 +}; + +const classes = { + IN: 1 +}; + +// Naïve DNS parser/serializer. + +function readDomainFromPacket(buffer, offset) { + assert.ok(offset < buffer.length); + const length = buffer[offset]; + if (length === 0) { + return { nread: 1, domain: '' }; + } else if ((length & 0xC0) === 0) { + offset += 1; + const chunk = buffer.toString('ascii', offset, offset + length); + // Read the rest of the domain. + const { nread, domain } = readDomainFromPacket(buffer, offset + length); + return { + nread: 1 + length + nread, + domain: domain ? `${chunk}.${domain}` : chunk + }; + } + // Pointer to another part of the packet. + assert.strictEqual(length & 0xC0, 0xC0); + // eslint-disable-next-line space-infix-ops, space-unary-ops + const pointeeOffset = buffer.readUInt16BE(offset) &~ 0xC000; + return { + nread: 2, + domain: readDomainFromPacket(buffer, pointeeOffset) + }; +} + +function parseDNSPacket(buffer) { + assert.ok(buffer.length > 12); + + const parsed = { + id: buffer.readUInt16BE(0), + flags: buffer.readUInt16BE(2), + }; + + const counts = [ + ['questions', buffer.readUInt16BE(4)], + ['answers', buffer.readUInt16BE(6)], + ['authorityAnswers', buffer.readUInt16BE(8)], + ['additionalRecords', buffer.readUInt16BE(10)], + ]; + + let offset = 12; + for (const [ sectionName, count ] of counts) { + parsed[sectionName] = []; + for (let i = 0; i < count; ++i) { + const { nread, domain } = readDomainFromPacket(buffer, offset); + offset += nread; + + const type = buffer.readUInt16BE(offset); + + const rr = { + domain, + cls: buffer.readUInt16BE(offset + 2), + }; + offset += 4; + + for (const name in types) { + if (types[name] === type) + rr.type = name; + } + + if (sectionName !== 'questions') { + rr.ttl = buffer.readInt32BE(offset); + const dataLength = buffer.readUInt16BE(offset); + offset += 6; + + switch (type) { + case types.A: + assert.strictEqual(dataLength, 4); + rr.address = `${buffer[offset + 0]}.${buffer[offset + 1]}.` + + `${buffer[offset + 2]}.${buffer[offset + 3]}`; + break; + case types.AAAA: + assert.strictEqual(dataLength, 16); + rr.address = buffer.toString('hex', offset, offset + 16) + .replace(/(.{4}(?!$))/g, '$1:'); + break; + case types.TXT: + { + let position = offset; + rr.entries = []; + while (position < offset + dataLength) { + const txtLength = buffer[offset]; + rr.entries.push(buffer.toString('utf8', + position + 1, + position + 1 + txtLength)); + position += 1 + txtLength; + } + assert.strictEqual(position, offset + dataLength); + break; + } + case types.MX: + { + rr.priority = buffer.readInt16BE(buffer, offset); + offset += 2; + const { nread, domain } = readDomainFromPacket(buffer, offset); + rr.exchange = domain; + assert.strictEqual(nread, dataLength); + break; + } + case types.NS: + case types.CNAME: + case types.PTR: + { + const { nread, domain } = readDomainFromPacket(buffer, offset); + rr.value = domain; + assert.strictEqual(nread, dataLength); + break; + } + case types.SOA: + { + const mname = readDomainFromPacket(buffer, offset); + const rname = readDomainFromPacket(buffer, offset + mname.nread); + rr.nsname = mname.domain; + rr.hostmaster = rname.domain; + const trailerOffset = offset + mname.nread + rname.nread; + rr.serial = buffer.readUInt32BE(trailerOffset); + rr.refresh = buffer.readUInt32BE(trailerOffset + 4); + rr.retry = buffer.readUInt32BE(trailerOffset + 8); + rr.expire = buffer.readUInt32BE(trailerOffset + 12); + rr.minttl = buffer.readUInt32BE(trailerOffset + 16); + + assert.strictEqual(trailerOffset + 20, dataLength); + break; + } + default: + throw new Error(`Unknown RR type ${rr.type}`); + } + offset += dataLength; + } + + parsed[sectionName].push(rr); + + assert.ok(offset <= buffer.length); + } + } + + assert.strictEqual(offset, buffer.length); + return parsed; +} + +function writeIPv6(ip) { + const parts = ip.replace(/^:|:$/g, '').split(':'); + const buf = Buffer.alloc(16); + + let offset = 0; + for (const part of parts) { + if (part === '') { + offset += 16 - 2 * (parts.length - 1); + } else { + buf.writeUInt16BE(parseInt(part, 16), offset); + offset += 2; + } + } + + return buf; +} + +function writeDomainName(domain) { + return Buffer.concat(domain.split('.').map((label) => { + assert(label.length < 64); + return Buffer.concat([ + Buffer.from([label.length]), + Buffer.from(label, 'ascii'), + ]); + }).concat([Buffer.alloc(1)])); +} + +function writeDNSPacket(parsed) { + const buffers = []; + const kStandardResponseFlags = 0x8180; + + buffers.push(new Uint16Array([ + parsed.id, + parsed.flags === undefined ? kStandardResponseFlags : parsed.flags, + parsed.questions && parsed.questions.length, + parsed.answers && parsed.answers.length, + parsed.authorityAnswers && parsed.authorityAnswers.length, + parsed.additionalRecords && parsed.additionalRecords.length, + ])); + + for (const q of parsed.questions) { + assert(types[q.type]); + buffers.push(writeDomainName(q.domain)); + buffers.push(new Uint16Array([ + types[q.type], + q.cls === undefined ? classes.IN : q.cls, + ])); + } + + for (const rr of [].concat(parsed.answers, + parsed.authorityAnswers, + parsed.additionalRecords)) { + if (!rr) continue; + + assert(types[rr.type]); + buffers.push(writeDomainName(rr.domain)); + buffers.push(new Uint16Array([ + types[rr.type], + rr.cls === undefined ? classes.IN : rr.cls, + ])); + buffers.push(new Int32Array([rr.ttl])); + + const rdLengthBuf = new Uint16Array(1); + buffers.push(rdLengthBuf); + + switch (rr.type) { + case 'A': + rdLengthBuf[0] = 4; + buffers.push(new Uint8Array(rr.address.split('.'))); + break; + case 'AAAA': + rdLengthBuf[0] = 16; + buffers.push(writeIPv6(rr.address)); + break; + case 'TXT': { + const total = rr.entries.map((s) => s.length).reduce((a, b) => a + b); + // Total length of all strings + 1 byte each for their lengths. + rdLengthBuf[0] = rr.entries.length + total; + for (const txt of rr.entries) { + buffers.push(new Uint8Array([Buffer.byteLength(txt)])); + buffers.push(Buffer.from(txt)); + } + break; + } + case 'MX': + rdLengthBuf[0] = 2; + buffers.push(new Uint16Array([rr.priority])); + // fall through + case 'NS': + case 'CNAME': + case 'PTR': + { + const domain = writeDomainName(rr.exchange || rr.value); + rdLengthBuf[0] += domain.length; + buffers.push(domain); + break; + } + case 'SOA': + { + const mname = writeDomainName(rr.nsname); + const rname = writeDomainName(rr.hostmaster); + rdLengthBuf[0] = mname.length + rname.length + 20; + buffers.push(mname, rname); + buffers.push(new Uint32Array([ + rr.serial, rr.refresh, rr.retry, rr.expire, rr.minttl, + ])); + break; + } + case 'CAA': + { + rdLengthBuf[0] = 5 + rr.issue.length + 2; + buffers.push(Buffer.from([Number(rr.critical)])); + buffers.push(Buffer.from([Number(5)])); + buffers.push(Buffer.from('issue' + rr.issue)); + break; + } + default: + throw new Error(`Unknown RR type ${rr.type}`); + } + } + + return Buffer.concat(buffers.map((typedArray) => { + const buf = Buffer.from(typedArray.buffer, + typedArray.byteOffset, + typedArray.byteLength); + if (os.endianness() === 'LE') { + if (typedArray.BYTES_PER_ELEMENT === 2) buf.swap16(); + if (typedArray.BYTES_PER_ELEMENT === 4) buf.swap32(); + } + return buf; + })); +} + +const mockedErrorCode = 'ENOTFOUND'; +const mockedSysCall = 'getaddrinfo'; + +function errorLookupMock(code = mockedErrorCode, syscall = mockedSysCall) { + return function lookupWithError(hostname, dnsopts, cb) { + const err = new Error(`${syscall} ${code} ${hostname}`); + err.code = code; + err.errno = code; + err.syscall = syscall; + err.hostname = hostname; + cb(err); + }; +} + +module.exports = { + types, + classes, + writeDNSPacket, + parseDNSPacket, + errorLookupMock, + mockedErrorCode, + mockedSysCall +}; diff --git a/cli/tests/node_compat/test/common/duplexpair.js b/cli/tests/node_compat/test/common/duplexpair.js new file mode 100644 index 00000000000000..9e5c498c4efb3e --- /dev/null +++ b/cli/tests/node_compat/test/common/duplexpair.js @@ -0,0 +1,55 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +'use strict'; +const { Duplex } = require('stream'); +const assert = require('assert'); + +const kCallback = Symbol('Callback'); +const kOtherSide = Symbol('Other'); + +class DuplexSocket extends Duplex { + constructor() { + super(); + this[kCallback] = null; + this[kOtherSide] = null; + } + + _read() { + const callback = this[kCallback]; + if (callback) { + this[kCallback] = null; + callback(); + } + } + + _write(chunk, encoding, callback) { + assert.notStrictEqual(this[kOtherSide], null); + assert.strictEqual(this[kOtherSide][kCallback], null); + if (chunk.length === 0) { + process.nextTick(callback); + } else { + this[kOtherSide].push(chunk); + this[kOtherSide][kCallback] = callback; + } + } + + _final(callback) { + this[kOtherSide].on('end', callback); + this[kOtherSide].push(null); + } +} + +function makeDuplexPair() { + const clientSide = new DuplexSocket(); + const serverSide = new DuplexSocket(); + clientSide[kOtherSide] = serverSide; + serverSide[kOtherSide] = clientSide; + return { clientSide, serverSide }; +} + +module.exports = makeDuplexPair; diff --git a/cli/tests/node_compat/test/common/fixtures.js b/cli/tests/node_compat/test/common/fixtures.js new file mode 100644 index 00000000000000..af4dbc5a5627ab --- /dev/null +++ b/cli/tests/node_compat/test/common/fixtures.js @@ -0,0 +1,45 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +'use strict'; + +const path = require('path'); +const fs = require('fs'); +const { pathToFileURL } = require('url'); + +const fixturesDir = path.join(__dirname, '..', 'fixtures'); + +function fixturesPath(...args) { + return path.join(fixturesDir, ...args); +} + +function fixturesFileURL(...args) { + return pathToFileURL(fixturesPath(...args)); +} + +function readFixtureSync(args, enc) { + if (Array.isArray(args)) + return fs.readFileSync(fixturesPath(...args), enc); + return fs.readFileSync(fixturesPath(args), enc); +} + +function readFixtureKey(name, enc) { + return fs.readFileSync(fixturesPath('keys', name), enc); +} + +function readFixtureKeys(enc, ...names) { + return names.map((name) => readFixtureKey(name, enc)); +} + +module.exports = { + fixturesDir, + path: fixturesPath, + fileURL: fixturesFileURL, + readSync: readFixtureSync, + readKey: readFixtureKey, + readKeys: readFixtureKeys, +}; diff --git a/cli/tests/node_compat/test/common/hijackstdio.js b/cli/tests/node_compat/test/common/hijackstdio.js new file mode 100644 index 00000000000000..a746e979168de9 --- /dev/null +++ b/cli/tests/node_compat/test/common/hijackstdio.js @@ -0,0 +1,39 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +'use strict'; + +// Hijack stdout and stderr +const stdWrite = {}; +function hijackStdWritable(name, listener) { + const stream = process[name]; + const _write = stdWrite[name] = stream.write; + + stream.writeTimes = 0; + stream.write = function(data, callback) { + try { + listener(data); + } catch (e) { + process.nextTick(() => { throw e; }); + } + + _write.call(stream, data, callback); + stream.writeTimes++; + }; +} + +function restoreWritable(name) { + process[name].write = stdWrite[name]; + delete process[name].writeTimes; +} + +module.exports = { + hijackStdout: hijackStdWritable.bind(null, 'stdout'), + hijackStderr: hijackStdWritable.bind(null, 'stderr'), + restoreStdout: restoreWritable.bind(null, 'stdout'), + restoreStderr: restoreWritable.bind(null, 'stderr') +}; diff --git a/cli/tests/node_compat/test/common/index.js b/cli/tests/node_compat/test/common/index.js new file mode 100644 index 00000000000000..f93f7b7a6bcd1e --- /dev/null +++ b/cli/tests/node_compat/test/common/index.js @@ -0,0 +1,489 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. + +/** + * This file is meant as a replacement for the original common/index.js + * + * That file has a lot of node functionality not currently supported, so this is a lite + * version of that file, which most tests should be able to use + */ +'use strict'; +const assert = require("assert"); +const path = require("path"); +const util = require("util"); +const tmpdir = require("./tmpdir"); + +function platformTimeout(ms) { + return ms; +} + +let localhostIPv4 = null; + +let knownGlobals = [ + AbortSignal, + addEventListener, + alert, + atob, + btoa, + Buffer, + caches, + clearImmediate, + close, + closed, + confirm, + console, + crypto, + Deno, + dispatchEvent, + fetch, + getParent, + global, + global.clearInterval, + global.clearTimeout, + global.setInterval, + global.setTimeout, + localStorage, + location, + navigator, + onload, + onunload, + process, + prompt, + queueMicrotask, + removeEventListener, + reportError, + self, + sessionStorage, + setImmediate, + window, +]; + +if (global.AbortController) + knownGlobals.push(global.AbortController); + +if (global.gc) { + knownGlobals.push(global.gc); +} + +if (global.performance) { + knownGlobals.push(global.performance); +} +if (global.PerformanceMark) { + knownGlobals.push(global.PerformanceMark); +} +if (global.PerformanceMeasure) { + knownGlobals.push(global.PerformanceMeasure); +} + +if (global.structuredClone) { + knownGlobals.push(global.structuredClone); +} + +function allowGlobals(...allowlist) { + knownGlobals = knownGlobals.concat(allowlist); +} + +if (process.env.NODE_TEST_KNOWN_GLOBALS !== '0') { + if (process.env.NODE_TEST_KNOWN_GLOBALS) { + const knownFromEnv = process.env.NODE_TEST_KNOWN_GLOBALS.split(','); + allowGlobals(...knownFromEnv); + } + + function leakedGlobals() { + const leaked = []; + + for (const val in global) { + if (!knownGlobals.includes(global[val])) { + leaked.push(val); + } + } + + return leaked; + } + + process.on('exit', function() { + const leaked = leakedGlobals(); + if (leaked.length > 0) { + assert.fail(`Unexpected global(s) found: ${leaked.join(', ')}`); + } + }); +} + +function _expectWarning(name, expected, code) { + if (typeof expected === 'string') { + expected = [[expected, code]]; + } else if (!Array.isArray(expected)) { + expected = Object.entries(expected).map(([a, b]) => [b, a]); + } else if (!(Array.isArray(expected[0]))) { + expected = [[expected[0], expected[1]]]; + } + // Deprecation codes are mandatory, everything else is not. + if (name === 'DeprecationWarning') { + expected.forEach(([_, code]) => assert(code, expected)); + } + return mustCall((warning) => { + const [ message, code ] = expected.shift(); + assert.strictEqual(warning.name, name); + if (typeof message === 'string') { + assert.strictEqual(warning.message, message); + } else { + assert.match(warning.message, message); + } + assert.strictEqual(warning.code, code); + }, expected.length); +} + +let catchWarning; + +// Accepts a warning name and description or array of descriptions or a map of +// warning names to description(s) ensures a warning is generated for each +// name/description pair. +// The expected messages have to be unique per `expectWarning()` call. +function expectWarning(nameOrMap, expected, code) { + if (catchWarning === undefined) { + catchWarning = {}; + process.on('warning', (warning) => { + if (!catchWarning[warning.name]) { + throw new TypeError( + `"${warning.name}" was triggered without being expected.\n` + + util.inspect(warning) + ); + } + catchWarning[warning.name](warning); + }); + } + if (typeof nameOrMap === 'string') { + catchWarning[nameOrMap] = _expectWarning(nameOrMap, expected, code); + } else { + Object.keys(nameOrMap).forEach((name) => { + catchWarning[name] = _expectWarning(name, nameOrMap[name]); + }); + } +} + +/** + * Useful for testing expected internal/error objects + * + * @param {Error} error + */ +function expectsError(validator, exact) { + /** + * @param {Error} error + */ + return mustCall((...args) => { + if (args.length !== 1) { + // Do not use `assert.strictEqual()` to prevent `inspect` from + // always being called. + assert.fail(`Expected one argument, got ${util.inspect(args)}`); + } + const error = args.pop(); + const descriptor = Object.getOwnPropertyDescriptor(error, 'message'); + // The error message should be non-enumerable + assert.strictEqual(descriptor.enumerable, false); + + assert.throws(() => { throw error; }, validator); + return true; + }, exact); +} + +const noop = () => {}; + +/** + * @param {Function} fn + * @param {number} exact + */ +function mustCall(fn, exact) { + return _mustCallInner(fn, exact, "exact"); +} + +function mustCallAtLeast(fn, minimum) { + return _mustCallInner(fn, minimum, 'minimum'); +} + +function mustSucceed(fn, exact) { + return mustCall(function(err, ...args) { + assert.ifError(err); + if (typeof fn === 'function') + return fn.apply(this, args); + }, exact); +} + +const mustCallChecks = []; +/** + * @param {number} exitCode + */ +function runCallChecks(exitCode) { + if (exitCode !== 0) return; + + const failed = mustCallChecks.filter(function (context) { + if ("minimum" in context) { + context.messageSegment = `at least ${context.minimum}`; + return context.actual < context.minimum; + } + context.messageSegment = `exactly ${context.exact}`; + return context.actual !== context.exact; + }); + + failed.forEach(function (context) { + console.log( + "Mismatched %s function calls. Expected %s, actual %d.", + context.name, + context.messageSegment, + context.actual, + ); + console.log(context.stack.split("\n").slice(2).join("\n")); + }); + + if (failed.length) process.exit(1); +} + +/** + * @param {Function} fn + * @param {"exact" | "minimum"} field + */ +function _mustCallInner(fn, criteria = 1, field) { + // @ts-ignore + if (process._exiting) { + throw new Error("Cannot use common.mustCall*() in process exit handler"); + } + if (typeof fn === "number") { + criteria = fn; + fn = noop; + } else if (fn === undefined) { + fn = noop; + } + + if (typeof criteria !== "number") { + throw new TypeError(`Invalid ${field} value: ${criteria}`); + } + + let context; + if (field === "exact") { + context = { + exact: criteria, + actual: 0, + stack: util.inspect(new Error()), + name: fn.name || "", + }; + } else { + context = { + minimum: criteria, + actual: 0, + stack: util.inspect(new Error()), + name: fn.name || "", + }; + } + + // Add the exit listener only once to avoid listener leak warnings + if (mustCallChecks.length === 0) process.on("exit", runCallChecks); + + mustCallChecks.push(context); + + return function () { + context.actual++; + return fn.apply(this, arguments); + }; +} + +/** + * @param {string=} msg + */ +function mustNotCall(msg) { + /** + * @param {any[]} args + */ + return function mustNotCall(...args) { + const argsInfo = args.length > 0 + ? `\ncalled with arguments: ${args.map(util.inspect).join(", ")}` + : ""; + assert.fail( + `${msg || "function should not have been called"} at unknown` + + argsInfo, + ); + }; +} + +const _mustNotMutateObjectDeepProxies = new WeakMap(); + +function mustNotMutateObjectDeep(original) { + // Return primitives and functions directly. Primitives are immutable, and + // proxied functions are impossible to compare against originals, e.g. with + // `assert.deepEqual()`. + if (original === null || typeof original !== 'object') { + return original; + } + + const cachedProxy = _mustNotMutateObjectDeepProxies.get(original); + if (cachedProxy) { + return cachedProxy; + } + + const _mustNotMutateObjectDeepHandler = { + __proto__: null, + defineProperty(target, property, descriptor) { + assert.fail(`Expected no side effects, got ${inspect(property)} ` + + 'defined'); + }, + deleteProperty(target, property) { + assert.fail(`Expected no side effects, got ${inspect(property)} ` + + 'deleted'); + }, + get(target, prop, receiver) { + return mustNotMutateObjectDeep(Reflect.get(target, prop, receiver)); + }, + preventExtensions(target) { + assert.fail('Expected no side effects, got extensions prevented on ' + + inspect(target)); + }, + set(target, property, value, receiver) { + assert.fail(`Expected no side effects, got ${inspect(value)} ` + + `assigned to ${inspect(property)}`); + }, + setPrototypeOf(target, prototype) { + assert.fail(`Expected no side effects, got set prototype to ${prototype}`); + } + }; + + const proxy = new Proxy(original, _mustNotMutateObjectDeepHandler); + _mustNotMutateObjectDeepProxies.set(original, proxy); + return proxy; +} + +// A helper function to simplify checking for ERR_INVALID_ARG_TYPE output. +function invalidArgTypeHelper(input) { + if (input == null) { + return ` Received ${input}`; + } + if (typeof input === "function" && input.name) { + return ` Received function ${input.name}`; + } + if (typeof input === "object") { + if (input.constructor && input.constructor.name) { + return ` Received an instance of ${input.constructor.name}`; + } + return ` Received ${util.inspect(input, { depth: -1 })}`; + } + let inspected = util.inspect(input, { colors: false }); + if (inspected.length > 25) { + inspected = `${inspected.slice(0, 25)}...`; + } + return ` Received type ${typeof input} (${inspected})`; +} + +const isWindows = process.platform === 'win32'; +const isAIX = process.platform === 'aix'; +const isSunOS = process.platform === 'sunos'; +const isFreeBSD = process.platform === 'freebsd'; +const isOpenBSD = process.platform === 'openbsd'; +const isLinux = process.platform === 'linux'; +const isOSX = process.platform === 'darwin'; + +const isDumbTerminal = process.env.TERM === 'dumb'; + +function skipIfDumbTerminal() { + if (isDumbTerminal) { + skip('skipping - dumb terminal'); + } +} + +function printSkipMessage(msg) { + console.log(`1..0 # Skipped: ${msg}`); +} + +function skip(msg) { + printSkipMessage(msg); + process.exit(0); +} + +const PIPE = (() => { + const localRelative = path.relative(process.cwd(), `${tmpdir.path}/`); + const pipePrefix = isWindows ? "\\\\.\\pipe\\" : localRelative; + const pipeName = `node-test.${process.pid}.sock`; + return path.join(pipePrefix, pipeName); +})(); + +function getArrayBufferViews(buf) { + const { buffer, byteOffset, byteLength } = buf; + + const out = []; + + const arrayBufferViews = [ + Int8Array, + Uint8Array, + Uint8ClampedArray, + Int16Array, + Uint16Array, + Int32Array, + Uint32Array, + Float32Array, + Float64Array, + DataView, + ]; + + for (const type of arrayBufferViews) { + const { BYTES_PER_ELEMENT = 1 } = type; + if (byteLength % BYTES_PER_ELEMENT === 0) { + out.push(new type(buffer, byteOffset, byteLength / BYTES_PER_ELEMENT)); + } + } + return out; +} + +function getBufferSources(buf) { + return [...getArrayBufferViews(buf), new Uint8Array(buf).buffer]; +} + +const pwdCommand = isWindows ? + ['cmd.exe', ['/d', '/c', 'cd']] : + ['pwd', []]; + +module.exports = { + allowGlobals, + expectsError, + expectWarning, + getArrayBufferViews, + getBufferSources, + hasCrypto: true, + invalidArgTypeHelper, + mustCall, + mustCallAtLeast, + mustNotCall, + mustNotMutateObjectDeep, + mustSucceed, + PIPE, + platformTimeout, + printSkipMessage, + pwdCommand, + skipIfDumbTerminal, + isDumbTerminal, + isWindows, + isAIX, + isSunOS, + isFreeBSD, + isOpenBSD, + isLinux, + isOSX, + isMainThread: true, // TODO(f3n67u): replace with `worker_thread.isMainThread` when `worker_thread` implemented + skip, + get hasIPv6() { + const iFaces = require('os').networkInterfaces(); + const re = isWindows ? /Loopback Pseudo-Interface/ : /lo/; + return Object.keys(iFaces).some((name) => { + return re.test(name) && + iFaces[name].some(({ family }) => family === 'IPv6'); + }); + }, + + get localhostIPv4() { + if (localhostIPv4 !== null) return localhostIPv4; + if (localhostIPv4 === null) localhostIPv4 = '127.0.0.1'; + + return localhostIPv4; + }, + + get PORT() { + return 12346; + }, +}; diff --git a/cli/tests/node_compat/test/common/index.mjs b/cli/tests/node_compat/test/common/index.mjs new file mode 100644 index 00000000000000..d5b9d7dad5efaf --- /dev/null +++ b/cli/tests/node_compat/test/common/index.mjs @@ -0,0 +1,113 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +import { createRequire } from 'module'; + +const require = createRequire(import.meta.url); +const common = require('./index.js'); + +const { + isMainThread, + isWindows, + isAIX, + isIBMi, + isLinuxPPCBE, + isSunOS, + isDumbTerminal, + isFreeBSD, + isOpenBSD, + isLinux, + isOSX, + enoughTestMem, + buildType, + localIPv6Hosts, + opensslCli, + PIPE, + hasCrypto, + hasIPv6, + childShouldThrowAndAbort, + checkoutEOL, + createZeroFilledFile, + platformTimeout, + allowGlobals, + mustCall, + mustCallAtLeast, + mustSucceed, + hasMultiLocalhost, + skipIfDumbTerminal, + skipIfEslintMissing, + canCreateSymLink, + getCallSite, + mustNotCall, + mustNotMutateObjectDeep, + printSkipMessage, + skip, + nodeProcessAborted, + isAlive, + expectWarning, + expectsError, + skipIfInspectorDisabled, + skipIf32Bits, + getArrayBufferViews, + getBufferSources, + getTTYfd, + runWithInvalidFD, + spawnPromisified, +} = common; + +const getPort = () => common.PORT; + +export { + isMainThread, + isWindows, + isAIX, + isIBMi, + isLinuxPPCBE, + isSunOS, + isDumbTerminal, + isFreeBSD, + isOpenBSD, + isLinux, + isOSX, + enoughTestMem, + buildType, + localIPv6Hosts, + opensslCli, + PIPE, + hasCrypto, + hasIPv6, + childShouldThrowAndAbort, + checkoutEOL, + createZeroFilledFile, + platformTimeout, + allowGlobals, + mustCall, + mustCallAtLeast, + mustSucceed, + hasMultiLocalhost, + skipIfDumbTerminal, + skipIfEslintMissing, + canCreateSymLink, + getCallSite, + mustNotCall, + mustNotMutateObjectDeep, + printSkipMessage, + skip, + nodeProcessAborted, + isAlive, + expectWarning, + expectsError, + skipIfInspectorDisabled, + skipIf32Bits, + getArrayBufferViews, + getBufferSources, + getTTYfd, + runWithInvalidFD, + createRequire, + spawnPromisified, + getPort, +}; diff --git a/cli/tests/node_compat/test/common/internet.js b/cli/tests/node_compat/test/common/internet.js new file mode 100644 index 00000000000000..b42fda66c63dab --- /dev/null +++ b/cli/tests/node_compat/test/common/internet.js @@ -0,0 +1,68 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.0 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +'use strict'; + +// Utilities for internet-related tests + +const addresses = { + // A generic host that has registered common DNS records, + // supports both IPv4 and IPv6, and provides basic HTTP/HTTPS services + INET_HOST: 'nodejs.org', + // A host that provides IPv4 services + INET4_HOST: 'nodejs.org', + // A host that provides IPv6 services + INET6_HOST: 'nodejs.org', + // An accessible IPv4 IP, + // defaults to the Google Public DNS IPv4 address + INET4_IP: '8.8.8.8', + // An accessible IPv6 IP, + // defaults to the Google Public DNS IPv6 address + INET6_IP: '2001:4860:4860::8888', + // An invalid host that cannot be resolved + // See https://tools.ietf.org/html/rfc2606#section-2 + INVALID_HOST: 'something.invalid', + // A host with MX records registered + MX_HOST: 'nodejs.org', + // On some systems, .invalid returns a server failure/try again rather than + // record not found. Use this to guarantee record not found. + NOT_FOUND: 'come.on.fhqwhgads.test', + // A host with SRV records registered + // TODO(kt3k): Temporarily use _caldav._tcp.google.com instead of + // _jabber._tcp.google.com, which currently doesn't respond + // SRV_HOST: '_jabber._tcp.google.com', + SRV_HOST: '_caldav._tcp.google.com', + // A host with PTR records registered + PTR_HOST: '8.8.8.8.in-addr.arpa', + // A host with NAPTR records registered + NAPTR_HOST: 'sip2sip.info', + // A host with SOA records registered + SOA_HOST: 'nodejs.org', + // A host with CAA record registered + CAA_HOST: 'google.com', + // A host with CNAME records registered + CNAME_HOST: 'blog.nodejs.org', + // A host with NS records registered + NS_HOST: 'nodejs.org', + // A host with TXT records registered + TXT_HOST: 'nodejs.org', + // An accessible IPv4 DNS server + DNS4_SERVER: '8.8.8.8', + // An accessible IPv4 DNS server + DNS6_SERVER: '2001:4860:4860::8888' +}; + +for (const key of Object.keys(addresses)) { + const envName = `NODE_TEST_${key}`; + if (process.env[envName]) { + addresses[key] = process.env[envName]; + } +} + +module.exports = { + addresses +}; diff --git a/cli/tests/node_compat/test/common/package.json b/cli/tests/node_compat/test/common/package.json new file mode 100644 index 00000000000000..0967ef424bce67 --- /dev/null +++ b/cli/tests/node_compat/test/common/package.json @@ -0,0 +1 @@ +{} diff --git a/cli/tests/node_compat/test/common/tmpdir.js b/cli/tests/node_compat/test/common/tmpdir.js new file mode 100644 index 00000000000000..d3ce98e45b5775 --- /dev/null +++ b/cli/tests/node_compat/test/common/tmpdir.js @@ -0,0 +1,69 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 16.13.0 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +'use strict'; + +const fs = require('fs'); +const path = require('path'); +// const { isMainThread } = require('worker_threads'); + +function rmSync(pathname) { + fs.rmSync(pathname, { maxRetries: 3, recursive: true, force: true }); +} + +const testRoot = process.env.NODE_TEST_DIR ? + fs.realpathSync(process.env.NODE_TEST_DIR) : path.resolve(__dirname, '..'); + +// Using a `.` prefixed name, which is the convention for "hidden" on POSIX, +// gets tools to ignore it by default or by simple rules, especially eslint. +const tmpdirName = '.tmp.' + + (process.env.TEST_SERIAL_ID || process.env.TEST_THREAD_ID || '0'); +const tmpPath = path.join(testRoot, tmpdirName); + +let firstRefresh = true; +function refresh() { + rmSync(this.path); + fs.mkdirSync(this.path); + + if (firstRefresh) { + firstRefresh = false; + // Clean only when a test uses refresh. This allows for child processes to + // use the tmpdir and only the parent will clean on exit. + process.on('exit', onexit); + } +} + +function onexit() { + // Change directory to avoid possible EBUSY + // TODO(f3n67u): uncomment when `worker_thread.isMainThread` implemented + // if (isMainThread) + // process.chdir(testRoot); + + try { + rmSync(tmpPath); + } catch (e) { + console.error('Can\'t clean tmpdir:', tmpPath); + + const files = fs.readdirSync(tmpPath); + console.error('Files blocking:', files); + + if (files.some((f) => f.startsWith('.nfs'))) { + // Warn about NFS "silly rename" + console.error('Note: ".nfs*" might be files that were open and ' + + 'unlinked but not closed.'); + console.error('See http://nfs.sourceforge.net/#faq_d2 for details.'); + } + + console.error(); + throw e; + } +} + +module.exports = { + path: tmpPath, + refresh +}; diff --git a/cli/tests/node_compat/test/fixtures/GH-1899-output.js b/cli/tests/node_compat/test/fixtures/GH-1899-output.js new file mode 100644 index 00000000000000..013b61ba0510e1 --- /dev/null +++ b/cli/tests/node_compat/test/fixtures/GH-1899-output.js @@ -0,0 +1,30 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. + +console.log('hello, world!'); + diff --git a/cli/tests/node_compat/test/fixtures/a.js b/cli/tests/node_compat/test/fixtures/a.js new file mode 100644 index 00000000000000..c3960fd92ceeb2 --- /dev/null +++ b/cli/tests/node_compat/test/fixtures/a.js @@ -0,0 +1,53 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. + +const c = require('./b/c'); + +console.error('load fixtures/a.js'); + +var string = 'A'; + +exports.SomeClass = c.SomeClass; + +exports.A = function() { + return string; +}; + +exports.C = function() { + return c.C(); +}; + +exports.D = function() { + return c.D(); +}; + +exports.number = 42; + +process.on('exit', function() { + string = 'A done'; +}); diff --git a/cli/tests/node_compat/test/fixtures/child-process-spawn-node.js b/cli/tests/node_compat/test/fixtures/child-process-spawn-node.js new file mode 100644 index 00000000000000..589da4dab19f58 --- /dev/null +++ b/cli/tests/node_compat/test/fixtures/child-process-spawn-node.js @@ -0,0 +1,14 @@ +const assert = require('assert'); +// TODO(kt3k): Uncomment this when util.debuglog is added +// const debug = require('util').debuglog('test'); +const debug = console.log; + +function onmessage(m) { + debug('CHILD got message:', m); + assert.ok(m.hello); + process.removeListener('message', onmessage); +} + +process.on('message', onmessage); +// TODO(kt3k): Uncomment the below when the ipc features are ready +// process.send({ foo: 'bar' }); diff --git a/cli/tests/node_compat/test/fixtures/child_process_should_emit_error.js b/cli/tests/node_compat/test/fixtures/child_process_should_emit_error.js new file mode 100644 index 00000000000000..7ceaf9209080f2 --- /dev/null +++ b/cli/tests/node_compat/test/fixtures/child_process_should_emit_error.js @@ -0,0 +1,36 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. + +const exec = require('child_process').exec; + +[0, 1].forEach(function(i) { + exec('ls', function(err, stdout, stderr) { + console.log(i); + throw new Error('hello world'); + }); +}); diff --git a/cli/tests/node_compat/test/fixtures/echo.js b/cli/tests/node_compat/test/fixtures/echo.js new file mode 100644 index 00000000000000..893099e9bd9d4c --- /dev/null +++ b/cli/tests/node_compat/test/fixtures/echo.js @@ -0,0 +1,41 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. + +const common = require('../common'); +const assert = require('assert'); + +process.stdout.write('hello world\r\n'); + +// TODO(PolarETech): process.openStdin() is not yet implemented. +// Use process.stdin instead. +var stdin = process.stdin; +// var stdin = process.openStdin(); + +stdin.on('data', function(data) { + process.stdout.write(data.toString()); +}); diff --git a/cli/tests/node_compat/test/fixtures/elipses.txt b/cli/tests/node_compat/test/fixtures/elipses.txt new file mode 100644 index 00000000000000..610560050537d6 --- /dev/null +++ b/cli/tests/node_compat/test/fixtures/elipses.txt @@ -0,0 +1 @@ +………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………… \ No newline at end of file diff --git a/cli/tests/node_compat/test/fixtures/empty.txt b/cli/tests/node_compat/test/fixtures/empty.txt new file mode 100644 index 00000000000000..e69de29bb2d1d6 diff --git a/cli/tests/node_compat/test/fixtures/exit.js b/cli/tests/node_compat/test/fixtures/exit.js new file mode 100644 index 00000000000000..ca80f48286b308 --- /dev/null +++ b/cli/tests/node_compat/test/fixtures/exit.js @@ -0,0 +1,31 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. + +// TODO(PolarETech): The process.argv[3] should be argv[2]. + +process.exit(process.argv[3] || 1); diff --git a/cli/tests/node_compat/test/fixtures/keys/agent1-cert.pem b/cli/tests/node_compat/test/fixtures/keys/agent1-cert.pem new file mode 100644 index 00000000000000..938ec42b721079 --- /dev/null +++ b/cli/tests/node_compat/test/fixtures/keys/agent1-cert.pem @@ -0,0 +1,30 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +-----BEGIN CERTIFICATE----- +MIID6DCCAtCgAwIBAgIUFH02wcL3Qgben6tfIibXitsApCYwDQYJKoZIhvcNAQEL +BQAwejELMAkGA1UEBhMCVVMxCzAJBgNVBAgMAkNBMQswCQYDVQQHDAJTRjEPMA0G +A1UECgwGSm95ZW50MRAwDgYDVQQLDAdOb2RlLmpzMQwwCgYDVQQDDANjYTExIDAe +BgkqhkiG9w0BCQEWEXJ5QHRpbnljbG91ZHMub3JnMCAXDTIyMDkwMzIxNDAzN1oY +DzIyOTYwNjE3MjE0MDM3WjB9MQswCQYDVQQGEwJVUzELMAkGA1UECAwCQ0ExCzAJ +BgNVBAcMAlNGMQ8wDQYDVQQKDAZKb3llbnQxEDAOBgNVBAsMB05vZGUuanMxDzAN +BgNVBAMMBmFnZW50MTEgMB4GCSqGSIb3DQEJARYRcnlAdGlueWNsb3Vkcy5vcmcw +ggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDUVjIK+yDTgnCT3CxChO0E +37q9VuHdrlKeKLeQzUJW2yczSfNzX/0zfHpjY+zKWie39z3HCJqWxtiG2wxiOI8c +3WqWOvzVmdWADlh6EfkIlg+E7VC6JaKDA+zabmhPvnuu3JzogBMnsWl68lCXzuPx +deQAmEwNtqjrh74DtM+Ud0ulb//Ixjxo1q3rYKu+aaexSramuee6qJta2rjrB4l8 +B/bU+j1mDf9XQQfSjo9jRnp4hiTFdBl2k+lZzqE2L/rhu6EMjA2IhAq/7xA2MbLo +9cObVUin6lfoo5+JKRgT9Fp2xEgDOit+2EA/S6oUfPNeLSVUqmXOSWlXlwlb9Nxr +AgMBAAGjYTBfMF0GCCsGAQUFBwEBBFEwTzAjBggrBgEFBQcwAYYXaHR0cDovL29j +c3Aubm9kZWpzLm9yZy8wKAYIKwYBBQUHMAKGHGh0dHA6Ly9jYS5ub2RlanMub3Jn +L2NhLmNlcnQwDQYJKoZIhvcNAQELBQADggEBAMM0mBBjLMt9pYXePtUeNO0VTw9y +FWCM8nAcAO2kRNwkJwcsispNpkcsHZ5o8Xf5mpCotdvziEWG1hyxwU6nAWyNOLcN +G0a0KUfbMO3B6ZYe1GwPDjXaQnv75SkAdxgX5zOzca3xnhITcjUUGjQ0fbDfwFV5 +ix8mnzvfXjDONdEznVa7PFcN6QliFUMwR/h8pCRHtE5+a10OSPeJSrGG+FtrGnRW +G1IJUv6oiGF/MvWCr84REVgc1j78xomGANJIu2hN7bnD1nEMON6em8IfnDOUtynV +9wfWTqiQYD5Zifj6WcGa0aAHMuetyFG4lIfMAHmd3gaKpks7j9l26LwRPvI= +-----END CERTIFICATE----- diff --git a/cli/tests/node_compat/test/fixtures/keys/agent1-key.pem b/cli/tests/node_compat/test/fixtures/keys/agent1-key.pem new file mode 100644 index 00000000000000..b182ac5ed9be1e --- /dev/null +++ b/cli/tests/node_compat/test/fixtures/keys/agent1-key.pem @@ -0,0 +1,34 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +-----BEGIN RSA PRIVATE KEY----- +MIIEpAIBAAKCAQEA1FYyCvsg04Jwk9wsQoTtBN+6vVbh3a5Snii3kM1CVtsnM0nz +c1/9M3x6Y2Psylont/c9xwialsbYhtsMYjiPHN1qljr81ZnVgA5YehH5CJYPhO1Q +uiWigwPs2m5oT757rtyc6IATJ7FpevJQl87j8XXkAJhMDbao64e+A7TPlHdLpW// +yMY8aNat62CrvmmnsUq2prnnuqibWtq46weJfAf21Po9Zg3/V0EH0o6PY0Z6eIYk +xXQZdpPpWc6hNi/64buhDIwNiIQKv+8QNjGy6PXDm1VIp+pX6KOfiSkYE/RadsRI +AzorfthAP0uqFHzzXi0lVKplzklpV5cJW/TcawIDAQABAoIBAAvbtHfAhpjJVBgt +15rvaX04MWmZjIugzKRgib/gdq/7FTlcC+iJl85kSUF7tyGl30n62MxgwqFhAX6m +hQ6HMhbelrFFIhGbwbyhEHfgwROlrcAysKt0pprCgVvBhrnNXYLqdyjU3jz9P3LK +TY3s0/YMK2uNFdI+PTjKH+Z9Foqn9NZUnUonEDepGyuRO7fLeccWJPv2L4CR4a/5 +ku4VbDgVpvVSVRG3PSVzbmxobnpdpl52og+T7tPx1cLnIknPtVljXPWtZdfekh2E +eAp2KxCCHOKzzG3ItBKsVu0woeqEpy8JcoO6LbgmEoVnZpgmtQClbBgef8+i+oGE +BgW9nmECgYEA8gA63QQuZOUC56N1QXURexN2PogF4wChPaCTFbQSJXvSBkQmbqfL +qRSD8P0t7GOioPrQK6pDwFf4BJB01AvkDf8Z6DxxOJ7cqIC7LOwDupXocWX7Q0Qk +O6cwclBVsrDZK00v60uRRpl/a39GW2dx7IiQDkKQndLh3/0TbMIWHNcCgYEA4J6r +yinZbLpKw2+ezhi4B4GT1bMLoKboJwpZVyNZZCzYR6ZHv+lS7HR/02rcYMZGoYbf +n7OHwF4SrnUS7vPhG4g2ZsOhKQnMvFSQqpGmK1ZTuoKGAevyvtouhK/DgtLWzGvX +9fSahiq/UvfXs/z4M11q9Rv9ztPCmG1cwSEHlo0CgYEAogQNZJK8DMhVnYcNpXke +7uskqtCeQE/Xo06xqkIYNAgloBRYNpUYAGa/vsOBz1UVN/kzDUi8ezVp0oRz8tLT +J5u2WIi+tE2HJTiqF3UbOfvK1sCT64DfUSCpip7GAQ/tFNRkVH8PD9kMOYfILsGe +v+DdsO5Xq5HXrwHb02BNNZkCgYBsl8lt33WiPx5OBfS8pu6xkk+qjPkeHhM2bKZs +nkZlS9j0KsudWGwirN/vkkYg8zrKdK5AQ0dqFRDrDuasZ3N5IA1M+V88u+QjWK7o +B6pSYVXxYZDv9OZSpqC+vUrEQLJf+fNakXrzSk9dCT1bYv2Lt6ox/epix7XYg2bI +Z/OHMQKBgQC2FUGhlndGeugTJaoJ8nhT/0VfRUX/h6sCgSerk5qFr/hNCBV4T022 +x0NDR2yLG6MXyqApJpG6rh3QIDElQoQCNlI3/KJ6JfEfmqrLLN2OigTvA5sE4fGU +Dp/ha8OQAx95EwXuaG7LgARduvOIK3x8qi8KsZoUGJcg2ywurUbkWA== +-----END RSA PRIVATE KEY----- diff --git a/cli/tests/node_compat/test/fixtures/loop.js b/cli/tests/node_compat/test/fixtures/loop.js new file mode 100644 index 00000000000000..69bac522c117bd --- /dev/null +++ b/cli/tests/node_compat/test/fixtures/loop.js @@ -0,0 +1,17 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +var t = 1; +var k = 1; +console.log('A message', 5); +while (t > 0) { + if (t++ === 1000) { + t = 0; + console.log(`Outputted message #${k++}`); + } +} +process.exit(55); diff --git a/cli/tests/node_compat/test/fixtures/package.json b/cli/tests/node_compat/test/fixtures/package.json new file mode 100644 index 00000000000000..0967ef424bce67 --- /dev/null +++ b/cli/tests/node_compat/test/fixtures/package.json @@ -0,0 +1 @@ +{} diff --git a/cli/tests/node_compat/test/fixtures/print-chars.js b/cli/tests/node_compat/test/fixtures/print-chars.js new file mode 100644 index 00000000000000..2519c77fd2e6bf --- /dev/null +++ b/cli/tests/node_compat/test/fixtures/print-chars.js @@ -0,0 +1,35 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. + +// TODO(PolarETech): The process.argv[3] should be argv[2]. + +const assert = require('assert'); + +var n = parseInt(process.argv[3]); + +process.stdout.write('c'.repeat(n)); diff --git a/cli/tests/node_compat/test/fixtures/x.txt b/cli/tests/node_compat/test/fixtures/x.txt new file mode 100644 index 00000000000000..cd470e619003f5 --- /dev/null +++ b/cli/tests/node_compat/test/fixtures/x.txt @@ -0,0 +1 @@ +xyz diff --git a/cli/tests/node_compat/test/internet/package.json b/cli/tests/node_compat/test/internet/package.json new file mode 100644 index 00000000000000..0967ef424bce67 --- /dev/null +++ b/cli/tests/node_compat/test/internet/package.json @@ -0,0 +1 @@ +{} diff --git a/cli/tests/node_compat/test/internet/test-dgram-connect.js b/cli/tests/node_compat/test/internet/test-dgram-connect.js new file mode 100644 index 00000000000000..17b52472cdc598 --- /dev/null +++ b/cli/tests/node_compat/test/internet/test-dgram-connect.js @@ -0,0 +1,26 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +'use strict'; + +const common = require('../common'); +const { addresses } = require('../common/internet'); +const assert = require('assert'); +const dgram = require('dgram'); + +const client = dgram.createSocket('udp4'); +client.connect(common.PORT, addresses.INVALID_HOST, common.mustCall((err) => { + assert.ok(err.code === 'ENOTFOUND' || err.code === 'EAI_AGAIN'); + + client.once('error', common.mustCall((err) => { + assert.ok(err.code === 'ENOTFOUND' || err.code === 'EAI_AGAIN'); + client.once('connect', common.mustCall(() => client.close())); + client.connect(common.PORT); + })); + + client.connect(common.PORT, addresses.INVALID_HOST); +})); diff --git a/cli/tests/node_compat/test/internet/test-dns-any.js b/cli/tests/node_compat/test/internet/test-dns-any.js new file mode 100644 index 00000000000000..cd4450524568e6 --- /dev/null +++ b/cli/tests/node_compat/test/internet/test-dns-any.js @@ -0,0 +1,192 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 16.13.0 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +// TODO(cmorten): enable remaining tests once functionality is implemented. + +'use strict'; + +const common = require('../common'); + +const assert = require('assert'); +const dns = require('dns'); +const net = require('net'); + +let running = false; +const queue = []; + +const dnsPromises = dns.promises; +const isIPv4 = net.isIPv4; +const isIPv6 = net.isIPv6; + +dns.setServers([ '8.8.8.8', '8.8.4.4' ]); + +function checkWrap(req) { + assert.ok(typeof req === 'object'); +} + +const checkers = { + checkA(r) { + assert.ok(isIPv4(r.address)); + // assert.strictEqual(typeof r.ttl, 'number'); + assert.strictEqual(r.type, 'A'); + }, + checkAAAA(r) { + assert.ok(isIPv6(r.address)); + // assert.strictEqual(typeof r.ttl, 'number'); + assert.strictEqual(r.type, 'AAAA'); + }, + checkCNAME(r) { + assert.ok(r.value); + assert.strictEqual(typeof r.value, 'string'); + assert.strictEqual(r.type, 'CNAME'); + }, + checkMX(r) { + assert.strictEqual(typeof r.exchange, 'string'); + assert.strictEqual(typeof r.priority, 'number'); + assert.strictEqual(r.type, 'MX'); + }, + checkNAPTR(r) { + assert.strictEqual(typeof r.flags, 'string'); + assert.strictEqual(typeof r.service, 'string'); + assert.strictEqual(typeof r.regexp, 'string'); + assert.strictEqual(typeof r.replacement, 'string'); + assert.strictEqual(typeof r.order, 'number'); + assert.strictEqual(typeof r.preference, 'number'); + assert.strictEqual(r.type, 'NAPTR'); + }, + checkNS(r) { + assert.strictEqual(typeof r.value, 'string'); + assert.strictEqual(r.type, 'NS'); + }, + checkPTR(r) { + assert.strictEqual(typeof r.value, 'string'); + assert.strictEqual(r.type, 'PTR'); + }, + checkTXT(r) { + assert.ok(Array.isArray(r.entries)); + assert.ok(r.entries.length > 0); + assert.strictEqual(r.type, 'TXT'); + }, + checkSOA(r) { + assert.strictEqual(typeof r.nsname, 'string'); + assert.strictEqual(typeof r.hostmaster, 'string'); + assert.strictEqual(typeof r.serial, 'number'); + assert.strictEqual(typeof r.refresh, 'number'); + assert.strictEqual(typeof r.retry, 'number'); + assert.strictEqual(typeof r.expire, 'number'); + assert.strictEqual(typeof r.minttl, 'number'); + assert.strictEqual(r.type, 'SOA'); + }, + checkSRV(r) { + assert.strictEqual(typeof r.name, 'string'); + assert.strictEqual(typeof r.port, 'number'); + assert.strictEqual(typeof r.priority, 'number'); + assert.strictEqual(typeof r.weight, 'number'); + assert.strictEqual(r.type, 'SRV'); + } +}; + +function TEST(f) { + function next() { + const f = queue.shift(); + if (f) { + running = true; + f(done); + } + } + + function done() { + running = false; + process.nextTick(next); + } + + queue.push(f); + + if (!running) { + next(); + } +} + +function processResult(res) { + assert.ok(Array.isArray(res)); + assert.ok(res.length > 0); + + const types = {}; + res.forEach((obj) => { + types[obj.type] = true; + checkers[`check${obj.type}`](obj); + }); + + return types; +} + +TEST(async function test_sip2sip_for_naptr(done) { + function validateResult(res) { + const types = processResult(res); + assert.ok( + types.A && types.NS && types.NAPTR && types.SOA, + `Missing record type, found ${Object.keys(types)}` + ); + } + + validateResult(await dnsPromises.resolve('sip2sip.info', 'ANY')); + + const req = dns.resolve( + 'sip2sip.info', + 'ANY', + common.mustSucceed((ret) => { + validateResult(ret); + done(); + })); + + checkWrap(req); +}); + +TEST(async function test_google_for_cname_and_srv(done) { + function validateResult(res) { + const types = processResult(res); + assert.ok(types.SRV); + } + + // TODO(kt3k): Temporarily use _caldav._tcp.google.com instead of + // _jabber._tcp.google.com, which currently doesn't respond + // validateResult(await dnsPromises.resolve('_jabber._tcp.google.com', 'ANY')); + validateResult(await dnsPromises.resolve('_caldav._tcp.google.com', 'ANY')); + + + // TODO(kt3k): Temporarily use _caldav._tcp.google.com instead of + // _jabber._tcp.google.com, which currently doesn't respond + const req = dns.resolve( + // '_jabber._tcp.google.com', + '_caldav._tcp.google.com', + 'ANY', + common.mustSucceed((ret) => { + validateResult(ret); + done(); + })); + + checkWrap(req); +}); + +TEST(async function test_ptr(done) { + function validateResult(res) { + const types = processResult(res); + assert.ok(types.PTR); + } + + validateResult(await dnsPromises.resolve('8.8.8.8.in-addr.arpa', 'ANY')); + + const req = dns.resolve( + '8.8.8.8.in-addr.arpa', + 'ANY', + common.mustSucceed((ret) => { + validateResult(ret); + done(); + })); + + checkWrap(req); +}); diff --git a/cli/tests/node_compat/test/internet/test-dns-idna2008.js b/cli/tests/node_compat/test/internet/test-dns-idna2008.js new file mode 100644 index 00000000000000..d082f75473d612 --- /dev/null +++ b/cli/tests/node_compat/test/internet/test-dns-idna2008.js @@ -0,0 +1,76 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +'use strict'; + +// Verify that non-ASCII hostnames are handled correctly as IDNA 2008. +// +// * Tests will fail with NXDOMAIN when UTF-8 leaks through to a getaddrinfo() +// that doesn't support IDNA at all. +// +// * "straße.de" will resolve to the wrong address when the resolver supports +// only IDNA 2003 (e.g., glibc until 2.28) because it encodes it wrong. + +const { mustCall } = require('../common'); +const assert = require('assert'); +const dns = require('dns'); +const { addresses } = require('../common/internet'); + +const fixture = { + hostname: 'straße.de', + expectedAddress: '81.169.145.78', + dnsServer: addresses.DNS4_SERVER, + family: 4, +}; + +// Explicitly use well-behaved DNS servers that are known to be able to resolve +// the query (which is a.k.a xn--strae-oqa.de). +dns.setServers([fixture.dnsServer]); + +dns.lookup( + fixture.hostname, + { family: fixture.family }, + mustCall((err, address) => { + if (err && err.errno === 'ESERVFAIL') { + assert.ok(err.message.includes('queryA ESERVFAIL straße.de')); + return; + } + assert.ifError(err); + assert.strictEqual(address, fixture.expectedAddress); + }) +); + +dns.promises.lookup(fixture.hostname, { family: fixture.family }) + .then(({ address }) => { + assert.strictEqual(address, fixture.expectedAddress); + }, (err) => { + if (err && err.errno === 'ESERVFAIL') { + assert.ok(err.message.includes('queryA ESERVFAIL straße.de')); + } else { + throw err; + } + }).finally(mustCall()); + +dns.resolve4(fixture.hostname, mustCall((err, addresses) => { + if (err && err.errno === 'ESERVFAIL') { + assert.ok(err.message.includes('queryA ESERVFAIL straße.de')); + return; + } + assert.ifError(err); + assert.deepStrictEqual(addresses, [fixture.expectedAddress]); +})); + +const p = new dns.promises.Resolver().resolve4(fixture.hostname); +p.then((addresses) => { + assert.deepStrictEqual(addresses, [fixture.expectedAddress]); +}, (err) => { + if (err && err.errno === 'ESERVFAIL') { + assert.ok(err.message.includes('queryA ESERVFAIL straße.de')); + } else { + throw err; + } +}).finally(mustCall()); diff --git a/cli/tests/node_compat/test/internet/test-dns-ipv4.js b/cli/tests/node_compat/test/internet/test-dns-ipv4.js new file mode 100644 index 00000000000000..43b60950a0b04d --- /dev/null +++ b/cli/tests/node_compat/test/internet/test-dns-ipv4.js @@ -0,0 +1,257 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 16.13.0 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +'use strict'; + +// TODO: enable remaining tests once functionality is implemented. + +const common = require('../common'); +const { addresses } = require('../common/internet'); +const assert = require('assert'); +const dns = require('dns'); +const net = require('net'); +// const util = require('util'); +const isIPv4 = net.isIPv4; + +const dnsPromises = dns.promises; +let running = false; +const queue = []; + +function TEST(f) { + function next() { + const f = queue.shift(); + if (f) { + running = true; + console.log(f.name); + f(done); + } + } + + function done() { + running = false; + process.nextTick(next); + } + + queue.push(f); + + if (!running) { + next(); + } +} + +function checkWrap(req) { + assert.ok(typeof req === 'object'); +} + +TEST(async function test_resolve4(done) { + function validateResult(res) { + assert.ok(res.length > 0); + + for (let i = 0; i < res.length; i++) { + assert.ok(isIPv4(res[i])); + } + } + + validateResult(await dnsPromises.resolve4(addresses.INET4_HOST)); + + const req = dns.resolve4( + addresses.INET4_HOST, + common.mustSucceed((ips) => { + validateResult(ips); + done(); + })); + + checkWrap(req); +}); + +// TEST(async function test_reverse_ipv4(done) { +// function validateResult(res) { +// assert.ok(res.length > 0); + +// for (let i = 0; i < res.length; i++) { +// assert.ok(res[i]); +// assert.ok(typeof res[i] === 'string'); +// } +// } + +// validateResult(await dnsPromises.reverse(addresses.INET4_IP)); + +// const req = dns.reverse( +// addresses.INET4_IP, +// common.mustSucceed((domains) => { +// validateResult(domains); +// done(); +// })); + +// checkWrap(req); +// }); + +TEST(async function test_lookup_ipv4_explicit(done) { + function validateResult(res) { + assert.ok(net.isIPv4(res.address)); + assert.strictEqual(res.family, 4); + } + + validateResult(await dnsPromises.lookup(addresses.INET4_HOST, 4)); + + const req = dns.lookup( + addresses.INET4_HOST, 4, + common.mustSucceed((ip, family) => { + validateResult({ address: ip, family }); + done(); + })); + + checkWrap(req); +}); + +TEST(async function test_lookup_ipv4_implicit(done) { + function validateResult(res) { + assert.ok(net.isIPv4(res.address)); + assert.strictEqual(res.family, 4); + } + + validateResult(await dnsPromises.lookup(addresses.INET4_HOST)); + + const req = dns.lookup( + addresses.INET4_HOST, + common.mustSucceed((ip, family) => { + validateResult({ address: ip, family }); + done(); + })); + + checkWrap(req); +}); + +TEST(async function test_lookup_ipv4_explicit_object(done) { + function validateResult(res) { + assert.ok(net.isIPv4(res.address)); + assert.strictEqual(res.family, 4); + } + + validateResult(await dnsPromises.lookup(addresses.INET4_HOST, { family: 4 })); + + const req = dns.lookup(addresses.INET4_HOST, { + family: 4 + }, common.mustSucceed((ip, family) => { + validateResult({ address: ip, family }); + done(); + })); + + checkWrap(req); +}); + +TEST(async function test_lookup_ipv4_hint_addrconfig(done) { + function validateResult(res) { + assert.ok(net.isIPv4(res.address)); + assert.strictEqual(res.family, 4); + } + + validateResult(await dnsPromises.lookup(addresses.INET4_HOST, { + hints: dns.ADDRCONFIG + })); + + const req = dns.lookup(addresses.INET4_HOST, { + hints: dns.ADDRCONFIG + }, common.mustSucceed((ip, family) => { + validateResult({ address: ip, family }); + done(); + })); + + checkWrap(req); +}); + +TEST(async function test_lookup_ip_ipv4(done) { + function validateResult(res) { + assert.strictEqual(res.address, '127.0.0.1'); + assert.strictEqual(res.family, 4); + } + + validateResult(await dnsPromises.lookup('127.0.0.1')); + + const req = dns.lookup('127.0.0.1', + common.mustSucceed((ip, family) => { + validateResult({ address: ip, family }); + done(); + })); + + checkWrap(req); +}); + +TEST(async function test_lookup_localhost_ipv4(done) { + function validateResult(res) { + assert.strictEqual(res.address, '127.0.0.1'); + assert.strictEqual(res.family, 4); + } + + validateResult(await dnsPromises.lookup('localhost', 4)); + + const req = dns.lookup('localhost', 4, + common.mustSucceed((ip, family) => { + validateResult({ address: ip, family }); + done(); + })); + + checkWrap(req); +}); + +TEST(async function test_lookup_all_ipv4(done) { + function validateResult(res) { + assert.ok(Array.isArray(res)); + assert.ok(res.length > 0); + + res.forEach((ip) => { + assert.ok(isIPv4(ip.address)); + assert.strictEqual(ip.family, 4); + }); + } + + validateResult(await dnsPromises.lookup(addresses.INET4_HOST, { + all: true, + family: 4 + })); + + const req = dns.lookup( + addresses.INET4_HOST, + { all: true, family: 4 }, + common.mustSucceed((ips) => { + validateResult(ips); + done(); + }) + ); + + checkWrap(req); +}); + +// TEST(async function test_lookupservice_ip_ipv4(done) { +// function validateResult(res) { +// assert.strictEqual(typeof res.hostname, 'string'); +// assert(res.hostname); +// assert(['http', 'www', '80'].includes(res.service)); +// } + +// validateResult(await dnsPromises.lookupService('127.0.0.1', 80)); + +// const req = dns.lookupService( +// '127.0.0.1', 80, +// common.mustSucceed((hostname, service) => { +// validateResult({ hostname, service }); +// done(); +// }) +// ); + +// checkWrap(req); +// }); + +// TEST(function test_lookupservice_ip_ipv4_promise(done) { +// util.promisify(dns.lookupService)('127.0.0.1', 80) +// .then(common.mustCall(({ hostname, service }) => { +// assert.strictEqual(typeof hostname, 'string'); +// assert(hostname.length > 0); +// assert(['http', 'www', '80'].includes(service)); +// done(); +// })); +// }); diff --git a/cli/tests/node_compat/test/internet/test-dns-ipv6.js b/cli/tests/node_compat/test/internet/test-dns-ipv6.js new file mode 100644 index 00000000000000..4b94d60414ecca --- /dev/null +++ b/cli/tests/node_compat/test/internet/test-dns-ipv6.js @@ -0,0 +1,250 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 16.13.0 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +'use strict'; + +// TODO: enable remaining tests once functionality is implemented. + +const common = require('../common'); +const { addresses } = require('../common/internet'); +if (!common.hasIPv6) + common.skip('this test, no IPv6 support'); + +const assert = require('assert'); +const dns = require('dns'); +const net = require('net'); +const dnsPromises = dns.promises; +const isIPv6 = net.isIPv6; + +let running = false; +const queue = []; + +function TEST(f) { + function next() { + const f = queue.shift(); + if (f) { + running = true; + console.log(f.name); + f(done); + } + } + + function done() { + running = false; + process.nextTick(next); + } + + queue.push(f); + + if (!running) { + next(); + } +} + +function checkWrap(req) { + assert.ok(typeof req === 'object'); +} + +TEST(async function test_resolve6(done) { + function validateResult(res) { + assert.ok(res.length > 0); + + for (let i = 0; i < res.length; i++) { + assert.ok(isIPv6(res[i])); + } + } + + validateResult(await dnsPromises.resolve6(addresses.INET6_HOST)); + + const req = dns.resolve6( + addresses.INET6_HOST, + common.mustSucceed((ips) => { + validateResult(ips); + done(); + })); + + checkWrap(req); +}); + +// TEST(async function test_reverse_ipv6(done) { +// function validateResult(res) { +// assert.ok(res.length > 0); + +// for (let i = 0; i < res.length; i++) { +// assert.ok(typeof res[i] === 'string'); +// } +// } + +// validateResult(await dnsPromises.reverse(addresses.INET6_IP)); + +// const req = dns.reverse( +// addresses.INET6_IP, +// common.mustSucceed((domains) => { +// validateResult(domains); +// done(); +// })); + +// checkWrap(req); +// }); + +TEST(async function test_lookup_ipv6_explicit(done) { + function validateResult(res) { + assert.ok(isIPv6(res.address)); + assert.strictEqual(res.family, 6); + } + + validateResult(await dnsPromises.lookup(addresses.INET6_HOST, 6)); + + const req = dns.lookup( + addresses.INET6_HOST, + 6, + common.mustSucceed((ip, family) => { + validateResult({ address: ip, family }); + done(); + })); + + checkWrap(req); +}); + +// This ends up just being too problematic to test +// TEST(function test_lookup_ipv6_implicit(done) { +// var req = dns.lookup(addresses.INET6_HOST, function(err, ip, family) { +// assert.ifError(err); +// assert.ok(net.isIPv6(ip)); +// assert.strictEqual(family, 6); + +// done(); +// }); + +// checkWrap(req); +// }); + +TEST(async function test_lookup_ipv6_explicit_object(done) { + function validateResult(res) { + assert.ok(isIPv6(res.address)); + assert.strictEqual(res.family, 6); + } + + validateResult(await dnsPromises.lookup(addresses.INET6_HOST, { family: 6 })); + + const req = dns.lookup(addresses.INET6_HOST, { + family: 6 + }, common.mustSucceed((ip, family) => { + validateResult({ address: ip, family }); + done(); + })); + + checkWrap(req); +}); + +TEST(function test_lookup_ipv6_hint(done) { + const req = dns.lookup(addresses.INET6_HOST, { + family: 6, + hints: dns.V4MAPPED + }, common.mustCall((err, ip, family) => { + if (err) { + // FreeBSD does not support V4MAPPED + if (common.isFreeBSD) { + assert(err instanceof Error); + assert.strictEqual(err.code, 'EAI_BADFLAGS'); + assert.strictEqual(err.hostname, addresses.INET_HOST); + assert.match(err.message, /getaddrinfo EAI_BADFLAGS/); + done(); + return; + } + + assert.ifError(err); + } + + assert.ok(isIPv6(ip)); + assert.strictEqual(family, 6); + + done(); + })); + + checkWrap(req); +}); + +TEST(async function test_lookup_ip_ipv6(done) { + function validateResult(res) { + assert.ok(isIPv6(res.address)); + assert.strictEqual(res.family, 6); + } + + validateResult(await dnsPromises.lookup('::1')); + + const req = dns.lookup( + '::1', + common.mustSucceed((ip, family) => { + validateResult({ address: ip, family }); + done(); + })); + + checkWrap(req); +}); + +TEST(async function test_lookup_all_ipv6(done) { + function validateResult(res) { + assert.ok(Array.isArray(res)); + assert.ok(res.length > 0); + + res.forEach((ip) => { + assert.ok(isIPv6(ip.address), + `Invalid IPv6: ${ip.address.toString()}`); + assert.strictEqual(ip.family, 6); + }); + } + + validateResult(await dnsPromises.lookup(addresses.INET6_HOST, { + all: true, + family: 6 + })); + + const req = dns.lookup( + addresses.INET6_HOST, + { all: true, family: 6 }, + common.mustSucceed((ips) => { + validateResult(ips); + done(); + }) + ); + + checkWrap(req); +}); + +// TEST(function test_lookupservice_ip_ipv6(done) { +// const req = dns.lookupService( +// '::1', 80, +// common.mustCall((err, host, service) => { +// if (err) { +// // Not skipping the test, rather checking an alternative result, +// // i.e. that ::1 may not be configured (e.g. in /etc/hosts) +// assert.strictEqual(err.code, 'ENOTFOUND'); +// return done(); +// } +// assert.strictEqual(typeof host, 'string'); +// assert(host); +// assert(['http', 'www', '80'].includes(service)); +// done(); +// }) +// ); + +// checkWrap(req); +// }); + +// Disabled because it appears to be not working on Linux. +// TEST(function test_lookup_localhost_ipv6(done) { +// var req = dns.lookup('localhost', 6, function(err, ip, family) { +// assert.ifError(err); +// assert.ok(net.isIPv6(ip)); +// assert.strictEqual(family, 6); +// +// done(); +// }); +// +// checkWrap(req); +// }); diff --git a/cli/tests/node_compat/test/internet/test-dns-lookup.js b/cli/tests/node_compat/test/internet/test-dns-lookup.js new file mode 100644 index 00000000000000..4ed3716b409f98 --- /dev/null +++ b/cli/tests/node_compat/test/internet/test-dns-lookup.js @@ -0,0 +1,61 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +'use strict'; + +require('../common'); +const common = require('../common'); +const dns = require('dns'); +const dnsPromises = dns.promises; +const { addresses } = require('../common/internet'); +const assert = require('assert'); + +assert.rejects( + dnsPromises.lookup(addresses.NOT_FOUND, { + hints: 0, + family: 0, + all: false + }), + { + code: 'ENOTFOUND', + message: `getaddrinfo ENOTFOUND ${addresses.NOT_FOUND}` + } +); + +assert.rejects( + dnsPromises.lookup(addresses.NOT_FOUND, { + hints: 0, + family: 0, + all: true + }), + { + code: 'ENOTFOUND', + message: `getaddrinfo ENOTFOUND ${addresses.NOT_FOUND}` + } +); + +dns.lookup(addresses.NOT_FOUND, { + hints: 0, + family: 0, + all: true +}, common.mustCall((error) => { + assert.strictEqual(error.code, 'ENOTFOUND'); + assert.strictEqual( + error.message, + `getaddrinfo ENOTFOUND ${addresses.NOT_FOUND}` + ); + assert.strictEqual(error.syscall, 'getaddrinfo'); + assert.strictEqual(error.hostname, addresses.NOT_FOUND); +})); + +assert.throws( + () => dnsPromises.lookup(addresses.NOT_FOUND, { + family: 'ipv4', + all: 'all' + }), + { code: 'ERR_INVALID_ARG_VALUE' } +); diff --git a/cli/tests/node_compat/test/internet/test-dns-promises-resolve.js b/cli/tests/node_compat/test/internet/test-dns-promises-resolve.js new file mode 100644 index 00000000000000..d700ad48ca119a --- /dev/null +++ b/cli/tests/node_compat/test/internet/test-dns-promises-resolve.js @@ -0,0 +1,49 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +'use strict'; +const common = require('../common'); +const assert = require('assert'); + +const dnsPromises = require('dns').promises; + +// Error when rrtype is invalid. +{ + const rrtype = 'DUMMY'; + assert.throws( + () => dnsPromises.resolve('example.org', rrtype), + { + code: 'ERR_INVALID_ARG_VALUE', + name: 'TypeError', + message: `The argument 'rrtype' is invalid. Received '${rrtype}'` + } + ); +} + +// Error when rrtype is a number. +{ + const rrtype = 0; + assert.throws( + () => dnsPromises.resolve('example.org', rrtype), + { + code: 'ERR_INVALID_ARG_TYPE', + name: 'TypeError', + message: 'The "rrtype" argument must be of type string. ' + + `Received type ${typeof rrtype} (${rrtype})` + } + ); +} + +// Setting rrtype to undefined should work like resolve4. +{ + (async function() { + const rrtype = undefined; + const result = await dnsPromises.resolve('example.org', rrtype); + assert.ok(result !== undefined); + assert.ok(result.length > 0); + })().then(common.mustCall()); +} diff --git a/cli/tests/node_compat/test/internet/test-dns-regress-6244.js b/cli/tests/node_compat/test/internet/test-dns-regress-6244.js new file mode 100644 index 00000000000000..db78a6f92a091d --- /dev/null +++ b/cli/tests/node_compat/test/internet/test-dns-regress-6244.js @@ -0,0 +1,35 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. + +'use strict'; +const common = require('../common'); +const dns = require('dns'); + +// Should not segfault. +// Ref: https://github.com/nodejs/node-v0.x-archive/issues/6244 +dns.resolve4('127.0.0.1', common.mustCall()); diff --git a/cli/tests/node_compat/test/internet/test-dns-setserver-in-callback-of-resolve4.js b/cli/tests/node_compat/test/internet/test-dns-setserver-in-callback-of-resolve4.js new file mode 100644 index 00000000000000..27356b9ca54c0e --- /dev/null +++ b/cli/tests/node_compat/test/internet/test-dns-setserver-in-callback-of-resolve4.js @@ -0,0 +1,25 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +'use strict'; + +// We don't care about `err` in the callback function of `dns.resolve4`. We just +// want to test whether `dns.setServers` that is run after `resolve4` will cause +// a crash or not. If it doesn't crash, the test succeeded. + +const common = require('../common'); +const { addresses } = require('../common/internet'); +const dns = require('dns'); + +dns.resolve4( + addresses.INET4_HOST, + common.mustCall(function(/* err, nameServers */) { + dns.setServers([ addresses.DNS4_SERVER ]); + })); + +// Test https://github.com/nodejs/node/issues/14734 +dns.resolve4(addresses.INET4_HOST, common.mustCall()); diff --git a/cli/tests/node_compat/test/internet/test-dns.js b/cli/tests/node_compat/test/internet/test-dns.js new file mode 100644 index 00000000000000..ae5e46ca95e4b6 --- /dev/null +++ b/cli/tests/node_compat/test/internet/test-dns.js @@ -0,0 +1,761 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 16.13.0 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +// Flags: --expose-internals +// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. + +'use strict'; + +// TODO(cmorten): enable remaining tests once functionality is implemented. + +const common = require('../common'); +const { addresses } = require('../common/internet'); +const { internalBinding } = require('internal/test/binding'); +// const { getSystemErrorName } = require('util'); +const assert = require('assert'); +const dns = require('dns'); +const net = require('net'); +const isIPv4 = net.isIPv4; +const isIPv6 = net.isIPv6; +const util = require('util'); +const dnsPromises = dns.promises; + +let expected = 0; +let completed = 0; +let running = false; +const queue = []; + + +function TEST(f) { + function next() { + const f = queue.shift(); + if (f) { + running = true; + console.log(f.name); + f(done); + } + } + + function done() { + running = false; + completed++; + process.nextTick(next); + } + + expected++; + queue.push(f); + + if (!running) { + next(); + } +} + + +function checkWrap(req) { + assert.strictEqual(typeof req, 'object'); +} + + +// TEST(function test_reverse_bogus(done) { +// dnsPromises.reverse('bogus ip') +// .then(common.mustNotCall()) +// .catch(common.mustCall((err) => { +// assert.strictEqual(err.code, 'EINVAL'); +// assert.strictEqual(getSystemErrorName(err.errno), 'EINVAL'); +// })); + +// assert.throws(() => { +// dns.reverse('bogus ip', common.mustNotCall()); +// }, /^Error: getHostByAddr EINVAL bogus ip$/); +// done(); +// }); + +// TEST(async function test_resolve4_ttl(done) { +// function validateResult(result) { +// assert.ok(result.length > 0); + +// for (const item of result) { +// assert.strictEqual(typeof item, 'object'); +// assert.strictEqual(typeof item.ttl, 'number'); +// assert.strictEqual(typeof item.address, 'string'); +// assert.ok(item.ttl >= 0); +// assert.ok(isIPv4(item.address)); +// } +// } + +// validateResult(await dnsPromises.resolve4(addresses.INET4_HOST, { +// ttl: true +// })); + +// const req = dns.resolve4(addresses.INET4_HOST, { +// ttl: true +// }, function(err, result) { +// assert.ifError(err); +// validateResult(result); +// done(); +// }); + +// checkWrap(req); +// }); + +// TEST(async function test_resolve6_ttl(done) { +// function validateResult(result) { +// assert.ok(result.length > 0); + +// for (const item of result) { +// assert.strictEqual(typeof item, 'object'); +// assert.strictEqual(typeof item.ttl, 'number'); +// assert.strictEqual(typeof item.address, 'string'); +// assert.ok(item.ttl >= 0); +// assert.ok(isIPv6(item.address)); +// } +// } + +// validateResult(await dnsPromises.resolve6(addresses.INET6_HOST, { +// ttl: true +// })); + +// const req = dns.resolve6(addresses.INET6_HOST, { +// ttl: true +// }, function(err, result) { +// assert.ifError(err); +// validateResult(result); +// done(); +// }); + +// checkWrap(req); +// }); + +TEST(async function test_resolveMx(done) { + function validateResult(result) { + assert.ok(result.length > 0); + + for (const item of result) { + assert.strictEqual(typeof item, 'object'); + assert.ok(item.exchange); + assert.strictEqual(typeof item.exchange, 'string'); + assert.strictEqual(typeof item.priority, 'number'); + } + } + + validateResult(await dnsPromises.resolveMx(addresses.MX_HOST)); + + const req = dns.resolveMx(addresses.MX_HOST, function(err, result) { + assert.ifError(err); + validateResult(result); + done(); + }); + + checkWrap(req); +}); + +TEST(function test_resolveMx_failure(done) { + dnsPromises.resolveMx(addresses.NOT_FOUND) + .then(common.mustNotCall()) + .catch(common.mustCall((err) => { + assert.strictEqual(err.code, 'ENOTFOUND'); + })); + + const req = dns.resolveMx(addresses.NOT_FOUND, function(err, result) { + assert.ok(err instanceof Error); + assert.strictEqual(err.code, 'ENOTFOUND'); + + assert.strictEqual(result, undefined); + + done(); + }); + + checkWrap(req); +}); + +TEST(async function test_resolveNs(done) { + function validateResult(result) { + assert.ok(result.length > 0); + + for (const item of result) { + assert.ok(item); + assert.strictEqual(typeof item, 'string'); + } + } + + validateResult(await dnsPromises.resolveNs(addresses.NS_HOST)); + + const req = dns.resolveNs(addresses.NS_HOST, function(err, names) { + assert.ifError(err); + validateResult(names); + done(); + }); + + checkWrap(req); +}); + +TEST(function test_resolveNs_failure(done) { + dnsPromises.resolveNs(addresses.NOT_FOUND) + .then(common.mustNotCall()) + .catch(common.mustCall((err) => { + assert.strictEqual(err.code, 'ENOTFOUND'); + })); + + const req = dns.resolveNs(addresses.NOT_FOUND, function(err, result) { + assert.ok(err instanceof Error); + assert.strictEqual(err.code, 'ENOTFOUND'); + + assert.strictEqual(result, undefined); + + done(); + }); + + checkWrap(req); +}); + +TEST(async function test_resolveSrv(done) { + function validateResult(result) { + assert.ok(result.length > 0); + + for (const item of result) { + assert.strictEqual(typeof item, 'object'); + assert.ok(item.name); + assert.strictEqual(typeof item.name, 'string'); + assert.strictEqual(typeof item.port, 'number'); + assert.strictEqual(typeof item.priority, 'number'); + assert.strictEqual(typeof item.weight, 'number'); + } + } + + validateResult(await dnsPromises.resolveSrv(addresses.SRV_HOST)); + + const req = dns.resolveSrv(addresses.SRV_HOST, function(err, result) { + assert.ifError(err); + validateResult(result); + done(); + }); + + checkWrap(req); +}); + +TEST(function test_resolveSrv_failure(done) { + dnsPromises.resolveSrv(addresses.NOT_FOUND) + .then(common.mustNotCall()) + .catch(common.mustCall((err) => { + assert.strictEqual(err.code, 'ENOTFOUND'); + })); + + const req = dns.resolveSrv(addresses.NOT_FOUND, function(err, result) { + assert.ok(err instanceof Error); + assert.strictEqual(err.code, 'ENOTFOUND'); + + assert.strictEqual(result, undefined); + + done(); + }); + + checkWrap(req); +}); + +TEST(async function test_resolvePtr(done) { + function validateResult(result) { + assert.ok(result.length > 0); + + for (const item of result) { + assert.ok(item); + assert.strictEqual(typeof item, 'string'); + } + } + + validateResult(await dnsPromises.resolvePtr(addresses.PTR_HOST)); + + const req = dns.resolvePtr(addresses.PTR_HOST, function(err, result) { + assert.ifError(err); + validateResult(result); + done(); + }); + + checkWrap(req); +}); + +TEST(function test_resolvePtr_failure(done) { + dnsPromises.resolvePtr(addresses.NOT_FOUND) + .then(common.mustNotCall()) + .catch(common.mustCall((err) => { + assert.strictEqual(err.code, 'ENOTFOUND'); + })); + + const req = dns.resolvePtr(addresses.NOT_FOUND, function(err, result) { + assert.ok(err instanceof Error); + assert.strictEqual(err.code, 'ENOTFOUND'); + + assert.strictEqual(result, undefined); + + done(); + }); + + checkWrap(req); +}); + +TEST(async function test_resolveNaptr(done) { + function validateResult(result) { + assert.ok(result.length > 0); + + for (const item of result) { + assert.strictEqual(typeof item, 'object'); + assert.strictEqual(typeof item.flags, 'string'); + assert.strictEqual(typeof item.service, 'string'); + assert.strictEqual(typeof item.regexp, 'string'); + assert.strictEqual(typeof item.replacement, 'string'); + assert.strictEqual(typeof item.order, 'number'); + assert.strictEqual(typeof item.preference, 'number'); + } + } + + validateResult(await dnsPromises.resolveNaptr(addresses.NAPTR_HOST)); + + const req = dns.resolveNaptr(addresses.NAPTR_HOST, function(err, result) { + assert.ifError(err); + validateResult(result); + done(); + }); + + checkWrap(req); +}); + +TEST(function test_resolveNaptr_failure(done) { + dnsPromises.resolveNaptr(addresses.NOT_FOUND) + .then(common.mustNotCall()) + .catch(common.mustCall((err) => { + assert.strictEqual(err.code, 'ENOTFOUND'); + })); + + const req = dns.resolveNaptr(addresses.NOT_FOUND, function(err, result) { + assert.ok(err instanceof Error); + assert.strictEqual(err.code, 'ENOTFOUND'); + + assert.strictEqual(result, undefined); + + done(); + }); + + checkWrap(req); +}); + +TEST(async function test_resolveSoa(done) { + function validateResult(result) { + assert.strictEqual(typeof result, 'object'); + assert.strictEqual(typeof result.nsname, 'string'); + assert.ok(result.nsname.length > 0); + assert.strictEqual(typeof result.hostmaster, 'string'); + assert.ok(result.hostmaster.length > 0); + assert.strictEqual(typeof result.serial, 'number'); + assert.ok((result.serial > 0) && (result.serial < 4294967295)); + assert.strictEqual(typeof result.refresh, 'number'); + assert.ok((result.refresh > 0) && (result.refresh < 2147483647)); + assert.strictEqual(typeof result.retry, 'number'); + assert.ok((result.retry > 0) && (result.retry < 2147483647)); + assert.strictEqual(typeof result.expire, 'number'); + assert.ok((result.expire > 0) && (result.expire < 2147483647)); + assert.strictEqual(typeof result.minttl, 'number'); + assert.ok((result.minttl >= 0) && (result.minttl < 2147483647)); + } + + validateResult(await dnsPromises.resolveSoa(addresses.SOA_HOST)); + + const req = dns.resolveSoa(addresses.SOA_HOST, function(err, result) { + assert.ifError(err); + validateResult(result); + done(); + }); + + checkWrap(req); +}); + +TEST(function test_resolveSoa_failure(done) { + dnsPromises.resolveSoa(addresses.NOT_FOUND) + .then(common.mustNotCall()) + .catch(common.mustCall((err) => { + assert.strictEqual(err.code, 'ENOTFOUND'); + })); + + const req = dns.resolveSoa(addresses.NOT_FOUND, function(err, result) { + assert.ok(err instanceof Error); + assert.strictEqual(err.code, 'ENOTFOUND'); + + assert.strictEqual(result, undefined); + + done(); + }); + + checkWrap(req); +}); + +TEST(async function test_resolveCaa(done) { + function validateResult(result) { + assert.ok(Array.isArray(result), + `expected array, got ${util.inspect(result)}`); + assert.strictEqual(result.length, 1); + assert.strictEqual(typeof result[0].critical, 'number'); + assert.strictEqual(result[0].critical, 0); + assert.strictEqual(result[0].issue, 'pki.goog'); + } + + validateResult(await dnsPromises.resolveCaa(addresses.CAA_HOST)); + + const req = dns.resolveCaa(addresses.CAA_HOST, function(err, records) { + assert.ifError(err); + validateResult(records); + done(); + }); + + checkWrap(req); +}); + +TEST(function test_resolveCaa_failure(done) { + dnsPromises.resolveTxt(addresses.NOT_FOUND) + .then(common.mustNotCall()) + .catch(common.mustCall((err) => { + assert.strictEqual(err.code, 'ENOTFOUND'); + })); + + const req = dns.resolveCaa(addresses.NOT_FOUND, function(err, result) { + assert.ok(err instanceof Error); + assert.strictEqual(err.code, 'ENOTFOUND'); + + assert.strictEqual(result, undefined); + + done(); + }); + + checkWrap(req); +}); + +TEST(async function test_resolveCname(done) { + function validateResult(result) { + assert.ok(result.length > 0); + + for (const item of result) { + assert.ok(item); + assert.strictEqual(typeof item, 'string'); + } + } + + validateResult(await dnsPromises.resolveCname(addresses.CNAME_HOST)); + + const req = dns.resolveCname(addresses.CNAME_HOST, function(err, names) { + assert.ifError(err); + validateResult(names); + done(); + }); + + checkWrap(req); +}); + +TEST(function test_resolveCname_failure(done) { + dnsPromises.resolveCname(addresses.NOT_FOUND) + .then(common.mustNotCall()) + .catch(common.mustCall((err) => { + assert.strictEqual(err.code, 'ENOTFOUND'); + })); + + const req = dns.resolveCname(addresses.NOT_FOUND, function(err, result) { + assert.ok(err instanceof Error); + assert.strictEqual(err.code, 'ENOTFOUND'); + + assert.strictEqual(result, undefined); + + done(); + }); + + checkWrap(req); +}); + + +TEST(async function test_resolveTxt(done) { + function validateResult(result) { + assert.ok(Array.isArray(result[0])); + assert.strictEqual(result.length, 1); + assert(result[0][0].startsWith('v=spf1')); + } + + validateResult(await dnsPromises.resolveTxt(addresses.TXT_HOST)); + + const req = dns.resolveTxt(addresses.TXT_HOST, function(err, records) { + assert.ifError(err); + validateResult(records); + done(); + }); + + checkWrap(req); +}); + +TEST(function test_resolveTxt_failure(done) { + dnsPromises.resolveTxt(addresses.NOT_FOUND) + .then(common.mustNotCall()) + .catch(common.mustCall((err) => { + assert.strictEqual(err.code, 'ENOTFOUND'); + })); + + const req = dns.resolveTxt(addresses.NOT_FOUND, function(err, result) { + assert.ok(err instanceof Error); + assert.strictEqual(err.code, 'ENOTFOUND'); + + assert.strictEqual(result, undefined); + + done(); + }); + + checkWrap(req); +}); + + +TEST(function test_lookup_failure(done) { + dnsPromises.lookup(addresses.NOT_FOUND, 4) + .then(common.mustNotCall()) + .catch(common.expectsError({ code: dns.NOTFOUND })); + + const req = dns.lookup(addresses.NOT_FOUND, 4, (err) => { + assert.ok(err instanceof Error); + assert.strictEqual(err.code, dns.NOTFOUND); + assert.strictEqual(err.code, 'ENOTFOUND'); + assert.doesNotMatch(err.message, /ENOENT/); + assert.ok(err.message.includes(addresses.NOT_FOUND)); + + done(); + }); + + checkWrap(req); +}); + + +TEST(async function test_lookup_ip_all(done) { + function validateResult(result) { + assert.ok(Array.isArray(result)); + assert.ok(result.length > 0); + assert.strictEqual(result[0].address, '127.0.0.1'); + assert.strictEqual(result[0].family, 4); + } + + validateResult(await dnsPromises.lookup('127.0.0.1', { all: true })); + + const req = dns.lookup( + '127.0.0.1', + { all: true }, + function(err, ips, family) { + assert.ifError(err); + assert.strictEqual(family, undefined); + validateResult(ips); + done(); + } + ); + + checkWrap(req); +}); + + +TEST(function test_lookup_ip_all_promise(done) { + const req = util.promisify(dns.lookup)('127.0.0.1', { all: true }) + .then(function(ips) { + assert.ok(Array.isArray(ips)); + assert.ok(ips.length > 0); + assert.strictEqual(ips[0].address, '127.0.0.1'); + assert.strictEqual(ips[0].family, 4); + + done(); + }); + + checkWrap(req); +}); + + +TEST(function test_lookup_ip_promise(done) { + util.promisify(dns.lookup)('127.0.0.1') + .then(function({ address, family }) { + assert.strictEqual(address, '127.0.0.1'); + assert.strictEqual(family, 4); + + done(); + }); +}); + + +TEST(async function test_lookup_null_all(done) { + assert.deepStrictEqual(await dnsPromises.lookup(null, { all: true }), []); + + const req = dns.lookup(null, { all: true }, (err, ips) => { + assert.ifError(err); + assert.ok(Array.isArray(ips)); + assert.strictEqual(ips.length, 0); + + done(); + }); + + checkWrap(req); +}); + + +TEST(async function test_lookup_all_mixed(done) { + function validateResult(result) { + assert.ok(Array.isArray(result)); + assert.ok(result.length > 0); + + result.forEach(function(ip) { + if (isIPv4(ip.address)) + assert.strictEqual(ip.family, 4); + else if (isIPv6(ip.address)) + assert.strictEqual(ip.family, 6); + else + assert.fail('unexpected IP address'); + }); + } + + validateResult(await dnsPromises.lookup(addresses.INET_HOST, { all: true })); + + const req = dns.lookup(addresses.INET_HOST, { + all: true + }, function(err, ips) { + assert.ifError(err); + validateResult(ips); + done(); + }); + + checkWrap(req); +}); + + +// TEST(function test_lookupservice_invalid(done) { +// dnsPromises.lookupService('1.2.3.4', 80) +// .then(common.mustNotCall()) +// .catch(common.expectsError({ code: 'ENOTFOUND' })); + +// const req = dns.lookupService('1.2.3.4', 80, (err) => { +// assert(err instanceof Error); +// assert.strictEqual(err.code, 'ENOTFOUND'); +// assert.match(err.message, /1\.2\.3\.4/); + +// done(); +// }); + +// checkWrap(req); +// }); + + +// TEST(function test_reverse_failure(done) { +// dnsPromises.reverse('203.0.113.0') +// .then(common.mustNotCall()) +// .catch(common.expectsError({ +// code: 'ENOTFOUND', +// hostname: '203.0.113.0' +// })); + +// // 203.0.113.0/24 are addresses reserved for (RFC) documentation use only +// const req = dns.reverse('203.0.113.0', function(err) { +// assert(err instanceof Error); +// assert.strictEqual(err.code, 'ENOTFOUND'); // Silly error code... +// assert.strictEqual(err.hostname, '203.0.113.0'); +// assert.match(err.message, /203\.0\.113\.0/); + +// done(); +// }); + +// checkWrap(req); +// }); + + +TEST(function test_lookup_failure(done) { + dnsPromises.lookup(addresses.NOT_FOUND) + .then(common.mustNotCall()) + .catch(common.expectsError({ + code: 'ENOTFOUND', + hostname: addresses.NOT_FOUND + })); + + const req = dns.lookup(addresses.NOT_FOUND, (err) => { + assert(err instanceof Error); + assert.strictEqual(err.code, 'ENOTFOUND'); // Silly error code... + assert.strictEqual(err.hostname, addresses.NOT_FOUND); + assert.ok(err.message.includes(addresses.NOT_FOUND)); + + done(); + }); + + checkWrap(req); +}); + + +TEST(function test_resolve_failure(done) { + const req = dns.resolve4(addresses.NOT_FOUND, (err) => { + assert(err instanceof Error); + + switch (err.code) { + case 'ENOTFOUND': + case 'ESERVFAIL': + break; + default: + assert.strictEqual(err.code, 'ENOTFOUND'); // Silly error code... + break; + } + + assert.strictEqual(err.hostname, addresses.NOT_FOUND); + assert.ok(err.message.includes(addresses.NOT_FOUND)); + + done(); + }); + + checkWrap(req); +}); + + +let getaddrinfoCallbackCalled = false; + +console.log(`looking up ${addresses.INET4_HOST}..`); + +const cares = internalBinding('cares_wrap'); +const req = new cares.GetAddrInfoReqWrap(); +cares.getaddrinfo(req, addresses.INET4_HOST, 4, + /* hints */ 0, /* verbatim */ true); + +req.oncomplete = function(err, domains) { + assert.strictEqual(err, 0); + console.log(`${addresses.INET4_HOST} = ${domains}`); + assert.ok(Array.isArray(domains)); + assert.ok(domains.length >= 1); + assert.strictEqual(typeof domains[0], 'string'); + getaddrinfoCallbackCalled = true; +}; + +process.on('exit', function() { + console.log(`${completed} tests completed`); + assert.strictEqual(running, false); + assert.strictEqual(completed, expected); + assert.ok(getaddrinfoCallbackCalled); +}); + +// Should not throw. +dns.lookup(addresses.INET6_HOST, 6, common.mustCall()); +dns.lookup(addresses.INET_HOST, {}, common.mustCall()); +// dns.lookupService('0.0.0.0', '0', common.mustCall()); +// dns.lookupService('0.0.0.0', 0, common.mustCall()); +(async function() { + await dnsPromises.lookup(addresses.INET6_HOST, 6); + await dnsPromises.lookup(addresses.INET_HOST, {}); +})().then(common.mustCall()); diff --git a/cli/tests/node_compat/test/parallel/package.json b/cli/tests/node_compat/test/parallel/package.json new file mode 100644 index 00000000000000..0967ef424bce67 --- /dev/null +++ b/cli/tests/node_compat/test/parallel/package.json @@ -0,0 +1 @@ +{} diff --git a/cli/tests/node_compat/test/parallel/test-assert-async.js b/cli/tests/node_compat/test/parallel/test-assert-async.js new file mode 100644 index 00000000000000..4bee92a1ae6dcf --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-assert-async.js @@ -0,0 +1,244 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +'use strict'; +const common = require('../common'); +const assert = require('assert'); + +// Run all tests in parallel and check their outcome at the end. +const promises = []; + +// Thenable object without `catch` method, +// shouldn't be considered as a valid Thenable +const invalidThenable = { + then: (fulfill, reject) => { + fulfill(); + }, +}; + +// Function that returns a Thenable function, +// a function with `catch` and `then` methods attached, +// shouldn't be considered as a valid Thenable. +const invalidThenableFunc = () => { + function f() {} + + f.then = (fulfill, reject) => { + fulfill(); + }; + f.catch = () => {}; + + return f; +}; + +// Test assert.rejects() and assert.doesNotReject() by checking their +// expected output and by verifying that they do not work sync + +// Check `assert.rejects`. +{ + const rejectingFn = async () => assert.fail(); + const errObj = { + code: 'ERR_ASSERTION', + name: 'AssertionError', + message: 'Failed' + }; + + // `assert.rejects` accepts a function or a promise + // or a thenable as first argument. + promises.push(assert.rejects(rejectingFn, errObj)); + promises.push(assert.rejects(rejectingFn(), errObj)); + + const validRejectingThenable = { + then: (fulfill, reject) => { + reject({ code: 'FAIL' }); + }, + catch: () => {} + }; + promises.push(assert.rejects(validRejectingThenable, { code: 'FAIL' })); + + // `assert.rejects` should not accept thenables that + // use a function as `obj` and that have no `catch` handler. + promises.push(assert.rejects( + assert.rejects(invalidThenable, {}), + { + code: 'ERR_INVALID_ARG_TYPE' + }) + ); + promises.push(assert.rejects( + assert.rejects(invalidThenableFunc, {}), + { + code: 'ERR_INVALID_RETURN_VALUE' + }) + ); + + const err = new Error('foobar'); + const validate = () => { return 'baz'; }; + promises.push(assert.rejects( + () => assert.rejects(Promise.reject(err), validate), + { + message: 'The "validate" validation function is expected to ' + + "return \"true\". Received 'baz'\n\nCaught error:\n\n" + + 'Error: foobar', + code: 'ERR_ASSERTION', + actual: err, + expected: validate, + name: 'AssertionError', + operator: 'rejects', + } + )); +} + +{ + const handler = (err) => { + assert(err instanceof assert.AssertionError, + `${err.name} is not instance of AssertionError`); + assert.strictEqual(err.code, 'ERR_ASSERTION'); + assert.strictEqual(err.message, + 'Missing expected rejection (mustNotCall).'); + assert.strictEqual(err.operator, 'rejects'); + assert.ok(!err.stack.includes('at Function.rejects')); + return true; + }; + + let promise = assert.rejects(async () => {}, common.mustNotCall()); + promises.push(assert.rejects(promise, common.mustCall(handler))); + + promise = assert.rejects(() => {}, common.mustNotCall()); + promises.push(assert.rejects(promise, { + name: 'TypeError', + code: 'ERR_INVALID_RETURN_VALUE', + // FIXME(JakobJingleheimer): This should match on key words, like /Promise/ and /undefined/. + message: 'Expected instance of Promise to be returned ' + + 'from the "promiseFn" function but got undefined.' + })); + + promise = assert.rejects(Promise.resolve(), common.mustNotCall()); + promises.push(assert.rejects(promise, common.mustCall(handler))); +} + +{ + const THROWN_ERROR = new Error(); + + promises.push(assert.rejects(() => { + throw THROWN_ERROR; + }, {}).catch(common.mustCall((err) => { + assert.strictEqual(err, THROWN_ERROR); + }))); +} + +promises.push(assert.rejects( + assert.rejects('fail', {}), + { + code: 'ERR_INVALID_ARG_TYPE', + message: 'The "promiseFn" argument must be of type function or an ' + + "instance of Promise. Received type string ('fail')" + } +)); + +{ + const handler = (generated, actual, err) => { + assert.strictEqual(err.generatedMessage, generated); + assert.strictEqual(err.code, 'ERR_ASSERTION'); + assert.strictEqual(err.actual, actual); + assert.strictEqual(err.operator, 'rejects'); + assert.match(err.stack, /rejects/); + return true; + }; + const err = new Error(); + promises.push(assert.rejects( + assert.rejects(Promise.reject(null), { code: 'FOO' }), + handler.bind(null, true, null) + )); + promises.push(assert.rejects( + assert.rejects(Promise.reject(5), { code: 'FOO' }, 'AAAAA'), + handler.bind(null, false, 5) + )); + promises.push(assert.rejects( + assert.rejects(Promise.reject(err), { code: 'FOO' }, 'AAAAA'), + handler.bind(null, false, err) + )); +} + +// Check `assert.doesNotReject`. +{ + // `assert.doesNotReject` accepts a function or a promise + // or a thenable as first argument. + /* eslint-disable no-restricted-syntax */ + let promise = assert.doesNotReject(() => new Map(), common.mustNotCall()); + promises.push(assert.rejects(promise, { + message: 'Expected instance of Promise to be returned ' + + 'from the "promiseFn" function but got an instance of Map.', + code: 'ERR_INVALID_RETURN_VALUE', + name: 'TypeError' + })); + promises.push(assert.doesNotReject(async () => {})); + promises.push(assert.doesNotReject(Promise.resolve())); + + // `assert.doesNotReject` should not accept thenables that + // use a function as `obj` and that have no `catch` handler. + const validFulfillingThenable = { + then: (fulfill, reject) => { + fulfill(); + }, + catch: () => {} + }; + promises.push(assert.doesNotReject(validFulfillingThenable)); + promises.push(assert.rejects( + assert.doesNotReject(invalidThenable), + { + code: 'ERR_INVALID_ARG_TYPE' + }) + ); + promises.push(assert.rejects( + assert.doesNotReject(invalidThenableFunc), + { + code: 'ERR_INVALID_RETURN_VALUE' + }) + ); + + const handler1 = (err) => { + assert(err instanceof assert.AssertionError, + `${err.name} is not instance of AssertionError`); + assert.strictEqual(err.code, 'ERR_ASSERTION'); + assert.strictEqual(err.message, 'Failed'); + return true; + }; + const handler2 = (err) => { + assert(err instanceof assert.AssertionError, + `${err.name} is not instance of AssertionError`); + assert.strictEqual(err.code, 'ERR_ASSERTION'); + assert.strictEqual(err.message, + 'Got unwanted rejection.\nActual message: "Failed"'); + assert.strictEqual(err.operator, 'doesNotReject'); + assert.ok(err.stack); + assert.ok(!err.stack.includes('at Function.doesNotReject')); + return true; + }; + + const rejectingFn = async () => assert.fail(); + + promise = assert.doesNotReject(rejectingFn, common.mustCall(handler1)); + promises.push(assert.rejects(promise, common.mustCall(handler2))); + + promise = assert.doesNotReject(rejectingFn(), common.mustCall(handler1)); + promises.push(assert.rejects(promise, common.mustCall(handler2))); + + promise = assert.doesNotReject(() => assert.fail(), common.mustNotCall()); + promises.push(assert.rejects(promise, common.mustCall(handler1))); + + promises.push(assert.rejects( + assert.doesNotReject(123), + { + code: 'ERR_INVALID_ARG_TYPE', + message: 'The "promiseFn" argument must be of type ' + + 'function or an instance of Promise. Received type number (123)' + } + )); + /* eslint-enable no-restricted-syntax */ +} + +// Make sure all async code gets properly executed. +Promise.all(promises).then(common.mustCall()); diff --git a/cli/tests/node_compat/test/parallel/test-assert-fail.js b/cli/tests/node_compat/test/parallel/test-assert-fail.js new file mode 100644 index 00000000000000..2aad9766d6ecf0 --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-assert-fail.js @@ -0,0 +1,51 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +'use strict'; + +const common = require('../common'); +const assert = require('assert'); + +// No args +assert.throws( + () => { assert.fail(); }, + { + code: 'ERR_ASSERTION', + name: 'AssertionError', + message: 'Failed', + operator: 'fail', + actual: undefined, + expected: undefined, + generatedMessage: true, + stack: /Failed/ + } +); + +// One arg = message +assert.throws(() => { + assert.fail('custom message'); +}, { + code: 'ERR_ASSERTION', + name: 'AssertionError', + message: 'custom message', + operator: 'fail', + actual: undefined, + expected: undefined, + generatedMessage: false +}); + +// One arg = Error +assert.throws(() => { + assert.fail(new TypeError('custom message')); +}, { + name: 'TypeError', + message: 'custom message' +}); + +Object.prototype.get = common.mustNotCall(); +assert.throws(() => assert.fail(''), { code: 'ERR_ASSERTION' }); +delete Object.prototype.get; diff --git a/cli/tests/node_compat/test/parallel/test-assert-strict-exists.js b/cli/tests/node_compat/test/parallel/test-assert-strict-exists.js new file mode 100644 index 00000000000000..79139f1e5a9a1c --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-assert-strict-exists.js @@ -0,0 +1,13 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +'use strict'; + +require('../common'); +const assert = require('assert'); + +assert.strictEqual(require('assert/strict'), assert.strict); diff --git a/cli/tests/node_compat/test/parallel/test-assert.js b/cli/tests/node_compat/test/parallel/test-assert.js new file mode 100644 index 00000000000000..58b95068c94c3f --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-assert.js @@ -0,0 +1,1615 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 15.5.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +// Flags: --expose-internals +// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. + +'use strict'; + +const common = require('../common'); +const assert = require('assert'); +const { inspect } = require('util'); +// TODO(kt3k): Enable these when "vm" is ready. +// const vm = require('vm'); +// TODO(kt3k): Enable these when "internal/test/binding" is ready. +// const { internalBinding } = require('internal/test/binding'); +const a = assert; + +// Disable colored output to prevent color codes from breaking assertion +// message comparisons. This should only be an issue when process.stdout +// is a TTY. +if (process.stdout.isTTY) + process.env.NODE_DISABLE_COLORS = '1'; + +const strictEqualMessageStart = 'Expected values to be strictly equal:\n'; +const start = 'Expected values to be strictly deep-equal:'; +const actExp = '+ actual - expected'; + +assert.ok(a.AssertionError.prototype instanceof Error, + 'a.AssertionError instanceof Error'); + +assert.throws(() => a(false), a.AssertionError, 'ok(false)'); +assert.throws(() => a.ok(false), a.AssertionError, 'ok(false)'); + +// Throw message if the message is instanceof Error. +{ + let threw = false; + try { + assert.ok(false, new Error('ok(false)')); + } catch (e) { + threw = true; + assert.ok(e instanceof Error); + } + assert.ok(threw, 'Error: ok(false)'); +} + + +a(true); +a('test', 'ok(\'test\')'); +a.ok(true); +a.ok('test'); + +assert.throws(() => a.equal(true, false), + a.AssertionError, 'equal(true, false)'); + +a.equal(null, null); +a.equal(undefined, undefined); +a.equal(null, undefined); +a.equal(true, true); +a.equal(2, '2'); +a.notEqual(true, false); + +assert.throws(() => a.notEqual(true, true), + a.AssertionError, 'notEqual(true, true)'); + +assert.throws(() => a.strictEqual(2, '2'), + a.AssertionError, 'strictEqual(2, \'2\')'); + +assert.throws(() => a.strictEqual(null, undefined), + a.AssertionError, 'strictEqual(null, undefined)'); + +assert.throws( + () => a.notStrictEqual(2, 2), + { + message: 'Expected "actual" to be strictly unequal to: 2\n', + name: 'AssertionError' + } +); + +assert.throws( + () => a.notStrictEqual('a '.repeat(30), 'a '.repeat(30)), + { + message: 'Expected "actual" to be strictly unequal to: ' + + `"${'a '.repeat(30)}"\n`, + name: 'AssertionError' + } +); + +assert.throws( + () => a.notEqual(1, 1), + { + message: '1 != 1', + operator: '!=' + } +); + +a.notStrictEqual(2, '2'); + +// Testing the throwing. +function thrower(errorConstructor) { + throw new errorConstructor({}); +} + +// The basic calls work. +assert.throws(() => thrower(a.AssertionError), a.AssertionError, 'message'); +assert.throws(() => thrower(a.AssertionError), a.AssertionError); +assert.throws(() => thrower(a.AssertionError)); + +// If not passing an error, catch all. +assert.throws(() => thrower(TypeError)); + +// When passing a type, only catch errors of the appropriate type. +assert.throws( + () => a.throws(() => thrower(TypeError), a.AssertionError), + { + // generatedMessage: true, + // actual: new TypeError({}), + expected: a.AssertionError, + code: 'ERR_ASSERTION', + name: 'AssertionError', + operator: 'throws', + message: 'The error is expected to be an instance of "AssertionError". ' + + 'Received "TypeError"\n\nError message:\n\n[object Object]' + } +); + +// doesNotThrow should pass through all errors. +{ + let threw = false; + try { + a.doesNotThrow(() => thrower(TypeError), a.AssertionError); + } catch (e) { + threw = true; + assert.ok(e instanceof TypeError); + } + assert(threw, 'a.doesNotThrow with an explicit error is eating extra errors'); +} + +// Key difference is that throwing our correct error makes an assertion error. +{ + let threw = false; + try { + a.doesNotThrow(() => thrower(TypeError), TypeError); + } catch (e) { + threw = true; + assert.ok(e instanceof a.AssertionError); + // Commented out the following assertion + // assert.ok(!e.stack.includes('at Function.doesNotThrow')); + } + assert.ok(threw, 'a.doesNotThrow is not catching type matching errors'); +} + +assert.throws( + () => a.doesNotThrow(() => thrower(Error), 'user message'), + { + name: 'AssertionError', + code: 'ERR_ASSERTION', + operator: 'doesNotThrow', + message: 'Got unwanted exception: user message\n' + + 'Actual message: "[object Object]"' + } +); + +assert.throws( + () => a.doesNotThrow(() => thrower(Error)), + { + code: 'ERR_ASSERTION', + message: 'Got unwanted exception.\nActual message: "[object Object]"' + } +); + +assert.throws( + () => a.doesNotThrow(() => thrower(Error), /\[[a-z]{6}\s[A-z]{6}\]/g, 'user message'), + { + name: 'AssertionError', + code: 'ERR_ASSERTION', + operator: 'doesNotThrow', + message: 'Got unwanted exception: user message\n' + + 'Actual message: "[object Object]"' + } +); + +// Make sure that validating using constructor really works. +{ + let threw = false; + try { + assert.throws( + () => { + throw ({}); + }, + Array + ); + } catch { + threw = true; + } + assert.ok(threw, 'wrong constructor validation'); +} + +// Use a RegExp to validate the error message. +{ + a.throws(() => thrower(TypeError), /\[object Object\]/); + + const symbol = Symbol('foo'); + a.throws(() => { + throw symbol; + }, /foo/); + + a.throws(() => { + a.throws(() => { + throw symbol; + }, /abc/); + }, { + message: 'The input did not match the regular expression /abc/. ' + + "Input:\n\n'Symbol(foo)'\n", + code: 'ERR_ASSERTION', + operator: 'throws', + actual: symbol, + expected: /abc/ + }); +} + +// Use a fn to validate the error object. +a.throws(() => thrower(TypeError), (err) => { + if ((err instanceof TypeError) && /\[object Object\]/.test(err)) { + return true; + } +}); + +// https://github.com/nodejs/node/issues/3188 +{ + let actual; + assert.throws( + () => { + const ES6Error = class extends Error {}; + const AnotherErrorType = class extends Error {}; + + assert.throws(() => { + actual = new AnotherErrorType('foo'); + throw actual; + }, ES6Error); + }, + (err) => { + assert.strictEqual( + err.message, + 'The error is expected to be an instance of "ES6Error". ' + + 'Received "AnotherErrorType"\n\nError message:\n\nfoo' + ); + assert.strictEqual(err.actual, actual); + return true; + } + ); +} + +// Check messages from assert.throws(). +{ + const noop = () => {}; + assert.throws( + () => { a.throws((noop)); }, + { + code: 'ERR_ASSERTION', + message: 'Missing expected exception.', + operator: 'throws', + actual: undefined, + expected: undefined + }); + + assert.throws( + () => { a.throws(noop, TypeError); }, + { + code: 'ERR_ASSERTION', + message: 'Missing expected exception (TypeError).', + actual: undefined, + expected: TypeError + }); + + assert.throws( + () => { a.throws(noop, 'fhqwhgads'); }, + { + code: 'ERR_ASSERTION', + message: 'Missing expected exception: fhqwhgads', + actual: undefined, + expected: undefined + }); + + assert.throws( + () => { a.throws(noop, TypeError, 'fhqwhgads'); }, + { + code: 'ERR_ASSERTION', + message: 'Missing expected exception (TypeError): fhqwhgads', + actual: undefined, + expected: TypeError + }); + + let threw = false; + try { + a.throws(noop); + } catch (e) { + threw = true; + assert.ok(e instanceof a.AssertionError); + // TODO(kt3k): enable this assertion + // assert.ok(!e.stack.includes('at Function.throws')); + } + assert.ok(threw); +} + +const circular = { y: 1 }; +circular.x = circular; + +function testAssertionMessage(actual, expected, msg) { + assert.throws( + () => assert.strictEqual(actual, ''), + { + generatedMessage: true, + message: msg || strictEqualMessageStart + + `+ actual - expected\n\n+ ${expected}\n- ''` + } + ); +} + +function testShortAssertionMessage(actual, expected) { + testAssertionMessage(actual, expected, strictEqualMessageStart + + `\n${inspect(actual)} !== ''\n`); +} + +// TODO(kt3k): Do we need completely simulate +// Node.js assertion error messages? +/* +testShortAssertionMessage(null, 'null'); +testShortAssertionMessage(true, 'true'); +testShortAssertionMessage(false, 'false'); +testShortAssertionMessage(100, '100'); +testShortAssertionMessage(NaN, 'NaN'); +testShortAssertionMessage(Infinity, 'Infinity'); +testShortAssertionMessage('a', '"a"'); +testShortAssertionMessage('foo', '\'foo\''); +testShortAssertionMessage(0, '0'); +testShortAssertionMessage(Symbol(), 'Symbol()'); +testShortAssertionMessage(undefined, 'undefined'); +testShortAssertionMessage(-Infinity, '-Infinity'); +testAssertionMessage([], '[]'); +testAssertionMessage(/a/, '/a/'); +testAssertionMessage(/abc/gim, '/abc/gim'); +testAssertionMessage({}, '{}'); +testAssertionMessage([1, 2, 3], '[\n+ 1,\n+ 2,\n+ 3\n+ ]'); +testAssertionMessage(function f() {}, '[Function: f]'); +testAssertionMessage(function() {}, '[Function (anonymous)]'); +testAssertionMessage(circular, + ' {\n+ x: [Circular *1],\n+ y: 1\n+ }'); +testAssertionMessage({ a: undefined, b: null }, + '{\n+ a: undefined,\n+ b: null\n+ }'); +testAssertionMessage({ a: NaN, b: Infinity, c: -Infinity }, + '{\n+ a: NaN,\n+ b: Infinity,\n+ c: -Infinity\n+ }'); +*/ + +// https://github.com/nodejs/node-v0.x-archive/issues/5292 +assert.throws( + () => assert.strictEqual(1, 2), +/* Memo: Disabled this object assertion + { + message: `${strictEqualMessageStart}\n1 !== 2\n`, + generatedMessage: true + } +*/ +); + +assert.throws( + () => assert.strictEqual(1, 2, 'oh no'), + { + message: 'oh no', + generatedMessage: false + } +); + +{ + let threw = false; + const rangeError = new RangeError('my range'); + + // Verify custom errors. + try { + assert.strictEqual(1, 2, rangeError); + } catch (e) { + assert.strictEqual(e, rangeError); + threw = true; + assert.ok(e instanceof RangeError, 'Incorrect error type thrown'); + } + assert.ok(threw); + threw = false; + + // Verify AssertionError is the result from doesNotThrow with custom Error. + try { + a.doesNotThrow(() => { + throw new TypeError('wrong type'); + }, TypeError, rangeError); + } catch (e) { + threw = true; + assert.ok(e.message.includes(rangeError.message)); + assert.ok(e instanceof assert.AssertionError); + // TODO(kt3k): Enable this assertion + // assert.ok(!e.stack.includes('doesNotThrow'), e); + } + assert.ok(threw); +} + +{ + // Verify that throws() and doesNotThrow() throw on non-functions. + const testBlockTypeError = (method, fn) => { + assert.throws( + () => method(fn), + { + code: 'ERR_INVALID_ARG_TYPE', + name: 'TypeError', + message: 'The "fn" argument must be of type function.' + + common.invalidArgTypeHelper(fn) + } + ); + }; + + testBlockTypeError(assert.throws, 'string'); + testBlockTypeError(assert.doesNotThrow, 'string'); + testBlockTypeError(assert.throws, 1); + testBlockTypeError(assert.doesNotThrow, 1); + testBlockTypeError(assert.throws, true); + testBlockTypeError(assert.doesNotThrow, true); + testBlockTypeError(assert.throws, false); + testBlockTypeError(assert.doesNotThrow, false); + testBlockTypeError(assert.throws, []); + testBlockTypeError(assert.doesNotThrow, []); + testBlockTypeError(assert.throws, {}); + testBlockTypeError(assert.doesNotThrow, {}); + testBlockTypeError(assert.throws, /foo/); + testBlockTypeError(assert.doesNotThrow, /foo/); + testBlockTypeError(assert.throws, null); + testBlockTypeError(assert.doesNotThrow, null); + testBlockTypeError(assert.throws, undefined); + testBlockTypeError(assert.doesNotThrow, undefined); +} + +// https://github.com/nodejs/node/issues/3275 +assert.throws(() => { throw 'error'; }, (err) => err === 'error'); +assert.throws(() => { throw new Error(); }, (err) => err instanceof Error); + +// Long values should be truncated for display. +assert.throws(() => { + assert.strictEqual('A'.repeat(1000), ''); +}, (err) => { + assert.strictEqual(err.code, 'ERR_ASSERTION'); + /* TODO(kt3k): Enable this assertion + assert.strictEqual(err.message, + `${strictEqualMessageStart}+ actual - expected\n\n` + + `+ '${'A'.repeat(1000)}'\n- ''`); + */ + assert.strictEqual(err.actual.length, 1000); + // TODO(kt3k): Enable this after fixing 'inspect' + // assert.ok(inspect(err).includes(`actual: '${'A'.repeat(488)}...'`)); + return true; +}); + +// Output that extends beyond 10 lines should also be truncated for display. +{ + const multilineString = 'fhqwhgads\n'.repeat(15); + assert.throws(() => { + assert.strictEqual(multilineString, ''); + }, (err) => { + assert.strictEqual(err.code, 'ERR_ASSERTION'); + // TODO(kt3k): Enable these assertion when the strictEqual message is aligned + // to Node.js API. + // assert.strictEqual(err.message.split('\n').length, 19); + assert.strictEqual(err.actual.split('\n').length, 16); + // TODO(kt3k): inspect(err) causes Maximum call stack error. + /* + assert.ok(inspect(err).includes( + "actual: 'fhqwhgads\\n' +\n" + + " 'fhqwhgads\\n' +\n".repeat(9) + + " '...'")); + */ + return true; + }); +} + +{ + // Bad args to AssertionError constructor should throw TypeError. + const args = [1, true, false, '', null, Infinity, Symbol('test'), undefined]; + args.forEach((input) => { + assert.throws( + () => new assert.AssertionError(input), + { + code: 'ERR_INVALID_ARG_TYPE', + name: 'TypeError', + message: 'The "options" argument must be of type object.' + + common.invalidArgTypeHelper(input) + }); + }); +} + +assert.throws( + () => assert.strictEqual(new Error('foo'), new Error('foobar')), + { + code: 'ERR_ASSERTION', + name: 'AssertionError', + /* TODO(kt3k): Enable this assertion when the assertion error message is fixed. + message: 'Expected "actual" to be reference-equal to "expected":\n' + + '+ actual - expected\n\n' + + '+ [Error: foo]\n- [Error: foobar]' + */ + } +); + +a.equal(NaN, NaN); +a.throws( + () => a.notEqual(NaN, NaN), + a.AssertionError +); + +// Test strict assert. +{ + const a = require('assert'); + const assert = require('assert').strict; + assert.throws(() => assert.equal(1, true), assert.AssertionError); + assert.notEqual(0, false); + assert.throws(() => assert.deepEqual(1, true), assert.AssertionError); + assert.notDeepEqual(0, false); + assert.equal(assert.strict, assert.strict.strict); + assert.equal(assert.equal, assert.strictEqual); + assert.equal(assert.deepEqual, assert.deepStrictEqual); + assert.equal(assert.notEqual, assert.notStrictEqual); + assert.equal(assert.notDeepEqual, assert.notDeepStrictEqual); + assert.equal(Object.keys(assert).length, Object.keys(a).length); + assert(7); + assert.throws( + () => assert(...[]), + { + message: 'No value argument passed to `assert.ok()`', + name: 'AssertionError', + // TODO(kt3k): Enable this + // generatedMessage: true + } + ); + assert.throws( + () => a(), + { + message: 'No value argument passed to `assert.ok()`', + name: 'AssertionError' + } + ); + + // Test setting the limit to zero and that assert.strict works properly. + const tmpLimit = Error.stackTraceLimit; + Error.stackTraceLimit = 0; + assert.throws( + () => { + assert.ok( + typeof 123 === 'string' + ); + }, + { + code: 'ERR_ASSERTION', + constructor: assert.AssertionError, + /* TODO(kt3k): Enable this assertion + message: 'The expression evaluated to a falsy value:\n\n ' + + "assert.ok(\n typeof 123 === 'string'\n )\n" + */ + } + ); + Error.stackTraceLimit = tmpLimit; + + // Test error diffs. + let message = [ + start, + `${actExp} ... Lines skipped`, + '', + ' [', + ' [', + ' [', + ' 1,', + ' 2,', + '+ 3', + "- '3'", + ' ]', + '...', + ' 4,', + ' 5', + ' ]'].join('\n'); + assert.throws( + () => assert.deepEqual([[[1, 2, 3]], 4, 5], [[[1, 2, '3']], 4, 5]), + /* TODO(kt3k): Enable this assertion + { message } + */ + ); + + message = [ + start, + `${actExp} ... Lines skipped`, + '', + ' [', + ' 1,', + '...', + ' 1,', + ' 0,', + '- 1,', + ' 1,', + '...', + ' 1,', + ' 1', + ' ]' + ].join('\n'); + assert.throws( + () => assert.deepEqual( + [1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1], + [1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1]), + /* TODO(kt3k): Enable this assertion + { message } + */ + ); + + message = [ + start, + `${actExp} ... Lines skipped`, + '', + ' [', + ' 1,', + '...', + ' 1,', + ' 0,', + '+ 1,', + ' 1,', + ' 1,', + ' 1', + ' ]' + ].join('\n'); + assert.throws( + () => assert.deepEqual( + [1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1], + [1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1]), + /* TODO(kt3k): Enable this assertion + { message } + */ + ); + + message = [ + start, + actExp, + '', + ' [', + ' 1,', + '+ 2,', + '- 1,', + ' 1,', + ' 1,', + ' 0,', + '+ 1,', + ' 1', + ' ]' + ].join('\n'); + assert.throws( + () => assert.deepEqual( + [1, 2, 1, 1, 0, 1, 1], + [1, 1, 1, 1, 0, 1]), + /* TODO(kt3k): Enable this assertion + { message } + */ + ); + + message = [ + start, + actExp, + '', + '+ [', + '+ 1,', + '+ 2,', + '+ 1', + '+ ]', + '- undefined', + ].join('\n'); + assert.throws( + () => assert.deepEqual([1, 2, 1], undefined), + /* TODO(kt3k): Enable this assertion + { message } + */ + ); + + message = [ + start, + actExp, + '', + ' [', + '+ 1,', + ' 2,', + ' 1', + ' ]' + ].join('\n'); + assert.throws( + () => assert.deepEqual([1, 2, 1], [2, 1]), + /* TODO(kt3k): Enable this assertion + { message } + */ + ); + + message = `${start}\n` + + `${actExp} ... Lines skipped\n` + + '\n' + + ' [\n' + + '+ 1,\n'.repeat(25) + + '...\n' + + '- 2,\n'.repeat(25) + + '...'; + assert.throws( + () => assert.deepEqual(Array(28).fill(1), Array(28).fill(2)), + /* TODO(kt3k): Enable this assertion + { message } + */ + ); + + const obj1 = {}; + const obj2 = { loop: 'forever' }; + obj2[inspect.custom] = () => '{}'; + // No infinite loop and no custom inspect. + assert.throws(() => assert.deepEqual(obj1, obj2), { + code: "ERR_ASSERTION", + /* TODO(kt3k): Enable this assertion + message: `${start}\n` + + `${actExp}\n` + + '\n' + + '+ {}\n' + + '- {\n' + + '- [Symbol(nodejs.util.inspect.custom)]: [Function (anonymous)],\n' + + "- loop: 'forever'\n" + + '- }' + */ + }); + + // notDeepEqual tests + assert.throws( + () => assert.notDeepEqual([1], [1]), + { + code: "ERR_ASSERTION", + /* TODO(kt3k): Enable this assertion + message: 'Expected "actual" not to be strictly deep-equal to:\n\n' + + '[\n 1\n]\n' + */ + } + ); + + message = 'Expected "actual" not to be strictly deep-equal to:' + + `\n\n[${'\n 1,'.repeat(45)}\n...\n`; + const data = Array(51).fill(1); + assert.throws( + () => assert.notDeepEqual(data, data), + /* TODO(kt3k): Enable this assertion + { message } + */ + ); +} + +assert.throws( + () => assert.ok(null), + { + code: 'ERR_ASSERTION', + constructor: assert.AssertionError, + generatedMessage: true, + /* TODO(kt3k): Enable this assertion + message: 'The expression evaluated to a falsy value:\n\n ' + + 'assert.ok(null)\n' + */ + } +); +assert.throws( + () => assert(typeof 123n === 'string'), + { + code: 'ERR_ASSERTION', + constructor: assert.AssertionError, + generatedMessage: true, + /* TODO(kt3k): Enable this assertion + message: 'The expression evaluated to a falsy value:\n\n ' + + "assert(typeof 123n === 'string')\n" + */ + } +); + +assert.throws( + () => assert(false, Symbol('foo')), + { + code: 'ERR_ASSERTION', + constructor: assert.AssertionError, + /* TODO(kt3k): Enable this assertion + generatedMessage: false, + message: 'Symbol(foo)' + */ + } +); + +// TODO(kt3k): Enable these when "internal/test/binding" is ready. +/* +{ + // Test caching. + const fs = internalBinding('fs'); + const tmp = fs.close; + fs.close = common.mustCall(tmp, 1); + function throwErr() { + assert( + (Buffer.from('test') instanceof Error) + ); + } + assert.throws( + () => throwErr(), + { + code: 'ERR_ASSERTION', + constructor: assert.AssertionError, + message: 'The expression evaluated to a falsy value:\n\n ' + + "assert(\n (Buffer.from('test') instanceof Error)\n )\n" + } + ); + assert.throws( + () => throwErr(), + { + code: 'ERR_ASSERTION', + constructor: assert.AssertionError, + message: 'The expression evaluated to a falsy value:\n\n ' + + "assert(\n (Buffer.from('test') instanceof Error)\n )\n" + } + ); + fs.close = tmp; +} +*/ + +assert.throws( + () => { + a( + (() => 'string')() + === + 123 instanceof + Buffer + ); + }, + { + code: 'ERR_ASSERTION', + constructor: assert.AssertionError, + /* TODO(kt3k): Enable this assertion + message: 'The expression evaluated to a falsy value:\n\n' + + ' a(\n' + + ' (() => \'string\')()\n' + + ' ===\n' + + ' 123 instanceof\n' + + ' Buffer\n' + + ' )\n' + */ + } +); + +assert.throws( + () => { + a( + (() => 'string')() + === + 123 instanceof + Buffer + ); + }, + { + code: 'ERR_ASSERTION', + constructor: assert.AssertionError, + /* TODO(kt3k): Enable this assertion + message: 'The expression evaluated to a falsy value:\n\n' + + ' a(\n' + + ' (() => \'string\')()\n' + + ' ===\n' + + ' 123 instanceof\n' + + ' Buffer\n' + + ' )\n' + */ + } +); + +assert.throws(() => { +a(( + () => 'string')() === +123 instanceof +Buffer +); +}, { + code: 'ERR_ASSERTION', + constructor: assert.AssertionError, + /* TODO(kt3k): Enable this assertion + message: 'The expression evaluated to a falsy value:\n\n' + + ' a((\n' + + ' () => \'string\')() ===\n' + + ' 123 instanceof\n' + + ' Buffer\n' + + ' )\n' + */ + } +); + +assert.throws( + () => { + assert(true); assert(null, undefined); + }, + { + code: 'ERR_ASSERTION', + constructor: assert.AssertionError, + /* TODO(kt3k): Enable this assertion + message: 'The expression evaluated to a falsy value:\n\n ' + + 'assert(null, undefined)\n' + */ + } +); + +assert.throws( + () => { + assert + .ok(null, undefined); + }, + { + code: 'ERR_ASSERTION', + constructor: assert.AssertionError, + /* TODO(kt3k): Enable this assertion + message: 'The expression evaluated to a falsy value:\n\n ' + + 'ok(null, undefined)\n' + */ + } +); + +assert.throws( + () => assert['ok']["apply"](null, [0]), + { + code: 'ERR_ASSERTION', + constructor: assert.AssertionError, + /* TODO(kt3k): Enable this assertion + message: 'The expression evaluated to a falsy value:\n\n ' + + 'assert[\'ok\']["apply"](null, [0])\n' + */ + } +); + +assert.throws( + () => { + const wrapper = (fn, value) => fn(value); + wrapper(assert, false); + }, + { + code: 'ERR_ASSERTION', + constructor: assert.AssertionError, + /* TODO(kt3k): Enable this assertion + message: 'The expression evaluated to a falsy value:\n\n fn(value)\n' + */ + } +); + +assert.throws( + () => assert.ok.call(null, 0), + { + code: 'ERR_ASSERTION', + constructor: assert.AssertionError, + /* TODO(kt3k): Enable this assertion + message: 'The expression evaluated to a falsy value:\n\n ' + + 'assert.ok.call(null, 0)\n', + generatedMessage: true + */ + } +); + +assert.throws( + () => assert.ok.call(null, 0, 'test'), + { + code: 'ERR_ASSERTION', + constructor: assert.AssertionError, + message: 'test', + generatedMessage: false + } +); + +// Works in eval. +assert.throws( + () => new Function('assert', 'assert(1 === 2);')(assert), + { + code: 'ERR_ASSERTION', + constructor: assert.AssertionError, + /* TODO(kt3k): Enable this assertion + message: 'The expression evaluated to a falsy value:\n\n assert(1 === 2)\n' + */ + } +); + +assert.throws( + () => eval('console.log("FOO");\nassert.ok(1 === 2);'), + { + code: 'ERR_ASSERTION', + /* TODO(kt3k): Enable this assertion + message: 'false == true' + */ + } +); + +assert.throws( + () => assert.throws(() => {}, 'Error message', 'message'), + { + code: 'ERR_INVALID_ARG_TYPE', + name: 'TypeError', + /* TODO(kt3k): Enable this assertion + message: 'The "error" argument must be of type function or ' + + 'an instance of Error, RegExp, or Object. Received type string ' + + "('Error message')" + */ + } +); + +[ + 1, + false, + Symbol() +].forEach((input) => { + assert.throws( + () => assert.throws(() => {}, input), + { + code: 'ERR_INVALID_ARG_TYPE', + /* TODO(kt3k): Enable this assertion + message: 'The "error" argument must be of type function or ' + + 'an instance of Error, RegExp, or Object.' + + common.invalidArgTypeHelper(input) + */ + } + ); +}); + +{ + + assert.throws(() => { + assert.ok((() => Boolean('' === false))()); + }, { + code: "ERR_ASSERTION", + /* TODO(kt3k): Enable this assertion + message: 'The expression evaluated to a falsy value:\n\n' + + " assert.ok((() => Boolean('\\u0001' === false))())\n" + */ + }); + + const errFn = () => { + const err = new TypeError('Wrong value'); + err.code = 404; + throw err; + }; + const errObj = { + name: 'TypeError', + message: 'Wrong value' + }; + assert.throws(errFn, errObj); + + errObj.code = 404; + assert.throws(errFn, errObj); + + // Fail in case a expected property is undefined and not existent on the + // error. + errObj.foo = undefined; + assert.throws( + () => assert.throws(errFn, errObj), + { + code: 'ERR_ASSERTION', + name: 'AssertionError', + /* TODO(kt3k): Enable this assertion + message: `${start}\n${actExp}\n\n` + + ' Comparison {\n' + + ' code: 404,\n' + + '- foo: undefined,\n' + + " message: 'Wrong value',\n" + + " name: 'TypeError'\n" + + ' }' + */ + } + ); + + // Show multiple wrong properties at the same time. + errObj.code = '404'; + assert.throws( + () => assert.throws(errFn, errObj), + { + code: 'ERR_ASSERTION', + name: 'AssertionError', + /* TODO(kt3k): Enable this assertion + message: `${start}\n${actExp}\n\n` + + ' Comparison {\n' + + '+ code: 404,\n' + + "- code: '404',\n" + + '- foo: undefined,\n' + + " message: 'Wrong value',\n" + + " name: 'TypeError'\n" + + ' }' + */ + } + ); + + assert.throws( + () => assert.throws(() => { throw new Error(); }, { foo: 'bar' }, 'foobar'), + { + constructor: assert.AssertionError, + code: 'ERR_ASSERTION', + message: 'foobar' + } + ); + + assert.throws( + () => a.doesNotThrow(() => { throw new Error(); }, { foo: 'bar' }), + { + name: 'TypeError', + code: 'ERR_INVALID_ARG_TYPE', + /* TODO(kt3k): Enable this assertion + message: 'The "expected" argument must be of type function or an ' + + 'instance of RegExp. Received an instance of Object' + */ + } + ); + + assert.throws(() => { throw new Error('e'); }, new Error('e')); + assert.throws( + () => assert.throws(() => { throw new TypeError('e'); }, new Error('e')), + { + name: 'AssertionError', + code: 'ERR_ASSERTION', + /* TODO(kt3k): Enable this assertion + message: `${start}\n${actExp}\n\n` + + ' Comparison {\n' + + " message: 'e',\n" + + "+ name: 'TypeError'\n" + + "- name: 'Error'\n" + + ' }' + */ + } + ); + assert.throws( + () => assert.throws(() => { throw new Error('foo'); }, new Error('')), + { + name: 'AssertionError', + code: 'ERR_ASSERTION', + /* TODO(kt3k): Enable this assertion + generatedMessage: true, + message: `${start}\n${actExp}\n\n` + + ' Comparison {\n' + + "+ message: 'foo',\n" + + "- message: '',\n" + + " name: 'Error'\n" + + ' }' + */ + } + ); + + assert.throws(() => { throw undefined; }, /undefined/); + assert.throws( + () => a.doesNotThrow(() => { throw undefined; }), + { + name: 'AssertionError', + code: 'ERR_ASSERTION', + message: 'Got unwanted exception.\nActual message: "undefined"' + } + ); +} + +assert.throws( + () => assert.throws(() => { throw new Error(); }, {}), + { + message: "The argument 'error' may not be an empty object. Received {}", + code: 'ERR_INVALID_ARG_VALUE' + } +); + +assert.throws( + () => a.throws( + () => { throw 'foo'; }, + 'foo' + ), + { + code: 'ERR_AMBIGUOUS_ARGUMENT', + message: 'The "error/message" argument is ambiguous. ' + + 'The error "foo" is identical to the message.' + } +); + +assert.throws( + () => a.throws( + () => { throw new TypeError('foo'); }, + 'foo' + ), + { + code: 'ERR_AMBIGUOUS_ARGUMENT', + message: 'The "error/message" argument is ambiguous. ' + + 'The error message "foo" is identical to the message.' + } +); + +// Should not throw. +assert.throws(() => { throw null; }, 'foo'); + +assert.throws( + () => assert.strictEqual([], []), + { + code: 'ERR_ASSERTION', + /* TODO(kt3k): Enable this assertion + message: 'Values have same structure but are not reference-equal:\n\n[]\n' + */ + } +); + +{ + const args = (function() { return arguments; })('a'); + assert.throws( + () => assert.strictEqual(args, { 0: 'a' }), + { + code: 'ERR_ASSERTION', + /* TODO(kt3k): Enable this assertion + message: 'Expected "actual" to be reference-equal to "expected":\n' + + '+ actual - expected\n\n' + + "+ [Arguments] {\n- {\n '0': 'a'\n }" + */ + } + ); +} + +assert.throws( + () => { throw new TypeError('foobar'); }, + { + message: /foo/, + name: /^TypeError$/ + } +); + +assert.throws( + () => assert.throws( + () => { throw new TypeError('foobar'); }, + { + message: /fooa/, + name: /^TypeError$/ + } + ), + { + code: 'ERR_ASSERTION', + /* TODO(kt3k): Enable this assertion + message: `${start}\n${actExp}\n\n` + + ' Comparison {\n' + + "+ message: 'foobar',\n" + + '- message: /fooa/,\n' + + " name: 'TypeError'\n" + + ' }' + */ + } +); + +{ + let actual = null; + const expected = { message: 'foo' }; + assert.throws( + () => assert.throws( + () => { throw actual; }, + expected + ), + { + operator: 'throws', + actual, + expected, + code: 'ERR_ASSERTION', + /* TODO(kt3k): Enable this assertion + generatedMessage: true, + message: `${start}\n${actExp}\n\n` + + '+ null\n' + + '- {\n' + + "- message: 'foo'\n" + + '- }' + */ + } + ); + + actual = 'foobar'; + const message = 'message'; + assert.throws( + () => assert.throws( + () => { throw actual; }, + { message: 'foobar' }, + message + ), + { + actual, + message, + operator: 'throws', + generatedMessage: false + } + ); +} + +// Indicate where the strings diverge. +assert.throws( + () => assert.strictEqual('test test', 'test foobar'), + { + code: 'ERR_ASSERTION', + name: 'AssertionError', + /* TODO(kt3k): Enable this assertion + message: strictEqualMessageStart + + '+ actual - expected\n\n' + + "+ 'test test'\n" + + "- 'test foobar'\n" + + ' ^' + */ + } +); + +// Check for reference-equal objects in `notStrictEqual()` +assert.throws( + () => { + const obj = {}; + assert.notStrictEqual(obj, obj); + }, + { + code: 'ERR_ASSERTION', + name: 'AssertionError', + /* TODO(kt3k): Enable this assertion + message: 'Expected "actual" not to be reference-equal to "expected": {}' + */ + } +); + +assert.throws( + () => { + const obj = { a: true }; + assert.notStrictEqual(obj, obj); + }, + { + code: 'ERR_ASSERTION', + name: 'AssertionError', + /* TODO(kt3k): Enable this assertion + message: 'Expected "actual" not to be reference-equal to "expected":\n\n' + + '{\n a: true\n}\n' + */ + } +); + +{ + let threw = false; + try { + assert.deepStrictEqual(Array(100).fill(1), 'foobar'); + } catch (err) { + threw = true; + /* TODO(kt3k): Enable this assertion + assert(/actual: \[Array],\n expected: 'foobar',/.test(inspect(err))); + */ + } + assert(threw); +} + +assert.throws( + () => a.equal(1), + { code: 'ERR_MISSING_ARGS' } +); + +assert.throws( + () => a.deepEqual(/a/), + { code: 'ERR_MISSING_ARGS' } +); + +assert.throws( + () => a.notEqual(null), + { code: 'ERR_MISSING_ARGS' } +); + +assert.throws( + () => a.notDeepEqual('test'), + { code: 'ERR_MISSING_ARGS' } +); + +assert.throws( + () => a.strictEqual({}), + { code: 'ERR_MISSING_ARGS' } +); + +assert.throws( + () => a.deepStrictEqual(Symbol()), + { code: 'ERR_MISSING_ARGS' } +); + +assert.throws( + () => a.notStrictEqual(5n), + { code: 'ERR_MISSING_ARGS' } +); + +assert.throws( + () => a.notDeepStrictEqual(undefined), + { code: 'ERR_MISSING_ARGS' } +); + +assert.throws( + () => a.strictEqual(), + { code: 'ERR_MISSING_ARGS' } +); + +assert.throws( + () => a.deepStrictEqual(), + { code: 'ERR_MISSING_ARGS' } +); + +// Verify that `stackStartFunction` works as alternative to `stackStartFn`. +{ + (function hidden() { + const err = new assert.AssertionError({ + actual: 'foo', + operator: 'strictEqual', + stackStartFunction: hidden + }); + const err2 = new assert.AssertionError({ + actual: 'foo', + operator: 'strictEqual', + stackStartFn: hidden + }); + assert(!err.stack.includes('hidden')); + assert(!err2.stack.includes('hidden')); + })(); +} + +assert.throws( + () => assert.throws(() => { throw Symbol('foo'); }, RangeError), + { + code: 'ERR_ASSERTION', + /* TODO(kt3k): Enable this assertion + message: 'The error is expected to be an instance of "RangeError". ' + + 'Received "Symbol(foo)"' + */ + } +); + +assert.throws( + () => assert.throws(() => { throw [1, 2]; }, RangeError), + { + code: 'ERR_ASSERTION', + /* TODO(kt3k): Enable this assertion + message: 'The error is expected to be an instance of "RangeError". ' + + 'Received "[Array]"' + */ + } +); + +{ + const err = new TypeError('foo'); + const validate = (() => () => ({ a: true, b: [ 1, 2, 3 ] }))(); + assert.throws( + () => assert.throws(() => { throw err; }, validate), + { + message: 'The validation function is expected to ' + + `return "true". Received ${inspect(validate())}\n\nCaught ` + + `error:\n\n${err}`, + code: 'ERR_ASSERTION', + actual: err, + expected: validate, + name: 'AssertionError', + operator: 'throws', + } + ); +} + +// TODO(kt3k): Enable these when "vm" is ready. +/* +assert.throws( + () => { + const script = new vm.Script('new RangeError("foobar");'); + const context = vm.createContext(); + const err = script.runInContext(context); + assert.throws(() => { throw err; }, RangeError); + }, + { + message: 'The error is expected to be an instance of "RangeError". ' + + 'Received an error with identical name but a different ' + + 'prototype.\n\nError message:\n\nfoobar' + } +); +*/ + +// Multiple assert.match() tests. +{ + assert.throws( + () => assert.match(/abc/, 'string'), + { + code: 'ERR_INVALID_ARG_TYPE', + /* TODO(kt3k): Enable this assertion + message: 'The "regexp" argument must be an instance of RegExp. ' + + "Received type string ('string')" + */ + } + ); + assert.throws( + () => assert.match('string', /abc/), + { + actual: 'string', + expected: /abc/, + operator: 'match', + /* TODO(kt3k): Enable this assertion + message: 'The input did not match the regular expression /abc/. ' + + "Input:\n\n'string'\n", + generatedMessage: true + */ + } + ); + assert.throws( + () => assert.match('string', /abc/, 'foobar'), + { + actual: 'string', + expected: /abc/, + operator: 'match', + message: 'foobar', + generatedMessage: false + } + ); + const errorMessage = new RangeError('foobar'); + assert.throws( + () => assert.match('string', /abc/, errorMessage), + errorMessage + ); + assert.throws( + () => assert.match({ abc: 123 }, /abc/), + { + actual: { abc: 123 }, + expected: /abc/, + operator: 'match', + /* TODO(kt3k): Enable this assertion + message: 'The "string" argument must be of type string. ' + + 'Received type object ({ abc: 123 })', + generatedMessage: true + */ + } + ); + assert.match('I will pass', /pass$/); +} + +// Multiple assert.doesNotMatch() tests. +{ + assert.throws( + () => assert.doesNotMatch(/abc/, 'string'), + { + code: 'ERR_INVALID_ARG_TYPE', + /* TODO(kt3k): Enable this assertion + message: 'The "regexp" argument must be an instance of RegExp. ' + + "Received type string ('string')" + */ + } + ); + assert.throws( + () => assert.doesNotMatch('string', /string/), + { + actual: 'string', + expected: /string/, + operator: 'doesNotMatch', + /* TODO(kt3k): Enable this assertion + message: 'The input was expected to not match the regular expression ' + + "/string/. Input:\n\n'string'\n", + generatedMessage: true + */ + } + ); + assert.throws( + () => assert.doesNotMatch('string', /string/, 'foobar'), + { + actual: 'string', + expected: /string/, + operator: 'doesNotMatch', + message: 'foobar', + generatedMessage: false + } + ); + const errorMessage = new RangeError('foobar'); + assert.throws( + () => assert.doesNotMatch('string', /string/, errorMessage), + errorMessage + ); + assert.throws( + () => assert.doesNotMatch({ abc: 123 }, /abc/), + { + actual: { abc: 123 }, + expected: /abc/, + operator: 'doesNotMatch', + message: 'The "string" argument must be of type string. ' + + 'Received type object ({ abc: 123 })', + /* TODO(kt3k): Enable this assertion + generatedMessage: true + */ + } + ); + assert.doesNotMatch('I will pass', /different$/); +} + +{ + const tempColor = inspect.defaultOptions.colors; + assert.throws(() => { + inspect.defaultOptions.colors = true; + // Guarantee the position indicator is placed correctly. + assert.strictEqual(111554n, 11111115); + }, (err) => { + // TODO(kt3k): Enable this assertion + // assert.strictEqual(inspect(err).split('\n')[5], ' ^'); + inspect.defaultOptions.colors = tempColor; + return true; + }); +} diff --git a/cli/tests/node_compat/test/parallel/test-bad-unicode.js b/cli/tests/node_compat/test/parallel/test-bad-unicode.js new file mode 100644 index 00000000000000..416e1a351bff54 --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-bad-unicode.js @@ -0,0 +1,40 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. + +'use strict'; +require('../common'); +const assert = require('assert'); +let exception = null; + +try { + eval('"\\uc/ef"'); +} catch (e) { + exception = e; +} + +assert(exception instanceof SyntaxError); diff --git a/cli/tests/node_compat/test/parallel/test-btoa-atob.js b/cli/tests/node_compat/test/parallel/test-btoa-atob.js new file mode 100644 index 00000000000000..b17f4d2a666d40 --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-btoa-atob.js @@ -0,0 +1,46 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +'use strict'; + +require('../common'); + +const { strictEqual, throws } = require('assert'); +const buffer = require('buffer'); + +// Exported on the global object +strictEqual(globalThis.atob, buffer.atob); +strictEqual(globalThis.btoa, buffer.btoa); + +// Throws type error on no argument passed +throws(() => buffer.atob(), /TypeError/); +throws(() => buffer.btoa(), /TypeError/); + +strictEqual(atob(' '), ''); +strictEqual(atob(' Y\fW\tJ\njZ A=\r= '), 'abcd'); + +strictEqual(atob(null), '\x9Eée'); +strictEqual(atob(NaN), '5£'); +strictEqual(atob(Infinity), '"wâ\x9E+r'); +strictEqual(atob(true), '¶»\x9E'); +strictEqual(atob(1234), '×mø'); +strictEqual(atob([]), ''); +strictEqual(atob({ toString: () => '' }), ''); +strictEqual(atob({ [Symbol.toPrimitive]: () => '' }), ''); + +throws(() => atob(Symbol()), /TypeError/); +[ + undefined, false, () => {}, {}, [1], + 0, 1, 0n, 1n, -Infinity, + 'a', 'a\n\n\n', '\ra\r\r', ' a ', '\t\t\ta', 'a\f\f\f', '\ta\r \n\f', +].forEach((value) => + // See #2 - https://html.spec.whatwg.org/multipage/webappapis.html#dom-atob + throws(() => atob(value), { + constructor: DOMException, + name: 'InvalidCharacterError', + code: 5, + })); diff --git a/cli/tests/node_compat/test/parallel/test-buffer-alloc.js b/cli/tests/node_compat/test/parallel/test-buffer-alloc.js new file mode 100644 index 00000000000000..c93e80f638ebbc --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-buffer-alloc.js @@ -0,0 +1,1181 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 16.13.0 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +'use strict'; +const common = require('../common'); + +const assert = require('assert'); +const vm = require('vm'); + +const SlowBuffer = require('buffer').SlowBuffer; + +// Verify the maximum Uint8Array size. There is no concrete limit by spec. The +// internal limits should be updated if this fails. +assert.throws( + () => new Uint8Array(2 ** 32 + 1), + { message: 'Invalid typed array length: 4294967297' } +); + +const b = Buffer.allocUnsafe(1024); +assert.strictEqual(b.length, 1024); + +b[0] = -1; +assert.strictEqual(b[0], 255); + +for (let i = 0; i < 1024; i++) { + b[i] = i % 256; +} + +for (let i = 0; i < 1024; i++) { + assert.strictEqual(i % 256, b[i]); +} + +const c = Buffer.allocUnsafe(512); +assert.strictEqual(c.length, 512); + +const d = Buffer.from([]); +assert.strictEqual(d.length, 0); + +// Test offset properties +{ + const b = Buffer.alloc(128); + assert.strictEqual(b.length, 128); + assert.strictEqual(b.byteOffset, 0); + assert.strictEqual(b.offset, 0); +} + +// Test creating a Buffer from a Uint32Array +{ + const ui32 = new Uint32Array(4).fill(42); + const e = Buffer.from(ui32); + for (const [index, value] of e.entries()) { + assert.strictEqual(value, ui32[index]); + } +} +// Test creating a Buffer from a Uint32Array (old constructor) +{ + const ui32 = new Uint32Array(4).fill(42); + const e = Buffer(ui32); + for (const [key, value] of e.entries()) { + assert.deepStrictEqual(value, ui32[key]); + } +} + +// Test invalid encoding for Buffer.toString +assert.throws(() => b.toString('invalid'), + /Unknown encoding: invalid/); +// Invalid encoding for Buffer.write +assert.throws(() => b.write('test string', 0, 5, 'invalid'), + /Unknown encoding: invalid/); +// Unsupported arguments for Buffer.write +assert.throws(() => b.write('test', 'utf8', 0), + { code: 'ERR_INVALID_ARG_TYPE' }); + +// Try to create 0-length buffers. Should not throw. +Buffer.from(''); +Buffer.from('', 'ascii'); +Buffer.from('', 'latin1'); +Buffer.alloc(0); +Buffer.allocUnsafe(0); +new Buffer(''); +new Buffer('', 'ascii'); +new Buffer('', 'latin1'); +new Buffer('', 'binary'); +Buffer(0); + +const outOfRangeError = { + code: 'ERR_OUT_OF_RANGE', + name: 'RangeError' +}; + +// Try to write a 0-length string beyond the end of b +assert.throws(() => b.write('', 2048), outOfRangeError); + +// Throw when writing to negative offset +assert.throws(() => b.write('a', -1), outOfRangeError); + +// Throw when writing past bounds from the pool +assert.throws(() => b.write('a', 2048), outOfRangeError); + +// Throw when writing to negative offset +assert.throws(() => b.write('a', -1), outOfRangeError); + +// Try to copy 0 bytes worth of data into an empty buffer +b.copy(Buffer.alloc(0), 0, 0, 0); + +// Try to copy 0 bytes past the end of the target buffer +b.copy(Buffer.alloc(0), 1, 1, 1); +b.copy(Buffer.alloc(1), 1, 1, 1); + +// Try to copy 0 bytes from past the end of the source buffer +b.copy(Buffer.alloc(1), 0, 2048, 2048); + +// Testing for smart defaults and ability to pass string values as offset +{ + const writeTest = Buffer.from('abcdes'); + writeTest.write('n', 'ascii'); + assert.throws( + () => writeTest.write('o', '1', 'ascii'), + { code: 'ERR_INVALID_ARG_TYPE' } + ); + writeTest.write('o', 1, 'ascii'); + writeTest.write('d', 2, 'ascii'); + writeTest.write('e', 3, 'ascii'); + writeTest.write('j', 4, 'ascii'); + assert.strictEqual(writeTest.toString(), 'nodejs'); +} + +// Offset points to the end of the buffer and does not throw. +// (see https://github.com/nodejs/node/issues/8127). +Buffer.alloc(1).write('', 1, 0); + +// ASCII slice test +{ + const asciiString = 'hello world'; + + for (let i = 0; i < asciiString.length; i++) { + b[i] = asciiString.charCodeAt(i); + } + const asciiSlice = b.toString('ascii', 0, asciiString.length); + assert.strictEqual(asciiString, asciiSlice); +} + +{ + const asciiString = 'hello world'; + const offset = 100; + + assert.strictEqual(asciiString.length, b.write(asciiString, offset, 'ascii')); + const asciiSlice = b.toString('ascii', offset, offset + asciiString.length); + assert.strictEqual(asciiString, asciiSlice); +} + +{ + const asciiString = 'hello world'; + const offset = 100; + + const sliceA = b.slice(offset, offset + asciiString.length); + const sliceB = b.slice(offset, offset + asciiString.length); + for (let i = 0; i < asciiString.length; i++) { + assert.strictEqual(sliceA[i], sliceB[i]); + } +} + +// UTF-8 slice test +{ + const utf8String = '¡hέlló wôrld!'; + const offset = 100; + + b.write(utf8String, 0, Buffer.byteLength(utf8String), 'utf8'); + let utf8Slice = b.toString('utf8', 0, Buffer.byteLength(utf8String)); + assert.strictEqual(utf8String, utf8Slice); + + assert.strictEqual(Buffer.byteLength(utf8String), + b.write(utf8String, offset, 'utf8')); + utf8Slice = b.toString('utf8', offset, + offset + Buffer.byteLength(utf8String)); + assert.strictEqual(utf8String, utf8Slice); + + const sliceA = b.slice(offset, offset + Buffer.byteLength(utf8String)); + const sliceB = b.slice(offset, offset + Buffer.byteLength(utf8String)); + for (let i = 0; i < Buffer.byteLength(utf8String); i++) { + assert.strictEqual(sliceA[i], sliceB[i]); + } +} + +{ + const slice = b.slice(100, 150); + assert.strictEqual(slice.length, 50); + for (let i = 0; i < 50; i++) { + assert.strictEqual(b[100 + i], slice[i]); + } +} + +{ + // Make sure only top level parent propagates from allocPool + const b = Buffer.allocUnsafe(5); + const c = b.slice(0, 4); + const d = c.slice(0, 2); + assert.strictEqual(b.parent, c.parent); + assert.strictEqual(b.parent, d.parent); +} + +{ + // Also from a non-pooled instance + const b = Buffer.allocUnsafeSlow(5); + const c = b.slice(0, 4); + const d = c.slice(0, 2); + assert.strictEqual(c.parent, d.parent); +} + +{ + // Bug regression test + const testValue = '\u00F6\u65E5\u672C\u8A9E'; // ö日本語 + const buffer = Buffer.allocUnsafe(32); + const size = buffer.write(testValue, 0, 'utf8'); + const slice = buffer.toString('utf8', 0, size); + assert.strictEqual(slice, testValue); +} + +{ + // Test triple slice + const a = Buffer.allocUnsafe(8); + for (let i = 0; i < 8; i++) a[i] = i; + const b = a.slice(4, 8); + assert.strictEqual(b[0], 4); + assert.strictEqual(b[1], 5); + assert.strictEqual(b[2], 6); + assert.strictEqual(b[3], 7); + const c = b.slice(2, 4); + assert.strictEqual(c[0], 6); + assert.strictEqual(c[1], 7); +} + +{ + const d = Buffer.from([23, 42, 255]); + assert.strictEqual(d.length, 3); + assert.strictEqual(d[0], 23); + assert.strictEqual(d[1], 42); + assert.strictEqual(d[2], 255); + assert.deepStrictEqual(d, Buffer.from(d)); +} + +{ + // Test for proper UTF-8 Encoding + const e = Buffer.from('über'); + assert.deepStrictEqual(e, Buffer.from([195, 188, 98, 101, 114])); +} + +{ + // Test for proper ascii Encoding, length should be 4 + const f = Buffer.from('über', 'ascii'); + assert.deepStrictEqual(f, Buffer.from([252, 98, 101, 114])); +} + +['ucs2', 'ucs-2', 'utf16le', 'utf-16le'].forEach((encoding) => { + { + // Test for proper UTF16LE encoding, length should be 8 + const f = Buffer.from('über', encoding); + assert.deepStrictEqual(f, Buffer.from([252, 0, 98, 0, 101, 0, 114, 0])); + } + + { + // Length should be 12 + const f = Buffer.from('привет', encoding); + assert.deepStrictEqual( + f, Buffer.from([63, 4, 64, 4, 56, 4, 50, 4, 53, 4, 66, 4]) + ); + assert.strictEqual(f.toString(encoding), 'привет'); + } + + { + const f = Buffer.from([0, 0, 0, 0, 0]); + assert.strictEqual(f.length, 5); + const size = f.write('あいうえお', encoding); + assert.strictEqual(size, 4); + assert.deepStrictEqual(f, Buffer.from([0x42, 0x30, 0x44, 0x30, 0x00])); + } +}); + +{ + const f = Buffer.from('\uD83D\uDC4D', 'utf-16le'); // THUMBS UP SIGN (U+1F44D) + assert.strictEqual(f.length, 4); + assert.deepStrictEqual(f, Buffer.from('3DD84DDC', 'hex')); +} + +// Test construction from arrayish object +{ + const arrayIsh = { 0: 0, 1: 1, 2: 2, 3: 3, length: 4 }; + let g = Buffer.from(arrayIsh); + assert.deepStrictEqual(g, Buffer.from([0, 1, 2, 3])); + const strArrayIsh = { 0: '0', 1: '1', 2: '2', 3: '3', length: 4 }; + g = Buffer.from(strArrayIsh); + assert.deepStrictEqual(g, Buffer.from([0, 1, 2, 3])); +} + +// +// Test toString('base64') +// +assert.strictEqual((Buffer.from('Man')).toString('base64'), 'TWFu'); +assert.strictEqual((Buffer.from('Woman')).toString('base64'), 'V29tYW4='); + +// +// Test toString('base64url') +// +assert.strictEqual((Buffer.from('Man')).toString('base64url'), 'TWFu'); +assert.strictEqual((Buffer.from('Woman')).toString('base64url'), 'V29tYW4'); + +{ + // Test that regular and URL-safe base64 both work both ways + const expected = [0xff, 0xff, 0xbe, 0xff, 0xef, 0xbf, 0xfb, 0xef, 0xff]; + assert.deepStrictEqual(Buffer.from('//++/++/++//', 'base64'), + Buffer.from(expected)); + assert.deepStrictEqual(Buffer.from('__--_--_--__', 'base64'), + Buffer.from(expected)); + assert.deepStrictEqual(Buffer.from('//++/++/++//', 'base64url'), + Buffer.from(expected)); + assert.deepStrictEqual(Buffer.from('__--_--_--__', 'base64url'), + Buffer.from(expected)); +} + +const base64flavors = ['base64', 'base64url']; + +{ + // Test that regular and URL-safe base64 both work both ways with padding + const expected = [0xff, 0xff, 0xbe, 0xff, 0xef, 0xbf, 0xfb, 0xef, 0xff, 0xfb]; + assert.deepStrictEqual(Buffer.from('//++/++/++//+w==', 'base64'), + Buffer.from(expected)); + assert.deepStrictEqual(Buffer.from('//++/++/++//+w==', 'base64'), + Buffer.from(expected)); + assert.deepStrictEqual(Buffer.from('//++/++/++//+w==', 'base64url'), + Buffer.from(expected)); + assert.deepStrictEqual(Buffer.from('//++/++/++//+w==', 'base64url'), + Buffer.from(expected)); +} + +{ + // big example + const quote = 'Man is distinguished, not only by his reason, but by this ' + + 'singular passion from other animals, which is a lust ' + + 'of the mind, that by a perseverance of delight in the ' + + 'continued and indefatigable generation of knowledge, ' + + 'exceeds the short vehemence of any carnal pleasure.'; + const expected = 'TWFuIGlzIGRpc3Rpbmd1aXNoZWQsIG5vdCBvbmx5IGJ5IGhpcyByZWFzb' + + '24sIGJ1dCBieSB0aGlzIHNpbmd1bGFyIHBhc3Npb24gZnJvbSBvdGhlci' + + 'BhbmltYWxzLCB3aGljaCBpcyBhIGx1c3Qgb2YgdGhlIG1pbmQsIHRoYXQ' + + 'gYnkgYSBwZXJzZXZlcmFuY2Ugb2YgZGVsaWdodCBpbiB0aGUgY29udGlu' + + 'dWVkIGFuZCBpbmRlZmF0aWdhYmxlIGdlbmVyYXRpb24gb2Yga25vd2xlZ' + + 'GdlLCBleGNlZWRzIHRoZSBzaG9ydCB2ZWhlbWVuY2Ugb2YgYW55IGNhcm' + + '5hbCBwbGVhc3VyZS4='; + assert.strictEqual(Buffer.from(quote).toString('base64'), expected); + assert.strictEqual( + Buffer.from(quote).toString('base64url'), + expected.replaceAll('+', '-').replaceAll('/', '_').replaceAll('=', '') + ); + + base64flavors.forEach((encoding) => { + let b = Buffer.allocUnsafe(1024); + let bytesWritten = b.write(expected, 0, encoding); + assert.strictEqual(quote.length, bytesWritten); + assert.strictEqual(quote, b.toString('ascii', 0, quote.length)); + + // Check that the base64 decoder ignores whitespace + const expectedWhite = `${expected.slice(0, 60)} \n` + + `${expected.slice(60, 120)} \n` + + `${expected.slice(120, 180)} \n` + + `${expected.slice(180, 240)} \n` + + `${expected.slice(240, 300)}\n` + + `${expected.slice(300, 360)}\n`; + b = Buffer.allocUnsafe(1024); + bytesWritten = b.write(expectedWhite, 0, encoding); + assert.strictEqual(quote.length, bytesWritten); + assert.strictEqual(quote, b.toString('ascii', 0, quote.length)); + + // Check that the base64 decoder on the constructor works + // even in the presence of whitespace. + b = Buffer.from(expectedWhite, encoding); + assert.strictEqual(quote.length, b.length); + assert.strictEqual(quote, b.toString('ascii', 0, quote.length)); + + // Check that the base64 decoder ignores illegal chars + const expectedIllegal = expected.slice(0, 60) + ' \x80' + + expected.slice(60, 120) + ' \xff' + + expected.slice(120, 180) + ' \x00' + + expected.slice(180, 240) + ' \x98' + + expected.slice(240, 300) + '\x03' + + expected.slice(300, 360); + b = Buffer.from(expectedIllegal, encoding); + assert.strictEqual(quote.length, b.length); + assert.strictEqual(quote, b.toString('ascii', 0, quote.length)); + }); +} + +base64flavors.forEach((encoding) => { + assert.strictEqual(Buffer.from('', encoding).toString(), ''); + assert.strictEqual(Buffer.from('K', encoding).toString(), ''); + + // multiple-of-4 with padding + assert.strictEqual(Buffer.from('Kg==', encoding).toString(), '*'); + assert.strictEqual(Buffer.from('Kio=', encoding).toString(), '*'.repeat(2)); + assert.strictEqual(Buffer.from('Kioq', encoding).toString(), '*'.repeat(3)); + assert.strictEqual( + Buffer.from('KioqKg==', encoding).toString(), '*'.repeat(4)); + assert.strictEqual( + Buffer.from('KioqKio=', encoding).toString(), '*'.repeat(5)); + assert.strictEqual( + Buffer.from('KioqKioq', encoding).toString(), '*'.repeat(6)); + assert.strictEqual(Buffer.from('KioqKioqKg==', encoding).toString(), + '*'.repeat(7)); + assert.strictEqual(Buffer.from('KioqKioqKio=', encoding).toString(), + '*'.repeat(8)); + assert.strictEqual(Buffer.from('KioqKioqKioq', encoding).toString(), + '*'.repeat(9)); + assert.strictEqual(Buffer.from('KioqKioqKioqKg==', encoding).toString(), + '*'.repeat(10)); + assert.strictEqual(Buffer.from('KioqKioqKioqKio=', encoding).toString(), + '*'.repeat(11)); + assert.strictEqual(Buffer.from('KioqKioqKioqKioq', encoding).toString(), + '*'.repeat(12)); + assert.strictEqual(Buffer.from('KioqKioqKioqKioqKg==', encoding).toString(), + '*'.repeat(13)); + assert.strictEqual(Buffer.from('KioqKioqKioqKioqKio=', encoding).toString(), + '*'.repeat(14)); + assert.strictEqual(Buffer.from('KioqKioqKioqKioqKioq', encoding).toString(), + '*'.repeat(15)); + assert.strictEqual( + Buffer.from('KioqKioqKioqKioqKioqKg==', encoding).toString(), + '*'.repeat(16)); + assert.strictEqual( + Buffer.from('KioqKioqKioqKioqKioqKio=', encoding).toString(), + '*'.repeat(17)); + assert.strictEqual( + Buffer.from('KioqKioqKioqKioqKioqKioq', encoding).toString(), + '*'.repeat(18)); + assert.strictEqual(Buffer.from('KioqKioqKioqKioqKioqKioqKg==', + encoding).toString(), + '*'.repeat(19)); + assert.strictEqual(Buffer.from('KioqKioqKioqKioqKioqKioqKio=', + encoding).toString(), + '*'.repeat(20)); + + // No padding, not a multiple of 4 + assert.strictEqual(Buffer.from('Kg', encoding).toString(), '*'); + assert.strictEqual(Buffer.from('Kio', encoding).toString(), '*'.repeat(2)); + assert.strictEqual(Buffer.from('KioqKg', encoding).toString(), '*'.repeat(4)); + assert.strictEqual( + Buffer.from('KioqKio', encoding).toString(), '*'.repeat(5)); + assert.strictEqual(Buffer.from('KioqKioqKg', encoding).toString(), + '*'.repeat(7)); + assert.strictEqual(Buffer.from('KioqKioqKio', encoding).toString(), + '*'.repeat(8)); + assert.strictEqual(Buffer.from('KioqKioqKioqKg', encoding).toString(), + '*'.repeat(10)); + assert.strictEqual(Buffer.from('KioqKioqKioqKio', encoding).toString(), + '*'.repeat(11)); + assert.strictEqual(Buffer.from('KioqKioqKioqKioqKg', encoding).toString(), + '*'.repeat(13)); + assert.strictEqual(Buffer.from('KioqKioqKioqKioqKio', encoding).toString(), + '*'.repeat(14)); + assert.strictEqual(Buffer.from('KioqKioqKioqKioqKioqKg', encoding).toString(), + '*'.repeat(16)); + assert.strictEqual( + Buffer.from('KioqKioqKioqKioqKioqKio', encoding).toString(), + '*'.repeat(17)); + assert.strictEqual( + Buffer.from('KioqKioqKioqKioqKioqKioqKg', encoding).toString(), + '*'.repeat(19)); + assert.strictEqual( + Buffer.from('KioqKioqKioqKioqKioqKioqKio', encoding).toString(), + '*'.repeat(20)); +}); + +// Handle padding graciously, multiple-of-4 or not +assert.strictEqual( + Buffer.from('72INjkR5fchcxk9+VgdGPFJDxUBFR5/rMFsghgxADiw==', 'base64').length, + 32 +); +assert.strictEqual( + Buffer.from('72INjkR5fchcxk9-VgdGPFJDxUBFR5_rMFsghgxADiw==', 'base64url') + .length, + 32 +); +assert.strictEqual( + Buffer.from('72INjkR5fchcxk9+VgdGPFJDxUBFR5/rMFsghgxADiw=', 'base64').length, + 32 +); +assert.strictEqual( + Buffer.from('72INjkR5fchcxk9-VgdGPFJDxUBFR5_rMFsghgxADiw=', 'base64url') + .length, + 32 +); +assert.strictEqual( + Buffer.from('72INjkR5fchcxk9+VgdGPFJDxUBFR5/rMFsghgxADiw', 'base64').length, + 32 +); +assert.strictEqual( + Buffer.from('72INjkR5fchcxk9-VgdGPFJDxUBFR5_rMFsghgxADiw', 'base64url') + .length, + 32 +); +assert.strictEqual( + Buffer.from('w69jACy6BgZmaFvv96HG6MYksWytuZu3T1FvGnulPg==', 'base64').length, + 31 +); +assert.strictEqual( + Buffer.from('w69jACy6BgZmaFvv96HG6MYksWytuZu3T1FvGnulPg==', 'base64url') + .length, + 31 +); +assert.strictEqual( + Buffer.from('w69jACy6BgZmaFvv96HG6MYksWytuZu3T1FvGnulPg=', 'base64').length, + 31 +); +assert.strictEqual( + Buffer.from('w69jACy6BgZmaFvv96HG6MYksWytuZu3T1FvGnulPg=', 'base64url') + .length, + 31 +); +assert.strictEqual( + Buffer.from('w69jACy6BgZmaFvv96HG6MYksWytuZu3T1FvGnulPg', 'base64').length, + 31 +); +assert.strictEqual( + Buffer.from('w69jACy6BgZmaFvv96HG6MYksWytuZu3T1FvGnulPg', 'base64url').length, + 31 +); + +{ +// This string encodes single '.' character in UTF-16 + const dot = Buffer.from('//4uAA==', 'base64'); + assert.strictEqual(dot[0], 0xff); + assert.strictEqual(dot[1], 0xfe); + assert.strictEqual(dot[2], 0x2e); + assert.strictEqual(dot[3], 0x00); + assert.strictEqual(dot.toString('base64'), '//4uAA=='); +} + +{ +// This string encodes single '.' character in UTF-16 + const dot = Buffer.from('//4uAA', 'base64url'); + assert.strictEqual(dot[0], 0xff); + assert.strictEqual(dot[1], 0xfe); + assert.strictEqual(dot[2], 0x2e); + assert.strictEqual(dot[3], 0x00); + assert.strictEqual(dot.toString('base64url'), '__4uAA'); +} + +{ + // Writing base64 at a position > 0 should not mangle the result. + // + // https://github.com/joyent/node/issues/402 + const segments = ['TWFkbmVzcz8h', 'IFRoaXM=', 'IGlz', 'IG5vZGUuanMh']; + const b = Buffer.allocUnsafe(64); + let pos = 0; + + for (let i = 0; i < segments.length; ++i) { + pos += b.write(segments[i], pos, 'base64'); + } + assert.strictEqual(b.toString('latin1', 0, pos), + 'Madness?! This is node.js!'); +} + +{ + // Writing base64url at a position > 0 should not mangle the result. + // + // https://github.com/joyent/node/issues/402 + const segments = ['TWFkbmVzcz8h', 'IFRoaXM', 'IGlz', 'IG5vZGUuanMh']; + const b = Buffer.allocUnsafe(64); + let pos = 0; + + for (let i = 0; i < segments.length; ++i) { + pos += b.write(segments[i], pos, 'base64url'); + } + assert.strictEqual(b.toString('latin1', 0, pos), + 'Madness?! This is node.js!'); +} + +// Regression test for https://github.com/nodejs/node/issues/3496. +assert.strictEqual(Buffer.from('=bad'.repeat(1e4), 'base64').length, 0); + +// Regression test for https://github.com/nodejs/node/issues/11987. +assert.deepStrictEqual(Buffer.from('w0 ', 'base64'), + Buffer.from('w0', 'base64')); + +// Regression test for https://github.com/nodejs/node/issues/13657. +assert.deepStrictEqual(Buffer.from(' YWJvcnVtLg', 'base64'), + Buffer.from('YWJvcnVtLg', 'base64')); + +{ + // Creating buffers larger than pool size. + const l = Buffer.poolSize + 5; + const s = 'h'.repeat(l); + const b = Buffer.from(s); + + for (let i = 0; i < l; i++) { + assert.strictEqual(b[i], 'h'.charCodeAt(0)); + } + + const sb = b.toString(); + assert.strictEqual(sb.length, s.length); + assert.strictEqual(sb, s); +} + +{ + // test hex toString + const hexb = Buffer.allocUnsafe(256); + for (let i = 0; i < 256; i++) { + hexb[i] = i; + } + const hexStr = hexb.toString('hex'); + assert.strictEqual(hexStr, + '000102030405060708090a0b0c0d0e0f' + + '101112131415161718191a1b1c1d1e1f' + + '202122232425262728292a2b2c2d2e2f' + + '303132333435363738393a3b3c3d3e3f' + + '404142434445464748494a4b4c4d4e4f' + + '505152535455565758595a5b5c5d5e5f' + + '606162636465666768696a6b6c6d6e6f' + + '707172737475767778797a7b7c7d7e7f' + + '808182838485868788898a8b8c8d8e8f' + + '909192939495969798999a9b9c9d9e9f' + + 'a0a1a2a3a4a5a6a7a8a9aaabacadaeaf' + + 'b0b1b2b3b4b5b6b7b8b9babbbcbdbebf' + + 'c0c1c2c3c4c5c6c7c8c9cacbcccdcecf' + + 'd0d1d2d3d4d5d6d7d8d9dadbdcdddedf' + + 'e0e1e2e3e4e5e6e7e8e9eaebecedeeef' + + 'f0f1f2f3f4f5f6f7f8f9fafbfcfdfeff'); + + const hexb2 = Buffer.from(hexStr, 'hex'); + for (let i = 0; i < 256; i++) { + assert.strictEqual(hexb2[i], hexb[i]); + } +} + +// Test single hex character is discarded. +assert.strictEqual(Buffer.from('A', 'hex').length, 0); + +// Test that if a trailing character is discarded, rest of string is processed. +assert.deepStrictEqual(Buffer.from('Abx', 'hex'), Buffer.from('Ab', 'hex')); + +// Test single base64 char encodes as 0. +assert.strictEqual(Buffer.from('A', 'base64').length, 0); + + +{ + // Test an invalid slice end. + const b = Buffer.from([1, 2, 3, 4, 5]); + const b2 = b.toString('hex', 1, 10000); + const b3 = b.toString('hex', 1, 5); + const b4 = b.toString('hex', 1); + assert.strictEqual(b2, b3); + assert.strictEqual(b2, b4); +} + +function buildBuffer(data) { + if (Array.isArray(data)) { + const buffer = Buffer.allocUnsafe(data.length); + data.forEach((v, k) => buffer[k] = v); + return buffer; + } + return null; +} + +const x = buildBuffer([0x81, 0xa3, 0x66, 0x6f, 0x6f, 0xa3, 0x62, 0x61, 0x72]); + +assert.strictEqual(x.inspect(), ''); + +{ + const z = x.slice(4); + assert.strictEqual(z.length, 5); + assert.strictEqual(z[0], 0x6f); + assert.strictEqual(z[1], 0xa3); + assert.strictEqual(z[2], 0x62); + assert.strictEqual(z[3], 0x61); + assert.strictEqual(z[4], 0x72); +} + +{ + const z = x.slice(0); + assert.strictEqual(z.length, x.length); +} + +{ + const z = x.slice(0, 4); + assert.strictEqual(z.length, 4); + assert.strictEqual(z[0], 0x81); + assert.strictEqual(z[1], 0xa3); +} + +{ + const z = x.slice(0, 9); + assert.strictEqual(z.length, 9); +} + +{ + const z = x.slice(1, 4); + assert.strictEqual(z.length, 3); + assert.strictEqual(z[0], 0xa3); +} + +{ + const z = x.slice(2, 4); + assert.strictEqual(z.length, 2); + assert.strictEqual(z[0], 0x66); + assert.strictEqual(z[1], 0x6f); +} + +['ucs2', 'ucs-2', 'utf16le', 'utf-16le'].forEach((encoding) => { + const b = Buffer.allocUnsafe(10); + b.write('あいうえお', encoding); + assert.strictEqual(b.toString(encoding), 'あいうえお'); +}); + +['ucs2', 'ucs-2', 'utf16le', 'utf-16le'].forEach((encoding) => { + const b = Buffer.allocUnsafe(11); + b.write('あいうえお', 1, encoding); + assert.strictEqual(b.toString(encoding, 1), 'あいうえお'); +}); + +{ + // latin1 encoding should write only one byte per character. + const b = Buffer.from([0xde, 0xad, 0xbe, 0xef]); + let s = String.fromCharCode(0xffff); + b.write(s, 0, 'latin1'); + assert.strictEqual(b[0], 0xff); + assert.strictEqual(b[1], 0xad); + assert.strictEqual(b[2], 0xbe); + assert.strictEqual(b[3], 0xef); + s = String.fromCharCode(0xaaee); + b.write(s, 0, 'latin1'); + assert.strictEqual(b[0], 0xee); + assert.strictEqual(b[1], 0xad); + assert.strictEqual(b[2], 0xbe); + assert.strictEqual(b[3], 0xef); +} + +{ + // Binary encoding should write only one byte per character. + const b = Buffer.from([0xde, 0xad, 0xbe, 0xef]); + let s = String.fromCharCode(0xffff); + b.write(s, 0, 'latin1'); + assert.strictEqual(b[0], 0xff); + assert.strictEqual(b[1], 0xad); + assert.strictEqual(b[2], 0xbe); + assert.strictEqual(b[3], 0xef); + s = String.fromCharCode(0xaaee); + b.write(s, 0, 'latin1'); + assert.strictEqual(b[0], 0xee); + assert.strictEqual(b[1], 0xad); + assert.strictEqual(b[2], 0xbe); + assert.strictEqual(b[3], 0xef); +} + +{ + // https://github.com/nodejs/node-v0.x-archive/pull/1210 + // Test UTF-8 string includes null character + let buf = Buffer.from('\0'); + assert.strictEqual(buf.length, 1); + buf = Buffer.from('\0\0'); + assert.strictEqual(buf.length, 2); +} + +{ + const buf = Buffer.allocUnsafe(2); + assert.strictEqual(buf.write(''), 0); // 0bytes + assert.strictEqual(buf.write('\0'), 1); // 1byte (v8 adds null terminator) + assert.strictEqual(buf.write('a\0'), 2); // 1byte * 2 + assert.strictEqual(buf.write('あ'), 0); // 3bytes + assert.strictEqual(buf.write('\0あ'), 1); // 1byte + 3bytes + assert.strictEqual(buf.write('\0\0あ'), 2); // 1byte * 2 + 3bytes +} + +{ + const buf = Buffer.allocUnsafe(10); + assert.strictEqual(buf.write('あいう'), 9); // 3bytes * 3 (v8 adds null term.) + assert.strictEqual(buf.write('あいう\0'), 10); // 3bytes * 3 + 1byte +} + +{ + // https://github.com/nodejs/node-v0.x-archive/issues/243 + // Test write() with maxLength + const buf = Buffer.allocUnsafe(4); + buf.fill(0xFF); + assert.strictEqual(buf.write('abcd', 1, 2, 'utf8'), 2); + assert.strictEqual(buf[0], 0xFF); + assert.strictEqual(buf[1], 0x61); + assert.strictEqual(buf[2], 0x62); + assert.strictEqual(buf[3], 0xFF); + + buf.fill(0xFF); + assert.strictEqual(buf.write('abcd', 1, 4), 3); + assert.strictEqual(buf[0], 0xFF); + assert.strictEqual(buf[1], 0x61); + assert.strictEqual(buf[2], 0x62); + assert.strictEqual(buf[3], 0x63); + + buf.fill(0xFF); + assert.strictEqual(buf.write('abcd', 1, 2, 'utf8'), 2); + assert.strictEqual(buf[0], 0xFF); + assert.strictEqual(buf[1], 0x61); + assert.strictEqual(buf[2], 0x62); + assert.strictEqual(buf[3], 0xFF); + + buf.fill(0xFF); + assert.strictEqual(buf.write('abcdef', 1, 2, 'hex'), 2); + assert.strictEqual(buf[0], 0xFF); + assert.strictEqual(buf[1], 0xAB); + assert.strictEqual(buf[2], 0xCD); + assert.strictEqual(buf[3], 0xFF); + + ['ucs2', 'ucs-2', 'utf16le', 'utf-16le'].forEach((encoding) => { + buf.fill(0xFF); + assert.strictEqual(buf.write('abcd', 0, 2, encoding), 2); + assert.strictEqual(buf[0], 0x61); + assert.strictEqual(buf[1], 0x00); + assert.strictEqual(buf[2], 0xFF); + assert.strictEqual(buf[3], 0xFF); + }); +} + +{ + // Test offset returns are correct + const b = Buffer.allocUnsafe(16); + assert.strictEqual(b.writeUInt32LE(0, 0), 4); + assert.strictEqual(b.writeUInt16LE(0, 4), 6); + assert.strictEqual(b.writeUInt8(0, 6), 7); + assert.strictEqual(b.writeInt8(0, 7), 8); + assert.strictEqual(b.writeDoubleLE(0, 8), 16); +} + +{ + // Test unmatched surrogates not producing invalid utf8 output + // ef bf bd = utf-8 representation of unicode replacement character + // see https://codereview.chromium.org/121173009/ + const buf = Buffer.from('ab\ud800cd', 'utf8'); + assert.strictEqual(buf[0], 0x61); + assert.strictEqual(buf[1], 0x62); + assert.strictEqual(buf[2], 0xef); + assert.strictEqual(buf[3], 0xbf); + assert.strictEqual(buf[4], 0xbd); + assert.strictEqual(buf[5], 0x63); + assert.strictEqual(buf[6], 0x64); +} + +{ + // Test for buffer overrun + const buf = Buffer.from([0, 0, 0, 0, 0]); // length: 5 + const sub = buf.slice(0, 4); // length: 4 + assert.strictEqual(sub.write('12345', 'latin1'), 4); + assert.strictEqual(buf[4], 0); + assert.strictEqual(sub.write('12345', 'binary'), 4); + assert.strictEqual(buf[4], 0); +} + +{ + // Test alloc with fill option + const buf = Buffer.alloc(5, '800A', 'hex'); + assert.strictEqual(buf[0], 128); + assert.strictEqual(buf[1], 10); + assert.strictEqual(buf[2], 128); + assert.strictEqual(buf[3], 10); + assert.strictEqual(buf[4], 128); +} + + +// Check for fractional length args, junk length args, etc. +// https://github.com/joyent/node/issues/1758 + +// Call .fill() first, stops valgrind warning about uninitialized memory reads. +Buffer.allocUnsafe(3.3).fill().toString(); +// Throws bad argument error in commit 43cb4ec +Buffer.alloc(3.3).fill().toString(); +assert.strictEqual(Buffer.allocUnsafe(3.3).length, 3); +assert.strictEqual(Buffer.from({ length: 3.3 }).length, 3); +assert.strictEqual(Buffer.from({ length: 'BAM' }).length, 0); + +// Make sure that strings are not coerced to numbers. +assert.strictEqual(Buffer.from('99').length, 2); +assert.strictEqual(Buffer.from('13.37').length, 5); + +// Ensure that the length argument is respected. +['ascii', 'utf8', 'hex', 'base64', 'latin1', 'binary'].forEach((enc) => { + assert.strictEqual(Buffer.allocUnsafe(1).write('aaaaaa', 0, 1, enc), 1); +}); + +{ + // Regression test, guard against buffer overrun in the base64 decoder. + const a = Buffer.allocUnsafe(3); + const b = Buffer.from('xxx'); + a.write('aaaaaaaa', 'base64'); + assert.strictEqual(b.toString(), 'xxx'); +} + +// issue GH-3416 +Buffer.from(Buffer.allocUnsafe(0), 0, 0); + +// issue GH-5587 +assert.throws( + () => Buffer.alloc(8).writeFloatLE(0, 5), + outOfRangeError +); +assert.throws( + () => Buffer.alloc(16).writeDoubleLE(0, 9), + outOfRangeError +); + +// Attempt to overflow buffers, similar to previous bug in array buffers +assert.throws( + () => Buffer.allocUnsafe(8).writeFloatLE(0.0, 0xffffffff), + outOfRangeError +); +assert.throws( + () => Buffer.allocUnsafe(8).writeFloatLE(0.0, 0xffffffff), + outOfRangeError +); + +// Ensure negative values can't get past offset +assert.throws( + () => Buffer.allocUnsafe(8).writeFloatLE(0.0, -1), + outOfRangeError +); +assert.throws( + () => Buffer.allocUnsafe(8).writeFloatLE(0.0, -1), + outOfRangeError +); + +// Test for common write(U)IntLE/BE +{ + let buf = Buffer.allocUnsafe(3); + buf.writeUIntLE(0x123456, 0, 3); + assert.deepStrictEqual(buf.toJSON().data, [0x56, 0x34, 0x12]); + assert.strictEqual(buf.readUIntLE(0, 3), 0x123456); + + buf.fill(0xFF); + buf.writeUIntBE(0x123456, 0, 3); + assert.deepStrictEqual(buf.toJSON().data, [0x12, 0x34, 0x56]); + assert.strictEqual(buf.readUIntBE(0, 3), 0x123456); + + buf.fill(0xFF); + buf.writeIntLE(0x123456, 0, 3); + assert.deepStrictEqual(buf.toJSON().data, [0x56, 0x34, 0x12]); + assert.strictEqual(buf.readIntLE(0, 3), 0x123456); + + buf.fill(0xFF); + buf.writeIntBE(0x123456, 0, 3); + assert.deepStrictEqual(buf.toJSON().data, [0x12, 0x34, 0x56]); + assert.strictEqual(buf.readIntBE(0, 3), 0x123456); + + buf.fill(0xFF); + buf.writeIntLE(-0x123456, 0, 3); + assert.deepStrictEqual(buf.toJSON().data, [0xaa, 0xcb, 0xed]); + assert.strictEqual(buf.readIntLE(0, 3), -0x123456); + + buf.fill(0xFF); + buf.writeIntBE(-0x123456, 0, 3); + assert.deepStrictEqual(buf.toJSON().data, [0xed, 0xcb, 0xaa]); + assert.strictEqual(buf.readIntBE(0, 3), -0x123456); + + buf.fill(0xFF); + buf.writeIntLE(-0x123400, 0, 3); + assert.deepStrictEqual(buf.toJSON().data, [0x00, 0xcc, 0xed]); + assert.strictEqual(buf.readIntLE(0, 3), -0x123400); + + buf.fill(0xFF); + buf.writeIntBE(-0x123400, 0, 3); + assert.deepStrictEqual(buf.toJSON().data, [0xed, 0xcc, 0x00]); + assert.strictEqual(buf.readIntBE(0, 3), -0x123400); + + buf.fill(0xFF); + buf.writeIntLE(-0x120000, 0, 3); + assert.deepStrictEqual(buf.toJSON().data, [0x00, 0x00, 0xee]); + assert.strictEqual(buf.readIntLE(0, 3), -0x120000); + + buf.fill(0xFF); + buf.writeIntBE(-0x120000, 0, 3); + assert.deepStrictEqual(buf.toJSON().data, [0xee, 0x00, 0x00]); + assert.strictEqual(buf.readIntBE(0, 3), -0x120000); + + buf = Buffer.allocUnsafe(5); + buf.writeUIntLE(0x1234567890, 0, 5); + assert.deepStrictEqual(buf.toJSON().data, [0x90, 0x78, 0x56, 0x34, 0x12]); + assert.strictEqual(buf.readUIntLE(0, 5), 0x1234567890); + + buf.fill(0xFF); + buf.writeUIntBE(0x1234567890, 0, 5); + assert.deepStrictEqual(buf.toJSON().data, [0x12, 0x34, 0x56, 0x78, 0x90]); + assert.strictEqual(buf.readUIntBE(0, 5), 0x1234567890); + + buf.fill(0xFF); + buf.writeIntLE(0x1234567890, 0, 5); + assert.deepStrictEqual(buf.toJSON().data, [0x90, 0x78, 0x56, 0x34, 0x12]); + assert.strictEqual(buf.readIntLE(0, 5), 0x1234567890); + + buf.fill(0xFF); + buf.writeIntBE(0x1234567890, 0, 5); + assert.deepStrictEqual(buf.toJSON().data, [0x12, 0x34, 0x56, 0x78, 0x90]); + assert.strictEqual(buf.readIntBE(0, 5), 0x1234567890); + + buf.fill(0xFF); + buf.writeIntLE(-0x1234567890, 0, 5); + assert.deepStrictEqual(buf.toJSON().data, [0x70, 0x87, 0xa9, 0xcb, 0xed]); + assert.strictEqual(buf.readIntLE(0, 5), -0x1234567890); + + buf.fill(0xFF); + buf.writeIntBE(-0x1234567890, 0, 5); + assert.deepStrictEqual(buf.toJSON().data, [0xed, 0xcb, 0xa9, 0x87, 0x70]); + assert.strictEqual(buf.readIntBE(0, 5), -0x1234567890); + + buf.fill(0xFF); + buf.writeIntLE(-0x0012000000, 0, 5); + assert.deepStrictEqual(buf.toJSON().data, [0x00, 0x00, 0x00, 0xee, 0xff]); + assert.strictEqual(buf.readIntLE(0, 5), -0x0012000000); + + buf.fill(0xFF); + buf.writeIntBE(-0x0012000000, 0, 5); + assert.deepStrictEqual(buf.toJSON().data, [0xff, 0xee, 0x00, 0x00, 0x00]); + assert.strictEqual(buf.readIntBE(0, 5), -0x0012000000); +} + +// Regression test for https://github.com/nodejs/node-v0.x-archive/issues/5482: +// should throw but not assert in C++ land. +assert.throws( + () => Buffer.from('', 'buffer'), + { + code: 'ERR_UNKNOWN_ENCODING', + name: 'TypeError', + message: 'Unknown encoding: buffer' + } +); + +// Regression test for https://github.com/nodejs/node-v0.x-archive/issues/6111. +// Constructing a buffer from another buffer should a) work, and b) not corrupt +// the source buffer. +{ + const a = [...Array(128).keys()]; // [0, 1, 2, 3, ... 126, 127] + const b = Buffer.from(a); + const c = Buffer.from(b); + assert.strictEqual(b.length, a.length); + assert.strictEqual(c.length, a.length); + for (let i = 0, k = a.length; i < k; ++i) { + assert.strictEqual(a[i], i); + assert.strictEqual(b[i], i); + assert.strictEqual(c[i], i); + } +} + +if (common.hasCrypto) { // eslint-disable-line node-core/crypto-check + // Test truncation after decode + const crypto = require('crypto'); + + const b1 = Buffer.from('YW55=======', 'base64'); + const b2 = Buffer.from('YW55', 'base64'); + + assert.strictEqual( + crypto.createHash('sha1').update(b1).digest('hex'), + crypto.createHash('sha1').update(b2).digest('hex') + ); +} else { + common.printSkipMessage('missing crypto'); +} + +const ps = Buffer.poolSize; +Buffer.poolSize = 0; +assert(Buffer.allocUnsafe(1).parent instanceof ArrayBuffer); +Buffer.poolSize = ps; + +assert.throws( + () => Buffer.allocUnsafe(10).copy(), + { + code: 'ERR_INVALID_ARG_TYPE', + name: 'TypeError', + message: 'The "target" argument must be an instance of Buffer or ' + + 'Uint8Array. Received undefined' + }); + +assert.throws(() => Buffer.from(), { + name: 'TypeError', + message: 'The first argument must be of type string or an instance of ' + + 'Buffer, ArrayBuffer, or Array or an Array-like Object. Received undefined' +}); +assert.throws(() => Buffer.from(null), { + name: 'TypeError', + message: 'The first argument must be of type string or an instance of ' + + 'Buffer, ArrayBuffer, or Array or an Array-like Object. Received null' +}); + +// Test prototype getters don't throw +assert.strictEqual(Buffer.prototype.parent, undefined); +assert.strictEqual(Buffer.prototype.offset, undefined); +assert.strictEqual(SlowBuffer.prototype.parent, undefined); +assert.strictEqual(SlowBuffer.prototype.offset, undefined); + + +{ + // Test that large negative Buffer length inputs don't affect the pool offset. + // Use the fromArrayLike() variant here because it's more lenient + // about its input and passes the length directly to allocate(). + assert.deepStrictEqual(Buffer.from({ length: -Buffer.poolSize }), + Buffer.from('')); + assert.deepStrictEqual(Buffer.from({ length: -100 }), + Buffer.from('')); + + // Check pool offset after that by trying to write string into the pool. + Buffer.from('abc'); +} + + +// Test that ParseArrayIndex handles full uint32 +{ + const errMsg = common.expectsError({ + code: 'ERR_BUFFER_OUT_OF_BOUNDS', + name: 'RangeError', + message: '"offset" is outside of buffer bounds' + }); + assert.throws(() => Buffer.from(new ArrayBuffer(0), -1 >>> 0), errMsg); +} + +// ParseArrayIndex() should reject values that don't fit in a 32 bits size_t. +assert.throws(() => { + const a = Buffer.alloc(1); + const b = Buffer.alloc(1); + a.copy(b, 0, 0x100000000, 0x100000001); +}, outOfRangeError); + +// Unpooled buffer (replaces SlowBuffer) +{ + const ubuf = Buffer.allocUnsafeSlow(10); + assert(ubuf); + assert(ubuf.buffer); + assert.strictEqual(ubuf.buffer.byteLength, 10); +} + +// Regression test to verify that an empty ArrayBuffer does not throw. +Buffer.from(new ArrayBuffer()); + +// TODO(kt3k): Enable this test when vm.runInNewContext is available +// Test that ArrayBuffer from a different context is detected correctly. +// const arrayBuf = vm.runInNewContext('new ArrayBuffer()'); +// Buffer.from(arrayBuf); +// Buffer.from({ buffer: arrayBuf }); + +assert.throws(() => Buffer.alloc({ valueOf: () => 1 }), + /"size" argument must be of type number/); +assert.throws(() => Buffer.alloc({ valueOf: () => -1 }), + /"size" argument must be of type number/); + +assert.strictEqual(Buffer.prototype.toLocaleString, Buffer.prototype.toString); +{ + const buf = Buffer.from('test'); + assert.strictEqual(buf.toLocaleString(), buf.toString()); +} + +assert.throws(() => { + Buffer.alloc(0x1000, 'This is not correctly encoded', 'hex'); +}, { + code: 'ERR_INVALID_ARG_VALUE', + name: 'TypeError' +}); + +assert.throws(() => { + Buffer.alloc(0x1000, 'c', 'hex'); +}, { + code: 'ERR_INVALID_ARG_VALUE', + name: 'TypeError' +}); + +assert.throws(() => { + Buffer.alloc(1, Buffer.alloc(0)); +}, { + code: 'ERR_INVALID_ARG_VALUE', + name: 'TypeError' +}); + +assert.throws(() => { + Buffer.alloc(40, 'x', 20); +}, { + code: 'ERR_INVALID_ARG_TYPE', + name: 'TypeError' +}); diff --git a/cli/tests/node_compat/test/parallel/test-buffer-arraybuffer.js b/cli/tests/node_compat/test/parallel/test-buffer-arraybuffer.js new file mode 100644 index 00000000000000..9f515736ec4e2c --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-buffer-arraybuffer.js @@ -0,0 +1,162 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 16.13.0 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +'use strict'; + +require('../common'); +const assert = require('assert'); + +const LENGTH = 16; + +const ab = new ArrayBuffer(LENGTH); +const dv = new DataView(ab); +const ui = new Uint8Array(ab); +const buf = Buffer.from(ab); + + +assert.ok(buf instanceof Buffer); +assert.strictEqual(buf.parent, buf.buffer); +assert.strictEqual(buf.buffer, ab); +assert.strictEqual(buf.length, ab.byteLength); + + +buf.fill(0xC); +for (let i = 0; i < LENGTH; i++) { + assert.strictEqual(ui[i], 0xC); + ui[i] = 0xF; + assert.strictEqual(buf[i], 0xF); +} + +buf.writeUInt32LE(0xF00, 0); +buf.writeUInt32BE(0xB47, 4); +buf.writeDoubleLE(3.1415, 8); + +assert.strictEqual(dv.getUint32(0, true), 0xF00); +assert.strictEqual(dv.getUint32(4), 0xB47); +assert.strictEqual(dv.getFloat64(8, true), 3.1415); + + +// Now test protecting users from doing stupid things + +// TODO(Soremwar) +// There is an inconsistency on feross implementation on how buffers are checked +// Enable once it's sorted out +// assert.throws(function() { +// function AB() { } +// Object.setPrototypeOf(AB, ArrayBuffer); +// Object.setPrototypeOf(AB.prototype, ArrayBuffer.prototype); +// Buffer.from(new AB()); +// }, { +// code: 'ERR_INVALID_ARG_TYPE', +// name: 'TypeError', +// message: 'The first argument must be of type string or an instance of ' + +// 'Buffer, ArrayBuffer, or Array or an Array-like Object. Received ' + +// 'an instance of AB' +// }); + +// Test the byteOffset and length arguments +{ + const ab = new Uint8Array(5); + ab[0] = 1; + ab[1] = 2; + ab[2] = 3; + ab[3] = 4; + ab[4] = 5; + const buf = Buffer.from(ab.buffer, 1, 3); + assert.strictEqual(buf.length, 3); + assert.strictEqual(buf[0], 2); + assert.strictEqual(buf[1], 3); + assert.strictEqual(buf[2], 4); + buf[0] = 9; + assert.strictEqual(ab[1], 9); + + assert.throws(() => Buffer.from(ab.buffer, 6), { + code: 'ERR_BUFFER_OUT_OF_BOUNDS', + name: 'RangeError', + message: '"offset" is outside of buffer bounds' + }); + assert.throws(() => Buffer.from(ab.buffer, 3, 6), { + code: 'ERR_BUFFER_OUT_OF_BOUNDS', + name: 'RangeError', + message: '"length" is outside of buffer bounds' + }); +} + +// Test the deprecated Buffer() version also +{ + const ab = new Uint8Array(5); + ab[0] = 1; + ab[1] = 2; + ab[2] = 3; + ab[3] = 4; + ab[4] = 5; + const buf = Buffer(ab.buffer, 1, 3); + assert.strictEqual(buf.length, 3); + assert.strictEqual(buf[0], 2); + assert.strictEqual(buf[1], 3); + assert.strictEqual(buf[2], 4); + buf[0] = 9; + assert.strictEqual(ab[1], 9); + + assert.throws(() => Buffer(ab.buffer, 6), { + code: 'ERR_BUFFER_OUT_OF_BOUNDS', + name: 'RangeError', + message: '"offset" is outside of buffer bounds' + }); + assert.throws(() => Buffer(ab.buffer, 3, 6), { + code: 'ERR_BUFFER_OUT_OF_BOUNDS', + name: 'RangeError', + message: '"length" is outside of buffer bounds' + }); +} + +{ + // If byteOffset is not numeric, it defaults to 0. + const ab = new ArrayBuffer(10); + const expected = Buffer.from(ab, 0); + assert.deepStrictEqual(Buffer.from(ab, 'fhqwhgads'), expected); + assert.deepStrictEqual(Buffer.from(ab, NaN), expected); + assert.deepStrictEqual(Buffer.from(ab, {}), expected); + assert.deepStrictEqual(Buffer.from(ab, []), expected); + + // If byteOffset can be converted to a number, it will be. + assert.deepStrictEqual(Buffer.from(ab, [1]), Buffer.from(ab, 1)); + + // If byteOffset is Infinity, throw. + assert.throws(() => { + Buffer.from(ab, Infinity); + }, { + code: 'ERR_BUFFER_OUT_OF_BOUNDS', + name: 'RangeError', + message: '"offset" is outside of buffer bounds' + }); +} + +{ + // If length is not numeric, it defaults to 0. + const ab = new ArrayBuffer(10); + const expected = Buffer.from(ab, 0, 0); + assert.deepStrictEqual(Buffer.from(ab, 0, 'fhqwhgads'), expected); + assert.deepStrictEqual(Buffer.from(ab, 0, NaN), expected); + assert.deepStrictEqual(Buffer.from(ab, 0, {}), expected); + assert.deepStrictEqual(Buffer.from(ab, 0, []), expected); + + // If length can be converted to a number, it will be. + assert.deepStrictEqual(Buffer.from(ab, 0, [1]), Buffer.from(ab, 0, 1)); + + // If length is Infinity, throw. + assert.throws(() => { + Buffer.from(ab, 0, Infinity); + }, { + code: 'ERR_BUFFER_OUT_OF_BOUNDS', + name: 'RangeError', + message: '"length" is outside of buffer bounds' + }); +} + +// Test an array like entry with the length set to NaN. +assert.deepStrictEqual(Buffer.from({ length: NaN }), Buffer.alloc(0)); diff --git a/cli/tests/node_compat/test/parallel/test-buffer-ascii.js b/cli/tests/node_compat/test/parallel/test-buffer-ascii.js new file mode 100644 index 00000000000000..9b03ddafb29831 --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-buffer-ascii.js @@ -0,0 +1,53 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. + +'use strict'; +require('../common'); +const assert = require('assert'); + +// ASCII conversion in node.js simply masks off the high bits, +// it doesn't do transliteration. +assert.strictEqual(Buffer.from('hérité').toString('ascii'), 'hC)ritC)'); + +// 71 characters, 78 bytes. The ’ character is a triple-byte sequence. +const input = 'C’est, graphiquement, la réunion d’un accent aigu ' + + 'et d’un accent grave.'; + +const expected = 'Cb\u0000\u0019est, graphiquement, la rC)union ' + + 'db\u0000\u0019un accent aigu et db\u0000\u0019un ' + + 'accent grave.'; + +const buf = Buffer.from(input); + +for (let i = 0; i < expected.length; ++i) { + assert.strictEqual(buf.slice(i).toString('ascii'), expected.slice(i)); + + // Skip remainder of multi-byte sequence. + if (input.charCodeAt(i) > 65535) ++i; + if (input.charCodeAt(i) > 127) ++i; +} diff --git a/cli/tests/node_compat/test/parallel/test-buffer-badhex.js b/cli/tests/node_compat/test/parallel/test-buffer-badhex.js new file mode 100644 index 00000000000000..abbc7f0b33aaec --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-buffer-badhex.js @@ -0,0 +1,55 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +'use strict'; +require('../common'); +const assert = require('assert'); + +// Test hex strings and bad hex strings +{ + const buf = Buffer.alloc(4); + assert.strictEqual(buf.length, 4); + assert.deepStrictEqual(buf, Buffer.from([0, 0, 0, 0])); + assert.strictEqual(buf.write('abcdxx', 0, 'hex'), 2); + assert.deepStrictEqual(buf, Buffer.from([0xab, 0xcd, 0x00, 0x00])); + assert.strictEqual(buf.toString('hex'), 'abcd0000'); + assert.strictEqual(buf.write('abcdef01', 0, 'hex'), 4); + assert.deepStrictEqual(buf, Buffer.from([0xab, 0xcd, 0xef, 0x01])); + assert.strictEqual(buf.toString('hex'), 'abcdef01'); + + const copy = Buffer.from(buf.toString('hex'), 'hex'); + assert.strictEqual(buf.toString('hex'), copy.toString('hex')); +} + +{ + const buf = Buffer.alloc(5); + assert.strictEqual(buf.write('abcdxx', 1, 'hex'), 2); + assert.strictEqual(buf.toString('hex'), '00abcd0000'); +} + +{ + const buf = Buffer.alloc(4); + assert.deepStrictEqual(buf, Buffer.from([0, 0, 0, 0])); + assert.strictEqual(buf.write('xxabcd', 0, 'hex'), 0); + assert.deepStrictEqual(buf, Buffer.from([0, 0, 0, 0])); + assert.strictEqual(buf.write('xxab', 1, 'hex'), 0); + assert.deepStrictEqual(buf, Buffer.from([0, 0, 0, 0])); + assert.strictEqual(buf.write('cdxxab', 0, 'hex'), 1); + assert.deepStrictEqual(buf, Buffer.from([0xcd, 0, 0, 0])); +} + +{ + const buf = Buffer.alloc(256); + for (let i = 0; i < 256; i++) + buf[i] = i; + + const hex = buf.toString('hex'); + assert.deepStrictEqual(Buffer.from(hex, 'hex'), buf); + + const badHex = `${hex.slice(0, 256)}xx${hex.slice(256, 510)}`; + assert.deepStrictEqual(Buffer.from(badHex, 'hex'), buf.slice(0, 128)); +} diff --git a/cli/tests/node_compat/test/parallel/test-buffer-bigint64.js b/cli/tests/node_compat/test/parallel/test-buffer-bigint64.js new file mode 100644 index 00000000000000..918c7b91d1b2cb --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-buffer-bigint64.js @@ -0,0 +1,62 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +'use strict'; +require('../common'); +const assert = require('assert'); + +const buf = Buffer.allocUnsafe(8); + +['LE', 'BE'].forEach(function(endianness) { + // Should allow simple BigInts to be written and read + let val = 123456789n; + buf[`writeBigInt64${endianness}`](val, 0); + let rtn = buf[`readBigInt64${endianness}`](0); + assert.strictEqual(val, rtn); + + // Should allow INT64_MAX to be written and read + val = 0x7fffffffffffffffn; + buf[`writeBigInt64${endianness}`](val, 0); + rtn = buf[`readBigInt64${endianness}`](0); + assert.strictEqual(val, rtn); + + // Should read and write a negative signed 64-bit integer + val = -123456789n; + buf[`writeBigInt64${endianness}`](val, 0); + assert.strictEqual(val, buf[`readBigInt64${endianness}`](0)); + + // Should read and write an unsigned 64-bit integer + val = 123456789n; + buf[`writeBigUInt64${endianness}`](val, 0); + assert.strictEqual(val, buf[`readBigUInt64${endianness}`](0)); + + // Should throw a RangeError upon INT64_MAX+1 being written + assert.throws(function() { + const val = 0x8000000000000000n; + buf[`writeBigInt64${endianness}`](val, 0); + }, RangeError); + + // Should throw a RangeError upon UINT64_MAX+1 being written + assert.throws(function() { + const val = 0x10000000000000000n; + buf[`writeBigUInt64${endianness}`](val, 0); + }, { + code: 'ERR_OUT_OF_RANGE', + message: 'The value of "value" is out of range. It must be ' + + '>= 0n and < 2n ** 64n. Received 18_446_744_073_709_551_616n' + }); + + // Should throw a TypeError upon invalid input + assert.throws(function() { + buf[`writeBigInt64${endianness}`]('bad', 0); + }, TypeError); + + // Should throw a TypeError upon invalid input + assert.throws(function() { + buf[`writeBigUInt64${endianness}`]('bad', 0); + }, TypeError); +}); diff --git a/cli/tests/node_compat/test/parallel/test-buffer-bytelength.js b/cli/tests/node_compat/test/parallel/test-buffer-bytelength.js new file mode 100644 index 00000000000000..e23b3c3c2846a4 --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-buffer-bytelength.js @@ -0,0 +1,142 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 16.13.0 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +'use strict'; + +const common = require('../common'); +const assert = require('assert'); +const SlowBuffer = require('buffer').SlowBuffer; +const vm = require('vm'); + +[ + [32, 'latin1'], + [NaN, 'utf8'], + [{}, 'latin1'], + [], +].forEach((args) => { + assert.throws( + () => Buffer.byteLength(...args), + { + code: 'ERR_INVALID_ARG_TYPE', + name: 'TypeError', + message: 'The "string" argument must be of type string or an instance ' + + 'of Buffer or ArrayBuffer.' + + common.invalidArgTypeHelper(args[0]) + } + ); +}); + +assert.strictEqual(Buffer.byteLength('', undefined, true), -1); + +assert(ArrayBuffer.isView(new Buffer(10))); +assert(ArrayBuffer.isView(new SlowBuffer(10))); +assert(ArrayBuffer.isView(Buffer.alloc(10))); +assert(ArrayBuffer.isView(Buffer.allocUnsafe(10))); +assert(ArrayBuffer.isView(Buffer.allocUnsafeSlow(10))); +assert(ArrayBuffer.isView(Buffer.from(''))); + +// buffer +const incomplete = Buffer.from([0xe4, 0xb8, 0xad, 0xe6, 0x96]); +assert.strictEqual(Buffer.byteLength(incomplete), 5); +const ascii = Buffer.from('abc'); +assert.strictEqual(Buffer.byteLength(ascii), 3); + +// ArrayBuffer +const buffer = new ArrayBuffer(8); +assert.strictEqual(Buffer.byteLength(buffer), 8); + +// TypedArray +const int8 = new Int8Array(8); +assert.strictEqual(Buffer.byteLength(int8), 8); +const uint8 = new Uint8Array(8); +assert.strictEqual(Buffer.byteLength(uint8), 8); +const uintc8 = new Uint8ClampedArray(2); +assert.strictEqual(Buffer.byteLength(uintc8), 2); +const int16 = new Int16Array(8); +assert.strictEqual(Buffer.byteLength(int16), 16); +const uint16 = new Uint16Array(8); +assert.strictEqual(Buffer.byteLength(uint16), 16); +const int32 = new Int32Array(8); +assert.strictEqual(Buffer.byteLength(int32), 32); +const uint32 = new Uint32Array(8); +assert.strictEqual(Buffer.byteLength(uint32), 32); +const float32 = new Float32Array(8); +assert.strictEqual(Buffer.byteLength(float32), 32); +const float64 = new Float64Array(8); +assert.strictEqual(Buffer.byteLength(float64), 64); + +// DataView +const dv = new DataView(new ArrayBuffer(2)); +assert.strictEqual(Buffer.byteLength(dv), 2); + +// Special case: zero length string +assert.strictEqual(Buffer.byteLength('', 'ascii'), 0); +assert.strictEqual(Buffer.byteLength('', 'HeX'), 0); + +// utf8 +assert.strictEqual(Buffer.byteLength('∑éllö wørl∂!', 'utf-8'), 19); +assert.strictEqual(Buffer.byteLength('κλμνξο', 'utf8'), 12); +assert.strictEqual(Buffer.byteLength('挵挶挷挸挹', 'utf-8'), 15); +assert.strictEqual(Buffer.byteLength('𠝹𠱓𠱸', 'UTF8'), 12); +// Without an encoding, utf8 should be assumed +assert.strictEqual(Buffer.byteLength('hey there'), 9); +assert.strictEqual(Buffer.byteLength('𠱸挶νξ#xx :)'), 17); +assert.strictEqual(Buffer.byteLength('hello world', ''), 11); +// It should also be assumed with unrecognized encoding +assert.strictEqual(Buffer.byteLength('hello world', 'abc'), 11); +assert.strictEqual(Buffer.byteLength('ßœ∑≈', 'unkn0wn enc0ding'), 10); + +// base64 +assert.strictEqual(Buffer.byteLength('aGVsbG8gd29ybGQ=', 'base64'), 11); +assert.strictEqual(Buffer.byteLength('aGVsbG8gd29ybGQ=', 'BASE64'), 11); +assert.strictEqual(Buffer.byteLength('bm9kZS5qcyByb2NrcyE=', 'base64'), 14); +assert.strictEqual(Buffer.byteLength('aGkk', 'base64'), 3); +assert.strictEqual( + Buffer.byteLength('bHNrZGZsa3NqZmtsc2xrZmFqc2RsZmtqcw==', 'base64'), 25 +); +assert.strictEqual(Buffer.byteLength('aGVsbG8gd29ybGQ', 'base64url'), 11); +assert.strictEqual(Buffer.byteLength('aGVsbG8gd29ybGQ', 'BASE64URL'), 11); +assert.strictEqual(Buffer.byteLength('bm9kZS5qcyByb2NrcyE', 'base64url'), 14); +assert.strictEqual(Buffer.byteLength('aGkk', 'base64url'), 3); +assert.strictEqual( + Buffer.byteLength('bHNrZGZsa3NqZmtsc2xrZmFqc2RsZmtqcw', 'base64url'), 25 +); +// special padding +assert.strictEqual(Buffer.byteLength('aaa=', 'base64'), 2); +assert.strictEqual(Buffer.byteLength('aaaa==', 'base64'), 3); +assert.strictEqual(Buffer.byteLength('aaa=', 'base64url'), 2); +assert.strictEqual(Buffer.byteLength('aaaa==', 'base64url'), 3); + +assert.strictEqual(Buffer.byteLength('Il était tué'), 14); +assert.strictEqual(Buffer.byteLength('Il était tué', 'utf8'), 14); + +['ascii', 'latin1', 'binary'] + .reduce((es, e) => es.concat(e, e.toUpperCase()), []) + .forEach((encoding) => { + assert.strictEqual(Buffer.byteLength('Il était tué', encoding), 12); + }); + +['ucs2', 'ucs-2', 'utf16le', 'utf-16le'] + .reduce((es, e) => es.concat(e, e.toUpperCase()), []) + .forEach((encoding) => { + assert.strictEqual(Buffer.byteLength('Il était tué', encoding), 24); + }); + +// TODO(Soremwar) +// Enable once vm module is available +// // Test that ArrayBuffer from a different context is detected correctly +// const arrayBuf = vm.runInNewContext('new ArrayBuffer()'); +// assert.strictEqual(Buffer.byteLength(arrayBuf), 0); + +// Verify that invalid encodings are treated as utf8 +for (let i = 1; i < 10; i++) { + const encoding = String(i).repeat(i); + + assert.ok(!Buffer.isEncoding(encoding)); + assert.strictEqual(Buffer.byteLength('foo', encoding), + Buffer.byteLength('foo', 'utf8')); +} diff --git a/cli/tests/node_compat/test/parallel/test-buffer-compare-offset.js b/cli/tests/node_compat/test/parallel/test-buffer-compare-offset.js new file mode 100644 index 00000000000000..fa4487ee013ba7 --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-buffer-compare-offset.js @@ -0,0 +1,101 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +'use strict'; + +require('../common'); +const assert = require('assert'); + +const a = Buffer.from([1, 2, 3, 4, 5, 6, 7, 8, 9, 0]); +const b = Buffer.from([5, 6, 7, 8, 9, 0, 1, 2, 3, 4]); + +assert.strictEqual(a.compare(b), -1); + +// Equivalent to a.compare(b). +assert.strictEqual(a.compare(b, 0), -1); +assert.throws(() => a.compare(b, '0'), { code: 'ERR_INVALID_ARG_TYPE' }); +assert.strictEqual(a.compare(b, undefined), -1); + +// Equivalent to a.compare(b). +assert.strictEqual(a.compare(b, 0, undefined, 0), -1); + +// Zero-length target, return 1 +assert.strictEqual(a.compare(b, 0, 0, 0), 1); +assert.throws( + () => a.compare(b, 0, '0', '0'), + { code: 'ERR_INVALID_ARG_TYPE' } +); + +// Equivalent to Buffer.compare(a, b.slice(6, 10)) +assert.strictEqual(a.compare(b, 6, 10), 1); + +// Zero-length source, return -1 +assert.strictEqual(a.compare(b, 6, 10, 0, 0), -1); + +// Zero-length source and target, return 0 +assert.strictEqual(a.compare(b, 0, 0, 0, 0), 0); +assert.strictEqual(a.compare(b, 1, 1, 2, 2), 0); + +// Equivalent to Buffer.compare(a.slice(4), b.slice(0, 5)) +assert.strictEqual(a.compare(b, 0, 5, 4), 1); + +// Equivalent to Buffer.compare(a.slice(1), b.slice(5)) +assert.strictEqual(a.compare(b, 5, undefined, 1), 1); + +// Equivalent to Buffer.compare(a.slice(2), b.slice(2, 4)) +assert.strictEqual(a.compare(b, 2, 4, 2), -1); + +// Equivalent to Buffer.compare(a.slice(4), b.slice(0, 7)) +assert.strictEqual(a.compare(b, 0, 7, 4), -1); + +// Equivalent to Buffer.compare(a.slice(4, 6), b.slice(0, 7)); +assert.strictEqual(a.compare(b, 0, 7, 4, 6), -1); + +// Null is ambiguous. +assert.throws( + () => a.compare(b, 0, null), + { code: 'ERR_INVALID_ARG_TYPE' } +); + +// Values do not get coerced. +assert.throws( + () => a.compare(b, 0, { valueOf: () => 5 }), + { code: 'ERR_INVALID_ARG_TYPE' } +); + +// Infinity should not be coerced. +assert.throws( + () => a.compare(b, Infinity, -Infinity), + { code: 'ERR_OUT_OF_RANGE' } +); + +// Zero length target because default for targetEnd <= targetSource +assert.strictEqual(a.compare(b, 0xff), 1); + +assert.throws( + () => a.compare(b, '0xff'), + { code: 'ERR_INVALID_ARG_TYPE' } +); +assert.throws( + () => a.compare(b, 0, '0xff'), + { code: 'ERR_INVALID_ARG_TYPE' } +); + +const oor = { code: 'ERR_OUT_OF_RANGE' }; + +assert.throws(() => a.compare(b, 0, 100, 0), oor); +assert.throws(() => a.compare(b, 0, 1, 0, 100), oor); +assert.throws(() => a.compare(b, -1), oor); +assert.throws(() => a.compare(b, 0, Infinity), oor); +assert.throws(() => a.compare(b, 0, 1, -1), oor); +assert.throws(() => a.compare(b, -Infinity, Infinity), oor); +assert.throws(() => a.compare(), { + code: 'ERR_INVALID_ARG_TYPE', + name: 'TypeError', + message: 'The "target" argument must be an instance of ' + + 'Buffer or Uint8Array. Received undefined' +}); diff --git a/cli/tests/node_compat/test/parallel/test-buffer-concat.js b/cli/tests/node_compat/test/parallel/test-buffer-concat.js new file mode 100644 index 00000000000000..d44b3e1b969dac --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-buffer-concat.js @@ -0,0 +1,107 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. + +'use strict'; +const common = require('../common'); +const assert = require('assert'); + +const zero = []; +const one = [ Buffer.from('asdf') ]; +const long = []; +for (let i = 0; i < 10; i++) long.push(Buffer.from('asdf')); + +const flatZero = Buffer.concat(zero); +const flatOne = Buffer.concat(one); +const flatLong = Buffer.concat(long); +const flatLongLen = Buffer.concat(long, 40); + +assert.strictEqual(flatZero.length, 0); +assert.strictEqual(flatOne.toString(), 'asdf'); + +const check = 'asdf'.repeat(10); + +// A special case where concat used to return the first item, +// if the length is one. This check is to make sure that we don't do that. +assert.notStrictEqual(flatOne, one[0]); +assert.strictEqual(flatLong.toString(), check); +assert.strictEqual(flatLongLen.toString(), check); + +[undefined, null, Buffer.from('hello')].forEach((value) => { + assert.throws(() => { + Buffer.concat(value); + }, { + code: 'ERR_INVALID_ARG_TYPE', + message: 'The "list" argument must be an instance of Array.' + + `${common.invalidArgTypeHelper(value)}` + }); +}); + +[[42], ['hello', Buffer.from('world')]].forEach((value) => { + assert.throws(() => { + Buffer.concat(value); + }, { + code: 'ERR_INVALID_ARG_TYPE', + message: 'The "list[0]" argument must be an instance of Buffer ' + + `or Uint8Array.${common.invalidArgTypeHelper(value[0])}` + }); +}); + +assert.throws(() => { + Buffer.concat([Buffer.from('hello'), 3]); +}, { + code: 'ERR_INVALID_ARG_TYPE', + message: 'The "list[1]" argument must be an instance of Buffer ' + + 'or Uint8Array. Received type number (3)' +}); + +// eslint-disable-next-line node-core/crypto-check +const random10 = common.hasCrypto ? + require('crypto').randomBytes(10) : + Buffer.alloc(10, 1); +const empty = Buffer.alloc(0); + +assert.notDeepStrictEqual(random10, empty); +assert.notDeepStrictEqual(random10, Buffer.alloc(10)); + +assert.deepStrictEqual(Buffer.concat([], 100), empty); +assert.deepStrictEqual(Buffer.concat([random10], 0), empty); +assert.deepStrictEqual(Buffer.concat([random10], 10), random10); +assert.deepStrictEqual(Buffer.concat([random10, random10], 10), random10); +assert.deepStrictEqual(Buffer.concat([empty, random10]), random10); +assert.deepStrictEqual(Buffer.concat([random10, empty, empty]), random10); + +// The tail should be zero-filled +assert.deepStrictEqual(Buffer.concat([empty], 100), Buffer.alloc(100)); +assert.deepStrictEqual(Buffer.concat([empty], 4096), Buffer.alloc(4096)); +assert.deepStrictEqual( + Buffer.concat([random10], 40), + Buffer.concat([random10, Buffer.alloc(30)])); + +assert.deepStrictEqual(Buffer.concat([new Uint8Array([0x41, 0x42]), + new Uint8Array([0x43, 0x44])]), + Buffer.from('ABCD')); diff --git a/cli/tests/node_compat/test/parallel/test-buffer-constants.js b/cli/tests/node_compat/test/parallel/test-buffer-constants.js new file mode 100644 index 00000000000000..a537f318bd10d8 --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-buffer-constants.js @@ -0,0 +1,25 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +'use strict'; +require('../common'); +const assert = require('assert'); + +const { kMaxLength, kStringMaxLength } = require('buffer'); +const { MAX_LENGTH, MAX_STRING_LENGTH } = require('buffer').constants; + +assert.strictEqual(typeof MAX_LENGTH, 'number'); +assert.strictEqual(typeof MAX_STRING_LENGTH, 'number'); +assert(MAX_STRING_LENGTH <= MAX_LENGTH); +assert.throws(() => ' '.repeat(MAX_STRING_LENGTH + 1), + /^RangeError: Invalid string length$/); + +' '.repeat(MAX_STRING_LENGTH); // Should not throw. + +// Legacy values match: +assert.strictEqual(kMaxLength, MAX_LENGTH); +assert.strictEqual(kStringMaxLength, MAX_STRING_LENGTH); diff --git a/cli/tests/node_compat/test/parallel/test-buffer-copy.js b/cli/tests/node_compat/test/parallel/test-buffer-copy.js new file mode 100644 index 00000000000000..b51664e78eea98 --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-buffer-copy.js @@ -0,0 +1,236 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +'use strict'; + +require('../common'); +const assert = require('assert'); + +const b = Buffer.allocUnsafe(1024); +const c = Buffer.allocUnsafe(512); + +let cntr = 0; + +{ + // copy 512 bytes, from 0 to 512. + b.fill(++cntr); + c.fill(++cntr); + const copied = b.copy(c, 0, 0, 512); + assert.strictEqual(copied, 512); + for (let i = 0; i < c.length; i++) { + assert.strictEqual(c[i], b[i]); + } +} + +{ + // Current behavior is to coerce values to integers. + b.fill(++cntr); + c.fill(++cntr); + const copied = b.copy(c, '0', '0', '512'); + assert.strictEqual(copied, 512); + for (let i = 0; i < c.length; i++) { + assert.strictEqual(c[i], b[i]); + } +} + +{ + // Floats will be converted to integers via `Math.floor` + b.fill(++cntr); + c.fill(++cntr); + const copied = b.copy(c, 0, 0, 512.5); + assert.strictEqual(copied, 512); + for (let i = 0; i < c.length; i++) { + assert.strictEqual(c[i], b[i]); + } +} + +{ + // Copy c into b, without specifying sourceEnd + b.fill(++cntr); + c.fill(++cntr); + const copied = c.copy(b, 0, 0); + assert.strictEqual(copied, c.length); + for (let i = 0; i < c.length; i++) { + assert.strictEqual(b[i], c[i]); + } +} + +{ + // Copy c into b, without specifying sourceStart + b.fill(++cntr); + c.fill(++cntr); + const copied = c.copy(b, 0); + assert.strictEqual(copied, c.length); + for (let i = 0; i < c.length; i++) { + assert.strictEqual(b[i], c[i]); + } +} + +{ + // Copied source range greater than source length + b.fill(++cntr); + c.fill(++cntr); + const copied = c.copy(b, 0, 0, c.length + 1); + assert.strictEqual(copied, c.length); + for (let i = 0; i < c.length; i++) { + assert.strictEqual(b[i], c[i]); + } +} + +{ + // Copy longer buffer b to shorter c without targetStart + b.fill(++cntr); + c.fill(++cntr); + const copied = b.copy(c); + assert.strictEqual(copied, c.length); + for (let i = 0; i < c.length; i++) { + assert.strictEqual(c[i], b[i]); + } +} + +{ + // Copy starting near end of b to c + b.fill(++cntr); + c.fill(++cntr); + const copied = b.copy(c, 0, b.length - Math.floor(c.length / 2)); + assert.strictEqual(copied, Math.floor(c.length / 2)); + for (let i = 0; i < Math.floor(c.length / 2); i++) { + assert.strictEqual(c[i], b[b.length - Math.floor(c.length / 2) + i]); + } + for (let i = Math.floor(c.length / 2) + 1; i < c.length; i++) { + assert.strictEqual(c[c.length - 1], c[i]); + } +} + +{ + // Try to copy 513 bytes, and check we don't overrun c + b.fill(++cntr); + c.fill(++cntr); + const copied = b.copy(c, 0, 0, 513); + assert.strictEqual(copied, c.length); + for (let i = 0; i < c.length; i++) { + assert.strictEqual(c[i], b[i]); + } +} + +{ + // copy 768 bytes from b into b + b.fill(++cntr); + b.fill(++cntr, 256); + const copied = b.copy(b, 0, 256, 1024); + assert.strictEqual(copied, 768); + for (let i = 0; i < b.length; i++) { + assert.strictEqual(b[i], cntr); + } +} + +// Copy string longer than buffer length (failure will segfault) +const bb = Buffer.allocUnsafe(10); +bb.fill('hello crazy world'); + + +// Try to copy from before the beginning of b. Should not throw. +b.copy(c, 0, 100, 10); + +// Throw with invalid source type +assert.throws( + () => Buffer.prototype.copy.call(0), + { + code: 'ERR_INVALID_ARG_TYPE', + name: 'TypeError', + } +); + +// Copy throws at negative targetStart +assert.throws( + () => Buffer.allocUnsafe(5).copy(Buffer.allocUnsafe(5), -1, 0), + { + code: 'ERR_OUT_OF_RANGE', + name: 'RangeError', + message: 'The value of "targetStart" is out of range. ' + + 'It must be >= 0. Received -1' + } +); + +// Copy throws at negative sourceStart +assert.throws( + () => Buffer.allocUnsafe(5).copy(Buffer.allocUnsafe(5), 0, -1), + { + code: 'ERR_OUT_OF_RANGE', + name: 'RangeError', + message: 'The value of "sourceStart" is out of range. ' + + 'It must be >= 0. Received -1' + } +); + +{ + // Check sourceEnd resets to targetEnd if former is greater than the latter + b.fill(++cntr); + c.fill(++cntr); + b.copy(c, 0, 0, 1025); + for (let i = 0; i < c.length; i++) { + assert.strictEqual(c[i], b[i]); + } +} + +// Throw with negative sourceEnd +assert.throws( + () => b.copy(c, 0, 0, -1), + { + code: 'ERR_OUT_OF_RANGE', + name: 'RangeError', + message: 'The value of "sourceEnd" is out of range. ' + + 'It must be >= 0. Received -1' + } +); + +// When sourceStart is greater than sourceEnd, zero copied +assert.strictEqual(b.copy(c, 0, 100, 10), 0); + +// When targetStart > targetLength, zero copied +assert.strictEqual(b.copy(c, 512, 0, 10), 0); + +// Test that the `target` can be a Uint8Array. +{ + const d = new Uint8Array(c); + // copy 512 bytes, from 0 to 512. + b.fill(++cntr); + d.fill(++cntr); + const copied = b.copy(d, 0, 0, 512); + assert.strictEqual(copied, 512); + for (let i = 0; i < d.length; i++) { + assert.strictEqual(d[i], b[i]); + } +} + +// Test that the source can be a Uint8Array, too. +{ + const e = new Uint8Array(b); + // copy 512 bytes, from 0 to 512. + e.fill(++cntr); + c.fill(++cntr); + const copied = Buffer.prototype.copy.call(e, c, 0, 0, 512); + assert.strictEqual(copied, 512); + for (let i = 0; i < c.length; i++) { + assert.strictEqual(c[i], e[i]); + } +} + +// https://github.com/nodejs/node/issues/23668: Do not crash for invalid input. +c.fill('c'); +b.copy(c, 'not a valid offset'); +// Make sure this acted like a regular copy with `0` offset. +assert.deepStrictEqual(c, b.slice(0, c.length)); + +{ + c.fill('C'); + assert.throws(() => { + b.copy(c, { [Symbol.toPrimitive]() { throw new Error('foo'); } }); + }, /foo/); + // No copying took place: + assert.deepStrictEqual(c.toString(), 'C'.repeat(c.length)); +} diff --git a/cli/tests/node_compat/test/parallel/test-buffer-equals.js b/cli/tests/node_compat/test/parallel/test-buffer-equals.js new file mode 100644 index 00000000000000..4c82006a71e4f3 --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-buffer-equals.js @@ -0,0 +1,32 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +'use strict'; + +require('../common'); +const assert = require('assert'); + +const b = Buffer.from('abcdf'); +const c = Buffer.from('abcdf'); +const d = Buffer.from('abcde'); +const e = Buffer.from('abcdef'); + +assert.ok(b.equals(c)); +assert.ok(!c.equals(d)); +assert.ok(!d.equals(e)); +assert.ok(d.equals(d)); +assert.ok(d.equals(new Uint8Array([0x61, 0x62, 0x63, 0x64, 0x65]))); + +assert.throws( + () => Buffer.alloc(1).equals('abc'), + { + code: 'ERR_INVALID_ARG_TYPE', + name: 'TypeError', + message: 'The "otherBuffer" argument must be an instance of ' + + "Buffer or Uint8Array. Received type string ('abc')" + } +); diff --git a/cli/tests/node_compat/test/parallel/test-buffer-failed-alloc-typed-arrays.js b/cli/tests/node_compat/test/parallel/test-buffer-failed-alloc-typed-arrays.js new file mode 100644 index 00000000000000..265652c09b84cf --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-buffer-failed-alloc-typed-arrays.js @@ -0,0 +1,40 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +'use strict'; + +require('../common'); +const assert = require('assert'); +const SlowBuffer = require('buffer').SlowBuffer; + +// Test failed or zero-sized Buffer allocations not affecting typed arrays. +// This test exists because of a regression that occurred. Because Buffer +// instances are allocated with the same underlying allocator as TypedArrays, +// but Buffer's can optional be non-zero filled, there was a regression that +// occurred when a Buffer allocated failed, the internal flag specifying +// whether or not to zero-fill was not being reset, causing TypedArrays to +// allocate incorrectly. +const zeroArray = new Uint32Array(10).fill(0); +const sizes = [1e10, 0, 0.1, -1, 'a', undefined, null, NaN]; +const allocators = [ + Buffer, + SlowBuffer, + Buffer.alloc, + Buffer.allocUnsafe, + Buffer.allocUnsafeSlow, +]; +for (const allocator of allocators) { + for (const size of sizes) { + try { + // Some of these allocations are known to fail. If they do, + // Uint32Array should still produce a zeroed out result. + allocator(size); + } catch { + assert.deepStrictEqual(zeroArray, new Uint32Array(10)); + } + } +} diff --git a/cli/tests/node_compat/test/parallel/test-buffer-fakes.js b/cli/tests/node_compat/test/parallel/test-buffer-fakes.js new file mode 100644 index 00000000000000..5f088b2acb960a --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-buffer-fakes.js @@ -0,0 +1,61 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +'use strict'; + +require('../common'); +const assert = require('assert'); + +function FakeBuffer() { } +Object.setPrototypeOf(FakeBuffer, Buffer); +Object.setPrototypeOf(FakeBuffer.prototype, Buffer.prototype); + +const fb = new FakeBuffer(); + +assert.throws(function() { + Buffer.from(fb); +}, TypeError); + +assert.throws(function() { + +Buffer.prototype; // eslint-disable-line no-unused-expressions +}, TypeError); + +assert.throws(function() { + Buffer.compare(fb, Buffer.alloc(0)); +}, TypeError); + +assert.throws(function() { + fb.write('foo'); +}, TypeError); + +assert.throws(function() { + Buffer.concat([fb, fb]); +}, TypeError); + +assert.throws(function() { + fb.toString(); +}, TypeError); + +assert.throws(function() { + fb.equals(Buffer.alloc(0)); +}, TypeError); + +assert.throws(function() { + fb.indexOf(5); +}, TypeError); + +assert.throws(function() { + fb.readFloatLE(0); +}, TypeError); + +assert.throws(function() { + fb.writeFloatLE(0); +}, TypeError); + +assert.throws(function() { + fb.fill(0); +}, TypeError); diff --git a/cli/tests/node_compat/test/parallel/test-buffer-from.js b/cli/tests/node_compat/test/parallel/test-buffer-from.js new file mode 100644 index 00000000000000..ef023cf0b7a064 --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-buffer-from.js @@ -0,0 +1,74 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 16.13.0 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +'use strict'; + +const common = require('../common'); +const { deepStrictEqual, throws } = require('assert'); +const { runInNewContext } = require('vm'); + +const checkString = 'test'; + +const check = Buffer.from(checkString); + +class MyString extends String { + constructor() { + super(checkString); + } +} + +class MyPrimitive { + [Symbol.toPrimitive]() { + return checkString; + } +} + +class MyBadPrimitive { + [Symbol.toPrimitive]() { + return 1; + } +} + +deepStrictEqual(Buffer.from(new String(checkString)), check); +deepStrictEqual(Buffer.from(new MyString()), check); +deepStrictEqual(Buffer.from(new MyPrimitive()), check); + +// TODO(Soremwar) +// Enable once again when vm works correctly +// deepStrictEqual( +// Buffer.from(runInNewContext('new String(checkString)', { checkString })), +// check +// ); + +[ + {}, + new Boolean(true), + { valueOf() { return null; } }, + { valueOf() { return undefined; } }, + { valueOf: null }, + Object.create(null), + new Number(true), + new MyBadPrimitive(), + Symbol(), + 5n, + (one, two, three) => {}, + undefined, + null, +].forEach((input) => { + const errObj = { + code: 'ERR_INVALID_ARG_TYPE', + name: 'TypeError', + message: 'The first argument must be of type string or an instance of ' + + 'Buffer, ArrayBuffer, or Array or an Array-like Object.' + + common.invalidArgTypeHelper(input) + }; + throws(() => Buffer.from(input), errObj); + throws(() => Buffer.from(input, 'hex'), errObj); +}); + +Buffer.allocUnsafe(10); // Should not throw. +Buffer.from('deadbeaf', 'hex'); // Should not throw. diff --git a/cli/tests/node_compat/test/parallel/test-buffer-includes.js b/cli/tests/node_compat/test/parallel/test-buffer-includes.js new file mode 100644 index 00000000000000..66da7bfd30fdab --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-buffer-includes.js @@ -0,0 +1,317 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 16.13.0 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +'use strict'; +const common = require('../common'); +const assert = require('assert'); + +const b = Buffer.from('abcdef'); +const buf_a = Buffer.from('a'); +const buf_bc = Buffer.from('bc'); +const buf_f = Buffer.from('f'); +const buf_z = Buffer.from('z'); +const buf_empty = Buffer.from(''); + +assert(b.includes('a')); +assert(!b.includes('a', 1)); +assert(!b.includes('a', -1)); +assert(!b.includes('a', -4)); +assert(b.includes('a', -b.length)); +assert(b.includes('a', NaN)); +assert(b.includes('a', -Infinity)); +assert(!b.includes('a', Infinity)); +assert(b.includes('bc')); +assert(!b.includes('bc', 2)); +assert(!b.includes('bc', -1)); +assert(!b.includes('bc', -3)); +assert(b.includes('bc', -5)); +assert(b.includes('bc', NaN)); +assert(b.includes('bc', -Infinity)); +assert(!b.includes('bc', Infinity)); +assert(b.includes('f'), b.length - 1); +assert(!b.includes('z')); +assert(b.includes('')); +assert(b.includes('', 1)); +assert(b.includes('', b.length + 1)); +assert(b.includes('', Infinity)); +assert(b.includes(buf_a)); +assert(!b.includes(buf_a, 1)); +assert(!b.includes(buf_a, -1)); +assert(!b.includes(buf_a, -4)); +assert(b.includes(buf_a, -b.length)); +assert(b.includes(buf_a, NaN)); +assert(b.includes(buf_a, -Infinity)); +assert(!b.includes(buf_a, Infinity)); +assert(b.includes(buf_bc)); +assert(!b.includes(buf_bc, 2)); +assert(!b.includes(buf_bc, -1)); +assert(!b.includes(buf_bc, -3)); +assert(b.includes(buf_bc, -5)); +assert(b.includes(buf_bc, NaN)); +assert(b.includes(buf_bc, -Infinity)); +assert(!b.includes(buf_bc, Infinity)); +assert(b.includes(buf_f), b.length - 1); +assert(!b.includes(buf_z)); +assert(b.includes(buf_empty)); +assert(b.includes(buf_empty, 1)); +assert(b.includes(buf_empty, b.length + 1)); +assert(b.includes(buf_empty, Infinity)); +assert(b.includes(0x61)); +assert(!b.includes(0x61, 1)); +assert(!b.includes(0x61, -1)); +assert(!b.includes(0x61, -4)); +assert(b.includes(0x61, -b.length)); +assert(b.includes(0x61, NaN)); +assert(b.includes(0x61, -Infinity)); +assert(!b.includes(0x61, Infinity)); +assert(!b.includes(0x0)); + +// test offsets +assert(b.includes('d', 2)); +assert(b.includes('f', 5)); +assert(b.includes('f', -1)); +assert(!b.includes('f', 6)); + +assert(b.includes(Buffer.from('d'), 2)); +assert(b.includes(Buffer.from('f'), 5)); +assert(b.includes(Buffer.from('f'), -1)); +assert(!b.includes(Buffer.from('f'), 6)); + +// TODO(Soremwar) +// Enable again once encoding is taking into account when evaluating indexOf +// assert(!Buffer.from('ff').includes(Buffer.from('f'), 1, 'ucs2')); + +// test hex encoding +assert.strictEqual( + Buffer.from(b.toString('hex'), 'hex') + .includes('64', 0, 'hex'), + true +); +assert.strictEqual( + Buffer.from(b.toString('hex'), 'hex') + .includes(Buffer.from('64', 'hex'), 0, 'hex'), + true +); + +// Test base64 encoding +assert.strictEqual( + Buffer.from(b.toString('base64'), 'base64') + .includes('ZA==', 0, 'base64'), + true +); +assert.strictEqual( + Buffer.from(b.toString('base64'), 'base64') + .includes(Buffer.from('ZA==', 'base64'), 0, 'base64'), + true +); + +// test ascii encoding +assert.strictEqual( + Buffer.from(b.toString('ascii'), 'ascii') + .includes('d', 0, 'ascii'), + true +); +assert.strictEqual( + Buffer.from(b.toString('ascii'), 'ascii') + .includes(Buffer.from('d', 'ascii'), 0, 'ascii'), + true +); + +// Test latin1 encoding +assert.strictEqual( + Buffer.from(b.toString('latin1'), 'latin1') + .includes('d', 0, 'latin1'), + true +); +assert.strictEqual( + Buffer.from(b.toString('latin1'), 'latin1') + .includes(Buffer.from('d', 'latin1'), 0, 'latin1'), + true +); + +// Test binary encoding +assert.strictEqual( + Buffer.from(b.toString('binary'), 'binary') + .includes('d', 0, 'binary'), + true +); +assert.strictEqual( + Buffer.from(b.toString('binary'), 'binary') + .includes(Buffer.from('d', 'binary'), 0, 'binary'), + true +); + + +// test ucs2 encoding +let twoByteString = Buffer.from('\u039a\u0391\u03a3\u03a3\u0395', 'ucs2'); + +assert(twoByteString.includes('\u0395', 4, 'ucs2')); +assert(twoByteString.includes('\u03a3', -4, 'ucs2')); +assert(twoByteString.includes('\u03a3', -6, 'ucs2')); +assert(twoByteString.includes( + Buffer.from('\u03a3', 'ucs2'), -6, 'ucs2')); +assert(!twoByteString.includes('\u03a3', -2, 'ucs2')); + +const mixedByteStringUcs2 = + Buffer.from('\u039a\u0391abc\u03a3\u03a3\u0395', 'ucs2'); +assert(mixedByteStringUcs2.includes('bc', 0, 'ucs2')); +assert(mixedByteStringUcs2.includes('\u03a3', 0, 'ucs2')); +assert(!mixedByteStringUcs2.includes('\u0396', 0, 'ucs2')); + +assert.ok( + mixedByteStringUcs2.includes(Buffer.from('bc', 'ucs2'), 0, 'ucs2')); +assert.ok( + mixedByteStringUcs2.includes(Buffer.from('\u03a3', 'ucs2'), 0, 'ucs2')); +assert.ok( + !mixedByteStringUcs2.includes(Buffer.from('\u0396', 'ucs2'), 0, 'ucs2')); + +twoByteString = Buffer.from('\u039a\u0391\u03a3\u03a3\u0395', 'ucs2'); + +// Test single char pattern +assert(twoByteString.includes('\u039a', 0, 'ucs2')); +assert(twoByteString.includes('\u0391', 0, 'ucs2'), 'Alpha'); +assert(twoByteString.includes('\u03a3', 0, 'ucs2'), 'First Sigma'); +assert(twoByteString.includes('\u03a3', 6, 'ucs2'), 'Second Sigma'); +assert(twoByteString.includes('\u0395', 0, 'ucs2'), 'Epsilon'); +assert(!twoByteString.includes('\u0392', 0, 'ucs2'), 'Not beta'); + +// Test multi-char pattern +assert(twoByteString.includes('\u039a\u0391', 0, 'ucs2'), 'Lambda Alpha'); +assert(twoByteString.includes('\u0391\u03a3', 0, 'ucs2'), 'Alpha Sigma'); +assert(twoByteString.includes('\u03a3\u03a3', 0, 'ucs2'), 'Sigma Sigma'); +assert(twoByteString.includes('\u03a3\u0395', 0, 'ucs2'), 'Sigma Epsilon'); + +const mixedByteStringUtf8 = Buffer.from('\u039a\u0391abc\u03a3\u03a3\u0395'); +assert(mixedByteStringUtf8.includes('bc')); +assert(mixedByteStringUtf8.includes('bc', 5)); +assert(mixedByteStringUtf8.includes('bc', -8)); +assert(mixedByteStringUtf8.includes('\u03a3')); +assert(!mixedByteStringUtf8.includes('\u0396')); + + +// Test complex string includes algorithms. Only trigger for long strings. +// Long string that isn't a simple repeat of a shorter string. +let longString = 'A'; +for (let i = 66; i < 76; i++) { // from 'B' to 'K' + longString = longString + String.fromCharCode(i) + longString; +} + +const longBufferString = Buffer.from(longString); + +// Pattern of 15 chars, repeated every 16 chars in long +let pattern = 'ABACABADABACABA'; +for (let i = 0; i < longBufferString.length - pattern.length; i += 7) { + const includes = longBufferString.includes(pattern, i); + assert(includes, `Long ABACABA...-string at index ${i}`); +} +assert(longBufferString.includes('AJABACA'), 'Long AJABACA, First J'); +assert(longBufferString.includes('AJABACA', 511), 'Long AJABACA, Second J'); + +pattern = 'JABACABADABACABA'; +assert(longBufferString.includes(pattern), 'Long JABACABA..., First J'); +assert(longBufferString.includes(pattern, 512), 'Long JABACABA..., Second J'); + +// Search for a non-ASCII string in a pure ASCII string. +const asciiString = Buffer.from( + 'arglebargleglopglyfarglebargleglopglyfarglebargleglopglyf'); +assert(!asciiString.includes('\x2061')); +assert(asciiString.includes('leb', 0)); + +// Search in string containing many non-ASCII chars. +const allCodePoints = []; +for (let i = 0; i < 65534; i++) allCodePoints[i] = i; +const allCharsString = String.fromCharCode.apply(String, allCodePoints) + + String.fromCharCode(65534, 65535); +const allCharsBufferUtf8 = Buffer.from(allCharsString); +const allCharsBufferUcs2 = Buffer.from(allCharsString, 'ucs2'); + +// Search for string long enough to trigger complex search with ASCII pattern +// and UC16 subject. +assert(!allCharsBufferUtf8.includes('notfound')); +assert(!allCharsBufferUcs2.includes('notfound')); + +// Find substrings in Utf8. +let lengths = [1, 3, 15]; // Single char, simple and complex. +let indices = [0x5, 0x60, 0x400, 0x680, 0x7ee, 0xFF02, 0x16610, 0x2f77b]; +for (let lengthIndex = 0; lengthIndex < lengths.length; lengthIndex++) { + for (let i = 0; i < indices.length; i++) { + const index = indices[i]; + let length = lengths[lengthIndex]; + + if (index + length > 0x7F) { + length = 2 * length; + } + + if (index + length > 0x7FF) { + length = 3 * length; + } + + if (index + length > 0xFFFF) { + length = 4 * length; + } + + const patternBufferUtf8 = allCharsBufferUtf8.slice(index, index + length); + assert(index, allCharsBufferUtf8.includes(patternBufferUtf8)); + + const patternStringUtf8 = patternBufferUtf8.toString(); + assert(index, allCharsBufferUtf8.includes(patternStringUtf8)); + } +} + +// Find substrings in Usc2. +lengths = [2, 4, 16]; // Single char, simple and complex. +indices = [0x5, 0x65, 0x105, 0x205, 0x285, 0x2005, 0x2085, 0xfff0]; +for (let lengthIndex = 0; lengthIndex < lengths.length; lengthIndex++) { + for (let i = 0; i < indices.length; i++) { + const index = indices[i] * 2; + const length = lengths[lengthIndex]; + + const patternBufferUcs2 = + allCharsBufferUcs2.slice(index, index + length); + assert.ok( + allCharsBufferUcs2.includes(patternBufferUcs2, 0, 'ucs2')); + + const patternStringUcs2 = patternBufferUcs2.toString('ucs2'); + assert.ok( + allCharsBufferUcs2.includes(patternStringUcs2, 0, 'ucs2')); + } +} + +[ + () => { }, + {}, + [], +].forEach((val) => { + assert.throws( + () => b.includes(val), + { + code: 'ERR_INVALID_ARG_TYPE', + name: 'TypeError', + message: 'The "value" argument must be one of type number or string ' + + 'or an instance of Buffer or Uint8Array.' + + common.invalidArgTypeHelper(val) + } + ); +}); + +// Test truncation of Number arguments to uint8 +// TODO(Soremwar) +// Enable once multi byte number search is available +// { +// const buf = Buffer.from('this is a test'); +// assert.ok(buf.includes(0x6973)); +// assert.ok(buf.includes(0x697320)); +// assert.ok(buf.includes(0x69732069)); +// assert.ok(buf.includes(0x697374657374)); +// assert.ok(buf.includes(0x69737374)); +// assert.ok(buf.includes(0x69737465)); +// assert.ok(buf.includes(0x69737465)); +// assert.ok(buf.includes(-140)); +// assert.ok(buf.includes(-152)); +// assert.ok(!buf.includes(0xff)); +// assert.ok(!buf.includes(0xffff)); +// } diff --git a/cli/tests/node_compat/test/parallel/test-buffer-indexof.js b/cli/tests/node_compat/test/parallel/test-buffer-indexof.js new file mode 100644 index 00000000000000..e98e343492e130 --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-buffer-indexof.js @@ -0,0 +1,646 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 16.13.0 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +'use strict'; +const common = require('../common'); +const assert = require('assert'); + +const b = Buffer.from('abcdef'); +const buf_a = Buffer.from('a'); +const buf_bc = Buffer.from('bc'); +const buf_f = Buffer.from('f'); +const buf_z = Buffer.from('z'); +const buf_empty = Buffer.from(''); + +const s = 'abcdef'; + +assert.strictEqual(b.indexOf('a'), 0); +assert.strictEqual(b.indexOf('a', 1), -1); +assert.strictEqual(b.indexOf('a', -1), -1); +assert.strictEqual(b.indexOf('a', -4), -1); +assert.strictEqual(b.indexOf('a', -b.length), 0); +assert.strictEqual(b.indexOf('a', NaN), 0); +assert.strictEqual(b.indexOf('a', -Infinity), 0); +assert.strictEqual(b.indexOf('a', Infinity), -1); +assert.strictEqual(b.indexOf('bc'), 1); +assert.strictEqual(b.indexOf('bc', 2), -1); +assert.strictEqual(b.indexOf('bc', -1), -1); +assert.strictEqual(b.indexOf('bc', -3), -1); +assert.strictEqual(b.indexOf('bc', -5), 1); +assert.strictEqual(b.indexOf('bc', NaN), 1); +assert.strictEqual(b.indexOf('bc', -Infinity), 1); +assert.strictEqual(b.indexOf('bc', Infinity), -1); +assert.strictEqual(b.indexOf('f'), b.length - 1); +assert.strictEqual(b.indexOf('z'), -1); +assert.strictEqual(b.indexOf(''), 0); +assert.strictEqual(b.indexOf('', 1), 1); +assert.strictEqual(b.indexOf('', b.length + 1), b.length); +assert.strictEqual(b.indexOf('', Infinity), b.length); +assert.strictEqual(b.indexOf(buf_a), 0); +assert.strictEqual(b.indexOf(buf_a, 1), -1); +assert.strictEqual(b.indexOf(buf_a, -1), -1); +assert.strictEqual(b.indexOf(buf_a, -4), -1); +assert.strictEqual(b.indexOf(buf_a, -b.length), 0); +assert.strictEqual(b.indexOf(buf_a, NaN), 0); +assert.strictEqual(b.indexOf(buf_a, -Infinity), 0); +assert.strictEqual(b.indexOf(buf_a, Infinity), -1); +assert.strictEqual(b.indexOf(buf_bc), 1); +assert.strictEqual(b.indexOf(buf_bc, 2), -1); +assert.strictEqual(b.indexOf(buf_bc, -1), -1); +assert.strictEqual(b.indexOf(buf_bc, -3), -1); +assert.strictEqual(b.indexOf(buf_bc, -5), 1); +assert.strictEqual(b.indexOf(buf_bc, NaN), 1); +assert.strictEqual(b.indexOf(buf_bc, -Infinity), 1); +assert.strictEqual(b.indexOf(buf_bc, Infinity), -1); +assert.strictEqual(b.indexOf(buf_f), b.length - 1); +assert.strictEqual(b.indexOf(buf_z), -1); +assert.strictEqual(b.indexOf(buf_empty), 0); +assert.strictEqual(b.indexOf(buf_empty, 1), 1); +assert.strictEqual(b.indexOf(buf_empty, b.length + 1), b.length); +assert.strictEqual(b.indexOf(buf_empty, Infinity), b.length); +assert.strictEqual(b.indexOf(0x61), 0); +assert.strictEqual(b.indexOf(0x61, 1), -1); +assert.strictEqual(b.indexOf(0x61, -1), -1); +assert.strictEqual(b.indexOf(0x61, -4), -1); +assert.strictEqual(b.indexOf(0x61, -b.length), 0); +assert.strictEqual(b.indexOf(0x61, NaN), 0); +assert.strictEqual(b.indexOf(0x61, -Infinity), 0); +assert.strictEqual(b.indexOf(0x61, Infinity), -1); +assert.strictEqual(b.indexOf(0x0), -1); + +// test offsets +assert.strictEqual(b.indexOf('d', 2), 3); +assert.strictEqual(b.indexOf('f', 5), 5); +assert.strictEqual(b.indexOf('f', -1), 5); +assert.strictEqual(b.indexOf('f', 6), -1); + +assert.strictEqual(b.indexOf(Buffer.from('d'), 2), 3); +assert.strictEqual(b.indexOf(Buffer.from('f'), 5), 5); +assert.strictEqual(b.indexOf(Buffer.from('f'), -1), 5); +assert.strictEqual(b.indexOf(Buffer.from('f'), 6), -1); + +// TODO(Soremwar) +// Enable again once encoding is taking into account when evaluating indexOf +// assert.strictEqual(Buffer.from('ff').indexOf(Buffer.from('f'), 1, 'ucs2'), -1); + +// Test invalid and uppercase encoding +assert.strictEqual(b.indexOf('b', 'utf8'), 1); +assert.strictEqual(b.indexOf('b', 'UTF8'), 1); +assert.strictEqual(b.indexOf('62', 'HEX'), 1); +assert.throws(() => b.indexOf('bad', 'enc'), /Unknown encoding: enc/); + +// test hex encoding +assert.strictEqual( + Buffer.from(b.toString('hex'), 'hex') + .indexOf('64', 0, 'hex'), + 3 +); +assert.strictEqual( + Buffer.from(b.toString('hex'), 'hex') + .indexOf(Buffer.from('64', 'hex'), 0, 'hex'), + 3 +); + +// Test base64 encoding +assert.strictEqual( + Buffer.from(b.toString('base64'), 'base64') + .indexOf('ZA==', 0, 'base64'), + 3 +); +assert.strictEqual( + Buffer.from(b.toString('base64'), 'base64') + .indexOf(Buffer.from('ZA==', 'base64'), 0, 'base64'), + 3 +); + +// Test base64url encoding +assert.strictEqual( + Buffer.from(b.toString('base64url'), 'base64url') + .indexOf('ZA==', 0, 'base64url'), + 3 +); + +// test ascii encoding +assert.strictEqual( + Buffer.from(b.toString('ascii'), 'ascii') + .indexOf('d', 0, 'ascii'), + 3 +); +assert.strictEqual( + Buffer.from(b.toString('ascii'), 'ascii') + .indexOf(Buffer.from('d', 'ascii'), 0, 'ascii'), + 3 +); + +// Test latin1 encoding +assert.strictEqual( + Buffer.from(b.toString('latin1'), 'latin1') + .indexOf('d', 0, 'latin1'), + 3 +); +assert.strictEqual( + Buffer.from(b.toString('latin1'), 'latin1') + .indexOf(Buffer.from('d', 'latin1'), 0, 'latin1'), + 3 +); +assert.strictEqual( + Buffer.from('aa\u00e8aa', 'latin1') + .indexOf('\u00e8', 'latin1'), + 2 +); +assert.strictEqual( + Buffer.from('\u00e8', 'latin1') + .indexOf('\u00e8', 'latin1'), + 0 +); +assert.strictEqual( + Buffer.from('\u00e8', 'latin1') + .indexOf(Buffer.from('\u00e8', 'latin1'), 'latin1'), + 0 +); + +// Test binary encoding +assert.strictEqual( + Buffer.from(b.toString('binary'), 'binary') + .indexOf('d', 0, 'binary'), + 3 +); +assert.strictEqual( + Buffer.from(b.toString('binary'), 'binary') + .indexOf(Buffer.from('d', 'binary'), 0, 'binary'), + 3 +); +assert.strictEqual( + Buffer.from('aa\u00e8aa', 'binary') + .indexOf('\u00e8', 'binary'), + 2 +); +assert.strictEqual( + Buffer.from('\u00e8', 'binary') + .indexOf('\u00e8', 'binary'), + 0 +); +assert.strictEqual( + Buffer.from('\u00e8', 'binary') + .indexOf(Buffer.from('\u00e8', 'binary'), 'binary'), + 0 +); + + +// Test optional offset with passed encoding +assert.strictEqual(Buffer.from('aaaa0').indexOf('30', 'hex'), 4); +assert.strictEqual(Buffer.from('aaaa00a').indexOf('3030', 'hex'), 4); + +{ + // Test usc2 and utf16le encoding + ['ucs2', 'utf16le'].forEach((encoding) => { + const twoByteString = Buffer.from( + '\u039a\u0391\u03a3\u03a3\u0395', encoding); + + assert.strictEqual(twoByteString.indexOf('\u0395', 4, encoding), 8); + assert.strictEqual(twoByteString.indexOf('\u03a3', -4, encoding), 6); + assert.strictEqual(twoByteString.indexOf('\u03a3', -6, encoding), 4); + assert.strictEqual(twoByteString.indexOf( + Buffer.from('\u03a3', encoding), -6, encoding), 4); + assert.strictEqual(-1, twoByteString.indexOf('\u03a3', -2, encoding)); + }); +} + +const mixedByteStringUcs2 = + Buffer.from('\u039a\u0391abc\u03a3\u03a3\u0395', 'ucs2'); +assert.strictEqual(mixedByteStringUcs2.indexOf('bc', 0, 'ucs2'), 6); +assert.strictEqual(mixedByteStringUcs2.indexOf('\u03a3', 0, 'ucs2'), 10); +assert.strictEqual(-1, mixedByteStringUcs2.indexOf('\u0396', 0, 'ucs2')); + +assert.strictEqual( + mixedByteStringUcs2.indexOf(Buffer.from('bc', 'ucs2'), 0, 'ucs2'), 6); +assert.strictEqual( + mixedByteStringUcs2.indexOf(Buffer.from('\u03a3', 'ucs2'), 0, 'ucs2'), 10); +assert.strictEqual( + -1, mixedByteStringUcs2.indexOf(Buffer.from('\u0396', 'ucs2'), 0, 'ucs2')); + +{ + const twoByteString = Buffer.from('\u039a\u0391\u03a3\u03a3\u0395', 'ucs2'); + + // Test single char pattern + assert.strictEqual(twoByteString.indexOf('\u039a', 0, 'ucs2'), 0); + let index = twoByteString.indexOf('\u0391', 0, 'ucs2'); + assert.strictEqual(index, 2, `Alpha - at index ${index}`); + index = twoByteString.indexOf('\u03a3', 0, 'ucs2'); + assert.strictEqual(index, 4, `First Sigma - at index ${index}`); + index = twoByteString.indexOf('\u03a3', 6, 'ucs2'); + assert.strictEqual(index, 6, `Second Sigma - at index ${index}`); + index = twoByteString.indexOf('\u0395', 0, 'ucs2'); + assert.strictEqual(index, 8, `Epsilon - at index ${index}`); + index = twoByteString.indexOf('\u0392', 0, 'ucs2'); + assert.strictEqual(-1, index, `Not beta - at index ${index}`); + + // Test multi-char pattern + index = twoByteString.indexOf('\u039a\u0391', 0, 'ucs2'); + assert.strictEqual(index, 0, `Lambda Alpha - at index ${index}`); + index = twoByteString.indexOf('\u0391\u03a3', 0, 'ucs2'); + assert.strictEqual(index, 2, `Alpha Sigma - at index ${index}`); + index = twoByteString.indexOf('\u03a3\u03a3', 0, 'ucs2'); + assert.strictEqual(index, 4, `Sigma Sigma - at index ${index}`); + index = twoByteString.indexOf('\u03a3\u0395', 0, 'ucs2'); + assert.strictEqual(index, 6, `Sigma Epsilon - at index ${index}`); +} + +const mixedByteStringUtf8 = Buffer.from('\u039a\u0391abc\u03a3\u03a3\u0395'); +assert.strictEqual(mixedByteStringUtf8.indexOf('bc'), 5); +assert.strictEqual(mixedByteStringUtf8.indexOf('bc', 5), 5); +assert.strictEqual(mixedByteStringUtf8.indexOf('bc', -8), 5); +assert.strictEqual(mixedByteStringUtf8.indexOf('\u03a3'), 7); +assert.strictEqual(mixedByteStringUtf8.indexOf('\u0396'), -1); + + +// Test complex string indexOf algorithms. Only trigger for long strings. +// Long string that isn't a simple repeat of a shorter string. +let longString = 'A'; +for (let i = 66; i < 76; i++) { // from 'B' to 'K' + longString = longString + String.fromCharCode(i) + longString; +} + +const longBufferString = Buffer.from(longString); + +// Pattern of 15 chars, repeated every 16 chars in long +let pattern = 'ABACABADABACABA'; +for (let i = 0; i < longBufferString.length - pattern.length; i += 7) { + const index = longBufferString.indexOf(pattern, i); + assert.strictEqual((i + 15) & ~0xf, index, + `Long ABACABA...-string at index ${i}`); +} + +let index = longBufferString.indexOf('AJABACA'); +assert.strictEqual(index, 510, `Long AJABACA, First J - at index ${index}`); +index = longBufferString.indexOf('AJABACA', 511); +assert.strictEqual(index, 1534, `Long AJABACA, Second J - at index ${index}`); + +pattern = 'JABACABADABACABA'; +index = longBufferString.indexOf(pattern); +assert.strictEqual(index, 511, `Long JABACABA..., First J - at index ${index}`); +index = longBufferString.indexOf(pattern, 512); +assert.strictEqual( + index, 1535, `Long JABACABA..., Second J - at index ${index}`); + +// Search for a non-ASCII string in a pure ASCII string. +const asciiString = Buffer.from( + 'arglebargleglopglyfarglebargleglopglyfarglebargleglopglyf'); +assert.strictEqual(-1, asciiString.indexOf('\x2061')); +assert.strictEqual(asciiString.indexOf('leb', 0), 3); + +// Search in string containing many non-ASCII chars. +const allCodePoints = []; +for (let i = 0; i < 65534; i++) allCodePoints[i] = i; +const allCharsString = String.fromCharCode.apply(String, allCodePoints) + + String.fromCharCode(65534, 65535); +const allCharsBufferUtf8 = Buffer.from(allCharsString); +const allCharsBufferUcs2 = Buffer.from(allCharsString, 'ucs2'); + +// Search for string long enough to trigger complex search with ASCII pattern +// and UC16 subject. +assert.strictEqual(-1, allCharsBufferUtf8.indexOf('notfound')); +assert.strictEqual(-1, allCharsBufferUcs2.indexOf('notfound')); + +// Needle is longer than haystack, but only because it's encoded as UTF-16 +assert.strictEqual(Buffer.from('aaaa').indexOf('a'.repeat(4), 'ucs2'), -1); + +assert.strictEqual(Buffer.from('aaaa').indexOf('a'.repeat(4), 'utf8'), 0); +assert.strictEqual(Buffer.from('aaaa').indexOf('你好', 'ucs2'), -1); + +// Haystack has odd length, but the needle is UCS2. +assert.strictEqual(Buffer.from('aaaaa').indexOf('b', 'ucs2'), -1); + +{ + // Find substrings in Utf8. + const lengths = [1, 3, 15]; // Single char, simple and complex. + const indices = [0x5, 0x60, 0x400, 0x680, 0x7ee, 0xFF02, 0x16610, 0x2f77b]; + for (let lengthIndex = 0; lengthIndex < lengths.length; lengthIndex++) { + for (let i = 0; i < indices.length; i++) { + const index = indices[i]; + let length = lengths[lengthIndex]; + + if (index + length > 0x7F) { + length = 2 * length; + } + + if (index + length > 0x7FF) { + length = 3 * length; + } + + if (index + length > 0xFFFF) { + length = 4 * length; + } + + const patternBufferUtf8 = allCharsBufferUtf8.slice(index, index + length); + assert.strictEqual(index, allCharsBufferUtf8.indexOf(patternBufferUtf8)); + + const patternStringUtf8 = patternBufferUtf8.toString(); + assert.strictEqual(index, allCharsBufferUtf8.indexOf(patternStringUtf8)); + } + } +} + +// TODO(Soremwar) +// Enable again once encoding is taking into account when evaluating indexOf +// { +// // Find substrings in Usc2. +// const lengths = [2, 4, 16]; // Single char, simple and complex. +// const indices = [0x5, 0x65, 0x105, 0x205, 0x285, 0x2005, 0x2085, 0xfff0]; +// for (let lengthIndex = 0; lengthIndex < lengths.length; lengthIndex++) { +// for (let i = 0; i < indices.length; i++) { +// const index = indices[i] * 2; +// const length = lengths[lengthIndex]; + +// const patternBufferUcs2 = +// allCharsBufferUcs2.slice(index, index + length); +// assert.strictEqual( +// index, allCharsBufferUcs2.indexOf(patternBufferUcs2, 0, 'ucs2')); + +// const patternStringUcs2 = patternBufferUcs2.toString('ucs2'); +// assert.strictEqual( +// index, allCharsBufferUcs2.indexOf(patternStringUcs2, 0, 'ucs2')); +// } +// } +// } + +[ + () => {}, + {}, + [], +].forEach((val) => { + assert.throws( + () => b.indexOf(val), + { + code: 'ERR_INVALID_ARG_TYPE', + name: 'TypeError', + message: 'The "value" argument must be one of type number or string ' + + 'or an instance of Buffer or Uint8Array.' + + common.invalidArgTypeHelper(val) + } + ); +}); + +// Test weird offset arguments. +// The following offsets coerce to NaN or 0, searching the whole Buffer +assert.strictEqual(b.indexOf('b', undefined), 1); +assert.strictEqual(b.indexOf('b', {}), 1); +assert.strictEqual(b.indexOf('b', 0), 1); +assert.strictEqual(b.indexOf('b', null), 1); +assert.strictEqual(b.indexOf('b', []), 1); + +// The following offset coerces to 2, in other words +[2] === 2 +assert.strictEqual(b.indexOf('b', [2]), -1); + +// Behavior should match String.indexOf() +assert.strictEqual( + b.indexOf('b', undefined), + s.indexOf('b', undefined)); +assert.strictEqual( + b.indexOf('b', {}), + s.indexOf('b', {})); +assert.strictEqual( + b.indexOf('b', 0), + s.indexOf('b', 0)); +assert.strictEqual( + b.indexOf('b', null), + s.indexOf('b', null)); +assert.strictEqual( + b.indexOf('b', []), + s.indexOf('b', [])); +assert.strictEqual( + b.indexOf('b', [2]), + s.indexOf('b', [2])); + +// All code for handling encodings is shared between Buffer.indexOf and +// Buffer.lastIndexOf, so only testing the separate lastIndexOf semantics. + +// Test lastIndexOf basic functionality; Buffer b contains 'abcdef'. +// lastIndexOf string: +assert.strictEqual(b.lastIndexOf('a'), 0); +assert.strictEqual(b.lastIndexOf('a', 1), 0); +assert.strictEqual(b.lastIndexOf('b', 1), 1); +assert.strictEqual(b.lastIndexOf('c', 1), -1); +assert.strictEqual(b.lastIndexOf('a', -1), 0); +assert.strictEqual(b.lastIndexOf('a', -4), 0); +assert.strictEqual(b.lastIndexOf('a', -b.length), 0); +assert.strictEqual(b.lastIndexOf('a', -b.length - 1), -1); +assert.strictEqual(b.lastIndexOf('a', NaN), 0); +assert.strictEqual(b.lastIndexOf('a', -Infinity), -1); +assert.strictEqual(b.lastIndexOf('a', Infinity), 0); +// lastIndexOf Buffer: +assert.strictEqual(b.lastIndexOf(buf_a), 0); +assert.strictEqual(b.lastIndexOf(buf_a, 1), 0); +assert.strictEqual(b.lastIndexOf(buf_a, -1), 0); +assert.strictEqual(b.lastIndexOf(buf_a, -4), 0); +assert.strictEqual(b.lastIndexOf(buf_a, -b.length), 0); +assert.strictEqual(b.lastIndexOf(buf_a, -b.length - 1), -1); +assert.strictEqual(b.lastIndexOf(buf_a, NaN), 0); +assert.strictEqual(b.lastIndexOf(buf_a, -Infinity), -1); +assert.strictEqual(b.lastIndexOf(buf_a, Infinity), 0); +assert.strictEqual(b.lastIndexOf(buf_bc), 1); +assert.strictEqual(b.lastIndexOf(buf_bc, 2), 1); +assert.strictEqual(b.lastIndexOf(buf_bc, -1), 1); +assert.strictEqual(b.lastIndexOf(buf_bc, -3), 1); +assert.strictEqual(b.lastIndexOf(buf_bc, -5), 1); +assert.strictEqual(b.lastIndexOf(buf_bc, -6), -1); +assert.strictEqual(b.lastIndexOf(buf_bc, NaN), 1); +assert.strictEqual(b.lastIndexOf(buf_bc, -Infinity), -1); +assert.strictEqual(b.lastIndexOf(buf_bc, Infinity), 1); +assert.strictEqual(b.lastIndexOf(buf_f), b.length - 1); +assert.strictEqual(b.lastIndexOf(buf_z), -1); +assert.strictEqual(b.lastIndexOf(buf_empty), b.length); +assert.strictEqual(b.lastIndexOf(buf_empty, 1), 1); +assert.strictEqual(b.lastIndexOf(buf_empty, b.length + 1), b.length); +assert.strictEqual(b.lastIndexOf(buf_empty, Infinity), b.length); +// lastIndexOf number: +assert.strictEqual(b.lastIndexOf(0x61), 0); +assert.strictEqual(b.lastIndexOf(0x61, 1), 0); +assert.strictEqual(b.lastIndexOf(0x61, -1), 0); +assert.strictEqual(b.lastIndexOf(0x61, -4), 0); +assert.strictEqual(b.lastIndexOf(0x61, -b.length), 0); +assert.strictEqual(b.lastIndexOf(0x61, -b.length - 1), -1); +assert.strictEqual(b.lastIndexOf(0x61, NaN), 0); +assert.strictEqual(b.lastIndexOf(0x61, -Infinity), -1); +assert.strictEqual(b.lastIndexOf(0x61, Infinity), 0); +assert.strictEqual(b.lastIndexOf(0x0), -1); + +// Test weird offset arguments. +// The following offsets coerce to NaN, searching the whole Buffer +assert.strictEqual(b.lastIndexOf('b', undefined), 1); +assert.strictEqual(b.lastIndexOf('b', {}), 1); + +// The following offsets coerce to 0 +assert.strictEqual(b.lastIndexOf('b', 0), -1); +assert.strictEqual(b.lastIndexOf('b', null), -1); +assert.strictEqual(b.lastIndexOf('b', []), -1); + +// The following offset coerces to 2, in other words +[2] === 2 +assert.strictEqual(b.lastIndexOf('b', [2]), 1); + +// Behavior should match String.lastIndexOf() +assert.strictEqual( + b.lastIndexOf('b', undefined), + s.lastIndexOf('b', undefined)); +assert.strictEqual( + b.lastIndexOf('b', {}), + s.lastIndexOf('b', {})); +assert.strictEqual( + b.lastIndexOf('b', 0), + s.lastIndexOf('b', 0)); +assert.strictEqual( + b.lastIndexOf('b', null), + s.lastIndexOf('b', null)); +assert.strictEqual( + b.lastIndexOf('b', []), + s.lastIndexOf('b', [])); +assert.strictEqual( + b.lastIndexOf('b', [2]), + s.lastIndexOf('b', [2])); + +// Test needles longer than the haystack. +assert.strictEqual(b.lastIndexOf('aaaaaaaaaaaaaaa', 'ucs2'), -1); +assert.strictEqual(b.lastIndexOf('aaaaaaaaaaaaaaa', 'utf8'), -1); +assert.strictEqual(b.lastIndexOf('aaaaaaaaaaaaaaa', 'latin1'), -1); +assert.strictEqual(b.lastIndexOf('aaaaaaaaaaaaaaa', 'binary'), -1); +assert.strictEqual(b.lastIndexOf(Buffer.from('aaaaaaaaaaaaaaa')), -1); +assert.strictEqual(b.lastIndexOf('aaaaaaaaaaaaaaa', 2, 'ucs2'), -1); +assert.strictEqual(b.lastIndexOf('aaaaaaaaaaaaaaa', 3, 'utf8'), -1); +assert.strictEqual(b.lastIndexOf('aaaaaaaaaaaaaaa', 5, 'latin1'), -1); +assert.strictEqual(b.lastIndexOf('aaaaaaaaaaaaaaa', 5, 'binary'), -1); +assert.strictEqual(b.lastIndexOf(Buffer.from('aaaaaaaaaaaaaaa'), 7), -1); + +// 你好 expands to a total of 6 bytes using UTF-8 and 4 bytes using UTF-16 +assert.strictEqual(buf_bc.lastIndexOf('你好', 'ucs2'), -1); +assert.strictEqual(buf_bc.lastIndexOf('你好', 'utf8'), -1); +assert.strictEqual(buf_bc.lastIndexOf('你好', 'latin1'), -1); +assert.strictEqual(buf_bc.lastIndexOf('你好', 'binary'), -1); +assert.strictEqual(buf_bc.lastIndexOf(Buffer.from('你好')), -1); +assert.strictEqual(buf_bc.lastIndexOf('你好', 2, 'ucs2'), -1); +assert.strictEqual(buf_bc.lastIndexOf('你好', 3, 'utf8'), -1); +assert.strictEqual(buf_bc.lastIndexOf('你好', 5, 'latin1'), -1); +assert.strictEqual(buf_bc.lastIndexOf('你好', 5, 'binary'), -1); +assert.strictEqual(buf_bc.lastIndexOf(Buffer.from('你好'), 7), -1); + +// Test lastIndexOf on a longer buffer: +const bufferString = Buffer.from('a man a plan a canal panama'); +assert.strictEqual(bufferString.lastIndexOf('canal'), 15); +assert.strictEqual(bufferString.lastIndexOf('panama'), 21); +assert.strictEqual(bufferString.lastIndexOf('a man a plan a canal panama'), 0); +assert.strictEqual(-1, bufferString.lastIndexOf('a man a plan a canal mexico')); +assert.strictEqual(-1, bufferString + .lastIndexOf('a man a plan a canal mexico city')); +assert.strictEqual(-1, bufferString.lastIndexOf(Buffer.from('a'.repeat(1000)))); +assert.strictEqual(bufferString.lastIndexOf('a man a plan', 4), 0); +assert.strictEqual(bufferString.lastIndexOf('a '), 13); +assert.strictEqual(bufferString.lastIndexOf('a ', 13), 13); +assert.strictEqual(bufferString.lastIndexOf('a ', 12), 6); +assert.strictEqual(bufferString.lastIndexOf('a ', 5), 0); +assert.strictEqual(bufferString.lastIndexOf('a ', -1), 13); +assert.strictEqual(bufferString.lastIndexOf('a ', -27), 0); +assert.strictEqual(-1, bufferString.lastIndexOf('a ', -28)); + +// Test lastIndexOf for the case that the first character can be found, +// but in a part of the buffer that does not make search to search +// due do length constraints. +const abInUCS2 = Buffer.from('ab', 'ucs2'); +assert.strictEqual(-1, Buffer.from('µaaaa¶bbbb', 'latin1').lastIndexOf('µ')); +assert.strictEqual(-1, Buffer.from('µaaaa¶bbbb', 'binary').lastIndexOf('µ')); +assert.strictEqual(-1, Buffer.from('bc').lastIndexOf('ab')); +assert.strictEqual(-1, Buffer.from('abc').lastIndexOf('qa')); +assert.strictEqual(-1, Buffer.from('abcdef').lastIndexOf('qabc')); +assert.strictEqual(-1, Buffer.from('bc').lastIndexOf(Buffer.from('ab'))); +assert.strictEqual(-1, Buffer.from('bc', 'ucs2').lastIndexOf('ab', 'ucs2')); +assert.strictEqual(-1, Buffer.from('bc', 'ucs2').lastIndexOf(abInUCS2)); + +assert.strictEqual(Buffer.from('abc').lastIndexOf('ab'), 0); +assert.strictEqual(Buffer.from('abc').lastIndexOf('ab', 1), 0); +assert.strictEqual(Buffer.from('abc').lastIndexOf('ab', 2), 0); +assert.strictEqual(Buffer.from('abc').lastIndexOf('ab', 3), 0); + +// The above tests test the LINEAR and SINGLE-CHAR strategies. +// Now, we test the BOYER-MOORE-HORSPOOL strategy. +// Test lastIndexOf on a long buffer w multiple matches: +pattern = 'JABACABADABACABA'; +assert.strictEqual(longBufferString.lastIndexOf(pattern), 1535); +assert.strictEqual(longBufferString.lastIndexOf(pattern, 1535), 1535); +assert.strictEqual(longBufferString.lastIndexOf(pattern, 1534), 511); + +// Finally, give it a really long input to trigger fallback from BMH to +// regular BOYER-MOORE (which has better worst-case complexity). + +// Generate a really long Thue-Morse sequence of 'yolo' and 'swag', +// "yolo swag swag yolo swag yolo yolo swag" ..., goes on for about 5MB. +// This is hard to search because it all looks similar, but never repeats. + +// countBits returns the number of bits in the binary representation of n. +function countBits(n) { + let count; + for (count = 0; n > 0; count++) { + n = n & (n - 1); // remove top bit + } + return count; +} +const parts = []; +for (let i = 0; i < 1000000; i++) { + parts.push((countBits(i) % 2 === 0) ? 'yolo' : 'swag'); +} +const reallyLong = Buffer.from(parts.join(' ')); +assert.strictEqual(reallyLong.slice(0, 19).toString(), 'yolo swag swag yolo'); + +// Expensive reverse searches. Stress test lastIndexOf: +pattern = reallyLong.slice(0, 100000); // First 1/50th of the pattern. +assert.strictEqual(reallyLong.lastIndexOf(pattern), 4751360); +assert.strictEqual(reallyLong.lastIndexOf(pattern, 4000000), 3932160); +assert.strictEqual(reallyLong.lastIndexOf(pattern, 3000000), 2949120); +pattern = reallyLong.slice(100000, 200000); // Second 1/50th. +assert.strictEqual(reallyLong.lastIndexOf(pattern), 4728480); +pattern = reallyLong.slice(0, 1000000); // First 1/5th. +assert.strictEqual(reallyLong.lastIndexOf(pattern), 3932160); +pattern = reallyLong.slice(0, 2000000); // first 2/5ths. +assert.strictEqual(reallyLong.lastIndexOf(pattern), 0); + +// Test truncation of Number arguments to uint8 +// TODO(Soremwar) +// Enable once multi byte number search is available +// { +// const buf = Buffer.from('this is a test'); +// assert.strictEqual(buf.indexOf(0x6973), 3); +// assert.strictEqual(buf.indexOf(0x697320), 4); +// assert.strictEqual(buf.indexOf(0x69732069), 2); +// assert.strictEqual(buf.indexOf(0x697374657374), 0); +// assert.strictEqual(buf.indexOf(0x69737374), 0); +// assert.strictEqual(buf.indexOf(0x69737465), 11); +// assert.strictEqual(buf.indexOf(0x69737465), 11); +// assert.strictEqual(buf.indexOf(-140), 0); +// assert.strictEqual(buf.indexOf(-152), 1); +// assert.strictEqual(buf.indexOf(0xff), -1); +// assert.strictEqual(buf.indexOf(0xffff), -1); +// } + +// Test that Uint8Array arguments are okay. +{ + const needle = new Uint8Array([ 0x66, 0x6f, 0x6f ]); + const haystack = Buffer.from('a foo b foo'); + assert.strictEqual(haystack.indexOf(needle), 2); + assert.strictEqual(haystack.lastIndexOf(needle), haystack.length - 3); +} + +// Avoid abort because of invalid usage +// see https://github.com/nodejs/node/issues/32753 +{ + assert.throws(() => { + const buffer = require('buffer'); + new buffer.Buffer.prototype.lastIndexOf(1, 'str'); + }, { + code: 'ERR_INVALID_ARG_TYPE', + name: 'TypeError', + message: 'The "buffer" argument must be an instance of Buffer, ' + + 'TypedArray, or DataView. ' + + 'Received an instance of lastIndexOf' + }); +} diff --git a/cli/tests/node_compat/test/parallel/test-buffer-inheritance.js b/cli/tests/node_compat/test/parallel/test-buffer-inheritance.js new file mode 100644 index 00000000000000..fb22a19a9b6665 --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-buffer-inheritance.js @@ -0,0 +1,46 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +'use strict'; + +require('../common'); +const assert = require('assert'); + + +function T(n) { + const ui8 = new Uint8Array(n); + Object.setPrototypeOf(ui8, T.prototype); + return ui8; +} +Object.setPrototypeOf(T.prototype, Buffer.prototype); +Object.setPrototypeOf(T, Buffer); + +T.prototype.sum = function sum() { + let cntr = 0; + for (let i = 0; i < this.length; i++) + cntr += this[i]; + return cntr; +}; + + +const vals = [new T(4), T(4)]; + +vals.forEach(function(t) { + assert.strictEqual(t.constructor, T); + assert.strictEqual(Object.getPrototypeOf(t), T.prototype); + assert.strictEqual(Object.getPrototypeOf(Object.getPrototypeOf(t)), + Buffer.prototype); + + t.fill(5); + let cntr = 0; + for (let i = 0; i < t.length; i++) + cntr += t[i]; + assert.strictEqual(cntr, t.length * 5); + + // Check this does not throw + t.toString(); +}); diff --git a/cli/tests/node_compat/test/parallel/test-buffer-isencoding.js b/cli/tests/node_compat/test/parallel/test-buffer-isencoding.js new file mode 100644 index 00000000000000..f6fdfd8f9a50fe --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-buffer-isencoding.js @@ -0,0 +1,45 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +'use strict'; + +require('../common'); +const assert = require('assert'); + +[ + 'hex', + 'utf8', + 'utf-8', + 'ascii', + 'latin1', + 'binary', + 'base64', + 'base64url', + 'ucs2', + 'ucs-2', + 'utf16le', + 'utf-16le', +].forEach((enc) => { + assert.strictEqual(Buffer.isEncoding(enc), true); +}); + +[ + 'utf9', + 'utf-7', + 'Unicode-FTW', + 'new gnu gun', + false, + NaN, + {}, + Infinity, + [], + 1, + 0, + -1, +].forEach((enc) => { + assert.strictEqual(Buffer.isEncoding(enc), false); +}); diff --git a/cli/tests/node_compat/test/parallel/test-buffer-iterator.js b/cli/tests/node_compat/test/parallel/test-buffer-iterator.js new file mode 100644 index 00000000000000..a93bb161a289e3 --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-buffer-iterator.js @@ -0,0 +1,69 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +'use strict'; +require('../common'); +const assert = require('assert'); + +const buffer = Buffer.from([1, 2, 3, 4, 5]); +let arr; +let b; + +// Buffers should be iterable + +arr = []; + +for (b of buffer) + arr.push(b); + +assert.deepStrictEqual(arr, [1, 2, 3, 4, 5]); + + +// Buffer iterators should be iterable + +arr = []; + +for (b of buffer[Symbol.iterator]()) + arr.push(b); + +assert.deepStrictEqual(arr, [1, 2, 3, 4, 5]); + + +// buffer#values() should return iterator for values + +arr = []; + +for (b of buffer.values()) + arr.push(b); + +assert.deepStrictEqual(arr, [1, 2, 3, 4, 5]); + + +// buffer#keys() should return iterator for keys + +arr = []; + +for (b of buffer.keys()) + arr.push(b); + +assert.deepStrictEqual(arr, [0, 1, 2, 3, 4]); + + +// buffer#entries() should return iterator for entries + +arr = []; + +for (b of buffer.entries()) + arr.push(b); + +assert.deepStrictEqual(arr, [ + [0, 1], + [1, 2], + [2, 3], + [3, 4], + [4, 5], +]); diff --git a/cli/tests/node_compat/test/parallel/test-buffer-new.js b/cli/tests/node_compat/test/parallel/test-buffer-new.js new file mode 100644 index 00000000000000..c79e80b50c57bb --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-buffer-new.js @@ -0,0 +1,18 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +'use strict'; + +require('../common'); +const assert = require('assert'); + +assert.throws(() => new Buffer(42, 'utf8'), { + code: 'ERR_INVALID_ARG_TYPE', + name: 'TypeError', + message: 'The "string" argument must be of type string. Received type ' + + 'number (42)' +}); diff --git a/cli/tests/node_compat/test/parallel/test-buffer-no-negative-allocation.js b/cli/tests/node_compat/test/parallel/test-buffer-no-negative-allocation.js new file mode 100644 index 00000000000000..742d298ba8fc58 --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-buffer-no-negative-allocation.js @@ -0,0 +1,45 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +'use strict'; + +require('../common'); +const assert = require('assert'); +const { SlowBuffer } = require('buffer'); + +const msg = { + code: 'ERR_INVALID_ARG_VALUE', + name: 'RangeError', + message: /^The argument 'size' is invalid\. Received [^"]*$/ +}; + +// Test that negative Buffer length inputs throw errors. + +assert.throws(() => Buffer(-Buffer.poolSize), msg); +assert.throws(() => Buffer(-100), msg); +assert.throws(() => Buffer(-1), msg); +assert.throws(() => Buffer(NaN), msg); + +assert.throws(() => Buffer.alloc(-Buffer.poolSize), msg); +assert.throws(() => Buffer.alloc(-100), msg); +assert.throws(() => Buffer.alloc(-1), msg); +assert.throws(() => Buffer.alloc(NaN), msg); + +assert.throws(() => Buffer.allocUnsafe(-Buffer.poolSize), msg); +assert.throws(() => Buffer.allocUnsafe(-100), msg); +assert.throws(() => Buffer.allocUnsafe(-1), msg); +assert.throws(() => Buffer.allocUnsafe(NaN), msg); + +assert.throws(() => Buffer.allocUnsafeSlow(-Buffer.poolSize), msg); +assert.throws(() => Buffer.allocUnsafeSlow(-100), msg); +assert.throws(() => Buffer.allocUnsafeSlow(-1), msg); +assert.throws(() => Buffer.allocUnsafeSlow(NaN), msg); + +assert.throws(() => SlowBuffer(-Buffer.poolSize), msg); +assert.throws(() => SlowBuffer(-100), msg); +assert.throws(() => SlowBuffer(-1), msg); +assert.throws(() => SlowBuffer(NaN), msg); diff --git a/cli/tests/node_compat/test/parallel/test-buffer-nopendingdep-map.js b/cli/tests/node_compat/test/parallel/test-buffer-nopendingdep-map.js new file mode 100644 index 00000000000000..5a6abca86a1cd3 --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-buffer-nopendingdep-map.js @@ -0,0 +1,20 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +// Flags: --no-warnings --pending-deprecation +'use strict'; + +const common = require('../common'); + +process.on('warning', common.mustNotCall('A warning should not be emitted')); + +// With the --pending-deprecation flag, the deprecation warning for +// new Buffer() should not be emitted when Uint8Array methods are called. + +Buffer.from('abc').map((i) => i); +Buffer.from('abc').filter((i) => i); +Buffer.from('abc').slice(1, 2); diff --git a/cli/tests/node_compat/test/parallel/test-buffer-of-no-deprecation.js b/cli/tests/node_compat/test/parallel/test-buffer-of-no-deprecation.js new file mode 100644 index 00000000000000..b98bc25a3584d7 --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-buffer-of-no-deprecation.js @@ -0,0 +1,14 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +'use strict'; + +const common = require('../common'); + +process.on('warning', common.mustNotCall()); + +Buffer.of(0, 1); diff --git a/cli/tests/node_compat/test/parallel/test-buffer-over-max-length.js b/cli/tests/node_compat/test/parallel/test-buffer-over-max-length.js new file mode 100644 index 00000000000000..4bcbfeb9be17c1 --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-buffer-over-max-length.js @@ -0,0 +1,37 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +'use strict'; +require('../common'); + +const assert = require('assert'); + +const buffer = require('buffer'); +const SlowBuffer = buffer.SlowBuffer; + +const kMaxLength = buffer.kMaxLength; +const bufferMaxSizeMsg = { + code: 'ERR_INVALID_ARG_VALUE', + name: 'RangeError', + message: /^The argument 'size' is invalid\. Received [^"]*$/ +}; + +assert.throws(() => Buffer((-1 >>> 0) + 2), bufferMaxSizeMsg); +assert.throws(() => SlowBuffer((-1 >>> 0) + 2), bufferMaxSizeMsg); +assert.throws(() => Buffer.alloc((-1 >>> 0) + 2), bufferMaxSizeMsg); +assert.throws(() => Buffer.allocUnsafe((-1 >>> 0) + 2), bufferMaxSizeMsg); +assert.throws(() => Buffer.allocUnsafeSlow((-1 >>> 0) + 2), bufferMaxSizeMsg); + +assert.throws(() => Buffer(kMaxLength + 1), bufferMaxSizeMsg); +assert.throws(() => SlowBuffer(kMaxLength + 1), bufferMaxSizeMsg); +assert.throws(() => Buffer.alloc(kMaxLength + 1), bufferMaxSizeMsg); +assert.throws(() => Buffer.allocUnsafe(kMaxLength + 1), bufferMaxSizeMsg); +assert.throws(() => Buffer.allocUnsafeSlow(kMaxLength + 1), bufferMaxSizeMsg); + +// issue GH-4331 +assert.throws(() => Buffer.allocUnsafe(0x100000001), bufferMaxSizeMsg); +assert.throws(() => Buffer.allocUnsafe(0xFFFFFFFFF), bufferMaxSizeMsg); diff --git a/cli/tests/node_compat/test/parallel/test-buffer-parent-property.js b/cli/tests/node_compat/test/parallel/test-buffer-parent-property.js new file mode 100644 index 00000000000000..c433ec04a969f5 --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-buffer-parent-property.js @@ -0,0 +1,28 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +'use strict'; + +// Fix for https://github.com/nodejs/node/issues/8266 +// +// Zero length Buffer objects should expose the `buffer` property of the +// TypedArrays, via the `parent` property. +require('../common'); +const assert = require('assert'); + +// If the length of the buffer object is zero +assert((new Buffer(0)).parent instanceof ArrayBuffer); + +// If the length of the buffer object is equal to the underlying ArrayBuffer +assert((new Buffer(Buffer.poolSize)).parent instanceof ArrayBuffer); + +// Same as the previous test, but with user created buffer +const arrayBuffer = new ArrayBuffer(0); +assert.strictEqual(new Buffer(arrayBuffer).parent, arrayBuffer); +assert.strictEqual(new Buffer(arrayBuffer).buffer, arrayBuffer); +assert.strictEqual(Buffer.from(arrayBuffer).parent, arrayBuffer); +assert.strictEqual(Buffer.from(arrayBuffer).buffer, arrayBuffer); diff --git a/cli/tests/node_compat/test/parallel/test-buffer-read.js b/cli/tests/node_compat/test/parallel/test-buffer-read.js new file mode 100644 index 00000000000000..6645250f5433a0 --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-buffer-read.js @@ -0,0 +1,113 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +'use strict'; +require('../common'); +const assert = require('assert'); + +// Testing basic buffer read functions +const buf = Buffer.from([0xa4, 0xfd, 0x48, 0xea, 0xcf, 0xff, 0xd9, 0x01, 0xde]); + +function read(buff, funx, args, expected) { + assert.strictEqual(buff[funx](...args), expected); + assert.throws( + () => buff[funx](-1, args[1]), + { code: 'ERR_OUT_OF_RANGE' } + ); +} + +// Testing basic functionality of readDoubleBE() and readDoubleLE() +read(buf, 'readDoubleBE', [1], -3.1827727774563287e+295); +read(buf, 'readDoubleLE', [1], -6.966010051009108e+144); + +// Testing basic functionality of readFloatBE() and readFloatLE() +read(buf, 'readFloatBE', [1], -1.6691549692541768e+37); +read(buf, 'readFloatLE', [1], -7861303808); + +// Testing basic functionality of readInt8() +read(buf, 'readInt8', [1], -3); + +// Testing basic functionality of readInt16BE() and readInt16LE() +read(buf, 'readInt16BE', [1], -696); +read(buf, 'readInt16LE', [1], 0x48fd); + +// Testing basic functionality of readInt32BE() and readInt32LE() +read(buf, 'readInt32BE', [1], -45552945); +read(buf, 'readInt32LE', [1], -806729475); + +// Testing basic functionality of readIntBE() and readIntLE() +read(buf, 'readIntBE', [1, 1], -3); +read(buf, 'readIntLE', [2, 1], 0x48); + +// Testing basic functionality of readUInt8() +read(buf, 'readUInt8', [1], 0xfd); + +// Testing basic functionality of readUInt16BE() and readUInt16LE() +read(buf, 'readUInt16BE', [2], 0x48ea); +read(buf, 'readUInt16LE', [2], 0xea48); + +// Testing basic functionality of readUInt32BE() and readUInt32LE() +read(buf, 'readUInt32BE', [1], 0xfd48eacf); +read(buf, 'readUInt32LE', [1], 0xcfea48fd); + +// Testing basic functionality of readUIntBE() and readUIntLE() +read(buf, 'readUIntBE', [2, 2], 0x48ea); +read(buf, 'readUIntLE', [2, 2], 0xea48); + +// Error name and message +const OOR_ERROR = +{ + name: 'RangeError' +}; + +const OOB_ERROR = +{ + name: 'RangeError', + message: 'Attempt to access memory outside buffer bounds' +}; + +// Attempt to overflow buffers, similar to previous bug in array buffers +assert.throws( + () => Buffer.allocUnsafe(8).readFloatBE(0xffffffff), OOR_ERROR); + +assert.throws( + () => Buffer.allocUnsafe(8).readFloatLE(0xffffffff), OOR_ERROR); + +// Ensure negative values can't get past offset +assert.throws( + () => Buffer.allocUnsafe(8).readFloatBE(-1), OOR_ERROR); +assert.throws( + () => Buffer.allocUnsafe(8).readFloatLE(-1), OOR_ERROR); + +// Offset checks +{ + const buf = Buffer.allocUnsafe(0); + + assert.throws( + () => buf.readUInt8(0), OOB_ERROR); + assert.throws( + () => buf.readInt8(0), OOB_ERROR); +} + +[16, 32].forEach((bit) => { + const buf = Buffer.allocUnsafe(bit / 8 - 1); + [`Int${bit}B`, `Int${bit}L`, `UInt${bit}B`, `UInt${bit}L`].forEach((fn) => { + assert.throws( + () => buf[`read${fn}E`](0), OOB_ERROR); + }); +}); + +[16, 32].forEach((bits) => { + const buf = Buffer.from([0xFF, 0xFF, 0xFF, 0xFF]); + ['LE', 'BE'].forEach((endian) => { + assert.strictEqual(buf[`readUInt${bits}${endian}`](0), + (0xFFFFFFFF >>> (32 - bits))); + + assert.strictEqual(buf[`readInt${bits}${endian}`](0), + (0xFFFFFFFF >> (32 - bits))); + }); +}); diff --git a/cli/tests/node_compat/test/parallel/test-buffer-readdouble.js b/cli/tests/node_compat/test/parallel/test-buffer-readdouble.js new file mode 100644 index 00000000000000..2cd2b82eec859c --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-buffer-readdouble.js @@ -0,0 +1,151 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +'use strict'; + +require('../common'); +const assert = require('assert'); + +// Test (64 bit) double +const buffer = Buffer.allocUnsafe(8); + +buffer[0] = 0x55; +buffer[1] = 0x55; +buffer[2] = 0x55; +buffer[3] = 0x55; +buffer[4] = 0x55; +buffer[5] = 0x55; +buffer[6] = 0xd5; +buffer[7] = 0x3f; +assert.strictEqual(buffer.readDoubleBE(0), 1.1945305291680097e+103); +assert.strictEqual(buffer.readDoubleLE(0), 0.3333333333333333); + +buffer[0] = 1; +buffer[1] = 0; +buffer[2] = 0; +buffer[3] = 0; +buffer[4] = 0; +buffer[5] = 0; +buffer[6] = 0xf0; +buffer[7] = 0x3f; +assert.strictEqual(buffer.readDoubleBE(0), 7.291122019655968e-304); +assert.strictEqual(buffer.readDoubleLE(0), 1.0000000000000002); + +buffer[0] = 2; +assert.strictEqual(buffer.readDoubleBE(0), 4.778309726801735e-299); +assert.strictEqual(buffer.readDoubleLE(0), 1.0000000000000004); + +buffer[0] = 1; +buffer[6] = 0; +buffer[7] = 0; +// eslint-disable-next-line no-loss-of-precision +assert.strictEqual(buffer.readDoubleBE(0), 7.291122019556398e-304); +assert.strictEqual(buffer.readDoubleLE(0), 5e-324); + +buffer[0] = 0xff; +buffer[1] = 0xff; +buffer[2] = 0xff; +buffer[3] = 0xff; +buffer[4] = 0xff; +buffer[5] = 0xff; +buffer[6] = 0x0f; +buffer[7] = 0x00; +assert.ok(Number.isNaN(buffer.readDoubleBE(0))); +assert.strictEqual(buffer.readDoubleLE(0), 2.225073858507201e-308); + +buffer[6] = 0xef; +buffer[7] = 0x7f; +assert.ok(Number.isNaN(buffer.readDoubleBE(0))); +assert.strictEqual(buffer.readDoubleLE(0), 1.7976931348623157e+308); + +buffer[0] = 0; +buffer[1] = 0; +buffer[2] = 0; +buffer[3] = 0; +buffer[4] = 0; +buffer[5] = 0; +buffer[6] = 0xf0; +buffer[7] = 0x3f; +assert.strictEqual(buffer.readDoubleBE(0), 3.03865e-319); +assert.strictEqual(buffer.readDoubleLE(0), 1); + +buffer[6] = 0; +buffer[7] = 0x40; +assert.strictEqual(buffer.readDoubleBE(0), 3.16e-322); +assert.strictEqual(buffer.readDoubleLE(0), 2); + +buffer[7] = 0xc0; +assert.strictEqual(buffer.readDoubleBE(0), 9.5e-322); +assert.strictEqual(buffer.readDoubleLE(0), -2); + +buffer[6] = 0x10; +buffer[7] = 0; +assert.strictEqual(buffer.readDoubleBE(0), 2.0237e-320); +assert.strictEqual(buffer.readDoubleLE(0), 2.2250738585072014e-308); + +buffer[6] = 0; +assert.strictEqual(buffer.readDoubleBE(0), 0); +assert.strictEqual(buffer.readDoubleLE(0), 0); +assert.ok(1 / buffer.readDoubleLE(0) >= 0); + +buffer[7] = 0x80; +assert.strictEqual(buffer.readDoubleBE(0), 6.3e-322); +assert.strictEqual(buffer.readDoubleLE(0), -0); +assert.ok(1 / buffer.readDoubleLE(0) < 0); + +buffer[6] = 0xf0; +buffer[7] = 0x7f; +assert.strictEqual(buffer.readDoubleBE(0), 3.0418e-319); +assert.strictEqual(buffer.readDoubleLE(0), Infinity); + +buffer[7] = 0xff; +assert.strictEqual(buffer.readDoubleBE(0), 3.04814e-319); +assert.strictEqual(buffer.readDoubleLE(0), -Infinity); + +['readDoubleLE', 'readDoubleBE'].forEach((fn) => { + + // Verify that default offset works fine. + buffer[fn](undefined); + buffer[fn](); + + ['', '0', null, {}, [], () => {}, true, false].forEach((off) => { + assert.throws( + () => buffer[fn](off), + { code: 'ERR_INVALID_ARG_TYPE' } + ); + }); + + [Infinity, -1, 1].forEach((offset) => { + assert.throws( + () => buffer[fn](offset), + { + code: 'ERR_OUT_OF_RANGE', + name: 'RangeError', + message: 'The value of "offset" is out of range. ' + + `It must be >= 0 and <= 0. Received ${offset}` + }); + }); + + assert.throws( + () => Buffer.alloc(1)[fn](1), + { + code: 'ERR_BUFFER_OUT_OF_BOUNDS', + name: 'RangeError', + message: 'Attempt to access memory outside buffer bounds' + }); + + [NaN, 1.01].forEach((offset) => { + assert.throws( + () => buffer[fn](offset), + { + code: 'ERR_OUT_OF_RANGE', + name: 'RangeError', + message: 'The value of "offset" is out of range. ' + + `It must be an integer. Received ${offset}` + }); + }); +}); diff --git a/cli/tests/node_compat/test/parallel/test-buffer-readfloat.js b/cli/tests/node_compat/test/parallel/test-buffer-readfloat.js new file mode 100644 index 00000000000000..3606594da30506 --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-buffer-readfloat.js @@ -0,0 +1,113 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +'use strict'; + +require('../common'); +const assert = require('assert'); + +// Test 32 bit float +const buffer = Buffer.alloc(4); + +buffer[0] = 0; +buffer[1] = 0; +buffer[2] = 0x80; +buffer[3] = 0x3f; +assert.strictEqual(buffer.readFloatBE(0), 4.600602988224807e-41); +assert.strictEqual(buffer.readFloatLE(0), 1); + +buffer[0] = 0; +buffer[1] = 0; +buffer[2] = 0; +buffer[3] = 0xc0; +assert.strictEqual(buffer.readFloatBE(0), 2.6904930515036488e-43); +assert.strictEqual(buffer.readFloatLE(0), -2); + +buffer[0] = 0xff; +buffer[1] = 0xff; +buffer[2] = 0x7f; +buffer[3] = 0x7f; +assert.ok(Number.isNaN(buffer.readFloatBE(0))); +assert.strictEqual(buffer.readFloatLE(0), 3.4028234663852886e+38); + +buffer[0] = 0xab; +buffer[1] = 0xaa; +buffer[2] = 0xaa; +buffer[3] = 0x3e; +assert.strictEqual(buffer.readFloatBE(0), -1.2126478207002966e-12); +assert.strictEqual(buffer.readFloatLE(0), 0.3333333432674408); + +buffer[0] = 0; +buffer[1] = 0; +buffer[2] = 0; +buffer[3] = 0; +assert.strictEqual(buffer.readFloatBE(0), 0); +assert.strictEqual(buffer.readFloatLE(0), 0); +assert.ok(1 / buffer.readFloatLE(0) >= 0); + +buffer[3] = 0x80; +assert.strictEqual(buffer.readFloatBE(0), 1.793662034335766e-43); +assert.strictEqual(buffer.readFloatLE(0), -0); +assert.ok(1 / buffer.readFloatLE(0) < 0); + +buffer[0] = 0; +buffer[1] = 0; +buffer[2] = 0x80; +buffer[3] = 0x7f; +assert.strictEqual(buffer.readFloatBE(0), 4.609571298396486e-41); +assert.strictEqual(buffer.readFloatLE(0), Infinity); + +buffer[0] = 0; +buffer[1] = 0; +buffer[2] = 0x80; +buffer[3] = 0xff; +assert.strictEqual(buffer.readFloatBE(0), 4.627507918739843e-41); +assert.strictEqual(buffer.readFloatLE(0), -Infinity); + +['readFloatLE', 'readFloatBE'].forEach((fn) => { + + // Verify that default offset works fine. + buffer[fn](undefined); + buffer[fn](); + + ['', '0', null, {}, [], () => {}, true, false].forEach((off) => { + assert.throws( + () => buffer[fn](off), + { code: 'ERR_INVALID_ARG_TYPE' } + ); + }); + + [Infinity, -1, 1].forEach((offset) => { + assert.throws( + () => buffer[fn](offset), + { + code: 'ERR_OUT_OF_RANGE', + name: 'RangeError', + message: 'The value of "offset" is out of range. ' + + `It must be >= 0 and <= 0. Received ${offset}` + }); + }); + + assert.throws( + () => Buffer.alloc(1)[fn](1), + { + code: 'ERR_BUFFER_OUT_OF_BOUNDS', + name: 'RangeError', + message: 'Attempt to access memory outside buffer bounds' + }); + + [NaN, 1.01].forEach((offset) => { + assert.throws( + () => buffer[fn](offset), + { + code: 'ERR_OUT_OF_RANGE', + name: 'RangeError', + message: 'The value of "offset" is out of range. ' + + `It must be an integer. Received ${offset}` + }); + }); +}); diff --git a/cli/tests/node_compat/test/parallel/test-buffer-readint.js b/cli/tests/node_compat/test/parallel/test-buffer-readint.js new file mode 100644 index 00000000000000..420c2731774db9 --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-buffer-readint.js @@ -0,0 +1,204 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +'use strict'; + +require('../common'); +const assert = require('assert'); + +// Test OOB +{ + const buffer = Buffer.alloc(4); + + ['Int8', 'Int16BE', 'Int16LE', 'Int32BE', 'Int32LE'].forEach((fn) => { + + // Verify that default offset works fine. + buffer[`read${fn}`](undefined); + buffer[`read${fn}`](); + + ['', '0', null, {}, [], () => {}, true, false].forEach((o) => { + assert.throws( + () => buffer[`read${fn}`](o), + { + code: 'ERR_INVALID_ARG_TYPE', + name: 'TypeError' + }); + }); + + [Infinity, -1, -4294967295].forEach((offset) => { + assert.throws( + () => buffer[`read${fn}`](offset), + { + code: 'ERR_OUT_OF_RANGE', + name: 'RangeError' + }); + }); + + [NaN, 1.01].forEach((offset) => { + assert.throws( + () => buffer[`read${fn}`](offset), + { + code: 'ERR_OUT_OF_RANGE', + name: 'RangeError', + message: 'The value of "offset" is out of range. ' + + `It must be an integer. Received ${offset}` + }); + }); + }); +} + +// Test 8 bit signed integers +{ + const data = Buffer.from([0x23, 0xab, 0x7c, 0xef]); + + assert.strictEqual(data.readInt8(0), 0x23); + + data[0] = 0xff; + assert.strictEqual(data.readInt8(0), -1); + + data[0] = 0x87; + assert.strictEqual(data.readInt8(0), -121); + assert.strictEqual(data.readInt8(1), -85); + assert.strictEqual(data.readInt8(2), 124); + assert.strictEqual(data.readInt8(3), -17); +} + +// Test 16 bit integers +{ + const buffer = Buffer.from([0x16, 0x79, 0x65, 0x6e, 0x69, 0x78]); + + assert.strictEqual(buffer.readInt16BE(0), 0x1679); + assert.strictEqual(buffer.readInt16LE(0), 0x7916); + + buffer[0] = 0xff; + buffer[1] = 0x80; + assert.strictEqual(buffer.readInt16BE(0), -128); + assert.strictEqual(buffer.readInt16LE(0), -32513); + + buffer[0] = 0x77; + buffer[1] = 0x65; + assert.strictEqual(buffer.readInt16BE(0), 0x7765); + assert.strictEqual(buffer.readInt16BE(1), 0x6565); + assert.strictEqual(buffer.readInt16BE(2), 0x656e); + assert.strictEqual(buffer.readInt16BE(3), 0x6e69); + assert.strictEqual(buffer.readInt16BE(4), 0x6978); + assert.strictEqual(buffer.readInt16LE(0), 0x6577); + assert.strictEqual(buffer.readInt16LE(1), 0x6565); + assert.strictEqual(buffer.readInt16LE(2), 0x6e65); + assert.strictEqual(buffer.readInt16LE(3), 0x696e); + assert.strictEqual(buffer.readInt16LE(4), 0x7869); +} + +// Test 32 bit integers +{ + const buffer = Buffer.from([0x43, 0x53, 0x16, 0x79, 0x36, 0x17]); + + assert.strictEqual(buffer.readInt32BE(0), 0x43531679); + assert.strictEqual(buffer.readInt32LE(0), 0x79165343); + + buffer[0] = 0xff; + buffer[1] = 0xfe; + buffer[2] = 0xef; + buffer[3] = 0xfa; + assert.strictEqual(buffer.readInt32BE(0), -69638); + assert.strictEqual(buffer.readInt32LE(0), -84934913); + + buffer[0] = 0x42; + buffer[1] = 0xc3; + buffer[2] = 0x95; + buffer[3] = 0xa9; + assert.strictEqual(buffer.readInt32BE(0), 0x42c395a9); + assert.strictEqual(buffer.readInt32BE(1), -1013601994); + assert.strictEqual(buffer.readInt32BE(2), -1784072681); + assert.strictEqual(buffer.readInt32LE(0), -1449802942); + assert.strictEqual(buffer.readInt32LE(1), 917083587); + assert.strictEqual(buffer.readInt32LE(2), 389458325); +} + +// Test Int +{ + const buffer = Buffer.from([0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08]); + + assert.strictEqual(buffer.readIntLE(0, 1), 0x01); + assert.strictEqual(buffer.readIntBE(0, 1), 0x01); + assert.strictEqual(buffer.readIntLE(0, 3), 0x030201); + assert.strictEqual(buffer.readIntBE(0, 3), 0x010203); + assert.strictEqual(buffer.readIntLE(0, 5), 0x0504030201); + assert.strictEqual(buffer.readIntBE(0, 5), 0x0102030405); + assert.strictEqual(buffer.readIntLE(0, 6), 0x060504030201); + assert.strictEqual(buffer.readIntBE(0, 6), 0x010203040506); + assert.strictEqual(buffer.readIntLE(1, 6), 0x070605040302); + assert.strictEqual(buffer.readIntBE(1, 6), 0x020304050607); + assert.strictEqual(buffer.readIntLE(2, 6), 0x080706050403); + assert.strictEqual(buffer.readIntBE(2, 6), 0x030405060708); + + // Check byteLength. + ['readIntBE', 'readIntLE'].forEach((fn) => { + ['', '0', null, {}, [], () => {}, true, false, undefined].forEach((len) => { + assert.throws( + () => buffer[fn](0, len), + { code: 'ERR_INVALID_ARG_TYPE' }); + }); + + [Infinity, -1].forEach((byteLength) => { + assert.throws( + () => buffer[fn](0, byteLength), + { + code: 'ERR_OUT_OF_RANGE', + message: 'The value of "byteLength" is out of range. ' + + `It must be >= 1 and <= 6. Received ${byteLength}` + }); + }); + + [NaN, 1.01].forEach((byteLength) => { + assert.throws( + () => buffer[fn](0, byteLength), + { + code: 'ERR_OUT_OF_RANGE', + name: 'RangeError', + message: 'The value of "byteLength" is out of range. ' + + `It must be an integer. Received ${byteLength}` + }); + }); + }); + + // Test 1 to 6 bytes. + for (let i = 1; i <= 6; i++) { + ['readIntBE', 'readIntLE'].forEach((fn) => { + ['', '0', null, {}, [], () => {}, true, false, undefined].forEach((o) => { + assert.throws( + () => buffer[fn](o, i), + { + code: 'ERR_INVALID_ARG_TYPE', + name: 'TypeError' + }); + }); + + [Infinity, -1, -4294967295].forEach((offset) => { + assert.throws( + () => buffer[fn](offset, i), + { + code: 'ERR_OUT_OF_RANGE', + name: 'RangeError', + message: 'The value of "offset" is out of range. ' + + `It must be >= 0 and <= ${8 - i}. Received ${offset}` + }); + }); + + [NaN, 1.01].forEach((offset) => { + assert.throws( + () => buffer[fn](offset, i), + { + code: 'ERR_OUT_OF_RANGE', + name: 'RangeError', + message: 'The value of "offset" is out of range. ' + + `It must be an integer. Received ${offset}` + }); + }); + }); + } +} diff --git a/cli/tests/node_compat/test/parallel/test-buffer-readuint.js b/cli/tests/node_compat/test/parallel/test-buffer-readuint.js new file mode 100644 index 00000000000000..b70e2909016a42 --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-buffer-readuint.js @@ -0,0 +1,172 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +'use strict'; + +require('../common'); +const assert = require('assert'); + +// Test OOB +{ + const buffer = Buffer.alloc(4); + + ['UInt8', 'UInt16BE', 'UInt16LE', 'UInt32BE', 'UInt32LE'].forEach((fn) => { + + // Verify that default offset works fine. + buffer[`read${fn}`](undefined); + buffer[`read${fn}`](); + + ['', '0', null, {}, [], () => {}, true, false].forEach((o) => { + assert.throws( + () => buffer[`read${fn}`](o), + { + code: 'ERR_INVALID_ARG_TYPE', + name: 'TypeError' + }); + }); + + [Infinity, -1, -4294967295].forEach((offset) => { + assert.throws( + () => buffer[`read${fn}`](offset), + { + code: 'ERR_OUT_OF_RANGE', + name: 'RangeError' + }); + }); + + [NaN, 1.01].forEach((offset) => { + assert.throws( + () => buffer[`read${fn}`](offset), + { + code: 'ERR_OUT_OF_RANGE', + name: 'RangeError', + message: 'The value of "offset" is out of range. ' + + `It must be an integer. Received ${offset}` + }); + }); + }); +} + +// Test 8 bit unsigned integers +{ + const data = Buffer.from([0xff, 0x2a, 0x2a, 0x2a]); + assert.strictEqual(data.readUInt8(0), 255); + assert.strictEqual(data.readUInt8(1), 42); + assert.strictEqual(data.readUInt8(2), 42); + assert.strictEqual(data.readUInt8(3), 42); +} + +// Test 16 bit unsigned integers +{ + const data = Buffer.from([0x00, 0x2a, 0x42, 0x3f]); + assert.strictEqual(data.readUInt16BE(0), 0x2a); + assert.strictEqual(data.readUInt16BE(1), 0x2a42); + assert.strictEqual(data.readUInt16BE(2), 0x423f); + assert.strictEqual(data.readUInt16LE(0), 0x2a00); + assert.strictEqual(data.readUInt16LE(1), 0x422a); + assert.strictEqual(data.readUInt16LE(2), 0x3f42); + + data[0] = 0xfe; + data[1] = 0xfe; + assert.strictEqual(data.readUInt16BE(0), 0xfefe); + assert.strictEqual(data.readUInt16LE(0), 0xfefe); +} + +// Test 32 bit unsigned integers +{ + const data = Buffer.from([0x32, 0x65, 0x42, 0x56, 0x23, 0xff]); + assert.strictEqual(data.readUInt32BE(0), 0x32654256); + assert.strictEqual(data.readUInt32BE(1), 0x65425623); + assert.strictEqual(data.readUInt32BE(2), 0x425623ff); + assert.strictEqual(data.readUInt32LE(0), 0x56426532); + assert.strictEqual(data.readUInt32LE(1), 0x23564265); + assert.strictEqual(data.readUInt32LE(2), 0xff235642); +} + +// Test UInt +{ + const buffer = Buffer.from([0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08]); + + assert.strictEqual(buffer.readUIntLE(0, 1), 0x01); + assert.strictEqual(buffer.readUIntBE(0, 1), 0x01); + assert.strictEqual(buffer.readUIntLE(0, 3), 0x030201); + assert.strictEqual(buffer.readUIntBE(0, 3), 0x010203); + assert.strictEqual(buffer.readUIntLE(0, 5), 0x0504030201); + assert.strictEqual(buffer.readUIntBE(0, 5), 0x0102030405); + assert.strictEqual(buffer.readUIntLE(0, 6), 0x060504030201); + assert.strictEqual(buffer.readUIntBE(0, 6), 0x010203040506); + assert.strictEqual(buffer.readUIntLE(1, 6), 0x070605040302); + assert.strictEqual(buffer.readUIntBE(1, 6), 0x020304050607); + assert.strictEqual(buffer.readUIntLE(2, 6), 0x080706050403); + assert.strictEqual(buffer.readUIntBE(2, 6), 0x030405060708); + + // Check byteLength. + ['readUIntBE', 'readUIntLE'].forEach((fn) => { + ['', '0', null, {}, [], () => {}, true, false, undefined].forEach((len) => { + assert.throws( + () => buffer[fn](0, len), + { code: 'ERR_INVALID_ARG_TYPE' }); + }); + + [Infinity, -1].forEach((byteLength) => { + assert.throws( + () => buffer[fn](0, byteLength), + { + code: 'ERR_OUT_OF_RANGE', + message: 'The value of "byteLength" is out of range. ' + + `It must be >= 1 and <= 6. Received ${byteLength}` + }); + }); + + [NaN, 1.01].forEach((byteLength) => { + assert.throws( + () => buffer[fn](0, byteLength), + { + code: 'ERR_OUT_OF_RANGE', + name: 'RangeError', + message: 'The value of "byteLength" is out of range. ' + + `It must be an integer. Received ${byteLength}` + }); + }); + }); + + // Test 1 to 6 bytes. + for (let i = 1; i <= 6; i++) { + ['readUIntBE', 'readUIntLE'].forEach((fn) => { + ['', '0', null, {}, [], () => {}, true, false, undefined].forEach((o) => { + assert.throws( + () => buffer[fn](o, i), + { + code: 'ERR_INVALID_ARG_TYPE', + name: 'TypeError' + }); + }); + + [Infinity, -1, -4294967295].forEach((offset) => { + assert.throws( + () => buffer[fn](offset, i), + { + code: 'ERR_OUT_OF_RANGE', + name: 'RangeError', + message: 'The value of "offset" is out of range. ' + + `It must be >= 0 and <= ${8 - i}. Received ${offset}` + }); + }); + + [NaN, 1.01].forEach((offset) => { + assert.throws( + () => buffer[fn](offset, i), + { + code: 'ERR_OUT_OF_RANGE', + name: 'RangeError', + message: 'The value of "offset" is out of range. ' + + `It must be an integer. Received ${offset}` + }); + }); + }); + } +} diff --git a/cli/tests/node_compat/test/parallel/test-buffer-safe-unsafe.js b/cli/tests/node_compat/test/parallel/test-buffer-safe-unsafe.js new file mode 100644 index 00000000000000..6529c25e515c10 --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-buffer-safe-unsafe.js @@ -0,0 +1,31 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +'use strict'; + +require('../common'); +const assert = require('assert'); + +const safe = Buffer.alloc(10); + +function isZeroFilled(buf) { + for (let n = 0; n < buf.length; n++) + if (buf[n] !== 0) return false; + return true; +} + +assert(isZeroFilled(safe)); + +// Test that unsafe allocations doesn't affect subsequent safe allocations +Buffer.allocUnsafe(10); +assert(isZeroFilled(new Float64Array(10))); + +new Buffer(10); +assert(isZeroFilled(new Float64Array(10))); + +Buffer.allocUnsafe(10); +assert(isZeroFilled(Buffer.alloc(10))); diff --git a/cli/tests/node_compat/test/parallel/test-buffer-sharedarraybuffer.js b/cli/tests/node_compat/test/parallel/test-buffer-sharedarraybuffer.js new file mode 100644 index 00000000000000..9ec8f1a6d2c248 --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-buffer-sharedarraybuffer.js @@ -0,0 +1,34 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +'use strict'; + +require('../common'); +const assert = require('assert'); + +const sab = new SharedArrayBuffer(24); +const arr1 = new Uint16Array(sab); +const arr2 = new Uint16Array(12); +arr2[0] = 5000; +arr1[0] = 5000; +arr1[1] = 4000; +arr2[1] = 4000; + +const arr_buf = Buffer.from(arr1.buffer); +const ar_buf = Buffer.from(arr2.buffer); + +assert.deepStrictEqual(arr_buf, ar_buf); + +arr1[1] = 6000; +arr2[1] = 6000; + +assert.deepStrictEqual(arr_buf, ar_buf); + +// Checks for calling Buffer.byteLength on a SharedArrayBuffer. +assert.strictEqual(Buffer.byteLength(sab), sab.byteLength); + +Buffer.from({ buffer: sab }); // Should not throw. diff --git a/cli/tests/node_compat/test/parallel/test-buffer-slice.js b/cli/tests/node_compat/test/parallel/test-buffer-slice.js new file mode 100644 index 00000000000000..8732f69dbe123c --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-buffer-slice.js @@ -0,0 +1,136 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. + +'use strict'; + +require('../common'); +const assert = require('assert'); + +assert.strictEqual(Buffer.from('hello', 'utf8').slice(0, 0).length, 0); +assert.strictEqual(Buffer('hello', 'utf8').slice(0, 0).length, 0); + +const buf = Buffer.from('0123456789', 'utf8'); +const expectedSameBufs = [ + [buf.slice(-10, 10), Buffer.from('0123456789', 'utf8')], + [buf.slice(-20, 10), Buffer.from('0123456789', 'utf8')], + [buf.slice(-20, -10), Buffer.from('', 'utf8')], + [buf.slice(), Buffer.from('0123456789', 'utf8')], + [buf.slice(0), Buffer.from('0123456789', 'utf8')], + [buf.slice(0, 0), Buffer.from('', 'utf8')], + [buf.slice(undefined), Buffer.from('0123456789', 'utf8')], + [buf.slice('foobar'), Buffer.from('0123456789', 'utf8')], + [buf.slice(undefined, undefined), Buffer.from('0123456789', 'utf8')], + [buf.slice(2), Buffer.from('23456789', 'utf8')], + [buf.slice(5), Buffer.from('56789', 'utf8')], + [buf.slice(10), Buffer.from('', 'utf8')], + [buf.slice(5, 8), Buffer.from('567', 'utf8')], + [buf.slice(8, -1), Buffer.from('8', 'utf8')], + [buf.slice(-10), Buffer.from('0123456789', 'utf8')], + [buf.slice(0, -9), Buffer.from('0', 'utf8')], + [buf.slice(0, -10), Buffer.from('', 'utf8')], + [buf.slice(0, -1), Buffer.from('012345678', 'utf8')], + [buf.slice(2, -2), Buffer.from('234567', 'utf8')], + [buf.slice(0, 65536), Buffer.from('0123456789', 'utf8')], + [buf.slice(65536, 0), Buffer.from('', 'utf8')], + [buf.slice(-5, -8), Buffer.from('', 'utf8')], + [buf.slice(-5, -3), Buffer.from('56', 'utf8')], + [buf.slice(-10, 10), Buffer.from('0123456789', 'utf8')], + [buf.slice('0', '1'), Buffer.from('0', 'utf8')], + [buf.slice('-5', '10'), Buffer.from('56789', 'utf8')], + [buf.slice('-10', '10'), Buffer.from('0123456789', 'utf8')], + [buf.slice('-10', '-5'), Buffer.from('01234', 'utf8')], + [buf.slice('-10', '-0'), Buffer.from('', 'utf8')], + [buf.slice('111'), Buffer.from('', 'utf8')], + [buf.slice('0', '-111'), Buffer.from('', 'utf8')], +]; + +for (let i = 0, s = buf.toString(); i < buf.length; ++i) { + expectedSameBufs.push( + [buf.slice(i), Buffer.from(s.slice(i))], + [buf.slice(0, i), Buffer.from(s.slice(0, i))], + [buf.slice(-i), Buffer.from(s.slice(-i))], + [buf.slice(0, -i), Buffer.from(s.slice(0, -i))] + ); +} + +expectedSameBufs.forEach(([buf1, buf2]) => { + assert.strictEqual(Buffer.compare(buf1, buf2), 0); +}); + +const utf16Buf = Buffer.from('0123456789', 'utf16le'); +assert.deepStrictEqual(utf16Buf.slice(0, 6), Buffer.from('012', 'utf16le')); +// Try to slice a zero length Buffer. +// See https://github.com/joyent/node/issues/5881 +assert.strictEqual(Buffer.alloc(0).slice(0, 1).length, 0); + +{ + // Single argument slice + assert.strictEqual(Buffer.from('abcde', 'utf8').slice(1).toString('utf8'), + 'bcde'); +} + +// slice(0,0).length === 0 +assert.strictEqual(Buffer.from('hello', 'utf8').slice(0, 0).length, 0); + +{ + // Regression tests for https://github.com/nodejs/node/issues/9096 + const buf = Buffer.from('abcd', 'utf8'); + assert.strictEqual(buf.slice(buf.length / 3).toString('utf8'), 'bcd'); + assert.strictEqual( + buf.slice(buf.length / 3, buf.length).toString(), + 'bcd' + ); +} + +{ + const buf = Buffer.from('abcdefg', 'utf8'); + assert.strictEqual(buf.slice(-(-1 >>> 0) - 1).toString('utf8'), + buf.toString('utf8')); +} + +{ + const buf = Buffer.from('abc', 'utf8'); + assert.strictEqual(buf.slice(-0.5).toString('utf8'), buf.toString('utf8')); +} + +{ + const buf = Buffer.from([ + 1, 29, 0, 0, 1, 143, 216, 162, 92, 254, 248, 63, 0, + 0, 0, 18, 184, 6, 0, 175, 29, 0, 8, 11, 1, 0, 0, + ]); + const chunk1 = Buffer.from([ + 1, 29, 0, 0, 1, 143, 216, 162, 92, 254, 248, 63, 0, + ]); + const chunk2 = Buffer.from([ + 0, 0, 18, 184, 6, 0, 175, 29, 0, 8, 11, 1, 0, 0, + ]); + const middle = buf.length / 2; + + assert.deepStrictEqual(buf.slice(0, middle), chunk1); + assert.deepStrictEqual(buf.slice(middle), chunk2); +} diff --git a/cli/tests/node_compat/test/parallel/test-buffer-slow.js b/cli/tests/node_compat/test/parallel/test-buffer-slow.js new file mode 100644 index 00000000000000..1cc4e5d2eb58bf --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-buffer-slow.js @@ -0,0 +1,69 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +'use strict'; + +require('../common'); +const assert = require('assert'); +const buffer = require('buffer'); +const SlowBuffer = buffer.SlowBuffer; + +const ones = [1, 1, 1, 1]; + +// Should create a Buffer +let sb = SlowBuffer(4); +assert(sb instanceof Buffer); +assert.strictEqual(sb.length, 4); +sb.fill(1); +for (const [key, value] of sb.entries()) { + assert.deepStrictEqual(value, ones[key]); +} + +// underlying ArrayBuffer should have the same length +assert.strictEqual(sb.buffer.byteLength, 4); + +// Should work without new +sb = SlowBuffer(4); +assert(sb instanceof Buffer); +assert.strictEqual(sb.length, 4); +sb.fill(1); +for (const [key, value] of sb.entries()) { + assert.deepStrictEqual(value, ones[key]); +} + +// Should work with edge cases +assert.strictEqual(SlowBuffer(0).length, 0); +try { + assert.strictEqual( + SlowBuffer(buffer.kMaxLength).length, buffer.kMaxLength); +} catch (e) { + // Don't match on message as it is from the JavaScript engine. V8 and + // ChakraCore provide different messages. + assert.strictEqual(e.name, 'RangeError'); +} + +// Should throw with invalid length type +const bufferInvalidTypeMsg = { + code: 'ERR_INVALID_ARG_TYPE', + name: 'TypeError', + message: /^The "size" argument must be of type number/, +}; +assert.throws(() => SlowBuffer(), bufferInvalidTypeMsg); +assert.throws(() => SlowBuffer({}), bufferInvalidTypeMsg); +assert.throws(() => SlowBuffer('6'), bufferInvalidTypeMsg); +assert.throws(() => SlowBuffer(true), bufferInvalidTypeMsg); + +// Should throw with invalid length value +const bufferMaxSizeMsg = { + code: 'ERR_INVALID_ARG_VALUE', + name: 'RangeError', + message: /^The argument 'size' is invalid\. Received [^"]*$/ +}; +assert.throws(() => SlowBuffer(NaN), bufferMaxSizeMsg); +assert.throws(() => SlowBuffer(Infinity), bufferMaxSizeMsg); +assert.throws(() => SlowBuffer(-1), bufferMaxSizeMsg); +assert.throws(() => SlowBuffer(buffer.kMaxLength + 1), bufferMaxSizeMsg); diff --git a/cli/tests/node_compat/test/parallel/test-buffer-swap.js b/cli/tests/node_compat/test/parallel/test-buffer-swap.js new file mode 100644 index 00000000000000..5464e9d9648237 --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-buffer-swap.js @@ -0,0 +1,159 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +'use strict'; + +require('../common'); +const assert = require('assert'); + +// Test buffers small enough to use the JS implementation +{ + const buf = Buffer.from([0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, + 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10]); + + assert.strictEqual(buf, buf.swap16()); + assert.deepStrictEqual(buf, Buffer.from([0x02, 0x01, 0x04, 0x03, 0x06, 0x05, + 0x08, 0x07, 0x0a, 0x09, 0x0c, 0x0b, + 0x0e, 0x0d, 0x10, 0x0f])); + buf.swap16(); // restore + + assert.strictEqual(buf, buf.swap32()); + assert.deepStrictEqual(buf, Buffer.from([0x04, 0x03, 0x02, 0x01, 0x08, 0x07, + 0x06, 0x05, 0x0c, 0x0b, 0x0a, 0x09, + 0x10, 0x0f, 0x0e, 0x0d])); + buf.swap32(); // restore + + assert.strictEqual(buf, buf.swap64()); + assert.deepStrictEqual(buf, Buffer.from([0x08, 0x07, 0x06, 0x05, 0x04, 0x03, + 0x02, 0x01, 0x10, 0x0f, 0x0e, 0x0d, + 0x0c, 0x0b, 0x0a, 0x09])); +} + +// Operates in-place +{ + const buf = Buffer.from([0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7]); + buf.slice(1, 5).swap32(); + assert.deepStrictEqual(buf, Buffer.from([0x1, 0x5, 0x4, 0x3, 0x2, 0x6, 0x7])); + buf.slice(1, 5).swap16(); + assert.deepStrictEqual(buf, Buffer.from([0x1, 0x4, 0x5, 0x2, 0x3, 0x6, 0x7])); + + // Length assertions + const re16 = /Buffer size must be a multiple of 16-bits/; + const re32 = /Buffer size must be a multiple of 32-bits/; + const re64 = /Buffer size must be a multiple of 64-bits/; + + assert.throws(() => Buffer.from(buf).swap16(), re16); + assert.throws(() => Buffer.alloc(1025).swap16(), re16); + assert.throws(() => Buffer.from(buf).swap32(), re32); + assert.throws(() => buf.slice(1, 3).swap32(), re32); + assert.throws(() => Buffer.alloc(1025).swap32(), re32); + assert.throws(() => buf.slice(1, 3).swap64(), re64); + assert.throws(() => Buffer.alloc(1025).swap64(), re64); +} + +{ + const buf = Buffer.from([0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, + 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10, + 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, + 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10]); + + buf.slice(2, 18).swap64(); + + assert.deepStrictEqual(buf, Buffer.from([0x01, 0x02, 0x0a, 0x09, 0x08, 0x07, + 0x06, 0x05, 0x04, 0x03, 0x02, 0x01, + 0x10, 0x0f, 0x0e, 0x0d, 0x0c, 0x0b, + 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, + 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, + 0x0f, 0x10])); +} + +// Force use of native code (Buffer size above threshold limit for js impl) +{ + const bufData = new Uint32Array(256).fill(0x04030201); + const buf = Buffer.from(bufData.buffer, bufData.byteOffset); + const otherBufData = new Uint32Array(256).fill(0x03040102); + const otherBuf = Buffer.from(otherBufData.buffer, otherBufData.byteOffset); + buf.swap16(); + assert.deepStrictEqual(buf, otherBuf); +} + +{ + const bufData = new Uint32Array(256).fill(0x04030201); + const buf = Buffer.from(bufData.buffer); + const otherBufData = new Uint32Array(256).fill(0x01020304); + const otherBuf = Buffer.from(otherBufData.buffer, otherBufData.byteOffset); + buf.swap32(); + assert.deepStrictEqual(buf, otherBuf); +} + +{ + const bufData = new Uint8Array(256 * 8); + const otherBufData = new Uint8Array(256 * 8); + for (let i = 0; i < bufData.length; i++) { + bufData[i] = i % 8; + otherBufData[otherBufData.length - i - 1] = i % 8; + } + const buf = Buffer.from(bufData.buffer, bufData.byteOffset); + const otherBuf = Buffer.from(otherBufData.buffer, otherBufData.byteOffset); + buf.swap64(); + assert.deepStrictEqual(buf, otherBuf); +} + +// Test native code with buffers that are not memory-aligned +{ + const bufData = new Uint8Array(256 * 8); + const otherBufData = new Uint8Array(256 * 8 - 2); + for (let i = 0; i < bufData.length; i++) { + bufData[i] = i % 2; + } + for (let i = 1; i < otherBufData.length; i++) { + otherBufData[otherBufData.length - i] = (i + 1) % 2; + } + const buf = Buffer.from(bufData.buffer, bufData.byteOffset); + // 0|1 0|1 0|1... + const otherBuf = Buffer.from(otherBufData.buffer, otherBufData.byteOffset); + // 0|0 1|0 1|0... + + buf.slice(1, buf.length - 1).swap16(); + assert.deepStrictEqual(buf.slice(0, otherBuf.length), otherBuf); +} + +{ + const bufData = new Uint8Array(256 * 8); + const otherBufData = new Uint8Array(256 * 8 - 4); + for (let i = 0; i < bufData.length; i++) { + bufData[i] = i % 4; + } + for (let i = 1; i < otherBufData.length; i++) { + otherBufData[otherBufData.length - i] = (i + 1) % 4; + } + const buf = Buffer.from(bufData.buffer, bufData.byteOffset); + // 0|1 2 3 0|1 2 3... + const otherBuf = Buffer.from(otherBufData.buffer, otherBufData.byteOffset); + // 0|0 3 2 1|0 3 2... + + buf.slice(1, buf.length - 3).swap32(); + assert.deepStrictEqual(buf.slice(0, otherBuf.length), otherBuf); +} + +{ + const bufData = new Uint8Array(256 * 8); + const otherBufData = new Uint8Array(256 * 8 - 8); + for (let i = 0; i < bufData.length; i++) { + bufData[i] = i % 8; + } + for (let i = 1; i < otherBufData.length; i++) { + otherBufData[otherBufData.length - i] = (i + 1) % 8; + } + const buf = Buffer.from(bufData.buffer, bufData.byteOffset); + // 0|1 2 3 4 5 6 7 0|1 2 3 4... + const otherBuf = Buffer.from(otherBufData.buffer, otherBufData.byteOffset); + // 0|0 7 6 5 4 3 2 1|0 7 6 5... + + buf.slice(1, buf.length - 7).swap64(); + assert.deepStrictEqual(buf.slice(0, otherBuf.length), otherBuf); +} diff --git a/cli/tests/node_compat/test/parallel/test-buffer-tojson.js b/cli/tests/node_compat/test/parallel/test-buffer-tojson.js new file mode 100644 index 00000000000000..e4d344c95e09e2 --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-buffer-tojson.js @@ -0,0 +1,42 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +'use strict'; + +require('../common'); +const assert = require('assert'); + +{ + assert.strictEqual(JSON.stringify(Buffer.alloc(0)), + '{"type":"Buffer","data":[]}'); + assert.strictEqual(JSON.stringify(Buffer.from([1, 2, 3, 4])), + '{"type":"Buffer","data":[1,2,3,4]}'); +} + +// issue GH-7849 +{ + const buf = Buffer.from('test'); + const json = JSON.stringify(buf); + const obj = JSON.parse(json); + const copy = Buffer.from(obj); + + assert.deepStrictEqual(buf, copy); +} + +// GH-5110 +{ + const buffer = Buffer.from('test'); + const string = JSON.stringify(buffer); + + assert.strictEqual(string, '{"type":"Buffer","data":[116,101,115,116]}'); + + function receiver(key, value) { + return value && value.type === 'Buffer' ? Buffer.from(value.data) : value; + } + + assert.deepStrictEqual(buffer, JSON.parse(string, receiver)); +} diff --git a/cli/tests/node_compat/test/parallel/test-buffer-tostring-range.js b/cli/tests/node_compat/test/parallel/test-buffer-tostring-range.js new file mode 100644 index 00000000000000..8e6db4754756ee --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-buffer-tostring-range.js @@ -0,0 +1,107 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +'use strict'; + +require('../common'); +const assert = require('assert'); + +const rangeBuffer = Buffer.from('abc'); + +// If start >= buffer's length, empty string will be returned +assert.strictEqual(rangeBuffer.toString('ascii', 3), ''); +assert.strictEqual(rangeBuffer.toString('ascii', +Infinity), ''); +assert.strictEqual(rangeBuffer.toString('ascii', 3.14, 3), ''); +assert.strictEqual(rangeBuffer.toString('ascii', 'Infinity', 3), ''); + +// If end <= 0, empty string will be returned +assert.strictEqual(rangeBuffer.toString('ascii', 1, 0), ''); +assert.strictEqual(rangeBuffer.toString('ascii', 1, -1.2), ''); +assert.strictEqual(rangeBuffer.toString('ascii', 1, -100), ''); +assert.strictEqual(rangeBuffer.toString('ascii', 1, -Infinity), ''); + +// If start < 0, start will be taken as zero +assert.strictEqual(rangeBuffer.toString('ascii', -1, 3), 'abc'); +assert.strictEqual(rangeBuffer.toString('ascii', -1.99, 3), 'abc'); +assert.strictEqual(rangeBuffer.toString('ascii', -Infinity, 3), 'abc'); +assert.strictEqual(rangeBuffer.toString('ascii', '-1', 3), 'abc'); +assert.strictEqual(rangeBuffer.toString('ascii', '-1.99', 3), 'abc'); +assert.strictEqual(rangeBuffer.toString('ascii', '-Infinity', 3), 'abc'); + +// If start is an invalid integer, start will be taken as zero +assert.strictEqual(rangeBuffer.toString('ascii', 'node.js', 3), 'abc'); +assert.strictEqual(rangeBuffer.toString('ascii', {}, 3), 'abc'); +assert.strictEqual(rangeBuffer.toString('ascii', [], 3), 'abc'); +assert.strictEqual(rangeBuffer.toString('ascii', NaN, 3), 'abc'); +assert.strictEqual(rangeBuffer.toString('ascii', null, 3), 'abc'); +assert.strictEqual(rangeBuffer.toString('ascii', undefined, 3), 'abc'); +assert.strictEqual(rangeBuffer.toString('ascii', false, 3), 'abc'); +assert.strictEqual(rangeBuffer.toString('ascii', '', 3), 'abc'); + +// But, if start is an integer when coerced, then it will be coerced and used. +assert.strictEqual(rangeBuffer.toString('ascii', '-1', 3), 'abc'); +assert.strictEqual(rangeBuffer.toString('ascii', '1', 3), 'bc'); +assert.strictEqual(rangeBuffer.toString('ascii', '-Infinity', 3), 'abc'); +assert.strictEqual(rangeBuffer.toString('ascii', '3', 3), ''); +assert.strictEqual(rangeBuffer.toString('ascii', Number(3), 3), ''); +assert.strictEqual(rangeBuffer.toString('ascii', '3.14', 3), ''); +assert.strictEqual(rangeBuffer.toString('ascii', '1.99', 3), 'bc'); +assert.strictEqual(rangeBuffer.toString('ascii', '-1.99', 3), 'abc'); +assert.strictEqual(rangeBuffer.toString('ascii', 1.99, 3), 'bc'); +assert.strictEqual(rangeBuffer.toString('ascii', true, 3), 'bc'); + +// If end > buffer's length, end will be taken as buffer's length +assert.strictEqual(rangeBuffer.toString('ascii', 0, 5), 'abc'); +assert.strictEqual(rangeBuffer.toString('ascii', 0, 6.99), 'abc'); +assert.strictEqual(rangeBuffer.toString('ascii', 0, Infinity), 'abc'); +assert.strictEqual(rangeBuffer.toString('ascii', 0, '5'), 'abc'); +assert.strictEqual(rangeBuffer.toString('ascii', 0, '6.99'), 'abc'); +assert.strictEqual(rangeBuffer.toString('ascii', 0, 'Infinity'), 'abc'); + +// If end is an invalid integer, end will be taken as buffer's length +assert.strictEqual(rangeBuffer.toString('ascii', 0, 'node.js'), ''); +assert.strictEqual(rangeBuffer.toString('ascii', 0, {}), ''); +assert.strictEqual(rangeBuffer.toString('ascii', 0, NaN), ''); +assert.strictEqual(rangeBuffer.toString('ascii', 0, undefined), 'abc'); +assert.strictEqual(rangeBuffer.toString('ascii', 0), 'abc'); +assert.strictEqual(rangeBuffer.toString('ascii', 0, null), ''); +assert.strictEqual(rangeBuffer.toString('ascii', 0, []), ''); +assert.strictEqual(rangeBuffer.toString('ascii', 0, false), ''); +assert.strictEqual(rangeBuffer.toString('ascii', 0, ''), ''); + +// But, if end is an integer when coerced, then it will be coerced and used. +assert.strictEqual(rangeBuffer.toString('ascii', 0, '-1'), ''); +assert.strictEqual(rangeBuffer.toString('ascii', 0, '1'), 'a'); +assert.strictEqual(rangeBuffer.toString('ascii', 0, '-Infinity'), ''); +assert.strictEqual(rangeBuffer.toString('ascii', 0, '3'), 'abc'); +assert.strictEqual(rangeBuffer.toString('ascii', 0, Number(3)), 'abc'); +assert.strictEqual(rangeBuffer.toString('ascii', 0, '3.14'), 'abc'); +assert.strictEqual(rangeBuffer.toString('ascii', 0, '1.99'), 'a'); +assert.strictEqual(rangeBuffer.toString('ascii', 0, '-1.99'), ''); +assert.strictEqual(rangeBuffer.toString('ascii', 0, 1.99), 'a'); +assert.strictEqual(rangeBuffer.toString('ascii', 0, true), 'a'); + +// Try toString() with an object as an encoding +assert.strictEqual(rangeBuffer.toString({ toString: function() { + return 'ascii'; +} }), 'abc'); + +// Try toString() with 0 and null as the encoding +assert.throws(() => { + rangeBuffer.toString(0, 1, 2); +}, { + code: 'ERR_UNKNOWN_ENCODING', + name: 'TypeError', + message: 'Unknown encoding: 0' +}); +assert.throws(() => { + rangeBuffer.toString(null, 1, 2); +}, { + code: 'ERR_UNKNOWN_ENCODING', + name: 'TypeError', + message: 'Unknown encoding: null' +}); diff --git a/cli/tests/node_compat/test/parallel/test-buffer-tostring-rangeerror.js b/cli/tests/node_compat/test/parallel/test-buffer-tostring-rangeerror.js new file mode 100644 index 00000000000000..3a0f9a0227d62b --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-buffer-tostring-rangeerror.js @@ -0,0 +1,28 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +'use strict'; +require('../common'); + +// This test ensures that Node.js throws a RangeError when trying to convert a +// gigantic buffer into a string. +// Regression test for https://github.com/nodejs/node/issues/649. + +const assert = require('assert'); +const SlowBuffer = require('buffer').SlowBuffer; + +const len = 1422561062959; +const message = { + code: 'ERR_INVALID_ARG_VALUE', + name: 'RangeError', + message: /^The argument 'size' is invalid\. Received [^"]*$/ +}; +assert.throws(() => Buffer(len).toString('utf8'), message); +assert.throws(() => SlowBuffer(len).toString('utf8'), message); +assert.throws(() => Buffer.alloc(len).toString('utf8'), message); +assert.throws(() => Buffer.allocUnsafe(len).toString('utf8'), message); +assert.throws(() => Buffer.allocUnsafeSlow(len).toString('utf8'), message); diff --git a/cli/tests/node_compat/test/parallel/test-buffer-tostring.js b/cli/tests/node_compat/test/parallel/test-buffer-tostring.js new file mode 100644 index 00000000000000..24e2bf1dfbd728 --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-buffer-tostring.js @@ -0,0 +1,44 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +'use strict'; + +const common = require('../common'); +const assert = require('assert'); + +// utf8, ucs2, ascii, latin1, utf16le +const encodings = ['utf8', 'utf-8', 'ucs2', 'ucs-2', 'ascii', 'latin1', + 'binary', 'utf16le', 'utf-16le']; + +encodings + .reduce((es, e) => es.concat(e, e.toUpperCase()), []) + .forEach((encoding) => { + assert.strictEqual(Buffer.from('foo', encoding).toString(encoding), 'foo'); + }); + +// base64 +['base64', 'BASE64'].forEach((encoding) => { + assert.strictEqual(Buffer.from('Zm9v', encoding).toString(encoding), 'Zm9v'); +}); + +// hex +['hex', 'HEX'].forEach((encoding) => { + assert.strictEqual(Buffer.from('666f6f', encoding).toString(encoding), + '666f6f'); +}); + +// Invalid encodings +for (let i = 1; i < 10; i++) { + const encoding = String(i).repeat(i); + const error = common.expectsError({ + code: 'ERR_UNKNOWN_ENCODING', + name: 'TypeError', + message: `Unknown encoding: ${encoding}` + }); + assert.ok(!Buffer.isEncoding(encoding)); + assert.throws(() => Buffer.from('foo').toString(encoding), error); +} diff --git a/cli/tests/node_compat/test/parallel/test-buffer-write.js b/cli/tests/node_compat/test/parallel/test-buffer-write.js new file mode 100644 index 00000000000000..5ca26c96d07512 --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-buffer-write.js @@ -0,0 +1,115 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +'use strict'; + +const common = require('../common'); +const assert = require('assert'); + +[-1, 10].forEach((offset) => { + assert.throws( + () => Buffer.alloc(9).write('foo', offset), + { + code: 'ERR_OUT_OF_RANGE', + name: 'RangeError', + message: 'The value of "offset" is out of range. ' + + `It must be >= 0 && <= 9. Received ${offset}` + } + ); +}); + +const resultMap = new Map([ + ['utf8', Buffer.from([102, 111, 111, 0, 0, 0, 0, 0, 0])], + ['ucs2', Buffer.from([102, 0, 111, 0, 111, 0, 0, 0, 0])], + ['ascii', Buffer.from([102, 111, 111, 0, 0, 0, 0, 0, 0])], + ['latin1', Buffer.from([102, 111, 111, 0, 0, 0, 0, 0, 0])], + ['binary', Buffer.from([102, 111, 111, 0, 0, 0, 0, 0, 0])], + ['utf16le', Buffer.from([102, 0, 111, 0, 111, 0, 0, 0, 0])], + ['base64', Buffer.from([102, 111, 111, 0, 0, 0, 0, 0, 0])], + ['base64url', Buffer.from([102, 111, 111, 0, 0, 0, 0, 0, 0])], + ['hex', Buffer.from([102, 111, 111, 0, 0, 0, 0, 0, 0])], +]); + +// utf8, ucs2, ascii, latin1, utf16le +const encodings = ['utf8', 'utf-8', 'ucs2', 'ucs-2', 'ascii', 'latin1', + 'binary', 'utf16le', 'utf-16le']; + +encodings + .reduce((es, e) => es.concat(e, e.toUpperCase()), []) + .forEach((encoding) => { + const buf = Buffer.alloc(9); + const len = Buffer.byteLength('foo', encoding); + assert.strictEqual(buf.write('foo', 0, len, encoding), len); + + if (encoding.includes('-')) + encoding = encoding.replace('-', ''); + + assert.deepStrictEqual(buf, resultMap.get(encoding.toLowerCase())); + }); + +// base64 +['base64', 'BASE64', 'base64url', 'BASE64URL'].forEach((encoding) => { + const buf = Buffer.alloc(9); + const len = Buffer.byteLength('Zm9v', encoding); + + assert.strictEqual(buf.write('Zm9v', 0, len, encoding), len); + assert.deepStrictEqual(buf, resultMap.get(encoding.toLowerCase())); +}); + +// hex +['hex', 'HEX'].forEach((encoding) => { + const buf = Buffer.alloc(9); + const len = Buffer.byteLength('666f6f', encoding); + + assert.strictEqual(buf.write('666f6f', 0, len, encoding), len); + assert.deepStrictEqual(buf, resultMap.get(encoding.toLowerCase())); +}); + +// Invalid encodings +for (let i = 1; i < 10; i++) { + const encoding = String(i).repeat(i); + const error = common.expectsError({ + code: 'ERR_UNKNOWN_ENCODING', + name: 'TypeError', + message: `Unknown encoding: ${encoding}` + }); + + assert.ok(!Buffer.isEncoding(encoding)); + assert.throws(() => Buffer.alloc(9).write('foo', encoding), error); +} + +// UCS-2 overflow CVE-2018-12115 +for (let i = 1; i < 4; i++) { + // Allocate two Buffers sequentially off the pool. Run more than once in case + // we hit the end of the pool and don't get sequential allocations + const x = Buffer.allocUnsafe(4).fill(0); + const y = Buffer.allocUnsafe(4).fill(1); + // Should not write anything, pos 3 doesn't have enough room for a 16-bit char + assert.strictEqual(x.write('ыыыыыы', 3, 'ucs2'), 0); + // CVE-2018-12115 experienced via buffer overrun to next block in the pool + assert.strictEqual(Buffer.compare(y, Buffer.alloc(4, 1)), 0); +} + +// Should not write any data when there is no space for 16-bit chars +const z = Buffer.alloc(4, 0); +assert.strictEqual(z.write('\u0001', 3, 'ucs2'), 0); +assert.strictEqual(Buffer.compare(z, Buffer.alloc(4, 0)), 0); +// Make sure longer strings are written up to the buffer end. +assert.strictEqual(z.write('abcd', 2), 2); +assert.deepStrictEqual([...z], [0, 0, 0x61, 0x62]); + +// Large overrun could corrupt the process +assert.strictEqual(Buffer.alloc(4) + .write('ыыыыыы'.repeat(100), 3, 'utf16le'), 0); + +{ + // .write() does not affect the byte after the written-to slice of the Buffer. + // Refs: https://github.com/nodejs/node/issues/26422 + const buf = Buffer.alloc(8); + assert.strictEqual(buf.write('ыы', 1, 'utf16le'), 4); + assert.deepStrictEqual([...buf], [0, 0x4b, 0x04, 0x4b, 0x04, 0, 0, 0]); +} diff --git a/cli/tests/node_compat/test/parallel/test-buffer-writedouble.js b/cli/tests/node_compat/test/parallel/test-buffer-writedouble.js new file mode 100644 index 00000000000000..92c9c9c7331720 --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-buffer-writedouble.js @@ -0,0 +1,140 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +'use strict'; + +// Tests to verify doubles are correctly written + +require('../common'); +const assert = require('assert'); + +const buffer = Buffer.allocUnsafe(16); + +buffer.writeDoubleBE(2.225073858507201e-308, 0); +buffer.writeDoubleLE(2.225073858507201e-308, 8); +assert.ok(buffer.equals(new Uint8Array([ + 0x00, 0x0f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x0f, 0x00, +]))); + +buffer.writeDoubleBE(1.0000000000000004, 0); +buffer.writeDoubleLE(1.0000000000000004, 8); +assert.ok(buffer.equals(new Uint8Array([ + 0x3f, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, + 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf0, 0x3f, +]))); + +buffer.writeDoubleBE(-2, 0); +buffer.writeDoubleLE(-2, 8); +assert.ok(buffer.equals(new Uint8Array([ + 0xc0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xc0, +]))); + +buffer.writeDoubleBE(1.7976931348623157e+308, 0); +buffer.writeDoubleLE(1.7976931348623157e+308, 8); +assert.ok(buffer.equals(new Uint8Array([ + 0x7f, 0xef, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xef, 0x7f, +]))); + +buffer.writeDoubleBE(0 * -1, 0); +buffer.writeDoubleLE(0 * -1, 8); +assert.ok(buffer.equals(new Uint8Array([ + 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, +]))); + +buffer.writeDoubleBE(Infinity, 0); +buffer.writeDoubleLE(Infinity, 8); + +assert.ok(buffer.equals(new Uint8Array([ + 0x7F, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xF0, 0x7F, +]))); + +assert.strictEqual(buffer.readDoubleBE(0), Infinity); +assert.strictEqual(buffer.readDoubleLE(8), Infinity); + +buffer.writeDoubleBE(-Infinity, 0); +buffer.writeDoubleLE(-Infinity, 8); + +assert.ok(buffer.equals(new Uint8Array([ + 0xFF, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xF0, 0xFF, +]))); + +assert.strictEqual(buffer.readDoubleBE(0), -Infinity); +assert.strictEqual(buffer.readDoubleLE(8), -Infinity); + +buffer.writeDoubleBE(NaN, 0); +buffer.writeDoubleLE(NaN, 8); + +// JS only knows a single NaN but there exist two platform specific +// implementations. Therefore, allow both quiet and signalling NaNs. +if (buffer[1] === 0xF7) { + assert.ok(buffer.equals(new Uint8Array([ + 0x7F, 0xF7, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF7, 0x7F, + ]))); +} else { + assert.ok(buffer.equals(new Uint8Array([ + 0x7F, 0xF8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xF8, 0x7F, + ]))); +} + +assert.ok(Number.isNaN(buffer.readDoubleBE(0))); +assert.ok(Number.isNaN(buffer.readDoubleLE(8))); + +// OOB in writeDouble{LE,BE} should throw. +{ + const small = Buffer.allocUnsafe(1); + + ['writeDoubleLE', 'writeDoubleBE'].forEach((fn) => { + + // Verify that default offset works fine. + buffer[fn](23, undefined); + buffer[fn](23); + + assert.throws( + () => small[fn](11.11, 0), + { + code: 'ERR_BUFFER_OUT_OF_BOUNDS', + name: 'RangeError', + message: 'Attempt to access memory outside buffer bounds' + }); + + ['', '0', null, {}, [], () => {}, true, false].forEach((off) => { + assert.throws( + () => small[fn](23, off), + { code: 'ERR_INVALID_ARG_TYPE' }); + }); + + [Infinity, -1, 9].forEach((offset) => { + assert.throws( + () => buffer[fn](23, offset), + { + code: 'ERR_OUT_OF_RANGE', + name: 'RangeError', + message: 'The value of "offset" is out of range. ' + + `It must be >= 0 and <= 8. Received ${offset}` + }); + }); + + [NaN, 1.01].forEach((offset) => { + assert.throws( + () => buffer[fn](42, offset), + { + code: 'ERR_OUT_OF_RANGE', + name: 'RangeError', + message: 'The value of "offset" is out of range. ' + + `It must be an integer. Received ${offset}` + }); + }); + }); +} diff --git a/cli/tests/node_compat/test/parallel/test-buffer-writefloat.js b/cli/tests/node_compat/test/parallel/test-buffer-writefloat.js new file mode 100644 index 00000000000000..3a70ba51c8036f --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-buffer-writefloat.js @@ -0,0 +1,124 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +'use strict'; + +// Tests to verify floats are correctly written + +require('../common'); +const assert = require('assert'); + +const buffer = Buffer.allocUnsafe(8); + +buffer.writeFloatBE(1, 0); +buffer.writeFloatLE(1, 4); +assert.ok(buffer.equals( + new Uint8Array([ 0x3f, 0x80, 0x00, 0x00, 0x00, 0x00, 0x80, 0x3f ]))); + +buffer.writeFloatBE(1 / 3, 0); +buffer.writeFloatLE(1 / 3, 4); +assert.ok(buffer.equals( + new Uint8Array([ 0x3e, 0xaa, 0xaa, 0xab, 0xab, 0xaa, 0xaa, 0x3e ]))); + +buffer.writeFloatBE(3.4028234663852886e+38, 0); +buffer.writeFloatLE(3.4028234663852886e+38, 4); +assert.ok(buffer.equals( + new Uint8Array([ 0x7f, 0x7f, 0xff, 0xff, 0xff, 0xff, 0x7f, 0x7f ]))); + +buffer.writeFloatLE(1.1754943508222875e-38, 0); +buffer.writeFloatBE(1.1754943508222875e-38, 4); +assert.ok(buffer.equals( + new Uint8Array([ 0x00, 0x00, 0x80, 0x00, 0x00, 0x80, 0x00, 0x00 ]))); + +buffer.writeFloatBE(0 * -1, 0); +buffer.writeFloatLE(0 * -1, 4); +assert.ok(buffer.equals( + new Uint8Array([ 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80 ]))); + +buffer.writeFloatBE(Infinity, 0); +buffer.writeFloatLE(Infinity, 4); +assert.ok(buffer.equals( + new Uint8Array([ 0x7F, 0x80, 0x00, 0x00, 0x00, 0x00, 0x80, 0x7F ]))); + +assert.strictEqual(buffer.readFloatBE(0), Infinity); +assert.strictEqual(buffer.readFloatLE(4), Infinity); + +buffer.writeFloatBE(-Infinity, 0); +buffer.writeFloatLE(-Infinity, 4); +assert.ok(buffer.equals( + new Uint8Array([ 0xFF, 0x80, 0x00, 0x00, 0x00, 0x00, 0x80, 0xFF ]))); + +assert.strictEqual(buffer.readFloatBE(0), -Infinity); +assert.strictEqual(buffer.readFloatLE(4), -Infinity); + +buffer.writeFloatBE(NaN, 0); +buffer.writeFloatLE(NaN, 4); + +// JS only knows a single NaN but there exist two platform specific +// implementations. Therefore, allow both quiet and signalling NaNs. +if (buffer[1] === 0xBF) { + assert.ok( + buffer.equals(new Uint8Array( + [ 0x7F, 0xBF, 0xFF, 0xFF, 0xFF, 0xFF, 0xBF, 0x7F ]))); +} else { + assert.ok( + buffer.equals(new Uint8Array( + [ 0x7F, 0xC0, 0x00, 0x00, 0x00, 0x00, 0xC0, 0x7F ]))); +} + +assert.ok(Number.isNaN(buffer.readFloatBE(0))); +assert.ok(Number.isNaN(buffer.readFloatLE(4))); + +// OOB in writeFloat{LE,BE} should throw. +{ + const small = Buffer.allocUnsafe(1); + + ['writeFloatLE', 'writeFloatBE'].forEach((fn) => { + + // Verify that default offset works fine. + buffer[fn](23, undefined); + buffer[fn](23); + + assert.throws( + () => small[fn](11.11, 0), + { + code: 'ERR_BUFFER_OUT_OF_BOUNDS', + name: 'RangeError', + message: 'Attempt to access memory outside buffer bounds' + }); + + ['', '0', null, {}, [], () => {}, true, false].forEach((off) => { + assert.throws( + () => small[fn](23, off), + { code: 'ERR_INVALID_ARG_TYPE' } + ); + }); + + [Infinity, -1, 5].forEach((offset) => { + assert.throws( + () => buffer[fn](23, offset), + { + code: 'ERR_OUT_OF_RANGE', + name: 'RangeError', + message: 'The value of "offset" is out of range. ' + + `It must be >= 0 and <= 4. Received ${offset}` + } + ); + }); + + [NaN, 1.01].forEach((offset) => { + assert.throws( + () => buffer[fn](42, offset), + { + code: 'ERR_OUT_OF_RANGE', + name: 'RangeError', + message: 'The value of "offset" is out of range. ' + + `It must be an integer. Received ${offset}` + }); + }); + }); +} diff --git a/cli/tests/node_compat/test/parallel/test-buffer-writeint.js b/cli/tests/node_compat/test/parallel/test-buffer-writeint.js new file mode 100644 index 00000000000000..6f67cf451fad6f --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-buffer-writeint.js @@ -0,0 +1,277 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +'use strict'; + +// Tests to verify signed integers are correctly written + +require('../common'); +const assert = require('assert'); +const errorOutOfBounds = { + code: 'ERR_OUT_OF_RANGE', + name: 'RangeError', + message: new RegExp('^The value of "value" is out of range\\. ' + + 'It must be >= -\\d+ and <= \\d+\\. Received .+$') +}; + +// Test 8 bit +{ + const buffer = Buffer.alloc(2); + + buffer.writeInt8(0x23, 0); + buffer.writeInt8(-5, 1); + assert.ok(buffer.equals(new Uint8Array([ 0x23, 0xfb ]))); + + /* Make sure we handle min/max correctly */ + buffer.writeInt8(0x7f, 0); + buffer.writeInt8(-0x80, 1); + assert.ok(buffer.equals(new Uint8Array([ 0x7f, 0x80 ]))); + + assert.throws(() => { + buffer.writeInt8(0x7f + 1, 0); + }, errorOutOfBounds); + assert.throws(() => { + buffer.writeInt8(-0x80 - 1, 0); + }, errorOutOfBounds); + + // Verify that default offset works fine. + buffer.writeInt8(23, undefined); + buffer.writeInt8(23); + + ['', '0', null, {}, [], () => {}, true, false].forEach((off) => { + assert.throws( + () => buffer.writeInt8(23, off), + { code: 'ERR_INVALID_ARG_TYPE' }); + }); + + [NaN, Infinity, -1, 1.01].forEach((off) => { + assert.throws( + () => buffer.writeInt8(23, off), + { code: 'ERR_OUT_OF_RANGE' }); + }); +} + +// Test 16 bit +{ + const buffer = Buffer.alloc(4); + + buffer.writeInt16BE(0x0023, 0); + buffer.writeInt16LE(0x0023, 2); + assert.ok(buffer.equals(new Uint8Array([ 0x00, 0x23, 0x23, 0x00 ]))); + + buffer.writeInt16BE(-5, 0); + buffer.writeInt16LE(-5, 2); + assert.ok(buffer.equals(new Uint8Array([ 0xff, 0xfb, 0xfb, 0xff ]))); + + buffer.writeInt16BE(-1679, 0); + buffer.writeInt16LE(-1679, 2); + assert.ok(buffer.equals(new Uint8Array([ 0xf9, 0x71, 0x71, 0xf9 ]))); + + /* Make sure we handle min/max correctly */ + buffer.writeInt16BE(0x7fff, 0); + buffer.writeInt16BE(-0x8000, 2); + assert.ok(buffer.equals(new Uint8Array([ 0x7f, 0xff, 0x80, 0x00 ]))); + + buffer.writeInt16LE(0x7fff, 0); + buffer.writeInt16LE(-0x8000, 2); + assert.ok(buffer.equals(new Uint8Array([ 0xff, 0x7f, 0x00, 0x80 ]))); + + ['writeInt16BE', 'writeInt16LE'].forEach((fn) => { + + // Verify that default offset works fine. + buffer[fn](23, undefined); + buffer[fn](23); + + assert.throws(() => { + buffer[fn](0x7fff + 1, 0); + }, errorOutOfBounds); + assert.throws(() => { + buffer[fn](-0x8000 - 1, 0); + }, errorOutOfBounds); + + ['', '0', null, {}, [], () => {}, true, false].forEach((off) => { + assert.throws( + () => buffer[fn](23, off), + { code: 'ERR_INVALID_ARG_TYPE' }); + }); + + [NaN, Infinity, -1, 1.01].forEach((off) => { + assert.throws( + () => buffer[fn](23, off), + { code: 'ERR_OUT_OF_RANGE' }); + }); + }); +} + +// Test 32 bit +{ + const buffer = Buffer.alloc(8); + + buffer.writeInt32BE(0x23, 0); + buffer.writeInt32LE(0x23, 4); + assert.ok(buffer.equals(new Uint8Array([ + 0x00, 0x00, 0x00, 0x23, 0x23, 0x00, 0x00, 0x00, + ]))); + + buffer.writeInt32BE(-5, 0); + buffer.writeInt32LE(-5, 4); + assert.ok(buffer.equals(new Uint8Array([ + 0xff, 0xff, 0xff, 0xfb, 0xfb, 0xff, 0xff, 0xff, + ]))); + + buffer.writeInt32BE(-805306713, 0); + buffer.writeInt32LE(-805306713, 4); + assert.ok(buffer.equals(new Uint8Array([ + 0xcf, 0xff, 0xfe, 0xa7, 0xa7, 0xfe, 0xff, 0xcf, + ]))); + + /* Make sure we handle min/max correctly */ + buffer.writeInt32BE(0x7fffffff, 0); + buffer.writeInt32BE(-0x80000000, 4); + assert.ok(buffer.equals(new Uint8Array([ + 0x7f, 0xff, 0xff, 0xff, 0x80, 0x00, 0x00, 0x00, + ]))); + + buffer.writeInt32LE(0x7fffffff, 0); + buffer.writeInt32LE(-0x80000000, 4); + assert.ok(buffer.equals(new Uint8Array([ + 0xff, 0xff, 0xff, 0x7f, 0x00, 0x00, 0x00, 0x80, + ]))); + + ['writeInt32BE', 'writeInt32LE'].forEach((fn) => { + + // Verify that default offset works fine. + buffer[fn](23, undefined); + buffer[fn](23); + + assert.throws(() => { + buffer[fn](0x7fffffff + 1, 0); + }, errorOutOfBounds); + assert.throws(() => { + buffer[fn](-0x80000000 - 1, 0); + }, errorOutOfBounds); + + ['', '0', null, {}, [], () => {}, true, false].forEach((off) => { + assert.throws( + () => buffer[fn](23, off), + { code: 'ERR_INVALID_ARG_TYPE' }); + }); + + [NaN, Infinity, -1, 1.01].forEach((off) => { + assert.throws( + () => buffer[fn](23, off), + { code: 'ERR_OUT_OF_RANGE' }); + }); + }); +} + +// Test 48 bit +{ + const value = 0x1234567890ab; + const buffer = Buffer.allocUnsafe(6); + buffer.writeIntBE(value, 0, 6); + assert.ok(buffer.equals(new Uint8Array([ + 0x12, 0x34, 0x56, 0x78, 0x90, 0xab, + ]))); + + buffer.writeIntLE(value, 0, 6); + assert.ok(buffer.equals(new Uint8Array([ + 0xab, 0x90, 0x78, 0x56, 0x34, 0x12, + ]))); +} + +// Test Int +{ + const data = Buffer.alloc(8); + + // Check byteLength. + ['writeIntBE', 'writeIntLE'].forEach((fn) => { + ['', '0', null, {}, [], () => {}, true, false, undefined].forEach((bl) => { + assert.throws( + () => data[fn](23, 0, bl), + { code: 'ERR_INVALID_ARG_TYPE' }); + }); + + [Infinity, -1].forEach((byteLength) => { + assert.throws( + () => data[fn](23, 0, byteLength), + { + code: 'ERR_OUT_OF_RANGE', + message: 'The value of "byteLength" is out of range. ' + + `It must be >= 1 and <= 6. Received ${byteLength}` + } + ); + }); + + [NaN, 1.01].forEach((byteLength) => { + assert.throws( + () => data[fn](42, 0, byteLength), + { + code: 'ERR_OUT_OF_RANGE', + name: 'RangeError', + message: 'The value of "byteLength" is out of range. ' + + `It must be an integer. Received ${byteLength}` + }); + }); + }); + + // Test 1 to 6 bytes. + for (let i = 1; i <= 6; i++) { + ['writeIntBE', 'writeIntLE'].forEach((fn) => { + const min = -(2 ** (i * 8 - 1)); + const max = 2 ** (i * 8 - 1) - 1; + let range = `>= ${min} and <= ${max}`; + if (i > 4) { + range = `>= -(2 ** ${i * 8 - 1}) and < 2 ** ${i * 8 - 1}`; + } + [min - 1, max + 1].forEach((val) => { + const received = i > 4 ? + String(val).replace(/(\d)(?=(\d\d\d)+(?!\d))/g, '$1_') : + val; + assert.throws(() => { + data[fn](val, 0, i); + }, { + code: 'ERR_OUT_OF_RANGE', + name: 'RangeError', + message: 'The value of "value" is out of range. ' + + `It must be ${range}. Received ${received}` + }); + }); + + ['', '0', null, {}, [], () => {}, true, false, undefined].forEach((o) => { + assert.throws( + () => data[fn](min, o, i), + { + code: 'ERR_INVALID_ARG_TYPE', + name: 'TypeError' + }); + }); + + [Infinity, -1, -4294967295].forEach((offset) => { + assert.throws( + () => data[fn](min, offset, i), + { + code: 'ERR_OUT_OF_RANGE', + name: 'RangeError', + message: 'The value of "offset" is out of range. ' + + `It must be >= 0 and <= ${8 - i}. Received ${offset}` + }); + }); + + [NaN, 1.01].forEach((offset) => { + assert.throws( + () => data[fn](max, offset, i), + { + code: 'ERR_OUT_OF_RANGE', + name: 'RangeError', + message: 'The value of "offset" is out of range. ' + + `It must be an integer. Received ${offset}` + }); + }); + }); + } +} diff --git a/cli/tests/node_compat/test/parallel/test-buffer-writeuint.js b/cli/tests/node_compat/test/parallel/test-buffer-writeuint.js new file mode 100644 index 00000000000000..43867476b5d816 --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-buffer-writeuint.js @@ -0,0 +1,237 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +'use strict'; + +require('../common'); +const assert = require('assert'); + +// We need to check the following things: +// - We are correctly resolving big endian (doesn't mean anything for 8 bit) +// - Correctly resolving little endian (doesn't mean anything for 8 bit) +// - Correctly using the offsets +// - Correctly interpreting values that are beyond the signed range as unsigned + +{ // OOB + const data = Buffer.alloc(8); + ['UInt8', 'UInt16BE', 'UInt16LE', 'UInt32BE', 'UInt32LE'].forEach((fn) => { + + // Verify that default offset works fine. + data[`write${fn}`](23, undefined); + data[`write${fn}`](23); + + ['', '0', null, {}, [], () => {}, true, false].forEach((o) => { + assert.throws( + () => data[`write${fn}`](23, o), + { code: 'ERR_INVALID_ARG_TYPE' }); + }); + + [NaN, Infinity, -1, 1.01].forEach((o) => { + assert.throws( + () => data[`write${fn}`](23, o), + { code: 'ERR_OUT_OF_RANGE' }); + }); + }); +} + +{ // Test 8 bit + const data = Buffer.alloc(4); + + data.writeUInt8(23, 0); + data.writeUInt8(23, 1); + data.writeUInt8(23, 2); + data.writeUInt8(23, 3); + assert.ok(data.equals(new Uint8Array([23, 23, 23, 23]))); + + data.writeUInt8(23, 0); + data.writeUInt8(23, 1); + data.writeUInt8(23, 2); + data.writeUInt8(23, 3); + assert.ok(data.equals(new Uint8Array([23, 23, 23, 23]))); + + data.writeUInt8(255, 0); + assert.strictEqual(data[0], 255); + + data.writeUInt8(255, 0); + assert.strictEqual(data[0], 255); +} + +// Test 16 bit +{ + let value = 0x2343; + const data = Buffer.alloc(4); + + data.writeUInt16BE(value, 0); + assert.ok(data.equals(new Uint8Array([0x23, 0x43, 0, 0]))); + + data.writeUInt16BE(value, 1); + assert.ok(data.equals(new Uint8Array([0x23, 0x23, 0x43, 0]))); + + data.writeUInt16BE(value, 2); + assert.ok(data.equals(new Uint8Array([0x23, 0x23, 0x23, 0x43]))); + + data.writeUInt16LE(value, 0); + assert.ok(data.equals(new Uint8Array([0x43, 0x23, 0x23, 0x43]))); + + data.writeUInt16LE(value, 1); + assert.ok(data.equals(new Uint8Array([0x43, 0x43, 0x23, 0x43]))); + + data.writeUInt16LE(value, 2); + assert.ok(data.equals(new Uint8Array([0x43, 0x43, 0x43, 0x23]))); + + value = 0xff80; + data.writeUInt16LE(value, 0); + assert.ok(data.equals(new Uint8Array([0x80, 0xff, 0x43, 0x23]))); + + data.writeUInt16BE(value, 0); + assert.ok(data.equals(new Uint8Array([0xff, 0x80, 0x43, 0x23]))); + + value = 0xfffff; + ['writeUInt16BE', 'writeUInt16LE'].forEach((fn) => { + assert.throws( + () => data[fn](value, 0), + { + code: 'ERR_OUT_OF_RANGE', + message: 'The value of "value" is out of range. ' + + `It must be >= 0 and <= 65535. Received ${value}` + } + ); + }); +} + +// Test 32 bit +{ + const data = Buffer.alloc(6); + const value = 0xe7f90a6d; + + data.writeUInt32BE(value, 0); + assert.ok(data.equals(new Uint8Array([0xe7, 0xf9, 0x0a, 0x6d, 0, 0]))); + + data.writeUInt32BE(value, 1); + assert.ok(data.equals(new Uint8Array([0xe7, 0xe7, 0xf9, 0x0a, 0x6d, 0]))); + + data.writeUInt32BE(value, 2); + assert.ok(data.equals(new Uint8Array([0xe7, 0xe7, 0xe7, 0xf9, 0x0a, 0x6d]))); + + data.writeUInt32LE(value, 0); + assert.ok(data.equals(new Uint8Array([0x6d, 0x0a, 0xf9, 0xe7, 0x0a, 0x6d]))); + + data.writeUInt32LE(value, 1); + assert.ok(data.equals(new Uint8Array([0x6d, 0x6d, 0x0a, 0xf9, 0xe7, 0x6d]))); + + data.writeUInt32LE(value, 2); + assert.ok(data.equals(new Uint8Array([0x6d, 0x6d, 0x6d, 0x0a, 0xf9, 0xe7]))); +} + +// Test 48 bit +{ + const value = 0x1234567890ab; + const data = Buffer.allocUnsafe(6); + data.writeUIntBE(value, 0, 6); + assert.ok(data.equals(new Uint8Array([0x12, 0x34, 0x56, 0x78, 0x90, 0xab]))); + + data.writeUIntLE(value, 0, 6); + assert.ok(data.equals(new Uint8Array([0xab, 0x90, 0x78, 0x56, 0x34, 0x12]))); +} + +// Test UInt +{ + const data = Buffer.alloc(8); + let val = 0x100; + + // Check byteLength. + ['writeUIntBE', 'writeUIntLE'].forEach((fn) => { + ['', '0', null, {}, [], () => {}, true, false, undefined].forEach((bl) => { + assert.throws( + () => data[fn](23, 0, bl), + { code: 'ERR_INVALID_ARG_TYPE' }); + }); + + [Infinity, -1].forEach((byteLength) => { + assert.throws( + () => data[fn](23, 0, byteLength), + { + code: 'ERR_OUT_OF_RANGE', + message: 'The value of "byteLength" is out of range. ' + + `It must be >= 1 and <= 6. Received ${byteLength}` + } + ); + }); + + [NaN, 1.01].forEach((byteLength) => { + assert.throws( + () => data[fn](42, 0, byteLength), + { + code: 'ERR_OUT_OF_RANGE', + name: 'RangeError', + message: 'The value of "byteLength" is out of range. ' + + `It must be an integer. Received ${byteLength}` + }); + }); + }); + + // Test 1 to 6 bytes. + for (let i = 1; i <= 6; i++) { + const range = i < 5 ? `= ${val - 1}` : ` 2 ** ${i * 8}`; + const received = i > 4 ? + String(val).replace(/(\d)(?=(\d\d\d)+(?!\d))/g, '$1_') : + val; + ['writeUIntBE', 'writeUIntLE'].forEach((fn) => { + assert.throws(() => { + data[fn](val, 0, i); + }, { + code: 'ERR_OUT_OF_RANGE', + name: 'RangeError', + message: 'The value of "value" is out of range. ' + + `It must be >= 0 and <${range}. Received ${received}` + }); + + ['', '0', null, {}, [], () => {}, true, false].forEach((o) => { + assert.throws( + () => data[fn](23, o, i), + { + code: 'ERR_INVALID_ARG_TYPE', + name: 'TypeError' + }); + }); + + [Infinity, -1, -4294967295].forEach((offset) => { + assert.throws( + () => data[fn](val - 1, offset, i), + { + code: 'ERR_OUT_OF_RANGE', + name: 'RangeError', + message: 'The value of "offset" is out of range. ' + + `It must be >= 0 and <= ${8 - i}. Received ${offset}` + }); + }); + + [NaN, 1.01].forEach((offset) => { + assert.throws( + () => data[fn](val - 1, offset, i), + { + code: 'ERR_OUT_OF_RANGE', + name: 'RangeError', + message: 'The value of "offset" is out of range. ' + + `It must be an integer. Received ${offset}` + }); + }); + }); + + val *= 0x100; + } +} + +for (const fn of [ + 'UInt8', 'UInt16LE', 'UInt16BE', 'UInt32LE', 'UInt32BE', 'UIntLE', 'UIntBE', + 'BigUInt64LE', 'BigUInt64BE', +]) { + const p = Buffer.prototype; + const lowerFn = fn.replace(/UInt/, 'Uint'); + assert.strictEqual(p[`write${fn}`], p[`write${lowerFn}`]); + assert.strictEqual(p[`read${fn}`], p[`read${lowerFn}`]); +} diff --git a/cli/tests/node_compat/test/parallel/test-buffer-zero-fill-cli.js b/cli/tests/node_compat/test/parallel/test-buffer-zero-fill-cli.js new file mode 100644 index 00000000000000..02a91b2e69d619 --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-buffer-zero-fill-cli.js @@ -0,0 +1,39 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +'use strict'; +// Flags: --zero-fill-buffers + +// when using --zero-fill-buffers, every Buffer and SlowBuffer +// instance must be zero filled upon creation + +require('../common'); +const SlowBuffer = require('buffer').SlowBuffer; +const assert = require('assert'); + +function isZeroFilled(buf) { + for (const n of buf) + if (n > 0) return false; + return true; +} + +// This can be somewhat unreliable because the +// allocated memory might just already happen to +// contain all zeroes. The test is run multiple +// times to improve the reliability. +for (let i = 0; i < 50; i++) { + const bufs = [ + Buffer.alloc(20), + Buffer.allocUnsafe(20), + SlowBuffer(20), + Buffer(20), + new SlowBuffer(20), + ]; + for (const buf of bufs) { + assert(isZeroFilled(buf)); + } +} diff --git a/cli/tests/node_compat/test/parallel/test-buffer-zero-fill-reset.js b/cli/tests/node_compat/test/parallel/test-buffer-zero-fill-reset.js new file mode 100644 index 00000000000000..5812f2609fd286 --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-buffer-zero-fill-reset.js @@ -0,0 +1,26 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +'use strict'; + +require('../common'); +const assert = require('assert'); + + +function testUint8Array(ui) { + const length = ui.length; + for (let i = 0; i < length; i++) + if (ui[i] !== 0) return false; + return true; +} + + +for (let i = 0; i < 100; i++) { + Buffer.alloc(0); + const ui = new Uint8Array(65); + assert.ok(testUint8Array(ui), `Uint8Array is not zero-filled: ${ui}`); +} diff --git a/cli/tests/node_compat/test/parallel/test-buffer-zero-fill.js b/cli/tests/node_compat/test/parallel/test-buffer-zero-fill.js new file mode 100644 index 00000000000000..c1f9219cdd52d4 --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-buffer-zero-fill.js @@ -0,0 +1,21 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +'use strict'; + +require('../common'); +const assert = require('assert'); + +// Tests deprecated Buffer API on purpose +const buf1 = Buffer(100); +const buf2 = new Buffer(100); + +for (let n = 0; n < buf1.length; n++) + assert.strictEqual(buf1[n], 0); + +for (let n = 0; n < buf2.length; n++) + assert.strictEqual(buf2[n], 0); diff --git a/cli/tests/node_compat/test/parallel/test-child-process-can-write-to-stdout.js b/cli/tests/node_compat/test/parallel/test-child-process-can-write-to-stdout.js new file mode 100644 index 00000000000000..11dbf9bcb3d211 --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-child-process-can-write-to-stdout.js @@ -0,0 +1,29 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +'use strict'; +// Tests that a spawned child process can write to stdout without throwing. +// See https://github.com/nodejs/node-v0.x-archive/issues/1899. + +require('../common'); +const fixtures = require('../common/fixtures'); +const assert = require('assert'); +const spawn = require('child_process').spawn; + +const child = spawn(process.argv[0], [ + fixtures.path('GH-1899-output.js'), +]); +let output = ''; + +child.stdout.on('data', function(data) { + output += data; +}); + +child.on('exit', function(code, signal) { + assert.strictEqual(code, 0); + assert.strictEqual(output, 'hello, world!\n'); +}); diff --git a/cli/tests/node_compat/test/parallel/test-child-process-default-options.js b/cli/tests/node_compat/test/parallel/test-child-process-default-options.js new file mode 100644 index 00000000000000..6f91058b51d745 --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-child-process-default-options.js @@ -0,0 +1,58 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. + +'use strict'; +const { isWindows } = require('../common'); +const assert = require('assert'); + +const spawn = require('child_process').spawn; +const debug = require('util').debuglog('test'); + +process.env.HELLO = 'WORLD'; + +let child; +if (isWindows) { + child = spawn('cmd.exe', ['/c', 'set'], {}); +} else { + child = spawn('/usr/bin/env', [], {}); +} + +let response = ''; + +child.stdout.setEncoding('utf8'); + +child.stdout.on('data', function(chunk) { + debug(`stdout: ${chunk}`); + response += chunk; +}); + +process.on('exit', function() { + assert.ok(response.includes('HELLO=WORLD'), + 'spawn did not use process.env as default ' + + `(process.env.HELLO = ${process.env.HELLO})`); +}); diff --git a/cli/tests/node_compat/test/parallel/test-child-process-double-pipe.js b/cli/tests/node_compat/test/parallel/test-child-process-double-pipe.js new file mode 100644 index 00000000000000..081eda3cb40efe --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-child-process-double-pipe.js @@ -0,0 +1,129 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. + +'use strict'; +const { + isWindows, + mustCall, + mustCallAtLeast, +} = require('../common'); +const assert = require('assert'); +const os = require('os'); +const spawn = require('child_process').spawn; +const debug = require('util').debuglog('test'); + +// We're trying to reproduce: +// $ echo "hello\nnode\nand\nworld" | grep o | sed s/o/a/ + +let grep, sed, echo; + +if (isWindows) { + grep = spawn('grep', ['--binary', 'o']); + sed = spawn('sed', ['--binary', 's/o/O/']); + echo = spawn('cmd.exe', + ['/c', 'echo', 'hello&&', 'echo', + 'node&&', 'echo', 'and&&', 'echo', 'world']); +} else { + grep = spawn('grep', ['o']); + sed = spawn('sed', ['s/o/O/']); + echo = spawn('echo', ['hello\nnode\nand\nworld\n']); +} + +// If the spawn function leaks file descriptors to subprocesses, grep and sed +// hang. +// This happens when calling pipe(2) and then forgetting to set the +// FD_CLOEXEC flag on the resulting file descriptors. +// +// This test checks child processes exit, meaning they don't hang like +// explained above. + + +// pipe echo | grep +echo.stdout.on('data', mustCallAtLeast((data) => { + debug(`grep stdin write ${data.length}`); + if (!grep.stdin.write(data)) { + echo.stdout.pause(); + } +})); + +// TODO(@jasnell): This does not appear to ever be +// emitted. It's not clear if it is necessary. +grep.stdin.on('drain', (data) => { + echo.stdout.resume(); +}); + +// Propagate end from echo to grep +echo.stdout.on('end', mustCall((code) => { + grep.stdin.end(); +})); + +echo.on('exit', mustCall(() => { + debug('echo exit'); +})); + +grep.on('exit', mustCall(() => { + debug('grep exit'); +})); + +sed.on('exit', mustCall(() => { + debug('sed exit'); +})); + + +// pipe grep | sed +grep.stdout.on('data', mustCallAtLeast((data) => { + debug(`grep stdout ${data.length}`); + if (!sed.stdin.write(data)) { + grep.stdout.pause(); + } +})); + +// TODO(@jasnell): This does not appear to ever be +// emitted. It's not clear if it is necessary. +sed.stdin.on('drain', (data) => { + grep.stdout.resume(); +}); + +// Propagate end from grep to sed +grep.stdout.on('end', mustCall((code) => { + debug('grep stdout end'); + sed.stdin.end(); +})); + + +let result = ''; + +// print sed's output +sed.stdout.on('data', mustCallAtLeast((data) => { + result += data.toString('utf8', 0, data.length); + debug(data); +})); + +sed.stdout.on('end', mustCall((code) => { + assert.strictEqual(result, `hellO${os.EOL}nOde${os.EOL}wOrld${os.EOL}`); +})); diff --git a/cli/tests/node_compat/test/parallel/test-child-process-exec-abortcontroller-promisified.js b/cli/tests/node_compat/test/parallel/test-child-process-exec-abortcontroller-promisified.js new file mode 100644 index 00000000000000..4ba699ba49e8ea --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-child-process-exec-abortcontroller-promisified.js @@ -0,0 +1,54 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.8.0 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +// TODO(PolarETech): The "eval" subcommand passed to execPromisifed() should be the "-e" option. + +'use strict'; +const common = require('../common'); +const assert = require('assert'); +const exec = require('child_process').exec; +const { promisify } = require('util'); + +const execPromisifed = promisify(exec); +const invalidArgTypeError = { + code: 'ERR_INVALID_ARG_TYPE', + name: 'TypeError' +}; + +const waitCommand = common.isLinux ? + 'sleep 2m' : + `${process.execPath} eval "setInterval(()=>{}, 99)"`; + +{ + const ac = new AbortController(); + const signal = ac.signal; + const promise = execPromisifed(waitCommand, { signal }); + assert.rejects(promise, /AbortError/, 'post aborted sync signal failed') + .then(common.mustCall()); + ac.abort(); +} + +{ + assert.throws(() => { + execPromisifed(waitCommand, { signal: {} }); + }, invalidArgTypeError); +} + +{ + function signal() {} + assert.throws(() => { + execPromisifed(waitCommand, { signal }); + }, invalidArgTypeError); +} + +{ + const signal = AbortSignal.abort(); // Abort in advance + const promise = execPromisifed(waitCommand, { signal }); + + assert.rejects(promise, /AbortError/, 'pre aborted signal failed') + .then(common.mustCall()); +} diff --git a/cli/tests/node_compat/test/parallel/test-child-process-exec-cwd.js b/cli/tests/node_compat/test/parallel/test-child-process-exec-cwd.js new file mode 100644 index 00000000000000..79f18a43584ef9 --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-child-process-exec-cwd.js @@ -0,0 +1,46 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. + +'use strict'; +const common = require('../common'); +const assert = require('assert'); +const exec = require('child_process').exec; + +let pwdcommand, dir; + +if (common.isWindows) { + pwdcommand = 'echo %cd%'; + dir = 'c:\\windows'; +} else { + pwdcommand = 'pwd'; + dir = '/dev'; +} + +exec(pwdcommand, { cwd: dir }, common.mustSucceed((stdout, stderr) => { + assert(stdout.startsWith(dir)); +})); diff --git a/cli/tests/node_compat/test/parallel/test-child-process-exec-encoding.js b/cli/tests/node_compat/test/parallel/test-child-process-exec-encoding.js new file mode 100644 index 00000000000000..fe03e98d06635d --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-child-process-exec-encoding.js @@ -0,0 +1,59 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.8.0 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +// TODO(PolarETech): The process.argv[3] check should be argv[2], and the +// command passed to exec() should not need to include "run", "-A", +// and "require.ts". + +'use strict'; +const common = require('../common'); +const stdoutData = 'foo'; +const stderrData = 'bar'; + +if (process.argv[3] === 'child') { + // The following console calls are part of the test. + console.log(stdoutData); + console.error(stderrData); +} else { + const assert = require('assert'); + const cp = require('child_process'); + const expectedStdout = `${stdoutData}\n`; + const expectedStderr = `${stderrData}\n`; + function run(options, callback) { + const cmd = `"${process.execPath}" run -A require.ts "${__filename}" child`; + + cp.exec(cmd, options, common.mustSucceed((stdout, stderr) => { + callback(stdout, stderr); + })); + } + + // Test default encoding, which should be utf8. + run({}, (stdout, stderr) => { + assert.strictEqual(typeof stdout, 'string'); + assert.strictEqual(typeof stderr, 'string'); + assert.strictEqual(stdout, expectedStdout); + assert.strictEqual(stderr, expectedStderr); + }); + + // Test explicit utf8 encoding. + run({ encoding: 'utf8' }, (stdout, stderr) => { + assert.strictEqual(typeof stdout, 'string'); + assert.strictEqual(typeof stderr, 'string'); + assert.strictEqual(stdout, expectedStdout); + assert.strictEqual(stderr, expectedStderr); + }); + + // Test cases that result in buffer encodings. + [undefined, null, 'buffer', 'invalid'].forEach((encoding) => { + run({ encoding }, (stdout, stderr) => { + assert(stdout instanceof Buffer); + assert(stdout instanceof Buffer); + assert.strictEqual(stdout.toString(), expectedStdout); + assert.strictEqual(stderr.toString(), expectedStderr); + }); + }); +} diff --git a/cli/tests/node_compat/test/parallel/test-child-process-exec-env.js b/cli/tests/node_compat/test/parallel/test-child-process-exec-env.js new file mode 100644 index 00000000000000..6a70252612e9c4 --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-child-process-exec-env.js @@ -0,0 +1,71 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. + +'use strict'; +const { isWindows } = require('../common'); +const assert = require('assert'); +const exec = require('child_process').exec; +const debug = require('util').debuglog('test'); + +let success_count = 0; +let error_count = 0; +let response = ''; +let child; + +function after(err, stdout, stderr) { + if (err) { + error_count++; + debug(`error!: ${err.code}`); + debug(`stdout: ${JSON.stringify(stdout)}`); + debug(`stderr: ${JSON.stringify(stderr)}`); + assert.strictEqual(err.killed, false); + } else { + success_count++; + assert.notStrictEqual(stdout, ''); + } +} + +if (!isWindows) { + child = exec('/usr/bin/env', { env: { 'HELLO': 'WORLD' } }, after); +} else { + child = exec('set', + { env: { ...process.env, 'HELLO': 'WORLD' } }, + after); +} + +child.stdout.setEncoding('utf8'); +child.stdout.on('data', function(chunk) { + response += chunk; +}); + +process.on('exit', function() { + debug('response: ', response); + assert.strictEqual(success_count, 1); + assert.strictEqual(error_count, 0); + assert.ok(response.includes('HELLO=WORLD')); +}); diff --git a/cli/tests/node_compat/test/parallel/test-child-process-exec-error.js b/cli/tests/node_compat/test/parallel/test-child-process-exec-error.js new file mode 100644 index 00000000000000..3c553457d92fcf --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-child-process-exec-error.js @@ -0,0 +1,51 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. + +'use strict'; +const common = require('../common'); +const assert = require('assert'); +const child_process = require('child_process'); + +function test(fn, code, expectPidType = 'number') { + const child = fn('does-not-exist', common.mustCall(function(err) { + assert.strictEqual(err.code, code); + assert(err.cmd.includes('does-not-exist')); + })); + + assert.strictEqual(typeof child.pid, expectPidType); +} + +// With `shell: true`, expect pid (of the shell) +if (common.isWindows) { + test(child_process.exec, 1, 'number'); // Exit code of cmd.exe +} else { + test(child_process.exec, 127, 'number'); // Exit code of /bin/sh +} + +// With `shell: false`, expect no pid +test(child_process.execFile, 'ENOENT', 'undefined'); diff --git a/cli/tests/node_compat/test/parallel/test-child-process-exec-kill-throws.js b/cli/tests/node_compat/test/parallel/test-child-process-exec-kill-throws.js new file mode 100644 index 00000000000000..6a28c2a18dfd23 --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-child-process-exec-kill-throws.js @@ -0,0 +1,42 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.8.0 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +// TODO(PolarETech): The process.argv[3] check should be argv[2], and the +// command passed to exec() should not need to include "run", "-A", +// and "require.ts". + +'use strict'; +// Flags: --expose-internals +const common = require('../common'); +const assert = require('assert'); +const cp = require('child_process'); + +if (process.argv[3] === 'child') { + // Since maxBuffer is 0, this should trigger an error. + console.log('foo'); +} else { + const internalCp = require('internal/child_process'); + + // Monkey patch ChildProcess#kill() to kill the process and then throw. + const kill = internalCp.ChildProcess.prototype.kill; + + internalCp.ChildProcess.prototype.kill = function() { + kill.apply(this, arguments); + throw new Error('mock error'); + }; + + const cmd = `"${process.execPath}" run -A require.ts "${__filename}" child`; + const options = { maxBuffer: 0, killSignal: 'SIGKILL' }; + + const child = cp.exec(cmd, options, common.mustCall((err, stdout, stderr) => { + // Verify that if ChildProcess#kill() throws, the error is reported. + assert.strictEqual(err.message, 'mock error', err); + assert.strictEqual(stdout, ''); + assert.strictEqual(stderr, ''); + assert.strictEqual(child.killed, true); + })); +} diff --git a/cli/tests/node_compat/test/parallel/test-child-process-exec-maxbuf.js b/cli/tests/node_compat/test/parallel/test-child-process-exec-maxbuf.js new file mode 100644 index 00000000000000..2e99855c040f52 --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-child-process-exec-maxbuf.js @@ -0,0 +1,161 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.8.0 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +// TODO(PolarETech): The "eval" subcommand passed to exec() should be the "-e" option. + +'use strict'; +const common = require('../common'); +const assert = require('assert'); +const cp = require('child_process'); + +function runChecks(err, stdio, streamName, expected) { + assert.strictEqual(err.message, `${streamName} maxBuffer length exceeded`); + assert(err instanceof RangeError); + assert.strictEqual(err.code, 'ERR_CHILD_PROCESS_STDIO_MAXBUFFER'); + assert.deepStrictEqual(stdio[streamName], expected); +} + +// default value +{ + const cmd = + `"${process.execPath}" eval "console.log('a'.repeat(1024 * 1024))"`; + + cp.exec(cmd, common.mustCall((err) => { + assert(err instanceof RangeError); + assert.strictEqual(err.message, 'stdout maxBuffer length exceeded'); + assert.strictEqual(err.code, 'ERR_CHILD_PROCESS_STDIO_MAXBUFFER'); + })); +} + +// default value +{ + const cmd = + `${process.execPath} eval "console.log('a'.repeat(1024 * 1024 - 1))"`; + + cp.exec(cmd, common.mustSucceed((stdout, stderr) => { + assert.strictEqual(stdout.trim(), 'a'.repeat(1024 * 1024 - 1)); + assert.strictEqual(stderr, ''); + })); +} + +{ + const cmd = `"${process.execPath}" eval "console.log('hello world');"`; + const options = { maxBuffer: Infinity }; + + cp.exec(cmd, options, common.mustSucceed((stdout, stderr) => { + assert.strictEqual(stdout.trim(), 'hello world'); + assert.strictEqual(stderr, ''); + })); +} + +{ + const cmd = 'echo hello world'; + + cp.exec( + cmd, + { maxBuffer: 5 }, + common.mustCall((err, stdout, stderr) => { + runChecks(err, { stdout, stderr }, 'stdout', 'hello'); + }) + ); +} + +// default value +{ + const cmd = + `"${process.execPath}" eval "console.log('a'.repeat(1024 * 1024))"`; + + cp.exec( + cmd, + common.mustCall((err, stdout, stderr) => { + runChecks( + err, + { stdout, stderr }, + 'stdout', + 'a'.repeat(1024 * 1024) + ); + }) + ); +} + +// default value +{ + const cmd = + `"${process.execPath}" eval "console.log('a'.repeat(1024 * 1024 - 1))"`; + + cp.exec(cmd, common.mustSucceed((stdout, stderr) => { + assert.strictEqual(stdout.trim(), 'a'.repeat(1024 * 1024 - 1)); + assert.strictEqual(stderr, ''); + })); +} + +const unicode = '中文测试'; // length = 4, byte length = 12 + +{ + const cmd = `"${process.execPath}" eval "console.log('${unicode}');"`; + + cp.exec( + cmd, + { maxBuffer: 10 }, + common.mustCall((err, stdout, stderr) => { + runChecks(err, { stdout, stderr }, 'stdout', '中文测试\n'); + }) + ); +} + +{ + const cmd = `"${process.execPath}" eval "console.error('${unicode}');"`; + + cp.exec( + cmd, + { maxBuffer: 3 }, + common.mustCall((err, stdout, stderr) => { + runChecks(err, { stdout, stderr }, 'stderr', '中文测'); + }) + ); +} + +{ + const cmd = `"${process.execPath}" eval "console.log('${unicode}');"`; + + const child = cp.exec( + cmd, + { encoding: null, maxBuffer: 10 }, + common.mustCall((err, stdout, stderr) => { + runChecks(err, { stdout, stderr }, 'stdout', '中文测试\n'); + }) + ); + + child.stdout.setEncoding('utf-8'); +} + +{ + const cmd = `"${process.execPath}" eval "console.error('${unicode}');"`; + + const child = cp.exec( + cmd, + { encoding: null, maxBuffer: 3 }, + common.mustCall((err, stdout, stderr) => { + runChecks(err, { stdout, stderr }, 'stderr', '中文测'); + }) + ); + + child.stderr.setEncoding('utf-8'); +} + +{ + const cmd = `"${process.execPath}" eval "console.error('${unicode}');"`; + + cp.exec( + cmd, + { encoding: null, maxBuffer: 5 }, + common.mustCall((err, stdout, stderr) => { + const buf = Buffer.from(unicode).slice(0, 5); + runChecks(err, { stdout, stderr }, 'stderr', buf); + }) + ); +} diff --git a/cli/tests/node_compat/test/parallel/test-child-process-exec-std-encoding.js b/cli/tests/node_compat/test/parallel/test-child-process-exec-std-encoding.js new file mode 100644 index 00000000000000..85f3ec2bf17507 --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-child-process-exec-std-encoding.js @@ -0,0 +1,33 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.8.0 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +// TODO(PolarETech): The process.argv[3] check should be argv[2], and the +// command passed to exec() should not need to include "run", "-A", +// and "require.ts". + +'use strict'; +const common = require('../common'); +const assert = require('assert'); +const cp = require('child_process'); +const stdoutData = 'foo'; +const stderrData = 'bar'; +const expectedStdout = `${stdoutData}\n`; +const expectedStderr = `${stderrData}\n`; + +if (process.argv[3] === 'child') { + // The following console calls are part of the test. + console.log(stdoutData); + console.error(stderrData); +} else { + const cmd = `"${process.execPath}" run -A require.ts "${__filename}" child`; + const child = cp.exec(cmd, common.mustSucceed((stdout, stderr) => { + assert.strictEqual(stdout, expectedStdout); + assert.strictEqual(stderr, expectedStderr); + })); + child.stdout.setEncoding('utf-8'); + child.stderr.setEncoding('utf-8'); +} diff --git a/cli/tests/node_compat/test/parallel/test-child-process-exec-stdout-stderr-data-string.js b/cli/tests/node_compat/test/parallel/test-child-process-exec-stdout-stderr-data-string.js new file mode 100644 index 00000000000000..6a4c9fe5fdda23 --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-child-process-exec-stdout-stderr-data-string.js @@ -0,0 +1,20 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +'use strict'; +// Refs: https://github.com/nodejs/node/issues/7342 +const common = require('../common'); +const assert = require('assert'); +const exec = require('child_process').exec; + +const command = common.isWindows ? 'dir' : 'ls'; + +exec(command).stdout.on('data', common.mustCallAtLeast()); + +exec('fhqwhgads').stderr.on('data', common.mustCallAtLeast((data) => { + assert.strictEqual(typeof data, 'string'); +})); diff --git a/cli/tests/node_compat/test/parallel/test-child-process-exec-timeout-expire.js b/cli/tests/node_compat/test/parallel/test-child-process-exec-timeout-expire.js new file mode 100644 index 00000000000000..67c4a720460838 --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-child-process-exec-timeout-expire.js @@ -0,0 +1,61 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +// TODO(PolarETech): The process.argv[3] check should be argv[2], and the +// command passed to exec() should not need to include "run", "-A", +// and "require.ts". + +'use strict'; + +// Test exec() with a timeout that expires. + +const common = require('../common'); +const assert = require('assert'); +const cp = require('child_process'); + +const { + cleanupStaleProcess, + logAfterTime, + kExpiringChildRunTime, + kExpiringParentTimer +} = require('../common/child_process'); + +if (process.argv[3] === 'child') { + logAfterTime(kExpiringChildRunTime); + return; +} + +const cmd = `"${process.execPath}" run -A require.ts "${__filename}" child`; + +cp.exec(cmd, { + timeout: kExpiringParentTimer, +}, common.mustCall((err, stdout, stderr) => { + console.log('[stdout]', stdout.trim()); + console.log('[stderr]', stderr.trim()); + + let sigterm = 'SIGTERM'; + assert.strictEqual(err.killed, true); + // TODO OpenBSD returns a null signal and 143 for code + if (common.isOpenBSD) { + assert.strictEqual(err.code, 143); + sigterm = null; + } else { + assert.strictEqual(err.code, null); + } + // At least starting with Darwin Kernel Version 16.4.0, sending a SIGTERM to a + // process that is still starting up kills it with SIGKILL instead of SIGTERM. + // See: https://github.com/libuv/libuv/issues/1226 + if (common.isOSX) + assert.ok(err.signal === 'SIGTERM' || err.signal === 'SIGKILL'); + else + assert.strictEqual(err.signal, sigterm); + assert.strictEqual(err.cmd, cmd); + assert.strictEqual(stdout.trim(), ''); + assert.strictEqual(stderr.trim(), ''); +})); + +cleanupStaleProcess(__filename); diff --git a/cli/tests/node_compat/test/parallel/test-child-process-exec-timeout-kill.js b/cli/tests/node_compat/test/parallel/test-child-process-exec-timeout-kill.js new file mode 100644 index 00000000000000..fd4884fc5fc8df --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-child-process-exec-timeout-kill.js @@ -0,0 +1,50 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +// TODO(PolarETech): The process.argv[3] check should be argv[2], and the +// command passed to exec() should not need to include "run", "-A", +// and "require.ts". + +'use strict'; + +// Test exec() with both a timeout and a killSignal. + +const common = require('../common'); +const assert = require('assert'); +const cp = require('child_process'); + +const { + cleanupStaleProcess, + logInTimeout, + kExpiringChildRunTime, + kExpiringParentTimer, +} = require('../common/child_process'); + +if (process.argv[3] === 'child') { + logInTimeout(kExpiringChildRunTime); + return; +} + +const cmd = `"${process.execPath}" run -A require.ts "${__filename}" child`; + +// Test with a different kill signal. +cp.exec(cmd, { + timeout: kExpiringParentTimer, + killSignal: 'SIGKILL' +}, common.mustCall((err, stdout, stderr) => { + console.log('[stdout]', stdout.trim()); + console.log('[stderr]', stderr.trim()); + + assert.strictEqual(err.killed, true); + assert.strictEqual(err.code, null); + assert.strictEqual(err.signal, 'SIGKILL'); + assert.strictEqual(err.cmd, cmd); + assert.strictEqual(stdout.trim(), ''); + assert.strictEqual(stderr.trim(), ''); +})); + +cleanupStaleProcess(__filename); diff --git a/cli/tests/node_compat/test/parallel/test-child-process-exec-timeout-not-expired.js b/cli/tests/node_compat/test/parallel/test-child-process-exec-timeout-not-expired.js new file mode 100644 index 00000000000000..31fa1f7259f747 --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-child-process-exec-timeout-not-expired.js @@ -0,0 +1,45 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +// TODO(PolarETech): The process.argv[3] check should be argv[2], and the +// command passed to exec() should not need to include "run", "-A", +// and "require.ts". + +'use strict'; + +// Test exec() when a timeout is set, but not expired. + +const common = require('../common'); +const assert = require('assert'); +const cp = require('child_process'); + +const { + cleanupStaleProcess, + logAfterTime +} = require('../common/child_process'); + +const kTimeoutNotSupposedToExpire = 2 ** 30; +const childRunTime = common.platformTimeout(100); + +// The time spent in the child should be smaller than the timeout below. +assert(childRunTime < kTimeoutNotSupposedToExpire); + +if (process.argv[3] === 'child') { + logAfterTime(childRunTime); + return; +} + +const cmd = `"${process.execPath}" run -A require.ts "${__filename}" child`; + +cp.exec(cmd, { + timeout: kTimeoutNotSupposedToExpire +}, common.mustSucceed((stdout, stderr) => { + assert.strictEqual(stdout.trim(), 'child stdout'); + assert.strictEqual(stderr.trim(), 'child stderr'); +})); + +cleanupStaleProcess(__filename); diff --git a/cli/tests/node_compat/test/parallel/test-child-process-execFile-promisified-abortController.js b/cli/tests/node_compat/test/parallel/test-child-process-execFile-promisified-abortController.js new file mode 100644 index 00000000000000..fe3e8765ea26dd --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-child-process-execFile-promisified-abortController.js @@ -0,0 +1,66 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +// TODO(PolarETech): The args passed to promisified() should not need to +// include "require.ts". + +'use strict'; + +const common = require('../common'); +const assert = require('assert'); +const { promisify } = require('util'); +const execFile = require('child_process').execFile; +const fixtures = require('../common/fixtures'); + +const echoFixture = fixtures.path('echo.js'); +const promisified = promisify(execFile); +const invalidArgTypeError = { + code: 'ERR_INVALID_ARG_TYPE', + name: 'TypeError' +}; + +{ + // Verify that the signal option works properly + const ac = new AbortController(); + const signal = ac.signal; + const promise = promisified(process.execPath, ['require.ts', echoFixture, 0], { signal }); + + ac.abort(); + + assert.rejects( + promise, + { name: 'AbortError' } + ).then(common.mustCall()); +} + +{ + // Verify that the signal option works properly when already aborted + const signal = AbortSignal.abort(); + + assert.rejects( + promisified(process.execPath, ['require.ts', echoFixture, 0], { signal }), + { name: 'AbortError' } + ).then(common.mustCall()); +} + +{ + // Verify that if something different than Abortcontroller.signal + // is passed, ERR_INVALID_ARG_TYPE is thrown + const signal = {}; + assert.throws(() => { + promisified(process.execPath, ['require.ts', echoFixture, 0], { signal }); + }, invalidArgTypeError); +} + +{ + // Verify that if something different than Abortcontroller.signal + // is passed, ERR_INVALID_ARG_TYPE is thrown + const signal = 'world!'; + assert.throws(() => { + promisified(process.execPath, ['require.ts', echoFixture, 0], { signal }); + }, invalidArgTypeError); +} diff --git a/cli/tests/node_compat/test/parallel/test-child-process-execfile-maxbuf.js b/cli/tests/node_compat/test/parallel/test-child-process-execfile-maxbuf.js new file mode 100644 index 00000000000000..ef31f4e225201b --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-child-process-execfile-maxbuf.js @@ -0,0 +1,99 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +'use strict'; +const common = require('../common'); +const assert = require('assert'); +const { execFile } = require('child_process'); + +function checkFactory(streamName) { + return common.mustCall((err) => { + assert(err instanceof RangeError); + assert.strictEqual(err.message, `${streamName} maxBuffer length exceeded`); + assert.strictEqual(err.code, 'ERR_CHILD_PROCESS_STDIO_MAXBUFFER'); + }); +} + +// default value +{ + execFile( + process.execPath, + ['-e', 'console.log("a".repeat(1024 * 1024))'], + checkFactory('stdout') + ); +} + +// default value +{ + execFile( + process.execPath, + ['-e', 'console.log("a".repeat(1024 * 1024 - 1))'], + common.mustSucceed((stdout, stderr) => { + assert.strictEqual(stdout.trim(), 'a'.repeat(1024 * 1024 - 1)); + assert.strictEqual(stderr, ''); + }) + ); +} + +{ + const options = { maxBuffer: Infinity }; + + execFile( + process.execPath, + ['-e', 'console.log("hello world");'], + options, + common.mustSucceed((stdout, stderr) => { + assert.strictEqual(stdout.trim(), 'hello world'); + assert.strictEqual(stderr, ''); + }) + ); +} + +{ + execFile('echo', ['hello world'], { maxBuffer: 5 }, checkFactory('stdout')); +} + +const unicode = '中文测试'; // length = 4, byte length = 12 + +{ + execFile( + process.execPath, + ['-e', `console.log('${unicode}');`], + { maxBuffer: 10 }, + checkFactory('stdout')); +} + +{ + execFile( + process.execPath, + ['-e', `console.error('${unicode}');`], + { maxBuffer: 10 }, + checkFactory('stderr') + ); +} + +{ + const child = execFile( + process.execPath, + ['-e', `console.log('${unicode}');`], + { encoding: null, maxBuffer: 10 }, + checkFactory('stdout') + ); + + child.stdout.setEncoding('utf-8'); +} + +{ + const child = execFile( + process.execPath, + ['-e', `console.error('${unicode}');`], + { encoding: null, maxBuffer: 10 }, + checkFactory('stderr') + ); + + child.stderr.setEncoding('utf-8'); +} diff --git a/cli/tests/node_compat/test/parallel/test-child-process-execfile.js b/cli/tests/node_compat/test/parallel/test-child-process-execfile.js new file mode 100644 index 00000000000000..9f9268407baa47 --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-child-process-execfile.js @@ -0,0 +1,135 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.8.0 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +// TODO(PolarETech): The args passed to execFile() should not need to +// include "require.ts". + +// TODO(cjihrig): See inline TODO comments below. + +'use strict'; + +const common = require('../common'); +const assert = require('assert'); +const { execFile, execFileSync } = require('child_process'); +const { getSystemErrorName } = require('util'); +const fixtures = require('../common/fixtures'); +const os = require('os'); + +const fixture = fixtures.path('exit.js'); +const echoFixture = fixtures.path('echo.js'); +const execOpts = { encoding: 'utf8', shell: true }; + +{ + execFile( + process.execPath, + ['require.ts', fixture, 42], + common.mustCall((e) => { + // Check that arguments are included in message + assert.strictEqual(e.message.trim(), + `Command failed: ${process.execPath} require.ts ${fixture} 42`); + assert.strictEqual(e.code, 42); + }) + ); +} + +{ + // Verify that negative exit codes can be translated to UV error names. + const errorString = `Error: Command failed: ${process.execPath}`; + const code = -1; + const callback = common.mustCall((err, stdout, stderr) => { + assert.strictEqual(err.toString().trim(), errorString); + assert.strictEqual(err.code, getSystemErrorName(code)); + assert.strictEqual(err.killed, true); + assert.strictEqual(err.signal, null); + assert.strictEqual(err.cmd, process.execPath); + assert.strictEqual(stdout.trim(), ''); + assert.strictEqual(stderr.trim(), ''); + }); + const child = execFile(process.execPath, callback); + + child.kill(); + child.emit('close', code, null); +} + +{ + // Verify the shell option works properly + execFile(process.execPath, ['require.ts', fixture, 0], execOpts, common.mustSucceed()); +} + +{ + // Verify that the signal option works properly + const ac = new AbortController(); + const { signal } = ac; + + const test = () => { + const check = common.mustCall((err) => { + assert.strictEqual(err.code, 'ABORT_ERR'); + assert.strictEqual(err.name, 'AbortError'); + assert.strictEqual(err.signal, undefined); + }); + execFile(process.execPath, ['require.ts', echoFixture, 0], { signal }, check); + }; + + // Verify that it still works the same way now that the signal is aborted. + test(); + ac.abort(); +} + +{ + // Verify that does not spawn a child if already aborted + const signal = AbortSignal.abort(); + + const check = common.mustCall((err) => { + assert.strictEqual(err.code, 'ABORT_ERR'); + assert.strictEqual(err.name, 'AbortError'); + assert.strictEqual(err.signal, undefined); + }); + execFile(process.execPath, ['require.ts', echoFixture, 0], { signal }, check); +} + +{ + // Verify that if something different than Abortcontroller.signal + // is passed, ERR_INVALID_ARG_TYPE is thrown + assert.throws(() => { + const callback = common.mustNotCall(() => {}); + + execFile(process.execPath, ['require.ts', echoFixture, 0], { signal: 'hello' }, callback); + }, { code: 'ERR_INVALID_ARG_TYPE', name: 'TypeError' }); +} +{ + // Verify that the process completing removes the abort listener + const ac = new AbortController(); + const { signal } = ac; + + const callback = common.mustCall((err) => { + // TODO(cjihrig): The assertion on the next line currently fails because + // kEvents is not defined. See TODO comment in _events.mjs. + // assert.strictEqual(getEventListeners(ac.signal).length, 0); + assert.strictEqual(err, null); + }); + execFile(process.execPath, ['require.ts', fixture, 0], { signal }, callback); +} + +// Verify the execFile() stdout is the same as execFileSync(). +{ + const file = 'echo'; + const args = ['foo', 'bar']; + + // Test with and without `{ shell: true }` + [ + // Skipping shell-less test on Windows because its echo command is a shell built-in command. + ...(common.isWindows ? [] : [{ encoding: 'utf8' }]), + { shell: true, encoding: 'utf8' }, + ].forEach((options) => { + const execFileSyncStdout = execFileSync(file, args, options); + assert.strictEqual(execFileSyncStdout, `foo bar${os.EOL}`); + + execFile(file, args, options, common.mustCall((_, stdout) => { + assert.strictEqual(stdout, execFileSyncStdout); + })); + }); +} diff --git a/cli/tests/node_compat/test/parallel/test-child-process-execfilesync-maxbuf.js b/cli/tests/node_compat/test/parallel/test-child-process-execfilesync-maxbuf.js new file mode 100644 index 00000000000000..e5f7166ea51305 --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-child-process-execfilesync-maxbuf.js @@ -0,0 +1,60 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +'use strict'; +require('../common'); + +// This test checks that the maxBuffer option for child_process.execFileSync() +// works as expected. + +const assert = require('assert'); +const { getSystemErrorName } = require('util'); +const { execFileSync } = require('child_process'); +const msgOut = 'this is stdout'; +const msgOutBuf = Buffer.from(`${msgOut}\n`); + +const args = [ + '-e', + `console.log("${msgOut}");`, +]; + +// Verify that an error is returned if maxBuffer is surpassed. +{ + assert.throws(() => { + execFileSync(process.execPath, args, { maxBuffer: 1 }); + }, (e) => { + assert.ok(e, 'maxBuffer should error'); + assert.strictEqual(e.code, 'ENOBUFS'); + assert.strictEqual(getSystemErrorName(e.errno), 'ENOBUFS'); + // We can have buffers larger than maxBuffer because underneath we alloc 64k + // that matches our read sizes. + assert.deepStrictEqual(e.stdout, msgOutBuf); + return true; + }); +} + +// Verify that a maxBuffer size of Infinity works. +{ + const ret = execFileSync(process.execPath, args, { maxBuffer: Infinity }); + + assert.deepStrictEqual(ret, msgOutBuf); +} + +// Default maxBuffer size is 1024 * 1024. +{ + assert.throws(() => { + execFileSync( + process.execPath, + ['-e', "console.log('a'.repeat(1024 * 1024))"] + ); + }, (e) => { + assert.ok(e, 'maxBuffer should error'); + assert.strictEqual(e.code, 'ENOBUFS'); + assert.strictEqual(getSystemErrorName(e.errno), 'ENOBUFS'); + return true; + }); +} diff --git a/cli/tests/node_compat/test/parallel/test-child-process-execsync-maxbuf.js b/cli/tests/node_compat/test/parallel/test-child-process-execsync-maxbuf.js new file mode 100644 index 00000000000000..703896ef170216 --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-child-process-execsync-maxbuf.js @@ -0,0 +1,76 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.8.0 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +// TODO(cjihrig): This should use Node's -e instead of Deno's eval CLI arg. + +'use strict'; +require('../common'); + +// This test checks that the maxBuffer option for child_process.spawnSync() +// works as expected. + +const assert = require('assert'); +const { getSystemErrorName } = require('util'); +const { execSync } = require('child_process'); +const msgOut = 'this is stdout'; +const msgOutBuf = Buffer.from(`${msgOut}\n`); + +const args = [ + 'eval', + `"console.log('${msgOut}')";`, +]; + +// Verify that an error is returned if maxBuffer is surpassed. +{ + assert.throws(() => { + execSync(`"${process.execPath}" ${args.join(' ')}`, { maxBuffer: 1 }); + }, (e) => { + assert.ok(e, 'maxBuffer should error'); + assert.strictEqual(e.code, 'ENOBUFS'); + assert.strictEqual(getSystemErrorName(e.errno), 'ENOBUFS'); + // We can have buffers larger than maxBuffer because underneath we alloc 64k + // that matches our read sizes. + assert.deepStrictEqual(e.stdout, msgOutBuf); + return true; + }); +} + +// Verify that a maxBuffer size of Infinity works. +{ + const ret = execSync( + `"${process.execPath}" ${args.join(' ')}`, + { maxBuffer: Infinity } + ); + + assert.deepStrictEqual(ret, msgOutBuf); +} + +// Default maxBuffer size is 1024 * 1024. +{ + assert.throws(() => { + execSync( + `"${process.execPath}" eval "console.log('a'.repeat(1024 * 1024))"` + ); + }, (e) => { + assert.ok(e, 'maxBuffer should error'); + assert.strictEqual(e.code, 'ENOBUFS'); + assert.strictEqual(getSystemErrorName(e.errno), 'ENOBUFS'); + return true; + }); +} + +// Default maxBuffer size is 1024 * 1024. +{ + const ret = execSync( + `"${process.execPath}" eval "console.log('a'.repeat(1024 * 1024 - 1))"` + ); + + assert.deepStrictEqual( + ret.toString().trim(), + 'a'.repeat(1024 * 1024 - 1) + ); +} diff --git a/cli/tests/node_compat/test/parallel/test-child-process-exit-code.js b/cli/tests/node_compat/test/parallel/test-child-process-exit-code.js new file mode 100644 index 00000000000000..caa57986b42bc6 --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-child-process-exit-code.js @@ -0,0 +1,51 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. + +// TODO(PolarETech): The args passed to spawn() should not need to +// include "require.ts". + +'use strict'; +const common = require('../common'); +const assert = require('assert'); +const spawn = require('child_process').spawn; +const fixtures = require('../common/fixtures'); + +const exitScript = fixtures.path('exit.js'); +const exitChild = spawn(process.argv[0], ['require.ts', exitScript, 23]); +exitChild.on('exit', common.mustCall(function(code, signal) { + assert.strictEqual(code, 23); + assert.strictEqual(signal, null); +})); + + +const errorScript = fixtures.path('child_process_should_emit_error.js'); +const errorChild = spawn(process.argv[0], ['require.ts', errorScript]); +errorChild.on('exit', common.mustCall(function(code, signal) { + assert.ok(code !== 0); + assert.strictEqual(signal, null); +})); diff --git a/cli/tests/node_compat/test/parallel/test-child-process-flush-stdio.js b/cli/tests/node_compat/test/parallel/test-child-process-flush-stdio.js new file mode 100644 index 00000000000000..72357bcdd42f6a --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-child-process-flush-stdio.js @@ -0,0 +1,40 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +'use strict'; +const common = require('../common'); +const cp = require('child_process'); +const assert = require('assert'); + +// Windows' `echo` command is a built-in shell command and not an external +// executable like on *nix +const opts = { shell: common.isWindows }; + +const p = cp.spawn('echo', [], opts); + +p.on('close', common.mustCall((code, signal) => { + assert.strictEqual(code, 0); + assert.strictEqual(signal, null); + spawnWithReadable(); +})); + +p.stdout.read(); + +const spawnWithReadable = () => { + const buffer = []; + const p = cp.spawn('echo', ['123'], opts); + p.on('close', common.mustCall((code, signal) => { + assert.strictEqual(code, 0); + assert.strictEqual(signal, null); + assert.strictEqual(Buffer.concat(buffer).toString().trim(), '123'); + })); + p.stdout.on('readable', () => { + let buf; + while ((buf = p.stdout.read()) !== null) + buffer.push(buf); + }); +}; diff --git a/cli/tests/node_compat/test/parallel/test-child-process-ipc.js b/cli/tests/node_compat/test/parallel/test-child-process-ipc.js new file mode 100644 index 00000000000000..c1d7bc2b618414 --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-child-process-ipc.js @@ -0,0 +1,73 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. + +// TODO(PolarETech): The args passed to spawn() should not need to +// include "require.ts". + +'use strict'; + +const { + mustCall, + mustNotCall, +} = require('../common'); +const assert = require('assert'); +const debug = require('util').debuglog('test'); + +const { spawn } = require('child_process'); +const fixtures = require('../common/fixtures'); + +const sub = fixtures.path('echo.js'); + +const child = spawn(process.argv[0], ['require.ts', sub]); + +child.stderr.on('data', mustNotCall()); + +child.stdout.setEncoding('utf8'); + +const messages = [ + 'hello world\r\n', + 'echo me\r\n', +]; + +child.stdout.on('data', mustCall((data) => { + debug(`child said: ${JSON.stringify(data)}`); + const test = messages.shift(); + debug(`testing for '${test}'`); + assert.strictEqual(data, test); + if (messages.length) { + debug(`writing '${messages[0]}'`); + child.stdin.write(messages[0]); + } else { + assert.strictEqual(messages.length, 0); + child.stdin.end(); + } +}, messages.length)); + +child.stdout.on('end', mustCall((data) => { + debug('child end'); +})); diff --git a/cli/tests/node_compat/test/parallel/test-child-process-kill.js b/cli/tests/node_compat/test/parallel/test-child-process-kill.js new file mode 100644 index 00000000000000..6d5c4cba1d6462 --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-child-process-kill.js @@ -0,0 +1,48 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. + +'use strict'; +const common = require('../common'); +const assert = require('assert'); +const spawn = require('child_process').spawn; +const cat = spawn(common.isWindows ? 'cmd' : 'cat'); + +cat.stdout.on('end', common.mustCall()); +cat.stderr.on('data', common.mustNotCall()); +cat.stderr.on('end', common.mustCall()); + +cat.on('exit', common.mustCall((code, signal) => { + assert.strictEqual(code, null); + assert.strictEqual(signal, 'SIGTERM'); + assert.strictEqual(cat.signalCode, 'SIGTERM'); +})); + +assert.strictEqual(cat.signalCode, null); +assert.strictEqual(cat.killed, false); +cat.kill(); +assert.strictEqual(cat.killed, true); diff --git a/cli/tests/node_compat/test/parallel/test-child-process-set-blocking.js b/cli/tests/node_compat/test/parallel/test-child-process-set-blocking.js new file mode 100644 index 00000000000000..0c771bad8fb97c --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-child-process-set-blocking.js @@ -0,0 +1,43 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. + +'use strict'; +const common = require('../common'); +const assert = require('assert'); +const ch = require('child_process'); + +const SIZE = 100000; +const python = process.env.PYTHON || (common.isWindows ? 'python' : 'python3'); + +const cp = ch.spawn(python, ['-c', `print(${SIZE} * "C")`], { + stdio: 'inherit' +}); + +cp.on('exit', common.mustCall(function(code) { + assert.strictEqual(code, 0); +})); diff --git a/cli/tests/node_compat/test/parallel/test-child-process-spawn-args.js b/cli/tests/node_compat/test/parallel/test-child-process-spawn-args.js new file mode 100644 index 00000000000000..1e3378935fe4ad --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-child-process-spawn-args.js @@ -0,0 +1,62 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +'use strict'; + +// This test confirms that `undefined`, `null`, and `[]` +// can be used as a placeholder for the second argument (`args`) of `spawn()`. +// Previously, there was a bug where using `undefined` for the second argument +// caused the third argument (`options`) to be ignored. +// See https://github.com/nodejs/node/issues/24912. + +const common = require('../common'); +const tmpdir = require('../common/tmpdir'); + +const assert = require('assert'); +const { spawn } = require('child_process'); + +tmpdir.refresh(); + +const command = common.isWindows ? 'cd' : 'pwd'; +const options = { cwd: tmpdir.path }; + +if (common.isWindows) { + // This test is not the case for Windows based systems + // unless the `shell` options equals to `true` + + options.shell = true; +} + +const testCases = [ + undefined, + null, + [], +]; + +const expectedResult = tmpdir.path.trim().toLowerCase(); + +(async () => { + const results = await Promise.all( + testCases.map((testCase) => { + return new Promise((resolve) => { + const subprocess = spawn(command, testCase, options); + + let accumulatedData = Buffer.alloc(0); + + subprocess.stdout.on('data', common.mustCall((data) => { + accumulatedData = Buffer.concat([accumulatedData, data]); + })); + + subprocess.stdout.on('end', () => { + resolve(accumulatedData.toString().trim().toLowerCase()); + }); + }); + }) + ); + + assert.deepStrictEqual([...new Set(results)], [expectedResult]); +})().then(common.mustCall()); diff --git a/cli/tests/node_compat/test/parallel/test-child-process-spawn-event.js b/cli/tests/node_compat/test/parallel/test-child-process-spawn-event.js new file mode 100644 index 00000000000000..62252f829ff2e9 --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-child-process-spawn-event.js @@ -0,0 +1,34 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +'use strict'; +const common = require('../common'); +const spawn = require('child_process').spawn; +const assert = require('assert'); + +const subprocess = spawn('echo', ['ok']); + +let didSpawn = false; +subprocess.on('spawn', function() { + didSpawn = true; +}); +function mustCallAfterSpawn() { + return common.mustCall(function() { + assert.ok(didSpawn); + }); +} + +subprocess.on('error', common.mustNotCall()); +subprocess.on('spawn', common.mustCall()); +subprocess.stdout.on('data', mustCallAfterSpawn()); +subprocess.stdout.on('end', mustCallAfterSpawn()); +subprocess.stdout.on('close', mustCallAfterSpawn()); +subprocess.stderr.on('data', common.mustNotCall()); +subprocess.stderr.on('end', mustCallAfterSpawn()); +subprocess.stderr.on('close', mustCallAfterSpawn()); +subprocess.on('exit', mustCallAfterSpawn()); +subprocess.on('close', mustCallAfterSpawn()); diff --git a/cli/tests/node_compat/test/parallel/test-child-process-spawnsync-args.js b/cli/tests/node_compat/test/parallel/test-child-process-spawnsync-args.js new file mode 100644 index 00000000000000..9bca413b09cb0e --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-child-process-spawnsync-args.js @@ -0,0 +1,55 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +'use strict'; + +// This test confirms that `undefined`, `null`, and `[]` can be used +// as a placeholder for the second argument (`args`) of `spawnSync()`. +// Previously, there was a bug where using `undefined` for the second argument +// caused the third argument (`options`) to be ignored. +// See https://github.com/nodejs/node/issues/24912. + +const common = require('../common'); +const tmpdir = require('../common/tmpdir'); + +const assert = require('assert'); +const { spawnSync } = require('child_process'); + +const command = common.isWindows ? 'cd' : 'pwd'; +const options = { cwd: tmpdir.path }; + +tmpdir.refresh(); + +if (common.isWindows) { + // This test is not the case for Windows based systems + // unless the `shell` options equals to `true` + + options.shell = true; +} + +const testCases = [ + undefined, + null, + [], +]; + +const expectedResult = tmpdir.path.trim().toLowerCase(); + +const results = testCases.map((testCase) => { + const { stdout, stderr, error } = spawnSync( + command, + testCase, + options + ); + + assert.ifError(error); + assert.deepStrictEqual(stderr, Buffer.alloc(0)); + + return stdout.toString().trim().toLowerCase(); +}); + +assert.deepStrictEqual([...new Set(results)], [expectedResult]); diff --git a/cli/tests/node_compat/test/parallel/test-child-process-spawnsync-env.js b/cli/tests/node_compat/test/parallel/test-child-process-spawnsync-env.js new file mode 100644 index 00000000000000..d08ed48d95f4af --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-child-process-spawnsync-env.js @@ -0,0 +1,47 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.8.0 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. + +// TODO(cjihrig): The process.argv[3] check should be argv[2], and the +// arguments array passed to spawnSync() should not need to include +// "require.ts". + +'use strict'; +require('../common'); +const assert = require('assert'); +const cp = require('child_process'); + +if (process.argv[3] === 'child') { + console.log(process.env.foo); +} else { + const expected = 'bar'; + const child = cp.spawnSync(process.execPath, ["require.ts", __filename, 'child'], { + env: Object.assign(process.env, { foo: expected }) + }); + + assert.strictEqual(child.stdout.toString().trim(), expected); +} diff --git a/cli/tests/node_compat/test/parallel/test-child-process-spawnsync-maxbuf.js b/cli/tests/node_compat/test/parallel/test-child-process-spawnsync-maxbuf.js new file mode 100644 index 00000000000000..5d18f127a69ddd --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-child-process-spawnsync-maxbuf.js @@ -0,0 +1,65 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +'use strict'; +require('../common'); + +// This test checks that the maxBuffer option for child_process.spawnSync() +// works as expected. + +const assert = require('assert'); +const spawnSync = require('child_process').spawnSync; +const { getSystemErrorName } = require('util'); +const msgOut = 'this is stdout'; +const msgOutBuf = Buffer.from(`${msgOut}\n`); + +const args = [ + '-e', + `console.log("${msgOut}");`, +]; + +// Verify that an error is returned if maxBuffer is surpassed. +{ + const ret = spawnSync(process.execPath, args, { maxBuffer: 1 }); + + assert.ok(ret.error, 'maxBuffer should error'); + assert.strictEqual(ret.error.code, 'ENOBUFS'); + assert.strictEqual(getSystemErrorName(ret.error.errno), 'ENOBUFS'); + // We can have buffers larger than maxBuffer because underneath we alloc 64k + // that matches our read sizes. + assert.deepStrictEqual(ret.stdout, msgOutBuf); +} + +// Verify that a maxBuffer size of Infinity works. +{ + const ret = spawnSync(process.execPath, args, { maxBuffer: Infinity }); + + assert.ifError(ret.error); + assert.deepStrictEqual(ret.stdout, msgOutBuf); +} + +// Default maxBuffer size is 1024 * 1024. +{ + const args = ['-e', "console.log('a'.repeat(1024 * 1024))"]; + const ret = spawnSync(process.execPath, args); + + assert.ok(ret.error, 'maxBuffer should error'); + assert.strictEqual(ret.error.code, 'ENOBUFS'); + assert.strictEqual(getSystemErrorName(ret.error.errno), 'ENOBUFS'); +} + +// Default maxBuffer size is 1024 * 1024. +{ + const args = ['-e', "console.log('a'.repeat(1024 * 1024 - 1))"]; + const ret = spawnSync(process.execPath, args); + + assert.ifError(ret.error); + assert.deepStrictEqual( + ret.stdout.toString().trim(), + 'a'.repeat(1024 * 1024 - 1) + ); +} diff --git a/cli/tests/node_compat/test/parallel/test-child-process-spawnsync-validation-errors.js b/cli/tests/node_compat/test/parallel/test-child-process-spawnsync-validation-errors.js new file mode 100644 index 00000000000000..b2b96e3c7af2c1 --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-child-process-spawnsync-validation-errors.js @@ -0,0 +1,223 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +'use strict'; +const common = require('../common'); +const assert = require('assert'); +const spawnSync = require('child_process').spawnSync; +const signals = require('os').constants.signals; +const rootUser = common.isWindows ? false : + common.isIBMi ? true : process.getuid() === 0; + +const invalidArgTypeError = { code: 'ERR_INVALID_ARG_TYPE', name: 'TypeError' }; +const invalidRangeError = { code: 'ERR_OUT_OF_RANGE', name: 'RangeError' }; + +function pass(option, value) { + // Run the command with the specified option. Since it's not a real command, + // spawnSync() should run successfully but return an ENOENT error. + const child = spawnSync('not_a_real_command', { [option]: value }); + + assert.strictEqual(child.error.code, 'ENOENT'); +} + +function fail(option, value, message) { + assert.throws(() => { + spawnSync('not_a_real_command', { [option]: value }); + }, message); +} + +{ + // Validate the cwd option + pass('cwd', undefined); + pass('cwd', null); + pass('cwd', __dirname); + fail('cwd', 0, invalidArgTypeError); + fail('cwd', 1, invalidArgTypeError); + fail('cwd', true, invalidArgTypeError); + fail('cwd', false, invalidArgTypeError); + fail('cwd', [], invalidArgTypeError); + fail('cwd', {}, invalidArgTypeError); + fail('cwd', common.mustNotCall(), invalidArgTypeError); +} + +{ + // Validate the detached option + pass('detached', undefined); + pass('detached', null); + pass('detached', true); + pass('detached', false); + fail('detached', 0, invalidArgTypeError); + fail('detached', 1, invalidArgTypeError); + fail('detached', __dirname, invalidArgTypeError); + fail('detached', [], invalidArgTypeError); + fail('detached', {}, invalidArgTypeError); + fail('detached', common.mustNotCall(), invalidArgTypeError); +} + +if (!common.isWindows) { + { + // Validate the uid option + if (!rootUser) { + pass('uid', undefined); + pass('uid', null); + pass('uid', process.getuid()); + fail('uid', __dirname, invalidArgTypeError); + fail('uid', true, invalidArgTypeError); + fail('uid', false, invalidArgTypeError); + fail('uid', [], invalidArgTypeError); + fail('uid', {}, invalidArgTypeError); + fail('uid', common.mustNotCall(), invalidArgTypeError); + fail('uid', NaN, invalidArgTypeError); + fail('uid', Infinity, invalidArgTypeError); + fail('uid', 3.1, invalidArgTypeError); + fail('uid', -3.1, invalidArgTypeError); + } + } + + { + // Validate the gid option + if (process.getgid() !== 0) { + pass('gid', undefined); + pass('gid', null); + pass('gid', process.getgid()); + fail('gid', __dirname, invalidArgTypeError); + fail('gid', true, invalidArgTypeError); + fail('gid', false, invalidArgTypeError); + fail('gid', [], invalidArgTypeError); + fail('gid', {}, invalidArgTypeError); + fail('gid', common.mustNotCall(), invalidArgTypeError); + fail('gid', NaN, invalidArgTypeError); + fail('gid', Infinity, invalidArgTypeError); + fail('gid', 3.1, invalidArgTypeError); + fail('gid', -3.1, invalidArgTypeError); + } + } +} + +{ + // Validate the shell option + pass('shell', undefined); + pass('shell', null); + pass('shell', false); + fail('shell', 0, invalidArgTypeError); + fail('shell', 1, invalidArgTypeError); + fail('shell', [], invalidArgTypeError); + fail('shell', {}, invalidArgTypeError); + fail('shell', common.mustNotCall(), invalidArgTypeError); +} + +{ + // Validate the argv0 option + pass('argv0', undefined); + pass('argv0', null); + pass('argv0', 'myArgv0'); + fail('argv0', 0, invalidArgTypeError); + fail('argv0', 1, invalidArgTypeError); + fail('argv0', true, invalidArgTypeError); + fail('argv0', false, invalidArgTypeError); + fail('argv0', [], invalidArgTypeError); + fail('argv0', {}, invalidArgTypeError); + fail('argv0', common.mustNotCall(), invalidArgTypeError); +} + +{ + // Validate the windowsHide option + pass('windowsHide', undefined); + pass('windowsHide', null); + pass('windowsHide', true); + pass('windowsHide', false); + fail('windowsHide', 0, invalidArgTypeError); + fail('windowsHide', 1, invalidArgTypeError); + fail('windowsHide', __dirname, invalidArgTypeError); + fail('windowsHide', [], invalidArgTypeError); + fail('windowsHide', {}, invalidArgTypeError); + fail('windowsHide', common.mustNotCall(), invalidArgTypeError); +} + +{ + // Validate the windowsVerbatimArguments option + pass('windowsVerbatimArguments', undefined); + pass('windowsVerbatimArguments', null); + pass('windowsVerbatimArguments', true); + pass('windowsVerbatimArguments', false); + fail('windowsVerbatimArguments', 0, invalidArgTypeError); + fail('windowsVerbatimArguments', 1, invalidArgTypeError); + fail('windowsVerbatimArguments', __dirname, invalidArgTypeError); + fail('windowsVerbatimArguments', [], invalidArgTypeError); + fail('windowsVerbatimArguments', {}, invalidArgTypeError); + fail('windowsVerbatimArguments', common.mustNotCall(), invalidArgTypeError); +} + +{ + // Validate the timeout option + pass('timeout', undefined); + pass('timeout', null); + pass('timeout', 1); + pass('timeout', 0); + fail('timeout', -1, invalidRangeError); + fail('timeout', true, invalidRangeError); + fail('timeout', false, invalidRangeError); + fail('timeout', __dirname, invalidRangeError); + fail('timeout', [], invalidRangeError); + fail('timeout', {}, invalidRangeError); + fail('timeout', common.mustNotCall(), invalidRangeError); + fail('timeout', NaN, invalidRangeError); + fail('timeout', Infinity, invalidRangeError); + fail('timeout', 3.1, invalidRangeError); + fail('timeout', -3.1, invalidRangeError); +} + +{ + // Validate the maxBuffer option + pass('maxBuffer', undefined); + pass('maxBuffer', null); + pass('maxBuffer', 1); + pass('maxBuffer', 0); + pass('maxBuffer', Infinity); + pass('maxBuffer', 3.14); + fail('maxBuffer', -1, invalidRangeError); + fail('maxBuffer', NaN, invalidRangeError); + fail('maxBuffer', -Infinity, invalidRangeError); + fail('maxBuffer', true, invalidRangeError); + fail('maxBuffer', false, invalidRangeError); + fail('maxBuffer', __dirname, invalidRangeError); + fail('maxBuffer', [], invalidRangeError); + fail('maxBuffer', {}, invalidRangeError); + fail('maxBuffer', common.mustNotCall(), invalidRangeError); +} + +{ + // Validate the killSignal option + const unknownSignalErr = { code: 'ERR_UNKNOWN_SIGNAL', name: 'TypeError' }; + + pass('killSignal', undefined); + pass('killSignal', null); + pass('killSignal', 'SIGKILL'); + fail('killSignal', 'SIGNOTAVALIDSIGNALNAME', unknownSignalErr); + fail('killSignal', true, invalidArgTypeError); + fail('killSignal', false, invalidArgTypeError); + fail('killSignal', [], invalidArgTypeError); + fail('killSignal', {}, invalidArgTypeError); + fail('killSignal', common.mustNotCall(), invalidArgTypeError); + + // Invalid signal names and numbers should fail + fail('killSignal', 500, unknownSignalErr); + fail('killSignal', 0, unknownSignalErr); + fail('killSignal', -200, unknownSignalErr); + fail('killSignal', 3.14, unknownSignalErr); + + Object.getOwnPropertyNames(Object.prototype).forEach((property) => { + fail('killSignal', property, unknownSignalErr); + }); + + // Valid signal names and numbers should pass + for (const signalName in signals) { + pass('killSignal', signals[signalName]); + pass('killSignal', signalName); + pass('killSignal', signalName.toLowerCase()); + } +} diff --git a/cli/tests/node_compat/test/parallel/test-child-process-spawnsync.js b/cli/tests/node_compat/test/parallel/test-child-process-spawnsync.js new file mode 100644 index 00000000000000..893e3b4e130124 --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-child-process-spawnsync.js @@ -0,0 +1,74 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. + +'use strict'; +const common = require('../common'); +const tmpdir = require('../common/tmpdir'); +tmpdir.refresh(); + +const assert = require('assert'); +const { spawnSync } = require('child_process'); +const { getSystemErrorName } = require('util'); + +// `sleep` does different things on Windows and Unix, but in both cases, it does +// more-or-less nothing if there are no parameters +const ret = spawnSync('sleep', ['0']); +assert.strictEqual(ret.status, 0); + +// Error test when command does not exist +const ret_err = spawnSync('command_does_not_exist', ['bar']).error; + +assert.strictEqual(ret_err.code, 'ENOENT'); +assert.strictEqual(getSystemErrorName(ret_err.errno), 'ENOENT'); +assert.strictEqual(ret_err.syscall, 'spawnSync command_does_not_exist'); +assert.strictEqual(ret_err.path, 'command_does_not_exist'); +assert.deepStrictEqual(ret_err.spawnargs, ['bar']); + +{ + // Test the cwd option + const cwd = tmpdir.path; + const response = spawnSync(...common.pwdCommand, { cwd }); + + assert.strictEqual(response.stdout.toString().trim(), cwd); +} + + +{ + // Assert Buffer is the default encoding + const retDefault = spawnSync(...common.pwdCommand); + const retBuffer = spawnSync(...common.pwdCommand, { encoding: 'buffer' }); + assert.deepStrictEqual(retDefault.output, retBuffer.output); + + const retUTF8 = spawnSync(...common.pwdCommand, { encoding: 'utf8' }); + const stringifiedDefault = [ + null, + retDefault.stdout.toString(), + retDefault.stderr.toString(), + ]; + assert.deepStrictEqual(retUTF8.output, stringifiedDefault); +} diff --git a/cli/tests/node_compat/test/parallel/test-child-process-stdio-inherit.js b/cli/tests/node_compat/test/parallel/test-child-process-stdio-inherit.js new file mode 100644 index 00000000000000..e213dd6b800928 --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-child-process-stdio-inherit.js @@ -0,0 +1,66 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. + +// TODO(PolarETech): The process.argv[3] check should be argv[2], and +// the args passed to spawn() should not need to include "require.ts". + +'use strict'; +require('../common'); +const assert = require('assert'); +const spawn = require('child_process').spawn; + +if (process.argv[3] === 'parent') + parent(); +else + grandparent(); + +function grandparent() { + const child = spawn(process.execPath, ['require.ts', __filename, 'parent']); + child.stderr.pipe(process.stderr); + let output = ''; + const input = 'asdfasdf'; + + child.stdout.on('data', function(chunk) { + output += chunk; + }); + child.stdout.setEncoding('utf8'); + + child.stdin.end(input); + + child.on('close', function(code, signal) { + assert.strictEqual(code, 0); + assert.strictEqual(signal, null); + // 'cat' on windows adds a \r\n at the end. + assert.strictEqual(output.trim(), input.trim()); + }); +} + +function parent() { + // Should not immediately exit. + spawn('cat', [], { stdio: 'inherit' }); +} diff --git a/cli/tests/node_compat/test/parallel/test-child-process-stdout-flush-exit.js b/cli/tests/node_compat/test/parallel/test-child-process-stdout-flush-exit.js new file mode 100644 index 00000000000000..585cc6084062db --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-child-process-stdout-flush-exit.js @@ -0,0 +1,67 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. + +// TODO(PolarETech): The process.argv[3] check should be argv[2], +// the args passed to spawn() should not need to include "require.ts", +// and the process.argv[2] passed to spawn() should be argv[1]. + +'use strict'; +const common = require('../common'); +const assert = require('assert'); + +// If child process output to console and exit +// The console.log statements here are part of the test. +if (process.argv[3] === 'child') { + console.log('hello'); + for (let i = 0; i < 200; i++) { + console.log('filler'); + } + console.log('goodbye'); + process.exit(0); +} else { + // parent process + const spawn = require('child_process').spawn; + + // spawn self as child + const child = spawn(process.argv[0], ['require.ts', process.argv[2], 'child']); + + let stdout = ''; + + child.stderr.on('data', common.mustNotCall()); + + // Check if we receive both 'hello' at start and 'goodbye' at end + child.stdout.setEncoding('utf8'); + child.stdout.on('data', common.mustCallAtLeast((data) => { + stdout += data; + })); + + child.on('close', common.mustCall(() => { + assert.strictEqual(stdout.slice(0, 6), 'hello\n'); + assert.strictEqual(stdout.slice(stdout.length - 8), 'goodbye\n'); + })); +} diff --git a/cli/tests/node_compat/test/parallel/test-child-process-stdout-flush.js b/cli/tests/node_compat/test/parallel/test-child-process-stdout-flush.js new file mode 100644 index 00000000000000..4054d2189202cd --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-child-process-stdout-flush.js @@ -0,0 +1,58 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. + +// TODO(PolarETech): The args passed to spawn() should not need to +// include "require.ts". + +'use strict'; +const common = require('../common'); +const assert = require('assert'); +const spawn = require('child_process').spawn; +const fixtures = require('../common/fixtures'); + +const sub = fixtures.path('print-chars.js'); + +const n = 500000; + +const child = spawn(process.argv[0], ['require.ts', sub, n]); + +let count = 0; + +child.stderr.setEncoding('utf8'); +child.stderr.on('data', common.mustNotCall()); + +child.stdout.setEncoding('utf8'); +child.stdout.on('data', (data) => { + count += data.length; +}); + +child.on('close', common.mustCall((code, signal) => { + assert.strictEqual(code, 0); + assert.strictEqual(signal, null); + assert.strictEqual(n, count); +})); diff --git a/cli/tests/node_compat/test/parallel/test-client-request-destroy.js b/cli/tests/node_compat/test/parallel/test-client-request-destroy.js new file mode 100644 index 00000000000000..f7e11ae0bf47c8 --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-client-request-destroy.js @@ -0,0 +1,20 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +'use strict'; + +// Test that http.ClientRequest,prototype.destroy() returns `this`. +require('../common'); + +const assert = require('assert'); +const http = require('http'); +const clientRequest = new http.ClientRequest({ createConnection: () => {} }); + +assert.strictEqual(clientRequest.destroyed, false); +assert.strictEqual(clientRequest.destroy(), clientRequest); +assert.strictEqual(clientRequest.destroyed, true); +assert.strictEqual(clientRequest.destroy(), clientRequest); diff --git a/cli/tests/node_compat/test/parallel/test-console-async-write-error.js b/cli/tests/node_compat/test/parallel/test-console-async-write-error.js new file mode 100644 index 00000000000000..9816ced4f58ac3 --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-console-async-write-error.js @@ -0,0 +1,22 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +'use strict'; +const common = require('../common'); +const { Console } = require('console'); +const { Writable } = require('stream'); + +for (const method of ['dir', 'log', 'warn']) { + const out = new Writable({ + write: common.mustCall((chunk, enc, callback) => { + process.nextTick(callback, new Error('foobar')); + }) + }); + + const c = new Console(out, out, true); + c[method]('abc'); // Should not throw. +} diff --git a/cli/tests/node_compat/test/parallel/test-console-group.js b/cli/tests/node_compat/test/parallel/test-console-group.js new file mode 100644 index 00000000000000..257317214ec83f --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-console-group.js @@ -0,0 +1,248 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +'use strict'; +require('../common'); +const { + hijackStdout, + hijackStderr, + restoreStdout, + restoreStderr +} = require('../common/hijackstdio'); + +const assert = require('assert'); +const Console = require('console').Console; + +let c, stdout, stderr; + +function setup(groupIndentation) { + stdout = ''; + hijackStdout(function(data) { + stdout += data; + }); + + stderr = ''; + hijackStderr(function(data) { + stderr += data; + }); + + c = new Console({ stdout: process.stdout, + stderr: process.stderr, + colorMode: false, + groupIndentation: groupIndentation }); +} + +function teardown() { + restoreStdout(); + restoreStderr(); +} + +// Basic group() functionality +{ + setup(); + const expectedOut = 'This is the outer level\n' + + ' Level 2\n' + + ' Level 3\n' + + ' Back to level 2\n' + + 'Back to the outer level\n' + + 'Still at the outer level\n'; + + + const expectedErr = ' More of level 3\n'; + + c.log('This is the outer level'); + c.group(); + c.log('Level 2'); + c.group(); + c.log('Level 3'); + c.warn('More of level 3'); + c.groupEnd(); + c.log('Back to level 2'); + c.groupEnd(); + c.log('Back to the outer level'); + c.groupEnd(); + c.log('Still at the outer level'); + + assert.strictEqual(stdout, expectedOut); + assert.strictEqual(stderr, expectedErr); + teardown(); +} + +// Group indentation is tracked per Console instance. +{ + setup(); + const expectedOut = 'No indentation\n' + + 'None here either\n' + + ' Now the first console is indenting\n' + + 'But the second one does not\n'; + const expectedErr = ''; + + const c2 = new Console(process.stdout, process.stderr); + c.log('No indentation'); + c2.log('None here either'); + c.group(); + c.log('Now the first console is indenting'); + c2.log('But the second one does not'); + + assert.strictEqual(stdout, expectedOut); + assert.strictEqual(stderr, expectedErr); + teardown(); +} + +// Make sure labels work. +{ + setup(); + const expectedOut = 'This is a label\n' + + ' And this is the data for that label\n'; + const expectedErr = ''; + + c.group('This is a label'); + c.log('And this is the data for that label'); + + assert.strictEqual(stdout, expectedOut); + assert.strictEqual(stderr, expectedErr); + teardown(); +} + +// Check that console.groupCollapsed() is an alias of console.group() +{ + setup(); + const expectedOut = 'Label\n' + + ' Level 2\n' + + ' Level 3\n'; + const expectedErr = ''; + + c.groupCollapsed('Label'); + c.log('Level 2'); + c.groupCollapsed(); + c.log('Level 3'); + + assert.strictEqual(stdout, expectedOut); + assert.strictEqual(stderr, expectedErr); + teardown(); +} + +// Check that multiline strings and object output are indented properly. +{ + setup(); + const expectedOut = 'not indented\n' + + ' indented\n' + + ' also indented\n' + + ' {\n' + + " also: 'a',\n" + + " multiline: 'object',\n" + + " should: 'be',\n" + + " indented: 'properly',\n" + + " kthx: 'bai'\n" + + ' }\n'; + const expectedErr = ''; + + c.log('not indented'); + c.group(); + c.log('indented\nalso indented'); + c.log({ also: 'a', + multiline: 'object', + should: 'be', + indented: 'properly', + kthx: 'bai' }); + + assert.strictEqual(stdout, expectedOut); + assert.strictEqual(stderr, expectedErr); + teardown(); +} + +// Check that the kGroupIndent symbol property is not enumerable +{ + const keys = Reflect.ownKeys(console) + .filter((val) => Object.prototype.propertyIsEnumerable.call(console, val)) + .map((val) => val.toString()); + assert(!keys.includes('Symbol(groupIndent)'), + 'groupIndent should not be enumerable'); +} + +// Check custom groupIndentation. +{ + setup(3); + const expectedOut = 'Set the groupIndentation parameter to 3\n' + + 'This is the outer level\n' + + ' Level 2\n' + + ' Level 3\n' + + ' Back to level 2\n' + + 'Back to the outer level\n' + + 'Still at the outer level\n'; + + + const expectedErr = ' More of level 3\n'; + + c.log('Set the groupIndentation parameter to 3'); + c.log('This is the outer level'); + c.group(); + c.log('Level 2'); + c.group(); + c.log('Level 3'); + c.warn('More of level 3'); + c.groupEnd(); + c.log('Back to level 2'); + c.groupEnd(); + c.log('Back to the outer level'); + c.groupEnd(); + c.log('Still at the outer level'); + + assert.strictEqual(stdout, expectedOut); + assert.strictEqual(stderr, expectedErr); + teardown(); +} + +// Check the correctness of the groupIndentation parameter. +{ + // TypeError + [null, 'str', [], false, true, {}].forEach((e) => { + assert.throws( + () => { + new Console({ stdout: process.stdout, + stderr: process.stderr, + groupIndentation: e }); + }, + { + code: 'ERR_INVALID_ARG_TYPE', + name: 'TypeError' + } + ); + }); + + // RangeError for integer + [NaN, 1.01].forEach((e) => { + assert.throws( + () => { + new Console({ stdout: process.stdout, + stderr: process.stderr, + groupIndentation: e }); + }, + { + code: 'ERR_OUT_OF_RANGE', + name: 'RangeError', + message: /an integer/, + } + ); + }); + + // RangeError + [-1, 1001].forEach((e) => { + assert.throws( + () => { + new Console({ stdout: process.stdout, + stderr: process.stderr, + groupIndentation: e }); + }, + { + code: 'ERR_OUT_OF_RANGE', + name: 'RangeError', + message: />= 0 && <= 1000/, + } + ); + }); +} diff --git a/cli/tests/node_compat/test/parallel/test-console-instance.js b/cli/tests/node_compat/test/parallel/test-console-instance.js new file mode 100644 index 00000000000000..ee561564fb8bca --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-console-instance.js @@ -0,0 +1,156 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 16.13.0 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. + +'use strict'; +const common = require('../common'); +const assert = require('assert'); +const Stream = require('stream'); +const requiredConsole = require('console'); +const Console = requiredConsole.Console; + +const out = new Stream(); +const err = new Stream(); + +// Ensure the Console instance doesn't write to the +// process' "stdout" or "stderr" streams. +process.stdout.write = process.stderr.write = common.mustNotCall(); + +// Make sure that the "Console" function exists. +assert.strictEqual(typeof Console, 'function'); + +// Note: We don't replace global console with node.js console +// assert.strictEqual(requiredConsole, global.console); +// Make sure the custom instanceof of Console works +assert.ok(global.console instanceof Console); +assert.ok(!({} instanceof Console)); + +// Make sure that the Console constructor throws +// when not given a writable stream instance. +assert.throws( + () => { new Console(); }, + { + code: 'ERR_CONSOLE_WRITABLE_STREAM', + name: 'TypeError', + message: /stdout/ + } +); + +// Console constructor should throw if stderr exists but is not writable. +assert.throws( + () => { + out.write = () => {}; + err.write = undefined; + new Console(out, err); + }, + { + code: 'ERR_CONSOLE_WRITABLE_STREAM', + name: 'TypeError', + message: /stderr/ + } +); + +out.write = err.write = (d) => {}; + +{ + const c = new Console(out, err); + assert.ok(c instanceof Console); + + out.write = err.write = common.mustCall((d) => { + assert.strictEqual(d, 'test\n'); + }, 2); + + c.log('test'); + c.error('test'); + + out.write = common.mustCall((d) => { + assert.strictEqual(d, '{ foo: 1 }\n'); + }); + + c.dir({ foo: 1 }); + + // Ensure that the console functions are bound to the console instance. + let called = 0; + out.write = common.mustCall((d) => { + called++; + assert.strictEqual(d, `${called} ${called - 1} [ 1, 2, 3 ]\n`); + }, 3); + + [1, 2, 3].forEach(c.log); +} + +// Test calling Console without the `new` keyword. +{ + const withoutNew = Console(out, err); + assert.ok(withoutNew instanceof Console); +} + +// Test extending Console +{ + class MyConsole extends Console { + hello() {} + // See if the methods on Console.prototype are overridable. + log() { return 'overridden'; } + } + const myConsole = new MyConsole(process.stdout); + assert.strictEqual(typeof myConsole.hello, 'function'); + assert.ok(myConsole instanceof Console); + assert.strictEqual(myConsole.log(), 'overridden'); + + const log = myConsole.log; + assert.strictEqual(log(), 'overridden'); +} + +// Instance that does not ignore the stream errors. +{ + const c2 = new Console(out, err, false); + + out.write = () => { throw new Error('out'); }; + err.write = () => { throw new Error('err'); }; + + assert.throws(() => c2.log('foo'), /^Error: out$/); + assert.throws(() => c2.warn('foo'), /^Error: err$/); + assert.throws(() => c2.dir('foo'), /^Error: out$/); +} + +// Console constructor throws if inspectOptions is not an object. +[null, true, false, 'foo', 5, Symbol()].forEach((inspectOptions) => { + assert.throws( + () => { + new Console({ + stdout: out, + stderr: err, + inspectOptions + }); + }, + { + message: 'The "options.inspectOptions" property must be of type object.' + + common.invalidArgTypeHelper(inspectOptions), + code: 'ERR_INVALID_ARG_TYPE' + } + ); +}); diff --git a/cli/tests/node_compat/test/parallel/test-console-log-stdio-broken-dest.js b/cli/tests/node_compat/test/parallel/test-console-log-stdio-broken-dest.js new file mode 100644 index 00000000000000..afe6f815f0a7b8 --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-console-log-stdio-broken-dest.js @@ -0,0 +1,31 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +'use strict'; + +const common = require('../common'); +const { Writable } = require('stream'); +const { Console } = require('console'); +const { EventEmitter } = require('events'); + +const stream = new Writable({ + write(chunk, enc, cb) { + cb(); + }, + writev(chunks, cb) { + setTimeout(cb, 10, new Error('kaboom')); + } +}); +const myConsole = new Console(stream, stream); + +process.on('warning', common.mustNotCall()); + +stream.cork(); +for (let i = 0; i < EventEmitter.defaultMaxListeners + 1; i++) { + myConsole.log('a message'); +} +stream.uncork(); diff --git a/cli/tests/node_compat/test/parallel/test-console-log-throw-primitive.js b/cli/tests/node_compat/test/parallel/test-console-log-throw-primitive.js new file mode 100644 index 00000000000000..9be7f963f3d506 --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-console-log-throw-primitive.js @@ -0,0 +1,21 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +'use strict'; +require('../common'); +const { Writable } = require('stream'); +const { Console } = require('console'); + +const stream = new Writable({ + write() { + throw null; // eslint-disable-line no-throw-literal + } +}); + +const console = new Console({ stdout: stream }); + +console.log('test'); // Should not throw diff --git a/cli/tests/node_compat/test/parallel/test-console-no-swallow-stack-overflow.js b/cli/tests/node_compat/test/parallel/test-console-no-swallow-stack-overflow.js new file mode 100644 index 00000000000000..81235e3c952981 --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-console-no-swallow-stack-overflow.js @@ -0,0 +1,26 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +'use strict'; +const common = require('../common'); +const assert = require('assert'); +const { Console } = require('console'); +const { Writable } = require('stream'); + +for (const method of ['dir', 'log', 'warn']) { + assert.throws(() => { + const out = new Writable({ + write: common.mustCall(function write(...args) { + // Exceeds call stack. + return write(...args); + }), + }); + const c = new Console(out, out, true); + + c[method]('Hello, world!'); + }, { name: 'RangeError' }); +} diff --git a/cli/tests/node_compat/test/parallel/test-console-sync-write-error.js b/cli/tests/node_compat/test/parallel/test-console-sync-write-error.js new file mode 100644 index 00000000000000..0eb34e6acf3d9e --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-console-sync-write-error.js @@ -0,0 +1,46 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +'use strict'; +const common = require('../common'); +const { Console } = require('console'); +const { Writable } = require('stream'); + +for (const method of ['dir', 'log', 'warn']) { + { + const out = new Writable({ + write: common.mustCall((chunk, enc, callback) => { + callback(new Error('foobar')); + }) + }); + + const c = new Console(out, out, true); + c[method]('abc'); // Should not throw. + } + + { + const out = new Writable({ + write: common.mustCall((chunk, enc, callback) => { + throw new Error('foobar'); + }) + }); + + const c = new Console(out, out, true); + c[method]('abc'); // Should not throw. + } + + { + const out = new Writable({ + write: common.mustCall((chunk, enc, callback) => { + setImmediate(() => callback(new Error('foobar'))); + }) + }); + + const c = new Console(out, out, true); + c[method]('abc'); // Should not throw. + } +} diff --git a/cli/tests/node_compat/test/parallel/test-console-table.js b/cli/tests/node_compat/test/parallel/test-console-table.js new file mode 100644 index 00000000000000..3aa34d7c117d14 --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-console-table.js @@ -0,0 +1,300 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +'use strict'; + +require('../common'); + +const assert = require('assert'); +const { Console } = require('console'); + +const queue = []; + +const console = new Console({ write: (x) => { + queue.push(x); +}, removeListener: () => {} }, process.stderr, false); + +function test(data, only, expected) { + if (arguments.length === 2) { + expected = only; + only = undefined; + } + console.table(data, only); + assert.deepStrictEqual( + queue.shift().split('\n'), + expected.trimLeft().split('\n') + ); +} + +assert.throws(() => console.table([], false), { + code: 'ERR_INVALID_ARG_TYPE', +}); + +test(null, 'null\n'); +test(undefined, 'undefined\n'); +test(false, 'false\n'); +test('hi', 'hi\n'); +test(Symbol(), 'Symbol()\n'); +test(function() {}, '[Function (anonymous)]\n'); + +test([1, 2, 3], ` +┌─────────┬────────┐ +│ (index) │ Values │ +├─────────┼────────┤ +│ 0 │ 1 │ +│ 1 │ 2 │ +│ 2 │ 3 │ +└─────────┴────────┘ +`); + +test([Symbol(), 5, [10]], ` +┌─────────┬────┬──────────┐ +│ (index) │ 0 │ Values │ +├─────────┼────┼──────────┤ +│ 0 │ │ Symbol() │ +│ 1 │ │ 5 │ +│ 2 │ 10 │ │ +└─────────┴────┴──────────┘ +`); + +test([null, 5], ` +┌─────────┬────────┐ +│ (index) │ Values │ +├─────────┼────────┤ +│ 0 │ null │ +│ 1 │ 5 │ +└─────────┴────────┘ +`); + +test([undefined, 5], ` +┌─────────┬───────────┐ +│ (index) │ Values │ +├─────────┼───────────┤ +│ 0 │ undefined │ +│ 1 │ 5 │ +└─────────┴───────────┘ +`); + +test({ a: 1, b: Symbol(), c: [10] }, ` +┌─────────┬────┬──────────┐ +│ (index) │ 0 │ Values │ +├─────────┼────┼──────────┤ +│ a │ │ 1 │ +│ b │ │ Symbol() │ +│ c │ 10 │ │ +└─────────┴────┴──────────┘ +`); + +test(new Map([ ['a', 1], [Symbol(), [2]] ]), ` +┌───────────────────┬──────────┬────────┐ +│ (iteration index) │ Key │ Values │ +├───────────────────┼──────────┼────────┤ +│ 0 │ 'a' │ 1 │ +│ 1 │ Symbol() │ [ 2 ] │ +└───────────────────┴──────────┴────────┘ +`); + +test(new Set([1, 2, Symbol()]), ` +┌───────────────────┬──────────┐ +│ (iteration index) │ Values │ +├───────────────────┼──────────┤ +│ 0 │ 1 │ +│ 1 │ 2 │ +│ 2 │ Symbol() │ +└───────────────────┴──────────┘ +`); + +test({ a: 1, b: 2 }, ['a'], ` +┌─────────┬───┐ +│ (index) │ a │ +├─────────┼───┤ +│ a │ │ +│ b │ │ +└─────────┴───┘ +`); + +test([{ a: 1, b: 2 }, { a: 3, c: 4 }], ['a'], ` +┌─────────┬───┐ +│ (index) │ a │ +├─────────┼───┤ +│ 0 │ 1 │ +│ 1 │ 3 │ +└─────────┴───┘ +`); + +test(new Map([[1, 1], [2, 2], [3, 3]]).entries(), ` +┌───────────────────┬─────┬────────┐ +│ (iteration index) │ Key │ Values │ +├───────────────────┼─────┼────────┤ +│ 0 │ 1 │ 1 │ +│ 1 │ 2 │ 2 │ +│ 2 │ 3 │ 3 │ +└───────────────────┴─────┴────────┘ +`); + +test(new Map([[1, 1], [2, 2], [3, 3]]).values(), ` +┌───────────────────┬────────┐ +│ (iteration index) │ Values │ +├───────────────────┼────────┤ +│ 0 │ 1 │ +│ 1 │ 2 │ +│ 2 │ 3 │ +└───────────────────┴────────┘ +`); + +test(new Map([[1, 1], [2, 2], [3, 3]]).keys(), ` +┌───────────────────┬────────┐ +│ (iteration index) │ Values │ +├───────────────────┼────────┤ +│ 0 │ 1 │ +│ 1 │ 2 │ +│ 2 │ 3 │ +└───────────────────┴────────┘ +`); + +test(new Set([1, 2, 3]).values(), ` +┌───────────────────┬────────┐ +│ (iteration index) │ Values │ +├───────────────────┼────────┤ +│ 0 │ 1 │ +│ 1 │ 2 │ +│ 2 │ 3 │ +└───────────────────┴────────┘ +`); + + +test({ a: { a: 1, b: 2, c: 3 } }, ` +┌─────────┬───┬───┬───┐ +│ (index) │ a │ b │ c │ +├─────────┼───┼───┼───┤ +│ a │ 1 │ 2 │ 3 │ +└─────────┴───┴───┴───┘ +`); + +test({ a: { a: { a: 1, b: 2, c: 3 } } }, ` +┌─────────┬──────────┐ +│ (index) │ a │ +├─────────┼──────────┤ +│ a │ [Object] │ +└─────────┴──────────┘ +`); + +test({ a: [1, 2] }, ` +┌─────────┬───┬───┐ +│ (index) │ 0 │ 1 │ +├─────────┼───┼───┤ +│ a │ 1 │ 2 │ +└─────────┴───┴───┘ +`); + +test({ a: [1, 2, 3, 4, 5], b: 5, c: { e: 5 } }, ` +┌─────────┬───┬───┬───┬───┬───┬───┬────────┐ +│ (index) │ 0 │ 1 │ 2 │ 3 │ 4 │ e │ Values │ +├─────────┼───┼───┼───┼───┼───┼───┼────────┤ +│ a │ 1 │ 2 │ 3 │ 4 │ 5 │ │ │ +│ b │ │ │ │ │ │ │ 5 │ +│ c │ │ │ │ │ │ 5 │ │ +└─────────┴───┴───┴───┴───┴───┴───┴────────┘ +`); + +test(new Uint8Array([1, 2, 3]), ` +┌─────────┬────────┐ +│ (index) │ Values │ +├─────────┼────────┤ +│ 0 │ 1 │ +│ 1 │ 2 │ +│ 2 │ 3 │ +└─────────┴────────┘ +`); + +test(Buffer.from([1, 2, 3]), ` +┌─────────┬────────┐ +│ (index) │ Values │ +├─────────┼────────┤ +│ 0 │ 1 │ +│ 1 │ 2 │ +│ 2 │ 3 │ +└─────────┴────────┘ +`); + +test({ a: undefined }, ['x'], ` +┌─────────┬───┐ +│ (index) │ x │ +├─────────┼───┤ +│ a │ │ +└─────────┴───┘ +`); + +test([], ` +┌─────────┐ +│ (index) │ +├─────────┤ +└─────────┘ +`); + +test(new Map(), ` +┌───────────────────┬─────┬────────┐ +│ (iteration index) │ Key │ Values │ +├───────────────────┼─────┼────────┤ +└───────────────────┴─────┴────────┘ +`); + +test([{ a: 1, b: 'Y' }, { a: 'Z', b: 2 }], ` +┌─────────┬─────┬─────┐ +│ (index) │ a │ b │ +├─────────┼─────┼─────┤ +│ 0 │ 1 │ 'Y' │ +│ 1 │ 'Z' │ 2 │ +└─────────┴─────┴─────┘ +`); + +{ + const line = '─'.repeat(79); + const header = `${' '.repeat(37)}name${' '.repeat(40)}`; + const name = 'very long long long long long long long long long long long ' + + 'long long long long'; + test([{ name }], ` +┌─────────┬──${line}──┐ +│ (index) │ ${header}│ +├─────────┼──${line}──┤ +│ 0 │ '${name}' │ +└─────────┴──${line}──┘ +`); +} + +test({ foo: '¥', bar: '¥' }, ` +┌─────────┬────────┐ +│ (index) │ Values │ +├─────────┼────────┤ +│ foo │ '¥' │ +│ bar │ '¥' │ +└─────────┴────────┘ +`); + +test({ foo: '你好', bar: 'hello' }, ` +┌─────────┬─────────┐ +│ (index) │ Values │ +├─────────┼─────────┤ +│ foo │ '你好' │ +│ bar │ 'hello' │ +└─────────┴─────────┘ +`); + +// Regression test for prototype pollution via console.table. Earlier versions +// of Node.js created an object with a non-null prototype within console.table +// and then wrote to object[column][index], which lead to an error as well as +// modifications to Object.prototype. +test([{ foo: 10 }, { foo: 20 }], ['__proto__'], ` +┌─────────┬───────────┐ +│ (index) │ __proto__ │ +├─────────┼───────────┤ +│ 0 │ │ +│ 1 │ │ +└─────────┴───────────┘ +`); +assert.strictEqual('0' in Object.prototype, false); +assert.strictEqual('1' in Object.prototype, false); diff --git a/cli/tests/node_compat/test/parallel/test-console-tty-colors.js b/cli/tests/node_compat/test/parallel/test-console-tty-colors.js new file mode 100644 index 00000000000000..9676529baa50f7 --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-console-tty-colors.js @@ -0,0 +1,102 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +'use strict'; +const common = require('../common'); +const assert = require('assert'); +const util = require('util'); +const { Writable } = require('stream'); +const { Console } = require('console'); + +function check(isTTY, colorMode, expectedColorMode, inspectOptions) { + const items = [ + 1, + { a: 2 }, + [ 'foo' ], + { '\\a': '\\bar' }, + ]; + + let i = 0; + const stream = new Writable({ + write: common.mustCall((chunk, enc, cb) => { + assert.strictEqual(chunk.trim(), + util.inspect(items[i++], { + colors: expectedColorMode, + ...inspectOptions + })); + cb(); + }, items.length), + decodeStrings: false + }); + stream.isTTY = isTTY; + + // Set ignoreErrors to `false` here so that we see assertion failures + // from the `write()` call happen. + const testConsole = new Console({ + stdout: stream, + ignoreErrors: false, + colorMode, + inspectOptions + }); + for (const item of items) { + testConsole.log(item); + } +} + +check(true, 'auto', true); +check(false, 'auto', false); +check(false, undefined, true, { colors: true, compact: false }); +check(true, 'auto', true, { compact: false }); +check(true, undefined, false, { colors: false }); +check(true, true, true); +check(false, true, true); +check(true, false, false); +check(false, false, false); + +// Check invalid options. +{ + const stream = new Writable({ + write: common.mustNotCall() + }); + + [0, 'true', null, {}, [], () => {}].forEach((colorMode) => { + const received = util.inspect(colorMode); + assert.throws( + () => { + new Console({ + stdout: stream, + ignoreErrors: false, + colorMode: colorMode + }); + }, + { + message: `The argument 'colorMode' is invalid. Received ${received}`, + code: 'ERR_INVALID_ARG_VALUE' + } + ); + }); + + [true, false, 'auto'].forEach((colorMode) => { + assert.throws( + () => { + new Console({ + stdout: stream, + ignoreErrors: false, + colorMode: colorMode, + inspectOptions: { + colors: false + } + }); + }, + { + message: 'Option "options.inspectOptions.color" cannot be used in ' + + 'combination with option "colorMode"', + code: 'ERR_INCOMPATIBLE_OPTION_PAIR' + } + ); + }); +} diff --git a/cli/tests/node_compat/test/parallel/test-crypto-hmac.js b/cli/tests/node_compat/test/parallel/test-crypto-hmac.js new file mode 100644 index 00000000000000..174457a6322d6c --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-crypto-hmac.js @@ -0,0 +1,483 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.0 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +'use strict'; +const common = require('../common'); +if (!common.hasCrypto) + common.skip('missing crypto'); + +const assert = require('assert'); +const crypto = require('crypto'); + +{ + const Hmac = crypto.Hmac; + const instance = crypto.Hmac('sha256', 'Node'); + assert(instance instanceof Hmac, 'Hmac is expected to return a new instance' + + ' when called without `new`'); +} + +assert.throws( + () => crypto.createHmac(null), + { + code: 'ERR_INVALID_ARG_TYPE', + name: 'TypeError', + message: 'The "hmac" argument must be of type string. Received null' + }); + +// This used to segfault. See: https://github.com/nodejs/node/issues/9819 +assert.throws( + () => crypto.createHmac('sha256', 'key').digest({ + toString: () => { throw new Error('boom'); }, + }), + { + name: 'Error', + message: 'boom' + }); + +assert.throws( + () => crypto.createHmac('sha1', null), + { + code: 'ERR_INVALID_ARG_TYPE', + name: 'TypeError', + }); + +function testHmac(algo, key, data, expected) { + // TODO(kt3k): Skip non-string key for now. + // Enable this when we implement crypto.createSecretKey + if (typeof key !== "string") { + return; + } + // FIPS does not support MD5. + if (common.hasFipsCrypto && algo === 'md5') + return; + + if (!Array.isArray(data)) + data = [data]; + + // If the key is a Buffer, test Hmac with a key object as well. + const keyWrappers = [ + (key) => key, + ...(typeof key === 'string' ? [] : [crypto.createSecretKey]), + ]; + + for (const keyWrapper of keyWrappers) { + const hmac = crypto.createHmac(algo, keyWrapper(key)); + for (const chunk of data) + hmac.update(chunk); + const actual = hmac.digest('hex'); + assert.strictEqual(actual, expected); + } +} + +{ + // Test HMAC with multiple updates. + testHmac('sha1', 'Node', ['some data', 'to hmac'], + '19fd6e1ba73d9ed2224dd5094a71babe85d9a892'); +} + +// Test HMAC (Wikipedia Test Cases) +const wikipedia = [ + { + key: 'key', data: 'The quick brown fox jumps over the lazy dog', + hmac: { // HMACs lifted from Wikipedia. + md5: '80070713463e7749b90c2dc24911e275', + sha1: 'de7c9b85b8b78aa6bc8a7a36f70a90701c9db4d9', + sha256: + 'f7bc83f430538424b13298e6aa6fb143ef4d59a14946175997479dbc' + + '2d1a3cd8' + } + }, + { + key: 'key', data: '', + hmac: { // Intermediate test to help debugging. + md5: '63530468a04e386459855da0063b6596', + sha1: 'f42bb0eeb018ebbd4597ae7213711ec60760843f', + sha256: + '5d5d139563c95b5967b9bd9a8c9b233a9dedb45072794cd232dc1b74' + + '832607d0' + } + }, + { + key: '', data: 'The quick brown fox jumps over the lazy dog', + hmac: { // Intermediate test to help debugging. + md5: 'ad262969c53bc16032f160081c4a07a0', + sha1: '2ba7f707ad5f187c412de3106583c3111d668de8', + sha256: + 'fb011e6154a19b9a4c767373c305275a5a69e8b68b0b4c9200c383dc' + + 'ed19a416' + } + }, + { + key: '', data: '', + hmac: { // HMACs lifted from Wikipedia. + md5: '74e6f7298a9c2d168935f58c001bad88', + sha1: 'fbdb1d1b18aa6c08324b7d64b71fb76370690e1d', + sha256: + 'b613679a0814d9ec772f95d778c35fc5ff1697c493715653c6c71214' + + '4292c5ad' + } + }, +]; + +for (const { key, data, hmac } of wikipedia) { + for (const hash in hmac) + testHmac(hash, key, data, hmac[hash]); +} + +// Test HMAC-SHA-* (rfc 4231 Test Cases) +const rfc4231 = [ + { + key: Buffer.from('0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b', 'hex'), + data: Buffer.from('4869205468657265', 'hex'), // 'Hi There' + hmac: { + sha224: '896fb1128abbdf196832107cd49df33f47b4b1169912ba4f53684b22', + sha256: + 'b0344c61d8db38535ca8afceaf0bf12b881dc200c9833da726e9376c' + + '2e32cff7', + sha384: + 'afd03944d84895626b0825f4ab46907f15f9dadbe4101ec682aa034c' + + '7cebc59cfaea9ea9076ede7f4af152e8b2fa9cb6', + sha512: + '87aa7cdea5ef619d4ff0b4241a1d6cb02379f4e2ce4ec2787ad0b305' + + '45e17cdedaa833b7d6b8a702038b274eaea3f4e4be9d914eeb61f170' + + '2e696c203a126854' + } + }, + { + key: Buffer.from('4a656665', 'hex'), // 'Jefe' + data: Buffer.from('7768617420646f2079612077616e7420666f72206e6f74686' + + '96e673f', 'hex'), // 'what do ya want for nothing?' + hmac: { + sha224: 'a30e01098bc6dbbf45690f3a7e9e6d0f8bbea2a39e6148008fd05e44', + sha256: + '5bdcc146bf60754e6a042426089575c75a003f089d2739839dec58b9' + + '64ec3843', + sha384: + 'af45d2e376484031617f78d2b58a6b1b9c7ef464f5a01b47e42ec373' + + '6322445e8e2240ca5e69e2c78b3239ecfab21649', + sha512: + '164b7a7bfcf819e2e395fbe73b56e0a387bd64222e831fd610270cd7' + + 'ea2505549758bf75c05a994a6d034f65f8f0e6fdcaeab1a34d4a6b4b' + + '636e070a38bce737' + } + }, + { + key: Buffer.from('aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa', 'hex'), + data: Buffer.from('ddddddddddddddddddddddddddddddddddddddddddddddddd' + + 'ddddddddddddddddddddddddddddddddddddddddddddddddddd', + 'hex'), + hmac: { + sha224: '7fb3cb3588c6c1f6ffa9694d7d6ad2649365b0c1f65d69d1ec8333ea', + sha256: + '773ea91e36800e46854db8ebd09181a72959098b3ef8c122d9635514' + + 'ced565fe', + sha384: + '88062608d3e6ad8a0aa2ace014c8a86f0aa635d947ac9febe83ef4e5' + + '5966144b2a5ab39dc13814b94e3ab6e101a34f27', + sha512: + 'fa73b0089d56a284efb0f0756c890be9b1b5dbdd8ee81a3655f83e33' + + 'b2279d39bf3e848279a722c806b485a47e67c807b946a337bee89426' + + '74278859e13292fb' + } + }, + { + key: Buffer.from('0102030405060708090a0b0c0d0e0f10111213141516171819', + 'hex'), + data: Buffer.from('cdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdc' + + 'dcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcd', + 'hex'), + hmac: { + sha224: '6c11506874013cac6a2abc1bb382627cec6a90d86efc012de7afec5a', + sha256: + '82558a389a443c0ea4cc819899f2083a85f0faa3e578f8077a2e3ff4' + + '6729665b', + sha384: + '3e8a69b7783c25851933ab6290af6ca77a9981480850009cc5577c6e' + + '1f573b4e6801dd23c4a7d679ccf8a386c674cffb', + sha512: + 'b0ba465637458c6990e5a8c5f61d4af7e576d97ff94b872de76f8050' + + '361ee3dba91ca5c11aa25eb4d679275cc5788063a5f19741120c4f2d' + + 'e2adebeb10a298dd' + } + }, + + { + key: Buffer.from('0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c', 'hex'), + // 'Test With Truncation' + data: Buffer.from('546573742057697468205472756e636174696f6e', 'hex'), + hmac: { + sha224: '0e2aea68a90c8d37c988bcdb9fca6fa8', + sha256: 'a3b6167473100ee06e0c796c2955552b', + sha384: '3abf34c3503b2a23a46efc619baef897', + sha512: '415fad6271580a531d4179bc891d87a6' + }, + truncate: true + }, + { + key: Buffer.from('aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa' + + 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa' + + 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa' + + 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa' + + 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa' + + 'aaaaaaaaaaaa', 'hex'), + // 'Test Using Larger Than Block-Size Key - Hash Key First' + data: Buffer.from('54657374205573696e67204c6172676572205468616e20426' + + 'c6f636b2d53697a65204b6579202d2048617368204b657920' + + '4669727374', 'hex'), + hmac: { + sha224: '95e9a0db962095adaebe9b2d6f0dbce2d499f112f2d2b7273fa6870e', + sha256: + '60e431591ee0b67f0d8a26aacbf5b77f8e0bc6213728c5140546040f' + + '0ee37f54', + sha384: + '4ece084485813e9088d2c63a041bc5b44f9ef1012a2b588f3cd11f05' + + '033ac4c60c2ef6ab4030fe8296248df163f44952', + sha512: + '80b24263c7c1a3ebb71493c1dd7be8b49b46d1f41b4aeec1121b0137' + + '83f8f3526b56d037e05f2598bd0fd2215d6a1e5295e64f73f63f0aec' + + '8b915a985d786598' + } + }, + { + key: Buffer.from('aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa' + + 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa' + + 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa' + + 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa' + + 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa' + + 'aaaaaaaaaaaa', 'hex'), + // 'This is a test using a larger than block-size key and a larger ' + + // 'than block-size data. The key needs to be hashed before being ' + + // 'used by the HMAC algorithm.' + data: Buffer.from('5468697320697320612074657374207573696e672061206c6' + + '172676572207468616e20626c6f636b2d73697a65206b6579' + + '20616e642061206c6172676572207468616e20626c6f636b2' + + 'd73697a6520646174612e20546865206b6579206e65656473' + + '20746f20626520686173686564206265666f7265206265696' + + 'e6720757365642062792074686520484d414320616c676f72' + + '6974686d2e', 'hex'), + hmac: { + sha224: '3a854166ac5d9f023f54d517d0b39dbd946770db9c2b95c9f6f565d1', + sha256: + '9b09ffa71b942fcb27635fbcd5b0e944bfdc63644f0713938a7f5153' + + '5c3a35e2', + sha384: + '6617178e941f020d351e2f254e8fd32c602420feb0b8fb9adccebb82' + + '461e99c5a678cc31e799176d3860e6110c46523e', + sha512: + 'e37b6a775dc87dbaa4dfa9f96e5e3ffddebd71f8867289865df5a32d' + + '20cdc944b6022cac3c4982b10d5eeb55c3e4de15134676fb6de04460' + + '65c97440fa8c6a58' + } + }, +]; + +for (let i = 0, l = rfc4231.length; i < l; i++) { + for (const hash in rfc4231[i].hmac) { + const str = crypto.createHmac(hash, rfc4231[i].key); + str.end(rfc4231[i].data); + let strRes = str.read().toString('hex'); + let actual = crypto.createHmac(hash, rfc4231[i].key) + .update(rfc4231[i].data) + .digest('hex'); + if (rfc4231[i].truncate) { + actual = actual.slice(0, 32); // first 128 bits == 32 hex chars + strRes = strRes.slice(0, 32); + } + const expected = rfc4231[i].hmac[hash]; + assert.strictEqual( + actual, + expected, + `Test HMAC-${hash} rfc 4231 case ${i + 1}: ${actual} must be ${expected}` + ); + assert.strictEqual( + actual, + strRes, + `Should get same result from stream (hash: ${hash} and case: ${i + 1})` + + ` => ${actual} must be ${strRes}` + ); + } +} + +// Test HMAC-MD5/SHA1 (rfc 2202 Test Cases) +const rfc2202_md5 = [ + { + key: Buffer.from('0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b', 'hex'), + data: 'Hi There', + hmac: '9294727a3638bb1c13f48ef8158bfc9d' + }, + { + key: 'Jefe', + data: 'what do ya want for nothing?', + hmac: '750c783e6ab0b503eaa86e310a5db738' + }, + { + key: Buffer.from('aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa', 'hex'), + data: Buffer.from('ddddddddddddddddddddddddddddddddddddddddddddddddd' + + 'ddddddddddddddddddddddddddddddddddddddddddddddddddd', + 'hex'), + hmac: '56be34521d144c88dbb8c733f0e8b3f6' + }, + { + key: Buffer.from('0102030405060708090a0b0c0d0e0f10111213141516171819', + 'hex'), + data: Buffer.from('cdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdc' + + 'dcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcd' + + 'cdcdcdcdcd', + 'hex'), + hmac: '697eaf0aca3a3aea3a75164746ffaa79' + }, + { + key: Buffer.from('0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c', 'hex'), + data: 'Test With Truncation', + hmac: '56461ef2342edc00f9bab995690efd4c' + }, + { + key: Buffer.from('aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa' + + 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa' + + 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa' + + 'aaaaaaaaaaaaaaaaaaaaaa', + 'hex'), + data: 'Test Using Larger Than Block-Size Key - Hash Key First', + hmac: '6b1ab7fe4bd7bf8f0b62e6ce61b9d0cd' + }, + { + key: Buffer.from('aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa' + + 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa' + + 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa' + + 'aaaaaaaaaaaaaaaaaaaaaa', + 'hex'), + data: + 'Test Using Larger Than Block-Size Key and Larger Than One ' + + 'Block-Size Data', + hmac: '6f630fad67cda0ee1fb1f562db3aa53e' + }, +]; + +for (const { key, data, hmac } of rfc2202_md5) + testHmac('md5', key, data, hmac); + +const rfc2202_sha1 = [ + { + key: Buffer.from('0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b', 'hex'), + data: 'Hi There', + hmac: 'b617318655057264e28bc0b6fb378c8ef146be00' + }, + { + key: 'Jefe', + data: 'what do ya want for nothing?', + hmac: 'effcdf6ae5eb2fa2d27416d5f184df9c259a7c79' + }, + { + key: Buffer.from('aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa', 'hex'), + data: Buffer.from('ddddddddddddddddddddddddddddddddddddddddddddd' + + 'ddddddddddddddddddddddddddddddddddddddddddddd' + + 'dddddddddd', + 'hex'), + hmac: '125d7342b9ac11cd91a39af48aa17b4f63f175d3' + }, + { + key: Buffer.from('0102030405060708090a0b0c0d0e0f10111213141516171819', + 'hex'), + data: Buffer.from('cdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdc' + + 'dcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcd' + + 'cdcdcdcdcd', + 'hex'), + hmac: '4c9007f4026250c6bc8414f9bf50c86c2d7235da' + }, + { + key: Buffer.from('0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c', 'hex'), + data: 'Test With Truncation', + hmac: '4c1a03424b55e07fe7f27be1d58bb9324a9a5a04' + }, + { + key: Buffer.from('aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa' + + 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa' + + 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa' + + 'aaaaaaaaaaaaaaaaaaaaaa', + 'hex'), + data: 'Test Using Larger Than Block-Size Key - Hash Key First', + hmac: 'aa4ae5e15272d00e95705637ce8a3b55ed402112' + }, + { + key: Buffer.from('aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa' + + 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa' + + 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa' + + 'aaaaaaaaaaaaaaaaaaaaaa', + 'hex'), + data: + 'Test Using Larger Than Block-Size Key and Larger Than One ' + + 'Block-Size Data', + hmac: 'e8e99d0f45237d786d6bbaa7965c7808bbff1a91' + }, +]; + +for (const { key, data, hmac } of rfc2202_sha1) + testHmac('sha1', key, data, hmac); + +assert.strictEqual( + crypto.createHmac('sha256', 'w00t').digest('ucs2'), + crypto.createHmac('sha256', 'w00t').digest().toString('ucs2')); + +// Check initialized -> uninitialized state transition after calling digest(). +{ + const expected = + '\u0010\u0041\u0052\u00c5\u00bf\u00dc\u00a0\u007b\u00c6\u0033' + + '\u00ee\u00bd\u0046\u0019\u009f\u0002\u0055\u00c9\u00f4\u009d'; + { + const h = crypto.createHmac('sha1', 'key').update('data'); + assert.deepStrictEqual(h.digest('buffer'), Buffer.from(expected, 'latin1')); + // TODO(kt3k): Enable this assertion + // assert.deepStrictEqual(h.digest('buffer'), Buffer.from('')); + } + { + const h = crypto.createHmac('sha1', 'key').update('data'); + assert.strictEqual(h.digest('latin1'), expected); + // TODO(kt3k): Enable this assertion + // assert.strictEqual(h.digest('latin1'), ''); + } +} + +// Check initialized -> uninitialized state transition after calling digest(). +// Calls to update() omitted intentionally. +{ + const expected = + '\u00f4\u002b\u00b0\u00ee\u00b0\u0018\u00eb\u00bd\u0045\u0097' + + '\u00ae\u0072\u0013\u0071\u001e\u00c6\u0007\u0060\u0084\u003f'; + { + const h = crypto.createHmac('sha1', 'key'); + assert.deepStrictEqual(h.digest('buffer'), Buffer.from(expected, 'latin1')); + // TODO(kt3k): Enable this assertion + // assert.deepStrictEqual(h.digest('buffer'), Buffer.from('')); + } + { + const h = crypto.createHmac('sha1', 'key'); + assert.strictEqual(h.digest('latin1'), expected); + // TODO(kt3k): Enable this assertion + // assert.strictEqual(h.digest('latin1'), ''); + } +} + +/* +TODO(kt3k): Enable this test. +{ + assert.throws( + () => crypto.createHmac('sha7', 'key'), + /Invalid digest/); +} +*/ + +/* + TODO(kt3k): enable this case when we implemented crypto.createSecretKey +{ + const buf = Buffer.alloc(0); + const keyObject = crypto.createSecretKey(Buffer.alloc(0)); + assert.deepStrictEqual( + crypto.createHmac('sha256', buf).update('foo').digest(), + crypto.createHmac('sha256', keyObject).update('foo').digest(), + ); +} +*/ diff --git a/cli/tests/node_compat/test/parallel/test-dgram-address.js b/cli/tests/node_compat/test/parallel/test-dgram-address.js new file mode 100644 index 00000000000000..ffcf8485e8aca1 --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-dgram-address.js @@ -0,0 +1,88 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. + +'use strict'; +const common = require('../common'); +const assert = require('assert'); +const dgram = require('dgram'); + +{ + // IPv4 Test + const socket = dgram.createSocket('udp4'); + + socket.on('listening', common.mustCall(() => { + const address = socket.address(); + + assert.strictEqual(address.address, common.localhostIPv4); + assert.strictEqual(typeof address.port, 'number'); + assert.ok(isFinite(address.port)); + assert.ok(address.port > 0); + assert.strictEqual(address.family, 'IPv4'); + socket.close(); + })); + + socket.on('error', (err) => { + socket.close(); + assert.fail(`Unexpected error on udp4 socket. ${err.toString()}`); + }); + + socket.bind(0, common.localhostIPv4); +} + +if (common.hasIPv6) { + // IPv6 Test + const socket = dgram.createSocket('udp6'); + const localhost = '::1'; + + socket.on('listening', common.mustCall(() => { + const address = socket.address(); + + assert.strictEqual(address.address, localhost); + assert.strictEqual(typeof address.port, 'number'); + assert.ok(isFinite(address.port)); + assert.ok(address.port > 0); + assert.strictEqual(address.family, 'IPv6'); + socket.close(); + })); + + socket.on('error', (err) => { + socket.close(); + assert.fail(`Unexpected error on udp6 socket. ${err.toString()}`); + }); + + socket.bind(0, localhost); +} + +{ + // Verify that address() throws if the socket is not bound. + const socket = dgram.createSocket('udp4'); + + assert.throws(() => { + socket.address(); + }, /^Error: getsockname EBADF$/); +} diff --git a/cli/tests/node_compat/test/parallel/test-dgram-bind-default-address.js b/cli/tests/node_compat/test/parallel/test-dgram-bind-default-address.js new file mode 100644 index 00000000000000..9a6e1412ca2aec --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-dgram-bind-default-address.js @@ -0,0 +1,60 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. + +'use strict'; +const common = require('../common'); +// Skip test in FreeBSD jails since 0.0.0.0 will resolve to default interface +if (common.inFreeBSDJail) + common.skip('In a FreeBSD jail'); + +const assert = require('assert'); +const dgram = require('dgram'); + +dgram.createSocket('udp4').bind(0, common.mustCall(function() { + assert.strictEqual(typeof this.address().port, 'number'); + assert.ok(isFinite(this.address().port)); + assert.ok(this.address().port > 0); + assert.strictEqual(this.address().address, '0.0.0.0'); + this.close(); +})); + +if (!common.hasIPv6) { + common.printSkipMessage('udp6 part of test, because no IPv6 support'); + return; +} + +dgram.createSocket('udp6').bind(0, common.mustCall(function() { + assert.strictEqual(typeof this.address().port, 'number'); + assert.ok(isFinite(this.address().port)); + assert.ok(this.address().port > 0); + let address = this.address().address; + if (address === '::ffff:0.0.0.0') + address = '::'; + assert.strictEqual(address, '::'); + this.close(); +})); diff --git a/cli/tests/node_compat/test/parallel/test-dgram-bind.js b/cli/tests/node_compat/test/parallel/test-dgram-bind.js new file mode 100644 index 00000000000000..93d68fd8a382cb --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-dgram-bind.js @@ -0,0 +1,50 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. + +'use strict'; +const common = require('../common'); +const assert = require('assert'); +const dgram = require('dgram'); + +const socket = dgram.createSocket('udp4'); + +socket.on('listening', common.mustCall(() => { + assert.throws(() => { + socket.bind(); + }, { + code: 'ERR_SOCKET_ALREADY_BOUND', + name: 'Error', + message: /^Socket is already bound$/ + }); + + socket.close(); +})); + +const result = socket.bind(); // Should not throw. + +assert.strictEqual(result, socket); // Should have returned itself. diff --git a/cli/tests/node_compat/test/parallel/test-dgram-bytes-length.js b/cli/tests/node_compat/test/parallel/test-dgram-bytes-length.js new file mode 100644 index 00000000000000..4ce2dabc352efd --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-dgram-bytes-length.js @@ -0,0 +1,46 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. + +'use strict'; +require('../common'); +const assert = require('assert'); +const dgram = require('dgram'); + +const message = Buffer.from('Some bytes'); +const client = dgram.createSocket('udp4'); +client.send( + message, + 0, + message.length, + 41234, + 'localhost', + function(err, bytes) { + assert.strictEqual(bytes, message.length); + client.close(); + } +); diff --git a/cli/tests/node_compat/test/parallel/test-dgram-close-during-bind.js b/cli/tests/node_compat/test/parallel/test-dgram-close-during-bind.js new file mode 100644 index 00000000000000..8e0621884cd1ea --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-dgram-close-during-bind.js @@ -0,0 +1,26 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +// Flags: --expose-internals +'use strict'; +const common = require('../common'); +const dgram = require('dgram'); +const { kStateSymbol } = require('internal/dgram'); +const socket = dgram.createSocket('udp4'); +const { handle } = socket[kStateSymbol]; +const lookup = handle.lookup; + +// Test the scenario where the socket is closed during a bind operation. +handle.bind = common.mustNotCall('bind() should not be called.'); + +handle.lookup = common.mustCall(function(address, callback) { + socket.close(common.mustCall(() => { + lookup.call(this, address, callback); + })); +}); + +socket.bind(common.mustNotCall('Socket should not bind.')); diff --git a/cli/tests/node_compat/test/parallel/test-dgram-close-in-listening.js b/cli/tests/node_compat/test/parallel/test-dgram-close-in-listening.js new file mode 100644 index 00000000000000..673b145b8637e2 --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-dgram-close-in-listening.js @@ -0,0 +1,33 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +'use strict'; +// Ensure that if a dgram socket is closed before the sendQueue is drained +// will not crash + +const common = require('../common'); +const dgram = require('dgram'); + +const buf = Buffer.alloc(1024, 42); + +const socket = dgram.createSocket('udp4'); + +socket.on('listening', function() { + socket.close(); +}); + +// Get a random port for send +const portGetter = dgram.createSocket('udp4') + .bind(0, 'localhost', common.mustCall(() => { + // Adds a listener to 'listening' to send the data when + // the socket is available + socket.send(buf, 0, buf.length, + portGetter.address().port, + portGetter.address().address); + + portGetter.close(); + })); diff --git a/cli/tests/node_compat/test/parallel/test-dgram-close-is-not-callback.js b/cli/tests/node_compat/test/parallel/test-dgram-close-is-not-callback.js new file mode 100644 index 00000000000000..d67bf5aa4d1ec5 --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-dgram-close-is-not-callback.js @@ -0,0 +1,28 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +'use strict'; +const common = require('../common'); +const dgram = require('dgram'); + +const buf = Buffer.alloc(1024, 42); + +const socket = dgram.createSocket('udp4'); + +// Get a random port for send +const portGetter = dgram.createSocket('udp4') + .bind(0, 'localhost', common.mustCall(() => { + socket.send(buf, 0, buf.length, + portGetter.address().port, + portGetter.address().address); + + // If close callback is not function, ignore the argument. + socket.close('bad argument'); + portGetter.close(); + + socket.on('close', common.mustCall()); + })); diff --git a/cli/tests/node_compat/test/parallel/test-dgram-close-signal.js b/cli/tests/node_compat/test/parallel/test-dgram-close-signal.js new file mode 100644 index 00000000000000..97683191bff836 --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-dgram-close-signal.js @@ -0,0 +1,38 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +'use strict'; + +const common = require('../common'); +const assert = require('assert'); +const dgram = require('dgram'); + +{ + // Test bad signal. + assert.throws( + () => dgram.createSocket({ type: 'udp4', signal: {} }), + { + code: 'ERR_INVALID_ARG_TYPE', + name: 'TypeError' + }); +} + +{ + // Test close. + const controller = new AbortController(); + const { signal } = controller; + const server = dgram.createSocket({ type: 'udp4', signal }); + server.on('close', common.mustCall()); + controller.abort(); +} + +{ + // Test close with pre-aborted signal. + const signal = AbortSignal.abort(); + const server = dgram.createSocket({ type: 'udp4', signal }); + server.on('close', common.mustCall()); +} diff --git a/cli/tests/node_compat/test/parallel/test-dgram-close.js b/cli/tests/node_compat/test/parallel/test-dgram-close.js new file mode 100644 index 00000000000000..46dfd80148b180 --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-dgram-close.js @@ -0,0 +1,63 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. + +// Flags: --expose-internals +'use strict'; +// Ensure that if a dgram socket is closed before the DNS lookup completes, it +// won't crash. + +const common = require('../common'); +const assert = require('assert'); +const dgram = require('dgram'); +const { kStateSymbol } = require('internal/dgram'); + +const buf = Buffer.alloc(1024, 42); + +let socket = dgram.createSocket('udp4'); +const { handle } = socket[kStateSymbol]; + +// Get a random port for send +const portGetter = dgram.createSocket('udp4') + .bind(0, 'localhost', common.mustCall(() => { + socket.send(buf, 0, buf.length, + portGetter.address().port, + portGetter.address().address); + + assert.strictEqual(socket.close(common.mustCall()), socket); + socket.on('close', common.mustCall()); + socket = null; + + // Verify that accessing handle after closure doesn't throw + setImmediate(function() { + setImmediate(function() { + console.log('Handle fd is: ', handle.fd); + }); + }); + + portGetter.close(); + })); diff --git a/cli/tests/node_compat/test/parallel/test-dgram-connect-send-callback-buffer-length.js b/cli/tests/node_compat/test/parallel/test-dgram-connect-send-callback-buffer-length.js new file mode 100644 index 00000000000000..87f878754f0d6e --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-dgram-connect-send-callback-buffer-length.js @@ -0,0 +1,30 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +'use strict'; + +const common = require('../common'); +const assert = require('assert'); + +const dgram = require('dgram'); +const client = dgram.createSocket('udp4'); + +const buf = Buffer.allocUnsafe(256); +const offset = 20; +const len = buf.length - offset; + +const messageSent = common.mustSucceed(function messageSent(bytes) { + assert.notStrictEqual(bytes, buf.length); + assert.strictEqual(bytes, buf.length - offset); + client.close(); +}); + +client.bind(0, common.mustCall(() => { + client.connect(client.address().port, common.mustCall(() => { + client.send(buf, offset, len, messageSent); + })); +})); diff --git a/cli/tests/node_compat/test/parallel/test-dgram-connect-send-callback-buffer.js b/cli/tests/node_compat/test/parallel/test-dgram-connect-send-callback-buffer.js new file mode 100644 index 00000000000000..3b1948cc012a57 --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-dgram-connect-send-callback-buffer.js @@ -0,0 +1,27 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +'use strict'; + +const common = require('../common'); +const assert = require('assert'); +const dgram = require('dgram'); + +const client = dgram.createSocket('udp4'); + +const buf = Buffer.allocUnsafe(256); + +const onMessage = common.mustSucceed((bytes) => { + assert.strictEqual(bytes, buf.length); + client.close(); +}); + +client.bind(0, common.mustCall(() => { + client.connect(client.address().port, common.mustCall(() => { + client.send(buf, onMessage); + })); +})); diff --git a/cli/tests/node_compat/test/parallel/test-dgram-connect-send-callback-multi-buffer.js b/cli/tests/node_compat/test/parallel/test-dgram-connect-send-callback-multi-buffer.js new file mode 100644 index 00000000000000..365d0c4e76eaa8 --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-dgram-connect-send-callback-multi-buffer.js @@ -0,0 +1,36 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +'use strict'; + +const common = require('../common'); +const assert = require('assert'); +const dgram = require('dgram'); + +const client = dgram.createSocket('udp4'); + +const messageSent = common.mustCall((err, bytes) => { + assert.strictEqual(bytes, buf1.length + buf2.length); +}); + +const buf1 = Buffer.alloc(256, 'x'); +const buf2 = Buffer.alloc(256, 'y'); + +client.on('listening', common.mustCall(() => { + const port = client.address().port; + client.connect(port, common.mustCall(() => { + client.send([buf1, buf2], messageSent); + })); +})); + +client.on('message', common.mustCall((buf, info) => { + const expected = Buffer.concat([buf1, buf2]); + assert.ok(buf.equals(expected), 'message was received correctly'); + client.close(); +})); + +client.bind(0); diff --git a/cli/tests/node_compat/test/parallel/test-dgram-connect-send-default-host.js b/cli/tests/node_compat/test/parallel/test-dgram-connect-send-default-host.js new file mode 100644 index 00000000000000..3b2a579c0005d9 --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-dgram-connect-send-default-host.js @@ -0,0 +1,55 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +'use strict'; + +const common = require('../common'); +const assert = require('assert'); +const dgram = require('dgram'); + +const client = dgram.createSocket('udp4'); +const server = dgram.createSocket('udp4'); + +const toSend = [Buffer.alloc(256, 'x'), + Buffer.alloc(256, 'y'), + Buffer.alloc(256, 'z'), + 'hello']; + +const received = []; + +server.on('listening', common.mustCall(() => { + const port = server.address().port; + client.connect(port, (err) => { + assert.ifError(err); + client.send(toSend[0], 0, toSend[0].length); + client.send(toSend[1]); + client.send([toSend[2]]); + client.send(toSend[3], 0, toSend[3].length); + + client.send(new Uint8Array(toSend[0]), 0, toSend[0].length); + client.send(new Uint8Array(toSend[1])); + client.send([new Uint8Array(toSend[2])]); + client.send(new Uint8Array(Buffer.from(toSend[3])), + 0, toSend[3].length); + }); +})); + +server.on('message', common.mustCall((buf, info) => { + received.push(buf.toString()); + + if (received.length === toSend.length * 2) { + // The replies may arrive out of order -> sort them before checking. + received.sort(); + + const expected = toSend.concat(toSend).map(String).sort(); + assert.deepStrictEqual(received, expected); + client.close(); + server.close(); + } +}, toSend.length * 2)); + +server.bind(0); diff --git a/cli/tests/node_compat/test/parallel/test-dgram-connect-send-empty-array.js b/cli/tests/node_compat/test/parallel/test-dgram-connect-send-empty-array.js new file mode 100644 index 00000000000000..3ca2f930b53380 --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-dgram-connect-send-empty-array.js @@ -0,0 +1,29 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +'use strict'; + +const common = require('../common'); + +const assert = require('assert'); +const dgram = require('dgram'); + +const client = dgram.createSocket('udp4'); + +client.on('message', common.mustCall((buf, info) => { + const expected = Buffer.alloc(0); + assert.ok(buf.equals(expected), `Expected empty message but got ${buf}`); + client.close(); +})); + +client.on('listening', common.mustCall(() => { + client.connect(client.address().port, + common.localhostIPv4, + common.mustCall(() => client.send([]))); +})); + +client.bind(0); diff --git a/cli/tests/node_compat/test/parallel/test-dgram-connect-send-empty-buffer.js b/cli/tests/node_compat/test/parallel/test-dgram-connect-send-empty-buffer.js new file mode 100644 index 00000000000000..07a841305ac048 --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-dgram-connect-send-empty-buffer.js @@ -0,0 +1,27 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +'use strict'; +const common = require('../common'); + +const assert = require('assert'); +const dgram = require('dgram'); + +const client = dgram.createSocket('udp4'); + +client.bind(0, common.mustCall(function() { + const port = this.address().port; + client.connect(port, common.mustCall(() => { + const buf = Buffer.alloc(0); + client.send(buf, 0, 0, common.mustSucceed()); + })); + + client.on('message', common.mustCall((buffer) => { + assert.strictEqual(buffer.length, 0); + client.close(); + })); +})); diff --git a/cli/tests/node_compat/test/parallel/test-dgram-connect-send-empty-packet.js b/cli/tests/node_compat/test/parallel/test-dgram-connect-send-empty-packet.js new file mode 100644 index 00000000000000..04bb36ab62e24c --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-dgram-connect-send-empty-packet.js @@ -0,0 +1,35 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +'use strict'; +const common = require('../common'); + +const assert = require('assert'); +const dgram = require('dgram'); + +const client = dgram.createSocket('udp4'); + +client.bind(0, common.mustCall(function() { + client.connect(client.address().port, common.mustCall(() => { + client.on('message', common.mustCall(callback)); + const buf = Buffer.alloc(1); + + const interval = setInterval(function() { + client.send(buf, 0, 0, common.mustCall(callback)); + }, 10); + + function callback(firstArg) { + // If client.send() callback, firstArg should be null. + // If client.on('message') listener, firstArg should be a 0-length buffer. + if (firstArg instanceof Buffer) { + assert.strictEqual(firstArg.length, 0); + clearInterval(interval); + client.close(); + } + } + })); +})); diff --git a/cli/tests/node_compat/test/parallel/test-dgram-connect-send-multi-buffer-copy.js b/cli/tests/node_compat/test/parallel/test-dgram-connect-send-multi-buffer-copy.js new file mode 100644 index 00000000000000..aec62ee51a4f4a --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-dgram-connect-send-multi-buffer-copy.js @@ -0,0 +1,36 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +'use strict'; + +const common = require('../common'); +const assert = require('assert'); +const dgram = require('dgram'); + +const client = dgram.createSocket('udp4'); + +const onMessage = common.mustCall(common.mustSucceed((bytes) => { + assert.strictEqual(bytes, buf1.length + buf2.length); +})); + +const buf1 = Buffer.alloc(256, 'x'); +const buf2 = Buffer.alloc(256, 'y'); + +client.on('listening', common.mustCall(function() { + const toSend = [buf1, buf2]; + client.connect(client.address().port, common.mustCall(() => { + client.send(toSend, onMessage); + })); +})); + +client.on('message', common.mustCall(function onMessage(buf, info) { + const expected = Buffer.concat([buf1, buf2]); + assert.ok(buf.equals(expected), 'message was received correctly'); + client.close(); +})); + +client.bind(0); diff --git a/cli/tests/node_compat/test/parallel/test-dgram-connect-send-multi-string-array.js b/cli/tests/node_compat/test/parallel/test-dgram-connect-send-multi-string-array.js new file mode 100644 index 00000000000000..daf787e25ddcd8 --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-dgram-connect-send-multi-string-array.js @@ -0,0 +1,24 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +'use strict'; +const common = require('../common'); +const assert = require('assert'); +const dgram = require('dgram'); +const socket = dgram.createSocket('udp4'); +const data = ['foo', 'bar', 'baz']; + +socket.on('message', common.mustCall((msg, rinfo) => { + socket.close(); + assert.deepStrictEqual(msg.toString(), data.join('')); +})); + +socket.bind(0, () => { + socket.connect(socket.address().port, common.mustCall(() => { + socket.send(data); + })); +}); diff --git a/cli/tests/node_compat/test/parallel/test-dgram-connect.js b/cli/tests/node_compat/test/parallel/test-dgram-connect.js new file mode 100644 index 00000000000000..41d6a60620f9a6 --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-dgram-connect.js @@ -0,0 +1,73 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +'use strict'; + +const common = require('../common'); +const assert = require('assert'); +const dgram = require('dgram'); + +const PORT = 12345; + +const client = dgram.createSocket('udp4'); +client.connect(PORT, common.mustCall(() => { + const remoteAddr = client.remoteAddress(); + assert.strictEqual(remoteAddr.port, PORT); + assert.throws(() => { + client.connect(PORT, common.mustNotCall()); + }, { + name: 'Error', + message: 'Already connected', + code: 'ERR_SOCKET_DGRAM_IS_CONNECTED' + }); + + client.disconnect(); + assert.throws(() => { + client.disconnect(); + }, { + name: 'Error', + message: 'Not connected', + code: 'ERR_SOCKET_DGRAM_NOT_CONNECTED' + }); + + assert.throws(() => { + client.remoteAddress(); + }, { + name: 'Error', + message: 'Not connected', + code: 'ERR_SOCKET_DGRAM_NOT_CONNECTED' + }); + + client.once('connect', common.mustCall(() => client.close())); + client.connect(PORT); +})); + +assert.throws(() => { + client.connect(PORT); +}, { + name: 'Error', + message: 'Already connected', + code: 'ERR_SOCKET_DGRAM_IS_CONNECTED' +}); + +assert.throws(() => { + client.disconnect(); +}, { + name: 'Error', + message: 'Not connected', + code: 'ERR_SOCKET_DGRAM_NOT_CONNECTED' +}); + +[ 0, null, 78960, undefined ].forEach((port) => { + assert.throws(() => { + client.connect(port); + }, { + name: 'RangeError', + message: /^Port should be > 0 and < 65536/, + code: 'ERR_SOCKET_BAD_PORT' + }); +}); diff --git a/cli/tests/node_compat/test/parallel/test-dgram-createSocket-type.js b/cli/tests/node_compat/test/parallel/test-dgram-createSocket-type.js new file mode 100644 index 00000000000000..afcb54ab2f22c1 --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-dgram-createSocket-type.js @@ -0,0 +1,68 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +'use strict'; +const common = require('../common'); +const assert = require('assert'); +const dgram = require('dgram'); +const invalidTypes = [ + 'test', + ['udp4'], + new String('udp4'), + 1, + {}, + true, + false, + null, + undefined, +]; +const validTypes = [ + 'udp4', + 'udp6', + { type: 'udp4' }, + { type: 'udp6' }, +]; +const errMessage = /^Bad socket type specified\. Valid types are: udp4, udp6$/; + +// Error must be thrown with invalid types +invalidTypes.forEach((invalidType) => { + assert.throws(() => { + dgram.createSocket(invalidType); + }, { + code: 'ERR_SOCKET_BAD_TYPE', + name: 'TypeError', + message: errMessage + }); +}); + +// Error must not be thrown with valid types +validTypes.forEach((validType) => { + const socket = dgram.createSocket(validType); + socket.close(); +}); + +// Ensure buffer sizes can be set +{ + const socket = dgram.createSocket({ + type: 'udp4', + recvBufferSize: 10000, + sendBufferSize: 15000 + }); + + socket.bind(common.mustCall(() => { + // note: linux will double the buffer size + assert.ok(socket.getRecvBufferSize() === 10000 || + socket.getRecvBufferSize() === 20000, + 'SO_RCVBUF not 10000 or 20000, ' + + `was ${socket.getRecvBufferSize()}`); + assert.ok(socket.getSendBufferSize() === 15000 || + socket.getSendBufferSize() === 30000, + 'SO_SNDBUF not 15000 or 30000, ' + + `was ${socket.getRecvBufferSize()}`); + socket.close(); + })); +} diff --git a/cli/tests/node_compat/test/parallel/test-dgram-custom-lookup.js b/cli/tests/node_compat/test/parallel/test-dgram-custom-lookup.js new file mode 100644 index 00000000000000..ca3bd3df337d0b --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-dgram-custom-lookup.js @@ -0,0 +1,56 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 16.13.0 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +'use strict'; +const common = require('../common'); +const assert = require('assert'); +const dgram = require('dgram'); +const dns = require('dns'); + +{ + // Verify that the provided lookup function is called. + const lookup = common.mustCall((host, family, callback) => { + dns.lookup(host, family, callback); + }); + + const socket = dgram.createSocket({ type: 'udp4', lookup }); + + socket.bind(common.mustCall(() => { + socket.close(); + })); +} + +// TODO: unable to overwrite imports with spies +// { +// // Verify that lookup defaults to dns.lookup(). +// const originalLookup = dns.lookup; + +// dns.lookup = common.mustCall((host, family, callback) => { +// dns.lookup = originalLookup; +// originalLookup(host, family, callback); +// }); + +// const socket = dgram.createSocket({ type: 'udp4' }); + +// socket.bind(common.mustCall(() => { +// socket.close(); +// })); +// } + +{ + // Verify that non-functions throw. + [null, true, false, 0, 1, NaN, '', 'foo', {}, Symbol()].forEach((value) => { + assert.throws(() => { + dgram.createSocket({ type: 'udp4', lookup: value }); + }, { + code: 'ERR_INVALID_ARG_TYPE', + name: 'TypeError', + message: 'The "lookup" argument must be of type function.' + + common.invalidArgTypeHelper(value) + }); + }); +} diff --git a/cli/tests/node_compat/test/parallel/test-dgram-error-message-address.js b/cli/tests/node_compat/test/parallel/test-dgram-error-message-address.js new file mode 100644 index 00000000000000..3bb144a8987f72 --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-dgram-error-message-address.js @@ -0,0 +1,64 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. + +'use strict'; +const common = require('../common'); +const assert = require('assert'); +const dgram = require('dgram'); + +// IPv4 Test +const socket_ipv4 = dgram.createSocket('udp4'); + +socket_ipv4.on('listening', common.mustNotCall()); + +socket_ipv4.on('error', common.mustCall(function(e) { + assert.strictEqual(e.port, undefined); + assert.strictEqual(e.message, 'bind EADDRNOTAVAIL 1.1.1.1'); + assert.strictEqual(e.address, '1.1.1.1'); + assert.strictEqual(e.code, 'EADDRNOTAVAIL'); + socket_ipv4.close(); +})); + +socket_ipv4.bind(0, '1.1.1.1'); + +// IPv6 Test +const socket_ipv6 = dgram.createSocket('udp6'); + +socket_ipv6.on('listening', common.mustNotCall()); + +socket_ipv6.on('error', common.mustCall(function(e) { + // EAFNOSUPPORT or EPROTONOSUPPORT means IPv6 is disabled on this system. + const allowed = ['EADDRNOTAVAIL', 'EAFNOSUPPORT', 'EPROTONOSUPPORT']; + assert(allowed.includes(e.code), `'${e.code}' was not one of ${allowed}.`); + assert.strictEqual(e.port, undefined); + assert.strictEqual(e.message, `bind ${e.code} 111::1`); + assert.strictEqual(e.address, '111::1'); + socket_ipv6.close(); +})); + +socket_ipv6.bind(0, '111::1'); diff --git a/cli/tests/node_compat/test/parallel/test-dgram-implicit-bind.js b/cli/tests/node_compat/test/parallel/test-dgram-implicit-bind.js new file mode 100644 index 00000000000000..34167c95b211d5 --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-dgram-implicit-bind.js @@ -0,0 +1,53 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. + +'use strict'; +const common = require('../common'); +const dgram = require('dgram'); + +const source = dgram.createSocket('udp4'); +const target = dgram.createSocket('udp4'); +let messages = 0; + +target.on('message', common.mustCall(function(buf) { + if (buf.toString() === 'abc') ++messages; + if (buf.toString() === 'def') ++messages; + if (messages === 2) { + source.close(); + target.close(); + } +}, 2)); + +target.on('listening', common.mustCall(function() { + // Second .send() call should not throw a bind error. + const port = this.address().port; + source.send(Buffer.from('abc'), 0, 3, port, '127.0.0.1'); + source.send(Buffer.from('def'), 0, 3, port, '127.0.0.1'); +})); + +target.bind(0); diff --git a/cli/tests/node_compat/test/parallel/test-dgram-ipv6only.js b/cli/tests/node_compat/test/parallel/test-dgram-ipv6only.js new file mode 100644 index 00000000000000..31f4e1fd9d120f --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-dgram-ipv6only.js @@ -0,0 +1,44 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 16.13.0 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +// TODO(cmorten): Deno.listenDatagram is currently `0.0.0.0` when you listen to `::`. + +'use strict'; + +const common = require('../common'); +if (!common.hasIPv6) + common.skip('no IPv6 support'); + +const dgram = require('dgram'); + +// This test ensures that dual-stack support is disabled when +// we specify the `ipv6Only` option in `dgram.createSocket()`. +const socket = dgram.createSocket({ + type: 'udp6', + ipv6Only: true, +}); + +socket.bind({ + port: 0, + address: '::', +}, common.mustCall(() => { + const { port } = socket.address(); + const client = dgram.createSocket('udp4'); + + // We can still bind to '0.0.0.0'. + // TODO: uncomment out when Deno allows IPv4 and IPv6 to be bound + // independently + // client.bind({ + // port, + // address: '0.0.0.0', + // }, common.mustCall(() => { + client.close(); + socket.close(); + // })); + + client.on('error', common.mustNotCall()); +})); diff --git a/cli/tests/node_compat/test/parallel/test-dgram-listen-after-bind.js b/cli/tests/node_compat/test/parallel/test-dgram-listen-after-bind.js new file mode 100644 index 00000000000000..3b9fd9771902fe --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-dgram-listen-after-bind.js @@ -0,0 +1,52 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. + +'use strict'; +require('../common'); +const common = require('../common'); +const assert = require('assert'); +const dgram = require('dgram'); + +const socket = dgram.createSocket('udp4'); + +socket.bind(); + +let fired = false; +const timer = setTimeout(() => { + socket.close(); +}, 100); + +socket.on('listening', common.mustCall(() => { + clearTimeout(timer); + fired = true; + socket.close(); +})); + +socket.on('close', common.mustCall(() => { + assert(fired, 'listening should fire after bind'); +})); diff --git a/cli/tests/node_compat/test/parallel/test-dgram-msgsize.js b/cli/tests/node_compat/test/parallel/test-dgram-msgsize.js new file mode 100644 index 00000000000000..88587842d9094d --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-dgram-msgsize.js @@ -0,0 +1,46 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. + +'use strict'; +const common = require('../common'); +const assert = require('assert'); +const dgram = require('dgram'); + +// Send a too big datagram. The destination doesn't matter because it's +// not supposed to get sent out anyway. +const buf = Buffer.allocUnsafe(256 * 1024); +const sock = dgram.createSocket('udp4'); +sock.send(buf, 0, buf.length, 12345, '127.0.0.1', common.mustCall(cb)); +function cb(err) { + assert(err instanceof Error); + assert.strictEqual(err.code, 'EMSGSIZE'); + assert.strictEqual(err.address, '127.0.0.1'); + assert.strictEqual(err.port, 12345); + assert.strictEqual(err.message, 'send EMSGSIZE 127.0.0.1:12345'); + sock.close(); +} diff --git a/cli/tests/node_compat/test/parallel/test-dgram-oob-buffer.js b/cli/tests/node_compat/test/parallel/test-dgram-oob-buffer.js new file mode 100644 index 00000000000000..6ab59420c1f812 --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-dgram-oob-buffer.js @@ -0,0 +1,52 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. + +'use strict'; +// Some operating systems report errors when an UDP message is sent to an +// unreachable host. This error can be reported by sendto() and even by +// recvfrom(). Node should not propagate this error to the user. + +const common = require('../common'); +const dgram = require('dgram'); + +const socket = dgram.createSocket('udp4'); +const buf = Buffer.from([1, 2, 3, 4]); +const portGetter = dgram.createSocket('udp4') + .bind(0, 'localhost', common.mustCall(() => { + const { address, port } = portGetter.address(); + portGetter.close(common.mustCall(() => { + socket.send(buf, 0, 0, port, address, common.mustNotCall()); + socket.send(buf, 0, 4, port, address, common.mustNotCall()); + socket.send(buf, 1, 3, port, address, common.mustNotCall()); + socket.send(buf, 3, 1, port, address, common.mustNotCall()); + // Since length of zero means nothing, don't error despite OOB. + socket.send(buf, 4, 0, port, address, common.mustNotCall()); + + socket.close(); + })); + })); diff --git a/cli/tests/node_compat/test/parallel/test-dgram-recv-error.js b/cli/tests/node_compat/test/parallel/test-dgram-recv-error.js new file mode 100644 index 00000000000000..11b2633d8878de --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-dgram-recv-error.js @@ -0,0 +1,26 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +// Flags: --expose-internals +'use strict'; +const common = require('../common'); +const assert = require('assert'); +const dgram = require('dgram'); +const { kStateSymbol } = require('internal/dgram'); +const s = dgram.createSocket('udp4'); +const { handle } = s[kStateSymbol]; + +s.on('error', common.mustCall((err) => { + s.close(); + + // Don't check the full error message, as the errno is not important here. + assert.match(String(err), /^Error: recvmsg/); + assert.strictEqual(err.syscall, 'recvmsg'); +})); + +s.on('message', common.mustNotCall('no message should be received.')); +s.bind(common.mustCall(() => handle.onmessage(-1, handle, null, null))); diff --git a/cli/tests/node_compat/test/parallel/test-dgram-send-bad-arguments.js b/cli/tests/node_compat/test/parallel/test-dgram-send-bad-arguments.js new file mode 100644 index 00000000000000..a465be0984fe0b --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-dgram-send-bad-arguments.js @@ -0,0 +1,162 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. + +'use strict'; +const common = require('../common'); +const assert = require('assert'); +const dgram = require('dgram'); + +const buf = Buffer.from('test'); +const host = '127.0.0.1'; +const sock = dgram.createSocket('udp4'); + +function checkArgs(connected) { + // First argument should be a buffer. + assert.throws( + () => { sock.send(); }, + { + code: 'ERR_INVALID_ARG_TYPE', + name: 'TypeError', + message: 'The "buffer" argument must be of type string or an instance ' + + 'of Buffer, TypedArray, or DataView. Received undefined' + } + ); + + // send(buf, offset, length, port, host) + if (connected) { + assert.throws( + () => { sock.send(buf, 1, 1, -1, host); }, + { + code: 'ERR_SOCKET_DGRAM_IS_CONNECTED', + name: 'Error', + message: 'Already connected' + } + ); + + assert.throws( + () => { sock.send(buf, 1, 1, 0, host); }, + { + code: 'ERR_SOCKET_DGRAM_IS_CONNECTED', + name: 'Error', + message: 'Already connected' + } + ); + + assert.throws( + () => { sock.send(buf, 1, 1, 65536, host); }, + { + code: 'ERR_SOCKET_DGRAM_IS_CONNECTED', + name: 'Error', + message: 'Already connected' + } + ); + + assert.throws( + () => { sock.send(buf, 1234, '127.0.0.1', common.mustNotCall()); }, + { + code: 'ERR_SOCKET_DGRAM_IS_CONNECTED', + name: 'Error', + message: 'Already connected' + } + ); + + const longArray = [1, 2, 3, 4, 5, 6, 7, 8]; + for (const input of ['hello', + Buffer.from('hello'), + Buffer.from('hello world').subarray(0, 5), + Buffer.from('hello world').subarray(4, 9), + Buffer.from('hello world').subarray(6), + new Uint8Array([1, 2, 3, 4, 5]), + new Uint8Array(longArray).subarray(0, 5), + new Uint8Array(longArray).subarray(2, 7), + new Uint8Array(longArray).subarray(3), + new DataView(new ArrayBuffer(5), 0), + new DataView(new ArrayBuffer(6), 1), + new DataView(new ArrayBuffer(7), 1, 5)]) { + assert.throws( + () => { sock.send(input, 6, 0); }, + { + code: 'ERR_BUFFER_OUT_OF_BOUNDS', + name: 'RangeError', + message: '"offset" is outside of buffer bounds', + } + ); + + assert.throws( + () => { sock.send(input, 0, 6); }, + { + code: 'ERR_BUFFER_OUT_OF_BOUNDS', + name: 'RangeError', + message: '"length" is outside of buffer bounds', + } + ); + + assert.throws( + () => { sock.send(input, 3, 4); }, + { + code: 'ERR_BUFFER_OUT_OF_BOUNDS', + name: 'RangeError', + message: '"length" is outside of buffer bounds', + } + ); + } + } else { + assert.throws(() => { sock.send(buf, 1, 1, -1, host); }, RangeError); + assert.throws(() => { sock.send(buf, 1, 1, 0, host); }, RangeError); + assert.throws(() => { sock.send(buf, 1, 1, 65536, host); }, RangeError); + } + + // send(buf, port, host) + assert.throws( + () => { sock.send(23, 12345, host); }, + { + code: 'ERR_INVALID_ARG_TYPE', + name: 'TypeError', + message: 'The "buffer" argument must be of type string or an instance ' + + 'of Buffer, TypedArray, or DataView. Received type number (23)' + } + ); + + // send([buf1, ..], port, host) + assert.throws( + () => { sock.send([buf, 23], 12345, host); }, + { + code: 'ERR_INVALID_ARG_TYPE', + name: 'TypeError', + message: 'The "buffer list arguments" argument must be of type string ' + + 'or an instance of Buffer, TypedArray, or DataView. ' + + 'Received an instance of Array' + } + ); +} + +checkArgs(); +sock.connect(12345, common.mustCall(() => { + checkArgs(true); + sock.close(); +})); diff --git a/cli/tests/node_compat/test/parallel/test-dgram-send-callback-buffer-empty-address.js b/cli/tests/node_compat/test/parallel/test-dgram-send-callback-buffer-empty-address.js new file mode 100644 index 00000000000000..f04945597ceaf0 --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-dgram-send-callback-buffer-empty-address.js @@ -0,0 +1,23 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +'use strict'; + +const common = require('../common'); +const assert = require('assert'); +const dgram = require('dgram'); + +const client = dgram.createSocket('udp4'); + +const buf = Buffer.alloc(256, 'x'); + +const onMessage = common.mustSucceed((bytes) => { + assert.strictEqual(bytes, buf.length); + client.close(); +}); + +client.bind(0, () => client.send(buf, client.address().port, onMessage)); diff --git a/cli/tests/node_compat/test/parallel/test-dgram-send-callback-buffer-length-empty-address.js b/cli/tests/node_compat/test/parallel/test-dgram-send-callback-buffer-length-empty-address.js new file mode 100644 index 00000000000000..bb7095b5904f59 --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-dgram-send-callback-buffer-length-empty-address.js @@ -0,0 +1,28 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +'use strict'; + +const common = require('../common'); +const assert = require('assert'); + +const dgram = require('dgram'); +const client = dgram.createSocket('udp4'); + +const buf = Buffer.alloc(256, 'x'); +const offset = 20; +const len = buf.length - offset; + +const onMessage = common.mustSucceed(function messageSent(bytes) { + assert.notStrictEqual(bytes, buf.length); + assert.strictEqual(bytes, buf.length - offset); + client.close(); +}); + +client.bind(0, () => client.send(buf, offset, len, + client.address().port, + onMessage)); diff --git a/cli/tests/node_compat/test/parallel/test-dgram-send-callback-buffer-length.js b/cli/tests/node_compat/test/parallel/test-dgram-send-callback-buffer-length.js new file mode 100644 index 00000000000000..03bb52a17e3e1a --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-dgram-send-callback-buffer-length.js @@ -0,0 +1,50 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. + +'use strict'; + +const common = require('../common'); +const assert = require('assert'); + +const dgram = require('dgram'); +const client = dgram.createSocket('udp4'); + +const buf = Buffer.allocUnsafe(256); +const offset = 20; +const len = buf.length - offset; + +const messageSent = common.mustSucceed(function messageSent(bytes) { + assert.notStrictEqual(bytes, buf.length); + assert.strictEqual(bytes, buf.length - offset); + client.close(); +}); + +client.bind(0, () => client.send(buf, offset, len, + client.address().port, + '127.0.0.1', + messageSent)); diff --git a/cli/tests/node_compat/test/parallel/test-dgram-send-callback-buffer.js b/cli/tests/node_compat/test/parallel/test-dgram-send-callback-buffer.js new file mode 100644 index 00000000000000..c486df86b57cc0 --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-dgram-send-callback-buffer.js @@ -0,0 +1,26 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +'use strict'; + +const common = require('../common'); +const assert = require('assert'); +const dgram = require('dgram'); + +const client = dgram.createSocket('udp4'); + +const buf = Buffer.allocUnsafe(256); + +const onMessage = common.mustSucceed((bytes) => { + assert.strictEqual(bytes, buf.length); + client.close(); +}); + +client.bind(0, () => client.send(buf, + client.address().port, + common.localhostIPv4, + onMessage)); diff --git a/cli/tests/node_compat/test/parallel/test-dgram-send-callback-multi-buffer-empty-address.js b/cli/tests/node_compat/test/parallel/test-dgram-send-callback-multi-buffer-empty-address.js new file mode 100644 index 00000000000000..33bb8d7f4e08fe --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-dgram-send-callback-multi-buffer-empty-address.js @@ -0,0 +1,34 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +'use strict'; + +const common = require('../common'); +const assert = require('assert'); +const dgram = require('dgram'); + +const client = dgram.createSocket('udp4'); + +const messageSent = common.mustSucceed(function messageSent(bytes) { + assert.strictEqual(bytes, buf1.length + buf2.length); +}); + +const buf1 = Buffer.alloc(256, 'x'); +const buf2 = Buffer.alloc(256, 'y'); + +client.on('listening', function() { + const port = this.address().port; + client.send([buf1, buf2], port, messageSent); +}); + +client.on('message', common.mustCall(function onMessage(buf) { + const expected = Buffer.concat([buf1, buf2]); + assert.ok(buf.equals(expected), 'message was received correctly'); + client.close(); +})); + +client.bind(0); diff --git a/cli/tests/node_compat/test/parallel/test-dgram-send-callback-multi-buffer.js b/cli/tests/node_compat/test/parallel/test-dgram-send-callback-multi-buffer.js new file mode 100644 index 00000000000000..3f6e793619650b --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-dgram-send-callback-multi-buffer.js @@ -0,0 +1,34 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +'use strict'; + +const common = require('../common'); +const assert = require('assert'); +const dgram = require('dgram'); + +const client = dgram.createSocket('udp4'); + +const messageSent = common.mustCall((err, bytes) => { + assert.strictEqual(bytes, buf1.length + buf2.length); +}); + +const buf1 = Buffer.alloc(256, 'x'); +const buf2 = Buffer.alloc(256, 'y'); + +client.on('listening', () => { + const port = client.address().port; + client.send([buf1, buf2], port, common.localhostIPv4, messageSent); +}); + +client.on('message', common.mustCall((buf, info) => { + const expected = Buffer.concat([buf1, buf2]); + assert.ok(buf.equals(expected), 'message was received correctly'); + client.close(); +})); + +client.bind(0); diff --git a/cli/tests/node_compat/test/parallel/test-dgram-send-callback-recursive.js b/cli/tests/node_compat/test/parallel/test-dgram-send-callback-recursive.js new file mode 100644 index 00000000000000..6752cbf4abfd61 --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-dgram-send-callback-recursive.js @@ -0,0 +1,50 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +'use strict'; +const common = require('../common'); +const assert = require('assert'); + +const dgram = require('dgram'); +const client = dgram.createSocket('udp4'); +const chunk = 'abc'; +let received = 0; +let sent = 0; +const limit = 10; +let async = false; +let port; + +function onsend() { + if (sent++ < limit) { + client.send(chunk, 0, chunk.length, port, common.localhostIPv4, onsend); + } else { + assert.strictEqual(async, true); + } +} + +client.on('listening', function() { + port = this.address().port; + + process.nextTick(() => { + async = true; + }); + + onsend(); +}); + +client.on('message', (buf, info) => { + received++; + if (received === limit) { + client.close(); + } +}); + +client.on('close', common.mustCall(function() { + assert.strictEqual(received, limit); +})); + +client.bind(0); diff --git a/cli/tests/node_compat/test/parallel/test-dgram-send-cb-quelches-error.js b/cli/tests/node_compat/test/parallel/test-dgram-send-cb-quelches-error.js new file mode 100644 index 00000000000000..d2fd5af50dcc89 --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-dgram-send-cb-quelches-error.js @@ -0,0 +1,47 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 16.13.0 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +// TODO(cmorten): uncomment dns module code once dns.setServer() has been +// implemented + +'use strict'; +const common = require('../common'); +const mustCall = common.mustCall; +const assert = require('assert'); +const dgram = require('dgram'); +// const dns = require('dns'); + +const socket = dgram.createSocket('udp4'); +const buffer = Buffer.from('gary busey'); + +// dns.setServers([]); + +socket.once('error', onEvent); + +// assert that: +// * callbacks act as "error" listeners if given. +// * error is never emitter for missing dns entries +// if a callback that handles error is present +// * error is emitted if a callback with no argument is passed +socket.send(buffer, 0, buffer.length, 100, + 'dne.example.com', mustCall(callbackOnly)); + +function callbackOnly(err) { + assert.ok(err); + socket.removeListener('error', onEvent); + socket.on('error', mustCall(onError)); + socket.send(buffer, 0, buffer.length, 100, 'dne.invalid'); +} + +function onEvent(err) { + assert.fail(`Error should not be emitted if there is callback: ${err}`); +} + +function onError(err) { + assert.ok(err); + socket.close(); +} diff --git a/cli/tests/node_compat/test/parallel/test-dgram-send-default-host.js b/cli/tests/node_compat/test/parallel/test-dgram-send-default-host.js new file mode 100644 index 00000000000000..694531347cfa33 --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-dgram-send-default-host.js @@ -0,0 +1,79 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +'use strict'; + +const common = require('../common'); +const assert = require('assert'); +const dgram = require('dgram'); + +const client = dgram.createSocket('udp4'); + +const toSend = [Buffer.alloc(256, 'x'), + Buffer.alloc(256, 'y'), + Buffer.alloc(256, 'z'), + 'hello']; + +const received = []; +let totalBytesSent = 0; +let totalBytesReceived = 0; +const arrayBufferViewsCount = common.getArrayBufferViews( + Buffer.from('') +).length; + +client.on('listening', common.mustCall(() => { + const port = client.address().port; + + client.send(toSend[0], 0, toSend[0].length, port); + client.send(toSend[1], port); + client.send([toSend[2]], port); + client.send(toSend[3], 0, toSend[3].length, port); + + totalBytesSent += toSend.map((buf) => buf.length) + .reduce((a, b) => a + b, 0); + + for (const msgBuf of common.getArrayBufferViews(toSend[0])) { + client.send(msgBuf, 0, msgBuf.byteLength, port); + totalBytesSent += msgBuf.byteLength; + } + for (const msgBuf of common.getArrayBufferViews(toSend[1])) { + client.send(msgBuf, port); + totalBytesSent += msgBuf.byteLength; + } + for (const msgBuf of common.getArrayBufferViews(toSend[2])) { + client.send([msgBuf], port); + totalBytesSent += msgBuf.byteLength; + } +})); + +client.on('message', common.mustCall((buf, info) => { + received.push(buf.toString()); + totalBytesReceived += info.size; + + if (totalBytesReceived === totalBytesSent) { + client.close(); + } + // For every buffer in `toSend`, we send the raw Buffer, + // as well as every TypedArray in getArrayBufferViews() +}, toSend.length + (toSend.length - 1) * arrayBufferViewsCount)); + +client.on('close', common.mustCall((buf, info) => { + // The replies may arrive out of order -> sort them before checking. + received.sort(); + + const repeated = [...toSend]; + for (let i = 0; i < arrayBufferViewsCount; i++) { + repeated.push(...toSend.slice(0, 3)); + } + + assert.strictEqual(totalBytesSent, totalBytesReceived); + + const expected = repeated.map(String).sort(); + assert.deepStrictEqual(received, expected); +})); + +client.bind(0); diff --git a/cli/tests/node_compat/test/parallel/test-dgram-send-empty-array.js b/cli/tests/node_compat/test/parallel/test-dgram-send-empty-array.js new file mode 100644 index 00000000000000..6554cb501237ca --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-dgram-send-empty-array.js @@ -0,0 +1,32 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +'use strict'; + +const common = require('../common'); + +const assert = require('assert'); +const dgram = require('dgram'); + +const client = dgram.createSocket('udp4'); + +let interval; + +client.on('message', common.mustCall(function onMessage(buf, info) { + const expected = Buffer.alloc(0); + assert.ok(buf.equals(expected), `Expected empty message but got ${buf}`); + clearInterval(interval); + client.close(); +})); + +client.on('listening', common.mustCall(function() { + interval = setInterval(function() { + client.send([], client.address().port, common.localhostIPv4); + }, 10); +})); + +client.bind(0); diff --git a/cli/tests/node_compat/test/parallel/test-dgram-send-empty-buffer.js b/cli/tests/node_compat/test/parallel/test-dgram-send-empty-buffer.js new file mode 100644 index 00000000000000..55ed56e5b7473c --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-dgram-send-empty-buffer.js @@ -0,0 +1,50 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. + +'use strict'; +const common = require('../common'); + +const assert = require('assert'); +const dgram = require('dgram'); + +const client = dgram.createSocket('udp4'); + +client.bind(0, common.mustCall(function() { + const port = this.address().port; + + client.on('message', common.mustCall(function onMessage(buffer) { + assert.strictEqual(buffer.length, 0); + clearInterval(interval); + client.close(); + })); + + const buf = Buffer.alloc(0); + const interval = setInterval(function() { + client.send(buf, 0, 0, port, '127.0.0.1', common.mustCall()); + }, 10); +})); diff --git a/cli/tests/node_compat/test/parallel/test-dgram-send-empty-packet.js b/cli/tests/node_compat/test/parallel/test-dgram-send-empty-packet.js new file mode 100644 index 00000000000000..78789a71a61ce6 --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-dgram-send-empty-packet.js @@ -0,0 +1,36 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +'use strict'; +const common = require('../common'); + +const assert = require('assert'); +const dgram = require('dgram'); + +const client = dgram.createSocket('udp4'); + +client.bind(0, common.mustCall(function() { + + client.on('message', common.mustCall(callback)); + + const port = this.address().port; + const buf = Buffer.alloc(1); + + const interval = setInterval(function() { + client.send(buf, 0, 0, port, '127.0.0.1', common.mustCall(callback)); + }, 10); + + function callback(firstArg) { + // If client.send() callback, firstArg should be null. + // If client.on('message') listener, firstArg should be a 0-length buffer. + if (firstArg instanceof Buffer) { + assert.strictEqual(firstArg.length, 0); + clearInterval(interval); + client.close(); + } + } +})); diff --git a/cli/tests/node_compat/test/parallel/test-dgram-send-error.js b/cli/tests/node_compat/test/parallel/test-dgram-send-error.js new file mode 100644 index 00000000000000..409097d082b7de --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-dgram-send-error.js @@ -0,0 +1,77 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +// Flags: --expose-internals +'use strict'; +const common = require('../common'); +const assert = require('assert'); +const dgram = require('dgram'); +const { internalBinding } = require('internal/test/binding'); +const { UV_UNKNOWN } = internalBinding('uv'); +const { getSystemErrorName } = require('util'); +const { kStateSymbol } = require('internal/dgram'); +const mockError = new Error('mock DNS error'); + +function getSocket(callback) { + const socket = dgram.createSocket('udp4'); + + socket.on('message', common.mustNotCall('Should not receive any messages.')); + socket.bind(common.mustCall(() => { + socket[kStateSymbol].handle.lookup = function(address, callback) { + process.nextTick(callback, mockError); + }; + + callback(socket); + })); + return socket; +} + +getSocket((socket) => { + socket.on('error', common.mustCall((err) => { + socket.close(); + assert.strictEqual(err, mockError); + })); + + socket.send('foo', socket.address().port, 'localhost'); +}); + +getSocket((socket) => { + const callback = common.mustCall((err) => { + socket.close(); + assert.strictEqual(err, mockError); + }); + + socket.send('foo', socket.address().port, 'localhost', callback); +}); + +{ + const socket = dgram.createSocket('udp4'); + + socket.on('message', common.mustNotCall('Should not receive any messages.')); + + socket.bind(common.mustCall(() => { + const port = socket.address().port; + const callback = common.mustCall((err) => { + socket.close(); + assert.strictEqual(err.code, 'UNKNOWN'); + assert.strictEqual(getSystemErrorName(err.errno), 'UNKNOWN'); + assert.strictEqual(err.syscall, 'send'); + assert.strictEqual(err.address, common.localhostIPv4); + assert.strictEqual(err.port, port); + assert.strictEqual( + err.message, + `${err.syscall} ${err.code} ${err.address}:${err.port}` + ); + }); + + socket[kStateSymbol].handle.send = function() { + return UV_UNKNOWN; + }; + + socket.send('foo', port, common.localhostIPv4, callback); + })); +} diff --git a/cli/tests/node_compat/test/parallel/test-dgram-send-invalid-msg-type.js b/cli/tests/node_compat/test/parallel/test-dgram-send-invalid-msg-type.js new file mode 100644 index 00000000000000..49d79c46ad0b28 --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-dgram-send-invalid-msg-type.js @@ -0,0 +1,43 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. + +'use strict'; +require('../common'); + +// This test ensures that a TypeError is raised when the argument to `send()` +// or `sendto()` is anything but a Buffer. +// https://github.com/nodejs/node-v0.x-archive/issues/4496 + +const assert = require('assert'); +const dgram = require('dgram'); + +// Should throw but not crash. +const socket = dgram.createSocket('udp4'); +assert.throws(function() { socket.send(true, 0, 1, 1, 'host'); }, TypeError); +assert.throws(function() { socket.sendto(5, 0, 1, 1, 'host'); }, TypeError); +socket.close(); diff --git a/cli/tests/node_compat/test/parallel/test-dgram-send-multi-buffer-copy.js b/cli/tests/node_compat/test/parallel/test-dgram-send-multi-buffer-copy.js new file mode 100644 index 00000000000000..437c1dc349d6c3 --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-dgram-send-multi-buffer-copy.js @@ -0,0 +1,35 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +'use strict'; + +const common = require('../common'); +const assert = require('assert'); +const dgram = require('dgram'); + +const client = dgram.createSocket('udp4'); + +const onMessage = common.mustCall(function(err, bytes) { + assert.strictEqual(bytes, buf1.length + buf2.length); +}); + +const buf1 = Buffer.alloc(256, 'x'); +const buf2 = Buffer.alloc(256, 'y'); + +client.on('listening', function() { + const toSend = [buf1, buf2]; + client.send(toSend, this.address().port, common.localhostIPv4, onMessage); + toSend.splice(0, 2); +}); + +client.on('message', common.mustCall(function onMessage(buf, info) { + const expected = Buffer.concat([buf1, buf2]); + assert.ok(buf.equals(expected), 'message was received correctly'); + client.close(); +})); + +client.bind(0); diff --git a/cli/tests/node_compat/test/parallel/test-dgram-send-multi-string-array.js b/cli/tests/node_compat/test/parallel/test-dgram-send-multi-string-array.js new file mode 100644 index 00000000000000..9754c90eb65d62 --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-dgram-send-multi-string-array.js @@ -0,0 +1,20 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +'use strict'; +const common = require('../common'); +const assert = require('assert'); +const dgram = require('dgram'); +const socket = dgram.createSocket('udp4'); +const data = ['foo', 'bar', 'baz']; + +socket.on('message', common.mustCall((msg, rinfo) => { + socket.close(); + assert.deepStrictEqual(msg.toString(), data.join('')); +})); + +socket.bind(() => socket.send(data, socket.address().port, 'localhost')); diff --git a/cli/tests/node_compat/test/parallel/test-dgram-socket-buffer-size.js b/cli/tests/node_compat/test/parallel/test-dgram-socket-buffer-size.js new file mode 100644 index 00000000000000..b2fc332626d485 --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-dgram-socket-buffer-size.js @@ -0,0 +1,178 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 16.13.0 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +// Flags: --expose-internals +'use strict'; + +const common = require('../common'); +const assert = require('assert'); +const dgram = require('dgram'); +const { inspect } = require('util'); +const { internalBinding } = require('internal/test/binding'); +const { + UV_EBADF, + UV_EINVAL, + UV_ENOTSOCK +} = internalBinding('uv'); + +// Note error test amendments from Node due to Deno formatting errors slightly +// differently. +function getExpectedError(type) { + const code = common.isWindows ? 'ENOTSOCK' : 'EBADF'; + const message = common.isWindows ? + 'socket operation on non-socket' : 'bad file descriptor'; + const errno = common.isWindows ? UV_ENOTSOCK : UV_EBADF; + const syscall = `uv_${type}_buffer_size`; + const suffix = common.isWindows ? + 'ENOTSOCK (socket operation on non-socket)' : 'EBADF (bad file descriptor)'; + const error = { + code: 'ERR_SOCKET_BUFFER_SIZE', + name: 'SystemError', + message: `Could not get or set buffer size: ${syscall} returned ${suffix}`, + info: { + code, + message, + errno, + syscall + } + }; + return error; +} + +{ + // Should throw error if the socket is never bound. + const errorObj = getExpectedError('send'); + + const socket = dgram.createSocket('udp4'); + + assert.throws(() => { + socket.setSendBufferSize(8192); + }, (err) => { + assert.strictEqual( + inspect(err).replace(/^ +at .*\n/gm, ""), + `ERR_SOCKET_BUFFER_SIZE [SystemError]: ${errorObj.message}\n` + + " code: 'ERR_SOCKET_BUFFER_SIZE',\n" + + " info: {\n" + + ` errno: ${errorObj.info.errno},\n` + + ` code: '${errorObj.info.code}',\n` + + ` message: '${errorObj.info.message}',\n` + + ` syscall: '${errorObj.info.syscall}'\n` + + " },\n" + + ` errno: [Getter/Setter],\n` + + ` syscall: [Getter/Setter]\n` + + "}" + ); + return true; + }); + + assert.throws(() => { + socket.getSendBufferSize(); + }, errorObj); +} + +{ + const socket = dgram.createSocket('udp4'); + + // Should throw error if the socket is never bound. + const errorObj = getExpectedError('recv'); + + assert.throws(() => { + socket.setRecvBufferSize(8192); + }, errorObj); + + assert.throws(() => { + socket.getRecvBufferSize(); + }, errorObj); +} + +{ + // Should throw error if invalid buffer size is specified + const errorObj = { + code: 'ERR_SOCKET_BAD_BUFFER_SIZE', + name: 'TypeError', + message: /^Buffer size must be a positive integer$/ + }; + + const badBufferSizes = [-1, Infinity, 'Doh!']; + + const socket = dgram.createSocket('udp4'); + + socket.bind(common.mustCall(() => { + badBufferSizes.forEach((badBufferSize) => { + assert.throws(() => { + socket.setRecvBufferSize(badBufferSize); + }, errorObj); + + assert.throws(() => { + socket.setSendBufferSize(badBufferSize); + }, errorObj); + }); + socket.close(); + })); +} + +{ + // Can set and get buffer sizes after binding the socket. + const socket = dgram.createSocket('udp4'); + + socket.bind(common.mustCall(() => { + socket.setRecvBufferSize(10000); + socket.setSendBufferSize(10000); + + // note: linux will double the buffer size + const expectedBufferSize = common.isLinux ? 20000 : 10000; + assert.strictEqual(socket.getRecvBufferSize(), expectedBufferSize); + assert.strictEqual(socket.getSendBufferSize(), expectedBufferSize); + socket.close(); + })); +} + +{ + const info = { + code: 'EINVAL', + message: 'invalid argument', + errno: UV_EINVAL, + syscall: 'uv_recv_buffer_size' + }; + const errorObj = { + code: 'ERR_SOCKET_BUFFER_SIZE', + name: 'SystemError', + message: 'Could not get or set buffer size: uv_recv_buffer_size ' + + 'returned EINVAL (invalid argument)', + info + }; + const socket = dgram.createSocket('udp4'); + socket.bind(common.mustCall(() => { + assert.throws(() => { + socket.setRecvBufferSize(2147483648); + }, errorObj); + socket.close(); + })); +} + +{ + const info = { + code: 'EINVAL', + message: 'invalid argument', + errno: UV_EINVAL, + syscall: 'uv_send_buffer_size' + }; + const errorObj = { + code: 'ERR_SOCKET_BUFFER_SIZE', + name: 'SystemError', + message: 'Could not get or set buffer size: uv_send_buffer_size ' + + 'returned EINVAL (invalid argument)', + info + }; + const socket = dgram.createSocket('udp4'); + socket.bind(common.mustCall(() => { + assert.throws(() => { + socket.setSendBufferSize(2147483648); + }, errorObj); + socket.close(); + })); +} diff --git a/cli/tests/node_compat/test/parallel/test-dgram-udp4.js b/cli/tests/node_compat/test/parallel/test-dgram-udp4.js new file mode 100644 index 00000000000000..ebe5c2169e7c78 --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-dgram-udp4.js @@ -0,0 +1,59 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. + +'use strict'; +const common = require('../common'); +const assert = require('assert'); +const dgram = require('dgram'); +const message_to_send = 'A message to send'; + +const server = dgram.createSocket('udp4'); +server.on('message', common.mustCall((msg, rinfo) => { + assert.strictEqual(rinfo.address, common.localhostIPv4); + assert.strictEqual(msg.toString(), message_to_send.toString()); + server.send(msg, 0, msg.length, rinfo.port, rinfo.address); +})); +server.on('listening', common.mustCall(() => { + const client = dgram.createSocket('udp4'); + const port = server.address().port; + client.on('message', common.mustCall((msg, rinfo) => { + assert.strictEqual(rinfo.address, common.localhostIPv4); + assert.strictEqual(rinfo.port, port); + assert.strictEqual(msg.toString(), message_to_send.toString()); + client.close(); + server.close(); + })); + client.send(message_to_send, + 0, + message_to_send.length, + port, + 'localhost'); + client.on('close', common.mustCall()); +})); +server.on('close', common.mustCall()); +server.bind(0); diff --git a/cli/tests/node_compat/test/parallel/test-dgram-udp6-link-local-address.js b/cli/tests/node_compat/test/parallel/test-dgram-udp6-link-local-address.js new file mode 100644 index 00000000000000..c828413a204825 --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-dgram-udp6-link-local-address.js @@ -0,0 +1,61 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 16.13.0 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +'use strict'; +const common = require('../common'); +if (!common.hasIPv6) + common.skip('no IPv6 support'); + +const assert = require('assert'); +const dgram = require('dgram'); +const os = require('os'); + +const { isWindows } = common; + +function linklocal() { + for (const [ifname, entries] of Object.entries(os.networkInterfaces())) { + for (const { address, family, scopeid } of entries) { + if (family === 'IPv6' && address.startsWith('fe80:')) { + return { address, ifname, scopeid }; + } + } + } +} +const iface = linklocal(); + +if (!iface) + common.skip('cannot find any IPv6 interfaces with a link local address'); + +const address = isWindows ? iface.address : `${iface.address}%${iface.ifname}`; +const message = 'Hello, local world!'; + +// Create a client socket for sending to the link-local address. +const client = dgram.createSocket('udp6'); + +// Create the server socket listening on the link-local address. +const server = dgram.createSocket('udp6'); + +server.on('listening', common.mustCall(() => { + const port = server.address().port; + client.send(message, 0, message.length, port, address); +})); + +server.on('message', common.mustCall((buf, info) => { + const received = buf.toString(); + assert.strictEqual(received, message); + // Check that the sender address is the one bound, + // including the link local scope identifier. + // TODO(cmorten): info.address is missing the link local scope identifier + // assert.strictEqual( + // info.address, + // isWindows ? `${iface.address}%${iface.scopeid}` : address + // ); + server.close(); + client.close(); +}, 1)); + +server.bind({ address }); diff --git a/cli/tests/node_compat/test/parallel/test-dgram-udp6-send-default-host.js b/cli/tests/node_compat/test/parallel/test-dgram-udp6-send-default-host.js new file mode 100644 index 00000000000000..94c9983ea859de --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-dgram-udp6-send-default-host.js @@ -0,0 +1,83 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +'use strict'; + +const common = require('../common'); +if (!common.hasIPv6) + common.skip('no IPv6 support'); + +const assert = require('assert'); +const dgram = require('dgram'); + +const client = dgram.createSocket('udp6'); + +const toSend = [Buffer.alloc(256, 'x'), + Buffer.alloc(256, 'y'), + Buffer.alloc(256, 'z'), + 'hello']; + +const received = []; +let totalBytesSent = 0; +let totalBytesReceived = 0; +const arrayBufferViewLength = common.getArrayBufferViews( + Buffer.from('') +).length; + +client.on('listening', common.mustCall(() => { + const port = client.address().port; + + client.send(toSend[0], 0, toSend[0].length, port); + client.send(toSend[1], port); + client.send([toSend[2]], port); + client.send(toSend[3], 0, toSend[3].length, port); + + totalBytesSent += toSend.map((buf) => buf.length) + .reduce((a, b) => a + b, 0); + + for (const msgBuf of common.getArrayBufferViews(toSend[0])) { + client.send(msgBuf, 0, msgBuf.byteLength, port); + totalBytesSent += msgBuf.byteLength; + } + for (const msgBuf of common.getArrayBufferViews(toSend[1])) { + client.send(msgBuf, port); + totalBytesSent += msgBuf.byteLength; + } + for (const msgBuf of common.getArrayBufferViews(toSend[2])) { + client.send([msgBuf], port); + totalBytesSent += msgBuf.byteLength; + } +})); + +client.on('message', common.mustCall((buf, info) => { + received.push(buf.toString()); + totalBytesReceived += info.size; + + if (totalBytesReceived === totalBytesSent) { + client.close(); + } + // For every buffer in `toSend`, we send the raw Buffer, + // as well as every TypedArray in getArrayBufferViews() +}, toSend.length + (toSend.length - 1) * arrayBufferViewLength)); + +client.on('close', common.mustCall((buf, info) => { + // The replies may arrive out of order -> sort them before checking. + received.sort(); + + const repeated = [...toSend]; + for (let i = 0; i < arrayBufferViewLength; i++) { + // We get arrayBufferViews only for toSend[0..2]. + repeated.push(...toSend.slice(0, 3)); + } + + assert.strictEqual(totalBytesSent, totalBytesReceived); + + const expected = repeated.map(String).sort(); + assert.deepStrictEqual(received, expected); +})); + +client.bind(0); diff --git a/cli/tests/node_compat/test/parallel/test-diagnostics-channel-has-subscribers.js b/cli/tests/node_compat/test/parallel/test-diagnostics-channel-has-subscribers.js new file mode 100644 index 00000000000000..bc6ff51e5f3c65 --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-diagnostics-channel-has-subscribers.js @@ -0,0 +1,17 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +'use strict'; +require('../common'); +const assert = require('assert'); +const { channel, hasSubscribers } = require('diagnostics_channel'); + +const dc = channel('test'); +assert.ok(!hasSubscribers('test')); + +dc.subscribe(() => {}); +assert.ok(hasSubscribers('test')); diff --git a/cli/tests/node_compat/test/parallel/test-diagnostics-channel-net.js b/cli/tests/node_compat/test/parallel/test-diagnostics-channel-net.js new file mode 100644 index 00000000000000..05405efdea6e38 --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-diagnostics-channel-net.js @@ -0,0 +1,32 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +'use strict'; +const common = require('../common'); +const assert = require('assert'); +const net = require('net'); +const dc = require('diagnostics_channel'); + +const isNetSocket = (socket) => socket instanceof net.Socket; + +dc.subscribe('net.client.socket', common.mustCall(({ socket }) => { + assert.strictEqual(isNetSocket(socket), true); +})); + +dc.subscribe('net.server.socket', common.mustCall(({ socket }) => { + assert.strictEqual(isNetSocket(socket), true); +})); + +const server = net.createServer(common.mustCall((socket) => { + socket.destroy(); + server.close(); +})); + +server.listen(() => { + const { port } = server.address(); + net.connect(port); +}); diff --git a/cli/tests/node_compat/test/parallel/test-diagnostics-channel-object-channel-pub-sub.js b/cli/tests/node_compat/test/parallel/test-diagnostics-channel-object-channel-pub-sub.js new file mode 100644 index 00000000000000..f1edec7098379e --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-diagnostics-channel-object-channel-pub-sub.js @@ -0,0 +1,53 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +'use strict'; + +const common = require('../common'); +const dc = require('diagnostics_channel'); +const assert = require('assert'); +const { Channel } = dc; + +const input = { + foo: 'bar' +}; + +// Should not have named channel +assert.ok(!dc.hasSubscribers('test')); + +// Individual channel objects can be created to avoid future lookups +const channel = dc.channel('test'); +assert.ok(channel instanceof Channel); + +// No subscribers yet, should not publish +assert.ok(!channel.hasSubscribers); + +const subscriber = common.mustCall((message, name) => { + assert.strictEqual(name, channel.name); + assert.deepStrictEqual(message, input); +}); + +// Now there's a subscriber, should publish +channel.subscribe(subscriber); +assert.ok(channel.hasSubscribers); + +// The ActiveChannel prototype swap should not fail instanceof +assert.ok(channel instanceof Channel); + +// Should trigger the subscriber once +channel.publish(input); + +// Should not publish after subscriber is unsubscribed +assert.ok(channel.unsubscribe(subscriber)); +assert.ok(!channel.hasSubscribers); + +// unsubscribe() should return false when subscriber is not found +assert.ok(!channel.unsubscribe(subscriber)); + +assert.throws(() => { + channel.subscribe(null); +}, { code: 'ERR_INVALID_ARG_TYPE' }); diff --git a/cli/tests/node_compat/test/parallel/test-diagnostics-channel-pub-sub.js b/cli/tests/node_compat/test/parallel/test-diagnostics-channel-pub-sub.js new file mode 100644 index 00000000000000..c4c684c0794ecc --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-diagnostics-channel-pub-sub.js @@ -0,0 +1,51 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +'use strict'; + +const common = require('../common'); +const dc = require('diagnostics_channel'); +const assert = require('assert'); +const { Channel } = dc; + +const name = 'test'; +const input = { + foo: 'bar' +}; + +// Individual channel objects can be created to avoid future lookups +const channel = dc.channel(name); +assert.ok(channel instanceof Channel); + +// No subscribers yet, should not publish +assert.ok(!channel.hasSubscribers); + +const subscriber = common.mustCall((message, name) => { + assert.strictEqual(name, channel.name); + assert.deepStrictEqual(message, input); +}); + +// Now there's a subscriber, should publish +dc.subscribe(name, subscriber); +assert.ok(channel.hasSubscribers); + +// The ActiveChannel prototype swap should not fail instanceof +assert.ok(channel instanceof Channel); + +// Should trigger the subscriber once +channel.publish(input); + +// Should not publish after subscriber is unsubscribed +assert.ok(dc.unsubscribe(name, subscriber)); +assert.ok(!channel.hasSubscribers); + +// unsubscribe() should return false when subscriber is not found +assert.ok(!dc.unsubscribe(name, subscriber)); + +assert.throws(() => { + dc.subscribe(name, null); +}, { code: 'ERR_INVALID_ARG_TYPE' }); diff --git a/cli/tests/node_compat/test/parallel/test-diagnostics-channel-symbol-named.js b/cli/tests/node_compat/test/parallel/test-diagnostics-channel-symbol-named.js new file mode 100644 index 00000000000000..1f241a0d208856 --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-diagnostics-channel-symbol-named.js @@ -0,0 +1,35 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +'use strict'; + +const common = require('../common'); +const dc = require('diagnostics_channel'); +const assert = require('assert'); + +const input = { + foo: 'bar' +}; + +const symbol = Symbol('test'); + +// Individual channel objects can be created to avoid future lookups +const channel = dc.channel(symbol); + +// Expect two successful publishes later +channel.subscribe(common.mustCall((message, name) => { + assert.strictEqual(name, symbol); + assert.deepStrictEqual(message, input); +})); + +channel.publish(input); + +{ + assert.throws(() => { + dc.channel(null); + }, /ERR_INVALID_ARG_TYPE/); +} diff --git a/cli/tests/node_compat/test/parallel/test-diagnostics-channel-udp.js b/cli/tests/node_compat/test/parallel/test-diagnostics-channel-udp.js new file mode 100644 index 00000000000000..557562f4fef2ae --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-diagnostics-channel-udp.js @@ -0,0 +1,22 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +'use strict'; +const common = require('../common'); +const assert = require('assert'); +const dgram = require('dgram'); +const dc = require('diagnostics_channel'); + +const udpSocketChannel = dc.channel('udp.socket'); + +const isUDPSocket = (socket) => socket instanceof dgram.Socket; + +udpSocketChannel.subscribe(common.mustCall(({ socket }) => { + assert.strictEqual(isUDPSocket(socket), true); +})); +const socket = dgram.createSocket('udp4'); +socket.close(); diff --git a/cli/tests/node_compat/test/parallel/test-dns-lookup.js b/cli/tests/node_compat/test/parallel/test-dns-lookup.js new file mode 100644 index 00000000000000..d137586d238793 --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-dns-lookup.js @@ -0,0 +1,179 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 16.13.0 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +// Flags: --expose-internals +'use strict'; + +// TODO: enable remaining tests once functionality is implemented. + +const common = require('../common'); +const assert = require('assert'); +// const { internalBinding } = require('internal/test/binding'); +// const cares = internalBinding('cares_wrap'); + +// Stub `getaddrinfo` to *always* error. This has to be done before we load the +// `dns` module to guarantee that the `dns` module uses the stub. +// cares.getaddrinfo = () => internalBinding('uv').UV_ENOMEM; + +const dns = require('dns'); +const dnsPromises = dns.promises; + +{ + const err = { + code: "ERR_INVALID_ARG_TYPE", + name: "TypeError", + message: + /^The "hostname" argument must be of type string\. Received type number/, + }; + + assert.throws(() => dns.lookup(1, {}), err); + assert.throws(() => dnsPromises.lookup(1, {}), err); +} + +// This also verifies different expectWarning notations. +// common.expectWarning({ +// // For 'internal/test/binding' module. +// 'internal/test/binding': [ +// 'These APIs are for internal testing only. Do not use them.', +// ], +// // For calling `dns.lookup` with falsy `hostname`. +// 'DeprecationWarning': { +// DEP0118: 'The provided hostname "false" is not a valid ' + +// 'hostname, and is supported in the dns module solely for compatibility.' +// } +// }); + +assert.throws( + () => { + dns.lookup(false, "cb"); + }, + { + code: "ERR_INVALID_ARG_TYPE", + name: "TypeError", + } +); + +assert.throws( + () => { + dns.lookup(false, "options", "cb"); + }, + { + code: "ERR_INVALID_ARG_TYPE", + name: "TypeError", + } +); + +{ + const err = { + code: 'ERR_INVALID_ARG_VALUE', + name: 'TypeError', + message: "The argument 'hints' is invalid. Received 100" + }; + const options = { + hints: 100, + family: 0, + all: false + }; + + assert.throws(() => { dnsPromises.lookup(false, options); }, err); + assert.throws(() => { + dns.lookup(false, options, common.mustNotCall()); + }, err); +} + +{ + const family = 20; + const err = { + code: "ERR_INVALID_ARG_VALUE", + name: "TypeError", + message: `The property 'options.family' must be one of: 0, 4, 6. Received ${family}`, + }; + const options = { + hints: 0, + family, + all: false + }; + + assert.throws(() => { dnsPromises.lookup(false, options); }, err); + assert.throws(() => { + dns.lookup(false, options, common.mustNotCall()); + }, err); +} + +(async function() { + let res; + + res = await dnsPromises.lookup(false, { + hints: 0, + family: 0, + all: true + }); + assert.deepStrictEqual(res, []); + + res = await dnsPromises.lookup('127.0.0.1', { + hints: 0, + family: 4, + all: true + }); + assert.deepStrictEqual(res, [{ address: '127.0.0.1', family: 4 }]); + + res = await dnsPromises.lookup('127.0.0.1', { + hints: 0, + family: 4, + all: false + }); + assert.deepStrictEqual(res, { address: '127.0.0.1', family: 4 }); +})().then(common.mustCall()); + +dns.lookup(false, { + hints: 0, + family: 0, + all: true +}, common.mustSucceed((result, addressType) => { + assert.deepStrictEqual(result, []); + assert.strictEqual(addressType, undefined); +})); + +dns.lookup('127.0.0.1', { + hints: 0, + family: 4, + all: true +}, common.mustSucceed((result, addressType) => { + assert.deepStrictEqual(result, [{ + address: '127.0.0.1', + family: 4 + }]); + assert.strictEqual(addressType, undefined); +})); + +dns.lookup('127.0.0.1', { + hints: 0, + family: 4, + all: false +}, common.mustSucceed((result, addressType) => { + assert.deepStrictEqual(result, '127.0.0.1'); + assert.strictEqual(addressType, 4); +})); + +// let tickValue = 0; + +// Should fail due to stub. +// dns.lookup('example.com', common.mustCall((error, result, addressType) => { +// assert(error); +// assert.strictEqual(tickValue, 1); +// assert.strictEqual(error.code, 'ENOMEM'); +// const descriptor = Object.getOwnPropertyDescriptor(error, 'message'); +// // The error message should be non-enumerable. +// assert.strictEqual(descriptor.enumerable, false); +// })); + +// Make sure that the error callback is called on next tick. +// tickValue = 1; + +// Should fail due to stub. +// assert.rejects(dnsPromises.lookup('example.com'), +// { code: 'ENOMEM', hostname: 'example.com' }); diff --git a/cli/tests/node_compat/test/parallel/test-dns-memory-error.js b/cli/tests/node_compat/test/parallel/test-dns-memory-error.js new file mode 100644 index 00000000000000..7d524414d9937d --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-dns-memory-error.js @@ -0,0 +1,23 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +// Flags: --expose-internals +'use strict'; + +// Check that if libuv reports a memory error on a DNS query, that the memory +// error is passed through and not replaced with ENOTFOUND. + +require('../common'); + +const assert = require('assert'); +const errors = require('internal/errors'); +const { internalBinding } = require('internal/test/binding'); + +const { UV_EAI_MEMORY } = internalBinding('uv'); +const memoryError = errors.dnsException(UV_EAI_MEMORY, 'fhqwhgads'); + +assert.strictEqual(memoryError.code, 'EAI_MEMORY'); diff --git a/cli/tests/node_compat/test/parallel/test-dns-multi-channel.js b/cli/tests/node_compat/test/parallel/test-dns-multi-channel.js new file mode 100644 index 00000000000000..65d0a2f3a746e4 --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-dns-multi-channel.js @@ -0,0 +1,59 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +'use strict'; +const common = require('../common'); +const dnstools = require('../common/dns'); +const { Resolver } = require('dns'); +const assert = require('assert'); +const dgram = require('dgram'); + +const servers = [ + { + socket: dgram.createSocket('udp4'), + reply: { type: 'A', address: '1.2.3.4', ttl: 123, domain: 'example.org' } + }, + { + socket: dgram.createSocket('udp4'), + reply: { type: 'A', address: '5.6.7.8', ttl: 123, domain: 'example.org' } + }, +]; + +let waiting = servers.length; +for (const { socket, reply } of servers) { + socket.on('message', common.mustCall((msg, { address, port }) => { + const parsed = dnstools.parseDNSPacket(msg); + const domain = parsed.questions[0].domain; + assert.strictEqual(domain, 'example.org'); + + socket.send(dnstools.writeDNSPacket({ + id: parsed.id, + questions: parsed.questions, + answers: [reply], + }), port, address); + })); + + socket.bind(0, common.mustCall(() => { + if (--waiting === 0) ready(); + })); +} + + +function ready() { + const resolvers = servers.map((server) => ({ + server, + resolver: new Resolver() + })); + + for (const { server: { socket, reply }, resolver } of resolvers) { + resolver.setServers([`127.0.0.1:${socket.address().port}`]); + resolver.resolve4('example.org', common.mustSucceed((res) => { + assert.deepStrictEqual(res, [reply.address]); + socket.close(); + })); + } +} diff --git a/cli/tests/node_compat/test/parallel/test-dns-promises-exists.js b/cli/tests/node_compat/test/parallel/test-dns-promises-exists.js new file mode 100644 index 00000000000000..4800d0775a200c --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-dns-promises-exists.js @@ -0,0 +1,40 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +'use strict'; + +require('../common'); +const assert = require('assert'); +const dnsPromises = require('dns/promises'); +const dns = require('dns'); + +assert.strictEqual(dnsPromises, dns.promises); + +assert.strictEqual(dnsPromises.NODATA, dns.NODATA); +assert.strictEqual(dnsPromises.FORMERR, dns.FORMERR); +assert.strictEqual(dnsPromises.SERVFAIL, dns.SERVFAIL); +assert.strictEqual(dnsPromises.NOTFOUND, dns.NOTFOUND); +assert.strictEqual(dnsPromises.NOTIMP, dns.NOTIMP); +assert.strictEqual(dnsPromises.REFUSED, dns.REFUSED); +assert.strictEqual(dnsPromises.BADQUERY, dns.BADQUERY); +assert.strictEqual(dnsPromises.BADNAME, dns.BADNAME); +assert.strictEqual(dnsPromises.BADFAMILY, dns.BADFAMILY); +assert.strictEqual(dnsPromises.BADRESP, dns.BADRESP); +assert.strictEqual(dnsPromises.CONNREFUSED, dns.CONNREFUSED); +assert.strictEqual(dnsPromises.TIMEOUT, dns.TIMEOUT); +assert.strictEqual(dnsPromises.EOF, dns.EOF); +assert.strictEqual(dnsPromises.FILE, dns.FILE); +assert.strictEqual(dnsPromises.NOMEM, dns.NOMEM); +assert.strictEqual(dnsPromises.DESTRUCTION, dns.DESTRUCTION); +assert.strictEqual(dnsPromises.BADSTR, dns.BADSTR); +assert.strictEqual(dnsPromises.BADFLAGS, dns.BADFLAGS); +assert.strictEqual(dnsPromises.NONAME, dns.NONAME); +assert.strictEqual(dnsPromises.BADHINTS, dns.BADHINTS); +assert.strictEqual(dnsPromises.NOTINITIALIZED, dns.NOTINITIALIZED); +assert.strictEqual(dnsPromises.LOADIPHLPAPI, dns.LOADIPHLPAPI); +assert.strictEqual(dnsPromises.ADDRGETNETWORKPARAMS, dns.ADDRGETNETWORKPARAMS); +assert.strictEqual(dnsPromises.CANCELLED, dns.CANCELLED); diff --git a/cli/tests/node_compat/test/parallel/test-dns-resolveany.js b/cli/tests/node_compat/test/parallel/test-dns-resolveany.js new file mode 100644 index 00000000000000..56d533ad99af04 --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-dns-resolveany.js @@ -0,0 +1,78 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 16.13.0 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +// TODO: enable remaining tests once functionality is implemented. + +'use strict'; +const common = require('../common'); +const dnstools = require('../common/dns'); +const dns = require('dns'); +const assert = require('assert'); +const dgram = require('dgram'); +const dnsPromises = dns.promises; + +const answers = [ + { type: 'A', address: '1.2.3.4', /*ttl: 123*/ }, + { type: 'AAAA', address: '::42', /*ttl: 123*/ }, + { + type: 'CAA', + critical: 128, + issue: 'platynum.ch' + }, + { type: 'MX', priority: 42, exchange: 'foobar.com', ttl: 124 }, + { type: 'NS', value: 'foobar.org', ttl: 457 }, + { type: 'PTR', value: 'baz.org', ttl: 987 }, + { + type: 'SOA', + nsname: 'ns1.example.com', + hostmaster: 'admin.example.com', + serial: 156696742, + refresh: 900, + retry: 900, + expire: 1800, + minttl: 60 + }, + { type: 'TXT', entries: [ 'v=spf1 ~all', 'xyz\x00foo' ] }, +]; + +const server = dgram.createSocket('udp4'); + +server.on('message', common.mustCall((msg, { address, port }) => { + const parsed = dnstools.parseDNSPacket(msg); + const domain = parsed.questions[0].domain; + assert.strictEqual(domain, 'example.org'); + + server.send(dnstools.writeDNSPacket({ + id: parsed.id, + questions: parsed.questions, + answers: answers.map((answer) => Object.assign({ domain }, answer)), + }), port, address); +}, /*2*/ 30)); + +server.bind(0, common.mustCall(async () => { + const address = server.address(); + dns.setServers([`127.0.0.1:${address.port}`]); + + validateResults(await dnsPromises.resolveAny('example.org')); + + dns.resolveAny('example.org', common.mustSucceed((res) => { + validateResults(res); + server.close(); + })); +})); + +function validateResults(res) { + // TTL values are only provided for A and AAAA entries. + assert.deepStrictEqual(res.map(maybeRedactTTL), answers.map(maybeRedactTTL)); +} + +function maybeRedactTTL(r) { + const ret = { ...r }; + if (!['A', 'AAAA'].includes(r.type)) + delete ret.ttl; + return ret; +} diff --git a/cli/tests/node_compat/test/parallel/test-dns-resolvens-typeerror.js b/cli/tests/node_compat/test/parallel/test-dns-resolvens-typeerror.js new file mode 100644 index 00000000000000..c59fab974cbb99 --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-dns-resolvens-typeerror.js @@ -0,0 +1,62 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. + +'use strict'; +require('../common'); + +// This test ensures `dns.resolveNs()` does not raise a C++-land assertion error +// and throw a JavaScript TypeError instead. +// Issue https://github.com/nodejs/node-v0.x-archive/issues/7070 + +const assert = require('assert'); +const dns = require('dns'); +const dnsPromises = dns.promises; + +assert.throws( + () => dnsPromises.resolveNs([]), // bad name + { + code: 'ERR_INVALID_ARG_TYPE', + name: 'TypeError', + message: /^The "name" argument must be of type string/ + } +); +assert.throws( + () => dns.resolveNs([]), // bad name + { + code: 'ERR_INVALID_ARG_TYPE', + name: 'TypeError', + message: /^The "name" argument must be of type string/ + } +); +assert.throws( + () => dns.resolveNs(''), // bad callback + { + code: 'ERR_INVALID_ARG_TYPE', + name: 'TypeError' + } +); diff --git a/cli/tests/node_compat/test/parallel/test-dns-setservers-type-check.js b/cli/tests/node_compat/test/parallel/test-dns-setservers-type-check.js new file mode 100644 index 00000000000000..f318df39cc720d --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-dns-setservers-type-check.js @@ -0,0 +1,127 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +'use strict'; +const common = require('../common'); +const { addresses } = require('../common/internet'); +const assert = require('assert'); +const dns = require('dns'); +const resolver = new dns.promises.Resolver(); +const dnsPromises = dns.promises; +const promiseResolver = new dns.promises.Resolver(); + +{ + [ + null, + undefined, + Number(addresses.DNS4_SERVER), + addresses.DNS4_SERVER, + { + address: addresses.DNS4_SERVER + }, + ].forEach((val) => { + const errObj = { + code: 'ERR_INVALID_ARG_TYPE', + name: 'TypeError', + message: 'The "servers" argument must be an instance of Array.' + + common.invalidArgTypeHelper(val) + }; + assert.throws( + () => { + dns.setServers(val); + }, errObj + ); + assert.throws( + () => { + resolver.setServers(val); + }, errObj + ); + assert.throws( + () => { + dnsPromises.setServers(val); + }, errObj + ); + assert.throws( + () => { + promiseResolver.setServers(val); + }, errObj + ); + }); +} + +{ + [ + [null], + [undefined], + [Number(addresses.DNS4_SERVER)], + [ + { + address: addresses.DNS4_SERVER + }, + ], + ].forEach((val) => { + const errObj = { + code: 'ERR_INVALID_ARG_TYPE', + name: 'TypeError', + message: 'The "servers[0]" argument must be of type string.' + + common.invalidArgTypeHelper(val[0]) + }; + assert.throws( + () => { + dns.setServers(val); + }, errObj + ); + assert.throws( + () => { + resolver.setServers(val); + }, errObj + ); + assert.throws( + () => { + dnsPromises.setServers(val); + }, errObj + ); + assert.throws( + () => { + promiseResolver.setServers(val); + }, errObj + ); + }); +} + +// This test for 'dns/promises' +{ + const { + setServers + } = require('dns/promises'); + + // This should not throw any error. + (async () => { + setServers([ '127.0.0.1' ]); + })().then(common.mustCall()); + + [ + [null], + [undefined], + [Number(addresses.DNS4_SERVER)], + [ + { + address: addresses.DNS4_SERVER + }, + ], + ].forEach((val) => { + const errObj = { + code: 'ERR_INVALID_ARG_TYPE', + name: 'TypeError', + message: 'The "servers[0]" argument must be of type string.' + + common.invalidArgTypeHelper(val[0]) + }; + assert.throws(() => { + setServers(val); + }, errObj); + }); +} diff --git a/cli/tests/node_compat/test/parallel/test-dns.js b/cli/tests/node_compat/test/parallel/test-dns.js new file mode 100644 index 00000000000000..e56f7ca40a94cd --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-dns.js @@ -0,0 +1,471 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 16.13.0 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. + +'use strict'; + +// TODO: enable remaining tests once functionality is implemented. + +const common = require('../common'); +const dnstools = require('../common/dns'); +const assert = require('assert'); + +const dns = require('dns'); +const dnsPromises = dns.promises; +const dgram = require('dgram'); + +// TODO(cmorten): currently don't expose defaults +// const existing = dns.getServers(); +// assert(existing.length > 0); + +// Verify that setServers() handles arrays with holes and other oddities +{ + const servers = []; + + servers[0] = '127.0.0.1'; + servers[2] = '0.0.0.0'; + dns.setServers(servers); + + assert.deepStrictEqual(dns.getServers(), ['127.0.0.1', '0.0.0.0']); +} + +{ + const servers = ['127.0.0.1', '192.168.1.1']; + + servers[3] = '127.1.0.1'; + servers[4] = '127.1.0.1'; + servers[5] = '127.1.1.1'; + + Object.defineProperty(servers, 2, { + enumerable: true, + get: () => { + servers.length = 3; + return '0.0.0.0'; + } + }); + + dns.setServers(servers); + assert.deepStrictEqual(dns.getServers(), [ + '127.0.0.1', + '192.168.1.1', + '0.0.0.0', + ]); +} + +{ + // Various invalidities, all of which should throw a clean error. + const invalidServers = [ + ' ', + '\n', + '\0', + '1'.repeat(3 * 4), + // Check for REDOS issues. + ':'.repeat(100000), + '['.repeat(100000), + '['.repeat(100000) + ']'.repeat(100000) + 'a', + ]; + invalidServers.forEach((serv) => { + assert.throws( + () => { + dns.setServers([serv]); + }, + { + name: 'TypeError', + code: 'ERR_INVALID_IP_ADDRESS' + } + ); + }); +} + +const goog = [ + '8.8.8.8', + '8.8.4.4', +]; +dns.setServers(goog); +assert.deepStrictEqual(dns.getServers(), goog); +assert.throws(() => dns.setServers(['foobar']), { + code: 'ERR_INVALID_IP_ADDRESS', + name: 'TypeError', + message: 'Invalid IP address: foobar' +}); +assert.throws(() => dns.setServers(['127.0.0.1:va']), { + code: 'ERR_INVALID_IP_ADDRESS', + name: 'TypeError', + message: 'Invalid IP address: 127.0.0.1:va' +}); +assert.deepStrictEqual(dns.getServers(), goog); + +const goog6 = [ + '2001:4860:4860::8888', + '2001:4860:4860::8844', +]; +dns.setServers(goog6); +assert.deepStrictEqual(dns.getServers(), goog6); + +goog6.push('4.4.4.4'); +dns.setServers(goog6); +assert.deepStrictEqual(dns.getServers(), goog6); + +const ports = [ + '4.4.4.4:53', + '[2001:4860:4860::8888]:53', + '103.238.225.181:666', + '[fe80::483a:5aff:fee6:1f04]:666', + '[fe80::483a:5aff:fee6:1f04]', +]; +const portsExpected = [ + '4.4.4.4', + '2001:4860:4860::8888', + '103.238.225.181:666', + '[fe80::483a:5aff:fee6:1f04]:666', + 'fe80::483a:5aff:fee6:1f04', +]; +dns.setServers(ports); +assert.deepStrictEqual(dns.getServers(), portsExpected); + +dns.setServers([]); +assert.deepStrictEqual(dns.getServers(), []); + +{ + const errObj = { + code: 'ERR_INVALID_ARG_TYPE', + name: 'TypeError', + message: 'The "rrtype" argument must be of type string. ' + + 'Received an instance of Array' + }; + assert.throws(() => { + dns.resolve('example.com', [], common.mustNotCall()); + }, errObj); + assert.throws(() => { + dnsPromises.resolve('example.com', []); + }, errObj); +} +{ + const errObj = { + code: 'ERR_INVALID_ARG_TYPE', + name: 'TypeError', + message: 'The "name" argument must be of type string. ' + + 'Received undefined' + }; + assert.throws(() => { + dnsPromises.resolve(); + }, errObj); +} + +// dns.lookup should accept only falsey and string values +{ + const errorReg = { + code: 'ERR_INVALID_ARG_TYPE', + name: 'TypeError', + message: /^The "hostname" argument must be of type string\. Received .*/ + }; + + assert.throws(() => dns.lookup({}, common.mustNotCall()), errorReg); + + assert.throws(() => dns.lookup([], common.mustNotCall()), errorReg); + + assert.throws(() => dns.lookup(true, common.mustNotCall()), errorReg); + + assert.throws(() => dns.lookup(1, common.mustNotCall()), errorReg); + + assert.throws(() => dns.lookup(common.mustNotCall(), common.mustNotCall()), + errorReg); + + assert.throws(() => dnsPromises.lookup({}), errorReg); + assert.throws(() => dnsPromises.lookup([]), errorReg); + assert.throws(() => dnsPromises.lookup(true), errorReg); + assert.throws(() => dnsPromises.lookup(1), errorReg); + assert.throws(() => dnsPromises.lookup(common.mustNotCall()), errorReg); +} + +// dns.lookup should accept falsey values +{ + const checkCallback = (err, address, family) => { + assert.ifError(err); + assert.strictEqual(address, null); + assert.strictEqual(family, 4); + }; + + ['', null, undefined, 0, NaN].forEach(async (value) => { + const res = await dnsPromises.lookup(value); + assert.deepStrictEqual(res, { address: null, family: 4 }); + dns.lookup(value, common.mustCall(checkCallback)); + }); +} + +{ + // Make sure that dns.lookup throws if hints does not represent a valid flag. + // (dns.V4MAPPED | dns.ADDRCONFIG | dns.ALL) + 1 is invalid because: + // - it's different from dns.V4MAPPED and dns.ADDRCONFIG and dns.ALL. + // - it's different from any subset of them bitwise ored. + // - it's different from 0. + // - it's an odd number different than 1, and thus is invalid, because + // flags are either === 1 or even. + const hints = (dns.V4MAPPED | dns.ADDRCONFIG | dns.ALL) + 1; + const err = { + code: 'ERR_INVALID_ARG_VALUE', + name: 'TypeError', + message: /The argument 'hints' is invalid\. Received \d+/ + }; + + assert.throws(() => { + dnsPromises.lookup('nodejs.org', { hints }); + }, err); + assert.throws(() => { + dns.lookup('nodejs.org', { hints }, common.mustNotCall()); + }, err); +} + +assert.throws(() => dns.lookup("nodejs.org"), { + code: "ERR_INVALID_ARG_TYPE", + name: "TypeError", +}); + +assert.throws(() => dns.lookup("nodejs.org", 4), { + code: "ERR_INVALID_ARG_TYPE", + name: "TypeError", +}); + +dns.lookup('', { family: 4, hints: 0 }, common.mustCall()); + +dns.lookup('', { + family: 6, + hints: dns.ADDRCONFIG +}, common.mustCall()); + +dns.lookup('', { hints: dns.V4MAPPED }, common.mustCall()); + +dns.lookup('', { + hints: dns.ADDRCONFIG | dns.V4MAPPED +}, common.mustCall()); + +dns.lookup('', { + hints: dns.ALL +}, common.mustCall()); + +dns.lookup('', { + hints: dns.V4MAPPED | dns.ALL +}, common.mustCall()); + +dns.lookup('', { + hints: dns.ADDRCONFIG | dns.V4MAPPED | dns.ALL +}, common.mustCall()); + +(async function() { + await dnsPromises.lookup('', { family: 4, hints: 0 }); + await dnsPromises.lookup('', { family: 6, hints: dns.ADDRCONFIG }); + await dnsPromises.lookup('', { hints: dns.V4MAPPED }); + await dnsPromises.lookup('', { hints: dns.ADDRCONFIG | dns.V4MAPPED }); + await dnsPromises.lookup('', { hints: dns.ALL }); + await dnsPromises.lookup('', { hints: dns.V4MAPPED | dns.ALL }); + await dnsPromises.lookup('', { + hints: dns.ADDRCONFIG | dns.V4MAPPED | dns.ALL + }); +})().then(common.mustCall()); + +// { +// const err = { +// code: 'ERR_MISSING_ARGS', +// name: 'TypeError', +// message: 'The "address", "port", and "callback" arguments must be ' + +// 'specified' +// }; + +// assert.throws(() => dns.lookupService('0.0.0.0'), err); +// err.message = 'The "address" and "port" arguments must be specified'; +// assert.throws(() => dnsPromises.lookupService('0.0.0.0'), err); +// } + +// { +// const invalidAddress = 'fasdfdsaf'; +// const err = { +// code: 'ERR_INVALID_ARG_VALUE', +// name: 'TypeError', +// message: `The argument 'address' is invalid. Received '${invalidAddress}'` +// }; + +// assert.throws(() => { +// dnsPromises.lookupService(invalidAddress, 0); +// }, err); + +// assert.throws(() => { +// dns.lookupService(invalidAddress, 0, common.mustNotCall()); +// }, err); +// } + +// const portErr = (port) => { +// const err = { +// code: 'ERR_SOCKET_BAD_PORT', +// message: +// `Port should be >= 0 and < 65536. Received ${port}.`, +// name: 'RangeError' +// }; + +// assert.throws(() => { +// dnsPromises.lookupService('0.0.0.0', port); +// }, err); + +// assert.throws(() => { +// dns.lookupService('0.0.0.0', port, common.mustNotCall()); +// }, err); +// }; +// portErr(null); +// portErr(undefined); +// portErr(65538); +// portErr('test'); + +// assert.throws(() => { +// dns.lookupService('0.0.0.0', 80, null); +// }, { +// code: 'ERR_INVALID_ARG_TYPE', +// name: 'TypeError' +// }); + +{ + dns.resolveMx('foo.onion', function(err) { + assert.deepStrictEqual(err.code, 'ENOTFOUND'); + assert.deepStrictEqual(err.syscall, 'queryMx'); + assert.deepStrictEqual(err.hostname, 'foo.onion'); + assert.deepStrictEqual(err.message, 'queryMx ENOTFOUND foo.onion'); + }); +} + +{ + const cases = [ + { + method: "resolveAny", + answers: [ + { type: "A", address: "1.2.3.4" /*ttl: 3333333333*/ }, + { type: "AAAA", address: "::42" /*ttl: 3333333333*/ }, + { type: "MX", priority: 42, exchange: "foobar.com", ttl: 3333333333 }, + { type: "NS", value: "foobar.org", ttl: 3333333333 }, + { type: "PTR", value: "baz.org", ttl: 3333333333 }, + { + type: "SOA", + nsname: "ns1.example.com", + hostmaster: "admin.example.com", + serial: 3210987654, + refresh: 900, + retry: 900, + expire: 1800, + minttl: 3333333333, + }, + ], + }, + + // TODO(cmorten): support ttl option + // { + // method: "resolve4", + // options: { ttl: true }, + // answers: [{ type: "A", address: "1.2.3.4", ttl: 3333333333 }], + // }, + + // { + // method: "resolve6", + // options: { ttl: true }, + // answers: [{ type: "AAAA", address: "::42", ttl: 3333333333 }], + // }, + + { + method: "resolveSoa", + answers: [ + { + type: "SOA", + nsname: "ns1.example.com", + hostmaster: "admin.example.com", + serial: 3210987654, + refresh: 900, + retry: 900, + expire: 1800, + minttl: 3333333333, + }, + ], + }, + ]; + + const server = dgram.createSocket('udp4'); + + server.on('message', common.mustCall((msg, { address, port }) => { + const parsed = dnstools.parseDNSPacket(msg); + const domain = parsed.questions[0].domain; + assert.strictEqual(domain, 'example.org'); + + server.send(dnstools.writeDNSPacket({ + id: parsed.id, + questions: parsed.questions, + answers: cases[0].answers.map( + (answer) => Object.assign({ domain }, answer) + ), + }), port, address); + // Don't have "ANY" query type available so calls greatly increased with + // polyfill method. + }, /*cases.length * 2*/ 32)); + + server.bind(0, common.mustCall(() => { + const address = server.address(); + dns.setServers([`127.0.0.1:${address.port}`]); + + function validateResults(res) { + if (!Array.isArray(res)) + res = [res]; + + assert.deepStrictEqual(res.map(tweakEntry), + cases[0].answers.map(tweakEntry)); + } + + function tweakEntry(r) { + const ret = { ...r }; + + const { method } = cases[0]; + + // TTL values are only provided for A and AAAA entries. + if (!['A', 'AAAA'].includes(ret.type) && !/^resolve(4|6)?$/.test(method)) + delete ret.ttl; + + if (method !== 'resolveAny') + delete ret.type; + + return ret; + } + + (async function nextCase() { + if (cases.length === 0) + return server.close(); + + const { method, options } = cases[0]; + + validateResults(await dnsPromises[method]('example.org', options)); + + dns[method]('example.org', options, common.mustSucceed((res) => { + validateResults(res); + cases.shift(); + nextCase(); + })); + })().then(common.mustCall()); + + })); +} diff --git a/cli/tests/node_compat/test/parallel/test-eval-strict-referenceerror.js b/cli/tests/node_compat/test/parallel/test-eval-strict-referenceerror.js new file mode 100644 index 00000000000000..f63fb2840287e5 --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-eval-strict-referenceerror.js @@ -0,0 +1,34 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +/* eslint-disable strict */ +require('../common'); + +// In Node.js 0.10, a bug existed that caused strict functions to not capture +// their environment when evaluated. When run in 0.10 `test()` fails with a +// `ReferenceError`. See https://github.com/nodejs/node/issues/2245 for details. + +const assert = require('assert'); + +function test() { + + const code = [ + 'var foo = {m: 1};', + '', + 'function bar() {', + '\'use strict\';', + 'return foo; // foo isn\'t captured in 0.10', + '};', + ].join('\n'); + + eval(code); + + return bar(); // eslint-disable-line no-undef + +} + +assert.deepStrictEqual(test(), { m: 1 }); diff --git a/cli/tests/node_compat/test/parallel/test-eval.js b/cli/tests/node_compat/test/parallel/test-eval.js new file mode 100644 index 00000000000000..22738c0970d712 --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-eval.js @@ -0,0 +1,14 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +'use strict'; + +require('../common'); +const assert = require('assert'); + +// Verify that eval is allowed by default. +assert.strictEqual(eval('"eval"'), 'eval'); diff --git a/cli/tests/node_compat/test/parallel/test-event-emitter-add-listeners.js b/cli/tests/node_compat/test/parallel/test-event-emitter-add-listeners.js new file mode 100644 index 00000000000000..28853cff224b4b --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-event-emitter-add-listeners.js @@ -0,0 +1,93 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. + +'use strict'; +const common = require('../common'); +const assert = require('assert'); +const EventEmitter = require('events'); + +{ + const ee = new EventEmitter(); + const events_new_listener_emitted = []; + const listeners_new_listener_emitted = []; + + // Sanity check + assert.strictEqual(ee.addListener, ee.on); + + ee.on('newListener', function(event, listener) { + // Don't track newListener listeners. + if (event === 'newListener') + return; + + events_new_listener_emitted.push(event); + listeners_new_listener_emitted.push(listener); + }); + + const hello = common.mustCall(function(a, b) { + assert.strictEqual(a, 'a'); + assert.strictEqual(b, 'b'); + }); + + ee.once('newListener', function(name, listener) { + assert.strictEqual(name, 'hello'); + assert.strictEqual(listener, hello); + assert.deepStrictEqual(this.listeners('hello'), []); + }); + + ee.on('hello', hello); + ee.once('foo', assert.fail); + assert.deepStrictEqual(['hello', 'foo'], events_new_listener_emitted); + assert.deepStrictEqual([hello, assert.fail], listeners_new_listener_emitted); + + ee.emit('hello', 'a', 'b'); +} + +// Just make sure that this doesn't throw: +{ + const f = new EventEmitter(); + + f.setMaxListeners(0); +} + +{ + const listen1 = () => {}; + const listen2 = () => {}; + const ee = new EventEmitter(); + + ee.once('newListener', function() { + assert.deepStrictEqual(ee.listeners('hello'), []); + ee.once('newListener', function() { + assert.deepStrictEqual(ee.listeners('hello'), []); + }); + ee.on('hello', listen2); + }); + ee.on('hello', listen1); + // The order of listeners on an event is not always the order in which the + // listeners were added. + assert.deepStrictEqual(ee.listeners('hello'), [listen2, listen1]); +} diff --git a/cli/tests/node_compat/test/parallel/test-event-emitter-check-listener-leaks.js b/cli/tests/node_compat/test/parallel/test-event-emitter-check-listener-leaks.js new file mode 100644 index 00000000000000..977667abda3034 --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-event-emitter-check-listener-leaks.js @@ -0,0 +1,110 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. + +'use strict'; + +const common = require('../common'); +const assert = require('assert'); +const events = require('events'); + +// default +{ + const e = new events.EventEmitter(); + + for (let i = 0; i < 10; i++) { + e.on('default', common.mustNotCall()); + } + assert.ok(!Object.hasOwn(e._events.default, 'warned')); + e.on('default', common.mustNotCall()); + assert.ok(e._events.default.warned); + + // symbol + const symbol = Symbol('symbol'); + e.setMaxListeners(1); + e.on(symbol, common.mustNotCall()); + assert.ok(!Object.hasOwn(e._events[symbol], 'warned')); + e.on(symbol, common.mustNotCall()); + assert.ok(Object.hasOwn(e._events[symbol], 'warned')); + + // specific + e.setMaxListeners(5); + for (let i = 0; i < 5; i++) { + e.on('specific', common.mustNotCall()); + } + assert.ok(!Object.hasOwn(e._events.specific, 'warned')); + e.on('specific', common.mustNotCall()); + assert.ok(e._events.specific.warned); + + // only one + e.setMaxListeners(1); + e.on('only one', common.mustNotCall()); + assert.ok(!Object.hasOwn(e._events['only one'], 'warned')); + e.on('only one', common.mustNotCall()); + assert.ok(Object.hasOwn(e._events['only one'], 'warned')); + + // unlimited + e.setMaxListeners(0); + for (let i = 0; i < 1000; i++) { + e.on('unlimited', common.mustNotCall()); + } + assert.ok(!Object.hasOwn(e._events.unlimited, 'warned')); +} + +// process-wide +{ + events.EventEmitter.defaultMaxListeners = 42; + const e = new events.EventEmitter(); + + for (let i = 0; i < 42; ++i) { + e.on('fortytwo', common.mustNotCall()); + } + assert.ok(!Object.hasOwn(e._events.fortytwo, 'warned')); + e.on('fortytwo', common.mustNotCall()); + assert.ok(Object.hasOwn(e._events.fortytwo, 'warned')); + delete e._events.fortytwo.warned; + + events.EventEmitter.defaultMaxListeners = 44; + e.on('fortytwo', common.mustNotCall()); + assert.ok(!Object.hasOwn(e._events.fortytwo, 'warned')); + e.on('fortytwo', common.mustNotCall()); + assert.ok(Object.hasOwn(e._events.fortytwo, 'warned')); +} + +// But _maxListeners still has precedence over defaultMaxListeners +{ + events.EventEmitter.defaultMaxListeners = 42; + const e = new events.EventEmitter(); + e.setMaxListeners(1); + e.on('uno', common.mustNotCall()); + assert.ok(!Object.hasOwn(e._events.uno, 'warned')); + e.on('uno', common.mustNotCall()); + assert.ok(Object.hasOwn(e._events.uno, 'warned')); + + // chainable + assert.strictEqual(e, e.setMaxListeners(1)); +} diff --git a/cli/tests/node_compat/test/parallel/test-event-emitter-emit-context.js b/cli/tests/node_compat/test/parallel/test-event-emitter-emit-context.js new file mode 100644 index 00000000000000..b18c3d2ed0a9d1 --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-event-emitter-emit-context.js @@ -0,0 +1,25 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +'use strict'; +const common = require('../common'); +const assert = require('assert'); +const EventEmitter = require('events'); + +// Test emit called by other context +const EE = new EventEmitter(); + +// Works as expected if the context has no `constructor.name` +{ + const ctx = Object.create(null); + assert.throws( + () => EE.emit.call(ctx, 'error', new Error('foo')), + common.expectsError({ name: 'Error', message: 'foo' }) + ); +} + +assert.strictEqual(EE.emit.call({}, 'foo'), false); diff --git a/cli/tests/node_compat/test/parallel/test-event-emitter-error-monitor.js b/cli/tests/node_compat/test/parallel/test-event-emitter-error-monitor.js new file mode 100644 index 00000000000000..d708393d9aa9c9 --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-event-emitter-error-monitor.js @@ -0,0 +1,39 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +'use strict'; +const common = require('../common'); +const assert = require('assert'); +const EventEmitter = require('events'); + +const EE = new EventEmitter(); +const theErr = new Error('MyError'); + +EE.on( + EventEmitter.errorMonitor, + common.mustCall(function onErrorMonitor(e) { + assert.strictEqual(e, theErr); + }, 3) +); + +// Verify with no error listener +assert.throws( + () => EE.emit('error', theErr), theErr +); + +// Verify with error listener +EE.once('error', common.mustCall((e) => assert.strictEqual(e, theErr))); +EE.emit('error', theErr); + + +// Verify it works with once +process.nextTick(() => EE.emit('error', theErr)); +assert.rejects(EventEmitter.once(EE, 'notTriggered'), theErr); + +// Only error events trigger error monitor +EE.on('aEvent', common.mustCall()); +EE.emit('aEvent'); diff --git a/cli/tests/node_compat/test/parallel/test-event-emitter-errors.js b/cli/tests/node_compat/test/parallel/test-event-emitter-errors.js new file mode 100644 index 00000000000000..8ab863f893e498 --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-event-emitter-errors.js @@ -0,0 +1,44 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +'use strict'; +require('../common'); +const assert = require('assert'); +const EventEmitter = require('events'); +const util = require('util'); + +const EE = new EventEmitter(); + +assert.throws( + () => EE.emit('error', 'Accepts a string'), + { + code: 'ERR_UNHANDLED_ERROR', + name: 'Error', + message: "Unhandled error. ('Accepts a string')" + } +); + +assert.throws( + () => EE.emit('error', { message: 'Error!' }), + { + code: 'ERR_UNHANDLED_ERROR', + name: 'Error', + message: "Unhandled error. ({ message: 'Error!' })" + } +); + +assert.throws( + () => EE.emit('error', { + message: 'Error!', + [util.inspect.custom]() { throw new Error(); } + }), + { + code: 'ERR_UNHANDLED_ERROR', + name: 'Error', + message: 'Unhandled error. ([object Object])' + } +); diff --git a/cli/tests/node_compat/test/parallel/test-event-emitter-get-max-listeners.js b/cli/tests/node_compat/test/parallel/test-event-emitter-get-max-listeners.js new file mode 100644 index 00000000000000..7af05126a89e06 --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-event-emitter-get-max-listeners.js @@ -0,0 +1,26 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +'use strict'; +require('../common'); +const assert = require('assert'); +const EventEmitter = require('events'); + +const emitter = new EventEmitter(); + +assert.strictEqual(emitter.getMaxListeners(), EventEmitter.defaultMaxListeners); + +emitter.setMaxListeners(0); +assert.strictEqual(emitter.getMaxListeners(), 0); + +emitter.setMaxListeners(3); +assert.strictEqual(emitter.getMaxListeners(), 3); + +// https://github.com/nodejs/node/issues/523 - second call should not throw. +const recv = {}; +EventEmitter.prototype.on.call(recv, 'event', () => {}); +EventEmitter.prototype.on.call(recv, 'event', () => {}); diff --git a/cli/tests/node_compat/test/parallel/test-event-emitter-invalid-listener.js b/cli/tests/node_compat/test/parallel/test-event-emitter-invalid-listener.js new file mode 100644 index 00000000000000..c5c948c625b686 --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-event-emitter-invalid-listener.js @@ -0,0 +1,27 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +'use strict'; + +require('../common'); +const assert = require('assert'); +const EventEmitter = require('events'); + +const eventsMethods = ['on', 'once', 'removeListener', 'prependOnceListener']; + +// Verify that the listener must be a function for events methods +for (const method of eventsMethods) { + assert.throws(() => { + const ee = new EventEmitter(); + ee[method]('foo', null); + }, { + code: 'ERR_INVALID_ARG_TYPE', + name: 'TypeError', + message: 'The "listener" argument must be of type function. ' + + 'Received null' + }, `event.${method}('foo', null) should throw the proper error`); +} diff --git a/cli/tests/node_compat/test/parallel/test-event-emitter-listener-count.js b/cli/tests/node_compat/test/parallel/test-event-emitter-listener-count.js new file mode 100644 index 00000000000000..17aa9e58aac9cb --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-event-emitter-listener-count.js @@ -0,0 +1,25 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +'use strict'; + +require('../common'); +const assert = require('assert'); +const EventEmitter = require('events'); + +const emitter = new EventEmitter(); +emitter.on('foo', () => {}); +emitter.on('foo', () => {}); +emitter.on('baz', () => {}); +// Allow any type +emitter.on(123, () => {}); + +assert.strictEqual(EventEmitter.listenerCount(emitter, 'foo'), 2); +assert.strictEqual(emitter.listenerCount('foo'), 2); +assert.strictEqual(emitter.listenerCount('bar'), 0); +assert.strictEqual(emitter.listenerCount('baz'), 1); +assert.strictEqual(emitter.listenerCount(123), 1); diff --git a/cli/tests/node_compat/test/parallel/test-event-emitter-listeners-side-effects.js b/cli/tests/node_compat/test/parallel/test-event-emitter-listeners-side-effects.js new file mode 100644 index 00000000000000..d6b1b5ac3583bf --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-event-emitter-listeners-side-effects.js @@ -0,0 +1,67 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. + +'use strict'; + +require('../common'); +const assert = require('assert'); + +const EventEmitter = require('events').EventEmitter; + +const e = new EventEmitter(); +let fl; // foo listeners + +fl = e.listeners('foo'); +assert(Array.isArray(fl)); +assert.strictEqual(fl.length, 0); +assert(!(e._events instanceof Object)); +assert.deepStrictEqual(Object.keys(e._events), []); + +e.on('foo', assert.fail); +fl = e.listeners('foo'); +assert.strictEqual(e._events.foo, assert.fail); +assert(Array.isArray(fl)); +assert.strictEqual(fl.length, 1); +assert.strictEqual(fl[0], assert.fail); + +e.listeners('bar'); + +e.on('foo', assert.ok); +fl = e.listeners('foo'); + +assert(Array.isArray(e._events.foo)); +assert.strictEqual(e._events.foo.length, 2); +assert.strictEqual(e._events.foo[0], assert.fail); +assert.strictEqual(e._events.foo[1], assert.ok); + +assert(Array.isArray(fl)); +assert.strictEqual(fl.length, 2); +assert.strictEqual(fl[0], assert.fail); +assert.strictEqual(fl[1], assert.ok); + +console.log('ok'); diff --git a/cli/tests/node_compat/test/parallel/test-event-emitter-listeners.js b/cli/tests/node_compat/test/parallel/test-event-emitter-listeners.js new file mode 100644 index 00000000000000..9ddc4cde39d0a3 --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-event-emitter-listeners.js @@ -0,0 +1,131 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. + +'use strict'; + +require('../common'); +const assert = require('assert'); +const events = require('events'); + +function listener() {} + +function listener2() {} + +function listener3() { + return 0; +} + +function listener4() { + return 1; +} + +{ + const ee = new events.EventEmitter(); + ee.on('foo', listener); + const fooListeners = ee.listeners('foo'); + assert.deepStrictEqual(ee.listeners('foo'), [listener]); + ee.removeAllListeners('foo'); + assert.deepStrictEqual(ee.listeners('foo'), []); + assert.deepStrictEqual(fooListeners, [listener]); +} + +{ + const ee = new events.EventEmitter(); + ee.on('foo', listener); + const eeListenersCopy = ee.listeners('foo'); + assert.deepStrictEqual(eeListenersCopy, [listener]); + assert.deepStrictEqual(ee.listeners('foo'), [listener]); + eeListenersCopy.push(listener2); + assert.deepStrictEqual(ee.listeners('foo'), [listener]); + assert.deepStrictEqual(eeListenersCopy, [listener, listener2]); +} + +{ + const ee = new events.EventEmitter(); + ee.on('foo', listener); + const eeListenersCopy = ee.listeners('foo'); + ee.on('foo', listener2); + assert.deepStrictEqual(ee.listeners('foo'), [listener, listener2]); + assert.deepStrictEqual(eeListenersCopy, [listener]); +} + +{ + const ee = new events.EventEmitter(); + ee.once('foo', listener); + assert.deepStrictEqual(ee.listeners('foo'), [listener]); +} + +{ + const ee = new events.EventEmitter(); + ee.on('foo', listener); + ee.once('foo', listener2); + assert.deepStrictEqual(ee.listeners('foo'), [listener, listener2]); +} + +{ + const ee = new events.EventEmitter(); + ee._events = undefined; + assert.deepStrictEqual(ee.listeners('foo'), []); +} + +{ + class TestStream extends events.EventEmitter {} + const s = new TestStream(); + assert.deepStrictEqual(s.listeners('foo'), []); +} + +{ + const ee = new events.EventEmitter(); + ee.on('foo', listener); + const wrappedListener = ee.rawListeners('foo'); + assert.strictEqual(wrappedListener.length, 1); + assert.strictEqual(wrappedListener[0], listener); + assert.notStrictEqual(wrappedListener, ee.rawListeners('foo')); + ee.once('foo', listener); + const wrappedListeners = ee.rawListeners('foo'); + assert.strictEqual(wrappedListeners.length, 2); + assert.strictEqual(wrappedListeners[0], listener); + assert.notStrictEqual(wrappedListeners[1], listener); + assert.strictEqual(wrappedListeners[1].listener, listener); + assert.notStrictEqual(wrappedListeners, ee.rawListeners('foo')); + ee.emit('foo'); + assert.strictEqual(wrappedListeners.length, 2); + assert.strictEqual(wrappedListeners[1].listener, listener); +} + +{ + const ee = new events.EventEmitter(); + ee.once('foo', listener3); + ee.on('foo', listener4); + const rawListeners = ee.rawListeners('foo'); + assert.strictEqual(rawListeners.length, 2); + assert.strictEqual(rawListeners[0](), 0); + const rawListener = ee.rawListeners('foo'); + assert.strictEqual(rawListener.length, 1); + assert.strictEqual(rawListener[0](), 1); +} diff --git a/cli/tests/node_compat/test/parallel/test-event-emitter-max-listeners-warning-for-null.js b/cli/tests/node_compat/test/parallel/test-event-emitter-max-listeners-warning-for-null.js new file mode 100644 index 00000000000000..0a17f5e719088e --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-event-emitter-max-listeners-warning-for-null.js @@ -0,0 +1,30 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +// Flags: --no-warnings +// The flag suppresses stderr output but the warning event will still emit +'use strict'; + +const common = require('../common'); +const events = require('events'); +const assert = require('assert'); + +const e = new events.EventEmitter(); +e.setMaxListeners(1); + +process.on('warning', common.mustCall((warning) => { + assert.ok(warning instanceof Error); + assert.strictEqual(warning.name, 'MaxListenersExceededWarning'); + assert.strictEqual(warning.emitter, e); + assert.strictEqual(warning.count, 2); + assert.strictEqual(warning.type, null); + assert.ok(warning.message.includes( + '2 null listeners added to [EventEmitter].')); +})); + +e.on(null, () => {}); +e.on(null, () => {}); diff --git a/cli/tests/node_compat/test/parallel/test-event-emitter-max-listeners-warning-for-symbol.js b/cli/tests/node_compat/test/parallel/test-event-emitter-max-listeners-warning-for-symbol.js new file mode 100644 index 00000000000000..2bc5832e5e8790 --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-event-emitter-max-listeners-warning-for-symbol.js @@ -0,0 +1,32 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +// Flags: --no-warnings +// The flag suppresses stderr output but the warning event will still emit +'use strict'; + +const common = require('../common'); +const events = require('events'); +const assert = require('assert'); + +const symbol = Symbol('symbol'); + +const e = new events.EventEmitter(); +e.setMaxListeners(1); + +process.on('warning', common.mustCall((warning) => { + assert.ok(warning instanceof Error); + assert.strictEqual(warning.name, 'MaxListenersExceededWarning'); + assert.strictEqual(warning.emitter, e); + assert.strictEqual(warning.count, 2); + assert.strictEqual(warning.type, symbol); + assert.ok(warning.message.includes( + '2 Symbol(symbol) listeners added to [EventEmitter].')); +})); + +e.on(symbol, () => {}); +e.on(symbol, () => {}); diff --git a/cli/tests/node_compat/test/parallel/test-event-emitter-max-listeners-warning.js b/cli/tests/node_compat/test/parallel/test-event-emitter-max-listeners-warning.js new file mode 100644 index 00000000000000..f1a391402fef3b --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-event-emitter-max-listeners-warning.js @@ -0,0 +1,38 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +// Flags: --no-warnings +// The flag suppresses stderr output but the warning event will still emit +'use strict'; + +const common = require('../common'); +const events = require('events'); +const assert = require('assert'); + +class FakeInput extends events.EventEmitter { + resume() {} + pause() {} + write() {} + end() {} +} + +const e = new FakeInput(); +e.setMaxListeners(1); + +process.on('warning', common.mustCall((warning) => { + assert.ok(warning instanceof Error); + assert.strictEqual(warning.name, 'MaxListenersExceededWarning'); + assert.strictEqual(warning.emitter, e); + assert.strictEqual(warning.count, 2); + assert.strictEqual(warning.type, 'event-type'); + assert.ok(warning.message.includes( + '2 event-type listeners added to [FakeInput].')); +})); + +e.on('event-type', () => {}); +e.on('event-type', () => {}); // Trigger warning. +e.on('event-type', () => {}); // Verify that warning is emitted only once. diff --git a/cli/tests/node_compat/test/parallel/test-event-emitter-max-listeners.js b/cli/tests/node_compat/test/parallel/test-event-emitter-max-listeners.js new file mode 100644 index 00000000000000..1245c6b9285fb9 --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-event-emitter-max-listeners.js @@ -0,0 +1,80 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 16.13.0 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. + +'use strict'; +const common = require('../common'); +const assert = require('assert'); +const events = require('events'); +const { inspect } = require('util'); +const e = new events.EventEmitter(); + +e.on('maxListeners', common.mustCall()); + +// Should not corrupt the 'maxListeners' queue. +e.setMaxListeners(42); + +const throwsObjs = [NaN, -1, 'and even this']; + +for (const obj of throwsObjs) { + assert.throws( + () => e.setMaxListeners(obj), + { + code: 'ERR_OUT_OF_RANGE', + name: 'RangeError', + message: 'The value of "n" is out of range. ' + + `It must be a non-negative number. Received ${inspect(obj)}` + } + ); + + assert.throws( + () => events.defaultMaxListeners = obj, + { + code: 'ERR_OUT_OF_RANGE', + name: 'RangeError', + message: 'The value of "defaultMaxListeners" is out of range. ' + + `It must be a non-negative number. Received ${inspect(obj)}` + } + ); +} + +e.emit('maxListeners'); + +{ + const { EventEmitter, defaultMaxListeners } = events; + for (const obj of throwsObjs) { + assert.throws(() => EventEmitter.setMaxListeners(obj), { + code: 'ERR_OUT_OF_RANGE', + }); + } + + // FIXME(bartlomieju): + // assert.throws( + // () => EventEmitter.setMaxListeners(defaultMaxListeners, 'INVALID_EMITTER'), + // { code: 'ERR_INVALID_ARG_TYPE' } + // ); +} diff --git a/cli/tests/node_compat/test/parallel/test-event-emitter-method-names.js b/cli/tests/node_compat/test/parallel/test-event-emitter-method-names.js new file mode 100644 index 00000000000000..45da20b9a4dc0e --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-event-emitter-method-names.js @@ -0,0 +1,42 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. + +'use strict'; +require('../common'); +const assert = require('assert'); +const events = require('events'); + +const E = events.EventEmitter.prototype; +assert.strictEqual(E.constructor.name, 'EventEmitter'); +assert.strictEqual(E.on, E.addListener); // Same method. +assert.strictEqual(E.off, E.removeListener); // Same method. +Object.getOwnPropertyNames(E).forEach(function(name) { + if (name === 'constructor' || name === 'on' || name === 'off') return; + if (typeof E[name] !== 'function') return; + assert.strictEqual(E[name].name, name); +}); diff --git a/cli/tests/node_compat/test/parallel/test-event-emitter-modify-in-emit.js b/cli/tests/node_compat/test/parallel/test-event-emitter-modify-in-emit.js new file mode 100644 index 00000000000000..ac1fe667b59250 --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-event-emitter-modify-in-emit.js @@ -0,0 +1,87 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. + +'use strict'; +require('../common'); +const assert = require('assert'); +const events = require('events'); + +let callbacks_called = []; + +const e = new events.EventEmitter(); + +function callback1() { + callbacks_called.push('callback1'); + e.on('foo', callback2); + e.on('foo', callback3); + e.removeListener('foo', callback1); +} + +function callback2() { + callbacks_called.push('callback2'); + e.removeListener('foo', callback2); +} + +function callback3() { + callbacks_called.push('callback3'); + e.removeListener('foo', callback3); +} + +e.on('foo', callback1); +assert.strictEqual(e.listeners('foo').length, 1); + +e.emit('foo'); +assert.strictEqual(e.listeners('foo').length, 2); +assert.deepStrictEqual(['callback1'], callbacks_called); + +e.emit('foo'); +assert.strictEqual(e.listeners('foo').length, 0); +assert.deepStrictEqual(['callback1', 'callback2', 'callback3'], + callbacks_called); + +e.emit('foo'); +assert.strictEqual(e.listeners('foo').length, 0); +assert.deepStrictEqual(['callback1', 'callback2', 'callback3'], + callbacks_called); + +e.on('foo', callback1); +e.on('foo', callback2); +assert.strictEqual(e.listeners('foo').length, 2); +e.removeAllListeners('foo'); +assert.strictEqual(e.listeners('foo').length, 0); + +// Verify that removing callbacks while in emit allows emits to propagate to +// all listeners +callbacks_called = []; + +e.on('foo', callback2); +e.on('foo', callback3); +assert.strictEqual(e.listeners('foo').length, 2); +e.emit('foo'); +assert.deepStrictEqual(['callback2', 'callback3'], callbacks_called); +assert.strictEqual(e.listeners('foo').length, 0); diff --git a/cli/tests/node_compat/test/parallel/test-event-emitter-no-error-provided-to-error-event.js b/cli/tests/node_compat/test/parallel/test-event-emitter-no-error-provided-to-error-event.js new file mode 100644 index 00000000000000..8ab7aec44e8f6a --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-event-emitter-no-error-provided-to-error-event.js @@ -0,0 +1,65 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 16.13.0 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. + +'use strict'; +const common = require('../common'); +const assert = require('assert'); +const events = require('events'); +/* TODO(uki00a): Uncomment this block when the 'domain' module is implemented. +const domain = require('domain'); + +{ + const e = new events.EventEmitter(); + const d = domain.create(); + d.add(e); + d.on('error', common.mustCall((er) => { + assert(er instanceof Error, 'error created'); + })); + e.emit('error'); +} + +for (const arg of [false, null, undefined]) { + const e = new events.EventEmitter(); + const d = domain.create(); + d.add(e); + d.on('error', common.mustCall((er) => { + assert(er instanceof Error, 'error created'); + })); + e.emit('error', arg); +} + +for (const arg of [42, 'fortytwo', true]) { + const e = new events.EventEmitter(); + const d = domain.create(); + d.add(e); + d.on('error', common.mustCall((er) => { + assert.strictEqual(er, arg); + })); + e.emit('error', arg); +} +*/ diff --git a/cli/tests/node_compat/test/parallel/test-event-emitter-num-args.js b/cli/tests/node_compat/test/parallel/test-event-emitter-num-args.js new file mode 100644 index 00000000000000..af8f264946c848 --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-event-emitter-num-args.js @@ -0,0 +1,61 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. + +'use strict'; +require('../common'); +const assert = require('assert'); +const events = require('events'); + +const e = new events.EventEmitter(); +const num_args_emitted = []; + +e.on('numArgs', function() { + const numArgs = arguments.length; + num_args_emitted.push(numArgs); +}); + +e.on('foo', function() { + num_args_emitted.push(arguments.length); +}); + +e.on('foo', function() { + num_args_emitted.push(arguments.length); +}); + +e.emit('numArgs'); +e.emit('numArgs', null); +e.emit('numArgs', null, null); +e.emit('numArgs', null, null, null); +e.emit('numArgs', null, null, null, null); +e.emit('numArgs', null, null, null, null, null); + +e.emit('foo', null, null, null, null); + +process.on('exit', function() { + assert.deepStrictEqual(num_args_emitted, [0, 1, 2, 3, 4, 5, 4, 4]); +}); diff --git a/cli/tests/node_compat/test/parallel/test-event-emitter-once.js b/cli/tests/node_compat/test/parallel/test-event-emitter-once.js new file mode 100644 index 00000000000000..dcfafba089430f --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-event-emitter-once.js @@ -0,0 +1,77 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. + +'use strict'; +const common = require('../common'); +const assert = require('assert'); +const EventEmitter = require('events'); + +const e = new EventEmitter(); + +e.once('hello', common.mustCall()); + +e.emit('hello', 'a', 'b'); +e.emit('hello', 'a', 'b'); +e.emit('hello', 'a', 'b'); +e.emit('hello', 'a', 'b'); + +function remove() { + assert.fail('once->foo should not be emitted'); +} + +e.once('foo', remove); +e.removeListener('foo', remove); +e.emit('foo'); + +e.once('e', common.mustCall(function() { + e.emit('e'); +})); + +e.once('e', common.mustCall()); + +e.emit('e'); + +{ + // once() has different code paths based on the number of arguments being + // emitted. Verify that all of the cases are covered. + const maxArgs = 4; + + for (let i = 0; i <= maxArgs; ++i) { + const ee = new EventEmitter(); + const args = ['foo']; + + for (let j = 0; j < i; ++j) + args.push(j); + + ee.once('foo', common.mustCall((...params) => { + assert.deepStrictEqual(params, args.slice(1)); + })); + + EventEmitter.prototype.emit.apply(ee, args); + } +} diff --git a/cli/tests/node_compat/test/parallel/test-event-emitter-prepend.js b/cli/tests/node_compat/test/parallel/test-event-emitter-prepend.js new file mode 100644 index 00000000000000..b0672274252e11 --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-event-emitter-prepend.js @@ -0,0 +1,50 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 16.13.0 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +'use strict'; + +const common = require('../common'); +const EventEmitter = require('events'); +const assert = require('assert'); + +const myEE = new EventEmitter(); +let m = 0; +// This one comes last. +myEE.on('foo', common.mustCall(() => assert.strictEqual(m, 2))); + +// This one comes second. +myEE.prependListener('foo', common.mustCall(() => assert.strictEqual(m++, 1))); + +// This one comes first. +myEE.prependOnceListener('foo', + common.mustCall(() => assert.strictEqual(m++, 0))); + +myEE.emit('foo'); + +// Test fallback if prependListener is undefined. +const stream = require('stream'); + +delete EventEmitter.prototype.prependListener; + +function Writable() { + this.writable = true; + stream.Stream.call(this); +} +Object.setPrototypeOf(Writable.prototype, stream.Stream.prototype); +Object.setPrototypeOf(Writable, stream.Stream); + +function Readable() { + this.readable = true; + stream.Stream.call(this); +} +Object.setPrototypeOf(Readable.prototype, stream.Stream.prototype); +Object.setPrototypeOf(Readable, stream.Stream); + +// FIXME(bartlomieju): +// const w = new Writable(); +// const r = new Readable(); +// r.pipe(w); diff --git a/cli/tests/node_compat/test/parallel/test-event-emitter-remove-all-listeners.js b/cli/tests/node_compat/test/parallel/test-event-emitter-remove-all-listeners.js new file mode 100644 index 00000000000000..75e04999c0ed04 --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-event-emitter-remove-all-listeners.js @@ -0,0 +1,130 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. + +'use strict'; +const common = require('../common'); +const assert = require('assert'); +const events = require('events'); + + +function expect(expected) { + const actual = []; + process.on('exit', function() { + assert.deepStrictEqual(actual.sort(), expected.sort()); + }); + function listener(name) { + actual.push(name); + } + return common.mustCall(listener, expected.length); +} + +{ + const ee = new events.EventEmitter(); + const noop = common.mustNotCall(); + ee.on('foo', noop); + ee.on('bar', noop); + ee.on('baz', noop); + ee.on('baz', noop); + const fooListeners = ee.listeners('foo'); + const barListeners = ee.listeners('bar'); + const bazListeners = ee.listeners('baz'); + ee.on('removeListener', expect(['bar', 'baz', 'baz'])); + ee.removeAllListeners('bar'); + ee.removeAllListeners('baz'); + assert.deepStrictEqual(ee.listeners('foo'), [noop]); + assert.deepStrictEqual(ee.listeners('bar'), []); + assert.deepStrictEqual(ee.listeners('baz'), []); + // After calling removeAllListeners(), + // the old listeners array should stay unchanged. + assert.deepStrictEqual(fooListeners, [noop]); + assert.deepStrictEqual(barListeners, [noop]); + assert.deepStrictEqual(bazListeners, [noop, noop]); + // After calling removeAllListeners(), + // new listeners arrays is different from the old. + assert.notStrictEqual(ee.listeners('bar'), barListeners); + assert.notStrictEqual(ee.listeners('baz'), bazListeners); +} + +{ + const ee = new events.EventEmitter(); + ee.on('foo', common.mustNotCall()); + ee.on('bar', common.mustNotCall()); + // Expect LIFO order + ee.on('removeListener', expect(['foo', 'bar', 'removeListener'])); + ee.on('removeListener', expect(['foo', 'bar'])); + ee.removeAllListeners(); + assert.deepStrictEqual([], ee.listeners('foo')); + assert.deepStrictEqual([], ee.listeners('bar')); +} + +{ + const ee = new events.EventEmitter(); + ee.on('removeListener', common.mustNotCall()); + // Check for regression where removeAllListeners() throws when + // there exists a 'removeListener' listener, but there exists + // no listeners for the provided event type. + ee.removeAllListeners.bind(ee, 'foo'); +} + +{ + const ee = new events.EventEmitter(); + let expectLength = 2; + ee.on('removeListener', function(name, noop) { + assert.strictEqual(expectLength--, this.listeners('baz').length); + }); + ee.on('baz', common.mustNotCall()); + ee.on('baz', common.mustNotCall()); + ee.on('baz', common.mustNotCall()); + assert.strictEqual(ee.listeners('baz').length, expectLength + 1); + ee.removeAllListeners('baz'); + assert.strictEqual(ee.listeners('baz').length, 0); +} + +{ + const ee = new events.EventEmitter(); + assert.deepStrictEqual(ee, ee.removeAllListeners()); +} + +{ + const ee = new events.EventEmitter(); + ee._events = undefined; + assert.strictEqual(ee, ee.removeAllListeners()); +} + +{ + const ee = new events.EventEmitter(); + const symbol = Symbol('symbol'); + const noop = common.mustNotCall(); + ee.on(symbol, noop); + + ee.on('removeListener', common.mustCall((...args) => { + assert.deepStrictEqual(args, [symbol, noop]); + })); + + ee.removeAllListeners(); +} diff --git a/cli/tests/node_compat/test/parallel/test-event-emitter-remove-listeners.js b/cli/tests/node_compat/test/parallel/test-event-emitter-remove-listeners.js new file mode 100644 index 00000000000000..027660a88169fb --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-event-emitter-remove-listeners.js @@ -0,0 +1,177 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. + +'use strict'; +const common = require('../common'); +const assert = require('assert'); +const EventEmitter = require('events'); + +function listener1() {} + +function listener2() {} + +{ + const ee = new EventEmitter(); + ee.on('hello', listener1); + ee.on('removeListener', common.mustCall((name, cb) => { + assert.strictEqual(name, 'hello'); + assert.strictEqual(cb, listener1); + })); + ee.removeListener('hello', listener1); + assert.deepStrictEqual([], ee.listeners('hello')); +} + +{ + const ee = new EventEmitter(); + ee.on('hello', listener1); + ee.on('removeListener', common.mustNotCall()); + ee.removeListener('hello', listener2); + assert.deepStrictEqual([listener1], ee.listeners('hello')); +} + +{ + const ee = new EventEmitter(); + ee.on('hello', listener1); + ee.on('hello', listener2); + ee.once('removeListener', common.mustCall((name, cb) => { + assert.strictEqual(name, 'hello'); + assert.strictEqual(cb, listener1); + assert.deepStrictEqual([listener2], ee.listeners('hello')); + })); + ee.removeListener('hello', listener1); + assert.deepStrictEqual([listener2], ee.listeners('hello')); + ee.once('removeListener', common.mustCall((name, cb) => { + assert.strictEqual(name, 'hello'); + assert.strictEqual(cb, listener2); + assert.deepStrictEqual([], ee.listeners('hello')); + })); + ee.removeListener('hello', listener2); + assert.deepStrictEqual([], ee.listeners('hello')); +} + +{ + const ee = new EventEmitter(); + + function remove1() { + assert.fail('remove1 should not have been called'); + } + + function remove2() { + assert.fail('remove2 should not have been called'); + } + + ee.on('removeListener', common.mustCall(function(name, cb) { + if (cb !== remove1) return; + this.removeListener('quux', remove2); + this.emit('quux'); + }, 2)); + ee.on('quux', remove1); + ee.on('quux', remove2); + ee.removeListener('quux', remove1); +} + +{ + const ee = new EventEmitter(); + ee.on('hello', listener1); + ee.on('hello', listener2); + ee.once('removeListener', common.mustCall((name, cb) => { + assert.strictEqual(name, 'hello'); + assert.strictEqual(cb, listener1); + assert.deepStrictEqual([listener2], ee.listeners('hello')); + ee.once('removeListener', common.mustCall((name, cb) => { + assert.strictEqual(name, 'hello'); + assert.strictEqual(cb, listener2); + assert.deepStrictEqual([], ee.listeners('hello')); + })); + ee.removeListener('hello', listener2); + assert.deepStrictEqual([], ee.listeners('hello')); + })); + ee.removeListener('hello', listener1); + assert.deepStrictEqual([], ee.listeners('hello')); +} + +{ + const ee = new EventEmitter(); + const listener3 = common.mustCall(() => { + ee.removeListener('hello', listener4); + }, 2); + const listener4 = common.mustCall(); + + ee.on('hello', listener3); + ee.on('hello', listener4); + + // listener4 will still be called although it is removed by listener 3. + ee.emit('hello'); + // This is so because the internal listener array at time of emit + // was [listener3,listener4] + + // Internal listener array [listener3] + ee.emit('hello'); +} + +{ + const ee = new EventEmitter(); + + ee.once('hello', listener1); + ee.on('removeListener', common.mustCall((eventName, listener) => { + assert.strictEqual(eventName, 'hello'); + assert.strictEqual(listener, listener1); + })); + ee.emit('hello'); +} + +{ + const ee = new EventEmitter(); + + assert.deepStrictEqual(ee, ee.removeListener('foo', () => {})); +} + +{ + const ee = new EventEmitter(); + const listener = () => {}; + ee._events = undefined; + const e = ee.removeListener('foo', listener); + assert.strictEqual(e, ee); +} + +{ + const ee = new EventEmitter(); + + ee.on('foo', listener1); + ee.on('foo', listener2); + assert.deepStrictEqual(ee.listeners('foo'), [listener1, listener2]); + + ee.removeListener('foo', listener1); + assert.strictEqual(ee._events.foo, listener2); + + ee.on('foo', listener1); + assert.deepStrictEqual(ee.listeners('foo'), [listener2, listener1]); + + ee.removeListener('foo', listener1); + assert.strictEqual(ee._events.foo, listener2); +} diff --git a/cli/tests/node_compat/test/parallel/test-event-emitter-set-max-listeners-side-effects.js b/cli/tests/node_compat/test/parallel/test-event-emitter-set-max-listeners-side-effects.js new file mode 100644 index 00000000000000..ab5b095f45cfba --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-event-emitter-set-max-listeners-side-effects.js @@ -0,0 +1,39 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. + +'use strict'; +require('../common'); +const assert = require('assert'); +const events = require('events'); + +const e = new events.EventEmitter(); + +assert(!(e._events instanceof Object)); +assert.deepStrictEqual(Object.keys(e._events), []); +e.setMaxListeners(5); +assert.deepStrictEqual(Object.keys(e._events), []); diff --git a/cli/tests/node_compat/test/parallel/test-event-emitter-special-event-names.js b/cli/tests/node_compat/test/parallel/test-event-emitter-special-event-names.js new file mode 100644 index 00000000000000..1a589221e08b09 --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-event-emitter-special-event-names.js @@ -0,0 +1,44 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +'use strict'; + +const common = require('../common'); +const EventEmitter = require('events'); +const assert = require('assert'); + +const ee = new EventEmitter(); +const handler = () => {}; + +assert.deepStrictEqual(ee.eventNames(), []); + +assert.strictEqual(ee._events.hasOwnProperty, undefined); +assert.strictEqual(ee._events.toString, undefined); + +ee.on('__proto__', handler); +ee.on('__defineGetter__', handler); +ee.on('toString', handler); + +assert.deepStrictEqual(ee.eventNames(), [ + '__proto__', + '__defineGetter__', + 'toString', +]); + +assert.deepStrictEqual(ee.listeners('__proto__'), [handler]); +assert.deepStrictEqual(ee.listeners('__defineGetter__'), [handler]); +assert.deepStrictEqual(ee.listeners('toString'), [handler]); + +ee.on('__proto__', common.mustCall(function(val) { + assert.strictEqual(val, 1); +})); +ee.emit('__proto__', 1); + +process.on('__proto__', common.mustCall(function(val) { + assert.strictEqual(val, 1); +})); +process.emit('__proto__', 1); diff --git a/cli/tests/node_compat/test/parallel/test-event-emitter-subclass.js b/cli/tests/node_compat/test/parallel/test-event-emitter-subclass.js new file mode 100644 index 00000000000000..030104150e86b7 --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-event-emitter-subclass.js @@ -0,0 +1,74 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. + +'use strict'; +const common = require('../common'); +const assert = require('assert'); +const EventEmitter = require('events').EventEmitter; + +Object.setPrototypeOf(MyEE.prototype, EventEmitter.prototype); +Object.setPrototypeOf(MyEE, EventEmitter); + +function MyEE(cb) { + this.once(1, cb); + this.emit(1); + this.removeAllListeners(); + EventEmitter.call(this); +} + +const myee = new MyEE(common.mustCall()); + +Object.setPrototypeOf(ErrorEE.prototype, EventEmitter.prototype); +Object.setPrototypeOf(ErrorEE, EventEmitter); +function ErrorEE() { + this.emit('error', new Error('blerg')); +} + +assert.throws(function() { + new ErrorEE(); +}, /blerg/); + +process.on('exit', function() { + assert(!(myee._events instanceof Object)); + assert.deepStrictEqual(Object.keys(myee._events), []); + console.log('ok'); +}); + + +function MyEE2() { + EventEmitter.call(this); +} + +MyEE2.prototype = new EventEmitter(); + +const ee1 = new MyEE2(); +const ee2 = new MyEE2(); + +ee1.on('x', () => {}); + +assert.strictEqual(ee2.listenerCount('x'), 0); diff --git a/cli/tests/node_compat/test/parallel/test-event-emitter-symbols.js b/cli/tests/node_compat/test/parallel/test-event-emitter-symbols.js new file mode 100644 index 00000000000000..b9dc9eaf3413a9 --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-event-emitter-symbols.js @@ -0,0 +1,30 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +'use strict'; + +const common = require('../common'); +const EventEmitter = require('events'); +const assert = require('assert'); + +const ee = new EventEmitter(); +const foo = Symbol('foo'); +const listener = common.mustCall(); + +ee.on(foo, listener); +assert.deepStrictEqual(ee.listeners(foo), [listener]); + +ee.emit(foo); + +ee.removeAllListeners(); +assert.deepStrictEqual(ee.listeners(foo), []); + +ee.on(foo, listener); +assert.deepStrictEqual(ee.listeners(foo), [listener]); + +ee.removeListener(foo, listener); +assert.deepStrictEqual(ee.listeners(foo), []); diff --git a/cli/tests/node_compat/test/parallel/test-events-list.js b/cli/tests/node_compat/test/parallel/test-events-list.js new file mode 100644 index 00000000000000..fc5ef71a87a1e0 --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-events-list.js @@ -0,0 +1,26 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +'use strict'; + +require('../common'); +const EventEmitter = require('events'); +const assert = require('assert'); + +const EE = new EventEmitter(); +const m = () => {}; +EE.on('foo', () => {}); +assert.deepStrictEqual(['foo'], EE.eventNames()); +EE.on('bar', m); +assert.deepStrictEqual(['foo', 'bar'], EE.eventNames()); +EE.removeListener('bar', m); +assert.deepStrictEqual(['foo'], EE.eventNames()); +const s = Symbol('s'); +EE.on(s, m); +assert.deepStrictEqual(['foo', s], EE.eventNames()); +EE.removeListener(s, m); +assert.deepStrictEqual(['foo'], EE.eventNames()); diff --git a/cli/tests/node_compat/test/parallel/test-events-on-async-iterator.js b/cli/tests/node_compat/test/parallel/test-events-on-async-iterator.js new file mode 100644 index 00000000000000..795edebfd60498 --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-events-on-async-iterator.js @@ -0,0 +1,399 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +// Flags: --expose-internals --no-warnings +'use strict'; + +const common = require('../common'); +const assert = require('assert'); +const { on, EventEmitter } = require('events'); +const { + NodeEventTarget +} = require('internal/event_target'); + +async function basic() { + const ee = new EventEmitter(); + process.nextTick(() => { + ee.emit('foo', 'bar'); + // 'bar' is a spurious event, we are testing + // that it does not show up in the iterable + ee.emit('bar', 24); + ee.emit('foo', 42); + }); + + const iterable = on(ee, 'foo'); + + const expected = [['bar'], [42]]; + + for await (const event of iterable) { + const current = expected.shift(); + + assert.deepStrictEqual(current, event); + + if (expected.length === 0) { + break; + } + } + assert.strictEqual(ee.listenerCount('foo'), 0); + assert.strictEqual(ee.listenerCount('error'), 0); +} + +async function invalidArgType() { + assert.throws(() => on({}, 'foo'), common.expectsError({ + code: 'ERR_INVALID_ARG_TYPE', + name: 'TypeError', + })); +} + +async function error() { + const ee = new EventEmitter(); + const _err = new Error('kaboom'); + process.nextTick(() => { + ee.emit('error', _err); + }); + + const iterable = on(ee, 'foo'); + let looped = false; + let thrown = false; + + try { + // eslint-disable-next-line no-unused-vars + for await (const event of iterable) { + looped = true; + } + } catch (err) { + thrown = true; + assert.strictEqual(err, _err); + } + assert.strictEqual(thrown, true); + assert.strictEqual(looped, false); +} + +async function errorDelayed() { + const ee = new EventEmitter(); + const _err = new Error('kaboom'); + process.nextTick(() => { + ee.emit('foo', 42); + ee.emit('error', _err); + }); + + const iterable = on(ee, 'foo'); + const expected = [[42]]; + let thrown = false; + + try { + for await (const event of iterable) { + const current = expected.shift(); + assert.deepStrictEqual(current, event); + } + } catch (err) { + thrown = true; + assert.strictEqual(err, _err); + } + assert.strictEqual(thrown, true); + assert.strictEqual(ee.listenerCount('foo'), 0); + assert.strictEqual(ee.listenerCount('error'), 0); +} + +async function throwInLoop() { + const ee = new EventEmitter(); + const _err = new Error('kaboom'); + + process.nextTick(() => { + ee.emit('foo', 42); + }); + + try { + for await (const event of on(ee, 'foo')) { + assert.deepStrictEqual(event, [42]); + throw _err; + } + } catch (err) { + assert.strictEqual(err, _err); + } + + assert.strictEqual(ee.listenerCount('foo'), 0); + assert.strictEqual(ee.listenerCount('error'), 0); +} + +async function next() { + const ee = new EventEmitter(); + const iterable = on(ee, 'foo'); + + process.nextTick(function() { + ee.emit('foo', 'bar'); + ee.emit('foo', 42); + iterable.return(); + }); + + const results = await Promise.all([ + iterable.next(), + iterable.next(), + iterable.next(), + ]); + + assert.deepStrictEqual(results, [{ + value: ['bar'], + done: false + }, { + value: [42], + done: false + }, { + value: undefined, + done: true + }]); + + assert.deepStrictEqual(await iterable.next(), { + value: undefined, + done: true + }); +} + +async function nextError() { + const ee = new EventEmitter(); + const iterable = on(ee, 'foo'); + const _err = new Error('kaboom'); + process.nextTick(function() { + ee.emit('error', _err); + }); + const results = await Promise.allSettled([ + iterable.next(), + iterable.next(), + iterable.next(), + ]); + assert.deepStrictEqual(results, [{ + status: 'rejected', + reason: _err + }, { + status: 'fulfilled', + value: { + value: undefined, + done: true + } + }, { + status: 'fulfilled', + value: { + value: undefined, + done: true + } + }]); + assert.strictEqual(ee.listeners('error').length, 0); +} + +async function iterableThrow() { + const ee = new EventEmitter(); + const iterable = on(ee, 'foo'); + + process.nextTick(() => { + ee.emit('foo', 'bar'); + ee.emit('foo', 42); // lost in the queue + iterable.throw(_err); + }); + + const _err = new Error('kaboom'); + let thrown = false; + + assert.throws(() => { + // No argument + iterable.throw(); + }, { + message: 'The "EventEmitter.AsyncIterator" property must be' + + ' an instance of Error. Received undefined', + name: 'TypeError' + }); + + const expected = [['bar'], [42]]; + + try { + for await (const event of iterable) { + assert.deepStrictEqual(event, expected.shift()); + } + } catch (err) { + thrown = true; + assert.strictEqual(err, _err); + } + assert.strictEqual(thrown, true); + assert.strictEqual(expected.length, 0); + assert.strictEqual(ee.listenerCount('foo'), 0); + assert.strictEqual(ee.listenerCount('error'), 0); +} + +async function eventTarget() { + const et = new EventTarget(); + const tick = () => et.dispatchEvent(new Event('tick')); + const interval = setInterval(tick, 0); + let count = 0; + for await (const [ event ] of on(et, 'tick')) { + count++; + assert.strictEqual(event.type, 'tick'); + if (count >= 5) { + break; + } + } + assert.strictEqual(count, 5); + clearInterval(interval); +} + +async function errorListenerCount() { + const et = new EventEmitter(); + on(et, 'foo'); + assert.strictEqual(et.listenerCount('error'), 1); +} + +async function nodeEventTarget() { + const et = new NodeEventTarget(); + const tick = () => et.dispatchEvent(new Event('tick')); + const interval = setInterval(tick, 0); + let count = 0; + for await (const [ event] of on(et, 'tick')) { + count++; + assert.strictEqual(event.type, 'tick'); + if (count >= 5) { + break; + } + } + assert.strictEqual(count, 5); + clearInterval(interval); +} + +async function abortableOnBefore() { + const ee = new EventEmitter(); + const abortedSignal = AbortSignal.abort(); + [1, {}, null, false, 'hi'].forEach((signal) => { + assert.throws(() => on(ee, 'foo', { signal }), { + code: 'ERR_INVALID_ARG_TYPE' + }); + }); + assert.throws(() => on(ee, 'foo', { signal: abortedSignal }), { + name: 'AbortError' + }); +} + +async function eventTargetAbortableOnBefore() { + const et = new EventTarget(); + const abortedSignal = AbortSignal.abort(); + [1, {}, null, false, 'hi'].forEach((signal) => { + assert.throws(() => on(et, 'foo', { signal }), { + code: 'ERR_INVALID_ARG_TYPE' + }); + }); + assert.throws(() => on(et, 'foo', { signal: abortedSignal }), { + name: 'AbortError' + }); +} + +async function abortableOnAfter() { + const ee = new EventEmitter(); + const ac = new AbortController(); + + const i = setInterval(() => ee.emit('foo', 'foo'), 10); + + async function foo() { + for await (const f of on(ee, 'foo', { signal: ac.signal })) { + assert.strictEqual(f, 'foo'); + } + } + + foo().catch(common.mustCall((error) => { + assert.strictEqual(error.name, 'AbortError'); + })).finally(() => { + clearInterval(i); + }); + + process.nextTick(() => ac.abort()); +} + +async function eventTargetAbortableOnAfter() { + const et = new EventTarget(); + const ac = new AbortController(); + + const i = setInterval(() => et.dispatchEvent(new Event('foo')), 10); + + async function foo() { + for await (const f of on(et, 'foo', { signal: ac.signal })) { + assert(f); + } + } + + foo().catch(common.mustCall((error) => { + assert.strictEqual(error.name, 'AbortError'); + })).finally(() => { + clearInterval(i); + }); + + process.nextTick(() => ac.abort()); +} + +async function eventTargetAbortableOnAfter2() { + const et = new EventTarget(); + const ac = new AbortController(); + + const i = setInterval(() => et.dispatchEvent(new Event('foo')), 10); + + async function foo() { + for await (const f of on(et, 'foo', { signal: ac.signal })) { + assert(f); + // Cancel after a single event has been triggered. + ac.abort(); + } + } + + foo().catch(common.mustCall((error) => { + assert.strictEqual(error.name, 'AbortError'); + })).finally(() => { + clearInterval(i); + }); +} + +async function abortableOnAfterDone() { + const ee = new EventEmitter(); + const ac = new AbortController(); + + const i = setInterval(() => ee.emit('foo', 'foo'), 1); + let count = 0; + + async function foo() { + for await (const f of on(ee, 'foo', { signal: ac.signal })) { + assert.strictEqual(f[0], 'foo'); + if (++count === 5) + break; + } + ac.abort(); // No error will occur + } + + foo().finally(() => { + clearInterval(i); + }); +} + +async function run() { + const funcs = [ + basic, + invalidArgType, + error, + errorDelayed, + throwInLoop, + next, + nextError, + iterableThrow, + eventTarget, + errorListenerCount, + nodeEventTarget, + abortableOnBefore, + abortableOnAfter, + eventTargetAbortableOnBefore, + eventTargetAbortableOnAfter, + eventTargetAbortableOnAfter2, + abortableOnAfterDone, + ]; + + for (const fn of funcs) { + await fn(); + } +} + +run().then(common.mustCall()); diff --git a/cli/tests/node_compat/test/parallel/test-events-once.js b/cli/tests/node_compat/test/parallel/test-events-once.js new file mode 100644 index 00000000000000..7236f983006fb7 --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-events-once.js @@ -0,0 +1,272 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.8.0 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +// TODO(cjihrig): kEvents is an internally used symbol in Node.js. It is not +// implemented in Deno, so parts of this test must be removed in order to pass. + +'use strict'; +// Flags: --no-warnings + +const common = require('../common'); +const { once, EventEmitter } = require('events'); +const { + strictEqual, + deepStrictEqual, + fail, + rejects, +} = require('assert'); + +async function onceAnEvent() { + const ee = new EventEmitter(); + + process.nextTick(() => { + ee.emit('myevent', 42); + }); + + const [value] = await once(ee, 'myevent'); + strictEqual(value, 42); + strictEqual(ee.listenerCount('error'), 0); + strictEqual(ee.listenerCount('myevent'), 0); +} + +async function onceAnEventWithNullOptions() { + const ee = new EventEmitter(); + + process.nextTick(() => { + ee.emit('myevent', 42); + }); + + const [value] = await once(ee, 'myevent', null); + strictEqual(value, 42); +} + + +async function onceAnEventWithTwoArgs() { + const ee = new EventEmitter(); + + process.nextTick(() => { + ee.emit('myevent', 42, 24); + }); + + const value = await once(ee, 'myevent'); + deepStrictEqual(value, [42, 24]); +} + +async function catchesErrors() { + const ee = new EventEmitter(); + + const expected = new Error('kaboom'); + let err; + process.nextTick(() => { + ee.emit('error', expected); + }); + + try { + await once(ee, 'myevent'); + } catch (_e) { + err = _e; + } + strictEqual(err, expected); + strictEqual(ee.listenerCount('error'), 0); + strictEqual(ee.listenerCount('myevent'), 0); +} + +async function catchesErrorsWithAbortSignal() { + const ee = new EventEmitter(); + const ac = new AbortController(); + const signal = ac.signal; + + const expected = new Error('boom'); + let err; + process.nextTick(() => { + ee.emit('error', expected); + }); + + try { + const promise = once(ee, 'myevent', { signal }); + strictEqual(ee.listenerCount('error'), 1); + + await promise; + } catch (e) { + err = e; + } + strictEqual(err, expected); + strictEqual(ee.listenerCount('error'), 0); + strictEqual(ee.listenerCount('myevent'), 0); +} + +async function stopListeningAfterCatchingError() { + const ee = new EventEmitter(); + + const expected = new Error('kaboom'); + let err; + process.nextTick(() => { + ee.emit('error', expected); + ee.emit('myevent', 42, 24); + }); + + try { + await once(ee, 'myevent'); + } catch (_e) { + err = _e; + } + process.removeAllListeners('multipleResolves'); + strictEqual(err, expected); + strictEqual(ee.listenerCount('error'), 0); + strictEqual(ee.listenerCount('myevent'), 0); +} + +async function onceError() { + const ee = new EventEmitter(); + + const expected = new Error('kaboom'); + process.nextTick(() => { + ee.emit('error', expected); + }); + + const promise = once(ee, 'error'); + strictEqual(ee.listenerCount('error'), 1); + const [ err ] = await promise; + strictEqual(err, expected); + strictEqual(ee.listenerCount('error'), 0); + strictEqual(ee.listenerCount('myevent'), 0); +} + +async function onceWithEventTarget() { + const et = new EventTarget(); + const event = new Event('myevent'); + process.nextTick(() => { + et.dispatchEvent(event); + }); + const [ value ] = await once(et, 'myevent'); + strictEqual(value, event); +} + +async function onceWithEventTargetError() { + const et = new EventTarget(); + const error = new Event('error'); + process.nextTick(() => { + et.dispatchEvent(error); + }); + + const [ err ] = await once(et, 'error'); + strictEqual(err, error); +} + +async function prioritizesEventEmitter() { + const ee = new EventEmitter(); + ee.addEventListener = fail; + ee.removeAllListeners = fail; + process.nextTick(() => ee.emit('foo')); + await once(ee, 'foo'); +} + +async function abortSignalBefore() { + const ee = new EventEmitter(); + ee.on('error', common.mustNotCall()); + const abortedSignal = AbortSignal.abort(); + + await Promise.all([1, {}, 'hi', null, false].map((signal) => { + return rejects(once(ee, 'foo', { signal }), { + code: 'ERR_INVALID_ARG_TYPE' + }); + })); + + return rejects(once(ee, 'foo', { signal: abortedSignal }), { + name: 'AbortError' + }); +} + +async function abortSignalAfter() { + const ee = new EventEmitter(); + const ac = new AbortController(); + ee.on('error', common.mustNotCall()); + const r = rejects(once(ee, 'foo', { signal: ac.signal }), { + name: 'AbortError' + }); + process.nextTick(() => ac.abort()); + return r; +} + +async function abortSignalAfterEvent() { + const ee = new EventEmitter(); + const ac = new AbortController(); + process.nextTick(() => { + ee.emit('foo'); + ac.abort(); + }); + const promise = once(ee, 'foo', { signal: ac.signal }); + await promise; +} + +async function abortSignalRemoveListener() { + const ee = new EventEmitter(); + const ac = new AbortController(); + + try { + process.nextTick(() => ac.abort()); + await once(ee, 'test', { signal: ac.signal }); + } catch { + strictEqual(ee.listeners('test').length, 0); + strictEqual(ee.listeners('error').length, 0); + } +} + +async function eventTargetAbortSignalBefore() { + const et = new EventTarget(); + const abortedSignal = AbortSignal.abort(); + + await Promise.all([1, {}, 'hi', null, false].map((signal) => { + return rejects(once(et, 'foo', { signal }), { + code: 'ERR_INVALID_ARG_TYPE' + }); + })); + + return rejects(once(et, 'foo', { signal: abortedSignal }), { + name: 'AbortError' + }); +} + +async function eventTargetAbortSignalAfter() { + const et = new EventTarget(); + const ac = new AbortController(); + const r = rejects(once(et, 'foo', { signal: ac.signal }), { + name: 'AbortError' + }); + process.nextTick(() => ac.abort()); + return r; +} + +async function eventTargetAbortSignalAfterEvent() { + const et = new EventTarget(); + const ac = new AbortController(); + process.nextTick(() => { + et.dispatchEvent(new Event('foo')); + ac.abort(); + }); + await once(et, 'foo', { signal: ac.signal }); +} + +Promise.all([ + onceAnEvent(), + onceAnEventWithNullOptions(), + onceAnEventWithTwoArgs(), + catchesErrors(), + catchesErrorsWithAbortSignal(), + stopListeningAfterCatchingError(), + onceError(), + onceWithEventTarget(), + onceWithEventTargetError(), + prioritizesEventEmitter(), + abortSignalBefore(), + abortSignalAfter(), + abortSignalAfterEvent(), + abortSignalRemoveListener(), + eventTargetAbortSignalBefore(), + eventTargetAbortSignalAfter(), + eventTargetAbortSignalAfterEvent(), +]).then(common.mustCall()); diff --git a/cli/tests/node_compat/test/parallel/test-events-uncaught-exception-stack.js b/cli/tests/node_compat/test/parallel/test-events-uncaught-exception-stack.js new file mode 100644 index 00000000000000..ec0efff65adb01 --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-events-uncaught-exception-stack.js @@ -0,0 +1,23 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +'use strict'; +const common = require('../common'); +const assert = require('assert'); +const EventEmitter = require('events'); + +// Tests that the error stack where the exception was thrown is *not* appended. + +process.on('uncaughtException', common.mustCall((err) => { + const lines = err.stack.split('\n'); + assert.strictEqual(lines[0], 'Error'); + lines.slice(1).forEach((line) => { + assert.match(line, /^ {4}at/); + }); +})); + +new EventEmitter().emit('error', new Error()); diff --git a/cli/tests/node_compat/test/parallel/test-eventtarget-brandcheck.js b/cli/tests/node_compat/test/parallel/test-eventtarget-brandcheck.js new file mode 100644 index 00000000000000..e02128a05173d7 --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-eventtarget-brandcheck.js @@ -0,0 +1,104 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +// Flags: --expose-internals +'use strict'; + +require('../common'); +const assert = require('assert'); + +const { + Event, + CustomEvent, + EventTarget, + NodeEventTarget, +} = require('internal/event_target'); + +[ + 'target', + 'currentTarget', + 'srcElement', + 'type', + 'cancelable', + 'defaultPrevented', + 'timeStamp', + 'returnValue', + 'bubbles', + 'composed', + 'eventPhase', +].forEach((i) => { + assert.throws(() => Reflect.get(Event.prototype, i, {}), { + code: 'ERR_INVALID_THIS', + }); +}); + +[ + 'stopImmediatePropagation', + 'preventDefault', + 'composedPath', + 'cancelBubble', + 'stopPropagation', +].forEach((i) => { + assert.throws(() => Reflect.apply(Event.prototype[i], [], {}), { + code: 'ERR_INVALID_THIS', + }); +}); + +[ + 'target', + 'currentTarget', + 'srcElement', + 'type', + 'cancelable', + 'defaultPrevented', + 'timeStamp', + 'returnValue', + 'bubbles', + 'composed', + 'eventPhase', + 'detail', +].forEach((i) => { + assert.throws(() => Reflect.get(CustomEvent.prototype, i, {}), { + code: 'ERR_INVALID_THIS', + }); +}); + +[ + 'stopImmediatePropagation', + 'preventDefault', + 'composedPath', + 'cancelBubble', + 'stopPropagation', +].forEach((i) => { + assert.throws(() => Reflect.apply(CustomEvent.prototype[i], [], {}), { + code: 'ERR_INVALID_THIS', + }); +}); + +['addEventListener', 'removeEventListener', 'dispatchEvent'].forEach((i) => { + assert.throws(() => Reflect.apply(EventTarget.prototype[i], [], {}), { + code: 'ERR_INVALID_THIS', + }); +}); + +[ + 'setMaxListeners', + 'getMaxListeners', + 'eventNames', + 'listenerCount', + 'off', + 'removeListener', + 'on', + 'addListener', + 'once', + 'emit', + 'removeAllListeners', +].forEach((i) => { + assert.throws(() => Reflect.apply(NodeEventTarget.prototype[i], [], {}), { + code: 'ERR_INVALID_THIS', + }); +}); diff --git a/cli/tests/node_compat/test/parallel/test-exception-handler.js b/cli/tests/node_compat/test/parallel/test-exception-handler.js new file mode 100644 index 00000000000000..a7739ad844880e --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-exception-handler.js @@ -0,0 +1,47 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. + +'use strict'; +const common = require('../common'); +const assert = require('assert'); + +const MESSAGE = 'catch me if you can'; + +process.on('uncaughtException', common.mustCall((e) => { + console.log('uncaught exception! 1'); + assert.strictEqual(MESSAGE, e.message); +})); + +process.on('uncaughtException', common.mustCall((e) => { + console.log('uncaught exception! 2'); + assert.strictEqual(MESSAGE, e.message); +})); + +setTimeout(() => { + throw new Error(MESSAGE); +}, 10); diff --git a/cli/tests/node_compat/test/parallel/test-exception-handler2.js b/cli/tests/node_compat/test/parallel/test-exception-handler2.js new file mode 100644 index 00000000000000..474c286c5a1bd0 --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-exception-handler2.js @@ -0,0 +1,43 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. + +'use strict'; +const common = require('../common'); +const assert = require('assert'); + +process.on('uncaughtException', function(err) { + console.log(`Caught exception: ${err}`); +}); + +setTimeout(common.mustCall(function() { + console.log('This will still run.'); +}), 50); + +// Intentionally cause an exception, but don't catch it. +nonexistentFunc(); // eslint-disable-line no-undef +assert.fail('This will not run.'); diff --git a/cli/tests/node_compat/test/parallel/test-file-read-noexist.js b/cli/tests/node_compat/test/parallel/test-file-read-noexist.js new file mode 100644 index 00000000000000..491e5d029e66ba --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-file-read-noexist.js @@ -0,0 +1,39 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. + +'use strict'; +const common = require('../common'); +const fixtures = require('../common/fixtures'); +const assert = require('assert'); +const fs = require('fs'); + +const filename = fixtures.path('does_not_exist.txt'); +fs.readFile(filename, 'latin1', common.mustCall(function(err, content) { + assert.ok(err); + assert.strictEqual(err.code, 'ENOENT'); +})); diff --git a/cli/tests/node_compat/test/parallel/test-file-write-stream.js b/cli/tests/node_compat/test/parallel/test-file-write-stream.js new file mode 100644 index 00000000000000..fb67530a61b61d --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-file-write-stream.js @@ -0,0 +1,91 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. + +'use strict'; +const common = require('../common'); +const assert = require('assert'); + +const path = require('path'); +const fs = require('fs'); +const tmpdir = require('../common/tmpdir'); +const fn = path.join(tmpdir.path, 'write.txt'); +tmpdir.refresh(); +const file = fs.createWriteStream(fn, { + highWaterMark: 10 +}); + +const EXPECTED = '012345678910'; + +const callbacks = { + open: -1, + drain: -2, + close: -1 +}; + +file + .on('open', function(fd) { + console.error('open!'); + callbacks.open++; + assert.strictEqual(typeof fd, 'number'); + }) + .on('drain', function() { + console.error('drain!', callbacks.drain); + callbacks.drain++; + if (callbacks.drain === -1) { + assert.strictEqual(fs.readFileSync(fn, 'utf8'), EXPECTED); + file.write(EXPECTED); + } else if (callbacks.drain === 0) { + assert.strictEqual(fs.readFileSync(fn, 'utf8'), EXPECTED + EXPECTED); + file.end(); + } + }) + .on('close', function() { + console.error('close!'); + assert.strictEqual(file.bytesWritten, EXPECTED.length * 2); + + callbacks.close++; + file.write('should not work anymore', common.expectsError({ + code: 'ERR_STREAM_WRITE_AFTER_END', + name: 'Error', + message: 'write after end' + })); + file.on('error', common.mustNotCall()); + + fs.unlinkSync(fn); + }); + +for (let i = 0; i < 11; i++) { + file.write(`${i}`); +} + +process.on('exit', function() { + for (const k in callbacks) { + assert.strictEqual(callbacks[k], 0, `${k} count off by ${callbacks[k]}`); + } + console.log('ok'); +}); diff --git a/cli/tests/node_compat/test/parallel/test-file-write-stream2.js b/cli/tests/node_compat/test/parallel/test-file-write-stream2.js new file mode 100644 index 00000000000000..2a48e736bcf3c1 --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-file-write-stream2.js @@ -0,0 +1,116 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. + +'use strict'; +require('../common'); +const assert = require('assert'); + +const path = require('path'); +const fs = require('fs'); + +const tmpdir = require('../common/tmpdir'); + + +const filepath = path.join(tmpdir.path, 'write.txt'); + +const EXPECTED = '012345678910'; + +const cb_expected = 'write open drain write drain close '; +let cb_occurred = ''; + +let countDrains = 0; + + +process.on('exit', function() { + removeTestFile(); + if (cb_occurred !== cb_expected) { + console.log(' Test callback events missing or out of order:'); + console.log(` expected: ${cb_expected}`); + console.log(` occurred: ${cb_occurred}`); + assert.strictEqual( + cb_occurred, cb_expected, + `events missing or out of order: "${cb_occurred}" !== "${cb_expected}"`); + } else { + console.log('ok'); + } +}); + +function removeTestFile() { + try { + fs.unlinkSync(filepath); + } catch { + // Continue regardless of error. + } +} + + +tmpdir.refresh(); + +// Drain at 0, return false at 10. +const file = fs.createWriteStream(filepath, { + highWaterMark: 11 +}); + +file.on('open', function(fd) { + console.error('open'); + cb_occurred += 'open '; + assert.strictEqual(typeof fd, 'number'); +}); + +file.on('drain', function() { + console.error('drain'); + cb_occurred += 'drain '; + ++countDrains; + if (countDrains === 1) { + console.error('drain=1, write again'); + assert.strictEqual(fs.readFileSync(filepath, 'utf8'), EXPECTED); + console.error(`ondrain write ret= ${file.write(EXPECTED)}`); + cb_occurred += 'write '; + } else if (countDrains === 2) { + console.error('second drain, end'); + assert.strictEqual(fs.readFileSync(filepath, 'utf8'), EXPECTED + EXPECTED); + file.end(); + } +}); + +file.on('close', function() { + cb_occurred += 'close '; + assert.strictEqual(file.bytesWritten, EXPECTED.length * 2); + file.write('should not work anymore', (err) => { + assert.ok(err.message.includes('write after end')); + }); +}); + +for (let i = 0; i < 11; i++) { + const ret = file.write(String(i)); + console.error(`${i} ${ret}`); + + // Return false when i hits 10 + assert.strictEqual(ret, i !== 10); +} +cb_occurred += 'write '; diff --git a/cli/tests/node_compat/test/parallel/test-file-write-stream3.js b/cli/tests/node_compat/test/parallel/test-file-write-stream3.js new file mode 100644 index 00000000000000..07704d814edd06 --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-file-write-stream3.js @@ -0,0 +1,221 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. + +'use strict'; +const common = require('../common'); +const assert = require('assert'); +const path = require('path'); +const fs = require('fs'); + +const tmpdir = require('../common/tmpdir'); + + +const filepath = path.join(tmpdir.path, 'write_pos.txt'); + + +const cb_expected = 'write open close write open close write open close '; +let cb_occurred = ''; + +const fileDataInitial = 'abcdefghijklmnopqrstuvwxyz'; + +const fileDataExpected_1 = 'abcdefghijklmnopqrstuvwxyz'; +const fileDataExpected_2 = 'abcdefghij123456qrstuvwxyz'; +const fileDataExpected_3 = 'abcdefghij\u2026\u2026qrstuvwxyz'; + + +process.on('exit', function() { + if (cb_occurred !== cb_expected) { + console.log(' Test callback events missing or out of order:'); + console.log(` expected: ${cb_expected}`); + console.log(` occurred: ${cb_occurred}`); + assert.strictEqual( + cb_occurred, cb_expected, + `events missing or out of order: "${cb_occurred}" !== "${cb_expected}"`); + } +}); + + +tmpdir.refresh(); + + +function run_test_1() { + const options = {}; + const file = fs.createWriteStream(filepath, options); + console.log(' (debug: start ', file.start); + console.log(' (debug: pos ', file.pos); + + file.on('open', function(fd) { + cb_occurred += 'open '; + }); + + file.on('close', function() { + cb_occurred += 'close '; + console.log(' (debug: bytesWritten ', file.bytesWritten); + console.log(' (debug: start ', file.start); + console.log(' (debug: pos ', file.pos); + assert.strictEqual(file.bytesWritten, buffer.length); + const fileData = fs.readFileSync(filepath, 'utf8'); + console.log(' (debug: file data ', fileData); + console.log(' (debug: expected ', fileDataExpected_1); + assert.strictEqual(fileData, fileDataExpected_1); + + run_test_2(); + }); + + file.on('error', function(err) { + cb_occurred += 'error '; + console.log(' (debug: err event ', err); + throw err; + }); + + const buffer = Buffer.from(fileDataInitial); + file.write(buffer); + cb_occurred += 'write '; + + file.end(); +} + + +function run_test_2() { + + const buffer = Buffer.from('123456'); + + const options = { start: 10, + flags: 'r+' }; + const file = fs.createWriteStream(filepath, options); + console.log(' (debug: start ', file.start); + console.log(' (debug: pos ', file.pos); + + file.on('open', function(fd) { + cb_occurred += 'open '; + }); + + file.on('close', function() { + cb_occurred += 'close '; + console.log(' (debug: bytesWritten ', file.bytesWritten); + console.log(' (debug: start ', file.start); + console.log(' (debug: pos ', file.pos); + assert.strictEqual(file.bytesWritten, buffer.length); + const fileData = fs.readFileSync(filepath, 'utf8'); + console.log(' (debug: file data ', fileData); + console.log(' (debug: expected ', fileDataExpected_2); + assert.strictEqual(fileData, fileDataExpected_2); + + run_test_3(); + }); + + file.on('error', function(err) { + cb_occurred += 'error '; + console.log(' (debug: err event ', err); + throw err; + }); + + file.write(buffer); + cb_occurred += 'write '; + + file.end(); +} + + +function run_test_3() { + + const data = '\u2026\u2026'; // 3 bytes * 2 = 6 bytes in UTF-8 + + const options = { start: 10, + flags: 'r+' }; + const file = fs.createWriteStream(filepath, options); + console.log(' (debug: start ', file.start); + console.log(' (debug: pos ', file.pos); + + file.on('open', function(fd) { + cb_occurred += 'open '; + }); + + file.on('close', function() { + cb_occurred += 'close '; + console.log(' (debug: bytesWritten ', file.bytesWritten); + console.log(' (debug: start ', file.start); + console.log(' (debug: pos ', file.pos); + assert.strictEqual(file.bytesWritten, data.length * 3); + const fileData = fs.readFileSync(filepath, 'utf8'); + console.log(' (debug: file data ', fileData); + console.log(' (debug: expected ', fileDataExpected_3); + assert.strictEqual(fileData, fileDataExpected_3); + + run_test_4(); + run_test_5(); + }); + + file.on('error', function(err) { + cb_occurred += 'error '; + console.log(' (debug: err event ', err); + throw err; + }); + + file.write(data, 'utf8'); + cb_occurred += 'write '; + + file.end(); +} + + +const run_test_4 = common.mustCall(function() { + // Error: start must be >= zero + const fn = () => { + fs.createWriteStream(filepath, { start: -5, flags: 'r+' }); + }; + // Verify the range of values using a common integer verifier. + // Limit Number.MAX_SAFE_INTEGER + const err = { + code: 'ERR_OUT_OF_RANGE', + message: 'The value of "start" is out of range. ' + + `It must be >= 0 && <= ${Number.MAX_SAFE_INTEGER}. Received -5`, + name: 'RangeError' + }; + assert.throws(fn, err); +}); + + +const run_test_5 = common.mustCall(function() { + // Error: start must be <= 2 ** 53 - 1 + const fn = () => { + fs.createWriteStream(filepath, { start: 2 ** 53, flags: 'r+' }); + }; + // Verify the range of values using a common integer verifier. + // Limit Number.MAX_SAFE_INTEGER + const err = { + code: 'ERR_OUT_OF_RANGE', + message: 'The value of "start" is out of range. It must be ' + + `>= 0 && <= ${Number.MAX_SAFE_INTEGER}. ` + + 'Received 9_007_199_254_740_992', + name: 'RangeError' + }; + assert.throws(fn, err); +}); + +run_test_1(); diff --git a/cli/tests/node_compat/test/parallel/test-file-write-stream4.js b/cli/tests/node_compat/test/parallel/test-file-write-stream4.js new file mode 100644 index 00000000000000..7d696e801c9bd6 --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-file-write-stream4.js @@ -0,0 +1,28 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +'use strict'; + +// Test that 'close' emits once and not twice when `emitClose: true` is set. +// Refs: https://github.com/nodejs/node/issues/31366 + +const common = require('../common'); +const path = require('path'); +const fs = require('fs'); + +const tmpdir = require('../common/tmpdir'); +tmpdir.refresh(); + +const filepath = path.join(tmpdir.path, 'write_pos.txt'); + +const fileReadStream = fs.createReadStream(process.execPath); +const fileWriteStream = fs.createWriteStream(filepath, { + emitClose: true +}); + +fileReadStream.pipe(fileWriteStream); +fileWriteStream.on('close', common.mustCall()); diff --git a/cli/tests/node_compat/test/parallel/test-fs-access.js b/cli/tests/node_compat/test/parallel/test-fs-access.js new file mode 100644 index 00000000000000..ed0fc94706e136 --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-fs-access.js @@ -0,0 +1,246 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +// Flags: --expose-internals +'use strict'; + +// This tests that fs.access and fs.accessSync works as expected +// and the errors thrown from these APIs include the desired properties + +const common = require('../common'); +if (!common.isWindows && process.getuid() === 0) + common.skip('as this test should not be run as `root`'); + +if (common.isIBMi) + common.skip('IBMi has a different access permission mechanism'); + +const assert = require('assert'); +const fs = require('fs'); +const path = require('path'); + +const { internalBinding } = require('internal/test/binding'); +const { UV_ENOENT } = internalBinding('uv'); + +const tmpdir = require('../common/tmpdir'); +const doesNotExist = path.join(tmpdir.path, '__this_should_not_exist'); +const readOnlyFile = path.join(tmpdir.path, 'read_only_file'); +const readWriteFile = path.join(tmpdir.path, 'read_write_file'); + +function createFileWithPerms(file, mode) { + fs.writeFileSync(file, ''); + fs.chmodSync(file, mode); +} + +tmpdir.refresh(); +createFileWithPerms(readOnlyFile, 0o444); +createFileWithPerms(readWriteFile, 0o666); + +// On non-Windows supported platforms, fs.access(readOnlyFile, W_OK, ...) +// always succeeds if node runs as the super user, which is sometimes the +// case for tests running on our continuous testing platform agents. +// +// In this case, this test tries to change its process user id to a +// non-superuser user so that the test that checks for write access to a +// read-only file can be more meaningful. +// +// The change of user id is done after creating the fixtures files for the same +// reason: the test may be run as the superuser within a directory in which +// only the superuser can create files, and thus it may need superuser +// privileges to create them. +// +// There's not really any point in resetting the process' user id to 0 after +// changing it to 'nobody', since in the case that the test runs without +// superuser privilege, it is not possible to change its process user id to +// superuser. +// +// It can prevent the test from removing files created before the change of user +// id, but that's fine. In this case, it is the responsibility of the +// continuous integration platform to take care of that. +let hasWriteAccessForReadonlyFile = false; +if (!common.isWindows && process.getuid() === 0) { + hasWriteAccessForReadonlyFile = true; + try { + process.setuid('nobody'); + hasWriteAccessForReadonlyFile = false; + } catch { + // Continue regardless of error. + } +} + +assert.strictEqual(typeof fs.F_OK, 'number'); +assert.strictEqual(typeof fs.R_OK, 'number'); +assert.strictEqual(typeof fs.W_OK, 'number'); +assert.strictEqual(typeof fs.X_OK, 'number'); + +const throwNextTick = (e) => { process.nextTick(() => { throw e; }); }; + +fs.access(__filename, common.mustCall(function(...args) { + assert.deepStrictEqual(args, [null]); +})); +fs.promises.access(__filename) + .then(common.mustCall()) + .catch(throwNextTick); +fs.access(__filename, fs.R_OK, common.mustCall(function(...args) { + assert.deepStrictEqual(args, [null]); +})); +fs.promises.access(__filename, fs.R_OK) + .then(common.mustCall()) + .catch(throwNextTick); +fs.access(readOnlyFile, fs.R_OK, common.mustCall(function(...args) { + assert.deepStrictEqual(args, [null]); +})); +fs.promises.access(readOnlyFile, fs.R_OK) + .then(common.mustCall()) + .catch(throwNextTick); + +{ + const expectedError = (err) => { + assert.notStrictEqual(err, null); + assert.strictEqual(err.code, 'ENOENT'); + assert.strictEqual(err.path, doesNotExist); + }; + fs.access(doesNotExist, common.mustCall(expectedError)); + fs.promises.access(doesNotExist) + .then(common.mustNotCall(), common.mustCall(expectedError)) + .catch(throwNextTick); +} + +{ + function expectedError(err) { + assert.strictEqual(this, undefined); + if (hasWriteAccessForReadonlyFile) { + assert.ifError(err); + } else { + assert.notStrictEqual(err, null); + assert.strictEqual(err.path, readOnlyFile); + } + } + fs.access(readOnlyFile, fs.W_OK, common.mustCall(expectedError)); + fs.promises.access(readOnlyFile, fs.W_OK) + .then(common.mustNotCall(), common.mustCall(expectedError)) + .catch(throwNextTick); +} + +{ + const expectedError = (err) => { + assert.strictEqual(err.code, 'ERR_INVALID_ARG_TYPE'); + assert.ok(err instanceof TypeError); + return true; + }; + assert.throws( + () => { fs.access(100, fs.F_OK, common.mustNotCall()); }, + expectedError + ); + + fs.promises.access(100, fs.F_OK) + .then(common.mustNotCall(), common.mustCall(expectedError)) + .catch(throwNextTick); +} + +assert.throws( + () => { + fs.access(__filename, fs.F_OK); + }, + { + code: 'ERR_INVALID_ARG_TYPE', + name: 'TypeError' + }); + +assert.throws( + () => { + fs.access(__filename, fs.F_OK, common.mustNotMutateObjectDeep({})); + }, + { + code: 'ERR_INVALID_ARG_TYPE', + name: 'TypeError' + }); + +// Regular access should not throw. +fs.accessSync(__filename); +const mode = fs.R_OK | fs.W_OK; +fs.accessSync(readWriteFile, mode); + +// Invalid modes should throw. +[ + false, + 1n, + { [Symbol.toPrimitive]() { return fs.R_OK; } }, + [1], + 'r', +].forEach((mode, i) => { + console.log(mode, i); + assert.throws( + () => fs.access(readWriteFile, mode, common.mustNotCall()), + { + code: 'ERR_INVALID_ARG_TYPE', + message: /"mode" argument.+integer/ + } + ); + assert.throws( + () => fs.accessSync(readWriteFile, mode), + { + code: 'ERR_INVALID_ARG_TYPE', + message: /"mode" argument.+integer/ + } + ); +}); + +// Out of range modes should throw +[ + -1, + 8, + Infinity, + NaN, +].forEach((mode, i) => { + console.log(mode, i); + assert.throws( + () => fs.access(readWriteFile, mode, common.mustNotCall()), + { + code: 'ERR_OUT_OF_RANGE', + message: /"mode".+It must be an integer >= 0 && <= 7/ + } + ); + assert.throws( + () => fs.accessSync(readWriteFile, mode), + { + code: 'ERR_OUT_OF_RANGE', + message: /"mode".+It must be an integer >= 0 && <= 7/ + } + ); +}); + +assert.throws( + () => { fs.accessSync(doesNotExist); }, + (err) => { + assert.strictEqual(err.code, 'ENOENT'); + assert.strictEqual(err.path, doesNotExist); + assert.strictEqual( + err.message, + `ENOENT: no such file or directory, access '${doesNotExist}'` + ); + assert.strictEqual(err.constructor, Error); + assert.strictEqual(err.syscall, 'access'); + assert.strictEqual(err.errno, UV_ENOENT); + return true; + } +); + +assert.throws( + () => { fs.accessSync(Buffer.from(doesNotExist)); }, + (err) => { + assert.strictEqual(err.code, 'ENOENT'); + assert.strictEqual(err.path, doesNotExist); + assert.strictEqual( + err.message, + `ENOENT: no such file or directory, access '${doesNotExist}'` + ); + assert.strictEqual(err.constructor, Error); + assert.strictEqual(err.syscall, 'access'); + assert.strictEqual(err.errno, UV_ENOENT); + return true; + } +); diff --git a/cli/tests/node_compat/test/parallel/test-fs-append-file-sync.js b/cli/tests/node_compat/test/parallel/test-fs-append-file-sync.js new file mode 100644 index 00000000000000..19ba266cfdfec2 --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-fs-append-file-sync.js @@ -0,0 +1,115 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. + +'use strict'; +const common = require('../common'); +const assert = require('assert'); +const join = require('path').join; +const fs = require('fs'); + +const currentFileData = 'ABCD'; +const m = 0o600; +const num = 220; +const data = '南越国是前203年至前111年存在于岭南地区的一个国家,国都位于番禺,疆域包括今天中国的广东、' + + '广西两省区的大部份地区,福建省、湖南、贵州、云南的一小部份地区和越南的北部。' + + '南越国是秦朝灭亡后,由南海郡尉赵佗于前203年起兵兼并桂林郡和象郡后建立。' + + '前196年和前179年,南越国曾先后两次名义上臣属于西汉,成为西汉的“外臣”。前112年,' + + '南越国末代君主赵建德与西汉发生战争,被汉武帝于前111年所灭。南越国共存在93年,' + + '历经五代君主。南越国是岭南地区的第一个有记载的政权国家,采用封建制和郡县制并存的制度,' + + '它的建立保证了秦末乱世岭南地区社会秩序的稳定,有效的改善了岭南地区落后的政治、##济现状。\n'; + +const tmpdir = require('../common/tmpdir'); +tmpdir.refresh(); + +// Test that empty file will be created and have content added. +const filename = join(tmpdir.path, 'append-sync.txt'); + +fs.appendFileSync(filename, data); + +const fileData = fs.readFileSync(filename); + +assert.strictEqual(Buffer.byteLength(data), fileData.length); + +// Test that appends data to a non empty file. +const filename2 = join(tmpdir.path, 'append-sync2.txt'); +fs.writeFileSync(filename2, currentFileData); + +fs.appendFileSync(filename2, data); + +const fileData2 = fs.readFileSync(filename2); + +assert.strictEqual(Buffer.byteLength(data) + currentFileData.length, + fileData2.length); + +// Test that appendFileSync accepts buffers. +const filename3 = join(tmpdir.path, 'append-sync3.txt'); +fs.writeFileSync(filename3, currentFileData); + +const buf = Buffer.from(data, 'utf8'); +fs.appendFileSync(filename3, buf); + +const fileData3 = fs.readFileSync(filename3); + +assert.strictEqual(buf.length + currentFileData.length, fileData3.length); + +const filename4 = join(tmpdir.path, 'append-sync4.txt'); +fs.writeFileSync(filename4, currentFileData, common.mustNotMutateObjectDeep({ mode: m })); + +[ + true, false, 0, 1, Infinity, () => {}, {}, [], undefined, null, +].forEach((value) => { + assert.throws( + () => fs.appendFileSync(filename4, value, common.mustNotMutateObjectDeep({ mode: m })), + { message: /data/, code: 'ERR_INVALID_ARG_TYPE' } + ); +}); +fs.appendFileSync(filename4, `${num}`, common.mustNotMutateObjectDeep({ mode: m })); + +// Windows permissions aren't Unix. +if (!common.isWindows) { + const st = fs.statSync(filename4); + assert.strictEqual(st.mode & 0o700, m); +} + +const fileData4 = fs.readFileSync(filename4); + +assert.strictEqual(Buffer.byteLength(String(num)) + currentFileData.length, + fileData4.length); + +// Test that appendFile accepts file descriptors. +const filename5 = join(tmpdir.path, 'append-sync5.txt'); +fs.writeFileSync(filename5, currentFileData); + +const filename5fd = fs.openSync(filename5, 'a+', 0o600); +fs.appendFileSync(filename5fd, data); +fs.closeSync(filename5fd); + +const fileData5 = fs.readFileSync(filename5); + +assert.strictEqual(Buffer.byteLength(data) + currentFileData.length, + fileData5.length); diff --git a/cli/tests/node_compat/test/parallel/test-fs-append-file.js b/cli/tests/node_compat/test/parallel/test-fs-append-file.js new file mode 100644 index 00000000000000..41c6be6848dd3d --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-fs-append-file.js @@ -0,0 +1,202 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 16.13.0 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. + +'use strict'; +const common = require('../common'); +const assert = require('assert'); +const fs = require('fs'); +const join = require('path').join; + +const tmpdir = require('../common/tmpdir'); + +const currentFileData = 'ABCD'; + +const s = '南越国是前203年至前111年存在于岭南地区的一个国家,国都位于番禺,疆域包括今天中国的广东、' + + '广西两省区的大部份地区,福建省、湖南、贵州、云南的一小部份地区和越南的北部。' + + '南越国是秦朝灭亡后,由南海郡尉赵佗于前203年起兵兼并桂林郡和象郡后建立。' + + '前196年和前179年,南越国曾先后两次名义上臣属于西汉,成为西汉的“外臣”。前112年,' + + '南越国末代君主赵建德与西汉发生战争,被汉武帝于前111年所灭。南越国共存在93年,' + + '历经五代君主。南越国是岭南地区的第一个有记载的政权国家,采用封建制和郡县制并存的制度,' + + '它的建立保证了秦末乱世岭南地区社会秩序的稳定,有效的改善了岭南地区落后的政治、##济现状。\n'; + +tmpdir.refresh(); + +const throwNextTick = (e) => { process.nextTick(() => { throw e; }); }; + +// Test that empty file will be created and have content added (callback API). +{ + const filename = join(tmpdir.path, 'append.txt'); + + fs.appendFile(filename, s, common.mustSucceed(() => { + fs.readFile(filename, common.mustSucceed((buffer) => { + assert.strictEqual(Buffer.byteLength(s), buffer.length); + })); + })); +} + +// Test that empty file will be created and have content added (promise API). +{ + const filename = join(tmpdir.path, 'append-promise.txt'); + + fs.promises.appendFile(filename, s) + .then(common.mustCall(() => fs.promises.readFile(filename))) + .then((buffer) => { + assert.strictEqual(Buffer.byteLength(s), buffer.length); + }) + .catch(throwNextTick); +} + +// Test that appends data to a non-empty file (callback API). +{ + const filename = join(tmpdir.path, 'append-non-empty.txt'); + fs.writeFileSync(filename, currentFileData); + + fs.appendFile(filename, s, common.mustSucceed(() => { + fs.readFile(filename, common.mustSucceed((buffer) => { + assert.strictEqual(Buffer.byteLength(s) + currentFileData.length, + buffer.length); + })); + })); +} + +// Test that appends data to a non-empty file (promise API). +{ + const filename = join(tmpdir.path, 'append-non-empty-promise.txt'); + fs.writeFileSync(filename, currentFileData); + + fs.promises.appendFile(filename, s) + .then(common.mustCall(() => fs.promises.readFile(filename))) + .then((buffer) => { + assert.strictEqual(Buffer.byteLength(s) + currentFileData.length, + buffer.length); + }) + .catch(throwNextTick); +} + +// Test that appendFile accepts buffers (callback API). +{ + const filename = join(tmpdir.path, 'append-buffer.txt'); + fs.writeFileSync(filename, currentFileData); + + const buf = Buffer.from(s, 'utf8'); + + fs.appendFile(filename, buf, common.mustSucceed(() => { + fs.readFile(filename, common.mustSucceed((buffer) => { + assert.strictEqual(buf.length + currentFileData.length, buffer.length); + })); + })); +} + +// Test that appendFile accepts buffers (promises API). +{ + const filename = join(tmpdir.path, 'append-buffer-promises.txt'); + fs.writeFileSync(filename, currentFileData); + + const buf = Buffer.from(s, 'utf8'); + + fs.promises.appendFile(filename, buf) + .then(common.mustCall(() => fs.promises.readFile(filename))) + .then((buffer) => { + assert.strictEqual(buf.length + currentFileData.length, buffer.length); + }) + .catch(throwNextTick); +} + +// Test that appendFile does not accept invalid data type (callback API). +[false, 5, {}, null, undefined].forEach(async (data) => { + const errObj = { + code: 'ERR_INVALID_ARG_TYPE', + message: /"data"|"buffer"/ + }; + const filename = join(tmpdir.path, 'append-invalid-data.txt'); + + assert.throws( + () => fs.appendFile(filename, data, common.mustNotCall()), + errObj + ); + + assert.throws( + () => fs.appendFileSync(filename, data), + errObj + ); + + await assert.rejects( + fs.promises.appendFile(filename, data), + errObj + ); + // The filename shouldn't exist if throwing error. + assert.throws( + () => fs.statSync(filename), + { + code: 'ENOENT', + message: /no such file or directory/ + } + ); +}); + +// Test that appendFile accepts file descriptors (callback API). +{ + const filename = join(tmpdir.path, 'append-descriptors.txt'); + fs.writeFileSync(filename, currentFileData); + + fs.open(filename, 'a+', common.mustSucceed((fd) => { + fs.appendFile(fd, s, common.mustSucceed(() => { + fs.close(fd, common.mustSucceed(() => { + fs.readFile(filename, common.mustSucceed((buffer) => { + assert.strictEqual(Buffer.byteLength(s) + currentFileData.length, + buffer.length); + })); + })); + })); + })); +} + +// FIXME(F3n67u): fs.promises.appendFile support FileHandle +// Test that appendFile accepts file descriptors (promises API). +// { +// const filename = join(tmpdir.path, 'append-descriptors-promises.txt'); +// fs.writeFileSync(filename, currentFileData); + +// let fd; +// fs.promises.open(filename, 'a+') +// .then(common.mustCall((fileDescriptor) => { +// fd = fileDescriptor; +// return fs.promises.appendFile(fd, s); +// })) +// .then(common.mustCall(() => fd.close())) +// .then(common.mustCall(() => fs.promises.readFile(filename))) +// .then(common.mustCall((buffer) => { +// assert.strictEqual(Buffer.byteLength(s) + currentFileData.length, +// buffer.length); +// })) +// .catch(throwNextTick); +// } + +assert.throws( + () => fs.appendFile(join(tmpdir.path, 'append6.txt'), console.log), + { code: 'ERR_INVALID_ARG_TYPE' }); diff --git a/cli/tests/node_compat/test/parallel/test-fs-chmod-mask.js b/cli/tests/node_compat/test/parallel/test-fs-chmod-mask.js new file mode 100644 index 00000000000000..f11567c7ec0f6c --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-fs-chmod-mask.js @@ -0,0 +1,106 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 16.13.0 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +'use strict'; + +// This tests that the lower bits of mode > 0o777 still works in fs APIs. + +const common = require('../common'); +const assert = require('assert'); +const path = require('path'); +const fs = require('fs'); + +// TODO(f3n67u): fs.chmod is not supported in Windows +if (common.isWindows) { + return; +} + +let mode; +// On Windows chmod is only able to manipulate write permission +if (common.isWindows) { + mode = 0o444; // read-only +} else { + mode = 0o777; +} + +const maskToIgnore = 0o10000; + +const tmpdir = require('../common/tmpdir'); +tmpdir.refresh(); + +function test(mode, asString) { + const suffix = asString ? 'str' : 'num'; + const input = asString ? + (mode | maskToIgnore).toString(8) : (mode | maskToIgnore); + + { + const file = path.join(tmpdir.path, `chmod-async-${suffix}.txt`); + fs.writeFileSync(file, 'test', 'utf-8'); + + fs.chmod(file, input, common.mustSucceed(() => { + assert.strictEqual(fs.statSync(file).mode & 0o777, mode); + })); + } + + { + const file = path.join(tmpdir.path, `chmodSync-${suffix}.txt`); + fs.writeFileSync(file, 'test', 'utf-8'); + + fs.chmodSync(file, input); + assert.strictEqual(fs.statSync(file).mode & 0o777, mode); + } + + // TODO(f3n67u): implement fs.fchmod + // { + // const file = path.join(tmpdir.path, `fchmod-async-${suffix}.txt`); + // fs.writeFileSync(file, 'test', 'utf-8'); + // fs.open(file, 'w', common.mustSucceed((fd) => { + // fs.fchmod(fd, input, common.mustSucceed(() => { + // assert.strictEqual(fs.fstatSync(fd).mode & 0o777, mode); + // fs.close(fd, assert.ifError); + // })); + // })); + // } + + // TODO(f3n67u): implement fs.fchmodSync + // { + // const file = path.join(tmpdir.path, `fchmodSync-${suffix}.txt`); + // fs.writeFileSync(file, 'test', 'utf-8'); + // const fd = fs.openSync(file, 'w'); + + // fs.fchmodSync(fd, input); + // assert.strictEqual(fs.fstatSync(fd).mode & 0o777, mode); + + // fs.close(fd, assert.ifError); + // } + + // TODO(f3n67u): implement fs.lchmod + // if (fs.lchmod) { + // const link = path.join(tmpdir.path, `lchmod-src-${suffix}`); + // const file = path.join(tmpdir.path, `lchmod-dest-${suffix}`); + // fs.writeFileSync(file, 'test', 'utf-8'); + // fs.symlinkSync(file, link); + + // fs.lchmod(link, input, common.mustSucceed(() => { + // assert.strictEqual(fs.lstatSync(link).mode & 0o777, mode); + // })); + // } + + // TODO(f3n67u): implement fs.lchmodSync + // if (fs.lchmodSync) { + // const link = path.join(tmpdir.path, `lchmodSync-src-${suffix}`); + // const file = path.join(tmpdir.path, `lchmodSync-dest-${suffix}`); + // fs.writeFileSync(file, 'test', 'utf-8'); + // fs.symlinkSync(file, link); + + // fs.lchmodSync(link, input); + // assert.strictEqual(fs.lstatSync(link).mode & 0o777, mode); + // } +} + +test(mode, true); +test(mode, false); diff --git a/cli/tests/node_compat/test/parallel/test-fs-chmod.js b/cli/tests/node_compat/test/parallel/test-fs-chmod.js new file mode 100644 index 00000000000000..b5f524f6424c31 --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-fs-chmod.js @@ -0,0 +1,167 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 16.13.0 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. + +'use strict'; +const common = require('../common'); +const assert = require('assert'); +const path = require('path'); +const fs = require('fs'); + +let mode_async; +let mode_sync; + +// Need to hijack fs.open/close to make sure that things +// get closed once they're opened. +fs._open = fs.open; +fs._openSync = fs.openSync; +fs.open = open; +fs.openSync = openSync; +fs._close = fs.close; +fs._closeSync = fs.closeSync; +fs.close = close; +fs.closeSync = closeSync; + +let openCount = 0; + +function open() { + openCount++; + return fs._open.apply(fs, arguments); +} + +function openSync() { + openCount++; + return fs._openSync.apply(fs, arguments); +} + +function close() { + openCount--; + return fs._close.apply(fs, arguments); +} + +function closeSync() { + openCount--; + return fs._closeSync.apply(fs, arguments); +} + +// TODO(f3n67u): fs.chmod is not supported in Windows +if (common.isWindows) { + return; +} + +// On Windows chmod is only able to manipulate write permission +if (common.isWindows) { + mode_async = 0o400; // read-only + mode_sync = 0o600; // read-write +} else { + mode_async = 0o777; + mode_sync = 0o644; +} + +const tmpdir = require('../common/tmpdir'); +tmpdir.refresh(); + +const file1 = path.join(tmpdir.path, 'a.js'); +const file2 = path.join(tmpdir.path, 'a1.js'); + +// Create file1. +fs.closeSync(fs.openSync(file1, 'w')); + +fs.chmod(file1, mode_async.toString(8), common.mustSucceed(() => { + if (common.isWindows) { + assert.ok((fs.statSync(file1).mode & 0o777) & mode_async); + } else { + assert.strictEqual(fs.statSync(file1).mode & 0o777, mode_async); + } + + fs.chmodSync(file1, mode_sync); + if (common.isWindows) { + assert.ok((fs.statSync(file1).mode & 0o777) & mode_sync); + } else { + assert.strictEqual(fs.statSync(file1).mode & 0o777, mode_sync); + } +})); + +// TODO(f3n67u): implement fs.fchmod +// fs.open(file2, 'w', common.mustSucceed((fd) => { +// fs.fchmod(fd, mode_async.toString(8), common.mustSucceed(() => { +// if (common.isWindows) { +// assert.ok((fs.fstatSync(fd).mode & 0o777) & mode_async); +// } else { +// assert.strictEqual(fs.fstatSync(fd).mode & 0o777, mode_async); +// } + +// assert.throws( +// () => fs.fchmod(fd, {}), +// { +// code: 'ERR_INVALID_ARG_TYPE', +// } +// ); + +// fs.fchmodSync(fd, mode_sync); +// if (common.isWindows) { +// assert.ok((fs.fstatSync(fd).mode & 0o777) & mode_sync); +// } else { +// assert.strictEqual(fs.fstatSync(fd).mode & 0o777, mode_sync); +// } + +// fs.close(fd, assert.ifError); +// })); +// })); + + +// TODO(f3n67u): implement fs.lchmod +// // lchmod +// if (fs.lchmod) { +// const link = path.join(tmpdir.path, 'symbolic-link'); + +// fs.symlinkSync(file2, link); + +// fs.lchmod(link, mode_async, common.mustSucceed(() => { +// assert.strictEqual(fs.lstatSync(link).mode & 0o777, mode_async); + +// fs.lchmodSync(link, mode_sync); +// assert.strictEqual(fs.lstatSync(link).mode & 0o777, mode_sync); + +// })); +// } + +[false, 1, {}, [], null, undefined].forEach((input) => { + const errObj = { + code: 'ERR_INVALID_ARG_TYPE', + name: 'TypeError', + message: 'The "path" argument must be of type string or an instance ' + + 'of Buffer or URL.' + + common.invalidArgTypeHelper(input) + }; + assert.throws(() => fs.chmod(input, 1, common.mustNotCall()), errObj); + assert.throws(() => fs.chmodSync(input, 1), errObj); +}); + +process.on('exit', function() { + assert.strictEqual(openCount, 0); +}); diff --git a/cli/tests/node_compat/test/parallel/test-fs-chown-type-check.js b/cli/tests/node_compat/test/parallel/test-fs-chown-type-check.js new file mode 100644 index 00000000000000..a015fc18267202 --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-fs-chown-type-check.js @@ -0,0 +1,60 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +'use strict'; + +const common = require('../common'); +const assert = require('assert'); +const fs = require('fs'); + +[false, 1, {}, [], null, undefined].forEach((i) => { + assert.throws( + () => fs.chown(i, 1, 1, common.mustNotCall()), + { + code: 'ERR_INVALID_ARG_TYPE', + name: 'TypeError' + } + ); + assert.throws( + () => fs.chownSync(i, 1, 1), + { + code: 'ERR_INVALID_ARG_TYPE', + name: 'TypeError' + } + ); +}); + +[false, 'test', {}, [], null, undefined].forEach((i) => { + assert.throws( + () => fs.chown('not_a_file_that_exists', i, 1, common.mustNotCall()), + { + code: 'ERR_INVALID_ARG_TYPE', + name: 'TypeError' + } + ); + assert.throws( + () => fs.chown('not_a_file_that_exists', 1, i, common.mustNotCall()), + { + code: 'ERR_INVALID_ARG_TYPE', + name: 'TypeError' + } + ); + assert.throws( + () => fs.chownSync('not_a_file_that_exists', i, 1), + { + code: 'ERR_INVALID_ARG_TYPE', + name: 'TypeError' + } + ); + assert.throws( + () => fs.chownSync('not_a_file_that_exists', 1, i), + { + code: 'ERR_INVALID_ARG_TYPE', + name: 'TypeError' + } + ); +}); diff --git a/cli/tests/node_compat/test/parallel/test-fs-copyfile.js b/cli/tests/node_compat/test/parallel/test-fs-copyfile.js new file mode 100644 index 00000000000000..94f49363689eeb --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-fs-copyfile.js @@ -0,0 +1,176 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +// Flags: --expose-internals +'use strict'; +const common = require('../common'); +const fixtures = require('../common/fixtures'); +const tmpdir = require('../common/tmpdir'); +const assert = require('assert'); +const fs = require('fs'); +const { internalBinding } = require('internal/test/binding'); +const { + UV_ENOENT, + UV_EEXIST +} = internalBinding('uv'); +const path = require('path'); +const src = fixtures.path('a.js'); +const dest = path.join(tmpdir.path, 'copyfile.out'); +const { + COPYFILE_EXCL, + COPYFILE_FICLONE, + COPYFILE_FICLONE_FORCE, + UV_FS_COPYFILE_EXCL, + UV_FS_COPYFILE_FICLONE, + UV_FS_COPYFILE_FICLONE_FORCE +} = fs.constants; + +function verify(src, dest) { + const srcData = fs.readFileSync(src, 'utf8'); + const srcStat = fs.statSync(src); + const destData = fs.readFileSync(dest, 'utf8'); + const destStat = fs.statSync(dest); + + assert.strictEqual(srcData, destData); + assert.strictEqual(srcStat.mode, destStat.mode); + assert.strictEqual(srcStat.size, destStat.size); +} + +tmpdir.refresh(); + +// Verify that flags are defined. +assert.strictEqual(typeof COPYFILE_EXCL, 'number'); +assert.strictEqual(typeof COPYFILE_FICLONE, 'number'); +assert.strictEqual(typeof COPYFILE_FICLONE_FORCE, 'number'); +assert.strictEqual(typeof UV_FS_COPYFILE_EXCL, 'number'); +assert.strictEqual(typeof UV_FS_COPYFILE_FICLONE, 'number'); +assert.strictEqual(typeof UV_FS_COPYFILE_FICLONE_FORCE, 'number'); +assert.strictEqual(COPYFILE_EXCL, UV_FS_COPYFILE_EXCL); +assert.strictEqual(COPYFILE_FICLONE, UV_FS_COPYFILE_FICLONE); +assert.strictEqual(COPYFILE_FICLONE_FORCE, UV_FS_COPYFILE_FICLONE_FORCE); + +// Verify that files are overwritten when no flags are provided. +fs.writeFileSync(dest, '', 'utf8'); +const result = fs.copyFileSync(src, dest); +assert.strictEqual(result, undefined); +verify(src, dest); + +// Verify that files are overwritten with default flags. +fs.copyFileSync(src, dest, 0); +verify(src, dest); + +// Verify that UV_FS_COPYFILE_FICLONE can be used. +fs.unlinkSync(dest); +fs.copyFileSync(src, dest, UV_FS_COPYFILE_FICLONE); +verify(src, dest); + +// Verify that COPYFILE_FICLONE_FORCE can be used. +try { + fs.unlinkSync(dest); + fs.copyFileSync(src, dest, COPYFILE_FICLONE_FORCE); + verify(src, dest); +} catch (err) { + assert.strictEqual(err.syscall, 'copyfile'); + assert(err.code === 'ENOTSUP' || err.code === 'ENOTTY' || + err.code === 'ENOSYS' || err.code === 'EXDEV'); + assert.strictEqual(err.path, src); + assert.strictEqual(err.dest, dest); +} + +// Copies asynchronously. +tmpdir.refresh(); // Don't use unlinkSync() since the last test may fail. +fs.copyFile(src, dest, common.mustSucceed(() => { + verify(src, dest); + + // Copy asynchronously with flags. + fs.copyFile(src, dest, COPYFILE_EXCL, common.mustCall((err) => { + if (err.code === 'ENOENT') { // Could be ENOENT or EEXIST + assert.strictEqual(err.message, + 'ENOENT: no such file or directory, copyfile ' + + `'${src}' -> '${dest}'`); + assert.strictEqual(err.errno, UV_ENOENT); + assert.strictEqual(err.code, 'ENOENT'); + assert.strictEqual(err.syscall, 'copyfile'); + } else { + assert.strictEqual(err.message, + 'EEXIST: file already exists, copyfile ' + + `'${src}' -> '${dest}'`); + assert.strictEqual(err.errno, UV_EEXIST); + assert.strictEqual(err.code, 'EEXIST'); + assert.strictEqual(err.syscall, 'copyfile'); + } + })); +})); + +// Throws if callback is not a function. +assert.throws(() => { + fs.copyFile(src, dest, 0, 0); +}, { + code: 'ERR_INVALID_ARG_TYPE', + name: 'TypeError' +}); + +// Throws if the source path is not a string. +[false, 1, {}, [], null, undefined].forEach((i) => { + assert.throws( + () => fs.copyFile(i, dest, common.mustNotCall()), + { + code: 'ERR_INVALID_ARG_TYPE', + name: 'TypeError', + message: /src/ + } + ); + assert.throws( + () => fs.copyFile(src, i, common.mustNotCall()), + { + code: 'ERR_INVALID_ARG_TYPE', + name: 'TypeError', + message: /dest/ + } + ); + assert.throws( + () => fs.copyFileSync(i, dest), + { + code: 'ERR_INVALID_ARG_TYPE', + name: 'TypeError', + message: /src/ + } + ); + assert.throws( + () => fs.copyFileSync(src, i), + { + code: 'ERR_INVALID_ARG_TYPE', + name: 'TypeError', + message: /dest/ + } + ); +}); + +assert.throws(() => { + fs.copyFileSync(src, dest, 'r'); +}, { + code: 'ERR_INVALID_ARG_TYPE', + name: 'TypeError', + message: /mode/ +}); + +assert.throws(() => { + fs.copyFileSync(src, dest, 8); +}, { + code: 'ERR_OUT_OF_RANGE', + name: 'RangeError', + message: 'The value of "mode" is out of range. It must be an integer ' + + '>= 0 && <= 7. Received 8' +}); + +assert.throws(() => { + fs.copyFile(src, dest, 'r', common.mustNotCall()); +}, { + code: 'ERR_INVALID_ARG_TYPE', + name: 'TypeError', + message: /mode/ +}); diff --git a/cli/tests/node_compat/test/parallel/test-fs-empty-readStream.js b/cli/tests/node_compat/test/parallel/test-fs-empty-readStream.js new file mode 100644 index 00000000000000..086798caad6f5a --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-fs-empty-readStream.js @@ -0,0 +1,57 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. + +'use strict'; +const common = require('../common'); +const assert = require('assert'); +const fs = require('fs'); +const fixtures = require('../common/fixtures'); + +const emptyFile = fixtures.path('empty.txt'); + +fs.open(emptyFile, 'r', common.mustSucceed((fd) => { + const read = fs.createReadStream(emptyFile, { fd }); + + read.once('data', common.mustNotCall('data event should not emit')); + + read.once('end', common.mustCall()); +})); + +fs.open(emptyFile, 'r', common.mustSucceed((fd) => { + const read = fs.createReadStream(emptyFile, { fd }); + + read.pause(); + + read.once('data', common.mustNotCall('data event should not emit')); + + read.once('end', common.mustNotCall('end event should not emit')); + + setTimeout(common.mustCall(() => { + assert.strictEqual(read.isPaused(), true); + }), common.platformTimeout(50)); +})); diff --git a/cli/tests/node_compat/test/parallel/test-fs-mkdir.js b/cli/tests/node_compat/test/parallel/test-fs-mkdir.js new file mode 100644 index 00000000000000..5a3897e91c0cce --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-fs-mkdir.js @@ -0,0 +1,379 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.8.0 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. + +'use strict'; +const common = require('../common'); +const assert = require('assert'); +const fs = require('fs'); +const path = require('path'); + +const tmpdir = require('../common/tmpdir'); +tmpdir.refresh(); + +let dirc = 0; +function nextdir() { + return `test${++dirc}`; +} + +// fs.mkdir creates directory using assigned path +{ + const pathname = path.join(tmpdir.path, nextdir()); + + fs.mkdir(pathname, common.mustCall(function(err) { + assert.strictEqual(err, null); + assert.strictEqual(fs.existsSync(pathname), true); + })); +} + +// fs.mkdir creates directory with assigned mode value +{ + const pathname = path.join(tmpdir.path, nextdir()); + + fs.mkdir(pathname, 0o777, common.mustCall(function(err) { + assert.strictEqual(err, null); + assert.strictEqual(fs.existsSync(pathname), true); + })); +} + +// fs.mkdir creates directory with mode passed as an options object +{ + const pathname = path.join(tmpdir.path, nextdir()); + + fs.mkdir(pathname, common.mustNotMutateObjectDeep({ mode: 0o777 }), common.mustCall(function(err) { + assert.strictEqual(err, null); + assert.strictEqual(fs.existsSync(pathname), true); + })); +} + +// fs.mkdirSync creates directory with mode passed as an options object +{ + const pathname = path.join(tmpdir.path, nextdir()); + + fs.mkdirSync(pathname, common.mustNotMutateObjectDeep({ mode: 0o777 })); + + assert.strictEqual(fs.existsSync(pathname), true); +} + +// mkdirSync successfully creates directory from given path +{ + const pathname = path.join(tmpdir.path, nextdir()); + + fs.mkdirSync(pathname); + + const exists = fs.existsSync(pathname); + assert.strictEqual(exists, true); +} + +// mkdirSync and mkdir require path to be a string, buffer or url. +// Anything else generates an error. +[false, 1, {}, [], null, undefined].forEach((i) => { + assert.throws( + () => fs.mkdir(i, common.mustNotCall()), + { + code: 'ERR_INVALID_ARG_TYPE', + name: 'TypeError' + } + ); + assert.throws( + () => fs.mkdirSync(i), + { + code: 'ERR_INVALID_ARG_TYPE', + name: 'TypeError' + } + ); +}); + +// mkdirpSync when both top-level, and sub-folders do not exist. +{ + const pathname = path.join(tmpdir.path, nextdir(), nextdir()); + + fs.mkdirSync(pathname, common.mustNotMutateObjectDeep({ recursive: true })); + + const exists = fs.existsSync(pathname); + assert.strictEqual(exists, true); + assert.strictEqual(fs.statSync(pathname).isDirectory(), true); +} + +// mkdirpSync when folder already exists. +{ + const pathname = path.join(tmpdir.path, nextdir(), nextdir()); + + fs.mkdirSync(pathname, { recursive: true }); + // Should not cause an error. + fs.mkdirSync(pathname, { recursive: true }); + + const exists = fs.existsSync(pathname); + assert.strictEqual(exists, true); + assert.strictEqual(fs.statSync(pathname).isDirectory(), true); +} + +// mkdirpSync ../ +{ + const pathname = `${tmpdir.path}/${nextdir()}/../${nextdir()}/${nextdir()}`; + fs.mkdirSync(pathname, { recursive: true }); + const exists = fs.existsSync(pathname); + assert.strictEqual(exists, true); + assert.strictEqual(fs.statSync(pathname).isDirectory(), true); +} + +// mkdirpSync when path is a file. +{ + const pathname = path.join(tmpdir.path, nextdir(), nextdir()); + + fs.mkdirSync(path.dirname(pathname)); + fs.writeFileSync(pathname, '', 'utf8'); + + assert.throws( + () => { fs.mkdirSync(pathname, common.mustNotMutateObjectDeep({ recursive: true })); }, + { + code: 'EEXIST', + message: /EEXIST: .*mkdir/, + name: 'Error', + syscall: 'mkdir', + } + ); +} + +// mkdirpSync when part of the path is a file. +{ + const filename = path.join(tmpdir.path, nextdir(), nextdir()); + const pathname = path.join(filename, nextdir(), nextdir()); + + fs.mkdirSync(path.dirname(filename)); + fs.writeFileSync(filename, '', 'utf8'); + + assert.throws( + () => { fs.mkdirSync(pathname, { recursive: true }); }, + { + code: 'ENOTDIR', + message: /ENOTDIR: .*mkdir/, + name: 'Error', + syscall: 'mkdir', + path: pathname // See: https://github.com/nodejs/node/issues/28015 + } + ); +} + +// `mkdirp` when folder does not yet exist. +{ + const pathname = path.join(tmpdir.path, nextdir(), nextdir()); + + fs.mkdir(pathname, common.mustNotMutateObjectDeep({ recursive: true }), common.mustCall(function(err) { + assert.strictEqual(err, null); + assert.strictEqual(fs.existsSync(pathname), true); + assert.strictEqual(fs.statSync(pathname).isDirectory(), true); + })); +} + +// `mkdirp` when path is a file. +{ + const pathname = path.join(tmpdir.path, nextdir(), nextdir()); + + fs.mkdirSync(path.dirname(pathname)); + fs.writeFileSync(pathname, '', 'utf8'); + fs.mkdir(pathname, common.mustNotMutateObjectDeep({ recursive: true }), common.mustCall((err) => { + assert.strictEqual(err.code, 'EEXIST'); + // TODO(wafuwafu13): Enable this + // assert.strictEqual(err.syscall, 'mkdir'); + assert.strictEqual(fs.statSync(pathname).isDirectory(), false); + })); +} + +// `mkdirp` when part of the path is a file. +{ + const filename = path.join(tmpdir.path, nextdir(), nextdir()); + const pathname = path.join(filename, nextdir(), nextdir()); + + fs.mkdirSync(path.dirname(filename)); + fs.writeFileSync(filename, '', 'utf8'); + fs.mkdir(pathname, common.mustNotMutateObjectDeep({ recursive: true }), common.mustCall((err) => { + // TODO(wafuwafu13): Enable this + // assert.strictEqual(err.code, 'ENOTDIR'); + // assert.strictEqual(err.syscall, 'mkdir'); + assert.strictEqual(fs.existsSync(pathname), false); + // See: https://github.com/nodejs/node/issues/28015 + // The path field varies slightly in Windows errors, vs., other platforms + // see: https://github.com/libuv/libuv/issues/2661, for this reason we + // use startsWith() rather than comparing to the full "pathname". + // TODO(wafuwafu13): Enable this + // assert(err.path.startsWith(filename)); + })); +} + +// mkdirpSync dirname loop +// XXX: windows and smartos have issues removing a directory that you're in. +if (common.isMainThread && (common.isLinux || common.isOSX)) { + const pathname = path.join(tmpdir.path, nextdir()); + fs.mkdirSync(pathname); + process.chdir(pathname); + fs.rmdirSync(pathname); + assert.throws( + () => { fs.mkdirSync('X', common.mustNotMutateObjectDeep({ recursive: true })); }, + { + code: 'ENOENT', + message: /ENOENT: .*mkdir/, + name: 'Error', + syscall: 'mkdir', + } + ); + fs.mkdir('X', common.mustNotMutateObjectDeep({ recursive: true }), (err) => { + assert.strictEqual(err.code, 'ENOENT'); + // TODO(wafuwafu13): Enable this + // assert.strictEqual(err.syscall, 'mkdir'); + }); +} + +// mkdirSync and mkdir require options.recursive to be a boolean. +// Anything else generates an error. +{ + const pathname = path.join(tmpdir.path, nextdir()); + ['', 1, {}, [], null, Symbol('test'), () => {}].forEach((recursive) => { + const received = common.invalidArgTypeHelper(recursive); + assert.throws( + () => fs.mkdir(pathname, common.mustNotMutateObjectDeep({ recursive }), common.mustNotCall()), + { + code: 'ERR_INVALID_ARG_TYPE', + name: 'TypeError', + message: 'The "options.recursive" property must be of type boolean.' + + received + } + ); + assert.throws( + () => fs.mkdirSync(pathname, common.mustNotMutateObjectDeep({ recursive })), + { + code: 'ERR_INVALID_ARG_TYPE', + name: 'TypeError', + message: 'The "options.recursive" property must be of type boolean.' + + received + } + ); + }); +} + +// `mkdirp` returns first folder created, when all folders are new. +{ + const dir1 = nextdir(); + const dir2 = nextdir(); + const firstPathCreated = path.join(tmpdir.path, dir1); + const pathname = path.join(tmpdir.path, dir1, dir2); + + fs.mkdir(pathname, common.mustNotMutateObjectDeep({ recursive: true }), common.mustCall(function(err, path) { + assert.strictEqual(err, null); + assert.strictEqual(fs.existsSync(pathname), true); + assert.strictEqual(fs.statSync(pathname).isDirectory(), true); + // TODO(wafuwafu13): Enable this + // assert.strictEqual(path, firstPathCreated); + })); +} + +// `mkdirp` returns first folder created, when last folder is new. +{ + const dir1 = nextdir(); + const dir2 = nextdir(); + const pathname = path.join(tmpdir.path, dir1, dir2); + fs.mkdirSync(path.join(tmpdir.path, dir1)); + fs.mkdir(pathname, common.mustNotMutateObjectDeep({ recursive: true }), common.mustCall(function(err, path) { + assert.strictEqual(err, null); + assert.strictEqual(fs.existsSync(pathname), true); + assert.strictEqual(fs.statSync(pathname).isDirectory(), true); + // TODO(wafuwafu13): Enable this + // assert.strictEqual(path, pathname); + })); +} + +// `mkdirp` returns undefined, when no new folders are created. +{ + const dir1 = nextdir(); + const dir2 = nextdir(); + const pathname = path.join(tmpdir.path, dir1, dir2); + fs.mkdirSync(path.join(tmpdir.path, dir1, dir2), common.mustNotMutateObjectDeep({ recursive: true })); + fs.mkdir(pathname, common.mustNotMutateObjectDeep({ recursive: true }), common.mustCall(function(err, path) { + assert.strictEqual(err, null); + assert.strictEqual(fs.existsSync(pathname), true); + assert.strictEqual(fs.statSync(pathname).isDirectory(), true); + assert.strictEqual(path, undefined); + })); +} + +// `mkdirp.sync` returns first folder created, when all folders are new. +{ + const dir1 = nextdir(); + const dir2 = nextdir(); + const firstPathCreated = path.join(tmpdir.path, dir1); + const pathname = path.join(tmpdir.path, dir1, dir2); + const p = fs.mkdirSync(pathname, common.mustNotMutateObjectDeep({ recursive: true })); + assert.strictEqual(fs.existsSync(pathname), true); + assert.strictEqual(fs.statSync(pathname).isDirectory(), true); + // TODO(wafuwafu13): Enable this + // assert.strictEqual(p, firstPathCreated); +} + +// `mkdirp.sync` returns first folder created, when last folder is new. +{ + const dir1 = nextdir(); + const dir2 = nextdir(); + const pathname = path.join(tmpdir.path, dir1, dir2); + fs.mkdirSync(path.join(tmpdir.path, dir1), common.mustNotMutateObjectDeep({ recursive: true })); + const p = fs.mkdirSync(pathname, common.mustNotMutateObjectDeep({ recursive: true })); + assert.strictEqual(fs.existsSync(pathname), true); + assert.strictEqual(fs.statSync(pathname).isDirectory(), true); + // TODO(wafuwafu13): Enable this + // assert.strictEqual(p, pathname); +} + +// `mkdirp.sync` returns undefined, when no new folders are created. +{ + const dir1 = nextdir(); + const dir2 = nextdir(); + const pathname = path.join(tmpdir.path, dir1, dir2); + fs.mkdirSync(path.join(tmpdir.path, dir1, dir2), common.mustNotMutateObjectDeep({ recursive: true })); + const p = fs.mkdirSync(pathname, common.mustNotMutateObjectDeep({ recursive: true })); + assert.strictEqual(fs.existsSync(pathname), true); + assert.strictEqual(fs.statSync(pathname).isDirectory(), true); + assert.strictEqual(p, undefined); +} + +// `mkdirp.promises` returns first folder created, when all folders are new. +{ + const dir1 = nextdir(); + const dir2 = nextdir(); + const firstPathCreated = path.join(tmpdir.path, dir1); + const pathname = path.join(tmpdir.path, dir1, dir2); + async function testCase() { + const p = await fs.promises.mkdir(pathname, common.mustNotMutateObjectDeep({ recursive: true })); + assert.strictEqual(fs.existsSync(pathname), true); + assert.strictEqual(fs.statSync(pathname).isDirectory(), true); + // TODO(wafuwafu13): Enable this + // assert.strictEqual(p, firstPathCreated); + } + testCase(); +} + +// Keep the event loop alive so the async mkdir() requests +// have a chance to run (since they don't ref the event loop). +process.nextTick(() => {}); diff --git a/cli/tests/node_compat/test/parallel/test-fs-open-flags.js b/cli/tests/node_compat/test/parallel/test-fs-open-flags.js new file mode 100644 index 00000000000000..adea1459d3373d --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-fs-open-flags.js @@ -0,0 +1,101 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. + +// Flags: --expose-internals +'use strict'; +const common = require('../common'); + +const fixtures = require('../common/fixtures'); + +const assert = require('assert'); +const fs = require('fs'); +const path = require('path'); + +// 0 if not found in fs.constants +const { O_APPEND = 0, + O_CREAT = 0, + O_EXCL = 0, + O_RDONLY = 0, + O_RDWR = 0, + O_SYNC = 0, + O_DSYNC = 0, + O_TRUNC = 0, + O_WRONLY = 0 } = fs.constants; + +const { stringToFlags } = require('internal/fs/utils'); + +assert.strictEqual(stringToFlags('r'), O_RDONLY); +assert.strictEqual(stringToFlags('r+'), O_RDWR); +assert.strictEqual(stringToFlags('rs+'), O_RDWR | O_SYNC); +assert.strictEqual(stringToFlags('sr+'), O_RDWR | O_SYNC); +assert.strictEqual(stringToFlags('w'), O_TRUNC | O_CREAT | O_WRONLY); +assert.strictEqual(stringToFlags('w+'), O_TRUNC | O_CREAT | O_RDWR); +assert.strictEqual(stringToFlags('a'), O_APPEND | O_CREAT | O_WRONLY); +assert.strictEqual(stringToFlags('a+'), O_APPEND | O_CREAT | O_RDWR); + +assert.strictEqual(stringToFlags('wx'), O_TRUNC | O_CREAT | O_WRONLY | O_EXCL); +assert.strictEqual(stringToFlags('xw'), O_TRUNC | O_CREAT | O_WRONLY | O_EXCL); +assert.strictEqual(stringToFlags('wx+'), O_TRUNC | O_CREAT | O_RDWR | O_EXCL); +assert.strictEqual(stringToFlags('xw+'), O_TRUNC | O_CREAT | O_RDWR | O_EXCL); +assert.strictEqual(stringToFlags('ax'), O_APPEND | O_CREAT | O_WRONLY | O_EXCL); +assert.strictEqual(stringToFlags('xa'), O_APPEND | O_CREAT | O_WRONLY | O_EXCL); +assert.strictEqual(stringToFlags('as'), O_APPEND | O_CREAT | O_WRONLY | O_SYNC); +assert.strictEqual(stringToFlags('sa'), O_APPEND | O_CREAT | O_WRONLY | O_SYNC); +assert.strictEqual(stringToFlags('ax+'), O_APPEND | O_CREAT | O_RDWR | O_EXCL); +assert.strictEqual(stringToFlags('xa+'), O_APPEND | O_CREAT | O_RDWR | O_EXCL); +assert.strictEqual(stringToFlags('as+'), O_APPEND | O_CREAT | O_RDWR | O_SYNC); +assert.strictEqual(stringToFlags('sa+'), O_APPEND | O_CREAT | O_RDWR | O_SYNC); + +('+ +a +r +w rw wa war raw r++ a++ w++ x +x x+ rx rx+ wxx wax xwx xxx') + .split(' ') + .forEach(function(flags) { + assert.throws( + () => stringToFlags(flags), + { code: 'ERR_INVALID_ARG_VALUE', name: 'TypeError' } + ); + }); + +assert.throws( + () => stringToFlags({}), + { code: 'ERR_INVALID_ARG_VALUE', name: 'TypeError' } +); + +assert.throws( + () => stringToFlags(true), + { code: 'ERR_INVALID_ARG_VALUE', name: 'TypeError' } +); + +if (common.isLinux || common.isOSX) { + const tmpdir = require('../common/tmpdir'); + tmpdir.refresh(); + const file = path.join(tmpdir.path, 'a.js'); + fs.copyFileSync(fixtures.path('a.js'), file); + fs.open(file, O_DSYNC, common.mustSucceed((fd) => { + fs.closeSync(fd); + })); +} diff --git a/cli/tests/node_compat/test/parallel/test-fs-open-mode-mask.js b/cli/tests/node_compat/test/parallel/test-fs-open-mode-mask.js new file mode 100644 index 00000000000000..d6ce372a466722 --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-fs-open-mode-mask.js @@ -0,0 +1,48 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +'use strict'; + +// This tests that the lower bits of mode > 0o777 still works in fs.open(). + +const common = require('../common'); +const assert = require('assert'); +const path = require('path'); +const fs = require('fs'); + +const mode = common.isWindows ? 0o444 : 0o644; + +const maskToIgnore = 0o10000; + +const tmpdir = require('../common/tmpdir'); +tmpdir.refresh(); + +function test(mode, asString) { + const suffix = asString ? 'str' : 'num'; + const input = asString ? + (mode | maskToIgnore).toString(8) : (mode | maskToIgnore); + + { + const file = path.join(tmpdir.path, `openSync-${suffix}.txt`); + const fd = fs.openSync(file, 'w+', input); + assert.strictEqual(fs.fstatSync(fd).mode & 0o777, mode); + fs.closeSync(fd); + assert.strictEqual(fs.statSync(file).mode & 0o777, mode); + } + + { + const file = path.join(tmpdir.path, `open-${suffix}.txt`); + fs.open(file, 'w+', input, common.mustSucceed((fd) => { + assert.strictEqual(fs.fstatSync(fd).mode & 0o777, mode); + fs.closeSync(fd); + assert.strictEqual(fs.statSync(file).mode & 0o777, mode); + })); + } +} + +test(mode, true); +test(mode, false); diff --git a/cli/tests/node_compat/test/parallel/test-fs-open-no-close.js b/cli/tests/node_compat/test/parallel/test-fs-open-no-close.js new file mode 100644 index 00000000000000..125c4835f38503 --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-fs-open-no-close.js @@ -0,0 +1,38 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +'use strict'; + +// Refs: https://github.com/nodejs/node/issues/34266 +// Failing to close a file should not keep the event loop open. + +const common = require('../common'); +const assert = require('assert'); + +const fs = require('fs'); + +const debuglog = (arg) => { + console.log(new Date().toLocaleString(), arg); +}; + +const tmpdir = require('../common/tmpdir'); +tmpdir.refresh(); + +let openFd; + +fs.open(`${tmpdir.path}/dummy`, 'wx+', common.mustCall((err, fd) => { + debuglog('fs open() callback'); + assert.ifError(err); + openFd = fd; +})); +debuglog('waiting for callback'); + +process.on('beforeExit', common.mustCall(() => { + if (openFd) { + fs.closeSync(openFd); + } +})); diff --git a/cli/tests/node_compat/test/parallel/test-fs-open-numeric-flags.js b/cli/tests/node_compat/test/parallel/test-fs-open-numeric-flags.js new file mode 100644 index 00000000000000..9e0161ca1ba99c --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-fs-open-numeric-flags.js @@ -0,0 +1,23 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +'use strict'; +require('../common'); + +const assert = require('assert'); +const fs = require('fs'); +const path = require('path'); + +const tmpdir = require('../common/tmpdir'); +tmpdir.refresh(); + +// O_WRONLY without O_CREAT shall fail with ENOENT +const pathNE = path.join(tmpdir.path, 'file-should-not-exist'); +assert.throws( + () => fs.openSync(pathNE, fs.constants.O_WRONLY), + (e) => e.code === 'ENOENT' +); diff --git a/cli/tests/node_compat/test/parallel/test-fs-open.js b/cli/tests/node_compat/test/parallel/test-fs-open.js new file mode 100644 index 00000000000000..631e96a2ef7852 --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-fs-open.js @@ -0,0 +1,128 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.8.0 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. + +'use strict'; +const common = require('../common'); +const assert = require('assert'); +const fs = require('fs'); + +let caughtException = false; + +try { + // Should throw ENOENT, not EBADF + // see https://github.com/joyent/node/pull/1228 + fs.openSync('/8hvftyuncxrt/path/to/file/that/does/not/exist', 'r'); +} catch (e) { + assert.strictEqual(e.code, 'ENOENT'); + caughtException = true; +} +assert.strictEqual(caughtException, true); + +fs.openSync(__filename); + +fs.open(__filename, common.mustSucceed()); + +fs.open(__filename, 'r', common.mustSucceed()); + +// TODO(wafuwafu13): Support 'rs' flag +// fs.open(__filename, 'rs', common.mustSucceed()); + +fs.open(__filename, 'r', 0, common.mustSucceed()); + +fs.open(__filename, 'r', null, common.mustSucceed()); + +async function promise() { + await fs.promises.open(__filename); + await fs.promises.open(__filename, 'r'); +} + +promise().then(common.mustCall()).catch(common.mustNotCall()); + +assert.throws( + () => fs.open(__filename, 'r', 'boom', common.mustNotCall()), + { + code: 'ERR_INVALID_ARG_VALUE', + name: 'TypeError' + } +); + +for (const extra of [[], ['r'], ['r', 0], ['r', 0, 'bad callback']]) { + assert.throws( + () => fs.open(__filename, ...extra), + { + code: 'ERR_INVALID_ARG_TYPE', + name: 'TypeError' + } + ); +} + +[false, 1, [], {}, null, undefined].forEach((i) => { + assert.throws( + () => fs.open(i, 'r', common.mustNotCall()), + { + code: 'ERR_INVALID_ARG_TYPE', + name: 'TypeError' + } + ); + assert.throws( + () => fs.openSync(i, 'r', common.mustNotCall()), + { + code: 'ERR_INVALID_ARG_TYPE', + name: 'TypeError' + } + ); + assert.rejects( + fs.promises.open(i, 'r'), + { + code: 'ERR_INVALID_ARG_TYPE', + name: 'TypeError' + } + ); +}); + +// Check invalid modes. +[false, [], {}].forEach((mode) => { + assert.throws( + () => fs.open(__filename, 'r', mode, common.mustNotCall()), + { + code: 'ERR_INVALID_ARG_TYPE' + } + ); + assert.throws( + () => fs.openSync(__filename, 'r', mode, common.mustNotCall()), + { + code: 'ERR_INVALID_ARG_TYPE' + } + ); + assert.rejects( + fs.promises.open(__filename, 'r', mode), + { + code: 'ERR_INVALID_ARG_TYPE' + } + ); +}); diff --git a/cli/tests/node_compat/test/parallel/test-fs-opendir.js b/cli/tests/node_compat/test/parallel/test-fs-opendir.js new file mode 100644 index 00000000000000..75c4aa074d5e0b --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-fs-opendir.js @@ -0,0 +1,300 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.8.0 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +'use strict'; + +const common = require('../common'); +const assert = require('assert'); +const fs = require('fs'); +const path = require('path'); + +const tmpdir = require('../common/tmpdir'); + +const testDir = tmpdir.path; +const files = ['empty', 'files', 'for', 'just', 'testing']; + +// Make sure tmp directory is clean +tmpdir.refresh(); + +// Create the necessary files +files.forEach(function(filename) { + fs.closeSync(fs.openSync(path.join(testDir, filename), 'w')); +}); + +function assertDirent(dirent) { + assert(dirent instanceof fs.Dirent); + assert.strictEqual(dirent.isFile(), true); + assert.strictEqual(dirent.isDirectory(), false); + // TODO(wafuwafu13): Support these method + // assert.strictEqual(dirent.isSocket(), false); + // assert.strictEqual(dirent.isBlockDevice(), false); + // assert.strictEqual(dirent.isCharacterDevice(), false); + // assert.strictEqual(dirent.isFIFO(), false); + assert.strictEqual(dirent.isSymbolicLink(), false); +} + +// NOTE: this error doesn't occur in Deno +const dirclosedError = { + code: 'ERR_DIR_CLOSED' +}; + +// NOTE: this error doesn't occur in Deno +const dirconcurrentError = { + code: 'ERR_DIR_CONCURRENT_OPERATION' +}; + +const invalidCallbackObj = { + code: 'ERR_INVALID_ARG_TYPE', + name: 'TypeError' +}; + +// Check the opendir Sync version +{ + const dir = fs.opendirSync(testDir); + const entries = files.map(() => { + const dirent = dir.readSync(); + assertDirent(dirent); + return dirent.name; + }); + assert.deepStrictEqual(files, entries.sort()); + + // dir.read should return null when no more entries exist + assert.strictEqual(dir.readSync(), null); + + // check .path + assert.strictEqual(dir.path, testDir); + + dir.closeSync(); + + // assert.throws(() => dir.readSync(), dirclosedError); + // assert.throws(() => dir.closeSync(), dirclosedError); +} + +// Check the opendir async version +fs.opendir(testDir, common.mustSucceed((dir) => { + let sync = true; + dir.read(common.mustSucceed((dirent) => { + assert(!sync); + + // Order is operating / file system dependent + assert(files.includes(dirent.name), `'files' should include ${dirent}`); + assertDirent(dirent); + + let syncInner = true; + dir.read(common.mustSucceed((dirent) => { + assert(!syncInner); + + dir.close(common.mustSucceed()); + })); + syncInner = false; + })); + sync = false; +})); + +// opendir() on file should throw ENOTDIR +assert.throws(function() { + fs.opendirSync(__filename); +}, /Error: ENOTDIR: not a directory/); + +assert.throws(function() { + fs.opendir(__filename); +}, /TypeError \[ERR_INVALID_ARG_TYPE\]: The "callback" argument must be of type function/); + +fs.opendir(__filename, common.mustCall(function(e) { + assert.strictEqual(e.code, 'ENOTDIR'); +})); + +[false, 1, [], {}, null, undefined].forEach((i) => { + assert.throws( + () => fs.opendir(i, common.mustNotCall()), + { + code: 'ERR_INVALID_ARG_TYPE', + name: 'TypeError' + } + ); + assert.throws( + () => fs.opendirSync(i), + { + code: 'ERR_INVALID_ARG_TYPE', + name: 'TypeError' + } + ); +}); + +// Promise-based tests +async function doPromiseTest() { + // Check the opendir Promise version + const dir = await fs.promises.opendir(testDir); + const entries = []; + + let i = files.length; + while (i--) { + const dirent = await dir.read(); + entries.push(dirent.name); + assertDirent(dirent); + } + + assert.deepStrictEqual(files, entries.sort()); + + // dir.read should return null when no more entries exist + assert.strictEqual(await dir.read(), null); + + await dir.close(); +} +doPromiseTest().then(common.mustCall()); + +// Async iterator +async function doAsyncIterTest() { + const entries = []; + for await (const dirent of await fs.promises.opendir(testDir)) { + entries.push(dirent.name); + assertDirent(dirent); + } + + assert.deepStrictEqual(files, entries.sort()); + + // Automatically closed during iterator +} +doAsyncIterTest().then(common.mustCall()); + +// Async iterators should do automatic cleanup + +async function doAsyncIterBreakTest() { + const dir = await fs.promises.opendir(testDir); + for await (const dirent of dir) { // eslint-disable-line no-unused-vars + break; + } + + // await assert.rejects(async () => dir.read(), dirclosedError); +} +doAsyncIterBreakTest().then(common.mustCall()); + +async function doAsyncIterReturnTest() { + const dir = await fs.promises.opendir(testDir); + await (async function() { + for await (const dirent of dir) { + return; + } + })(); + + // await assert.rejects(async () => dir.read(), dirclosedError); +} +doAsyncIterReturnTest().then(common.mustCall()); + +async function doAsyncIterThrowTest() { + const dir = await fs.promises.opendir(testDir); + try { + for await (const dirent of dir) { // eslint-disable-line no-unused-vars + throw new Error('oh no'); + } + } catch (err) { + if (err.message !== 'oh no') { + throw err; + } + } + + // await assert.rejects(async () => dir.read(), dirclosedError); +} +doAsyncIterThrowTest().then(common.mustCall()); + +// Check error thrown on invalid values of bufferSize +for (const bufferSize of [-1, 0, 0.5, 1.5, Infinity, NaN]) { + assert.throws( + () => fs.opendirSync(testDir, common.mustNotMutateObjectDeep({ bufferSize })), + { + code: 'ERR_OUT_OF_RANGE' + }); +} +for (const bufferSize of ['', '1', null]) { + assert.throws( + () => fs.opendirSync(testDir, common.mustNotMutateObjectDeep({ bufferSize })), + { + code: 'ERR_INVALID_ARG_TYPE' + }); +} + +// Check that passing a positive integer as bufferSize works +{ + const dir = fs.opendirSync(testDir, common.mustNotMutateObjectDeep({ bufferSize: 1024 })); + assertDirent(dir.readSync()); + dir.close(); +} + +// TODO(wafuwafu13): enable this +// // Check that when passing a string instead of function - throw an exception +// async function doAsyncIterInvalidCallbackTest() { +// const dir = await fs.promises.opendir(testDir); +// assert.throws(() => dir.close('not function'), invalidCallbackObj); +// } +// doAsyncIterInvalidCallbackTest().then(common.mustCall()); + +// Check first call to close() - should not report an error. +async function doAsyncIterDirClosedTest() { + const dir = await fs.promises.opendir(testDir); + await dir.close(); + // await assert.rejects(() => dir.close(), dirclosedError); +} +doAsyncIterDirClosedTest().then(common.mustCall()); + +// Check that readSync() and closeSync() during read() throw exceptions +async function doConcurrentAsyncAndSyncOps() { + const dir = await fs.promises.opendir(testDir); + const promise = dir.read(); + + // assert.throws(() => dir.closeSync(), dirconcurrentError); + // assert.throws(() => dir.readSync(), dirconcurrentError); + + await promise; + dir.closeSync(); +} +doConcurrentAsyncAndSyncOps().then(common.mustCall()); + +// TODO(wafuwafu13): enable this +// // Check read throw exceptions on invalid callback +// { +// const dir = fs.opendirSync(testDir); +// assert.throws(() => dir.read('INVALID_CALLBACK'), /ERR_INVALID_ARG_TYPE/); +// } + +// Check that concurrent read() operations don't do weird things. +async function doConcurrentAsyncOps() { + const dir = await fs.promises.opendir(testDir); + const promise1 = dir.read(); + const promise2 = dir.read(); + + assertDirent(await promise1); + assertDirent(await promise2); + dir.closeSync(); +} +doConcurrentAsyncOps().then(common.mustCall()); + +// Check that concurrent read() + close() operations don't do weird things. +async function doConcurrentAsyncMixedOps() { + const dir = await fs.promises.opendir(testDir); + const promise1 = dir.read(); + const promise2 = dir.close(); + + assertDirent(await promise1); + await promise2; +} +doConcurrentAsyncMixedOps().then(common.mustCall()); + +// Check if directory already closed - the callback should pass an error. +{ + const dir = fs.opendirSync(testDir); + dir.closeSync(); + dir.close(common.mustCall((error) => { + // assert.strictEqual(error.code, dirclosedError.code); + })); +} + +// Check if directory already closed - throw an promise exception. +{ + const dir = fs.opendirSync(testDir); + dir.closeSync(); + // assert.rejects(dir.close(), dirclosedError).then(common.mustCall()); +} diff --git a/cli/tests/node_compat/test/parallel/test-fs-read-stream-autoClose.js b/cli/tests/node_compat/test/parallel/test-fs-read-stream-autoClose.js new file mode 100644 index 00000000000000..7e7e3e0795c921 --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-fs-read-stream-autoClose.js @@ -0,0 +1,23 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +'use strict'; + +const common = require('../common'); +const fs = require('fs'); +const path = require('path'); +const assert = require('assert'); +const tmpdir = require('../common/tmpdir'); +const writeFile = path.join(tmpdir.path, 'write-autoClose.txt'); +tmpdir.refresh(); + +const file = fs.createWriteStream(writeFile, { autoClose: true }); + +file.on('finish', common.mustCall(() => { + assert.strictEqual(file.destroyed, false); +})); +file.end('asd'); diff --git a/cli/tests/node_compat/test/parallel/test-fs-read-stream-concurrent-reads.js b/cli/tests/node_compat/test/parallel/test-fs-read-stream-concurrent-reads.js new file mode 100644 index 00000000000000..fd4490d45bfa26 --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-fs-read-stream-concurrent-reads.js @@ -0,0 +1,54 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +'use strict'; +const common = require('../common'); +const fixtures = require('../common/fixtures'); +const assert = require('assert'); +const fs = require('fs'); + +// Test that concurrent file read streams don’t interfere with each other’s +// contents, and that the chunks generated by the reads only retain a +// 'reasonable' amount of memory. + +// Refs: https://github.com/nodejs/node/issues/21967 + +const filename = fixtures.path('loop.js'); // Some small non-homogeneous file. +const content = fs.readFileSync(filename); + +const N = 2000; +let started = 0; +let done = 0; + +const arrayBuffers = new Set(); + +function startRead() { + ++started; + const chunks = []; + fs.createReadStream(filename) + .on('data', (chunk) => { + chunks.push(chunk); + arrayBuffers.add(chunk.buffer); + }) + .on('end', common.mustCall(() => { + if (started < N) + startRead(); + assert.deepStrictEqual(Buffer.concat(chunks), content); + if (++done === N) { + const retainedMemory = + [...arrayBuffers].map((ab) => ab.byteLength).reduce((a, b) => a + b); + assert(retainedMemory / (N * content.length) <= 3, + `Retaining ${retainedMemory} bytes in ABs for ${N} ` + + `chunks of size ${content.length}`); + } + })); +} + +// Don’t start the reads all at once – that way we would have to allocate +// a large amount of memory upfront. +for (let i = 0; i < 6; ++i) + startRead(); diff --git a/cli/tests/node_compat/test/parallel/test-fs-read-stream-double-close.js b/cli/tests/node_compat/test/parallel/test-fs-read-stream-double-close.js new file mode 100644 index 00000000000000..e0e2222a71cd18 --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-fs-read-stream-double-close.js @@ -0,0 +1,26 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +'use strict'; + +const common = require('../common'); +const fs = require('fs'); + +{ + const s = fs.createReadStream(__filename); + + s.close(common.mustCall()); + s.close(common.mustCall()); +} + +{ + const s = fs.createReadStream(__filename); + + // This is a private API, but it is worth testing. close calls this + s.destroy(null, common.mustCall()); + s.destroy(null, common.mustCall()); +} diff --git a/cli/tests/node_compat/test/parallel/test-fs-read-stream-encoding.js b/cli/tests/node_compat/test/parallel/test-fs-read-stream-encoding.js new file mode 100644 index 00000000000000..a23bebc32d185d --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-fs-read-stream-encoding.js @@ -0,0 +1,24 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +'use strict'; +require('../common'); +const assert = require('assert'); +const fs = require('fs'); +const stream = require('stream'); +const fixtures = require('../common/fixtures'); +const encoding = 'base64'; + +const example = fixtures.path('x.txt'); +const assertStream = new stream.Writable({ + write: function(chunk, enc, next) { + const expected = Buffer.from('xyz'); + assert(chunk.equals(expected)); + } +}); +assertStream.setDefaultEncoding(encoding); +fs.createReadStream(example, encoding).pipe(assertStream); diff --git a/cli/tests/node_compat/test/parallel/test-fs-read-stream-fd.js b/cli/tests/node_compat/test/parallel/test-fs-read-stream-fd.js new file mode 100644 index 00000000000000..9e895dcec567d6 --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-fs-read-stream-fd.js @@ -0,0 +1,53 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. + +'use strict'; +const common = require('../common'); +const fs = require('fs'); +const assert = require('assert'); +const path = require('path'); +const tmpdir = require('../common/tmpdir'); +const file = path.join(tmpdir.path, '/read_stream_fd_test.txt'); +const input = 'hello world'; + +let output = ''; +tmpdir.refresh(); +fs.writeFileSync(file, input); + +const fd = fs.openSync(file, 'r'); +const stream = fs.createReadStream(null, { fd: fd, encoding: 'utf8' }); + +assert.strictEqual(stream.path, undefined); + +stream.on('data', common.mustCallAtLeast((data) => { + output += data; +})); + +process.on('exit', () => { + assert.strictEqual(output, input); +}); diff --git a/cli/tests/node_compat/test/parallel/test-fs-read-stream-inherit.js b/cli/tests/node_compat/test/parallel/test-fs-read-stream-inherit.js new file mode 100644 index 00000000000000..a498c5ade68c27 --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-fs-read-stream-inherit.js @@ -0,0 +1,212 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +'use strict'; + +const common = require('../common'); + +const assert = require('assert'); +const fs = require('fs'); +const fixtures = require('../common/fixtures'); + +const fn = fixtures.path('elipses.txt'); +const rangeFile = fixtures.path('x.txt'); + +{ + let paused = false; + + const file = fs.ReadStream(fn); + + file.on('open', common.mustCall(function(fd) { + file.length = 0; + assert.strictEqual(typeof fd, 'number'); + assert.ok(file.readable); + + // GH-535 + file.pause(); + file.resume(); + file.pause(); + file.resume(); + })); + + file.on('data', common.mustCallAtLeast(function(data) { + assert.ok(data instanceof Buffer); + assert.ok(!paused); + file.length += data.length; + + paused = true; + file.pause(); + + setTimeout(function() { + paused = false; + file.resume(); + }, 10); + })); + + + file.on('end', common.mustCall()); + + + file.on('close', common.mustCall(function() { + assert.strictEqual(file.length, 30000); + })); +} + +{ + const file = fs.createReadStream(fn, Object.create({ encoding: 'utf8' })); + file.length = 0; + file.on('data', function(data) { + assert.strictEqual(typeof data, 'string'); + file.length += data.length; + + for (let i = 0; i < data.length; i++) { + // http://www.fileformat.info/info/unicode/char/2026/index.htm + assert.strictEqual(data[i], '\u2026'); + } + }); + + file.on('close', common.mustCall(function() { + assert.strictEqual(file.length, 10000); + })); +} + +{ + const options = Object.create({ bufferSize: 1, start: 1, end: 2 }); + const file = fs.createReadStream(rangeFile, options); + assert.strictEqual(file.start, 1); + assert.strictEqual(file.end, 2); + let contentRead = ''; + file.on('data', function(data) { + contentRead += data.toString('utf-8'); + }); + file.on('end', common.mustCall(function() { + assert.strictEqual(contentRead, 'yz'); + })); +} + +{ + const options = Object.create({ bufferSize: 1, start: 1 }); + const file = fs.createReadStream(rangeFile, options); + assert.strictEqual(file.start, 1); + file.data = ''; + file.on('data', function(data) { + file.data += data.toString('utf-8'); + }); + file.on('end', common.mustCall(function() { + assert.strictEqual(file.data, 'yz\n'); + })); +} + +// https://github.com/joyent/node/issues/2320 +{ + const options = Object.create({ bufferSize: 1.23, start: 1 }); + const file = fs.createReadStream(rangeFile, options); + assert.strictEqual(file.start, 1); + file.data = ''; + file.on('data', function(data) { + file.data += data.toString('utf-8'); + }); + file.on('end', common.mustCall(function() { + assert.strictEqual(file.data, 'yz\n'); + })); +} + +{ + const message = + 'The value of "start" is out of range. It must be <= "end" (here: 2).' + + ' Received 10'; + + assert.throws( + () => { + fs.createReadStream(rangeFile, Object.create({ start: 10, end: 2 })); + }, + { + code: 'ERR_OUT_OF_RANGE', + message, + name: 'RangeError' + }); +} + +{ + const options = Object.create({ start: 0, end: 0 }); + const stream = fs.createReadStream(rangeFile, options); + assert.strictEqual(stream.start, 0); + assert.strictEqual(stream.end, 0); + stream.data = ''; + + stream.on('data', function(chunk) { + stream.data += chunk; + }); + + stream.on('end', common.mustCall(function() { + assert.strictEqual(stream.data, 'x'); + })); +} + +// Pause and then resume immediately. +{ + const pauseRes = fs.createReadStream(rangeFile); + pauseRes.pause(); + pauseRes.resume(); +} + +{ + let data = ''; + let file = + fs.createReadStream(rangeFile, Object.create({ autoClose: false })); + assert.strictEqual(file.autoClose, false); + file.on('data', (chunk) => { data += chunk; }); + file.on('end', common.mustCall(function() { + process.nextTick(common.mustCall(function() { + assert(!file.closed); + assert(!file.destroyed); + assert.strictEqual(data, 'xyz\n'); + fileNext(); + })); + })); + + function fileNext() { + // This will tell us if the fd is usable again or not. + file = fs.createReadStream(null, Object.create({ fd: file.fd, start: 0 })); + file.data = ''; + file.on('data', function(data) { + file.data += data; + }); + file.on('end', common.mustCall(function() { + assert.strictEqual(file.data, 'xyz\n'); + })); + } + process.on('exit', function() { + assert(file.closed); + assert(file.destroyed); + }); +} + +// Just to make sure autoClose won't close the stream because of error. +{ + const options = Object.create({ fd: 13337, autoClose: false }); + const file = fs.createReadStream(null, options); + file.on('data', common.mustNotCall()); + file.on('error', common.mustCall()); + process.on('exit', function() { + assert(!file.closed); + assert(!file.destroyed); + assert(file.fd); + }); +} + +// Make sure stream is destroyed when file does not exist. +{ + const file = fs.createReadStream('/path/to/file/that/does/not/exist'); + file.on('data', common.mustNotCall()); + file.on('error', common.mustCall()); + + process.on('exit', function() { + assert(file.closed); + assert(file.destroyed); + }); +} diff --git a/cli/tests/node_compat/test/parallel/test-fs-read-stream-patch-open.js b/cli/tests/node_compat/test/parallel/test-fs-read-stream-patch-open.js new file mode 100644 index 00000000000000..b2b12ad53af2b0 --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-fs-read-stream-patch-open.js @@ -0,0 +1,24 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +'use strict'; +const common = require('../common'); +const fs = require('fs'); + +common.expectWarning( + 'DeprecationWarning', + 'ReadStream.prototype.open() is deprecated', 'DEP0135'); +const s = fs.createReadStream('asd') + // We don't care about errors in this test. + .on('error', () => {}); +s.open(); + +process.nextTick(() => { + // Allow overriding open(). + fs.ReadStream.prototype.open = common.mustCall(); + fs.createReadStream('asd'); +}); diff --git a/cli/tests/node_compat/test/parallel/test-fs-read-stream-resume.js b/cli/tests/node_compat/test/parallel/test-fs-read-stream-resume.js new file mode 100644 index 00000000000000..f999f4b860fadb --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-fs-read-stream-resume.js @@ -0,0 +1,59 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. + +'use strict'; +const common = require('../common'); +const fixtures = require('../common/fixtures'); +const assert = require('assert'); + +const fs = require('fs'); + +const file = fixtures.path('x.txt'); +let data = ''; +let first = true; + +const stream = fs.createReadStream(file); +stream.setEncoding('utf8'); +stream.on('data', common.mustCallAtLeast(function(chunk) { + data += chunk; + if (first) { + first = false; + stream.resume(); + } +})); + +process.nextTick(function() { + stream.pause(); + setTimeout(function() { + stream.resume(); + }, 100); +}); + +process.on('exit', function() { + assert.strictEqual(data, 'xyz\n'); +}); diff --git a/cli/tests/node_compat/test/parallel/test-fs-read-stream-throw-type-error.js b/cli/tests/node_compat/test/parallel/test-fs-read-stream-throw-type-error.js new file mode 100644 index 00000000000000..f7ee1a087abb4f --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-fs-read-stream-throw-type-error.js @@ -0,0 +1,84 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +'use strict'; +require('../common'); +const fixtures = require('../common/fixtures'); +const assert = require('assert'); +const fs = require('fs'); + +// This test ensures that appropriate TypeError is thrown by createReadStream +// when an argument with invalid type is passed + +const example = fixtures.path('x.txt'); +// Should not throw. +fs.createReadStream(example, undefined); +fs.createReadStream(example, null); +fs.createReadStream(example, 'utf8'); +fs.createReadStream(example, { encoding: 'utf8' }); + +const createReadStreamErr = (path, opt, error) => { + assert.throws(() => { + fs.createReadStream(path, opt); + }, error); +}; + +const typeError = { + code: 'ERR_INVALID_ARG_TYPE', + name: 'TypeError' +}; + +const rangeError = { + code: 'ERR_OUT_OF_RANGE', + name: 'RangeError' +}; + +[123, 0, true, false].forEach((opts) => + createReadStreamErr(example, opts, typeError) +); + +// Case 0: Should not throw if either start or end is undefined +[{}, { start: 0 }, { end: Infinity }].forEach((opts) => + fs.createReadStream(example, opts) +); + +// Case 1: Should throw TypeError if either start or end is not of type 'number' +[ + { start: 'invalid' }, + { end: 'invalid' }, + { start: 'invalid', end: 'invalid' }, +].forEach((opts) => createReadStreamErr(example, opts, typeError)); + +// Case 2: Should throw RangeError if either start or end is NaN +[{ start: NaN }, { end: NaN }, { start: NaN, end: NaN }].forEach((opts) => + createReadStreamErr(example, opts, rangeError) +); + +// Case 3: Should throw RangeError if either start or end is negative +[{ start: -1 }, { end: -1 }, { start: -1, end: -1 }].forEach((opts) => + createReadStreamErr(example, opts, rangeError) +); + +// Case 4: Should throw RangeError if either start or end is fractional +[{ start: 0.1 }, { end: 0.1 }, { start: 0.1, end: 0.1 }].forEach((opts) => + createReadStreamErr(example, opts, rangeError) +); + +// Case 5: Should not throw if both start and end are whole numbers +fs.createReadStream(example, { start: 1, end: 5 }); + +// Case 6: Should throw RangeError if start is greater than end +createReadStreamErr(example, { start: 5, end: 1 }, rangeError); + +// Case 7: Should throw RangeError if start or end is not safe integer +const NOT_SAFE_INTEGER = 2 ** 53; +[ + { start: NOT_SAFE_INTEGER, end: Infinity }, + { start: 0, end: NOT_SAFE_INTEGER }, +].forEach((opts) => + createReadStreamErr(example, opts, rangeError) +); diff --git a/cli/tests/node_compat/test/parallel/test-fs-read-stream.js b/cli/tests/node_compat/test/parallel/test-fs-read-stream.js new file mode 100644 index 00000000000000..6c1c512fbfd787 --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-fs-read-stream.js @@ -0,0 +1,284 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. + +'use strict'; +const common = require('../common'); +const tmpdir = require('../common/tmpdir'); + +const child_process = require('child_process'); +const assert = require('assert'); +const fs = require('fs'); +const fixtures = require('../common/fixtures'); + +const fn = fixtures.path('elipses.txt'); +const rangeFile = fixtures.path('x.txt'); + +function test1(options) { + let paused = false; + let bytesRead = 0; + + const file = fs.createReadStream(fn, options); + const fileSize = fs.statSync(fn).size; + + assert.strictEqual(file.bytesRead, 0); + + file.on('open', common.mustCall(function(fd) { + file.length = 0; + assert.strictEqual(typeof fd, 'number'); + assert.strictEqual(file.bytesRead, 0); + assert.ok(file.readable); + + // GH-535 + file.pause(); + file.resume(); + file.pause(); + file.resume(); + })); + + file.on('data', function(data) { + assert.ok(data instanceof Buffer); + assert.ok(data.byteOffset % 8 === 0); + assert.ok(!paused); + file.length += data.length; + + bytesRead += data.length; + assert.strictEqual(file.bytesRead, bytesRead); + + paused = true; + file.pause(); + + setTimeout(function() { + paused = false; + file.resume(); + }, 10); + }); + + + file.on('end', common.mustCall(function(chunk) { + assert.strictEqual(bytesRead, fileSize); + assert.strictEqual(file.bytesRead, fileSize); + })); + + + file.on('close', common.mustCall(function() { + assert.strictEqual(bytesRead, fileSize); + assert.strictEqual(file.bytesRead, fileSize); + })); + + process.on('exit', function() { + assert.strictEqual(file.length, 30000); + }); +} + +test1({}); +test1({ + fs: { + open: common.mustCall(fs.open), + read: common.mustCallAtLeast(fs.read, 1), + close: common.mustCall(fs.close), + } +}); + +{ + const file = fs.createReadStream(fn, common.mustNotMutateObjectDeep({ encoding: 'utf8' })); + file.length = 0; + file.on('data', function(data) { + assert.strictEqual(typeof data, 'string'); + file.length += data.length; + + for (let i = 0; i < data.length; i++) { + // http://www.fileformat.info/info/unicode/char/2026/index.htm + assert.strictEqual(data[i], '\u2026'); + } + }); + + file.on('close', common.mustCall()); + + process.on('exit', function() { + assert.strictEqual(file.length, 10000); + }); +} + +{ + const file = + fs.createReadStream(rangeFile, common.mustNotMutateObjectDeep({ bufferSize: 1, start: 1, end: 2 })); + let contentRead = ''; + file.on('data', function(data) { + contentRead += data.toString('utf-8'); + }); + file.on('end', common.mustCall(function(data) { + assert.strictEqual(contentRead, 'yz'); + })); +} + +{ + const file = fs.createReadStream(rangeFile, common.mustNotMutateObjectDeep({ bufferSize: 1, start: 1 })); + file.data = ''; + file.on('data', function(data) { + file.data += data.toString('utf-8'); + }); + file.on('end', common.mustCall(function() { + assert.strictEqual(file.data, 'yz\n'); + })); +} + +{ + // Ref: https://github.com/nodejs/node-v0.x-archive/issues/2320 + const file = fs.createReadStream(rangeFile, common.mustNotMutateObjectDeep({ bufferSize: 1.23, start: 1 })); + file.data = ''; + file.on('data', function(data) { + file.data += data.toString('utf-8'); + }); + file.on('end', common.mustCall(function() { + assert.strictEqual(file.data, 'yz\n'); + })); +} + +assert.throws( + () => { + fs.createReadStream(rangeFile, common.mustNotMutateObjectDeep({ start: 10, end: 2 })); + }, + { + code: 'ERR_OUT_OF_RANGE', + message: 'The value of "start" is out of range. It must be <= "end"' + + ' (here: 2). Received 10', + name: 'RangeError' + }); + +{ + const stream = fs.createReadStream(rangeFile, common.mustNotMutateObjectDeep({ start: 0, end: 0 })); + stream.data = ''; + + stream.on('data', function(chunk) { + stream.data += chunk; + }); + + stream.on('end', common.mustCall(function() { + assert.strictEqual(stream.data, 'x'); + })); +} + +{ + // Verify that end works when start is not specified. + const stream = new fs.createReadStream(rangeFile, common.mustNotMutateObjectDeep({ end: 1 })); + stream.data = ''; + + stream.on('data', function(chunk) { + stream.data += chunk; + }); + + stream.on('end', common.mustCall(function() { + assert.strictEqual(stream.data, 'xy'); + })); +} + +if (!common.isWindows) { + // Verify that end works when start is not specified, and we do not try to + // use positioned reads. This makes sure that this keeps working for + // non-seekable file descriptors. + tmpdir.refresh(); + const filename = `${tmpdir.path}/foo.pipe`; + const mkfifoResult = child_process.spawnSync('mkfifo', [filename]); + if (!mkfifoResult.error) { + child_process.exec(`echo "xyz foobar" > '${filename}'`); + const stream = new fs.createReadStream(filename, common.mustNotMutateObjectDeep({ end: 1 })); + stream.data = ''; + + stream.on('data', function(chunk) { + stream.data += chunk; + }); + + stream.on('end', common.mustCall(function() { + assert.strictEqual(stream.data, 'xy'); + fs.unlinkSync(filename); + })); + } else { + common.printSkipMessage('mkfifo not available'); + } +} + +{ + // Pause and then resume immediately. + const pauseRes = fs.createReadStream(rangeFile); + pauseRes.pause(); + pauseRes.resume(); +} + +{ + let file = fs.createReadStream(rangeFile, common.mustNotMutateObjectDeep({ autoClose: false })); + let data = ''; + file.on('data', function(chunk) { data += chunk; }); + file.on('end', common.mustCall(function() { + assert.strictEqual(data, 'xyz\n'); + process.nextTick(function() { + assert(!file.closed); + assert(!file.destroyed); + fileNext(); + }); + })); + + function fileNext() { + // This will tell us if the fd is usable again or not. + file = fs.createReadStream(null, common.mustNotMutateObjectDeep({ fd: file.fd, start: 0 })); + file.data = ''; + file.on('data', function(data) { + file.data += data; + }); + file.on('end', common.mustCall(function(err) { + assert.strictEqual(file.data, 'xyz\n'); + })); + process.on('exit', function() { + assert(file.closed); + assert(file.destroyed); + }); + } +} + +{ + // Just to make sure autoClose won't close the stream because of error. + const file = fs.createReadStream(null, common.mustNotMutateObjectDeep({ fd: 13337, autoClose: false })); + file.on('data', common.mustNotCall()); + file.on('error', common.mustCall()); + process.on('exit', function() { + assert(!file.closed); + assert(!file.destroyed); + assert(file.fd); + }); +} + +{ + // Make sure stream is destroyed when file does not exist. + const file = fs.createReadStream('/path/to/file/that/does/not/exist'); + file.on('data', common.mustNotCall()); + file.on('error', common.mustCall()); + + process.on('exit', function() { + assert(file.closed); + assert(file.destroyed); + }); +} diff --git a/cli/tests/node_compat/test/parallel/test-fs-read-type.js b/cli/tests/node_compat/test/parallel/test-fs-read-type.js new file mode 100644 index 00000000000000..5eceb7627de812 --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-fs-read-type.js @@ -0,0 +1,250 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +'use strict'; +const common = require('../common'); +const fs = require('fs'); +const assert = require('assert'); +const fixtures = require('../common/fixtures'); + +const filepath = fixtures.path('x.txt'); +const fd = fs.openSync(filepath, 'r'); +const expected = 'xyz\n'; + + +// Error must be thrown with string +assert.throws( + () => fs.read(fd, expected.length, 0, 'utf-8', common.mustNotCall()), + { + code: 'ERR_INVALID_ARG_TYPE', + name: 'TypeError', + message: 'The "buffer" argument must be an instance of Buffer, ' + + 'TypedArray, or DataView. Received type number (4)' + } +); + +[true, null, undefined, () => {}, {}].forEach((value) => { + assert.throws(() => { + fs.read(value, + Buffer.allocUnsafe(expected.length), + 0, + expected.length, + 0, + common.mustNotCall()); + }, { + code: 'ERR_INVALID_ARG_TYPE', + name: 'TypeError' + }); +}); + +assert.throws(() => { + fs.read(fd, + Buffer.allocUnsafe(expected.length), + -1, + expected.length, + 0, + common.mustNotCall()); +}, { + code: 'ERR_OUT_OF_RANGE', + name: 'RangeError', +}); + +assert.throws(() => { + fs.read(fd, + Buffer.allocUnsafe(expected.length), + NaN, + expected.length, + 0, + common.mustNotCall()); +}, { + code: 'ERR_OUT_OF_RANGE', + name: 'RangeError', + message: 'The value of "offset" is out of range. It must be an integer. ' + + 'Received NaN' +}); + +assert.throws(() => { + fs.read(fd, + Buffer.allocUnsafe(expected.length), + 0, + -1, + 0, + common.mustNotCall()); +}, { + code: 'ERR_OUT_OF_RANGE', + name: 'RangeError', + message: 'The value of "length" is out of range. ' + + 'It must be >= 0. Received -1' +}); + +[true, () => {}, {}, ''].forEach((value) => { + assert.throws(() => { + fs.read(fd, + Buffer.allocUnsafe(expected.length), + 0, + expected.length, + value, + common.mustNotCall()); + }, { + code: 'ERR_INVALID_ARG_TYPE', + name: 'TypeError' + }); +}); + +[0.5, 2 ** 53, 2n ** 63n].forEach((value) => { + assert.throws(() => { + fs.read(fd, + Buffer.allocUnsafe(expected.length), + 0, + expected.length, + value, + common.mustNotCall()); + }, { + code: 'ERR_OUT_OF_RANGE', + name: 'RangeError' + }); +}); + +fs.read(fd, + Buffer.allocUnsafe(expected.length), + 0, + expected.length, + 0n, + common.mustSucceed()); + +fs.read(fd, + Buffer.allocUnsafe(expected.length), + 0, + expected.length, + 2n ** 53n - 1n, + common.mustCall((err) => { + if (err) { + if (common.isIBMi) + assert.strictEqual(err.code, 'EOVERFLOW'); + else + assert.strictEqual(err.code, 'EFBIG'); + } + })); + +assert.throws( + () => fs.readSync(fd, expected.length, 0, 'utf-8'), + { + code: 'ERR_INVALID_ARG_TYPE', + name: 'TypeError', + message: 'The "buffer" argument must be an instance of Buffer, ' + + 'TypedArray, or DataView. Received type number (4)' + } +); + +[true, null, undefined, () => {}, {}].forEach((value) => { + assert.throws(() => { + fs.readSync(value, + Buffer.allocUnsafe(expected.length), + 0, + expected.length, + 0); + }, { + code: 'ERR_INVALID_ARG_TYPE', + name: 'TypeError' + }); +}); + +assert.throws(() => { + fs.readSync(fd, + Buffer.allocUnsafe(expected.length), + -1, + expected.length, + 0); +}, { + code: 'ERR_OUT_OF_RANGE', + name: 'RangeError', +}); + +assert.throws(() => { + fs.readSync(fd, + Buffer.allocUnsafe(expected.length), + NaN, + expected.length, + 0); +}, { + code: 'ERR_OUT_OF_RANGE', + name: 'RangeError', + message: 'The value of "offset" is out of range. It must be an integer. ' + + 'Received NaN' +}); + +assert.throws(() => { + fs.readSync(fd, + Buffer.allocUnsafe(expected.length), + 0, + -1, + 0); +}, { + code: 'ERR_OUT_OF_RANGE', + name: 'RangeError', + message: 'The value of "length" is out of range. ' + + 'It must be >= 0. Received -1' +}); + +assert.throws(() => { + fs.readSync(fd, + Buffer.allocUnsafe(expected.length), + 0, + expected.length + 1, + 0); +}, { + code: 'ERR_OUT_OF_RANGE', + name: 'RangeError', + message: 'The value of "length" is out of range. ' + + 'It must be <= 4. Received 5' +}); + +[true, () => {}, {}, ''].forEach((value) => { + assert.throws(() => { + fs.readSync(fd, + Buffer.allocUnsafe(expected.length), + 0, + expected.length, + value); + }, { + code: 'ERR_INVALID_ARG_TYPE', + name: 'TypeError' + }); +}); + +[0.5, 2 ** 53, 2n ** 63n].forEach((value) => { + assert.throws(() => { + fs.readSync(fd, + Buffer.allocUnsafe(expected.length), + 0, + expected.length, + value); + }, { + code: 'ERR_OUT_OF_RANGE', + name: 'RangeError' + }); +}); + +fs.readSync(fd, + Buffer.allocUnsafe(expected.length), + 0, + expected.length, + 0n); + +try { + fs.readSync(fd, + Buffer.allocUnsafe(expected.length), + 0, + expected.length, + 2n ** 53n - 1n); +} catch (err) { + // On systems where max file size is below 2^53-1, we'd expect a EFBIG error. + // This is not using `assert.throws` because the above call should not raise + // any error on systems that allows file of that size. + if (err.code !== 'EFBIG' && !(common.isIBMi && err.code === 'EOVERFLOW')) + throw err; +} diff --git a/cli/tests/node_compat/test/parallel/test-fs-read-zero-length.js b/cli/tests/node_compat/test/parallel/test-fs-read-zero-length.js new file mode 100644 index 00000000000000..9c514a70bf5d9c --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-fs-read-zero-length.js @@ -0,0 +1,25 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +'use strict'; +const common = require('../common'); +const fixtures = require('../common/fixtures'); +const assert = require('assert'); +const fs = require('fs'); +const filepath = fixtures.path('x.txt'); +const fd = fs.openSync(filepath, 'r'); +const bufferAsync = Buffer.alloc(0); +const bufferSync = Buffer.alloc(0); + +fs.read(fd, bufferAsync, 0, 0, 0, common.mustCall((err, bytesRead) => { + assert.strictEqual(bytesRead, 0); + assert.deepStrictEqual(bufferAsync, Buffer.alloc(0)); +})); + +const r = fs.readSync(fd, bufferSync, 0, 0, 0); +assert.deepStrictEqual(bufferSync, Buffer.alloc(0)); +assert.strictEqual(r, 0); diff --git a/cli/tests/node_compat/test/parallel/test-fs-read.js b/cli/tests/node_compat/test/parallel/test-fs-read.js new file mode 100644 index 00000000000000..cf49366b1d0b5f --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-fs-read.js @@ -0,0 +1,109 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. + +'use strict'; +const common = require('../common'); +const fixtures = require('../common/fixtures'); +const assert = require('assert'); +const fs = require('fs'); +const filepath = fixtures.path('x.txt'); +const fd = fs.openSync(filepath, 'r'); + +const expected = Buffer.from('xyz\n'); + +function test(bufferAsync, bufferSync, expected) { + fs.read(fd, + bufferAsync, + 0, + expected.length, + 0, + common.mustSucceed((bytesRead) => { + assert.strictEqual(bytesRead, expected.length); + assert.deepStrictEqual(bufferAsync, expected); + })); + + const r = fs.readSync(fd, bufferSync, 0, expected.length, 0); + assert.deepStrictEqual(bufferSync, expected); + assert.strictEqual(r, expected.length); +} + +test(Buffer.allocUnsafe(expected.length), + Buffer.allocUnsafe(expected.length), + expected); + +test(new Uint8Array(expected.length), + new Uint8Array(expected.length), + Uint8Array.from(expected)); + +{ + // Reading beyond file length (3 in this case) should return no data. + // This is a test for a bug where reads > uint32 would return data + // from the current position in the file. + const pos = 0xffffffff + 1; // max-uint32 + 1 + const nRead = fs.readSync(fd, Buffer.alloc(1), 0, 1, pos); + assert.strictEqual(nRead, 0); + + fs.read(fd, Buffer.alloc(1), 0, 1, pos, common.mustSucceed((nRead) => { + assert.strictEqual(nRead, 0); + })); +} + +assert.throws(() => new fs.Dir(), { + code: 'ERR_MISSING_ARGS', +}); + +assert.throws( + () => fs.read(fd, Buffer.alloc(1), 0, 1, 0), + { + code: 'ERR_INVALID_ARG_TYPE', + } +); + +assert.throws( + () => fs.read(fd, { buffer: null }, common.mustNotCall()), + /TypeError: Cannot read properties of null \(reading 'byteLength'\)/, + 'throws when options.buffer is null' +); + +assert.throws( + () => fs.readSync(fd, { buffer: null }), + { + name: 'TypeError', + message: 'The "buffer" argument must be an instance of Buffer, ' + + 'TypedArray, or DataView. Received an instance of Object', + }, + 'throws when options.buffer is null' +); + +assert.throws( + () => fs.read(null, Buffer.alloc(1), 0, 1, 0), + { + message: 'The "fd" argument must be of type number. Received null', + code: 'ERR_INVALID_ARG_TYPE', + } +); diff --git a/cli/tests/node_compat/test/parallel/test-fs-readdir-stack-overflow.js b/cli/tests/node_compat/test/parallel/test-fs-readdir-stack-overflow.js new file mode 100644 index 00000000000000..f25933d0d16eb1 --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-fs-readdir-stack-overflow.js @@ -0,0 +1,26 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +'use strict'; + +require('../common'); + +const assert = require('assert'); +const fs = require('fs'); + +function recurse() { + fs.readdirSync('.'); + recurse(); +} + +assert.throws( + () => recurse(), + { + name: 'RangeError', + message: 'Maximum call stack size exceeded' + } +); diff --git a/cli/tests/node_compat/test/parallel/test-fs-readdir.js b/cli/tests/node_compat/test/parallel/test-fs-readdir.js new file mode 100644 index 00000000000000..98de468bd37a8c --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-fs-readdir.js @@ -0,0 +1,60 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +'use strict'; + +const common = require('../common'); +const assert = require('assert'); +const fs = require('fs'); + +const tmpdir = require('../common/tmpdir'); + +const readdirDir = tmpdir.path; +const files = ['empty', 'files', 'for', 'just', 'testing']; + +// Make sure tmp directory is clean +tmpdir.refresh(); + +// Create the necessary files +files.forEach(function(currentFile) { + fs.closeSync(fs.openSync(`${readdirDir}/${currentFile}`, 'w')); +}); + +// Check the readdir Sync version +assert.deepStrictEqual(files, fs.readdirSync(readdirDir).sort()); + +// Check the readdir async version +fs.readdir(readdirDir, common.mustSucceed((f) => { + assert.deepStrictEqual(files, f.sort()); +})); + +// readdir() on file should throw ENOTDIR +// https://github.com/joyent/node/issues/1869 +assert.throws(function() { + fs.readdirSync(__filename); +}, /Error: ENOTDIR: not a directory/); + +fs.readdir(__filename, common.mustCall(function(e) { + assert.strictEqual(e.code, 'ENOTDIR'); +})); + +[false, 1, [], {}, null, undefined].forEach((i) => { + assert.throws( + () => fs.readdir(i, common.mustNotCall()), + { + code: 'ERR_INVALID_ARG_TYPE', + name: 'TypeError' + } + ); + assert.throws( + () => fs.readdirSync(i), + { + code: 'ERR_INVALID_ARG_TYPE', + name: 'TypeError' + } + ); +}); diff --git a/cli/tests/node_compat/test/parallel/test-fs-readfile-empty.js b/cli/tests/node_compat/test/parallel/test-fs-readfile-empty.js new file mode 100644 index 00000000000000..82dd70eeeff666 --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-fs-readfile-empty.js @@ -0,0 +1,52 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. + +'use strict'; + +// Trivial test of fs.readFile on an empty file. +const common = require('../common'); +const fs = require('fs'); +const assert = require('assert'); +const fixtures = require('../common/fixtures'); + +const fn = fixtures.path('empty.txt'); + +fs.readFile(fn, common.mustCall((err, data) => { + assert.ok(data); +})); + +fs.readFile(fn, 'utf8', common.mustCall((err, data) => { + assert.strictEqual(data, ''); +})); + +fs.readFile(fn, { encoding: 'utf8' }, common.mustCall((err, data) => { + assert.strictEqual(data, ''); +})); + +assert.ok(fs.readFileSync(fn)); +assert.strictEqual(fs.readFileSync(fn, 'utf8'), ''); diff --git a/cli/tests/node_compat/test/parallel/test-fs-realpath-native.js b/cli/tests/node_compat/test/parallel/test-fs-realpath-native.js new file mode 100644 index 00000000000000..d99a2e559502d2 --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-fs-realpath-native.js @@ -0,0 +1,25 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +'use strict'; +const common = require('../common'); +const assert = require('assert'); +const fs = require('fs'); + +const filename = __filename.toLowerCase(); + +assert.strictEqual( + fs.realpathSync.native('./test/parallel/test-fs-realpath-native.js') + .toLowerCase(), + filename); + +fs.realpath.native( + './test/parallel/test-fs-realpath-native.js', + common.mustSucceed(function(res) { + assert.strictEqual(res.toLowerCase(), filename); + assert.strictEqual(this, undefined); + })); diff --git a/cli/tests/node_compat/test/parallel/test-fs-rm.js b/cli/tests/node_compat/test/parallel/test-fs-rm.js new file mode 100644 index 00000000000000..d1b29b623a1041 --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-fs-rm.js @@ -0,0 +1,433 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +// Flags: --expose-internals +'use strict'; +const common = require('../common'); +const tmpdir = require('../common/tmpdir'); +const assert = require('assert'); +const fs = require('fs'); +const path = require('path'); +const { pathToFileURL } = require('url'); +const { execSync } = require('child_process'); + +const { validateRmOptionsSync } = require('internal/fs/utils'); + +tmpdir.refresh(); + +let count = 0; +const nextDirPath = (name = 'rm') => + path.join(tmpdir.path, `${name}-${count++}`); + +const isGitPresent = (() => { + try { execSync('git --version'); return true; } catch { return false; } +})(); + +function gitInit(gitDirectory) { + fs.mkdirSync(gitDirectory); + execSync('git init', common.mustNotMutateObjectDeep({ cwd: gitDirectory })); +} + +function makeNonEmptyDirectory(depth, files, folders, dirname, createSymLinks) { + fs.mkdirSync(dirname, common.mustNotMutateObjectDeep({ recursive: true })); + fs.writeFileSync(path.join(dirname, 'text.txt'), 'hello', 'utf8'); + + const options = common.mustNotMutateObjectDeep({ flag: 'wx' }); + + for (let f = files; f > 0; f--) { + fs.writeFileSync(path.join(dirname, `f-${depth}-${f}`), '', options); + } + + if (createSymLinks) { + // Valid symlink + fs.symlinkSync( + `f-${depth}-1`, + path.join(dirname, `link-${depth}-good`), + 'file' + ); + + // Invalid symlink + fs.symlinkSync( + 'does-not-exist', + path.join(dirname, `link-${depth}-bad`), + 'file' + ); + } + + // File with a name that looks like a glob + fs.writeFileSync(path.join(dirname, '[a-z0-9].txt'), '', options); + + depth--; + if (depth <= 0) { + return; + } + + for (let f = folders; f > 0; f--) { + fs.mkdirSync( + path.join(dirname, `folder-${depth}-${f}`), + { recursive: true } + ); + makeNonEmptyDirectory( + depth, + files, + folders, + path.join(dirname, `d-${depth}-${f}`), + createSymLinks + ); + } +} + +function removeAsync(dir) { + // Removal should fail without the recursive option. + fs.rm(dir, common.mustCall((err) => { + assert.strictEqual(err.syscall, 'rm'); + + // Removal should fail without the recursive option set to true. + fs.rm(dir, common.mustNotMutateObjectDeep({ recursive: false }), common.mustCall((err) => { + assert.strictEqual(err.syscall, 'rm'); + + // Recursive removal should succeed. + fs.rm(dir, common.mustNotMutateObjectDeep({ recursive: true }), common.mustSucceed(() => { + + // Attempted removal should fail now because the directory is gone. + fs.rm(dir, common.mustCall((err) => { + assert.strictEqual(err.syscall, 'stat'); + })); + })); + })); + })); +} + +// Test the asynchronous version +{ + // Create a 4-level folder hierarchy including symlinks + let dir = nextDirPath(); + makeNonEmptyDirectory(4, 10, 2, dir, true); + removeAsync(dir); + + // Create a 2-level folder hierarchy without symlinks + dir = nextDirPath(); + makeNonEmptyDirectory(2, 10, 2, dir, false); + removeAsync(dir); + + // Same test using URL instead of a path + dir = nextDirPath(); + makeNonEmptyDirectory(2, 10, 2, dir, false); + removeAsync(pathToFileURL(dir)); + + // Create a flat folder including symlinks + dir = nextDirPath(); + makeNonEmptyDirectory(1, 10, 2, dir, true); + removeAsync(dir); + + // Should fail if target does not exist + fs.rm( + path.join(tmpdir.path, 'noexist.txt'), + common.mustNotMutateObjectDeep({ recursive: true }), + common.mustCall((err) => { + assert.strictEqual(err.code, 'ENOENT'); + }) + ); + + // Should delete a file + const filePath = path.join(tmpdir.path, 'rm-async-file.txt'); + fs.writeFileSync(filePath, ''); + fs.rm(filePath, common.mustNotMutateObjectDeep({ recursive: true }), common.mustCall((err) => { + try { + assert.strictEqual(err, null); + assert.strictEqual(fs.existsSync(filePath), false); + } finally { + fs.rmSync(filePath, common.mustNotMutateObjectDeep({ force: true })); + } + })); +} + +// Removing a .git directory should not throw an EPERM. +// Refs: https://github.com/isaacs/rimraf/issues/21. +if (isGitPresent) { + const gitDirectory = nextDirPath(); + gitInit(gitDirectory); + fs.rm(gitDirectory, common.mustNotMutateObjectDeep({ recursive: true }), common.mustSucceed(() => { + assert.strictEqual(fs.existsSync(gitDirectory), false); + })); +} + +// Test the synchronous version. +{ + const dir = nextDirPath(); + makeNonEmptyDirectory(4, 10, 2, dir, true); + + // Removal should fail without the recursive option set to true. + assert.throws(() => { + fs.rmSync(dir); + }, { syscall: 'rm' }); + assert.throws(() => { + fs.rmSync(dir, common.mustNotMutateObjectDeep({ recursive: false })); + }, { syscall: 'rm' }); + + // Should fail if target does not exist + assert.throws(() => { + fs.rmSync(path.join(tmpdir.path, 'noexist.txt'), common.mustNotMutateObjectDeep({ recursive: true })); + }, { + code: 'ENOENT', + name: 'Error', + message: /^ENOENT: no such file or directory, stat/ + }); + + // Should delete a file + const filePath = path.join(tmpdir.path, 'rm-file.txt'); + fs.writeFileSync(filePath, ''); + + try { + fs.rmSync(filePath, common.mustNotMutateObjectDeep({ recursive: true })); + } finally { + fs.rmSync(filePath, common.mustNotMutateObjectDeep({ force: true })); + } + + // Should accept URL + const fileURL = pathToFileURL(path.join(tmpdir.path, 'rm-file.txt')); + fs.writeFileSync(fileURL, ''); + + try { + fs.rmSync(fileURL, common.mustNotMutateObjectDeep({ recursive: true })); + } finally { + fs.rmSync(fileURL, common.mustNotMutateObjectDeep({ force: true })); + } + + // Recursive removal should succeed. + fs.rmSync(dir, { recursive: true }); + + // Attempted removal should fail now because the directory is gone. + assert.throws(() => fs.rmSync(dir), { syscall: 'stat' }); +} + +// Removing a .git directory should not throw an EPERM. +// Refs: https://github.com/isaacs/rimraf/issues/21. +if (isGitPresent) { + const gitDirectory = nextDirPath(); + gitInit(gitDirectory); + fs.rmSync(gitDirectory, common.mustNotMutateObjectDeep({ recursive: true })); + assert.strictEqual(fs.existsSync(gitDirectory), false); +} + +// Test the Promises based version. +(async () => { + const dir = nextDirPath(); + makeNonEmptyDirectory(4, 10, 2, dir, true); + + // Removal should fail without the recursive option set to true. + await assert.rejects(fs.promises.rm(dir), { syscall: 'rm' }); + await assert.rejects(fs.promises.rm(dir, common.mustNotMutateObjectDeep({ recursive: false })), { + syscall: 'rm' + }); + + // Recursive removal should succeed. + await fs.promises.rm(dir, common.mustNotMutateObjectDeep({ recursive: true })); + + // Attempted removal should fail now because the directory is gone. + await assert.rejects(fs.promises.rm(dir), { syscall: 'stat' }); + + // Should fail if target does not exist + await assert.rejects(fs.promises.rm( + path.join(tmpdir.path, 'noexist.txt'), + { recursive: true } + ), { + code: 'ENOENT', + name: 'Error', + message: /^ENOENT: no such file or directory, stat/ + }); + + // Should not fail if target does not exist and force option is true + await fs.promises.rm(path.join(tmpdir.path, 'noexist.txt'), common.mustNotMutateObjectDeep({ force: true })); + + // Should delete file + const filePath = path.join(tmpdir.path, 'rm-promises-file.txt'); + fs.writeFileSync(filePath, ''); + + try { + await fs.promises.rm(filePath, common.mustNotMutateObjectDeep({ recursive: true })); + } finally { + fs.rmSync(filePath, common.mustNotMutateObjectDeep({ force: true })); + } + + // Should accept URL + const fileURL = pathToFileURL(path.join(tmpdir.path, 'rm-promises-file.txt')); + fs.writeFileSync(fileURL, ''); + + try { + await fs.promises.rm(fileURL, common.mustNotMutateObjectDeep({ recursive: true })); + } finally { + fs.rmSync(fileURL, common.mustNotMutateObjectDeep({ force: true })); + } +})().then(common.mustCall()); + +// Removing a .git directory should not throw an EPERM. +// Refs: https://github.com/isaacs/rimraf/issues/21. +if (isGitPresent) { + (async () => { + const gitDirectory = nextDirPath(); + gitInit(gitDirectory); + await fs.promises.rm(gitDirectory, common.mustNotMutateObjectDeep({ recursive: true })); + assert.strictEqual(fs.existsSync(gitDirectory), false); + })().then(common.mustCall()); +} + +// Test input validation. +{ + const dir = nextDirPath(); + makeNonEmptyDirectory(4, 10, 2, dir, true); + const filePath = (path.join(tmpdir.path, 'rm-args-file.txt')); + fs.writeFileSync(filePath, ''); + + const defaults = { + retryDelay: 100, + maxRetries: 0, + recursive: false, + force: false + }; + const modified = { + retryDelay: 953, + maxRetries: 5, + recursive: true, + force: false + }; + + assert.deepStrictEqual(validateRmOptionsSync(filePath), defaults); + assert.deepStrictEqual(validateRmOptionsSync(filePath, {}), defaults); + assert.deepStrictEqual(validateRmOptionsSync(filePath, modified), modified); + assert.deepStrictEqual(validateRmOptionsSync(filePath, { + maxRetries: 99 + }), { + retryDelay: 100, + maxRetries: 99, + recursive: false, + force: false + }); + + [null, 'foo', 5, NaN].forEach((bad) => { + assert.throws(() => { + validateRmOptionsSync(filePath, bad); + }, { + code: 'ERR_INVALID_ARG_TYPE', + name: 'TypeError', + message: /^The "options" argument must be of type object\./ + }); + }); + + [undefined, null, 'foo', Infinity, function() {}].forEach((bad) => { + assert.throws(() => { + validateRmOptionsSync(filePath, { recursive: bad }); + }, { + code: 'ERR_INVALID_ARG_TYPE', + name: 'TypeError', + message: /^The "options\.recursive" property must be of type boolean\./ + }); + }); + + [undefined, null, 'foo', Infinity, function() {}].forEach((bad) => { + assert.throws(() => { + validateRmOptionsSync(filePath, { force: bad }); + }, { + code: 'ERR_INVALID_ARG_TYPE', + name: 'TypeError', + message: /^The "options\.force" property must be of type boolean\./ + }); + }); + + assert.throws(() => { + validateRmOptionsSync(filePath, { retryDelay: -1 }); + }, { + code: 'ERR_OUT_OF_RANGE', + name: 'RangeError', + message: /^The value of "options\.retryDelay" is out of range\./ + }); + + assert.throws(() => { + validateRmOptionsSync(filePath, { maxRetries: -1 }); + }, { + code: 'ERR_OUT_OF_RANGE', + name: 'RangeError', + message: /^The value of "options\.maxRetries" is out of range\./ + }); +} + +{ + // IBMi has a different access permission mechanism + // This test should not be run as `root` + if (!common.isIBMi && (common.isWindows || process.getuid() !== 0)) { + function makeDirectoryReadOnly(dir, mode) { + let accessErrorCode = 'EACCES'; + if (common.isWindows) { + accessErrorCode = 'EPERM'; + execSync(`icacls ${dir} /deny "everyone:(OI)(CI)(DE,DC)"`); + } else { + fs.chmodSync(dir, mode); + } + return accessErrorCode; + } + + function makeDirectoryWritable(dir) { + if (fs.existsSync(dir)) { + if (common.isWindows) { + execSync(`icacls ${dir} /remove:d "everyone"`); + } else { + fs.chmodSync(dir, 0o777); + } + } + } + + { + // Check that deleting a file that cannot be accessed using rmsync throws + // https://github.com/nodejs/node/issues/38683 + const dirname = nextDirPath(); + const filePath = path.join(dirname, 'text.txt'); + try { + fs.mkdirSync(dirname, common.mustNotMutateObjectDeep({ recursive: true })); + fs.writeFileSync(filePath, 'hello'); + const code = makeDirectoryReadOnly(dirname, 0o444); + assert.throws(() => { + fs.rmSync(filePath, common.mustNotMutateObjectDeep({ force: true })); + }, { + code, + name: 'Error', + }); + } finally { + makeDirectoryWritable(dirname); + } + } + + { + // Check endless recursion. + // https://github.com/nodejs/node/issues/34580 + const dirname = nextDirPath(); + fs.mkdirSync(dirname, common.mustNotMutateObjectDeep({ recursive: true })); + const root = fs.mkdtempSync(path.join(dirname, 'fs-')); + const middle = path.join(root, 'middle'); + fs.mkdirSync(middle); + fs.mkdirSync(path.join(middle, 'leaf')); // Make `middle` non-empty + try { + const code = makeDirectoryReadOnly(middle, 0o555); + try { + assert.throws(() => { + fs.rmSync(root, common.mustNotMutateObjectDeep({ recursive: true })); + }, { + code, + name: 'Error', + }); + } catch (err) { + // Only fail the test if the folder was not deleted. + // as in some cases rmSync succesfully deletes read-only folders. + if (fs.existsSync(root)) { + throw err; + } + } + } finally { + makeDirectoryWritable(middle); + } + } + } +} diff --git a/cli/tests/node_compat/test/parallel/test-fs-rmdir-recursive-sync-warns-not-found.js b/cli/tests/node_compat/test/parallel/test-fs-rmdir-recursive-sync-warns-not-found.js new file mode 100644 index 00000000000000..13fe487568abc7 --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-fs-rmdir-recursive-sync-warns-not-found.js @@ -0,0 +1,30 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +'use strict'; +const common = require('../common'); +const tmpdir = require('../common/tmpdir'); +const assert = require('assert'); +const fs = require('fs'); +const path = require('path'); + +tmpdir.refresh(); + +{ + // Should warn when trying to delete a nonexistent path + common.expectWarning( + 'DeprecationWarning', + 'In future versions of Node.js, fs.rmdir(path, { recursive: true }) ' + + 'will be removed. Use fs.rm(path, { recursive: true }) instead', + 'DEP0147' + ); + assert.throws( + () => fs.rmdirSync(path.join(tmpdir.path, 'noexist.txt'), + { recursive: true }), + { code: 'ENOENT' } + ); +} diff --git a/cli/tests/node_compat/test/parallel/test-fs-rmdir-recursive-sync-warns-on-file.js b/cli/tests/node_compat/test/parallel/test-fs-rmdir-recursive-sync-warns-on-file.js new file mode 100644 index 00000000000000..d0b053a2f58bd6 --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-fs-rmdir-recursive-sync-warns-on-file.js @@ -0,0 +1,30 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +'use strict'; +const common = require('../common'); +const tmpdir = require('../common/tmpdir'); +const assert = require('assert'); +const fs = require('fs'); +const path = require('path'); + +tmpdir.refresh(); + +{ + common.expectWarning( + 'DeprecationWarning', + 'In future versions of Node.js, fs.rmdir(path, { recursive: true }) ' + + 'will be removed. Use fs.rm(path, { recursive: true }) instead', + 'DEP0147' + ); + const filePath = path.join(tmpdir.path, 'rmdir-recursive.txt'); + fs.writeFileSync(filePath, ''); + assert.throws( + () => fs.rmdirSync(filePath, { recursive: true }), + { code: common.isWindows ? 'ENOENT' : 'ENOTDIR' } + ); +} diff --git a/cli/tests/node_compat/test/parallel/test-fs-rmdir-recursive-throws-not-found.js b/cli/tests/node_compat/test/parallel/test-fs-rmdir-recursive-throws-not-found.js new file mode 100644 index 00000000000000..e94ed3c8f94d06 --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-fs-rmdir-recursive-throws-not-found.js @@ -0,0 +1,43 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +'use strict'; +const common = require('../common'); +const tmpdir = require('../common/tmpdir'); +const assert = require('assert'); +const fs = require('fs'); +const path = require('path'); + +tmpdir.refresh(); + +{ + assert.throws( + () => + fs.rmdirSync(path.join(tmpdir.path, 'noexist.txt'), { recursive: true }), + { + code: 'ENOENT', + } + ); +} +{ + fs.rmdir( + path.join(tmpdir.path, 'noexist.txt'), + { recursive: true }, + common.mustCall((err) => { + assert.strictEqual(err.code, 'ENOENT'); + }) + ); +} +{ + assert.rejects( + () => fs.promises.rmdir(path.join(tmpdir.path, 'noexist.txt'), + { recursive: true }), + { + code: 'ENOENT', + } + ).then(common.mustCall()); +} diff --git a/cli/tests/node_compat/test/parallel/test-fs-rmdir-recursive-throws-on-file.js b/cli/tests/node_compat/test/parallel/test-fs-rmdir-recursive-throws-on-file.js new file mode 100644 index 00000000000000..da007796eac09f --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-fs-rmdir-recursive-throws-on-file.js @@ -0,0 +1,36 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +'use strict'; +const common = require('../common'); +const tmpdir = require('../common/tmpdir'); +const assert = require('assert'); +const fs = require('fs'); +const path = require('path'); + +tmpdir.refresh(); + +const code = common.isWindows ? 'ENOENT' : 'ENOTDIR'; + +{ + const filePath = path.join(tmpdir.path, 'rmdir-recursive.txt'); + fs.writeFileSync(filePath, ''); + assert.throws(() => fs.rmdirSync(filePath, { recursive: true }), { code }); +} +{ + const filePath = path.join(tmpdir.path, 'rmdir-recursive.txt'); + fs.writeFileSync(filePath, ''); + fs.rmdir(filePath, { recursive: true }, common.mustCall((err) => { + assert.strictEqual(err.code, code); + })); +} +{ + const filePath = path.join(tmpdir.path, 'rmdir-recursive.txt'); + fs.writeFileSync(filePath, ''); + assert.rejects(() => fs.promises.rmdir(filePath, { recursive: true }), + { code }).then(common.mustCall()); +} diff --git a/cli/tests/node_compat/test/parallel/test-fs-rmdir-recursive-warns-not-found.js b/cli/tests/node_compat/test/parallel/test-fs-rmdir-recursive-warns-not-found.js new file mode 100644 index 00000000000000..55865a98919265 --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-fs-rmdir-recursive-warns-not-found.js @@ -0,0 +1,29 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +'use strict'; +const common = require('../common'); +const tmpdir = require('../common/tmpdir'); +const fs = require('fs'); +const path = require('path'); + +tmpdir.refresh(); + +{ + // Should warn when trying to delete a nonexistent path + common.expectWarning( + 'DeprecationWarning', + 'In future versions of Node.js, fs.rmdir(path, { recursive: true }) ' + + 'will be removed. Use fs.rm(path, { recursive: true }) instead', + 'DEP0147' + ); + fs.rmdir( + path.join(tmpdir.path, 'noexist.txt'), + { recursive: true }, + common.mustCall() + ); +} diff --git a/cli/tests/node_compat/test/parallel/test-fs-rmdir-recursive-warns-on-file.js b/cli/tests/node_compat/test/parallel/test-fs-rmdir-recursive-warns-on-file.js new file mode 100644 index 00000000000000..db0c8442c9a410 --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-fs-rmdir-recursive-warns-on-file.js @@ -0,0 +1,29 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +'use strict'; +const common = require('../common'); +const tmpdir = require('../common/tmpdir'); +const assert = require('assert'); +const fs = require('fs'); +const path = require('path'); + +tmpdir.refresh(); + +{ + common.expectWarning( + 'DeprecationWarning', + 'In future versions of Node.js, fs.rmdir(path, { recursive: true }) ' + + 'will be removed. Use fs.rm(path, { recursive: true }) instead', + 'DEP0147' + ); + const filePath = path.join(tmpdir.path, 'rmdir-recursive.txt'); + fs.writeFileSync(filePath, ''); + fs.rmdir(filePath, { recursive: true }, common.mustCall((err) => { + assert.strictEqual(err.code, common.isWindows ? 'ENOENT' : 'ENOTDIR'); + })); +} diff --git a/cli/tests/node_compat/test/parallel/test-fs-rmdir-recursive.js b/cli/tests/node_compat/test/parallel/test-fs-rmdir-recursive.js new file mode 100644 index 00000000000000..31bde44874163b --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-fs-rmdir-recursive.js @@ -0,0 +1,252 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 16.13.0 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +// Flags: --expose-internals +'use strict'; +const common = require('../common'); +const tmpdir = require('../common/tmpdir'); +const assert = require('assert'); +const fs = require('fs'); +const path = require('path'); +const { validateRmdirOptions } = require('internal/fs/utils'); + +common.expectWarning( + 'DeprecationWarning', + 'In future versions of Node.js, fs.rmdir(path, { recursive: true }) ' + + 'will be removed. Use fs.rm(path, { recursive: true }) instead', + 'DEP0147' +); + +tmpdir.refresh(); + +let count = 0; +const nextDirPath = (name = 'rmdir-recursive') => + path.join(tmpdir.path, `${name}-${count++}`); + +function makeNonEmptyDirectory(depth, files, folders, dirname, createSymLinks) { + fs.mkdirSync(dirname, { recursive: true }); + fs.writeFileSync(path.join(dirname, 'text.txt'), 'hello', 'utf8'); + + const options = { flag: 'wx' }; + + for (let f = files; f > 0; f--) { + fs.writeFileSync(path.join(dirname, `f-${depth}-${f}`), '', options); + } + + if (createSymLinks) { + // Valid symlink + fs.symlinkSync( + `f-${depth}-1`, + path.join(dirname, `link-${depth}-good`), + 'file' + ); + + // Invalid symlink + fs.symlinkSync( + 'does-not-exist', + path.join(dirname, `link-${depth}-bad`), + 'file' + ); + } + + // File with a name that looks like a glob + fs.writeFileSync(path.join(dirname, '[a-z0-9].txt'), '', options); + + depth--; + if (depth <= 0) { + return; + } + + for (let f = folders; f > 0; f--) { + fs.mkdirSync( + path.join(dirname, `folder-${depth}-${f}`), + { recursive: true } + ); + makeNonEmptyDirectory( + depth, + files, + folders, + path.join(dirname, `d-${depth}-${f}`), + createSymLinks + ); + } +} + +function removeAsync(dir) { + // Removal should fail without the recursive option. + fs.rmdir(dir, common.mustCall((err) => { + assert.strictEqual(err.syscall, 'rmdir'); + + // Removal should fail without the recursive option set to true. + fs.rmdir(dir, { recursive: false }, common.mustCall((err) => { + assert.strictEqual(err.syscall, 'rmdir'); + + // Recursive removal should succeed. + fs.rmdir(dir, { recursive: true }, common.mustSucceed(() => { + // An error should occur if recursive and the directory does not exist. + fs.rmdir(dir, { recursive: true }, common.mustCall((err) => { + assert.strictEqual(err.code, 'ENOENT'); + // Attempted removal should fail now because the directory is gone. + fs.rmdir(dir, common.mustCall((err) => { + assert.strictEqual(err.syscall, 'rmdir'); + })); + })); + })); + })); + })); +} + +// Test the asynchronous version +{ + // Create a 4-level folder hierarchy including symlinks + let dir = nextDirPath(); + makeNonEmptyDirectory(4, 10, 2, dir, true); + removeAsync(dir); + + // Create a 2-level folder hierarchy without symlinks + dir = nextDirPath(); + makeNonEmptyDirectory(2, 10, 2, dir, false); + removeAsync(dir); + + // Create a flat folder including symlinks + dir = nextDirPath(); + makeNonEmptyDirectory(1, 10, 2, dir, true); + removeAsync(dir); +} + +// Test the synchronous version. +{ + const dir = nextDirPath(); + makeNonEmptyDirectory(4, 10, 2, dir, true); + + // Removal should fail without the recursive option set to true. + assert.throws(() => { + fs.rmdirSync(dir); + }, { syscall: 'rmdir' }); + assert.throws(() => { + fs.rmdirSync(dir, { recursive: false }); + }, { syscall: 'rmdir' }); + + // Recursive removal should succeed. + fs.rmdirSync(dir, { recursive: true }); + + // An error should occur if recursive and the directory does not exist. + assert.throws(() => fs.rmdirSync(dir, { recursive: true }), + { code: 'ENOENT' }); + + // Attempted removal should fail now because the directory is gone. + assert.throws(() => fs.rmdirSync(dir), { syscall: 'rmdir' }); +} + +// Test the Promises based version. +(async () => { + const dir = nextDirPath(); + makeNonEmptyDirectory(4, 10, 2, dir, true); + + // Removal should fail without the recursive option set to true. + assert.rejects(fs.promises.rmdir(dir), { syscall: 'rmdir' }); + assert.rejects(fs.promises.rmdir(dir, { recursive: false }), { + syscall: 'rmdir' + }); + + // Recursive removal should succeed. + await fs.promises.rmdir(dir, { recursive: true }); + + // An error should occur if recursive and the directory does not exist. + await assert.rejects(fs.promises.rmdir(dir, { recursive: true }), + { code: 'ENOENT' }); + + // Attempted removal should fail now because the directory is gone. + assert.rejects(fs.promises.rmdir(dir), { syscall: 'rmdir' }); +})().then(common.mustCall()); + +// Test input validation. +{ + const defaults = { + retryDelay: 100, + maxRetries: 0, + recursive: false + }; + const modified = { + retryDelay: 953, + maxRetries: 5, + recursive: true + }; + + assert.deepStrictEqual(validateRmdirOptions(), defaults); + assert.deepStrictEqual(validateRmdirOptions({}), defaults); + assert.deepStrictEqual(validateRmdirOptions(modified), modified); + assert.deepStrictEqual(validateRmdirOptions({ + maxRetries: 99 + }), { + retryDelay: 100, + maxRetries: 99, + recursive: false + }); + + [null, 'foo', 5, NaN].forEach((bad) => { + assert.throws(() => { + validateRmdirOptions(bad); + }, { + code: 'ERR_INVALID_ARG_TYPE', + name: 'TypeError', + message: /^The "options" argument must be of type object\./ + }); + }); + + [undefined, null, 'foo', Infinity, function() {}].forEach((bad) => { + assert.throws(() => { + validateRmdirOptions({ recursive: bad }); + }, { + code: 'ERR_INVALID_ARG_TYPE', + name: 'TypeError', + message: /^The "options\.recursive" property must be of type boolean\./ + }); + }); + + assert.throws(() => { + validateRmdirOptions({ retryDelay: -1 }); + }, { + code: 'ERR_OUT_OF_RANGE', + name: 'RangeError', + message: /^The value of "options\.retryDelay" is out of range\./ + }); + + assert.throws(() => { + validateRmdirOptions({ maxRetries: -1 }); + }, { + code: 'ERR_OUT_OF_RANGE', + name: 'RangeError', + message: /^The value of "options\.maxRetries" is out of range\./ + }); +} + +// FIXME(f3n67u): make this test pass +// It should not pass recursive option to rmdirSync, when called from +// rimraf (see: #35566) +// { +// // Make a non-empty directory: +// const original = fs.rmdirSync; +// const dir = `${nextDirPath()}/foo/bar`; +// fs.mkdirSync(dir, { recursive: true }); +// fs.writeFileSync(`${dir}/foo.txt`, 'hello world', 'utf8'); + +// // When called the second time from rimraf, the recursive option should +// // not be set for rmdirSync: +// let callCount = 0; +// let rmdirSyncOptionsFromRimraf; +// fs.rmdirSync = (path, options) => { +// if (callCount > 0) { +// rmdirSyncOptionsFromRimraf = { ...options }; +// } +// callCount++; +// return original(path, options); +// }; +// fs.rmdirSync(dir, { recursive: true }); +// fs.rmdirSync = original; +// assert.strictEqual(rmdirSyncOptionsFromRimraf.recursive, undefined); +// } diff --git a/cli/tests/node_compat/test/parallel/test-fs-rmdir-type-check.js b/cli/tests/node_compat/test/parallel/test-fs-rmdir-type-check.js new file mode 100644 index 00000000000000..8cddd3777d554f --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-fs-rmdir-type-check.js @@ -0,0 +1,29 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +'use strict'; + +const common = require('../common'); +const assert = require('assert'); +const fs = require('fs'); + +[false, 1, [], {}, null, undefined].forEach((i) => { + assert.throws( + () => fs.rmdir(i, common.mustNotCall()), + { + code: 'ERR_INVALID_ARG_TYPE', + name: 'TypeError' + } + ); + assert.throws( + () => fs.rmdirSync(i), + { + code: 'ERR_INVALID_ARG_TYPE', + name: 'TypeError' + } + ); +}); diff --git a/cli/tests/node_compat/test/parallel/test-fs-watch.js b/cli/tests/node_compat/test/parallel/test-fs-watch.js new file mode 100644 index 00000000000000..785d42911267eb --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-fs-watch.js @@ -0,0 +1,105 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +'use strict'; +const common = require('../common'); + +if (common.isIBMi) + common.skip('IBMi does not support `fs.watch()`'); + +// Tests if `filename` is provided to watcher on supported platforms + +const fs = require('fs'); +const assert = require('assert'); +const { join } = require('path'); + +class WatchTestCase { + constructor(shouldInclude, dirName, fileName, field) { + this.dirName = dirName; + this.fileName = fileName; + this.field = field; + this.shouldSkip = !shouldInclude; + } + get dirPath() { return join(tmpdir.path, this.dirName); } + get filePath() { return join(this.dirPath, this.fileName); } +} + +const cases = [ + // Watch on a file should callback with a filename on supported systems + new WatchTestCase( + common.isLinux || common.isOSX || common.isWindows || common.isAIX, + 'watch1', + 'foo', + 'filePath' + ), + // Watch on a directory should callback with a filename on supported systems + new WatchTestCase( + common.isLinux || common.isOSX || common.isWindows, + 'watch2', + 'bar', + 'dirPath' + ), +]; + +const tmpdir = require('../common/tmpdir'); +tmpdir.refresh(); + +for (const testCase of cases) { + if (testCase.shouldSkip) continue; + fs.mkdirSync(testCase.dirPath); + // Long content so it's actually flushed. + const content1 = Date.now() + testCase.fileName.toLowerCase().repeat(1e4); + fs.writeFileSync(testCase.filePath, content1); + + let interval; + const pathToWatch = testCase[testCase.field]; + const watcher = fs.watch(pathToWatch); + watcher.on('error', (err) => { + if (interval) { + clearInterval(interval); + interval = null; + } + assert.fail(err); + }); + watcher.on('close', common.mustCall(() => { + watcher.close(); // Closing a closed watcher should be a noop + })); + watcher.on('change', common.mustCall(function(eventType, argFilename) { + if (interval) { + clearInterval(interval); + interval = null; + } + if (common.isOSX) + assert.strictEqual(['rename', 'change'].includes(eventType), true); + else + assert.strictEqual(eventType, 'change'); + assert.strictEqual(argFilename, testCase.fileName); + + watcher.close(); + + // We document that watchers cannot be used anymore when it's closed, + // here we turn the methods into noops instead of throwing + watcher.close(); // Closing a closed watcher should be a noop + })); + + // Long content so it's actually flushed. toUpperCase so there's real change. + const content2 = Date.now() + testCase.fileName.toUpperCase().repeat(1e4); + interval = setInterval(() => { + fs.writeFileSync(testCase.filePath, ''); + fs.writeFileSync(testCase.filePath, content2); + }, 100); +} + +[false, 1, {}, [], null, undefined].forEach((input) => { + assert.throws( + () => fs.watch(input, common.mustNotCall()), + { + code: 'ERR_INVALID_ARG_TYPE', + name: 'TypeError' + } + ); +}); diff --git a/cli/tests/node_compat/test/parallel/test-fs-watchfile.js b/cli/tests/node_compat/test/parallel/test-fs-watchfile.js new file mode 100644 index 00000000000000..907a7697a82a62 --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-fs-watchfile.js @@ -0,0 +1,112 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +'use strict'; +const common = require('../common'); + +const assert = require('assert'); +const fs = require('fs'); +const path = require('path'); + +const tmpdir = require('../common/tmpdir'); + +// Basic usage tests. +assert.throws( + () => { + fs.watchFile('./some-file'); + }, + { + code: 'ERR_INVALID_ARG_TYPE', + name: 'TypeError' + }); + +assert.throws( + () => { + fs.watchFile('./another-file', {}, 'bad listener'); + }, + { + code: 'ERR_INVALID_ARG_TYPE', + name: 'TypeError' + }); + +assert.throws(() => { + fs.watchFile(new Object(), common.mustNotCall()); +}, { code: 'ERR_INVALID_ARG_TYPE', name: 'TypeError' }); + +const enoentFile = path.join(tmpdir.path, 'non-existent-file'); +const expectedStatObject = new fs.Stats( + 0, // dev + 0, // mode + 0, // nlink + 0, // uid + 0, // gid + 0, // rdev + 0, // blksize + 0, // ino + 0, // size + 0, // blocks + Date.UTC(1970, 0, 1, 0, 0, 0), // atime + Date.UTC(1970, 0, 1, 0, 0, 0), // mtime + Date.UTC(1970, 0, 1, 0, 0, 0), // ctime + Date.UTC(1970, 0, 1, 0, 0, 0) // birthtime +); + +tmpdir.refresh(); + +// If the file initially didn't exist, and gets created at a later point of +// time, the callback should be invoked again with proper values in stat object +let fileExists = false; + +const watcher = + fs.watchFile(enoentFile, { interval: 0 }, common.mustCall((curr, prev) => { + if (!fileExists) { + // If the file does not exist, all the fields should be zero and the date + // fields should be UNIX EPOCH time + assert.deepStrictEqual(curr, expectedStatObject); + assert.deepStrictEqual(prev, expectedStatObject); + // Create the file now, so that the callback will be called back once the + // event loop notices it. + fs.closeSync(fs.openSync(enoentFile, 'w')); + fileExists = true; + } else { + // If the ino (inode) value is greater than zero, it means that the file + // is present in the filesystem and it has a valid inode number. + assert(curr.ino > 0); + // As the file just got created, previous ino value should be lesser than + // or equal to zero (non-existent file). + assert(prev.ino <= 0); + // Stop watching the file + fs.unwatchFile(enoentFile); + watcher.stop(); // Stopping a stopped watcher should be a noop + } + }, 2)); + +// 'stop' should only be emitted once - stopping a stopped watcher should +// not trigger a 'stop' event. +watcher.on('stop', common.mustCall(function onStop() {})); + +// Watch events should callback with a filename on supported systems. +// Omitting AIX. It works but not reliably. +if (common.isLinux || common.isOSX || common.isWindows) { + const dir = path.join(tmpdir.path, 'watch'); + + fs.mkdir(dir, common.mustCall(function(err) { + if (err) assert.fail(err); + + fs.watch(dir, common.mustCall(function(eventType, filename) { + clearInterval(interval); + this._handle.close(); + assert.strictEqual(filename, 'foo.txt'); + })); + + const interval = setInterval(() => { + fs.writeFile(path.join(dir, 'foo.txt'), 'foo', common.mustCall((err) => { + if (err) assert.fail(err); + })); + }, 1); + })); +} diff --git a/cli/tests/node_compat/test/parallel/test-fs-write-buffer.js b/cli/tests/node_compat/test/parallel/test-fs-write-buffer.js new file mode 100644 index 00000000000000..6627ac2793172c --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-fs-write-buffer.js @@ -0,0 +1,172 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. + +'use strict'; +const common = require('../common'); +const assert = require('assert'); +const path = require('path'); +const fs = require('fs'); +const expected = Buffer.from('hello'); + +const tmpdir = require('../common/tmpdir'); +tmpdir.refresh(); + +// fs.write with all parameters provided: +{ + const filename = path.join(tmpdir.path, 'write1.txt'); + fs.open(filename, 'w', 0o644, common.mustSucceed((fd) => { + const cb = common.mustSucceed((written) => { + assert.strictEqual(written, expected.length); + fs.closeSync(fd); + + const found = fs.readFileSync(filename, 'utf8'); + assert.strictEqual(found, expected.toString()); + }); + + fs.write(fd, expected, 0, expected.length, null, cb); + })); +} + +// fs.write with a buffer, without the length parameter: +{ + const filename = path.join(tmpdir.path, 'write2.txt'); + fs.open(filename, 'w', 0o644, common.mustSucceed((fd) => { + const cb = common.mustSucceed((written) => { + assert.strictEqual(written, 2); + fs.closeSync(fd); + + const found = fs.readFileSync(filename, 'utf8'); + assert.strictEqual(found, 'lo'); + }); + + fs.write(fd, Buffer.from('hello'), 3, cb); + })); +} + +// fs.write with a buffer, without the offset and length parameters: +{ + const filename = path.join(tmpdir.path, 'write3.txt'); + fs.open(filename, 'w', 0o644, common.mustSucceed((fd) => { + const cb = common.mustSucceed((written) => { + assert.strictEqual(written, expected.length); + fs.closeSync(fd); + + const found = fs.readFileSync(filename, 'utf8'); + assert.deepStrictEqual(expected.toString(), found); + }); + + fs.write(fd, expected, cb); + })); +} + +// fs.write with the offset passed as undefined followed by the callback: +{ + const filename = path.join(tmpdir.path, 'write4.txt'); + fs.open(filename, 'w', 0o644, common.mustSucceed((fd) => { + const cb = common.mustSucceed((written) => { + assert.strictEqual(written, expected.length); + fs.closeSync(fd); + + const found = fs.readFileSync(filename, 'utf8'); + assert.deepStrictEqual(expected.toString(), found); + }); + + fs.write(fd, expected, undefined, cb); + })); +} + +// fs.write with offset and length passed as undefined followed by the callback: +{ + const filename = path.join(tmpdir.path, 'write5.txt'); + fs.open(filename, 'w', 0o644, common.mustSucceed((fd) => { + const cb = common.mustSucceed((written) => { + assert.strictEqual(written, expected.length); + fs.closeSync(fd); + + const found = fs.readFileSync(filename, 'utf8'); + assert.strictEqual(found, expected.toString()); + }); + + fs.write(fd, expected, undefined, undefined, cb); + })); +} + +// fs.write with a Uint8Array, without the offset and length parameters: +{ + const filename = path.join(tmpdir.path, 'write6.txt'); + fs.open(filename, 'w', 0o644, common.mustSucceed((fd) => { + const cb = common.mustSucceed((written) => { + assert.strictEqual(written, expected.length); + fs.closeSync(fd); + + const found = fs.readFileSync(filename, 'utf8'); + assert.strictEqual(found, expected.toString()); + }); + + fs.write(fd, Uint8Array.from(expected), cb); + })); +} + +// fs.write with invalid offset type +{ + const filename = path.join(tmpdir.path, 'write7.txt'); + fs.open(filename, 'w', 0o644, common.mustSucceed((fd) => { + assert.throws(() => { + fs.write(fd, + Buffer.from('abcd'), + NaN, + expected.length, + 0, + common.mustNotCall()); + }, { + code: 'ERR_OUT_OF_RANGE', + name: 'RangeError', + message: 'The value of "offset" is out of range. ' + + 'It must be an integer. Received NaN' + }); + + fs.closeSync(fd); + })); +} + +// fs.write with a DataView, without the offset and length parameters: +{ + const filename = path.join(tmpdir.path, 'write8.txt'); + fs.open(filename, 'w', 0o644, common.mustSucceed((fd) => { + const cb = common.mustSucceed((written) => { + assert.strictEqual(written, expected.length); + fs.closeSync(fd); + + const found = fs.readFileSync(filename, 'utf8'); + assert.strictEqual(found, expected.toString()); + }); + + const uint8 = Uint8Array.from(expected); + fs.write(fd, new DataView(uint8.buffer), cb); + })); +} diff --git a/cli/tests/node_compat/test/parallel/test-fs-write-file-buffer.js b/cli/tests/node_compat/test/parallel/test-fs-write-file-buffer.js new file mode 100644 index 00000000000000..66a8e3eefb5b01 --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-fs-write-file-buffer.js @@ -0,0 +1,62 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. + +'use strict'; +require('../common'); +const join = require('path').join; +const util = require('util'); +const fs = require('fs'); + +let data = [ + '/9j/4AAQSkZJRgABAQAAAQABAAD/2wBDAAUDBAQEAwUEBAQFBQUGBwwIBwcH', + 'Bw8LCwkMEQ8SEhEPERETFhwXExQaFRERGCEYGh0dHx8fExciJCIeJBweHx7/', + '2wBDAQUFBQcGBw4ICA4eFBEUHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4e', + 'Hh4eHh4eHh4eHh4eHh4eHh4eHh4eHh7/wAARCAAQABADASIAAhEBAxEB/8QA', + 'HwAAAQUBAQEBAQEAAAAAAAAAAAECAwQFBgcICQoL/8QAtRAAAgEDAwIEAwUF', + 'BAQAAAF9AQIDAAQRBRIhMUEGE1FhByJxFDKBkaEII0KxwRVS0fAkM2JyggkK', + 'FhcYGRolJicoKSo0NTY3ODk6Q0RFRkdISUpTVFVWV1hZWmNkZWZnaGlqc3R1', + 'dnd4eXqDhIWGh4iJipKTlJWWl5iZmqKjpKWmp6ipqrKztLW2t7i5usLDxMXG', + 'x8jJytLT1NXW19jZ2uHi4+Tl5ufo6erx8vP09fb3+Pn6/8QAHwEAAwEBAQEB', + 'AQEBAQAAAAAAAAECAwQFBgcICQoL/8QAtREAAgECBAQDBAcFBAQAAQJ3AAEC', + 'AxEEBSExBhJBUQdhcRMiMoEIFEKRobHBCSMzUvAVYnLRChYkNOEl8RcYGRom', + 'JygpKjU2Nzg5OkNERUZHSElKU1RVVldYWVpjZGVmZ2hpanN0dXZ3eHl6goOE', + 'hYaHiImKkpOUlZaXmJmaoqOkpaanqKmqsrO0tba3uLm6wsPExcbHyMnK0tPU', + '1dbX2Nna4uPk5ebn6Onq8vP09fb3+Pn6/9oADAMBAAIRAxEAPwDhfBUFl/wk', + 'OmPqKJJZw3aiZFBw4z93jnkkc9u9dj8XLfSI/EBt7DTo7ea2Ox5YXVo5FC7g', + 'Tjq24nJPXNVtO0KATRvNHCIg3zoWJWQHqp+o4pun+EtJ0zxBq8mnLJa2d1L5', + '0NvnKRjJBUE5PAx3NYxxUY0pRtvYHSc5Ka2X9d7H/9k=']; + +data = data.join('\n'); + +const tmpdir = require('../common/tmpdir'); +tmpdir.refresh(); + +const buf = Buffer.from(data, 'base64'); +fs.writeFileSync(join(tmpdir.path, 'test.jpg'), buf); + +util.log('Done!'); diff --git a/cli/tests/node_compat/test/parallel/test-fs-write-file-invalid-path.js b/cli/tests/node_compat/test/parallel/test-fs-write-file-invalid-path.js new file mode 100644 index 00000000000000..631dd5ef604b8b --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-fs-write-file-invalid-path.js @@ -0,0 +1,53 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +'use strict'; + +const common = require('../common'); +const assert = require('assert'); +const fs = require('fs'); +const path = require('path'); + +if (!common.isWindows) + common.skip('This test is for Windows only.'); + +const tmpdir = require('../common/tmpdir'); +tmpdir.refresh(); + +const DATA_VALUE = 'hello'; + +// Refs: https://msdn.microsoft.com/en-us/library/windows/desktop/aa365247(v=vs.85).aspx +// Ignore '/', '\\' and ':' +const RESERVED_CHARACTERS = '<>"|?*'; + +[...RESERVED_CHARACTERS].forEach((ch) => { + const pathname = path.join(tmpdir.path, `somefile_${ch}`); + assert.throws( + () => { + fs.writeFileSync(pathname, DATA_VALUE); + }, + /^Error: ENOENT: no such file or directory, open '.*'$/, + `failed with '${ch}'`); +}); + +// Test for ':' (NTFS data streams). +// Refs: https://msdn.microsoft.com/en-us/library/windows/desktop/bb540537.aspx +const pathname = path.join(tmpdir.path, 'foo:bar'); +fs.writeFileSync(pathname, DATA_VALUE); + +let content = ''; +const fileDataStream = fs.createReadStream(pathname, { + encoding: 'utf8' +}); + +fileDataStream.on('data', (data) => { + content += data; +}); + +fileDataStream.on('end', common.mustCall(() => { + assert.strictEqual(content, DATA_VALUE); +})); diff --git a/cli/tests/node_compat/test/parallel/test-fs-write-file-sync.js b/cli/tests/node_compat/test/parallel/test-fs-write-file-sync.js new file mode 100644 index 00000000000000..b84c4d2046a5a3 --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-fs-write-file-sync.js @@ -0,0 +1,128 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. + +'use strict'; +const common = require('../common'); + +if (!common.isMainThread) + common.skip('Setting process.umask is not supported in Workers'); + +const assert = require('assert'); +const path = require('path'); +const fs = require('fs'); + +// On Windows chmod is only able to manipulate read-only bit. Test if creating +// the file in read-only mode works. +const mode = common.isWindows ? 0o444 : 0o755; + +// Reset the umask for testing +process.umask(0o000); + +const tmpdir = require('../common/tmpdir'); +tmpdir.refresh(); + +// Test writeFileSync +{ + const file = path.join(tmpdir.path, 'testWriteFileSync.txt'); + + fs.writeFileSync(file, '123', { mode }); + const content = fs.readFileSync(file, { encoding: 'utf8' }); + assert.strictEqual(content, '123'); + assert.strictEqual(fs.statSync(file).mode & 0o777, mode); +} + +// Test appendFileSync +{ + const file = path.join(tmpdir.path, 'testAppendFileSync.txt'); + + fs.appendFileSync(file, 'abc', { mode }); + const content = fs.readFileSync(file, { encoding: 'utf8' }); + assert.strictEqual(content, 'abc'); + assert.strictEqual(fs.statSync(file).mode & mode, mode); +} + +// Test writeFileSync with file descriptor +{ + // Need to hijack fs.open/close to make sure that things + // get closed once they're opened. + const _openSync = fs.openSync; + const _closeSync = fs.closeSync; + let openCount = 0; + + fs.openSync = (...args) => { + openCount++; + return _openSync(...args); + }; + + fs.closeSync = (...args) => { + openCount--; + return _closeSync(...args); + }; + + const file = path.join(tmpdir.path, 'testWriteFileSyncFd.txt'); + const fd = fs.openSync(file, 'w+', mode); + + fs.writeFileSync(fd, '123'); + fs.closeSync(fd); + const content = fs.readFileSync(file, { encoding: 'utf8' }); + assert.strictEqual(content, '123'); + assert.strictEqual(fs.statSync(file).mode & 0o777, mode); + + // Verify that all opened files were closed. + assert.strictEqual(openCount, 0); + fs.openSync = _openSync; + fs.closeSync = _closeSync; +} + +// Test writeFileSync with flags +{ + const file = path.join(tmpdir.path, 'testWriteFileSyncFlags.txt'); + + fs.writeFileSync(file, 'hello ', { encoding: 'utf8', flag: 'a' }); + fs.writeFileSync(file, 'world!', { encoding: 'utf8', flag: 'a' }); + const content = fs.readFileSync(file, { encoding: 'utf8' }); + assert.strictEqual(content, 'hello world!'); +} + +// Test writeFileSync with an object with an own toString function +{ + // Runtime deprecated by DEP0162 + common.expectWarning('DeprecationWarning', + 'Implicit coercion of objects with own toString property is deprecated.', + 'DEP0162'); + const file = path.join(tmpdir.path, 'testWriteFileSyncStringify.txt'); + const data = { + toString() { + return 'hello world!'; + } + }; + + fs.writeFileSync(file, data, { encoding: 'utf8', flag: 'a' }); + const content = fs.readFileSync(file, { encoding: 'utf8' }); + assert.strictEqual(content, String(data)); +} diff --git a/cli/tests/node_compat/test/parallel/test-fs-write-file.js b/cli/tests/node_compat/test/parallel/test-fs-write-file.js new file mode 100644 index 00000000000000..a5c93cd232151b --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-fs-write-file.js @@ -0,0 +1,115 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 16.13.0 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. + +'use strict'; +const common = require('../common'); +const assert = require('assert'); +const fs = require('fs'); +const join = require('path').join; + +const tmpdir = require('../common/tmpdir'); +tmpdir.refresh(); + +const filename = join(tmpdir.path, 'test.txt'); + +const s = '南越国是前203年至前111年存在于岭南地区的一个国家,国都位于番禺,疆域包括今天中国的广东、' + + '广西两省区的大部份地区,福建省、湖南、贵州、云南的一小部份地区和越南的北部。' + + '南越国是秦朝灭亡后,由南海郡尉赵佗于前203年起兵兼并桂林郡和象郡后建立。' + + '前196年和前179年,南越国曾先后两次名义上臣属于西汉,成为西汉的“外臣”。前112年,' + + '南越国末代君主赵建德与西汉发生战争,被汉武帝于前111年所灭。南越国共存在93年,' + + '历经五代君主。南越国是岭南地区的第一个有记载的政权国家,采用封建制和郡县制并存的制度,' + + '它的建立保证了秦末乱世岭南地区社会秩序的稳定,有效的改善了岭南地区落后的政治、##济现状。\n'; + +fs.writeFile(filename, s, common.mustSucceed(() => { + fs.readFile(filename, common.mustSucceed((buffer) => { + assert.strictEqual(Buffer.byteLength(s), buffer.length); + })); +})); + +// Test that writeFile accepts buffers. +const filename2 = join(tmpdir.path, 'test2.txt'); +const buf = Buffer.from(s, 'utf8'); + +fs.writeFile(filename2, buf, common.mustSucceed(() => { + fs.readFile(filename2, common.mustSucceed((buffer) => { + assert.strictEqual(buf.length, buffer.length); + })); +})); + +// Test that writeFile accepts file descriptors. +const filename4 = join(tmpdir.path, 'test4.txt'); + +fs.open(filename4, 'w+', common.mustSucceed((fd) => { + fs.writeFile(fd, s, common.mustSucceed(() => { + fs.close(fd, common.mustSucceed(() => { + fs.readFile(filename4, common.mustSucceed((buffer) => { + assert.strictEqual(Buffer.byteLength(s), buffer.length); + })); + })); + })); +})); + + +{ + // Test that writeFile is cancellable with an AbortSignal. + // Before the operation has started + const controller = new AbortController(); + const signal = controller.signal; + const filename3 = join(tmpdir.path, 'test3.txt'); + + fs.writeFile(filename3, s, { signal }, common.mustCall((err) => { + assert.strictEqual(err.name, 'AbortError'); + })); + + controller.abort(); +} + +// FIXME(bartlomieju): +// { +// // Test that writeFile is cancellable with an AbortSignal. +// // After the operation has started +// const controller = new AbortController(); +// const signal = controller.signal; +// const filename4 = join(tmpdir.path, 'test5.txt'); + +// fs.writeFile(filename4, s, { signal }, common.mustCall((err) => { +// assert.strictEqual(err.name, 'AbortError'); +// })); + +// process.nextTick(() => controller.abort()); +// } + +{ + // Test read-only mode + const filename = join(tmpdir.path, 'test6.txt'); + fs.writeFileSync(filename, ''); + + // TODO: Correct the error type + const expectedError = common.isWindows ? /EPERM/ : /EBADF/; + fs.writeFile(filename, s, { flag: 'r' }, common.expectsError(expectedError)); +} diff --git a/cli/tests/node_compat/test/parallel/test-fs-write-no-fd.js b/cli/tests/node_compat/test/parallel/test-fs-write-no-fd.js new file mode 100644 index 00000000000000..7a6300a3fb6297 --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-fs-write-no-fd.js @@ -0,0 +1,19 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +'use strict'; +const common = require('../common'); +const fs = require('fs'); +const assert = require('assert'); + +assert.throws(function() { + fs.write(null, Buffer.allocUnsafe(1), 0, 1, common.mustNotCall()); +}, /TypeError/); + +assert.throws(function() { + fs.write(null, '1', 0, 1, common.mustNotCall()); +}, /TypeError/); diff --git a/cli/tests/node_compat/test/parallel/test-fs-write-stream-autoclose-option.js b/cli/tests/node_compat/test/parallel/test-fs-write-stream-autoclose-option.js new file mode 100644 index 00000000000000..ebb92f031369c0 --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-fs-write-stream-autoclose-option.js @@ -0,0 +1,66 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +'use strict'; +const common = require('../common'); +const assert = require('assert'); +const path = require('path'); +const fs = require('fs'); + +const tmpdir = require('../common/tmpdir'); + +const file = path.join(tmpdir.path, 'write-autoclose-opt1.txt'); +tmpdir.refresh(); +let stream = fs.createWriteStream(file, { flags: 'w+', autoClose: false }); +stream.write('Test1'); +stream.end(); +stream.on('finish', common.mustCall(function() { + stream.on('close', common.mustNotCall()); + process.nextTick(common.mustCall(function() { + assert.strictEqual(stream.closed, false); + assert.notStrictEqual(stream.fd, null); + next(); + })); +})); + +function next() { + // This will tell us if the fd is usable again or not + stream = fs.createWriteStream(null, { fd: stream.fd, start: 0 }); + stream.write('Test2'); + stream.end(); + stream.on('finish', common.mustCall(function() { + assert.strictEqual(stream.closed, false); + stream.on('close', common.mustCall(function() { + assert.strictEqual(stream.fd, null); + assert.strictEqual(stream.closed, true); + process.nextTick(next2); + })); + })); +} + +function next2() { + // This will test if after reusing the fd data is written properly + fs.readFile(file, function(err, data) { + assert.ifError(err); + assert.strictEqual(data.toString(), 'Test2'); + process.nextTick(common.mustCall(next3)); + }); +} + +function next3() { + // This is to test success scenario where autoClose is true + const stream = fs.createWriteStream(file, { autoClose: true }); + stream.write('Test3'); + stream.end(); + stream.on('finish', common.mustCall(function() { + assert.strictEqual(stream.closed, false); + stream.on('close', common.mustCall(function() { + assert.strictEqual(stream.fd, null); + assert.strictEqual(stream.closed, true); + })); + })); +} diff --git a/cli/tests/node_compat/test/parallel/test-fs-write-stream-close-without-callback.js b/cli/tests/node_compat/test/parallel/test-fs-write-stream-close-without-callback.js new file mode 100644 index 00000000000000..d7c3511a1da78c --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-fs-write-stream-close-without-callback.js @@ -0,0 +1,20 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +'use strict'; + +require('../common'); +const fs = require('fs'); +const path = require('path'); + +const tmpdir = require('../common/tmpdir'); +tmpdir.refresh(); + +const s = fs.createWriteStream(path.join(tmpdir.path, 'nocallback')); + +s.end('hello world'); +s.close(); diff --git a/cli/tests/node_compat/test/parallel/test-fs-write-stream-double-close.js b/cli/tests/node_compat/test/parallel/test-fs-write-stream-double-close.js new file mode 100644 index 00000000000000..be3c1cbc7177b4 --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-fs-write-stream-double-close.js @@ -0,0 +1,53 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +'use strict'; + +const common = require('../common'); +const assert = require('assert'); +const fs = require('fs'); +const path = require('path'); + +const tmpdir = require('../common/tmpdir'); +tmpdir.refresh(); + +{ + const s = fs.createWriteStream(path.join(tmpdir.path, 'rw')); + + s.close(common.mustCall()); + s.close(common.mustCall()); +} + +{ + const s = fs.createWriteStream(path.join(tmpdir.path, 'rw2')); + + let emits = 0; + s.on('close', () => { + emits++; + }); + + s.close(common.mustCall(() => { + assert.strictEqual(emits, 1); + s.close(common.mustCall(() => { + assert.strictEqual(emits, 1); + })); + process.nextTick(() => { + s.close(common.mustCall(() => { + assert.strictEqual(emits, 1); + })); + }); + })); +} + +{ + const s = fs.createWriteStream(path.join(tmpdir.path, 'rw'), { + autoClose: false + }); + + s.close(common.mustCall()); + s.close(common.mustCall()); +} diff --git a/cli/tests/node_compat/test/parallel/test-fs-write-stream-end.js b/cli/tests/node_compat/test/parallel/test-fs-write-stream-end.js new file mode 100644 index 00000000000000..4940c7c0146092 --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-fs-write-stream-end.js @@ -0,0 +1,67 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. + +'use strict'; +const common = require('../common'); +const assert = require('assert'); +const path = require('path'); +const fs = require('fs'); + +const tmpdir = require('../common/tmpdir'); +tmpdir.refresh(); + +{ + const file = path.join(tmpdir.path, 'write-end-test0.txt'); + const stream = fs.createWriteStream(file); + stream.end(); + stream.on('close', common.mustCall()); +} + +{ + const file = path.join(tmpdir.path, 'write-end-test1.txt'); + const stream = fs.createWriteStream(file); + stream.end('a\n', 'utf8'); + stream.on('close', common.mustCall(function() { + const content = fs.readFileSync(file, 'utf8'); + assert.strictEqual(content, 'a\n'); + })); +} + +{ + const file = path.join(tmpdir.path, 'write-end-test2.txt'); + const stream = fs.createWriteStream(file); + stream.end(); + + let calledOpen = false; + stream.on('open', () => { + calledOpen = true; + }); + stream.on('finish', common.mustCall(() => { + assert.strictEqual(calledOpen, true); + })); +} diff --git a/cli/tests/node_compat/test/parallel/test-fs-write-stream-fs.js b/cli/tests/node_compat/test/parallel/test-fs-write-stream-fs.js new file mode 100644 index 00000000000000..be3fbf754fef4e --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-fs-write-stream-fs.js @@ -0,0 +1,45 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +'use strict'; +const common = require('../common'); +const path = require('path'); +const fs = require('fs'); + +const tmpdir = require('../common/tmpdir'); +tmpdir.refresh(); + +{ + const file = path.join(tmpdir.path, 'write-end-test0.txt'); + const stream = fs.createWriteStream(file, { + fs: { + open: common.mustCall(fs.open), + write: common.mustCallAtLeast(fs.write, 1), + close: common.mustCall(fs.close), + } + }); + stream.end('asd'); + stream.on('close', common.mustCall()); +} + + +{ + const file = path.join(tmpdir.path, 'write-end-test1.txt'); + const stream = fs.createWriteStream(file, { + fs: { + open: common.mustCall(fs.open), + write: fs.write, + writev: common.mustCallAtLeast(fs.writev, 1), + close: common.mustCall(fs.close), + } + }); + stream.write('asd'); + stream.write('asd'); + stream.write('asd'); + stream.end(); + stream.on('close', common.mustCall()); +} diff --git a/cli/tests/node_compat/test/parallel/test-fs-write-stream-throw-type-error.js b/cli/tests/node_compat/test/parallel/test-fs-write-stream-throw-type-error.js new file mode 100644 index 00000000000000..3b53fbbefd8b23 --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-fs-write-stream-throw-type-error.js @@ -0,0 +1,39 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +'use strict'; +require('../common'); +const assert = require('assert'); +const fs = require('fs'); +const path = require('path'); + +const tmpdir = require('../common/tmpdir'); + +const example = path.join(tmpdir.path, 'dummy'); + +tmpdir.refresh(); +// Should not throw. +fs.createWriteStream(example, undefined).end(); +fs.createWriteStream(example, null).end(); +fs.createWriteStream(example, 'utf8').end(); +fs.createWriteStream(example, { encoding: 'utf8' }).end(); + +const createWriteStreamErr = (path, opt) => { + assert.throws( + () => { + fs.createWriteStream(path, opt); + }, + { + code: 'ERR_INVALID_ARG_TYPE', + name: 'TypeError' + }); +}; + +createWriteStreamErr(example, 123); +createWriteStreamErr(example, 0); +createWriteStreamErr(example, true); +createWriteStreamErr(example, false); diff --git a/cli/tests/node_compat/test/parallel/test-fs-write-stream.js b/cli/tests/node_compat/test/parallel/test-fs-write-stream.js new file mode 100644 index 00000000000000..b30aaf8acb9711 --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-fs-write-stream.js @@ -0,0 +1,74 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. + +'use strict'; +const common = require('../common'); +const assert = require('assert'); +const path = require('path'); +const fs = require('fs'); + +const tmpdir = require('../common/tmpdir'); + +const file = path.join(tmpdir.path, 'write.txt'); + +tmpdir.refresh(); + +{ + const stream = fs.WriteStream(file); + const _fs_close = fs.close; + + fs.close = function(fd) { + assert.ok(fd, 'fs.close must not be called without an undefined fd.'); + fs.close = _fs_close; + fs.closeSync(fd); + }; + stream.destroy(); +} + +{ + const stream = fs.createWriteStream(file); + + stream.on('drain', function() { + assert.fail('\'drain\' event must not be emitted before ' + + 'stream.write() has been called at least once.'); + }); + stream.destroy(); +} + +// Throws if data is not of type Buffer. +{ + const stream = fs.createWriteStream(file); + stream.on('error', common.mustNotCall()); + assert.throws(() => { + stream.write(42); + }, { + code: 'ERR_INVALID_ARG_TYPE', + name: 'TypeError' + }); + stream.destroy(); +} diff --git a/cli/tests/node_compat/test/parallel/test-fs-write-sync.js b/cli/tests/node_compat/test/parallel/test-fs-write-sync.js new file mode 100644 index 00000000000000..24c20b80d401b4 --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-fs-write-sync.js @@ -0,0 +1,63 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. + +'use strict'; +require('../common'); +const assert = require('assert'); +const path = require('path'); +const fs = require('fs'); +const tmpdir = require('../common/tmpdir'); +const filename = path.join(tmpdir.path, 'write.txt'); + +tmpdir.refresh(); + +{ + const parameters = [Buffer.from('bár'), 0, Buffer.byteLength('bár')]; + + // The first time fs.writeSync is called with all parameters provided. + // After that, each pop in the cycle removes the final parameter. So: + // - The 2nd time fs.writeSync with a buffer, without the length parameter. + // - The 3rd time fs.writeSync with a buffer, without the offset and length + // parameters. + while (parameters.length > 0) { + const fd = fs.openSync(filename, 'w'); + + let written = fs.writeSync(fd, ''); + assert.strictEqual(written, 0); + + fs.writeSync(fd, 'foo'); + + written = fs.writeSync(fd, ...parameters); + assert.ok(written > 3); + fs.closeSync(fd); + + assert.strictEqual(fs.readFileSync(filename, 'utf-8'), 'foobár'); + + parameters.pop(); + } +} diff --git a/cli/tests/node_compat/test/parallel/test-fs-write.js b/cli/tests/node_compat/test/parallel/test-fs-write.js new file mode 100644 index 00000000000000..33fcb84cf985c7 --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-fs-write.js @@ -0,0 +1,212 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 16.13.0 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. + +// Flags: --expose_externalize_string +'use strict'; +const common = require('../common'); +const assert = require('assert'); +const path = require('path'); +const fs = require('fs'); +const tmpdir = require('../common/tmpdir'); + +tmpdir.refresh(); + +const fn = path.join(tmpdir.path, 'write.txt'); +const fn2 = path.join(tmpdir.path, 'write2.txt'); +const fn3 = path.join(tmpdir.path, 'write3.txt'); +const fn4 = path.join(tmpdir.path, 'write4.txt'); +const fn5 = path.join(tmpdir.path, 'write5.txt'); +const expected = 'ümlaut.'; +const constants = fs.constants; + +const { externalizeString, isOneByteString } = global; + +// Account for extra globals exposed by --expose_externalize_string. +common.allowGlobals(externalizeString, isOneByteString, global.x); + +{ + const expected = 'ümlaut sechzig'; // Must be a unique string. + externalizeString(expected); + assert.strictEqual(isOneByteString(expected), true); + const fd = fs.openSync(fn, 'w'); + fs.writeSync(fd, expected, 0, 'latin1'); + fs.closeSync(fd); + assert.strictEqual(fs.readFileSync(fn, 'latin1'), expected); +} + +{ + const expected = 'ümlaut neunzig'; // Must be a unique string. + externalizeString(expected); + assert.strictEqual(isOneByteString(expected), true); + const fd = fs.openSync(fn, 'w'); + fs.writeSync(fd, expected, 0, 'utf8'); + fs.closeSync(fd); + assert.strictEqual(fs.readFileSync(fn, 'utf8'), expected); +} + +{ + const expected = 'Zhōngwén 1'; // Must be a unique string. + externalizeString(expected); + assert.strictEqual(isOneByteString(expected), false); + const fd = fs.openSync(fn, 'w'); + fs.writeSync(fd, expected, 0, 'ucs2'); + fs.closeSync(fd); + assert.strictEqual(fs.readFileSync(fn, 'ucs2'), expected); +} + +{ + const expected = 'Zhōngwén 2'; // Must be a unique string. + externalizeString(expected); + assert.strictEqual(isOneByteString(expected), false); + const fd = fs.openSync(fn, 'w'); + fs.writeSync(fd, expected, 0, 'utf8'); + fs.closeSync(fd); + assert.strictEqual(fs.readFileSync(fn, 'utf8'), expected); +} + +fs.open(fn, 'w', 0o644, common.mustSucceed((fd) => { + const done = common.mustSucceed((written) => { + assert.strictEqual(written, Buffer.byteLength(expected)); + fs.closeSync(fd); + const found = fs.readFileSync(fn, 'utf8'); + fs.unlinkSync(fn); + assert.strictEqual(found, expected); + }); + + const written = common.mustSucceed((written) => { + assert.strictEqual(written, 0); + fs.write(fd, expected, 0, 'utf8', done); + }); + + fs.write(fd, '', 0, 'utf8', written); +})); + +// TODO(kt3k): Enable this test when fs.open supports number for `flags` +// paramter. +/* +const args = constants.O_CREAT | constants.O_WRONLY | constants.O_TRUNC; +fs.open(fn2, args, 0o644, common.mustSucceed((fd) => { + const done = common.mustSucceed((written) => { + assert.strictEqual(written, Buffer.byteLength(expected)); + fs.closeSync(fd); + const found = fs.readFileSync(fn2, 'utf8'); + fs.unlinkSync(fn2); + assert.strictEqual(found, expected); + }); + + const written = common.mustSucceed((written) => { + assert.strictEqual(written, 0); + fs.write(fd, expected, 0, 'utf8', done); + }); + + fs.write(fd, '', 0, 'utf8', written); +})); +*/ + +fs.open(fn3, 'w', 0o644, common.mustSucceed((fd) => { + const done = common.mustSucceed((written) => { + assert.strictEqual(written, Buffer.byteLength(expected)); + fs.closeSync(fd); + }); + + fs.write(fd, expected, done); +})); + +fs.open(fn4, 'w', 0o644, common.mustSucceed((fd) => { + const done = common.mustSucceed((written) => { + assert.strictEqual(written, Buffer.byteLength(expected)); + fs.closeSync(fd); + }); + + const data = { + toString() { return expected; } + }; + fs.write(fd, data, done); +})); + +[false, 'test', {}, [], null, undefined].forEach((i) => { + assert.throws( + () => fs.write(i, common.mustNotCall()), + { + code: 'ERR_INVALID_ARG_TYPE', + name: 'TypeError' + } + ); + assert.throws( + () => fs.writeSync(i), + { + code: 'ERR_INVALID_ARG_TYPE', + name: 'TypeError' + } + ); +}); + +[false, 5, {}, [], null, undefined].forEach((data) => { + assert.throws( + () => fs.write(1, data, common.mustNotCall()), + { + code: 'ERR_INVALID_ARG_TYPE', + message: /"buffer"/ + } + ); + assert.throws( + () => fs.writeSync(1, data), + { + code: 'ERR_INVALID_ARG_TYPE', + message: /"buffer"/ + } + ); +}); + +{ + // Regression test for https://github.com/nodejs/node/issues/38168 + const fd = fs.openSync(fn5, 'w'); + + assert.throws( + () => fs.writeSync(fd, 'abc', 0, 'hex'), + { + code: 'ERR_INVALID_ARG_VALUE', + message: /'encoding' is invalid for data of length 3/ + } + ); + + assert.throws( + () => fs.writeSync(fd, 'abc', 0, 'hex', common.mustNotCall()), + { + code: 'ERR_INVALID_ARG_VALUE', + message: /'encoding' is invalid for data of length 3/ + } + ); + + assert.strictEqual(fs.writeSync(fd, 'abcd', 0, 'hex'), 2); + + fs.write(fd, 'abcd', 0, 'hex', common.mustSucceed((written) => { + assert.strictEqual(written, 2); + fs.closeSync(fd); + })); +} diff --git a/cli/tests/node_compat/test/parallel/test-fs-writev-sync.js b/cli/tests/node_compat/test/parallel/test-fs-writev-sync.js new file mode 100644 index 00000000000000..40e7c4bb8e0861 --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-fs-writev-sync.js @@ -0,0 +1,104 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +'use strict'; + +require('../common'); +const assert = require('assert'); +const path = require('path'); +const fs = require('fs'); +const tmpdir = require('../common/tmpdir'); + +tmpdir.refresh(); + +const expected = 'ümlaut. Лорем 運務ホソモ指及 आपको करने विकास 紙読決多密所 أضف'; + +const getFileName = (i) => path.join(tmpdir.path, `writev_sync_${i}.txt`); + +/** + * Testing with a array of buffers input + */ + +// fs.writevSync with array of buffers with all parameters +{ + const filename = getFileName(1); + const fd = fs.openSync(filename, 'w'); + + const buffer = Buffer.from(expected); + const bufferArr = [buffer, buffer]; + const expectedLength = bufferArr.length * buffer.byteLength; + + let written = fs.writevSync(fd, [Buffer.from('')], null); + assert.strictEqual(written, 0); + + written = fs.writevSync(fd, bufferArr, null); + assert.strictEqual(written, expectedLength); + + fs.closeSync(fd); + + assert(Buffer.concat(bufferArr).equals(fs.readFileSync(filename))); +} + +// fs.writevSync with array of buffers without position +{ + const filename = getFileName(2); + const fd = fs.openSync(filename, 'w'); + + const buffer = Buffer.from(expected); + const bufferArr = [buffer, buffer, buffer]; + const expectedLength = bufferArr.length * buffer.byteLength; + + let written = fs.writevSync(fd, [Buffer.from('')]); + assert.strictEqual(written, 0); + + written = fs.writevSync(fd, bufferArr); + assert.strictEqual(written, expectedLength); + + fs.closeSync(fd); + + assert(Buffer.concat(bufferArr).equals(fs.readFileSync(filename))); +} + +// fs.writevSync with empty array of buffers +{ + const filename = getFileName(3); + const fd = fs.openSync(filename, 'w'); + const written = fs.writevSync(fd, []); + assert.strictEqual(written, 0); + fs.closeSync(fd); + +} + +/** + * Testing with wrong input types + */ +{ + const filename = getFileName(4); + const fd = fs.openSync(filename, 'w'); + + [false, 'test', {}, [{}], ['sdf'], null, undefined].forEach((i) => { + assert.throws( + () => fs.writevSync(fd, i, null), { + code: 'ERR_INVALID_ARG_TYPE', + name: 'TypeError' + } + ); + }); + + fs.closeSync(fd); +} + +// fs.writevSync with wrong fd types +[false, 'test', {}, [{}], null, undefined].forEach((i) => { + assert.throws( + () => fs.writevSync(i), + { + code: 'ERR_INVALID_ARG_TYPE', + name: 'TypeError' + } + ); +}); diff --git a/cli/tests/node_compat/test/parallel/test-fs-writev.js b/cli/tests/node_compat/test/parallel/test-fs-writev.js new file mode 100644 index 00000000000000..a0f3622cc277e9 --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-fs-writev.js @@ -0,0 +1,114 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +'use strict'; + +const common = require('../common'); +const assert = require('assert'); +const path = require('path'); +const fs = require('fs'); +const tmpdir = require('../common/tmpdir'); + +tmpdir.refresh(); + +const expected = 'ümlaut. Лорем 運務ホソモ指及 आपको करने विकास 紙読決多密所 أضف'; + +const getFileName = (i) => path.join(tmpdir.path, `writev_${i}.txt`); + +/** + * Testing with a array of buffers input + */ + +// fs.writev with array of buffers with all parameters +{ + const filename = getFileName(1); + const fd = fs.openSync(filename, 'w'); + + const buffer = Buffer.from(expected); + const bufferArr = [buffer, buffer]; + + const done = common.mustSucceed((written, buffers) => { + assert.deepStrictEqual(bufferArr, buffers); + const expectedLength = bufferArr.length * buffer.byteLength; + assert.deepStrictEqual(written, expectedLength); + fs.closeSync(fd); + + assert(Buffer.concat(bufferArr).equals(fs.readFileSync(filename))); + }); + + fs.writev(fd, bufferArr, null, done); +} + +// fs.writev with array of buffers without position +{ + const filename = getFileName(2); + const fd = fs.openSync(filename, 'w'); + + const buffer = Buffer.from(expected); + const bufferArr = [buffer, buffer]; + + const done = common.mustSucceed((written, buffers) => { + assert.deepStrictEqual(bufferArr, buffers); + + const expectedLength = bufferArr.length * buffer.byteLength; + assert.deepStrictEqual(written, expectedLength); + fs.closeSync(fd); + + assert(Buffer.concat(bufferArr).equals(fs.readFileSync(filename))); + }); + + fs.writev(fd, bufferArr, done); +} + + +// fs.writev with empty array of buffers +{ + const filename = getFileName(3); + const fd = fs.openSync(filename, 'w'); + const bufferArr = []; + let afterSyncCall = false; + + const done = common.mustSucceed((written, buffers) => { + assert.strictEqual(buffers.length, 0); + assert.strictEqual(written, 0); + assert(afterSyncCall); + fs.closeSync(fd); + }); + + fs.writev(fd, bufferArr, done); + afterSyncCall = true; +} + +/** + * Testing with wrong input types + */ +{ + const filename = getFileName(4); + const fd = fs.openSync(filename, 'w'); + + [false, 'test', {}, [{}], ['sdf'], null, undefined].forEach((i) => { + assert.throws( + () => fs.writev(fd, i, null, common.mustNotCall()), { + code: 'ERR_INVALID_ARG_TYPE', + name: 'TypeError' + } + ); + }); + + fs.closeSync(fd); +} + +// fs.writev with wrong fd types +[false, 'test', {}, [{}], null, undefined].forEach((i) => { + assert.throws( + () => fs.writev(i, common.mustNotCall()), + { + code: 'ERR_INVALID_ARG_TYPE', + name: 'TypeError' + } + ); +}); diff --git a/cli/tests/node_compat/test/parallel/test-handle-wrap-close-abort.js b/cli/tests/node_compat/test/parallel/test-handle-wrap-close-abort.js new file mode 100644 index 00000000000000..c782de76a5322e --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-handle-wrap-close-abort.js @@ -0,0 +1,44 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. + +'use strict'; +const common = require('../common'); + +process.on('uncaughtException', common.mustCall(2)); + +setTimeout(function() { + process.nextTick(function() { + const c = setInterval(function() { + clearInterval(c); + throw new Error('setInterval'); + }, 1); + }); + setTimeout(function() { + throw new Error('setTimeout'); + }, 1); +}, 1); diff --git a/cli/tests/node_compat/test/parallel/test-http-agent-getname.js b/cli/tests/node_compat/test/parallel/test-http-agent-getname.js new file mode 100644 index 00000000000000..f3067791e9b4d5 --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-http-agent-getname.js @@ -0,0 +1,63 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +'use strict'; + +require('../common'); +const assert = require('assert'); +const http = require('http'); +const path = require('path'); + +const tmpdir = require('../common/tmpdir'); + +const agent = new http.Agent(); + +// Default to localhost +assert.strictEqual( + agent.getName({ + port: 80, + localAddress: '192.168.1.1' + }), + 'localhost:80:192.168.1.1' +); + +// empty argument +assert.strictEqual( + agent.getName(), + 'localhost::' +); + +// empty options +assert.strictEqual( + agent.getName({}), + 'localhost::' +); + +// pass all arguments +assert.strictEqual( + agent.getName({ + host: '0.0.0.0', + port: 80, + localAddress: '192.168.1.1' + }), + '0.0.0.0:80:192.168.1.1' +); + +// unix socket +const socketPath = path.join(tmpdir.path, 'foo', 'bar'); +assert.strictEqual( + agent.getName({ + socketPath + }), + `localhost:::${socketPath}` +); + +for (const family of [0, null, undefined, 'bogus']) + assert.strictEqual(agent.getName({ family }), 'localhost::'); + +for (const family of [4, 6]) + assert.strictEqual(agent.getName({ family }), `localhost:::${family}`); diff --git a/cli/tests/node_compat/test/parallel/test-http-client-get-url.js b/cli/tests/node_compat/test/parallel/test-http-client-get-url.js new file mode 100644 index 00000000000000..f6b5a1aa38fbce --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-http-client-get-url.js @@ -0,0 +1,53 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. + +'use strict'; +const common = require('../common'); +const assert = require('assert'); +const http = require('http'); +const url = require('url'); +const testPath = '/foo?bar'; + +const server = http.createServer(common.mustCall((req, res) => { + assert.strictEqual(req.method, 'GET'); + assert.strictEqual(req.url, testPath); + res.writeHead(200, { 'Content-Type': 'text/plain' }); + res.write('hello\n'); + res.end(); +}, 3)); + +server.listen(0, common.localhostIPv4, common.mustCall(() => { + const u = `http://${common.localhostIPv4}:${server.address().port}${testPath}`; + http.get(u, common.mustCall(() => { + http.get(url.parse(u), common.mustCall(() => { + http.get(new URL(u), common.mustCall(() => { + server.close(); + })); + })); + })); +})); diff --git a/cli/tests/node_compat/test/parallel/test-http-client-read-in-error.js b/cli/tests/node_compat/test/parallel/test-http-client-read-in-error.js new file mode 100644 index 00000000000000..ec127ece62ec97 --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-http-client-read-in-error.js @@ -0,0 +1,48 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +'use strict'; +const common = require('../common'); +const net = require('net'); +const http = require('http'); + +class Agent extends http.Agent { + createConnection() { + const socket = new net.Socket(); + + socket.on('error', function() { + socket.push('HTTP/1.1 200\r\n\r\n'); + }); + + let onNewListener; + socket.on('newListener', onNewListener = (name) => { + if (name !== 'error') + return; + socket.removeListener('newListener', onNewListener); + + // Let other listeners to be set up too + process.nextTick(() => { + this.breakSocket(socket); + }); + }); + + return socket; + } + + breakSocket(socket) { + socket.emit('error', new Error('Intentional error')); + } +} + +const agent = new Agent(); + +http.request({ + agent +}).once('error', function() { + console.log('ignore'); + this.on('data', common.mustNotCall()); +}); diff --git a/cli/tests/node_compat/test/parallel/test-http-outgoing-buffer.js b/cli/tests/node_compat/test/parallel/test-http-outgoing-buffer.js new file mode 100644 index 00000000000000..87e46c017e2879 --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-http-outgoing-buffer.js @@ -0,0 +1,26 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +// Flags: --expose-internals +'use strict'; +require('../common'); +const assert = require('assert'); +const { getDefaultHighWaterMark } = require('internal/streams/state'); + +const http = require('http'); +const OutgoingMessage = http.OutgoingMessage; + +const msg = new OutgoingMessage(); +msg._implicitHeader = function() {}; + +// Writes should be buffered until highwatermark +// even when no socket is assigned. + +assert.strictEqual(msg.write('asd'), true); +while (msg.write('asd')); +const highwatermark = msg.writableHighWaterMark || getDefaultHighWaterMark(); +assert(msg.outputSize >= highwatermark); diff --git a/cli/tests/node_compat/test/parallel/test-http-outgoing-destroy.js b/cli/tests/node_compat/test/parallel/test-http-outgoing-destroy.js new file mode 100644 index 00000000000000..52b41d9c395a5a --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-http-outgoing-destroy.js @@ -0,0 +1,24 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +'use strict'; +const common = require('../common'); +const assert = require('assert'); + +const http = require('http'); +const OutgoingMessage = http.OutgoingMessage; + +{ + const msg = new OutgoingMessage(); + assert.strictEqual(msg.destroyed, false); + msg.destroy(); + assert.strictEqual(msg.destroyed, true); + msg.write('asd', common.mustCall((err) => { + assert.strictEqual(err.code, 'ERR_STREAM_DESTROYED'); + })); + msg.on('error', common.mustNotCall()); +} diff --git a/cli/tests/node_compat/test/parallel/test-http-outgoing-finish-writable.js b/cli/tests/node_compat/test/parallel/test-http-outgoing-finish-writable.js new file mode 100644 index 00000000000000..f87a1130c7ecba --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-http-outgoing-finish-writable.js @@ -0,0 +1,47 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +'use strict'; +const common = require('../common'); +const assert = require('assert'); +const http = require('http'); + +// Verify that after calling end() on an `OutgoingMessage` (or a type that +// inherits from `OutgoingMessage`), its `writable` property is not set to false + +const server = http.createServer(common.mustCall(function(req, res) { + assert.strictEqual(res.writable, true); + assert.strictEqual(res.finished, false); + assert.strictEqual(res.writableEnded, false); + res.end(); + + // res.writable is set to false after it has finished sending + // Ref: https://github.com/nodejs/node/issues/15029 + assert.strictEqual(res.writable, true); + assert.strictEqual(res.finished, true); + assert.strictEqual(res.writableEnded, true); + + server.close(); +})); + +server.listen(0); + +server.on('listening', common.mustCall(function() { + const clientRequest = http.request({ + port: server.address().port, + method: 'GET', + path: '/' + }); + + assert.strictEqual(clientRequest.writable, true); + clientRequest.end(); + + // Writable is still true when close + // THIS IS LEGACY, we cannot change it + // unless we break error detection + assert.strictEqual(clientRequest.writable, true); +})); diff --git a/cli/tests/node_compat/test/parallel/test-http-outgoing-internal-headernames-getter.js b/cli/tests/node_compat/test/parallel/test-http-outgoing-internal-headernames-getter.js new file mode 100644 index 00000000000000..c95a07cfe2cf3b --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-http-outgoing-internal-headernames-getter.js @@ -0,0 +1,30 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +'use strict'; +const common = require('../common'); + +const { OutgoingMessage } = require('http'); +const assert = require('assert'); + +const warn = 'OutgoingMessage.prototype._headerNames is deprecated'; +common.expectWarning('DeprecationWarning', warn, 'DEP0066'); + +{ + // Tests for _headerNames get method + const outgoingMessage = new OutgoingMessage(); + outgoingMessage._headerNames; // eslint-disable-line no-unused-expressions +} + +{ + // Tests _headerNames getter result after setting a header. + const outgoingMessage = new OutgoingMessage(); + outgoingMessage.setHeader('key', 'value'); + const expect = Object.create(null); + expect.key = 'key'; + assert.deepStrictEqual(outgoingMessage._headerNames, expect); +} diff --git a/cli/tests/node_compat/test/parallel/test-http-outgoing-internal-headernames-setter.js b/cli/tests/node_compat/test/parallel/test-http-outgoing-internal-headernames-setter.js new file mode 100644 index 00000000000000..cbafbcc569bea5 --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-http-outgoing-internal-headernames-setter.js @@ -0,0 +1,22 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +'use strict'; +const common = require('../common'); + +const { OutgoingMessage } = require('http'); + +const warn = 'OutgoingMessage.prototype._headerNames is deprecated'; +common.expectWarning('DeprecationWarning', warn, 'DEP0066'); + +{ + // Tests for _headerNames set method + const outgoingMessage = new OutgoingMessage(); + outgoingMessage._headerNames = { + 'x-flow-id': '61bba6c5-28a3-4eab-9241-2ecaa6b6a1fd' + }; +} diff --git a/cli/tests/node_compat/test/parallel/test-http-outgoing-internal-headers.js b/cli/tests/node_compat/test/parallel/test-http-outgoing-internal-headers.js new file mode 100644 index 00000000000000..2c11f1ce5f7588 --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-http-outgoing-internal-headers.js @@ -0,0 +1,51 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +// Flags: --expose-internals +'use strict'; +const common = require('../common'); +const assert = require('assert'); + +const { kOutHeaders } = require('internal/http'); +const { OutgoingMessage } = require('http'); + +const warn = 'OutgoingMessage.prototype._headers is deprecated'; +common.expectWarning('DeprecationWarning', warn, 'DEP0066'); + +{ + // Tests for _headers get method + const outgoingMessage = new OutgoingMessage(); + outgoingMessage.getHeaders = common.mustCall(); + outgoingMessage._headers; // eslint-disable-line no-unused-expressions +} + +{ + // Tests for _headers set method + const outgoingMessage = new OutgoingMessage(); + outgoingMessage._headers = { + host: 'risingstack.com', + Origin: 'localhost' + }; + + assert.deepStrictEqual( + Object.entries(outgoingMessage[kOutHeaders]), + Object.entries({ + host: ['host', 'risingstack.com'], + origin: ['Origin', 'localhost'] + })); +} + +{ + // Tests for _headers set method `null` + const outgoingMessage = new OutgoingMessage(); + outgoingMessage._headers = null; + + assert.strictEqual( + outgoingMessage[kOutHeaders], + null + ); +} diff --git a/cli/tests/node_compat/test/parallel/test-http-outgoing-message-inheritance.js b/cli/tests/node_compat/test/parallel/test-http-outgoing-message-inheritance.js new file mode 100644 index 00000000000000..84ed9b1574c763 --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-http-outgoing-message-inheritance.js @@ -0,0 +1,43 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +'use strict'; + +const common = require('../common'); +const { OutgoingMessage } = require('http'); +const { Writable } = require('stream'); +const assert = require('assert'); + +// Check that OutgoingMessage can be used without a proper Socket +// Refs: https://github.com/nodejs/node/issues/14386 +// Refs: https://github.com/nodejs/node/issues/14381 + +class Response extends OutgoingMessage { + _implicitHeader() {} +} + +const res = new Response(); + +let firstChunk = true; + +const ws = new Writable({ + write: common.mustCall((chunk, encoding, callback) => { + if (firstChunk) { + assert(chunk.toString().endsWith('hello world')); + firstChunk = false; + } else { + assert.strictEqual(chunk.length, 0); + } + setImmediate(callback); + }, 2) +}); + +res.socket = ws; +ws._httpMessage = res; +res.connection = ws; + +res.end('hello world'); diff --git a/cli/tests/node_compat/test/parallel/test-http-outgoing-renderHeaders.js b/cli/tests/node_compat/test/parallel/test-http-outgoing-renderHeaders.js new file mode 100644 index 00000000000000..6fb8bfc9006da8 --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-http-outgoing-renderHeaders.js @@ -0,0 +1,57 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +'use strict'; +// Flags: --expose-internals + +require('../common'); +const assert = require('assert'); + +const kOutHeaders = require('internal/http').kOutHeaders; +const http = require('http'); +const OutgoingMessage = http.OutgoingMessage; + +{ + const outgoingMessage = new OutgoingMessage(); + outgoingMessage._header = {}; + assert.throws( + () => outgoingMessage._renderHeaders(), + { + code: 'ERR_HTTP_HEADERS_SENT', + name: 'Error', + message: 'Cannot render headers after they are sent to the client' + } + ); +} + +{ + const outgoingMessage = new OutgoingMessage(); + outgoingMessage[kOutHeaders] = null; + const result = outgoingMessage._renderHeaders(); + assert.deepStrictEqual(result, {}); +} + + +{ + const outgoingMessage = new OutgoingMessage(); + outgoingMessage[kOutHeaders] = {}; + const result = outgoingMessage._renderHeaders(); + assert.deepStrictEqual(result, {}); +} + +{ + const outgoingMessage = new OutgoingMessage(); + outgoingMessage[kOutHeaders] = { + host: ['host', 'nodejs.org'], + origin: ['Origin', 'localhost'] + }; + const result = outgoingMessage._renderHeaders(); + assert.deepStrictEqual(result, { + host: 'nodejs.org', + Origin: 'localhost' + }); +} diff --git a/cli/tests/node_compat/test/parallel/test-http-outgoing-settimeout.js b/cli/tests/node_compat/test/parallel/test-http-outgoing-settimeout.js new file mode 100644 index 00000000000000..8e3db0bf9c658a --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-http-outgoing-settimeout.js @@ -0,0 +1,37 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +'use strict'; +const common = require('../common'); +const assert = require('assert'); + +const { OutgoingMessage } = require('http'); + +{ + // Tests for settimeout method with socket + const expectedMsecs = 42; + const outgoingMessage = new OutgoingMessage(); + outgoingMessage.socket = { + setTimeout: common.mustCall((msecs) => { + assert.strictEqual(msecs, expectedMsecs); + }) + }; + outgoingMessage.setTimeout(expectedMsecs); +} + +{ + // Tests for settimeout method without socket + const expectedMsecs = 23; + const outgoingMessage = new OutgoingMessage(); + outgoingMessage.setTimeout(expectedMsecs); + + outgoingMessage.emit('socket', { + setTimeout: common.mustCall((msecs) => { + assert.strictEqual(msecs, expectedMsecs); + }) + }); +} diff --git a/cli/tests/node_compat/test/parallel/test-http-url.parse-auth.js b/cli/tests/node_compat/test/parallel/test-http-url.parse-auth.js new file mode 100644 index 00000000000000..baaea7c76d549a --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-http-url.parse-auth.js @@ -0,0 +1,55 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. + +'use strict'; +require('../common'); +const assert = require('assert'); +const http = require('http'); +const url = require('url'); + +function check(request) { + // The correct authorization header is be passed + assert.strictEqual(request.headers.authorization, 'Basic dXNlcjpwYXNzOg=='); +} + +const server = http.createServer(function(request, response) { + // Run the check function + check(request); + response.writeHead(200, {}); + response.end('ok'); + server.close(); +}); + +server.listen(0, function() { + const port = this.address().port; + // username = "user", password = "pass:" + const testURL = url.parse(`http://user:pass%3A@localhost:${port}`); + + // make the request + http.request(testURL).end(); +}); diff --git a/cli/tests/node_compat/test/parallel/test-http-url.parse-path.js b/cli/tests/node_compat/test/parallel/test-http-url.parse-path.js new file mode 100644 index 00000000000000..7587caa2a783cb --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-http-url.parse-path.js @@ -0,0 +1,53 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. + +'use strict'; +require('../common'); +const assert = require('assert'); +const http = require('http'); +const url = require('url'); + +function check(request) { + // A path should come over + assert.strictEqual(request.url, '/asdf'); +} + +const server = http.createServer(function(request, response) { + // Run the check function + check(request); + response.writeHead(200, {}); + response.end('ok'); + server.close(); +}); + +server.listen(0, function() { + const testURL = url.parse(`http://localhost:${this.address().port}/asdf`); + + // make the request + http.request(testURL).end(); +}); diff --git a/cli/tests/node_compat/test/parallel/test-http-url.parse-post.js b/cli/tests/node_compat/test/parallel/test-http-url.parse-post.js new file mode 100644 index 00000000000000..2e461cc331a5ec --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-http-url.parse-post.js @@ -0,0 +1,61 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. + +'use strict'; +require('../common'); +const assert = require('assert'); +const http = require('http'); +const url = require('url'); + +let testURL; + +function check(request) { + // url.parse should not mess with the method + assert.strictEqual(request.method, 'POST'); + // Everything else should be right + assert.strictEqual(request.url, '/asdf?qwer=zxcv'); + // The host header should use the url.parse.hostname + assert.strictEqual(request.headers.host, + `${testURL.hostname}:${testURL.port}`); +} + +const server = http.createServer(function(request, response) { + // Run the check function + check(request); + response.writeHead(200, {}); + response.end('ok'); + server.close(); +}); + +server.listen(0, function() { + testURL = url.parse(`http://localhost:${this.address().port}/asdf?qwer=zxcv`); + testURL.method = 'POST'; + + // make the request + http.request(testURL).end(); +}); diff --git a/cli/tests/node_compat/test/parallel/test-http-url.parse-search.js b/cli/tests/node_compat/test/parallel/test-http-url.parse-search.js new file mode 100644 index 00000000000000..60304c657bcfa8 --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-http-url.parse-search.js @@ -0,0 +1,54 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. + +'use strict'; +require('../common'); +const assert = require('assert'); +const http = require('http'); +const url = require('url'); + +function check(request) { + // A path should come over with params + assert.strictEqual(request.url, '/asdf?qwer=zxcv'); +} + +const server = http.createServer(function(request, response) { + // Run the check function + check(request); + response.writeHead(200, {}); + response.end('ok'); + server.close(); +}); + +server.listen(0, function() { + const port = this.address().port; + const testURL = url.parse(`http://localhost:${port}/asdf?qwer=zxcv`); + + // make the request + http.request(testURL).end(); +}); diff --git a/cli/tests/node_compat/test/parallel/test-net-access-byteswritten.js b/cli/tests/node_compat/test/parallel/test-net-access-byteswritten.js new file mode 100644 index 00000000000000..1be6b29b7f49ed --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-net-access-byteswritten.js @@ -0,0 +1,28 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +'use strict'; + +const common = require('../common'); +if (!common.hasCrypto) + common.skip('missing crypto'); + +const assert = require('assert'); +const net = require('net'); +const tls = require('tls'); +const tty = require('tty'); + +// Check that the bytesWritten getter doesn't crash if object isn't +// constructed. +assert.strictEqual(net.Socket.prototype.bytesWritten, undefined); +assert.strictEqual(Object.getPrototypeOf(tls.TLSSocket).prototype.bytesWritten, + undefined); +assert.strictEqual(tls.TLSSocket.prototype.bytesWritten, undefined); +assert.strictEqual(Object.getPrototypeOf(tty.ReadStream).prototype.bytesWritten, + undefined); +assert.strictEqual(tty.ReadStream.prototype.bytesWritten, undefined); +assert.strictEqual(tty.WriteStream.prototype.bytesWritten, undefined); diff --git a/cli/tests/node_compat/test/parallel/test-net-after-close.js b/cli/tests/node_compat/test/parallel/test-net-after-close.js new file mode 100644 index 00000000000000..9ffa68407ef302 --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-net-after-close.js @@ -0,0 +1,58 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. + +'use strict'; +const common = require('../common'); +const assert = require('assert'); +const net = require('net'); + +const server = net.createServer(common.mustCall((s) => { + console.error('SERVER: got connection'); + s.end(); +})); + +server.listen(0, common.mustCall(() => { + const c = net.createConnection(server.address().port); + c.on('close', common.mustCall(() => { + /* eslint-disable no-unused-expressions */ + console.error('connection closed'); + assert.strictEqual(c._handle, null); + // Calling functions / accessing properties of a closed socket should not + // throw. + c.setNoDelay(); + c.setKeepAlive(); + c.bufferSize; + c.pause(); + c.resume(); + c.address(); + c.remoteAddress; + c.remotePort; + server.close(); + /* eslint-enable no-unused-expressions */ + })); +})); diff --git a/cli/tests/node_compat/test/parallel/test-net-allow-half-open.js b/cli/tests/node_compat/test/parallel/test-net-allow-half-open.js new file mode 100644 index 00000000000000..cfc2ec5520e205 --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-net-allow-half-open.js @@ -0,0 +1,54 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +'use strict'; + +const common = require('../common'); +const assert = require('assert'); +const net = require('net'); + +{ + const server = net.createServer(common.mustCall((socket) => { + socket.end(Buffer.alloc(1024)); + })).listen(0, common.mustCall(() => { + const socket = net.connect(server.address().port); + assert.strictEqual(socket.allowHalfOpen, false); + socket.resume(); + socket.on('end', common.mustCall(() => { + process.nextTick(() => { + // Ensure socket is not destroyed straight away + // without proper shutdown. + assert(!socket.destroyed); + server.close(); + }); + })); + socket.on('finish', common.mustCall(() => { + assert(!socket.destroyed); + })); + socket.on('close', common.mustCall()); + })); +} + +{ + const server = net.createServer(common.mustCall((socket) => { + socket.end(Buffer.alloc(1024)); + })).listen(0, common.mustCall(() => { + const socket = net.connect(server.address().port); + assert.strictEqual(socket.allowHalfOpen, false); + socket.resume(); + socket.on('end', common.mustCall(() => { + assert(!socket.destroyed); + })); + socket.end('asd'); + socket.on('finish', common.mustCall(() => { + assert(!socket.destroyed); + })); + socket.on('close', common.mustCall(() => { + server.close(); + })); + })); +} diff --git a/cli/tests/node_compat/test/parallel/test-net-better-error-messages-listen-path.js b/cli/tests/node_compat/test/parallel/test-net-better-error-messages-listen-path.js new file mode 100644 index 00000000000000..80bc6f7bcaec4e --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-net-better-error-messages-listen-path.js @@ -0,0 +1,17 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +'use strict'; +const common = require('../common'); +const assert = require('assert'); +const net = require('net'); +const fp = '/blah/fadfa'; +const server = net.createServer(common.mustNotCall()); +server.listen(fp, common.mustNotCall()); +server.on('error', common.mustCall(function(e) { + assert.strictEqual(e.address, fp); +})); diff --git a/cli/tests/node_compat/test/parallel/test-net-better-error-messages-listen.js b/cli/tests/node_compat/test/parallel/test-net-better-error-messages-listen.js new file mode 100644 index 00000000000000..14febbcdf4bcd0 --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-net-better-error-messages-listen.js @@ -0,0 +1,19 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +'use strict'; +const common = require('../common'); +const assert = require('assert'); +const net = require('net'); + +const server = net.createServer(common.mustNotCall()); +server.listen(1, '1.1.1.1', common.mustNotCall()); +server.on('error', common.mustCall(function(e) { + assert.strictEqual(e.address, '1.1.1.1'); + assert.strictEqual(e.port, 1); + assert.strictEqual(e.syscall, 'listen'); +})); diff --git a/cli/tests/node_compat/test/parallel/test-net-better-error-messages-path.js b/cli/tests/node_compat/test/parallel/test-net-better-error-messages-path.js new file mode 100644 index 00000000000000..d1bada362dd7b2 --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-net-better-error-messages-path.js @@ -0,0 +1,29 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 16.13.0 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +'use strict'; +const common = require('../common'); +const assert = require('assert'); +const net = require('net'); + +{ + const fp = '/tmp/fadagagsdfgsdf'; + const c = net.connect(fp); + + c.on('connect', common.mustNotCall()); + c.on('error', common.expectsError({ + code: 'ENOENT', + message: `connect ENOENT ${fp}` + })); +} + +{ + assert.throws( + () => net.createConnection({ path: {} }), + { code: 'ERR_INVALID_ARG_TYPE' } + ); +} diff --git a/cli/tests/node_compat/test/parallel/test-net-better-error-messages-port-hostname.js b/cli/tests/node_compat/test/parallel/test-net-better-error-messages-port-hostname.js new file mode 100644 index 00000000000000..e9a87118861b0f --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-net-better-error-messages-port-hostname.js @@ -0,0 +1,44 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +'use strict'; + +// This tests that the error thrown from net.createConnection +// comes with host and port properties. +// See https://github.com/nodejs/node-v0.x-archive/issues/7005 + +const common = require('../common'); +const assert = require('assert'); +const net = require('net'); + +const { addresses } = require('../common/internet'); +const { + errorLookupMock, + mockedErrorCode +} = require('../common/dns'); + +// Using port 0 as hostname used is already invalid. +const c = net.createConnection({ + port: 0, + host: addresses.INVALID_HOST, + lookup: common.mustCall(errorLookupMock()) +}); + +c.on('connect', common.mustNotCall()); + +c.on('error', common.mustCall((error) => { + assert.ok(!('port' in error)); + assert.ok(!('host' in error)); + assert.throws(() => { throw error; }, { + errno: mockedErrorCode, + code: mockedErrorCode, + name: 'Error', + message: 'getaddrinfo ENOTFOUND something.invalid', + hostname: addresses.INVALID_HOST, + syscall: 'getaddrinfo' + }); +})); diff --git a/cli/tests/node_compat/test/parallel/test-net-bind-twice.js b/cli/tests/node_compat/test/parallel/test-net-bind-twice.js new file mode 100644 index 00000000000000..6500f2aef7ca99 --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-net-bind-twice.js @@ -0,0 +1,43 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. + +'use strict'; +const common = require('../common'); +const assert = require('assert'); +const net = require('net'); + +const server1 = net.createServer(common.mustNotCall()); +server1.listen(0, '127.0.0.1', common.mustCall(function() { + const server2 = net.createServer(common.mustNotCall()); + server2.listen(this.address().port, '127.0.0.1', common.mustNotCall()); + + server2.on('error', common.mustCall(function(e) { + assert.strictEqual(e.code, 'EADDRINUSE'); + server1.close(); + })); +})); diff --git a/cli/tests/node_compat/test/parallel/test-net-buffersize.js b/cli/tests/node_compat/test/parallel/test-net-buffersize.js new file mode 100644 index 00000000000000..0831fa52602c5c --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-net-buffersize.js @@ -0,0 +1,59 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. + +'use strict'; +const common = require('../common'); +const assert = require('assert'); +const net = require('net'); + +const iter = 10; + +const server = net.createServer(function(socket) { + socket.on('readable', function() { + socket.read(); + }); + + socket.on('end', function() { + server.close(); + }); +}); + +server.listen(0, common.mustCall(function() { + const client = net.connect(this.address().port); + + client.on('finish', common.mustCall(() => { + assert.strictEqual(client.bufferSize, 0); + })); + + for (let i = 1; i < iter; i++) { + client.write('a'); + assert.strictEqual(client.bufferSize, i); + } + + client.end(); +})); diff --git a/cli/tests/node_compat/test/parallel/test-net-bytes-written-large.js b/cli/tests/node_compat/test/parallel/test-net-bytes-written-large.js new file mode 100644 index 00000000000000..98df17dca57901 --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-net-bytes-written-large.js @@ -0,0 +1,74 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +'use strict'; +const common = require('../common'); +const assert = require('assert'); +const net = require('net'); + +// Regression test for https://github.com/nodejs/node/issues/19562: +// Writing to a socket first tries to push through as much data as possible +// without blocking synchronously, and, if that is not enough, queues more +// data up for asynchronous writing. +// Check that `bytesWritten` accounts for both parts of a write. + +const N = 10000000; +{ + // Variant 1: Write a Buffer. + const server = net.createServer(common.mustCall((socket) => { + socket.end(Buffer.alloc(N), common.mustCall(() => { + assert.strictEqual(socket.bytesWritten, N); + })); + assert.strictEqual(socket.bytesWritten, N); + })).listen(0, common.mustCall(() => { + const client = net.connect(server.address().port); + client.resume(); + client.on('close', common.mustCall(() => { + assert.strictEqual(client.bytesRead, N); + server.close(); + })); + })); +} + +{ + // Variant 2: Write a string. + const server = net.createServer(common.mustCall((socket) => { + socket.end('a'.repeat(N), common.mustCall(() => { + assert.strictEqual(socket.bytesWritten, N); + })); + assert.strictEqual(socket.bytesWritten, N); + })).listen(0, common.mustCall(() => { + const client = net.connect(server.address().port); + client.resume(); + client.on('close', common.mustCall(() => { + assert.strictEqual(client.bytesRead, N); + server.close(); + })); + })); +} + +{ + // Variant 2: writev() with mixed data. + const server = net.createServer(common.mustCall((socket) => { + socket.cork(); + socket.write('a'.repeat(N)); + assert.strictEqual(socket.bytesWritten, N); + socket.write(Buffer.alloc(N)); + assert.strictEqual(socket.bytesWritten, 2 * N); + socket.end('', common.mustCall(() => { + assert.strictEqual(socket.bytesWritten, 2 * N); + })); + socket.uncork(); + })).listen(0, common.mustCall(() => { + const client = net.connect(server.address().port); + client.resume(); + client.on('close', common.mustCall(() => { + assert.strictEqual(client.bytesRead, 2 * N); + server.close(); + })); + })); +} diff --git a/cli/tests/node_compat/test/parallel/test-net-can-reset-timeout.js b/cli/tests/node_compat/test/parallel/test-net-can-reset-timeout.js new file mode 100644 index 00000000000000..535a86a10d1f6b --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-net-can-reset-timeout.js @@ -0,0 +1,64 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. + +'use strict'; +const common = require('../common'); + +// Ref: https://github.com/nodejs/node-v0.x-archive/issues/481 + +const net = require('net'); + +const server = net.createServer(common.mustCall(function(stream) { + stream.setTimeout(100); + + stream.resume(); + + stream.once('timeout', common.mustCall(function() { + console.log('timeout'); + // Try to reset the timeout. + stream.write('WHAT.'); + })); + + stream.on('end', common.mustCall(function() { + console.log('server side end'); + stream.end(); + })); +})); + +server.listen(0, common.mustCall(function() { + const c = net.createConnection(this.address().port); + + c.on('data', function() { + c.end(); + }); + + c.on('end', function() { + console.log('client side end'); + server.close(); + }); +})); diff --git a/cli/tests/node_compat/test/parallel/test-net-connect-after-destroy.js b/cli/tests/node_compat/test/parallel/test-net-connect-after-destroy.js new file mode 100644 index 00000000000000..b04799f569f138 --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-net-connect-after-destroy.js @@ -0,0 +1,16 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +'use strict'; +// Regression test for https://github.com/nodejs/node-v0.x-archive/issues/819. + +require('../common'); +const net = require('net'); + +// Connect to something that we need to DNS resolve +const c = net.createConnection(80, 'google.com'); +c.destroy(); diff --git a/cli/tests/node_compat/test/parallel/test-net-connect-buffer.js b/cli/tests/node_compat/test/parallel/test-net-connect-buffer.js new file mode 100644 index 00000000000000..04e71247e425da --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-net-connect-buffer.js @@ -0,0 +1,86 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 16.13.0 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. + +'use strict'; +const common = require('../common'); +const assert = require('assert'); +const net = require('net'); + +// TODO: support not using "new" +const tcp = new net.Server(common.mustCall((s) => { + tcp.close(); + + let buf = ''; + s.setEncoding('utf8'); + s.on('data', function(d) { + buf += d; + }); + + s.on('end', common.mustCall(function() { + console.error('SERVER: end', buf); + assert.strictEqual(buf, "L'État, c'est moi"); + s.end(); + })); +})); + +tcp.listen(0, common.mustCall(function() { + // TODO: support not using "new" + const socket = new net.Stream({ highWaterMark: 0 }); + + let connected = false; + assert.strictEqual(socket.pending, true); + socket.connect(this.address().port, common.mustCall(() => connected = true)); + + assert.strictEqual(socket.pending, true); + assert.strictEqual(socket.connecting, true); + assert.strictEqual(socket.readyState, 'opening'); + + // Write a string that contains a multi-byte character sequence to test that + // `bytesWritten` is incremented with the # of bytes, not # of characters. + const a = "L'État, c'est "; + const b = 'moi'; + + // We're still connecting at this point so the datagram is first pushed onto + // the connect queue. Make sure that it's not added to `bytesWritten` again + // when the actual write happens. + const r = socket.write(a, common.mustCall((er) => { + console.error('write cb'); + assert.ok(connected); + assert.strictEqual(socket.bytesWritten, Buffer.from(a + b).length); + assert.strictEqual(socket.pending, false); + })); + socket.on('close', common.mustCall(() => { + assert.strictEqual(socket.pending, true); + })); + + assert.strictEqual(socket.bytesWritten, Buffer.from(a).length); + assert.strictEqual(r, false); + socket.end(b); + + assert.strictEqual(socket.readyState, 'opening'); +})); diff --git a/cli/tests/node_compat/test/parallel/test-net-connect-buffer2.js b/cli/tests/node_compat/test/parallel/test-net-connect-buffer2.js new file mode 100644 index 00000000000000..499f3849f287ef --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-net-connect-buffer2.js @@ -0,0 +1,63 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 16.13.0 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +'use strict'; +const common = require('../common'); +const assert = require('assert'); +const net = require('net'); + +const tcp = new net.Server(common.mustCall((s) => { + tcp.close(); + + let buf = ''; + s.setEncoding('utf8'); + s.on('data', function(d) { + buf += d; + }); + + s.on('end', common.mustCall(function() { + console.error('SERVER: end', buf); + assert.strictEqual(buf, "L'État, c'est moi"); + s.end(); + })); +})); + +tcp.listen(0, common.mustCall(function() { + const socket = new net.Stream({ highWaterMark: 0 }); + + let connected = false; + assert.strictEqual(socket.pending, true); + socket.connect(this.address().port, common.mustCall(() => connected = true)); + + assert.strictEqual(socket.pending, true); + assert.strictEqual(socket.connecting, true); + assert.strictEqual(socket.readyState, 'opening'); + + // Write a string that contains a multi-byte character sequence to test that + // `bytesWritten` is incremented with the # of bytes, not # of characters. + const a = "L'État, c'est "; + const b = 'moi'; + + // We're still connecting at this point so the datagram is first pushed onto + // the connect queue. Make sure that it's not added to `bytesWritten` again + // when the actual write happens. + const r = socket.write(a, common.mustCall((er) => { + console.error('write cb'); + assert.ok(connected); + assert.strictEqual(socket.bytesWritten, Buffer.from(a + b).length); + assert.strictEqual(socket.pending, false); + })); + socket.on('close', common.mustCall(() => { + assert.strictEqual(socket.pending, true); + })); + + assert.strictEqual(socket.bytesWritten, Buffer.from(a).length); + assert.strictEqual(r, false); + socket.end(b); + + assert.strictEqual(socket.readyState, 'opening'); +})); diff --git a/cli/tests/node_compat/test/parallel/test-net-connect-call-socket-connect.js b/cli/tests/node_compat/test/parallel/test-net-connect-call-socket-connect.js new file mode 100644 index 00000000000000..87412d4adde89a --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-net-connect-call-socket-connect.js @@ -0,0 +1,46 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +'use strict'; +const common = require('../common'); + +// This test checks that calling `net.connect` internally calls +// `Socket.prototype.connect`. +// +// This is important for people who monkey-patch `Socket.prototype.connect` +// since it's not possible to monkey-patch `net.connect` directly (as the core +// `connect` function is called internally in Node instead of calling the +// `exports.connect` function). +// +// Monkey-patching of `Socket.prototype.connect` is done by - among others - +// most APM vendors, the async-listener module and the +// continuation-local-storage module. +// +// Related: +// - https://github.com/nodejs/node/pull/12342 +// - https://github.com/nodejs/node/pull/12852 + +const net = require('net'); +const Socket = net.Socket; + +// Monkey patch Socket.prototype.connect to check that it's called. +const orig = Socket.prototype.connect; +Socket.prototype.connect = common.mustCall(function() { + return orig.apply(this, arguments); +}); + +const server = net.createServer(); + +server.listen(common.mustCall(function() { + const port = server.address().port; + const client = net.connect({ port }, common.mustCall(function() { + client.end(); + })); + client.on('end', common.mustCall(function() { + server.close(); + })); +})); diff --git a/cli/tests/node_compat/test/parallel/test-net-connect-destroy.js b/cli/tests/node_compat/test/parallel/test-net-connect-destroy.js new file mode 100644 index 00000000000000..bdeb9eb0cdd715 --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-net-connect-destroy.js @@ -0,0 +1,14 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +'use strict'; +const common = require('../common'); +const net = require('net'); + +const socket = new net.Socket(); +socket.on('close', common.mustCall()); +socket.destroy(); diff --git a/cli/tests/node_compat/test/parallel/test-net-connect-immediate-destroy.js b/cli/tests/node_compat/test/parallel/test-net-connect-immediate-destroy.js new file mode 100644 index 00000000000000..0412d3f5635ef0 --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-net-connect-immediate-destroy.js @@ -0,0 +1,18 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +'use strict'; +const common = require('../common'); +const net = require('net'); + +const server = net.createServer(); +server.listen(0); +const port = server.address().port; +const socket = net.connect(port, common.localhostIPv4, common.mustNotCall()); +socket.on('error', common.mustNotCall()); +server.close(); +socket.destroy(); diff --git a/cli/tests/node_compat/test/parallel/test-net-connect-immediate-finish.js b/cli/tests/node_compat/test/parallel/test-net-connect-immediate-finish.js new file mode 100644 index 00000000000000..51c2176df6148b --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-net-connect-immediate-finish.js @@ -0,0 +1,66 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. + +'use strict'; + +// This tests that if the socket is still in the 'connecting' state +// when the user calls socket.end() ('finish'), the socket would emit +// 'connect' and defer the handling until the 'connect' event is handled. + +const common = require('../common'); +const assert = require('assert'); +const net = require('net'); + +const { addresses } = require('../common/internet'); +const { + errorLookupMock, + mockedErrorCode, + mockedSysCall +} = require('../common/dns'); + +const client = net.connect({ + host: addresses.INVALID_HOST, + port: 80, // Port number doesn't matter because host name is invalid + lookup: common.mustCall(errorLookupMock()) +}, common.mustNotCall()); + +client.once('error', common.mustCall((error) => { + // TODO(BridgeAR): Add a better way to handle not defined properties using + // `assert.throws(fn, object)`. + assert.ok(!('port' in error)); + assert.ok(!('host' in error)); + assert.throws(() => { throw error; }, { + code: mockedErrorCode, + errno: mockedErrorCode, + syscall: mockedSysCall, + hostname: addresses.INVALID_HOST, + message: 'getaddrinfo ENOTFOUND something.invalid' + }); +})); + +client.end(); diff --git a/cli/tests/node_compat/test/parallel/test-net-connect-no-arg.js b/cli/tests/node_compat/test/parallel/test-net-connect-no-arg.js new file mode 100644 index 00000000000000..6fd3e88abaab81 --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-net-connect-no-arg.js @@ -0,0 +1,42 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +'use strict'; + +require('../common'); +const assert = require('assert'); +const net = require('net'); + +// Tests that net.connect() called without arguments throws ERR_MISSING_ARGS. + +assert.throws(() => { + net.connect(); +}, { + code: 'ERR_MISSING_ARGS', + message: 'The "options" or "port" or "path" argument must be specified', +}); + +assert.throws(() => { + new net.Socket().connect(); +}, { + code: 'ERR_MISSING_ARGS', + message: 'The "options" or "port" or "path" argument must be specified', +}); + +assert.throws(() => { + net.connect({}); +}, { + code: 'ERR_MISSING_ARGS', + message: 'The "options" or "port" or "path" argument must be specified', +}); + +assert.throws(() => { + new net.Socket().connect({}); +}, { + code: 'ERR_MISSING_ARGS', + message: 'The "options" or "port" or "path" argument must be specified', +}); diff --git a/cli/tests/node_compat/test/parallel/test-net-connect-options-ipv6.js b/cli/tests/node_compat/test/parallel/test-net-connect-options-ipv6.js new file mode 100644 index 00000000000000..af87d57133a62c --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-net-connect-options-ipv6.js @@ -0,0 +1,74 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. + +// Test that the family option of net.connect is honored. + +'use strict'; +const common = require('../common'); +if (!common.hasIPv6) + common.skip('no IPv6 support'); + +const assert = require('assert'); +const net = require('net'); + +const hostAddrIPv6 = '::1'; +const HOSTNAME = 'dummy'; + +const server = net.createServer({ allowHalfOpen: true }, (socket) => { + socket.resume(); + socket.on('end', common.mustCall()); + socket.end(); +}); + +function tryConnect() { + const connectOpt = { + host: HOSTNAME, + port: server.address().port, + family: 6, + allowHalfOpen: true, + lookup: common.mustCall((addr, opt, cb) => { + assert.strictEqual(addr, HOSTNAME); + assert.strictEqual(opt.family, 6); + cb(null, hostAddrIPv6, opt.family); + }) + }; + // No `mustCall`, since test could skip, and it's the only path to `close`. + const client = net.connect(connectOpt, () => { + client.resume(); + client.on('end', () => { + // Wait for next uv tick and make sure the socket stream is writable. + setTimeout(function() { + assert(client.writable); + client.end(); + }, 10); + }); + client.on('close', () => server.close()); + }); +} + +server.listen(0, hostAddrIPv6, tryConnect); diff --git a/cli/tests/node_compat/test/parallel/test-net-connect-options-port.js b/cli/tests/node_compat/test/parallel/test-net-connect-options-port.js new file mode 100644 index 00000000000000..6dd10461a6f20c --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-net-connect-options-port.js @@ -0,0 +1,237 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. + +'use strict'; +const common = require('../common'); +const assert = require('assert'); +const dns = require('dns'); +const net = require('net'); + +// Test wrong type of ports +{ + const portTypeError = { + code: 'ERR_INVALID_ARG_TYPE', + name: 'TypeError' + }; + + syncFailToConnect(true, portTypeError); + syncFailToConnect(false, portTypeError); + syncFailToConnect([], portTypeError, true); + syncFailToConnect({}, portTypeError, true); + syncFailToConnect(null, portTypeError); +} + +// Test out of range ports +{ + const portRangeError = { + code: 'ERR_SOCKET_BAD_PORT', + name: 'RangeError' + }; + + syncFailToConnect('', portRangeError); + syncFailToConnect(' ', portRangeError); + syncFailToConnect('0x', portRangeError, true); + syncFailToConnect('-0x1', portRangeError, true); + syncFailToConnect(NaN, portRangeError); + syncFailToConnect(Infinity, portRangeError); + syncFailToConnect(-1, portRangeError); + syncFailToConnect(65536, portRangeError); +} + +// Test invalid hints +{ + // connect({hint}, cb) and connect({hint}) + const hints = (dns.ADDRCONFIG | dns.V4MAPPED | dns.ALL) + 42; + const hintOptBlocks = doConnect([{ port: 42, hints }], + () => common.mustNotCall()); + for (const fn of hintOptBlocks) { + assert.throws(fn, { + code: 'ERR_INVALID_ARG_VALUE', + name: 'TypeError', + message: /The argument 'hints' is invalid\. Received \d+/ + }); + } +} + +// Test valid combinations of connect(port) and connect(port, host) +{ + const expectedConnections = 72; + let serverConnected = 0; + + const server = net.createServer(common.mustCall((socket) => { + socket.end('ok'); + if (++serverConnected === expectedConnections) { + server.close(); + } + }, expectedConnections)); + + server.listen(0, common.localhostIPv4, common.mustCall(() => { + const port = server.address().port; + + // Total connections = 3 * 4(canConnect) * 6(doConnect) = 72 + canConnect(port); + canConnect(String(port)); + canConnect(`0x${port.toString(16)}`); + })); + + // Try connecting to random ports, but do so once the server is closed + server.on('close', () => { + asyncFailToConnect(0); + }); +} + +function doConnect(args, getCb) { + return [ + function createConnectionWithCb() { + return net.createConnection.apply(net, args.concat(getCb())) + .resume(); + }, + function createConnectionWithoutCb() { + return net.createConnection.apply(net, args) + .on('connect', getCb()) + .resume(); + }, + function connectWithCb() { + return net.connect.apply(net, args.concat(getCb())) + .resume(); + }, + function connectWithoutCb() { + return net.connect.apply(net, args) + .on('connect', getCb()) + .resume(); + }, + function socketConnectWithCb() { + const socket = new net.Socket(); + return socket.connect.apply(socket, args.concat(getCb())) + .resume(); + }, + function socketConnectWithoutCb() { + const socket = new net.Socket(); + return socket.connect.apply(socket, args) + .on('connect', getCb()) + .resume(); + }, + ]; +} + +function syncFailToConnect(port, assertErr, optOnly) { + const family = 4; + if (!optOnly) { + // connect(port, cb) and connect(port) + const portArgFunctions = doConnect([{ port, family }], + () => common.mustNotCall()); + for (const fn of portArgFunctions) { + assert.throws(fn, assertErr, `${fn.name}(${port})`); + } + + // connect(port, host, cb) and connect(port, host) + const portHostArgFunctions = doConnect([{ port, + host: 'localhost', + family }], + () => common.mustNotCall()); + for (const fn of portHostArgFunctions) { + assert.throws(fn, assertErr, `${fn.name}(${port}, 'localhost')`); + } + } + // connect({port}, cb) and connect({port}) + const portOptFunctions = doConnect([{ port, family }], + () => common.mustNotCall()); + for (const fn of portOptFunctions) { + assert.throws(fn, assertErr, `${fn.name}({port: ${port}})`); + } + + // connect({port, host}, cb) and connect({port, host}) + const portHostOptFunctions = doConnect([{ port: port, + host: 'localhost', + family: family }], + () => common.mustNotCall()); + for (const fn of portHostOptFunctions) { + assert.throws(fn, + assertErr, + `${fn.name}({port: ${port}, host: 'localhost'})`); + } +} + +function canConnect(port) { + const noop = () => common.mustCall(); + const family = 4; + + // connect(port, cb) and connect(port) + const portArgFunctions = doConnect([{ port, family }], noop); + for (const fn of portArgFunctions) { + fn(); + } + + // connect(port, host, cb) and connect(port, host) + const portHostArgFunctions = doConnect([{ port, host: 'localhost', family }], + noop); + for (const fn of portHostArgFunctions) { + fn(); + } + + // connect({port}, cb) and connect({port}) + const portOptFunctions = doConnect([{ port, family }], noop); + for (const fn of portOptFunctions) { + fn(); + } + + // connect({port, host}, cb) and connect({port, host}) + const portHostOptFns = doConnect([{ port, host: 'localhost', family }], + noop); + for (const fn of portHostOptFns) { + fn(); + } +} + +function asyncFailToConnect(port) { + const onError = () => common.mustCall((err) => { + const regexp = /^Error: connect E\w+.+$/; + assert.match(String(err), regexp); + }); + + const dont = () => common.mustNotCall(); + const family = 4; + // connect(port, cb) and connect(port) + const portArgFunctions = doConnect([{ port, family }], dont); + for (const fn of portArgFunctions) { + fn().on('error', onError()); + } + + // connect({port}, cb) and connect({port}) + const portOptFunctions = doConnect([{ port, family }], dont); + for (const fn of portOptFunctions) { + fn().on('error', onError()); + } + + // connect({port, host}, cb) and connect({port, host}) + const portHostOptFns = doConnect([{ port, host: 'localhost', family }], + dont); + for (const fn of portHostOptFns) { + fn().on('error', onError()); + } +} diff --git a/cli/tests/node_compat/test/parallel/test-net-dns-custom-lookup.js b/cli/tests/node_compat/test/parallel/test-net-dns-custom-lookup.js new file mode 100644 index 00000000000000..6847dc57e441ae --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-net-dns-custom-lookup.js @@ -0,0 +1,61 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +'use strict'; +const common = require('../common'); +const assert = require('assert'); +const net = require('net'); + +function check(addressType, cb) { + const server = net.createServer(function(client) { + client.end(); + server.close(); + cb && cb(); + }); + + const address = addressType === 4 ? common.localhostIPv4 : '::1'; + server.listen(0, address, common.mustCall(function() { + net.connect({ + port: this.address().port, + host: 'localhost', + family: addressType, + lookup: lookup + }).on('lookup', common.mustCall(function(err, ip, type) { + assert.strictEqual(err, null); + assert.strictEqual(address, ip); + assert.strictEqual(type, addressType); + })); + })); + + function lookup(host, dnsopts, cb) { + dnsopts.family = addressType; + if (addressType === 4) { + process.nextTick(function() { + cb(null, common.localhostIPv4, 4); + }); + } else { + process.nextTick(function() { + cb(null, '::1', 6); + }); + } + } +} + +check(4, function() { + common.hasIPv6 && check(6); +}); + +// Verify that bad lookup() IPs are handled. +{ + net.connect({ + host: 'localhost', + port: 80, + lookup(host, dnsopts, cb) { + cb(null, undefined, 4); + } + }).on('error', common.expectsError({ code: 'ERR_INVALID_IP_ADDRESS' })); +} diff --git a/cli/tests/node_compat/test/parallel/test-net-dns-error.js b/cli/tests/node_compat/test/parallel/test-net-dns-error.js new file mode 100644 index 00000000000000..8362d646519f30 --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-net-dns-error.js @@ -0,0 +1,48 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. + +'use strict'; +const common = require('../common'); + +const assert = require('assert'); +const net = require('net'); + +const host = '*'.repeat(64); +// Resolving hostname > 63 characters may return EAI_FAIL (permanent failure). +const errCodes = ['ENOTFOUND', 'EAI_FAIL']; + +const socket = net.connect(42, host, common.mustNotCall()); +socket.on('error', common.mustCall(function(err) { + assert(errCodes.includes(err.code), err); +})); +socket.on('lookup', common.mustCall(function(err, ip, type) { + assert(err instanceof Error); + assert(errCodes.includes(err.code), err); + assert.strictEqual(ip, undefined); + assert.strictEqual(type, undefined); +})); diff --git a/cli/tests/node_compat/test/parallel/test-net-dns-lookup-skip.js b/cli/tests/node_compat/test/parallel/test-net-dns-lookup-skip.js new file mode 100644 index 00000000000000..f0fb49781b8fd8 --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-net-dns-lookup-skip.js @@ -0,0 +1,26 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +'use strict'; +const common = require('../common'); +const net = require('net'); + +function check(addressType) { + const server = net.createServer(function(client) { + client.end(); + server.close(); + }); + + const address = addressType === 4 ? '127.0.0.1' : '::1'; + server.listen(0, address, function() { + net.connect(this.address().port, address) + .on('lookup', common.mustNotCall()); + }); +} + +check(4); +common.hasIPv6 && check(6); diff --git a/cli/tests/node_compat/test/parallel/test-net-dns-lookup.js b/cli/tests/node_compat/test/parallel/test-net-dns-lookup.js new file mode 100644 index 00000000000000..57cb90d39bc28d --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-net-dns-lookup.js @@ -0,0 +1,47 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. + +'use strict'; +const common = require('../common'); +const assert = require('assert'); +const net = require('net'); + +const server = net.createServer(function(client) { + client.end(); + server.close(); +}); + +server.listen(0, common.mustCall(function() { + net.connect(this.address().port, 'localhost') + .on('lookup', common.mustCall(function(err, ip, type, host) { + assert.strictEqual(err, null); + assert.match(ip, /^(127\.0\.0\.1|::1)$/); + assert.match(type.toString(), /^(4|6)$/); + assert.strictEqual(host, 'localhost'); + })); +})); diff --git a/cli/tests/node_compat/test/parallel/test-net-during-close.js b/cli/tests/node_compat/test/parallel/test-net-during-close.js new file mode 100644 index 00000000000000..3262e281b117ce --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-net-during-close.js @@ -0,0 +1,49 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. + +'use strict'; +const common = require('../common'); +const net = require('net'); + +const server = net.createServer(function(socket) { + socket.end(); +}); + +server.listen(0, common.mustCall(function() { + /* eslint-disable no-unused-expressions */ + const client = net.createConnection(this.address().port); + server.close(); + // Server connection event has not yet fired client is still attempting to + // connect. Accessing properties should not throw in this case. + client.remoteAddress; + client.remoteFamily; + client.remotePort; + // Exit now, do not wait for the client error event. + process.exit(0); + /* eslint-enable no-unused-expressions */ +})); diff --git a/cli/tests/node_compat/test/parallel/test-net-eaddrinuse.js b/cli/tests/node_compat/test/parallel/test-net-eaddrinuse.js new file mode 100644 index 00000000000000..d4b9b234fbb2ec --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-net-eaddrinuse.js @@ -0,0 +1,44 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. + +'use strict'; +const common = require('../common'); +const assert = require('assert'); +const net = require('net'); + +const server1 = net.createServer(function(socket) { +}); +const server2 = net.createServer(function(socket) { +}); +server1.listen(0, common.mustCall(function() { + server2.on('error', function(error) { + assert.strictEqual(error.message.includes('EADDRINUSE'), true); + server1.close(); + }); + server2.listen(this.address().port); +})); diff --git a/cli/tests/node_compat/test/parallel/test-net-end-close.js b/cli/tests/node_compat/test/parallel/test-net-end-close.js new file mode 100644 index 00000000000000..a818dd0974cf28 --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-net-end-close.js @@ -0,0 +1,44 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 16.13.0 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +// Flags: --expose-internals +'use strict'; +require('../common'); +const assert = require('assert'); +const net = require('net'); + +// const { internalBinding } = require('internal/test/binding'); +// const { UV_EOF } = internalBinding('uv'); +// const { streamBaseState, kReadBytesOrError } = internalBinding('stream_wrap'); + +const s = new net.Socket({ + handle: { + readStart: function() { + setImmediate(() => { + // streamBaseState[kReadBytesOrError] = UV_EOF; + // internal onread has different shape to Node. + this.onread(new Uint8Array(), -4095); + }); + }, + close: (cb) => setImmediate(cb) + }, + writable: false +}); +assert.strictEqual(s, s.resume()); + +const events = []; + +s.on('end', () => { + events.push('end'); +}); +s.on('close', () => { + events.push('close'); +}); + +process.on('exit', () => { + assert.deepStrictEqual(events, [ 'end', 'close' ]); +}); diff --git a/cli/tests/node_compat/test/parallel/test-net-end-destroyed.js b/cli/tests/node_compat/test/parallel/test-net-end-destroyed.js new file mode 100644 index 00000000000000..fc440844ed352d --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-net-end-destroyed.js @@ -0,0 +1,33 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +'use strict'; + +const common = require('../common'); +const net = require('net'); +const assert = require('assert'); + +const server = net.createServer(); + +server.on('connection', common.mustCall()); + +// Ensure that the socket is not destroyed when the 'end' event is emitted. + +server.listen(common.mustCall(function() { + const socket = net.createConnection({ + port: server.address().port + }); + + socket.on('connect', common.mustCall(function() { + socket.on('end', common.mustCall(function() { + assert.strictEqual(socket.destroyed, false); + server.close(); + })); + + socket.end(); + })); +})); diff --git a/cli/tests/node_compat/test/parallel/test-net-end-without-connect.js b/cli/tests/node_compat/test/parallel/test-net-end-without-connect.js new file mode 100644 index 00000000000000..10b7a9e14f30f3 --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-net-end-without-connect.js @@ -0,0 +1,34 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. + +'use strict'; +require('../common'); +const net = require('net'); + +const sock = new net.Socket(); +sock.end(); // Should not throw. diff --git a/cli/tests/node_compat/test/parallel/test-net-isip.js b/cli/tests/node_compat/test/parallel/test-net-isip.js new file mode 100644 index 00000000000000..c85dbcbfd4a5ef --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-net-isip.js @@ -0,0 +1,103 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. + +'use strict'; +require('../common'); +const assert = require('assert'); +const net = require('net'); + +assert.strictEqual(net.isIP('127.0.0.1'), 4); +assert.strictEqual(net.isIP('x127.0.0.1'), 0); +assert.strictEqual(net.isIP('example.com'), 0); +assert.strictEqual(net.isIP('0000:0000:0000:0000:0000:0000:0000:0000'), 6); +assert.strictEqual(net.isIP('0000:0000:0000:0000:0000:0000:0000:0000::0000'), + 0); +assert.strictEqual(net.isIP('1050:0:0:0:5:600:300c:326b'), 6); +assert.strictEqual(net.isIP('2001:252:0:1::2008:6'), 6); +assert.strictEqual(net.isIP('2001:dead:beef:1::2008:6'), 6); +assert.strictEqual(net.isIP('2001::'), 6); +assert.strictEqual(net.isIP('2001:dead::'), 6); +assert.strictEqual(net.isIP('2001:dead:beef::'), 6); +assert.strictEqual(net.isIP('2001:dead:beef:1::'), 6); +assert.strictEqual(net.isIP('ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff'), 6); +assert.strictEqual(net.isIP(':2001:252:0:1::2008:6:'), 0); +assert.strictEqual(net.isIP(':2001:252:0:1::2008:6'), 0); +assert.strictEqual(net.isIP('2001:252:0:1::2008:6:'), 0); +assert.strictEqual(net.isIP('2001:252::1::2008:6'), 0); +assert.strictEqual(net.isIP('::2001:252:1:2008:6'), 6); +assert.strictEqual(net.isIP('::2001:252:1:1.1.1.1'), 6); +assert.strictEqual(net.isIP('::2001:252:1:255.255.255.255'), 6); +assert.strictEqual(net.isIP('::2001:252:1:255.255.255.255.76'), 0); +assert.strictEqual(net.isIP('fe80::2008%eth0'), 6); +assert.strictEqual(net.isIP('fe80::2008%eth0.0'), 6); +assert.strictEqual(net.isIP('fe80::2008%eth0@1'), 0); +assert.strictEqual(net.isIP('::anything'), 0); +assert.strictEqual(net.isIP('::1'), 6); +assert.strictEqual(net.isIP('::'), 6); +assert.strictEqual(net.isIP('0000:0000:0000:0000:0000:0000:12345:0000'), 0); +assert.strictEqual(net.isIP('0'), 0); +assert.strictEqual(net.isIP(), 0); +assert.strictEqual(net.isIP(''), 0); +assert.strictEqual(net.isIP(null), 0); +assert.strictEqual(net.isIP(123), 0); +assert.strictEqual(net.isIP(true), 0); +assert.strictEqual(net.isIP({}), 0); +assert.strictEqual(net.isIP({ toString: () => '::2001:252:1:255.255.255.255' }), + 6); +assert.strictEqual(net.isIP({ toString: () => '127.0.0.1' }), 4); +assert.strictEqual(net.isIP({ toString: () => 'bla' }), 0); + +assert.strictEqual(net.isIPv4('127.0.0.1'), true); +assert.strictEqual(net.isIPv4('example.com'), false); +assert.strictEqual(net.isIPv4('2001:252:0:1::2008:6'), false); +assert.strictEqual(net.isIPv4(), false); +assert.strictEqual(net.isIPv4(''), false); +assert.strictEqual(net.isIPv4(null), false); +assert.strictEqual(net.isIPv4(123), false); +assert.strictEqual(net.isIPv4(true), false); +assert.strictEqual(net.isIPv4({}), false); +assert.strictEqual(net.isIPv4({ + toString: () => '::2001:252:1:255.255.255.255' +}), false); +assert.strictEqual(net.isIPv4({ toString: () => '127.0.0.1' }), true); +assert.strictEqual(net.isIPv4({ toString: () => 'bla' }), false); + +assert.strictEqual(net.isIPv6('127.0.0.1'), false); +assert.strictEqual(net.isIPv6('example.com'), false); +assert.strictEqual(net.isIPv6('2001:252:0:1::2008:6'), true); +assert.strictEqual(net.isIPv6(), false); +assert.strictEqual(net.isIPv6(''), false); +assert.strictEqual(net.isIPv6(null), false); +assert.strictEqual(net.isIPv6(123), false); +assert.strictEqual(net.isIPv6(true), false); +assert.strictEqual(net.isIPv6({}), false); +assert.strictEqual(net.isIPv6({ + toString: () => '::2001:252:1:255.255.255.255' +}), true); +assert.strictEqual(net.isIPv6({ toString: () => '127.0.0.1' }), false); +assert.strictEqual(net.isIPv6({ toString: () => 'bla' }), false); diff --git a/cli/tests/node_compat/test/parallel/test-net-isipv4.js b/cli/tests/node_compat/test/parallel/test-net-isipv4.js new file mode 100644 index 00000000000000..abdbd7b0bfd477 --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-net-isipv4.js @@ -0,0 +1,53 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +'use strict'; +require('../common'); +const assert = require('assert'); +const net = require('net'); + +const v4 = [ + '0.0.0.0', + '8.8.8.8', + '127.0.0.1', + '100.100.100.100', + '192.168.0.1', + '18.101.25.153', + '123.23.34.2', + '172.26.168.134', + '212.58.241.131', + '128.0.0.0', + '23.71.254.72', + '223.255.255.255', + '192.0.2.235', + '99.198.122.146', + '46.51.197.88', + '173.194.34.134', +]; + +const v4not = [ + '.100.100.100.100', + '100..100.100.100.', + '100.100.100.100.', + '999.999.999.999', + '256.256.256.256', + '256.100.100.100.100', + '123.123.123', + 'http://123.123.123', + '1000.2.3.4', + '999.2.3.4', + '0000000192.168.0.200', + '192.168.0.2000000000', +]; + +v4.forEach((ip) => { + assert.strictEqual(net.isIPv4(ip), true); +}); + +v4not.forEach((ip) => { + assert.strictEqual(net.isIPv4(ip), false); +}); diff --git a/cli/tests/node_compat/test/parallel/test-net-isipv6.js b/cli/tests/node_compat/test/parallel/test-net-isipv6.js new file mode 100644 index 00000000000000..9aa09295707467 --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-net-isipv6.js @@ -0,0 +1,251 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +'use strict'; +require('../common'); +const assert = require('assert'); +const net = require('net'); + +const v6 = [ + '::', + '1::', + '::1', + '1::8', + '1::7:8', + '1:2:3:4:5:6:7:8', + '1:2:3:4:5:6::8', + '1:2:3:4:5:6:7::', + '1:2:3:4:5::7:8', + '1:2:3:4:5::8', + '1:2:3::8', + '1::4:5:6:7:8', + '1::6:7:8', + '1::3:4:5:6:7:8', + '1:2:3:4::6:7:8', + '1:2::4:5:6:7:8', + '::2:3:4:5:6:7:8', + '1:2::8', + '2001:0000:1234:0000:0000:C1C0:ABCD:0876', + '3ffe:0b00:0000:0000:0001:0000:0000:000a', + 'FF02:0000:0000:0000:0000:0000:0000:0001', + '0000:0000:0000:0000:0000:0000:0000:0001', + '0000:0000:0000:0000:0000:0000:0000:0000', + '::ffff:192.168.1.26', + '2::10', + 'ff02::1', + 'fe80::', + '2002::', + '2001:db8::', + '2001:0db8:1234::', + '::ffff:0:0', + '::ffff:192.168.1.1', + '1:2:3:4::8', + '1::2:3:4:5:6:7', + '1::2:3:4:5:6', + '1::2:3:4:5', + '1::2:3:4', + '1::2:3', + '::2:3:4:5:6:7', + '::2:3:4:5:6', + '::2:3:4:5', + '::2:3:4', + '::2:3', + '::8', + '1:2:3:4:5:6::', + '1:2:3:4:5::', + '1:2:3:4::', + '1:2:3::', + '1:2::', + '1:2:3:4::7:8', + '1:2:3::7:8', + '1:2::7:8', + '1:2:3:4:5:6:1.2.3.4', + '1:2:3:4:5::1.2.3.4', + '1:2:3:4::1.2.3.4', + '1:2:3::1.2.3.4', + '1:2::1.2.3.4', + '1::1.2.3.4', + '1:2:3:4::5:1.2.3.4', + '1:2:3::5:1.2.3.4', + '1:2::5:1.2.3.4', + '1::5:1.2.3.4', + '1::5:11.22.33.44', + 'fe80::217:f2ff:254.7.237.98', + 'fe80::217:f2ff:fe07:ed62', + '2001:DB8:0:0:8:800:200C:417A', + 'FF01:0:0:0:0:0:0:101', + '0:0:0:0:0:0:0:1', + '0:0:0:0:0:0:0:0', + '2001:DB8::8:800:200C:417A', + 'FF01::101', + '0:0:0:0:0:0:13.1.68.3', + '0:0:0:0:0:FFFF:129.144.52.38', + '::13.1.68.3', + '::FFFF:129.144.52.38', + 'fe80:0000:0000:0000:0204:61ff:fe9d:f156', + 'fe80:0:0:0:204:61ff:fe9d:f156', + 'fe80::204:61ff:fe9d:f156', + 'fe80:0:0:0:204:61ff:254.157.241.86', + 'fe80::204:61ff:254.157.241.86', + 'fe80::1', + '2001:0db8:85a3:0000:0000:8a2e:0370:7334', + '2001:db8:85a3:0:0:8a2e:370:7334', + '2001:db8:85a3::8a2e:370:7334', + '2001:0db8:0000:0000:0000:0000:1428:57ab', + '2001:0db8:0000:0000:0000::1428:57ab', + '2001:0db8:0:0:0:0:1428:57ab', + '2001:0db8:0:0::1428:57ab', + '2001:0db8::1428:57ab', + '2001:db8::1428:57ab', + '::ffff:12.34.56.78', + '::ffff:0c22:384e', + '2001:0db8:1234:0000:0000:0000:0000:0000', + '2001:0db8:1234:ffff:ffff:ffff:ffff:ffff', + '2001:db8:a::123', + '::ffff:192.0.2.128', + '::ffff:c000:280', + 'a:b:c:d:e:f:f1:f2', + 'a:b:c::d:e:f:f1', + 'a:b:c::d:e:f', + 'a:b:c::d:e', + 'a:b:c::d', + '::a', + '::a:b:c', + '::a:b:c:d:e:f:f1', + 'a::', + 'a:b:c::', + 'a:b:c:d:e:f:f1::', + 'a:bb:ccc:dddd:000e:00f:0f::', + '0:a:0:a:0:0:0:a', + '0:a:0:0:a:0:0:a', + '2001:db8:1:1:1:1:0:0', + '2001:db8:1:1:1:0:0:0', + '2001:db8:1:1:0:0:0:0', + '2001:db8:1:0:0:0:0:0', + '2001:db8:0:0:0:0:0:0', + '2001:0:0:0:0:0:0:0', + 'A:BB:CCC:DDDD:000E:00F:0F::', + '0:0:0:0:0:0:0:a', + '0:0:0:0:a:0:0:0', + '0:0:0:a:0:0:0:0', + 'a:0:0:a:0:0:a:a', + 'a:0:0:a:0:0:0:a', + 'a:0:0:0:a:0:0:a', + 'a:0:0:0:a:0:0:0', + 'a:0:0:0:0:0:0:0', + 'fe80::7:8%eth0', + 'fe80::7:8%1', +]; + +const v6not = [ + '', + '1:', + ':1', + '11:36:12', + '02001:0000:1234:0000:0000:C1C0:ABCD:0876', + '2001:0000:1234:0000:00001:C1C0:ABCD:0876', + '2001:0000:1234: 0000:0000:C1C0:ABCD:0876', + '2001:1:1:1:1:1:255Z255X255Y255', + '3ffe:0b00:0000:0001:0000:0000:000a', + 'FF02:0000:0000:0000:0000:0000:0000:0000:0001', + '3ffe:b00::1::a', + '::1111:2222:3333:4444:5555:6666::', + '1:2:3::4:5::7:8', + '12345::6:7:8', + '1::5:400.2.3.4', + '1::5:260.2.3.4', + '1::5:256.2.3.4', + '1::5:1.256.3.4', + '1::5:1.2.256.4', + '1::5:1.2.3.256', + '1::5:300.2.3.4', + '1::5:1.300.3.4', + '1::5:1.2.300.4', + '1::5:1.2.3.300', + '1::5:900.2.3.4', + '1::5:1.900.3.4', + '1::5:1.2.900.4', + '1::5:1.2.3.900', + '1::5:300.300.300.300', + '1::5:3000.30.30.30', + '1::400.2.3.4', + '1::260.2.3.4', + '1::256.2.3.4', + '1::1.256.3.4', + '1::1.2.256.4', + '1::1.2.3.256', + '1::300.2.3.4', + '1::1.300.3.4', + '1::1.2.300.4', + '1::1.2.3.300', + '1::900.2.3.4', + '1::1.900.3.4', + '1::1.2.900.4', + '1::1.2.3.900', + '1::300.300.300.300', + '1::3000.30.30.30', + '::400.2.3.4', + '::260.2.3.4', + '::256.2.3.4', + '::1.256.3.4', + '::1.2.256.4', + '::1.2.3.256', + '::300.2.3.4', + '::1.300.3.4', + '::1.2.300.4', + '::1.2.3.300', + '::900.2.3.4', + '::1.900.3.4', + '::1.2.900.4', + '::1.2.3.900', + '::300.300.300.300', + '::3000.30.30.30', + '2001:DB8:0:0:8:800:200C:417A:221', + 'FF01::101::2', + '1111:2222:3333:4444::5555:', + '1111:2222:3333::5555:', + '1111:2222::5555:', + '1111::5555:', + '::5555:', + ':::', + '1111:', + ':', + ':1111:2222:3333:4444::5555', + ':1111:2222:3333::5555', + ':1111:2222::5555', + ':1111::5555', + ':::5555', + '1.2.3.4:1111:2222:3333:4444::5555', + '1.2.3.4:1111:2222:3333::5555', + '1.2.3.4:1111:2222::5555', + '1.2.3.4:1111::5555', + '1.2.3.4::5555', + '1.2.3.4::', + 'fe80:0000:0000:0000:0204:61ff:254.157.241.086', + '123', + 'ldkfj', + '2001::FFD3::57ab', + '2001:db8:85a3::8a2e:37023:7334', + '2001:db8:85a3::8a2e:370k:7334', + '1:2:3:4:5:6:7:8:9', + '1::2::3', + '1:::3:4:5', + '1:2:3::4:5:6:7:8:9', + '::ffff:2.3.4', + '::ffff:257.1.2.3', + '::ffff:12345678901234567890.1.26', + '2001:0000:1234:0000:0000:C1C0:ABCD:0876 0', + '02001:0000:1234:0000:0000:C1C0:ABCD:0876', +]; + +v6.forEach((ip) => { + assert.strictEqual(net.isIPv6(ip), true); +}); + +v6not.forEach((ip) => { + assert.strictEqual(net.isIPv6(ip), false); +}); diff --git a/cli/tests/node_compat/test/parallel/test-net-listen-after-destroying-stdin.js b/cli/tests/node_compat/test/parallel/test-net-listen-after-destroying-stdin.js new file mode 100644 index 00000000000000..aa1c2ec28a32e2 --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-net-listen-after-destroying-stdin.js @@ -0,0 +1,29 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +'use strict'; +// Just test that destroying stdin doesn't mess up listening on a server. +// This is a regression test for +// https://github.com/nodejs/node-v0.x-archive/issues/746. + +const common = require('../common'); +const net = require('net'); + +process.stdin.destroy(); + +const server = net.createServer(common.mustCall((socket) => { + console.log('accepted...'); + socket.end(common.mustCall(() => { console.log('finished...'); })); + server.close(common.mustCall(() => { console.log('closed'); })); +})); + + +server.listen(0, common.mustCall(() => { + console.log('listening...'); + + net.createConnection(server.address().port); +})); diff --git a/cli/tests/node_compat/test/parallel/test-net-listen-close-server-callback-is-not-function.js b/cli/tests/node_compat/test/parallel/test-net-listen-close-server-callback-is-not-function.js new file mode 100644 index 00000000000000..74e7dbd8cd2175 --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-net-listen-close-server-callback-is-not-function.js @@ -0,0 +1,18 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +'use strict'; +const common = require('../common'); +const net = require('net'); + +const server = net.createServer(common.mustNotCall()); + +server.on('close', common.mustCall()); + +server.listen(0, common.mustNotCall()); + +server.close('bad argument'); diff --git a/cli/tests/node_compat/test/parallel/test-net-listen-close-server.js b/cli/tests/node_compat/test/parallel/test-net-listen-close-server.js new file mode 100644 index 00000000000000..16e861c8886eb0 --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-net-listen-close-server.js @@ -0,0 +1,37 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. + +'use strict'; +const common = require('../common'); +const net = require('net'); + +const server = net.createServer(function(socket) { +}); +server.listen(0, common.mustNotCall()); +server.on('error', common.mustNotCall()); +server.close(); diff --git a/cli/tests/node_compat/test/parallel/test-net-listen-error.js b/cli/tests/node_compat/test/parallel/test-net-listen-error.js new file mode 100644 index 00000000000000..25d2cd90806309 --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-net-listen-error.js @@ -0,0 +1,36 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. + +'use strict'; +const common = require('../common'); +const net = require('net'); + +const server = net.createServer(function(socket) { +}); +server.listen(1, '1.1.1.1', common.mustNotCall()); // EACCES or EADDRNOTAVAIL +server.on('error', common.mustCall()); diff --git a/cli/tests/node_compat/test/parallel/test-net-listen-invalid-port.js b/cli/tests/node_compat/test/parallel/test-net-listen-invalid-port.js new file mode 100644 index 00000000000000..10685f1d0ffedb --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-net-listen-invalid-port.js @@ -0,0 +1,52 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 16.13.0 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +'use strict'; +const common = require('../common'); + +// This test ensures that port numbers are validated in *all* kinds of `listen` +// calls. If an invalid port is supplied, ensures a `RangeError` is thrown. +// https://github.com/nodejs/node/issues/5727 + +const assert = require('assert'); +const net = require('net'); + +const invalidPort = -1 >>> 0; + +// TODO: support net.Server() without new + +new net.Server().listen(0, function() { + const address = this.address(); + const key = `${address.family}:${address.address}:0`; + + assert.strictEqual(this._connectionKey, key); + this.close(); +}); + +// The first argument is a configuration object +assert.throws(() => { + new net.Server().listen({ port: invalidPort }, common.mustNotCall()); +}, { + code: 'ERR_SOCKET_BAD_PORT', + name: 'RangeError' +}); + +// The first argument is the port, no IP given. +assert.throws(() => { + new net.Server().listen(invalidPort, common.mustNotCall()); +}, { + code: 'ERR_SOCKET_BAD_PORT', + name: 'RangeError' +}); + +// The first argument is the port, the second an IP. +assert.throws(() => { + new net.Server().listen(invalidPort, '0.0.0.0', common.mustNotCall()); +}, { + code: 'ERR_SOCKET_BAD_PORT', + name: 'RangeError' +}); diff --git a/cli/tests/node_compat/test/parallel/test-net-listening.js b/cli/tests/node_compat/test/parallel/test-net-listening.js new file mode 100644 index 00000000000000..4ddf72d33d9ad9 --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-net-listening.js @@ -0,0 +1,23 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +'use strict'; +const common = require('../common'); +const assert = require('assert'); +const net = require('net'); + +const server = net.createServer(); + +assert.strictEqual(server.listening, false); + +server.listen(0, common.mustCall(() => { + assert.strictEqual(server.listening, true); + + server.close(common.mustCall(() => { + assert.strictEqual(server.listening, false); + })); +})); diff --git a/cli/tests/node_compat/test/parallel/test-net-local-address-port.js b/cli/tests/node_compat/test/parallel/test-net-local-address-port.js new file mode 100644 index 00000000000000..98a63a952169c1 --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-net-local-address-port.js @@ -0,0 +1,50 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. + +'use strict'; +const common = require('../common'); +const assert = require('assert'); +const net = require('net'); + +const server = net.createServer(common.mustCall(function(socket) { + assert.strictEqual(socket.localAddress, common.localhostIPv4); + assert.strictEqual(socket.localPort, this.address().port); + assert.strictEqual(socket.localFamily, this.address().family); + socket.on('end', function() { + server.close(); + }); + socket.resume(); +})); + +server.listen(0, common.localhostIPv4, function() { + const client = net.createConnection(this.address() + .port, common.localhostIPv4); + client.on('connect', function() { + client.end(); + }); +}); diff --git a/cli/tests/node_compat/test/parallel/test-net-localerror.js b/cli/tests/node_compat/test/parallel/test-net-localerror.js new file mode 100644 index 00000000000000..2c60d00d588057 --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-net-localerror.js @@ -0,0 +1,51 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. + +'use strict'; +require('../common'); +const assert = require('assert'); +const net = require('net'); + +const connect = (opts, code, type) => { + assert.throws( + () => net.connect(opts), + { code, name: type.name } + ); +}; + +connect({ + host: 'localhost', + port: 0, + localAddress: 'foobar', +}, 'ERR_INVALID_IP_ADDRESS', TypeError); + +connect({ + host: 'localhost', + port: 0, + localPort: 'foobar', +}, 'ERR_INVALID_ARG_TYPE', TypeError); diff --git a/cli/tests/node_compat/test/parallel/test-net-options-lookup.js b/cli/tests/node_compat/test/parallel/test-net-options-lookup.js new file mode 100644 index 00000000000000..f03c1e4ff37479 --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-net-options-lookup.js @@ -0,0 +1,55 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +'use strict'; +const common = require('../common'); +const assert = require('assert'); +const net = require('net'); + +['foobar', 1, {}, []].forEach((input) => connectThrows(input)); + +// Using port 0 as lookup is emitted before connecting. +function connectThrows(input) { + const opts = { + host: 'localhost', + port: 0, + lookup: input + }; + + assert.throws(() => { + net.connect(opts); + }, { + code: 'ERR_INVALID_ARG_TYPE', + name: 'TypeError' + }); +} + +connectDoesNotThrow(() => {}); + +function connectDoesNotThrow(input) { + const opts = { + host: 'localhost', + port: 0, + lookup: input + }; + + return net.connect(opts); +} + +{ + // Verify that an error is emitted when an invalid address family is returned. + const s = connectDoesNotThrow((host, options, cb) => { + cb(null, '127.0.0.1', 100); + }); + + s.on('error', common.expectsError({ + code: 'ERR_INVALID_ADDRESS_FAMILY', + host: 'localhost', + port: 0, + message: 'Invalid address family: 100 localhost:0' + })); +} diff --git a/cli/tests/node_compat/test/parallel/test-net-pause-resume-connecting.js b/cli/tests/node_compat/test/parallel/test-net-pause-resume-connecting.js new file mode 100644 index 00000000000000..07f3254625ec34 --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-net-pause-resume-connecting.js @@ -0,0 +1,102 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. + +'use strict'; +const common = require('../common'); +const assert = require('assert'); +const net = require('net'); + +let connections = 0; +let dataEvents = 0; +let conn; + + +// Server +const server = net.createServer(function(conn) { + connections++; + conn.end('This was the year he fell to pieces.'); + + if (connections === 5) + server.close(); +}); + +server.listen(0, function() { + // Client 1 + conn = net.createConnection(this.address().port, 'localhost'); + conn.resume(); + conn.on('data', onDataOk); + + + // Client 2 + conn = net.createConnection(this.address().port, 'localhost'); + conn.pause(); + conn.resume(); + conn.on('data', onDataOk); + + + // Client 3 + conn = net.createConnection(this.address().port, 'localhost'); + conn.pause(); + conn.on('data', common.mustNotCall()); + scheduleTearDown(conn); + + + // Client 4 + conn = net.createConnection(this.address().port, 'localhost'); + conn.resume(); + conn.pause(); + conn.resume(); + conn.on('data', onDataOk); + + + // Client 5 + conn = net.createConnection(this.address().port, 'localhost'); + conn.resume(); + conn.resume(); + conn.pause(); + conn.on('data', common.mustNotCall()); + scheduleTearDown(conn); + + function onDataOk() { + dataEvents++; + } + + function scheduleTearDown(conn) { + setTimeout(function() { + conn.removeAllListeners('data'); + conn.resume(); + }, 100); + } +}); + + +// Exit sanity checks +process.on('exit', function() { + assert.strictEqual(connections, 5); + assert.strictEqual(dataEvents, 3); +}); diff --git a/cli/tests/node_compat/test/parallel/test-net-persistent-ref-unref.js b/cli/tests/node_compat/test/parallel/test-net-persistent-ref-unref.js new file mode 100644 index 00000000000000..06e2c6d0f1c8f8 --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-net-persistent-ref-unref.js @@ -0,0 +1,48 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +// Flags: --expose-internals +'use strict'; +require('../common'); +const assert = require('assert'); +const net = require('net'); +const { internalBinding } = require('internal/test/binding'); +const TCPWrap = internalBinding('tcp_wrap').TCP; + +const echoServer = net.createServer((conn) => { + conn.end(); +}); + +const ref = TCPWrap.prototype.ref; +const unref = TCPWrap.prototype.unref; + +let refCount = 0; + +TCPWrap.prototype.ref = function() { + ref.call(this); + refCount++; + assert.strictEqual(refCount, 0); +}; + +TCPWrap.prototype.unref = function() { + unref.call(this); + refCount--; + assert.strictEqual(refCount, -1); +}; + +echoServer.listen(0); + +echoServer.on('listening', function() { + const sock = new net.Socket(); + sock.unref(); + sock.ref(); + sock.connect(this.address().port); + sock.on('end', () => { + assert.strictEqual(refCount, 0); + echoServer.close(); + }); +}); diff --git a/cli/tests/node_compat/test/parallel/test-net-pipe-connect-errors.js b/cli/tests/node_compat/test/parallel/test-net-pipe-connect-errors.js new file mode 100644 index 00000000000000..6266e4f072f54d --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-net-pipe-connect-errors.js @@ -0,0 +1,104 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. + +'use strict'; +const common = require('../common'); +const fixtures = require('../common/fixtures'); +const fs = require('fs'); +const net = require('net'); +const assert = require('assert'); + +// Test if ENOTSOCK is fired when trying to connect to a file which is not +// a socket. + +let emptyTxt; + +if (common.isWindows) { + // On Win, common.PIPE will be a named pipe, so we use an existing empty + // file instead + emptyTxt = fixtures.path('empty.txt'); +} else { + const tmpdir = require('../common/tmpdir'); + tmpdir.refresh(); + // Keep the file name very short so that we don't exceed the 108 char limit + // on CI for a POSIX socket. Even though this isn't actually a socket file, + // the error will be different from the one we are expecting if we exceed the + // limit. + emptyTxt = `${tmpdir.path}0.txt`; + + function cleanup() { + try { + fs.unlinkSync(emptyTxt); + } catch (e) { + assert.strictEqual(e.code, 'ENOENT'); + } + } + process.on('exit', cleanup); + cleanup(); + fs.writeFileSync(emptyTxt, ''); +} + +const notSocketClient = net.createConnection(emptyTxt, function() { + assert.fail('connection callback should not run'); +}); + +notSocketClient.on('error', common.mustCall(function(err) { + assert(err.code === 'ENOTSOCK' || err.code === 'ECONNREFUSED', + `received ${err.code} instead of ENOTSOCK or ECONNREFUSED`); +})); + + +// Trying to connect to not-existing socket should result in ENOENT error +const noEntSocketClient = net.createConnection('no-ent-file', function() { + assert.fail('connection to non-existent socket, callback should not run'); +}); + +noEntSocketClient.on('error', common.mustCall(function(err) { + assert.strictEqual(err.code, 'ENOENT'); +})); + + +// On Windows or IBMi or when running as root, +// a chmod has no effect on named pipes +if (!common.isWindows && !common.isIBMi && process.getuid() !== 0) { + // Trying to connect to a socket one has no access to should result in EACCES + const accessServer = net.createServer( + common.mustNotCall('server callback should not run')); + accessServer.listen(common.PIPE, common.mustCall(function() { + fs.chmodSync(common.PIPE, 0); + + const accessClient = net.createConnection(common.PIPE, function() { + assert.fail('connection should get EACCES, callback should not run'); + }); + + accessClient.on('error', common.mustCall(function(err) { + assert.strictEqual(err.code, 'EACCES'); + accessServer.close(); + })); + })); +} diff --git a/cli/tests/node_compat/test/parallel/test-net-remote-address-port.js b/cli/tests/node_compat/test/parallel/test-net-remote-address-port.js new file mode 100644 index 00000000000000..c1dc0282c6803e --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-net-remote-address-port.js @@ -0,0 +1,91 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. + +'use strict'; +const common = require('../common'); +const assert = require('assert'); + +const net = require('net'); + +let conns_closed = 0; + +const remoteAddrCandidates = [ common.localhostIPv4, + '::1', + '::ffff:127.0.0.1' ]; + +const remoteFamilyCandidates = ['IPv4', 'IPv6']; + +const server = net.createServer(common.mustCall(function(socket) { + assert.ok(remoteAddrCandidates.includes(socket.remoteAddress), + `Invalid remoteAddress: ${socket.remoteAddress}`); + assert.ok(remoteFamilyCandidates.includes(socket.remoteFamily), + `Invalid remoteFamily: ${socket.remoteFamily}`); + assert.ok(socket.remotePort); + assert.notStrictEqual(socket.remotePort, this.address().port); + socket.on('end', function() { + if (++conns_closed === 2) server.close(); + }); + socket.on('close', function() { + assert.ok(remoteAddrCandidates.includes(socket.remoteAddress)); + assert.ok(remoteFamilyCandidates.includes(socket.remoteFamily)); + }); + socket.resume(); +}, 2)); + +server.listen(0, function() { + const client = net.createConnection(this.address().port, '127.0.0.1'); + const client2 = net.createConnection(this.address().port); + + assert.strictEqual(client.remoteAddress, undefined); + assert.strictEqual(client.remoteFamily, undefined); + assert.strictEqual(client.remotePort, undefined); + assert.strictEqual(client2.remoteAddress, undefined); + assert.strictEqual(client2.remoteFamily, undefined); + assert.strictEqual(client2.remotePort, undefined); + + client.on('connect', function() { + assert.ok(remoteAddrCandidates.includes(client.remoteAddress)); + assert.ok(remoteFamilyCandidates.includes(client.remoteFamily)); + assert.strictEqual(client.remotePort, server.address().port); + client.end(); + }); + client.on('close', function() { + assert.ok(remoteAddrCandidates.includes(client.remoteAddress)); + assert.ok(remoteFamilyCandidates.includes(client.remoteFamily)); + }); + client2.on('connect', function() { + assert.ok(remoteAddrCandidates.includes(client2.remoteAddress)); + assert.ok(remoteFamilyCandidates.includes(client2.remoteFamily)); + assert.strictEqual(client2.remotePort, server.address().port); + client2.end(); + }); + client2.on('close', function() { + assert.ok(remoteAddrCandidates.includes(client2.remoteAddress)); + assert.ok(remoteFamilyCandidates.includes(client2.remoteFamily)); + }); +}); diff --git a/cli/tests/node_compat/test/parallel/test-net-server-call-listen-multiple-times.js b/cli/tests/node_compat/test/parallel/test-net-server-call-listen-multiple-times.js new file mode 100644 index 00000000000000..30b443b1806faf --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-net-server-call-listen-multiple-times.js @@ -0,0 +1,56 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 16.13.0 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +'use strict'; + +const common = require('../common'); +const assert = require('assert'); +const net = require('net'); + +// TODO: support net.Server() without new + +// First test. Check that after error event you can listen right away. +{ + const dummyServer = new net.Server(); + const server = new net.Server(); + + // Run some server in order to simulate EADDRINUSE error. + dummyServer.listen(common.mustCall(() => { + // Try to listen used port. + server.listen(dummyServer.address().port); + })); + + server.on('error', common.mustCall((e) => { + server.listen(common.mustCall(() => { + dummyServer.close(); + server.close(); + })); + })); +} + +// Second test. Check that second listen call throws an error. +{ + const server = new net.Server(); + + server.listen(common.mustCall(() => server.close())); + + assert.throws(() => server.listen(), { + code: 'ERR_SERVER_ALREADY_LISTEN', + name: 'Error' + }); +} + +// Third test. +// Check that after the close call you can run listen method just fine. +{ + const server = new net.Server(); + + server.listen(common.mustCall(() => { + server.close(); + server.listen(common.mustCall(() => server.close())); + })); +} diff --git a/cli/tests/node_compat/test/parallel/test-net-server-capture-rejection.js b/cli/tests/node_compat/test/parallel/test-net-server-capture-rejection.js new file mode 100644 index 00000000000000..d8096251e20e71 --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-net-server-capture-rejection.js @@ -0,0 +1,34 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +'use strict'; + +const common = require('../common'); +const assert = require('assert'); +const events = require('events'); +const { createServer, connect } = require('net'); + +events.captureRejections = true; + +const server = createServer(common.mustCall(async (sock) => { + server.close(); + + const _err = new Error('kaboom'); + sock.on('error', common.mustCall((err) => { + assert.strictEqual(err, _err); + })); + throw _err; +})); + +server.listen(0, common.mustCall(() => { + const sock = connect( + server.address().port, + server.address().host + ); + + sock.on('close', common.mustCall()); +})); diff --git a/cli/tests/node_compat/test/parallel/test-net-server-close.js b/cli/tests/node_compat/test/parallel/test-net-server-close.js new file mode 100644 index 00000000000000..1be4a9c44c99d7 --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-net-server-close.js @@ -0,0 +1,52 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. + +'use strict'; +const common = require('../common'); +const assert = require('assert'); +const net = require('net'); + +const sockets = []; + +const server = net.createServer(function(c) { + c.on('close', common.mustCall()); + + sockets.push(c); + + if (sockets.length === 2) { + assert.strictEqual(server.close(), server); + sockets.forEach((c) => c.destroy()); + } +}); + +server.on('close', common.mustCall()); + +assert.strictEqual(server, server.listen(0, () => { + net.createConnection(server.address().port); + net.createConnection(server.address().port); +})); diff --git a/cli/tests/node_compat/test/parallel/test-net-server-listen-options-signal.js b/cli/tests/node_compat/test/parallel/test-net-server-listen-options-signal.js new file mode 100644 index 00000000000000..ecb14deca121e2 --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-net-server-listen-options-signal.js @@ -0,0 +1,39 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +'use strict'; +const common = require('../common'); +const assert = require('assert'); +const net = require('net'); + +{ + // Test bad signal. + const server = net.createServer(); + assert.throws( + () => server.listen({ port: 0, signal: 'INVALID_SIGNAL' }), + { + code: 'ERR_INVALID_ARG_TYPE', + name: 'TypeError' + }); +} + +{ + // Test close. + const server = net.createServer(); + const controller = new AbortController(); + server.on('close', common.mustCall()); + server.listen({ port: 0, signal: controller.signal }); + controller.abort(); +} + +{ + // Test close with pre-aborted signal. + const server = net.createServer(); + const signal = AbortSignal.abort(); + server.on('close', common.mustCall()); + server.listen({ port: 0, signal }); +} diff --git a/cli/tests/node_compat/test/parallel/test-net-server-listen-options.js b/cli/tests/node_compat/test/parallel/test-net-server-listen-options.js new file mode 100644 index 00000000000000..9a515fd91073d4 --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-net-server-listen-options.js @@ -0,0 +1,101 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +'use strict'; +const common = require('../common'); +const assert = require('assert'); +const net = require('net'); + +function close() { this.close(); } + +{ + // Test listen() + net.createServer().listen().on('listening', common.mustCall(close)); + // Test listen(cb) + net.createServer().listen(common.mustCall(close)); + // Test listen(port) + net.createServer().listen(0).on('listening', common.mustCall(close)); + // Test listen({port}) + net.createServer().listen({ port: 0 }) + .on('listening', common.mustCall(close)); +} + +// Test listen(port, cb) and listen({ port }, cb) combinations +const listenOnPort = [ + (port, cb) => net.createServer().listen({ port }, cb), + (port, cb) => net.createServer().listen(port, cb), +]; + +{ + const assertPort = () => { + return common.expectsError({ + code: 'ERR_SOCKET_BAD_PORT', + name: 'RangeError' + }); + }; + + for (const listen of listenOnPort) { + // Arbitrary unused ports + listen('0', common.mustCall(close)); + listen(0, common.mustCall(close)); + listen(undefined, common.mustCall(close)); + listen(null, common.mustCall(close)); + // Test invalid ports + assert.throws(() => listen(-1, common.mustNotCall()), assertPort()); + assert.throws(() => listen(NaN, common.mustNotCall()), assertPort()); + assert.throws(() => listen(123.456, common.mustNotCall()), assertPort()); + assert.throws(() => listen(65536, common.mustNotCall()), assertPort()); + assert.throws(() => listen(1 / 0, common.mustNotCall()), assertPort()); + assert.throws(() => listen(-1 / 0, common.mustNotCall()), assertPort()); + } + // In listen(options, cb), port takes precedence over path + assert.throws(() => { + net.createServer().listen({ port: -1, path: common.PIPE }, + common.mustNotCall()); + }, assertPort()); +} + +{ + function shouldFailToListen(options) { + const fn = () => { + net.createServer().listen(options, common.mustNotCall()); + }; + + if (typeof options === 'object' && + !(('port' in options) || ('path' in options))) { + assert.throws(fn, + { + code: 'ERR_INVALID_ARG_VALUE', + name: 'TypeError', + message: /^The argument 'options' must have the property "port" or "path"\. Received .+$/, + }); + } else { + assert.throws(fn, + { + code: 'ERR_INVALID_ARG_VALUE', + name: 'TypeError', + message: /^The argument 'options' is invalid\. Received .+$/, + }); + } + } + + shouldFailToListen(false, { port: false }); + shouldFailToListen({ port: false }); + shouldFailToListen(true); + shouldFailToListen({ port: true }); + // Invalid fd as listen(handle) + shouldFailToListen({ fd: -1 }); + // Invalid path in listen(options) + shouldFailToListen({ path: -1 }); + + // Neither port or path are specified in options + shouldFailToListen({}); + shouldFailToListen({ host: 'localhost' }); + shouldFailToListen({ host: 'localhost:3000' }); + shouldFailToListen({ host: { port: 3000 } }); + shouldFailToListen({ exclusive: true }); +} diff --git a/cli/tests/node_compat/test/parallel/test-net-server-listen-path.js b/cli/tests/node_compat/test/parallel/test-net-server-listen-path.js new file mode 100644 index 00000000000000..559e9c7eb4a8b0 --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-net-server-listen-path.js @@ -0,0 +1,100 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 16.13.0 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +'use strict'; + +const common = require('../common'); +const net = require('net'); +const assert = require('assert'); +const fs = require('fs'); + +const tmpdir = require('../common/tmpdir'); +tmpdir.refresh(); + +function closeServer() { + return common.mustCall(function() { + this.close(); + }); +} + +let counter = 0; + +// Avoid conflict with listen-handle +function randomPipePath() { + return `${common.PIPE}-listen-path-${counter++}`; +} + +// Test listen(path) +{ + const handlePath = randomPipePath(); + net.createServer() + .listen(handlePath) + .on('listening', closeServer()); +} + +// Test listen({path}) +{ + const handlePath = randomPipePath(); + net.createServer() + .listen({ path: handlePath }) + .on('listening', closeServer()); +} + +// Test listen(path, cb) +{ + const handlePath = randomPipePath(); + net.createServer() + .listen(handlePath, closeServer()); +} + +// Test listen(path, cb) +{ + const handlePath = randomPipePath(); + net.createServer() + .listen({ path: handlePath }, closeServer()); +} + +// Test pipe chmod +{ + const handlePath = randomPipePath(); + + const server = net.createServer() + .listen({ + path: handlePath, + readableAll: true, + writableAll: true + }, common.mustCall(() => { + if (process.platform !== 'win32') { + const mode = fs.statSync(handlePath).mode; + assert.notStrictEqual(mode & fs.constants.S_IROTH, 0); + assert.notStrictEqual(mode & fs.constants.S_IWOTH, 0); + } + server.close(); + })); +} + +// TODO(cmorten): seems Deno.listen() for Unix domains isn't throwing +// Deno.errors.AddrInUse errors as would expect...? +// Test should emit "error" events when listening fails. +// { +// const handlePath = randomPipePath(); +// const server1 = net.createServer().listen({ path: handlePath }, () => { +// // As the handlePath is in use, binding to the same address again should +// // make the server emit an 'EADDRINUSE' error. +// const server2 = net.createServer() +// .listen({ +// path: handlePath, +// writableAll: true, +// }, common.mustNotCall()); + +// server2.on('error', common.mustCall((err) => { +// server1.close(); +// assert.strictEqual(err.code, 'EADDRINUSE'); +// assert.match(err.message, /^listen EADDRINUSE: address already in use/); +// })); +// }); +// } diff --git a/cli/tests/node_compat/test/parallel/test-net-server-listen-remove-callback.js b/cli/tests/node_compat/test/parallel/test-net-server-listen-remove-callback.js new file mode 100644 index 00000000000000..00a32e1186cee4 --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-net-server-listen-remove-callback.js @@ -0,0 +1,51 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. + +'use strict'; +require('../common'); +const assert = require('assert'); +const net = require('net'); + +// Server should only fire listen callback once +const server = net.createServer(); + +server.on('close', function() { + const listeners = server.listeners('listening'); + console.log('Closed, listeners:', listeners.length); + assert.strictEqual(listeners.length, 0); +}); + +server.listen(0, function() { + server.close(); +}); + +server.once('close', function() { + server.listen(0, function() { + server.close(); + }); +}); diff --git a/cli/tests/node_compat/test/parallel/test-net-server-max-connections.js b/cli/tests/node_compat/test/parallel/test-net-server-max-connections.js new file mode 100644 index 00000000000000..76a6098087ff94 --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-net-server-max-connections.js @@ -0,0 +1,114 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. + +'use strict'; +const common = require('../common'); +const assert = require('assert'); + +const net = require('net'); + +// This test creates 20 connections to a server and sets the server's +// maxConnections property to 10. The first 10 connections make it through +// and the last 10 connections are rejected. + +const N = 20; +let closes = 0; +const waits = []; + +const server = net.createServer(common.mustCall(function(connection) { + connection.write('hello'); + waits.push(function() { connection.end(); }); +}, N / 2)); + +server.listen(0, function() { + makeConnection(0); +}); + +server.maxConnections = N / 2; + + +function makeConnection(index) { + const c = net.createConnection(server.address().port); + let gotData = false; + + c.on('connect', function() { + if (index + 1 < N) { + makeConnection(index + 1); + } + + c.on('close', function() { + console.error(`closed ${index}`); + closes++; + + if (closes < N / 2) { + assert.ok( + server.maxConnections <= index, + `${index} should not have been one of the first closed connections` + ); + } + + if (closes === N / 2) { + let cb; + console.error('calling wait callback.'); + while ((cb = waits.shift()) !== undefined) { + cb(); + } + server.close(); + } + + if (index < server.maxConnections) { + assert.strictEqual(gotData, true, + `${index} didn't get data, but should have`); + } else { + assert.strictEqual(gotData, false, + `${index} got data, but shouldn't have`); + } + }); + }); + + c.on('end', function() { c.end(); }); + + c.on('data', function(b) { + gotData = true; + assert.ok(b.length > 0); + }); + + c.on('error', function(e) { + // Retry if SmartOS and ECONNREFUSED. See + // https://github.com/nodejs/node/issues/2663. + if (common.isSunOS && (e.code === 'ECONNREFUSED')) { + c.connect(server.address().port); + } + console.error(`error ${index}: ${e}`); + }); +} + + +process.on('exit', function() { + assert.strictEqual(closes, N); +}); diff --git a/cli/tests/node_compat/test/parallel/test-net-server-options.js b/cli/tests/node_compat/test/parallel/test-net-server-options.js new file mode 100644 index 00000000000000..6b532e427723d8 --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-net-server-options.js @@ -0,0 +1,23 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +'use strict'; +require('../common'); +const assert = require('assert'); +const net = require('net'); + +assert.throws(() => net.createServer('path'), + { + code: 'ERR_INVALID_ARG_TYPE', + name: 'TypeError' + }); + +assert.throws(() => net.createServer(0), + { + code: 'ERR_INVALID_ARG_TYPE', + name: 'TypeError' + }); diff --git a/cli/tests/node_compat/test/parallel/test-net-server-pause-on-connect.js b/cli/tests/node_compat/test/parallel/test-net-server-pause-on-connect.js new file mode 100644 index 00000000000000..9329a306a675a4 --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-net-server-pause-on-connect.js @@ -0,0 +1,79 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. + +'use strict'; +const common = require('../common'); +const assert = require('assert'); +const net = require('net'); +const msg = 'test'; +let stopped = true; +let server1Sock; + + +const server1ConnHandler = (socket) => { + socket.on('data', function(data) { + if (stopped) { + assert.fail('data event should not have happened yet'); + } + + assert.strictEqual(data.toString(), msg); + socket.end(); + server1.close(); + }); + + server1Sock = socket; +}; + +const server1 = net.createServer({ pauseOnConnect: true }, server1ConnHandler); + +const server2ConnHandler = (socket) => { + socket.on('data', function(data) { + assert.strictEqual(data.toString(), msg); + socket.end(); + server2.close(); + + assert.strictEqual(server1Sock.bytesRead, 0); + server1Sock.resume(); + stopped = false; + }); +}; + +const server2 = net.createServer({ pauseOnConnect: false }, server2ConnHandler); + +server1.listen(0, function() { + const clientHandler = common.mustCall(function() { + server2.listen(0, function() { + net.createConnection({ port: this.address().port }).write(msg); + }); + }); + net.createConnection({ port: this.address().port }).write(msg, clientHandler); +}); + +process.on('exit', function() { + assert.strictEqual(stopped, false); +}); diff --git a/cli/tests/node_compat/test/parallel/test-net-server-try-ports.js b/cli/tests/node_compat/test/parallel/test-net-server-try-ports.js new file mode 100644 index 00000000000000..69dbc78b8d2acd --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-net-server-try-ports.js @@ -0,0 +1,54 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 16.13.0 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. + +'use strict'; +// This test binds to one port, then attempts to start a server on that +// port. It should be EADDRINUSE but be able to then bind to another port. +const common = require('../common'); +const assert = require('assert'); +const net = require('net'); + +// TODO: support net.Server() without new + +const server1 = new net.Server(); + +const server2 = new net.Server(); + +server2.on('error', common.mustCall(function(e) { + assert.strictEqual(e.code, 'EADDRINUSE'); + + server2.listen(0, common.mustCall(function() { + server1.close(); + server2.close(); + })); +})); + +server1.listen(0, common.mustCall(function() { + // This should make server2 emit EADDRINUSE + server2.listen(this.address().port); +})); diff --git a/cli/tests/node_compat/test/parallel/test-net-server-unref-persistent.js b/cli/tests/node_compat/test/parallel/test-net-server-unref-persistent.js new file mode 100644 index 00000000000000..34293df9ea39e8 --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-net-server-unref-persistent.js @@ -0,0 +1,19 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +'use strict'; +const common = require('../common'); +const net = require('net'); +const server = net.createServer(); + +// Unref before listening +server.unref(); +server.listen(); + +// If the timeout fires, that means the server held the event loop open +// and the unref() was not persistent. Close the server and fail the test. +setTimeout(common.mustNotCall(), 1000).unref(); diff --git a/cli/tests/node_compat/test/parallel/test-net-server-unref.js b/cli/tests/node_compat/test/parallel/test-net-server-unref.js new file mode 100644 index 00000000000000..4820c3be44ba24 --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-net-server-unref.js @@ -0,0 +1,37 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. + +'use strict'; +const common = require('../common'); +const net = require('net'); + +const s = net.createServer(); +s.listen(0); +s.unref(); + +setTimeout(common.mustNotCall(), 1000).unref(); diff --git a/cli/tests/node_compat/test/parallel/test-net-socket-close-after-end.js b/cli/tests/node_compat/test/parallel/test-net-socket-close-after-end.js new file mode 100644 index 00000000000000..8e19e09ab806ae --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-net-socket-close-after-end.js @@ -0,0 +1,38 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +'use strict'; +const common = require('../common'); + +const assert = require('assert'); +const net = require('net'); + +const server = net.createServer(); + +server.on('connection', (socket) => { + let endEmitted = false; + + socket.once('readable', () => { + setTimeout(() => { + socket.read(); + }, common.platformTimeout(100)); + }); + socket.on('end', () => { + endEmitted = true; + }); + socket.on('close', () => { + assert(endEmitted); + server.close(); + }); + socket.end('foo'); +}); + +server.listen(common.mustCall(() => { + const socket = net.createConnection(server.address().port, () => { + socket.end('foo'); + }); +})); diff --git a/cli/tests/node_compat/test/parallel/test-net-socket-connect-without-cb.js b/cli/tests/node_compat/test/parallel/test-net-socket-connect-without-cb.js new file mode 100644 index 00000000000000..8f810938051c5b --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-net-socket-connect-without-cb.js @@ -0,0 +1,33 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +'use strict'; +const common = require('../common'); + +// This test ensures that socket.connect can be called without callback +// which is optional. + +const net = require('net'); + +const server = net.createServer(common.mustCall(function(conn) { + conn.end(); + server.close(); +})).listen(0, common.mustCall(function() { + const client = new net.Socket(); + + client.on('connect', common.mustCall(function() { + client.end(); + })); + + const address = server.address(); + if (!common.hasIPv6 && address.family === 'IPv6') { + // Necessary to pass CI running inside containers. + client.connect(address.port); + } else { + client.connect(address); + } +})); diff --git a/cli/tests/node_compat/test/parallel/test-net-socket-connecting.js b/cli/tests/node_compat/test/parallel/test-net-socket-connecting.js new file mode 100644 index 00000000000000..96883f5e12cc9d --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-net-socket-connecting.js @@ -0,0 +1,28 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +'use strict'; +require('../common'); +const assert = require('assert'); +const net = require('net'); + +const server = net.createServer((conn) => { + conn.end(); + server.close(); +}).listen(0, () => { + const client = net.connect(server.address().port, () => { + assert.strictEqual(client.connecting, false); + + // Legacy getter + assert.strictEqual(client._connecting, false); + client.end(); + }); + assert.strictEqual(client.connecting, true); + + // Legacy getter + assert.strictEqual(client._connecting, true); +}); diff --git a/cli/tests/node_compat/test/parallel/test-net-socket-destroy-send.js b/cli/tests/node_compat/test/parallel/test-net-socket-destroy-send.js new file mode 100644 index 00000000000000..47ac7c1add4413 --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-net-socket-destroy-send.js @@ -0,0 +1,31 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +'use strict'; + +const common = require('../common'); +const net = require('net'); +const assert = require('assert'); + +const server = net.createServer(); +server.listen(0, common.mustCall(function() { + const port = server.address().port; + const conn = net.createConnection(port); + + conn.on('connect', common.mustCall(function() { + // Test destroy returns this, even on multiple calls when it short-circuits. + assert.strictEqual(conn, conn.destroy().destroy()); + conn.on('error', common.mustNotCall()); + + conn.write(Buffer.from('kaboom'), common.expectsError({ + code: 'ERR_STREAM_DESTROYED', + message: 'Cannot call write after a stream was destroyed', + name: 'Error' + })); + server.close(); + })); +})); diff --git a/cli/tests/node_compat/test/parallel/test-net-socket-destroy-twice.js b/cli/tests/node_compat/test/parallel/test-net-socket-destroy-twice.js new file mode 100644 index 00000000000000..05027494277e01 --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-net-socket-destroy-twice.js @@ -0,0 +1,43 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. + +'use strict'; +const common = require('../common'); +const net = require('net'); + +const server = net.createServer(); +server.listen(0); +const port = server.address().port; +const conn = net.createConnection(port); + +conn.on('error', common.mustCall(() => { + conn.destroy(); +})); + +conn.on('close', common.mustCall()); +server.close(); diff --git a/cli/tests/node_compat/test/parallel/test-net-socket-end-before-connect.js b/cli/tests/node_compat/test/parallel/test-net-socket-end-before-connect.js new file mode 100644 index 00000000000000..be83964be37c5e --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-net-socket-end-before-connect.js @@ -0,0 +1,20 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +'use strict'; + +const common = require('../common'); + +const net = require('net'); + +const server = net.createServer(); + +server.listen(common.mustCall(() => { + const socket = net.createConnection(server.address().port); + socket.on('close', common.mustCall(() => server.close())); + socket.end(); +})); diff --git a/cli/tests/node_compat/test/parallel/test-net-socket-end-callback.js b/cli/tests/node_compat/test/parallel/test-net-socket-end-callback.js new file mode 100644 index 00000000000000..7b0e48df5b85bd --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-net-socket-end-callback.js @@ -0,0 +1,29 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +'use strict'; + +const common = require('../common'); +const net = require('net'); + +const server = net.createServer((socket) => { + socket.resume(); +}).unref(); + +server.listen(common.mustCall(() => { + const connect = (...args) => { + const socket = net.createConnection(server.address().port, () => { + socket.end(...args); + }); + }; + + const cb = common.mustCall(() => {}, 3); + + connect(cb); + connect('foo', cb); + connect('foo', 'utf8', cb); +})); diff --git a/cli/tests/node_compat/test/parallel/test-net-socket-no-halfopen-enforcer.js b/cli/tests/node_compat/test/parallel/test-net-socket-no-halfopen-enforcer.js new file mode 100644 index 00000000000000..84c14ba71b5cd1 --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-net-socket-no-halfopen-enforcer.js @@ -0,0 +1,18 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +'use strict'; +require('../common'); + +// This test ensures that `net.Socket` does not inherit the no-half-open +// enforcer from `stream.Duplex`. + +const { Socket } = require('net'); +const { strictEqual } = require('assert'); + +const socket = new Socket({ allowHalfOpen: false }); +strictEqual(socket.listenerCount('end'), 1); diff --git a/cli/tests/node_compat/test/parallel/test-net-socket-ready-without-cb.js b/cli/tests/node_compat/test/parallel/test-net-socket-ready-without-cb.js new file mode 100644 index 00000000000000..29cf729dfa4812 --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-net-socket-ready-without-cb.js @@ -0,0 +1,27 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +'use strict'; +const common = require('../common'); + +// This test ensures that socket.connect can be called without callback +// which is optional. + +const net = require('net'); + +const server = net.createServer(common.mustCall(function(conn) { + conn.end(); + server.close(); +})).listen(0, 'localhost', common.mustCall(function() { + const client = new net.Socket(); + + client.on('ready', common.mustCall(function() { + client.end(); + })); + + client.connect(server.address()); +})); diff --git a/cli/tests/node_compat/test/parallel/test-net-socket-timeout.js b/cli/tests/node_compat/test/parallel/test-net-socket-timeout.js new file mode 100644 index 00000000000000..b69e4053088c2b --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-net-socket-timeout.js @@ -0,0 +1,88 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 16.13.0 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. + +'use strict'; +const common = require('../common'); +const net = require('net'); +const assert = require('assert'); +const { inspect } = require('util'); + +// Verify that invalid delays throw +const s = new net.Socket(); +const nonNumericDelays = [ + '100', true, false, undefined, null, '', {}, () => {}, [], +]; +const badRangeDelays = [-0.001, -1, -Infinity, Infinity, NaN]; +const validDelays = [0, 0.001, 1, 1e6]; +const invalidCallbacks = [ + 1, '100', true, false, null, {}, [], Symbol('test'), +]; + + +for (let i = 0; i < nonNumericDelays.length; i++) { + assert.throws(() => { + s.setTimeout(nonNumericDelays[i], () => {}); + }, { code: 'ERR_INVALID_ARG_TYPE' }, nonNumericDelays[i]); +} + +for (let i = 0; i < badRangeDelays.length; i++) { + assert.throws(() => { + s.setTimeout(badRangeDelays[i], () => {}); + }, { code: 'ERR_OUT_OF_RANGE' }, badRangeDelays[i]); +} + +for (let i = 0; i < validDelays.length; i++) { + s.setTimeout(validDelays[i], () => {}); +} + +for (let i = 0; i < invalidCallbacks.length; i++) { + [0, 1].forEach((mesc) => + assert.throws( + () => s.setTimeout(mesc, invalidCallbacks[i]), + { + code: 'ERR_INVALID_ARG_TYPE', + name: 'TypeError', + } + ) + ); +} + +// TODO: support net.Server() without new + +const server = new net.Server(); +server.listen(0, common.mustCall(() => { + const socket = net.createConnection(server.address().port); + assert.strictEqual( + socket.setTimeout(1, common.mustCall(() => { + socket.destroy(); + assert.strictEqual(socket.setTimeout(1, common.mustNotCall()), socket); + server.close(); + })), + socket + ); +})); diff --git a/cli/tests/node_compat/test/parallel/test-net-socket-write-after-close.js b/cli/tests/node_compat/test/parallel/test-net-socket-write-after-close.js new file mode 100644 index 00000000000000..abd30aee11687a --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-net-socket-write-after-close.js @@ -0,0 +1,49 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +'use strict'; +const common = require('../common'); +const assert = require('assert'); +const net = require('net'); + +{ + const server = net.createServer(); + + server.listen(common.mustCall(() => { + const port = server.address().port; + const client = net.connect({ port }, common.mustCall(() => { + client.on('error', common.mustCall((err) => { + server.close(); + assert.strictEqual(err.constructor, Error); + assert.strictEqual(err.message, 'write EBADF'); + })); + client._handle.close(); + client.write('foo'); + })); + })); +} + +{ + const server = net.createServer(); + + server.listen(common.mustCall(() => { + const port = server.address().port; + const client = net.connect({ port }, common.mustCall(() => { + client.on('error', common.expectsError({ + code: 'ERR_SOCKET_CLOSED', + message: 'Socket is closed', + name: 'Error' + })); + + server.close(); + + client._handle.close(); + client._handle = null; + client.write('foo'); + })); + })); +} diff --git a/cli/tests/node_compat/test/parallel/test-net-socket-write-error.js b/cli/tests/node_compat/test/parallel/test-net-socket-write-error.js new file mode 100644 index 00000000000000..d38ab30b4046fe --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-net-socket-write-error.js @@ -0,0 +1,29 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +'use strict'; + +const common = require('../common'); +const net = require('net'); +const assert = require('assert'); + +const server = net.createServer().listen(0, connectToServer); + +function connectToServer() { + const client = net.createConnection(this.address().port, () => { + client.on('error', common.mustNotCall()); + assert.throws(() => { + client.write(1337); + }, { + code: 'ERR_INVALID_ARG_TYPE', + name: 'TypeError' + }); + + client.destroy(); + }) + .on('close', () => server.close()); +} diff --git a/cli/tests/node_compat/test/parallel/test-net-sync-cork.js b/cli/tests/node_compat/test/parallel/test-net-sync-cork.js new file mode 100644 index 00000000000000..eb4274b7406d3f --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-net-sync-cork.js @@ -0,0 +1,40 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +'use strict'; + +const common = require('../common'); +const assert = require('assert'); +const net = require('net'); + +const server = net.createServer(handle); + +const N = 100; +const buf = Buffer.alloc(2, 'a'); + +server.listen(0, function() { + const conn = net.connect(this.address().port); + + conn.on('connect', () => { + let res = true; + let i = 0; + for (; i < N && res; i++) { + conn.cork(); + conn.write(buf); + res = conn.write(buf); + conn.uncork(); + } + assert.strictEqual(i, N); + conn.end(); + }); +}); + +function handle(socket) { + socket.resume(); + socket.on('error', common.mustNotCall()) + .on('close', common.mustCall(() => server.close())); +} diff --git a/cli/tests/node_compat/test/parallel/test-net-timeout-no-handle.js b/cli/tests/node_compat/test/parallel/test-net-timeout-no-handle.js new file mode 100644 index 00000000000000..aa12da70fa2fb9 --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-net-timeout-no-handle.js @@ -0,0 +1,24 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +'use strict'; + +const common = require('../common'); +const net = require('net'); +const assert = require('assert'); + +const socket = new net.Socket(); +socket.setTimeout(common.platformTimeout(50)); + +socket.on('timeout', common.mustCall(() => { + assert.strictEqual(socket._handle, null); +})); + +socket.on('connect', common.mustNotCall()); + +// Since the timeout is unrefed, the code will exit without this +setTimeout(() => {}, common.platformTimeout(200)); diff --git a/cli/tests/node_compat/test/parallel/test-net-writable.js b/cli/tests/node_compat/test/parallel/test-net-writable.js new file mode 100644 index 00000000000000..a7cc5e5beedfd0 --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-net-writable.js @@ -0,0 +1,22 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +'use strict'; +const common = require('../common'); +const assert = require('assert'); +const net = require('net'); + +const server = net.createServer(common.mustCall(function(s) { + server.close(); + s.end(); +})).listen(0, '127.0.0.1', common.mustCall(function() { + const socket = net.connect(this.address().port, '127.0.0.1'); + socket.on('end', common.mustCall(() => { + assert.strictEqual(socket.writable, true); + socket.write('hello world'); + })); +})); diff --git a/cli/tests/node_compat/test/parallel/test-net-write-after-end-nt.js b/cli/tests/node_compat/test/parallel/test-net-write-after-end-nt.js new file mode 100644 index 00000000000000..8f38d3bfdadc74 --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-net-write-after-end-nt.js @@ -0,0 +1,39 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +'use strict'; +const common = require('../common'); + +const assert = require('assert'); +const net = require('net'); + +const { expectsError, mustCall } = common; + +// This test ensures those errors caused by calling `net.Socket.write()` +// after sockets ending will be emitted in the next tick. +const server = net.createServer(mustCall((socket) => { + socket.end(); +})).listen(() => { + const client = net.connect(server.address().port, () => { + let hasError = false; + client.on('error', mustCall((err) => { + hasError = true; + server.close(); + })); + client.on('end', mustCall(() => { + const ret = client.write('hello', expectsError({ + code: 'EPIPE', + message: 'This socket has been ended by the other party', + name: 'Error' + })); + + assert.strictEqual(ret, false); + assert(!hasError, 'The error should be emitted in the next tick.'); + })); + client.end(); + }); +}); diff --git a/cli/tests/node_compat/test/parallel/test-net-write-arguments.js b/cli/tests/node_compat/test/parallel/test-net-write-arguments.js new file mode 100644 index 00000000000000..d6beb72ee162bc --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-net-write-arguments.js @@ -0,0 +1,46 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 16.13.0 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +'use strict'; +const common = require('../common'); +const net = require('net'); +const assert = require('assert'); +const socket = new net.Stream({ highWaterMark: 0 }); + +// Make sure that anything besides a buffer or a string throws. +socket.on('error', common.mustNotCall()); +assert.throws(() => { + socket.write(null); +}, { + code: 'ERR_STREAM_NULL_VALUES', + name: 'TypeError', + message: 'May not write null values to stream' +}); + +[ + true, + false, + undefined, + 1, + 1.0, + +Infinity, + -Infinity, + [], + {}, +].forEach((value) => { + const socket = new net.Stream({ highWaterMark: 0 }); + // We need to check the callback since 'error' will only + // be emitted once per instance. + assert.throws(() => { + socket.write(value); + }, { + code: 'ERR_INVALID_ARG_TYPE', + name: 'TypeError', + message: 'The "chunk" argument must be of type string or an instance of ' + + `Buffer or Uint8Array.${common.invalidArgTypeHelper(value)}` + }); +}); diff --git a/cli/tests/node_compat/test/parallel/test-net-write-fully-async-buffer.js b/cli/tests/node_compat/test/parallel/test-net-write-fully-async-buffer.js new file mode 100644 index 00000000000000..5e93ad4e225079 --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-net-write-fully-async-buffer.js @@ -0,0 +1,41 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +'use strict'; +// Flags: --expose-gc + +// Note: This is a variant of test-net-write-fully-async-hex-string.js. +// This always worked, but it seemed appropriate to add a test that checks the +// behavior for Buffers, too. +const common = require('../common'); +const net = require('net'); + +const data = Buffer.alloc(1000000); + +const server = net.createServer(common.mustCall(function(conn) { + conn.resume(); +})).listen(0, common.mustCall(function() { + const conn = net.createConnection(this.address().port, common.mustCall(() => { + let count = 0; + + function writeLoop() { + if (count++ === 200) { + conn.destroy(); + server.close(); + return; + } + + while (conn.write(Buffer.from(data))); + global.gc({ type: 'minor' }); + // The buffer allocated above should still be alive. + } + + conn.on('drain', writeLoop); + + writeLoop(); + })); +})); diff --git a/cli/tests/node_compat/test/parallel/test-net-write-fully-async-hex-string.js b/cli/tests/node_compat/test/parallel/test-net-write-fully-async-hex-string.js new file mode 100644 index 00000000000000..ff355aa2a7ba8b --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-net-write-fully-async-hex-string.js @@ -0,0 +1,39 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +'use strict'; +// Flags: --expose-gc + +// Regression test for https://github.com/nodejs/node/issues/8251. +const common = require('../common'); +const net = require('net'); + +const data = Buffer.alloc(1000000).toString('hex'); + +const server = net.createServer(common.mustCall(function(conn) { + conn.resume(); +})).listen(0, common.mustCall(function() { + const conn = net.createConnection(this.address().port, common.mustCall(() => { + let count = 0; + + function writeLoop() { + if (count++ === 20) { + conn.destroy(); + server.close(); + return; + } + + while (conn.write(data, 'hex')); + global.gc({ type: 'minor' }); + // The buffer allocated inside the .write() call should still be alive. + } + + conn.on('drain', writeLoop); + + writeLoop(); + })); +})); diff --git a/cli/tests/node_compat/test/parallel/test-net-write-slow.js b/cli/tests/node_compat/test/parallel/test-net-write-slow.js new file mode 100644 index 00000000000000..6c00efb1f4cbfb --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-net-write-slow.js @@ -0,0 +1,70 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. + +'use strict'; +const common = require('../common'); +const assert = require('assert'); +const net = require('net'); + +const SIZE = 2E5; +const N = 10; +let flushed = 0; +let received = 0; +const buf = Buffer.alloc(SIZE, 'a'); + +const server = net.createServer(function(socket) { + socket.setNoDelay(); + socket.setTimeout(9999); + socket.on('timeout', function() { + assert.fail(`flushed: ${flushed}, received: ${received}/${SIZE * N}`); + }); + + for (let i = 0; i < N; ++i) { + socket.write(buf, function() { + ++flushed; + if (flushed === N) { + socket.setTimeout(0); + } + }); + } + socket.end(); + +}).listen(0, common.mustCall(function() { + const conn = net.connect(this.address().port); + conn.on('data', function(buf) { + received += buf.length; + conn.pause(); + setTimeout(function() { + conn.resume(); + }, 20); + }); + conn.on('end', common.mustCall(function() { + server.close(); + assert.strictEqual(received, SIZE * N); + })); +})); diff --git a/cli/tests/node_compat/test/parallel/test-next-tick-doesnt-hang.js b/cli/tests/node_compat/test/parallel/test-next-tick-doesnt-hang.js new file mode 100644 index 00000000000000..a614fe850272bf --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-next-tick-doesnt-hang.js @@ -0,0 +1,37 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. + +'use strict'; + +// This test verifies that having a single nextTick statement and nothing else +// does not hang the event loop. If this test times out it has failed. + +require('../common'); +process.nextTick(function() { + // Nothing +}); diff --git a/cli/tests/node_compat/test/parallel/test-next-tick-fixed-queue-regression.js b/cli/tests/node_compat/test/parallel/test-next-tick-fixed-queue-regression.js new file mode 100644 index 00000000000000..254a756eae35f6 --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-next-tick-fixed-queue-regression.js @@ -0,0 +1,25 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +'use strict'; + +const common = require('../common'); + +// This tests a highly specific regression tied to the FixedQueue size, which +// was introduced in Node.js 9.7.0: https://github.com/nodejs/node/pull/18617 +// More specifically, a nextTick list could potentially end up not fully +// clearing in one run through if exactly 2048 ticks were added after +// microtasks were executed within the nextTick loop. + +process.nextTick(() => { + Promise.resolve(1).then(() => { + for (let i = 0; i < 2047; i++) + process.nextTick(common.mustCall()); + const immediate = setImmediate(common.mustNotCall()); + process.nextTick(common.mustCall(() => clearImmediate(immediate))); + }); +}); diff --git a/cli/tests/node_compat/test/parallel/test-next-tick-intentional-starvation.js b/cli/tests/node_compat/test/parallel/test-next-tick-intentional-starvation.js new file mode 100644 index 00000000000000..e7ab4d44565217 --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-next-tick-intentional-starvation.js @@ -0,0 +1,68 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. + +'use strict'; +require('../common'); +const assert = require('assert'); + +// This is the inverse of test-next-tick-starvation. it verifies +// that process.nextTick will *always* come before other events + +let ran = false; +let starved = false; +const start = +new Date(); +let timerRan = false; + +function spin() { + ran = true; + const now = +new Date(); + if (now - start > 100) { + console.log('The timer is starving, just as we planned.'); + starved = true; + + // now let it out. + return; + } + + process.nextTick(spin); +} + +function onTimeout() { + if (!starved) throw new Error('The timer escaped!'); + console.log('The timer ran once the ban was lifted'); + timerRan = true; +} + +spin(); +setTimeout(onTimeout, 50); + +process.on('exit', function() { + assert.ok(ran); + assert.ok(starved); + assert.ok(timerRan); +}); diff --git a/cli/tests/node_compat/test/parallel/test-next-tick-ordering.js b/cli/tests/node_compat/test/parallel/test-next-tick-ordering.js new file mode 100644 index 00000000000000..6108808d72b1e8 --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-next-tick-ordering.js @@ -0,0 +1,62 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. + +'use strict'; +require('../common'); +const assert = require('assert'); +let i; + +const N = 30; +const done = []; + +function get_printer(timeout) { + return function() { + console.log(`Running from setTimeout ${timeout}`); + done.push(timeout); + }; +} + +process.nextTick(function() { + console.log('Running from nextTick'); + done.push('nextTick'); +}); + +for (i = 0; i < N; i += 1) { + setTimeout(get_printer(i), i); +} + +console.log('Running from main.'); + + +process.on('exit', function() { + assert.strictEqual(done[0], 'nextTick'); + // Disabling this test. I don't think we can ensure the order + // for (i = 0; i < N; i += 1) { + // assert.strictEqual(i, done[i + 1]); + // } +}); diff --git a/cli/tests/node_compat/test/parallel/test-next-tick-ordering2.js b/cli/tests/node_compat/test/parallel/test-next-tick-ordering2.js new file mode 100644 index 00000000000000..a1929efa49644d --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-next-tick-ordering2.js @@ -0,0 +1,46 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. + +'use strict'; +require('../common'); +const assert = require('assert'); + +const order = []; +process.nextTick(function() { + setTimeout(function() { + order.push('setTimeout'); + }, 0); + + process.nextTick(function() { + order.push('nextTick'); + }); +}); + +process.on('exit', function() { + assert.deepStrictEqual(order, ['nextTick', 'setTimeout']); +}); diff --git a/cli/tests/node_compat/test/parallel/test-next-tick-when-exiting.js b/cli/tests/node_compat/test/parallel/test-next-tick-when-exiting.js new file mode 100644 index 00000000000000..996c764626e15f --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-next-tick-when-exiting.js @@ -0,0 +1,21 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +'use strict'; + +const common = require('../common'); +const assert = require('assert'); + +process.on('exit', () => { + assert.strictEqual(process._exiting, true); + + process.nextTick( + common.mustNotCall('process is exiting, should not be called') + ); +}); + +process.exit(); diff --git a/cli/tests/node_compat/test/parallel/test-next-tick.js b/cli/tests/node_compat/test/parallel/test-next-tick.js new file mode 100644 index 00000000000000..dc989fd7627a4d --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-next-tick.js @@ -0,0 +1,70 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. + +'use strict'; +const common = require('../common'); + +const assert = require('assert'); + +process.nextTick(common.mustCall(function() { + process.nextTick(common.mustCall(function() { + process.nextTick(common.mustCall()); + })); +})); + +setTimeout(common.mustCall(function() { + process.nextTick(common.mustCall()); +}), 50); + +process.nextTick(common.mustCall()); + +const obj = {}; + +process.nextTick(function(a, b) { + assert.strictEqual(a, 42); + assert.strictEqual(b, obj); + assert.strictEqual(this, undefined); +}, 42, obj); + +process.nextTick((a, b) => { + assert.strictEqual(a, 42); + assert.strictEqual(b, obj); + assert.deepStrictEqual(this, {}); +}, 42, obj); + +process.nextTick(function() { + assert.strictEqual(this, undefined); +}, 1, 2, 3, 4); + +process.nextTick(() => { + assert.deepStrictEqual(this, {}); +}, 1, 2, 3, 4); + +process.on('exit', function() { + process.nextTick(common.mustNotCall()); +}); diff --git a/cli/tests/node_compat/test/parallel/test-nodeeventtarget.js b/cli/tests/node_compat/test/parallel/test-nodeeventtarget.js new file mode 100644 index 00000000000000..3bf102c5ce8be5 --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-nodeeventtarget.js @@ -0,0 +1,190 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +// Flags: --expose-internals --no-warnings +'use strict'; + +const common = require('../common'); +const { NodeEventTarget } = require('internal/event_target'); + +const { + deepStrictEqual, + ok, + strictEqual, + throws, +} = require('assert'); + +const { on } = require('events'); + +{ + const eventTarget = new NodeEventTarget(); + strictEqual(eventTarget.listenerCount('foo'), 0); + deepStrictEqual(eventTarget.eventNames(), []); + + const ev1 = common.mustCall(function(event) { + strictEqual(event.type, 'foo'); + strictEqual(this, eventTarget); + }, 2); + + const ev2 = { + handleEvent: common.mustCall(function(event) { + strictEqual(event.type, 'foo'); + strictEqual(this, ev2); + }) + }; + + eventTarget.addEventListener('foo', ev1); + eventTarget.addEventListener('foo', ev2, { once: true }); + strictEqual(eventTarget.listenerCount('foo'), 2); + ok(eventTarget.dispatchEvent(new Event('foo'))); + strictEqual(eventTarget.listenerCount('foo'), 1); + eventTarget.dispatchEvent(new Event('foo')); + + eventTarget.removeEventListener('foo', ev1); + strictEqual(eventTarget.listenerCount('foo'), 0); + eventTarget.dispatchEvent(new Event('foo')); +} + +{ + const eventTarget = new NodeEventTarget(); + strictEqual(eventTarget.listenerCount('foo'), 0); + deepStrictEqual(eventTarget.eventNames(), []); + + const ev1 = common.mustCall((event) => { + strictEqual(event.type, 'foo'); + }, 2); + + const ev2 = { + handleEvent: common.mustCall((event) => { + strictEqual(event.type, 'foo'); + }) + }; + + strictEqual(eventTarget.on('foo', ev1), eventTarget); + strictEqual(eventTarget.once('foo', ev2, { once: true }), eventTarget); + strictEqual(eventTarget.listenerCount('foo'), 2); + eventTarget.dispatchEvent(new Event('foo')); + strictEqual(eventTarget.listenerCount('foo'), 1); + eventTarget.dispatchEvent(new Event('foo')); + + strictEqual(eventTarget.off('foo', ev1), eventTarget); + strictEqual(eventTarget.listenerCount('foo'), 0); + eventTarget.dispatchEvent(new Event('foo')); +} + +{ + const eventTarget = new NodeEventTarget(); + strictEqual(eventTarget.listenerCount('foo'), 0); + deepStrictEqual(eventTarget.eventNames(), []); + + const ev1 = common.mustCall((event) => { + strictEqual(event.type, 'foo'); + }, 2); + + const ev2 = { + handleEvent: common.mustCall((event) => { + strictEqual(event.type, 'foo'); + }) + }; + + eventTarget.addListener('foo', ev1); + eventTarget.once('foo', ev2, { once: true }); + strictEqual(eventTarget.listenerCount('foo'), 2); + eventTarget.dispatchEvent(new Event('foo')); + strictEqual(eventTarget.listenerCount('foo'), 1); + eventTarget.dispatchEvent(new Event('foo')); + + eventTarget.removeListener('foo', ev1); + strictEqual(eventTarget.listenerCount('foo'), 0); + eventTarget.dispatchEvent(new Event('foo')); +} + +{ + const eventTarget = new NodeEventTarget(); + strictEqual(eventTarget.listenerCount('foo'), 0); + deepStrictEqual(eventTarget.eventNames(), []); + + // Won't actually be called. + const ev1 = () => {}; + + // Won't actually be called. + const ev2 = { handleEvent() {} }; + + eventTarget.addListener('foo', ev1); + eventTarget.addEventListener('foo', ev1); + eventTarget.once('foo', ev2, { once: true }); + eventTarget.once('foo', ev2, { once: false }); + eventTarget.on('bar', ev1); + strictEqual(eventTarget.listenerCount('foo'), 2); + strictEqual(eventTarget.listenerCount('bar'), 1); + deepStrictEqual(eventTarget.eventNames(), ['foo', 'bar']); + strictEqual(eventTarget.removeAllListeners('foo'), eventTarget); + strictEqual(eventTarget.listenerCount('foo'), 0); + strictEqual(eventTarget.listenerCount('bar'), 1); + deepStrictEqual(eventTarget.eventNames(), ['bar']); + strictEqual(eventTarget.removeAllListeners(), eventTarget); + strictEqual(eventTarget.listenerCount('foo'), 0); + strictEqual(eventTarget.listenerCount('bar'), 0); + deepStrictEqual(eventTarget.eventNames(), []); +} + +{ + const target = new NodeEventTarget(); + + process.on('warning', common.mustCall((warning) => { + ok(warning instanceof Error); + strictEqual(warning.name, 'MaxListenersExceededWarning'); + strictEqual(warning.target, target); + strictEqual(warning.count, 2); + strictEqual(warning.type, 'foo'); + ok(warning.message.includes( + '2 foo listeners added to NodeEventTarget')); + })); + + strictEqual(target.getMaxListeners(), NodeEventTarget.defaultMaxListeners); + target.setMaxListeners(1); + target.on('foo', () => {}); + target.on('foo', () => {}); +} +{ + // Test NodeEventTarget emit + const emitter = new NodeEventTarget(); + emitter.addEventListener('foo', common.mustCall((e) => { + strictEqual(e.type, 'foo'); + strictEqual(e.detail, 'bar'); + ok(e instanceof Event); + }), { once: true }); + emitter.once('foo', common.mustCall((e, droppedAdditionalArgument) => { + strictEqual(e, 'bar'); + strictEqual(droppedAdditionalArgument, undefined); + })); + emitter.emit('foo', 'bar', 'baz'); +} +{ + // Test NodeEventTarget emit unsupported usage + const emitter = new NodeEventTarget(); + throws(() => { + emitter.emit(); + }, /ERR_INVALID_ARG_TYPE/); +} + +(async () => { + // test NodeEventTarget async-iterability + const emitter = new NodeEventTarget(); + const interval = setInterval(() => { + emitter.dispatchEvent(new Event('foo')); + }, 0); + let count = 0; + for await (const [ item ] of on(emitter, 'foo')) { + count++; + strictEqual(item.type, 'foo'); + if (count > 5) { + break; + } + } + clearInterval(interval); +})().then(common.mustCall()); diff --git a/cli/tests/node_compat/test/parallel/test-os.js b/cli/tests/node_compat/test/parallel/test-os.js new file mode 100644 index 00000000000000..09d97222c58552 --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-os.js @@ -0,0 +1,278 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 16.13.0 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. + +'use strict'; +const common = require('../common'); +const assert = require('assert'); +const os = require('os'); +const path = require('path'); +const { inspect } = require('util'); + +const is = { + number: (value, key) => { + assert(!Number.isNaN(value), `${key} should not be NaN`); + assert.strictEqual(typeof value, 'number'); + }, + string: (value) => { assert.strictEqual(typeof value, 'string'); }, + array: (value) => { assert.ok(Array.isArray(value)); }, + object: (value) => { + assert.strictEqual(typeof value, 'object'); + assert.notStrictEqual(value, null); + } +}; + +/* TODO(kt3k): Enable this test +process.env.TMPDIR = '/tmpdir'; +process.env.TMP = '/tmp'; +process.env.TEMP = '/temp'; +if (common.isWindows) { + assert.strictEqual(os.tmpdir(), '/temp'); + process.env.TEMP = ''; + assert.strictEqual(os.tmpdir(), '/tmp'); + process.env.TMP = ''; + const expected = `${process.env.SystemRoot || process.env.windir}\\temp`; + assert.strictEqual(os.tmpdir(), expected); + process.env.TEMP = '\\temp\\'; + assert.strictEqual(os.tmpdir(), '\\temp'); + process.env.TEMP = '\\tmpdir/'; + assert.strictEqual(os.tmpdir(), '\\tmpdir/'); + process.env.TEMP = '\\'; + assert.strictEqual(os.tmpdir(), '\\'); + process.env.TEMP = 'C:\\'; + assert.strictEqual(os.tmpdir(), 'C:\\'); +} else { + assert.strictEqual(os.tmpdir(), '/tmpdir'); + process.env.TMPDIR = ''; + assert.strictEqual(os.tmpdir(), '/tmp'); + process.env.TMP = ''; + assert.strictEqual(os.tmpdir(), '/temp'); + process.env.TEMP = ''; + assert.strictEqual(os.tmpdir(), '/tmp'); + process.env.TMPDIR = '/tmpdir/'; + assert.strictEqual(os.tmpdir(), '/tmpdir'); + process.env.TMPDIR = '/tmpdir\\'; + assert.strictEqual(os.tmpdir(), '/tmpdir\\'); + process.env.TMPDIR = '/'; + assert.strictEqual(os.tmpdir(), '/'); +} +*/ + +const endianness = os.endianness(); +is.string(endianness); +assert.match(endianness, /[BL]E/); + +const hostname = os.hostname(); +is.string(hostname); +assert.ok(hostname.length > 0); + +// On IBMi, os.uptime() returns 'undefined' +if (!common.isIBMi) { + const uptime = os.uptime(); + is.number(uptime); + assert.ok(uptime > 0); +} + +const cpus = os.cpus(); +is.array(cpus); +assert.ok(cpus.length > 0); +for (const cpu of cpus) { + assert.strictEqual(typeof cpu.model, 'string'); + assert.strictEqual(typeof cpu.speed, 'number'); + assert.strictEqual(typeof cpu.times.user, 'number'); + assert.strictEqual(typeof cpu.times.nice, 'number'); + assert.strictEqual(typeof cpu.times.sys, 'number'); + assert.strictEqual(typeof cpu.times.idle, 'number'); + assert.strictEqual(typeof cpu.times.irq, 'number'); +} + +const type = os.type(); +is.string(type); +assert.ok(type.length > 0); + +const release = os.release(); +is.string(release); +assert.ok(release.length > 0); +// TODO: Check format on more than just AIX +if (common.isAIX) + assert.match(release, /^\d+\.\d+$/); + +const platform = os.platform(); +is.string(platform); +assert.ok(platform.length > 0); + +const arch = os.arch(); +is.string(arch); +assert.ok(arch.length > 0); + +if (!common.isSunOS) { + // not implemented yet + assert.ok(os.loadavg().length > 0); + assert.ok(os.freemem() > 0); + assert.ok(os.totalmem() > 0); +} + +const interfaces = os.networkInterfaces(); +switch (platform) { + case 'linux': { + const filter = (e) => + e.address === '127.0.0.1' && + e.netmask === '255.0.0.0'; + + const actual = interfaces.lo.filter(filter); + const expected = [{ + address: '127.0.0.1', + netmask: '255.0.0.0', + family: 'IPv4', + mac: '00:00:00:00:00:00', + internal: true, + cidr: '127.0.0.1/8' + }]; + assert.deepStrictEqual(actual, expected); + break; + } + case 'win32': { + const filter = (e) => + e.address === '127.0.0.1'; + + const actual = interfaces['Loopback Pseudo-Interface 1'].filter(filter); + const expected = [{ + address: '127.0.0.1', + netmask: '255.0.0.0', + family: 'IPv4', + mac: '00:00:00:00:00:00', + internal: true, + cidr: '127.0.0.1/8' + }]; + assert.deepStrictEqual(actual, expected); + break; + } +} +const netmaskToCIDRSuffixMap = new Map(Object.entries({ + '255.0.0.0': 8, + '255.255.255.0': 24, + 'ffff:ffff:ffff:ffff::': 64, + 'ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff': 128 +})); + +Object.values(interfaces) + .flat(Infinity) + .map((v) => ({ v, mask: netmaskToCIDRSuffixMap.get(v.netmask) })) + .forEach(({ v, mask }) => { + assert.ok('cidr' in v, `"cidr" prop not found in ${inspect(v)}`); + if (mask) { + assert.strictEqual(v.cidr, `${v.address}/${mask}`); + } + }); + +const EOL = os.EOL; +if (common.isWindows) { + assert.strictEqual(EOL, '\r\n'); +} else { + assert.strictEqual(EOL, '\n'); +} + +const home = os.homedir(); +is.string(home); +assert.ok(home.includes(path.sep)); + +const version = os.version(); +assert.strictEqual(typeof version, 'string'); +assert(version); + +if (common.isWindows && process.env.USERPROFILE) { + assert.strictEqual(home, process.env.USERPROFILE); + delete process.env.USERPROFILE; + assert.ok(os.homedir().includes(path.sep)); + process.env.USERPROFILE = home; +} else if (!common.isWindows && process.env.HOME) { + assert.strictEqual(home, process.env.HOME); + delete process.env.HOME; + assert.ok(os.homedir().includes(path.sep)); + process.env.HOME = home; +} + +/* TODO(kt3k): Enable this test +const pwd = os.userInfo(); +is.object(pwd); +const pwdBuf = os.userInfo({ encoding: 'buffer' }); + +if (common.isWindows) { + assert.strictEqual(pwd.uid, -1); + assert.strictEqual(pwd.gid, -1); + assert.strictEqual(pwd.shell, null); + assert.strictEqual(pwdBuf.uid, -1); + assert.strictEqual(pwdBuf.gid, -1); + assert.strictEqual(pwdBuf.shell, null); +} else { + is.number(pwd.uid); + is.number(pwd.gid); + assert.strictEqual(typeof pwd.shell, 'string'); + // It's possible for /etc/passwd to leave the user's shell blank. + if (pwd.shell.length > 0) { + assert(pwd.shell.includes(path.sep)); + } + assert.strictEqual(pwd.uid, pwdBuf.uid); + assert.strictEqual(pwd.gid, pwdBuf.gid); + assert.strictEqual(pwd.shell, pwdBuf.shell.toString('utf8')); +} + +is.string(pwd.username); +assert.ok(pwd.homedir.includes(path.sep)); +assert.strictEqual(pwd.username, pwdBuf.username.toString('utf8')); +assert.strictEqual(pwd.homedir, pwdBuf.homedir.toString('utf8')); +*/ + +assert.strictEqual(`${os.hostname}`, os.hostname()); +assert.strictEqual(`${os.homedir}`, os.homedir()); +assert.strictEqual(`${os.release}`, os.release()); +assert.strictEqual(`${os.type}`, os.type()); +assert.strictEqual(`${os.endianness}`, os.endianness()); +// TODO(kt3k): Enable this test +// assert.strictEqual(`${os.tmpdir}`, os.tmpdir()); +assert.strictEqual(`${os.arch}`, os.arch()); +assert.strictEqual(`${os.platform}`, os.platform()); +assert.strictEqual(`${os.version}`, os.version()); + +assert.strictEqual(+os.totalmem, os.totalmem()); + +// Assert that the following values are coercible to numbers. +// On IBMi, os.uptime() returns 'undefined' +if (!common.isIBMi) { + is.number(+os.uptime, 'uptime'); + is.number(os.uptime(), 'uptime'); +} + +is.number(+os.freemem, 'freemem'); +is.number(os.freemem(), 'freemem'); + +const devNull = os.devNull; +if (common.isWindows) { + assert.strictEqual(devNull, '\\\\.\\nul'); +} else { + assert.strictEqual(devNull, '/dev/null'); +} diff --git a/cli/tests/node_compat/test/parallel/test-outgoing-message-destroy.js b/cli/tests/node_compat/test/parallel/test-outgoing-message-destroy.js new file mode 100644 index 00000000000000..1302fb03d9a22c --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-outgoing-message-destroy.js @@ -0,0 +1,20 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +'use strict'; + +// Test that http.OutgoingMessage,prototype.destroy() returns `this`. +require('../common'); + +const assert = require('assert'); +const http = require('http'); +const outgoingMessage = new http.OutgoingMessage(); + +assert.strictEqual(outgoingMessage.destroyed, false); +assert.strictEqual(outgoingMessage.destroy(), outgoingMessage); +assert.strictEqual(outgoingMessage.destroyed, true); +assert.strictEqual(outgoingMessage.destroy(), outgoingMessage); diff --git a/cli/tests/node_compat/test/parallel/test-outgoing-message-pipe.js b/cli/tests/node_compat/test/parallel/test-outgoing-message-pipe.js new file mode 100644 index 00000000000000..1b40080a859bd4 --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-outgoing-message-pipe.js @@ -0,0 +1,22 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +'use strict'; +require('../common'); +const assert = require('assert'); +const OutgoingMessage = require('_http_outgoing').OutgoingMessage; + +// Verify that an error is thrown upon a call to `OutgoingMessage.pipe`. + +const outgoingMessage = new OutgoingMessage(); +assert.throws( + () => { outgoingMessage.pipe(outgoingMessage); }, + { + code: 'ERR_STREAM_CANNOT_PIPE', + name: 'Error' + } +); diff --git a/cli/tests/node_compat/test/parallel/test-path-basename.js b/cli/tests/node_compat/test/parallel/test-path-basename.js new file mode 100644 index 00000000000000..dec890c8556d2e --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-path-basename.js @@ -0,0 +1,83 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +'use strict'; +require('../common'); +const assert = require('assert'); +const path = require('path'); + +assert.strictEqual(path.basename(__filename), 'test-path-basename.js'); +assert.strictEqual(path.basename(__filename, '.js'), 'test-path-basename'); +assert.strictEqual(path.basename('.js', '.js'), ''); +assert.strictEqual(path.basename('js', '.js'), 'js'); +assert.strictEqual(path.basename('file.js', '.ts'), 'file.js'); +assert.strictEqual(path.basename('file', '.js'), 'file'); +assert.strictEqual(path.basename('file.js.old', '.js.old'), 'file'); +assert.strictEqual(path.basename(''), ''); +assert.strictEqual(path.basename('/dir/basename.ext'), 'basename.ext'); +assert.strictEqual(path.basename('/basename.ext'), 'basename.ext'); +assert.strictEqual(path.basename('basename.ext'), 'basename.ext'); +assert.strictEqual(path.basename('basename.ext/'), 'basename.ext'); +assert.strictEqual(path.basename('basename.ext//'), 'basename.ext'); +assert.strictEqual(path.basename('aaa/bbb', '/bbb'), 'bbb'); +assert.strictEqual(path.basename('aaa/bbb', 'a/bbb'), 'bbb'); +assert.strictEqual(path.basename('aaa/bbb', 'bbb'), 'bbb'); +assert.strictEqual(path.basename('aaa/bbb//', 'bbb'), 'bbb'); +assert.strictEqual(path.basename('aaa/bbb', 'bb'), 'b'); +assert.strictEqual(path.basename('aaa/bbb', 'b'), 'bb'); +assert.strictEqual(path.basename('/aaa/bbb', '/bbb'), 'bbb'); +assert.strictEqual(path.basename('/aaa/bbb', 'a/bbb'), 'bbb'); +assert.strictEqual(path.basename('/aaa/bbb', 'bbb'), 'bbb'); +assert.strictEqual(path.basename('/aaa/bbb//', 'bbb'), 'bbb'); +assert.strictEqual(path.basename('/aaa/bbb', 'bb'), 'b'); +assert.strictEqual(path.basename('/aaa/bbb', 'b'), 'bb'); +assert.strictEqual(path.basename('/aaa/bbb'), 'bbb'); +assert.strictEqual(path.basename('/aaa/'), 'aaa'); +assert.strictEqual(path.basename('/aaa/b'), 'b'); +assert.strictEqual(path.basename('/a/b'), 'b'); +assert.strictEqual(path.basename('//a'), 'a'); +assert.strictEqual(path.basename('a', 'a'), ''); + +// On Windows a backslash acts as a path separator. +assert.strictEqual(path.win32.basename('\\dir\\basename.ext'), 'basename.ext'); +assert.strictEqual(path.win32.basename('\\basename.ext'), 'basename.ext'); +assert.strictEqual(path.win32.basename('basename.ext'), 'basename.ext'); +assert.strictEqual(path.win32.basename('basename.ext\\'), 'basename.ext'); +assert.strictEqual(path.win32.basename('basename.ext\\\\'), 'basename.ext'); +assert.strictEqual(path.win32.basename('foo'), 'foo'); +assert.strictEqual(path.win32.basename('aaa\\bbb', '\\bbb'), 'bbb'); +assert.strictEqual(path.win32.basename('aaa\\bbb', 'a\\bbb'), 'bbb'); +assert.strictEqual(path.win32.basename('aaa\\bbb', 'bbb'), 'bbb'); +assert.strictEqual(path.win32.basename('aaa\\bbb\\\\\\\\', 'bbb'), 'bbb'); +assert.strictEqual(path.win32.basename('aaa\\bbb', 'bb'), 'b'); +assert.strictEqual(path.win32.basename('aaa\\bbb', 'b'), 'bb'); +assert.strictEqual(path.win32.basename('C:'), ''); +assert.strictEqual(path.win32.basename('C:.'), '.'); +assert.strictEqual(path.win32.basename('C:\\'), ''); +assert.strictEqual(path.win32.basename('C:\\dir\\base.ext'), 'base.ext'); +assert.strictEqual(path.win32.basename('C:\\basename.ext'), 'basename.ext'); +assert.strictEqual(path.win32.basename('C:basename.ext'), 'basename.ext'); +assert.strictEqual(path.win32.basename('C:basename.ext\\'), 'basename.ext'); +assert.strictEqual(path.win32.basename('C:basename.ext\\\\'), 'basename.ext'); +assert.strictEqual(path.win32.basename('C:foo'), 'foo'); +assert.strictEqual(path.win32.basename('file:stream'), 'file:stream'); +assert.strictEqual(path.win32.basename('a', 'a'), ''); + +// On unix a backslash is just treated as any other character. +assert.strictEqual(path.posix.basename('\\dir\\basename.ext'), + '\\dir\\basename.ext'); +assert.strictEqual(path.posix.basename('\\basename.ext'), '\\basename.ext'); +assert.strictEqual(path.posix.basename('basename.ext'), 'basename.ext'); +assert.strictEqual(path.posix.basename('basename.ext\\'), 'basename.ext\\'); +assert.strictEqual(path.posix.basename('basename.ext\\\\'), 'basename.ext\\\\'); +assert.strictEqual(path.posix.basename('foo'), 'foo'); + +// POSIX filenames may include control characters +// c.f. http://www.dwheeler.com/essays/fixing-unix-linux-filenames.html +const controlCharFilename = `Icon${String.fromCharCode(13)}`; +assert.strictEqual(path.posix.basename(`/a/b/${controlCharFilename}`), + controlCharFilename); diff --git a/cli/tests/node_compat/test/parallel/test-path-dirname.js b/cli/tests/node_compat/test/parallel/test-path-dirname.js new file mode 100644 index 00000000000000..7043bfa0555627 --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-path-dirname.js @@ -0,0 +1,66 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +'use strict'; +const common = require('../common'); +const assert = require('assert'); +const path = require('path'); + +assert.strictEqual(path.dirname(__filename).substr(-13), + common.isWindows ? 'test\\parallel' : 'test/parallel'); + +assert.strictEqual(path.posix.dirname('/a/b/'), '/a'); +assert.strictEqual(path.posix.dirname('/a/b'), '/a'); +assert.strictEqual(path.posix.dirname('/a'), '/'); +assert.strictEqual(path.posix.dirname(''), '.'); +assert.strictEqual(path.posix.dirname('/'), '/'); +assert.strictEqual(path.posix.dirname('////'), '/'); +assert.strictEqual(path.posix.dirname('//a'), '//'); +assert.strictEqual(path.posix.dirname('foo'), '.'); + +assert.strictEqual(path.win32.dirname('c:\\'), 'c:\\'); +assert.strictEqual(path.win32.dirname('c:\\foo'), 'c:\\'); +assert.strictEqual(path.win32.dirname('c:\\foo\\'), 'c:\\'); +assert.strictEqual(path.win32.dirname('c:\\foo\\bar'), 'c:\\foo'); +assert.strictEqual(path.win32.dirname('c:\\foo\\bar\\'), 'c:\\foo'); +assert.strictEqual(path.win32.dirname('c:\\foo\\bar\\baz'), 'c:\\foo\\bar'); +assert.strictEqual(path.win32.dirname('c:\\foo bar\\baz'), 'c:\\foo bar'); +assert.strictEqual(path.win32.dirname('\\'), '\\'); +assert.strictEqual(path.win32.dirname('\\foo'), '\\'); +assert.strictEqual(path.win32.dirname('\\foo\\'), '\\'); +assert.strictEqual(path.win32.dirname('\\foo\\bar'), '\\foo'); +assert.strictEqual(path.win32.dirname('\\foo\\bar\\'), '\\foo'); +assert.strictEqual(path.win32.dirname('\\foo\\bar\\baz'), '\\foo\\bar'); +assert.strictEqual(path.win32.dirname('\\foo bar\\baz'), '\\foo bar'); +assert.strictEqual(path.win32.dirname('c:'), 'c:'); +assert.strictEqual(path.win32.dirname('c:foo'), 'c:'); +assert.strictEqual(path.win32.dirname('c:foo\\'), 'c:'); +assert.strictEqual(path.win32.dirname('c:foo\\bar'), 'c:foo'); +assert.strictEqual(path.win32.dirname('c:foo\\bar\\'), 'c:foo'); +assert.strictEqual(path.win32.dirname('c:foo\\bar\\baz'), 'c:foo\\bar'); +assert.strictEqual(path.win32.dirname('c:foo bar\\baz'), 'c:foo bar'); +assert.strictEqual(path.win32.dirname('file:stream'), '.'); +assert.strictEqual(path.win32.dirname('dir\\file:stream'), 'dir'); +assert.strictEqual(path.win32.dirname('\\\\unc\\share'), + '\\\\unc\\share'); +assert.strictEqual(path.win32.dirname('\\\\unc\\share\\foo'), + '\\\\unc\\share\\'); +assert.strictEqual(path.win32.dirname('\\\\unc\\share\\foo\\'), + '\\\\unc\\share\\'); +assert.strictEqual(path.win32.dirname('\\\\unc\\share\\foo\\bar'), + '\\\\unc\\share\\foo'); +assert.strictEqual(path.win32.dirname('\\\\unc\\share\\foo\\bar\\'), + '\\\\unc\\share\\foo'); +assert.strictEqual(path.win32.dirname('\\\\unc\\share\\foo\\bar\\baz'), + '\\\\unc\\share\\foo\\bar'); +assert.strictEqual(path.win32.dirname('/a/b/'), '/a'); +assert.strictEqual(path.win32.dirname('/a/b'), '/a'); +assert.strictEqual(path.win32.dirname('/a'), '/'); +assert.strictEqual(path.win32.dirname(''), '.'); +assert.strictEqual(path.win32.dirname('/'), '/'); +assert.strictEqual(path.win32.dirname('////'), '/'); +assert.strictEqual(path.win32.dirname('foo'), '.'); diff --git a/cli/tests/node_compat/test/parallel/test-path-extname.js b/cli/tests/node_compat/test/parallel/test-path-extname.js new file mode 100644 index 00000000000000..d0e00adccfc8ff --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-path-extname.js @@ -0,0 +1,106 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +'use strict'; +require('../common'); +const assert = require('assert'); +const path = require('path'); + +const failures = []; +const slashRE = /\//g; + +[ + [__filename, '.js'], + ['', ''], + ['/path/to/file', ''], + ['/path/to/file.ext', '.ext'], + ['/path.to/file.ext', '.ext'], + ['/path.to/file', ''], + ['/path.to/.file', ''], + ['/path.to/.file.ext', '.ext'], + ['/path/to/f.ext', '.ext'], + ['/path/to/..ext', '.ext'], + ['/path/to/..', ''], + ['file', ''], + ['file.ext', '.ext'], + ['.file', ''], + ['.file.ext', '.ext'], + ['/file', ''], + ['/file.ext', '.ext'], + ['/.file', ''], + ['/.file.ext', '.ext'], + ['.path/file.ext', '.ext'], + ['file.ext.ext', '.ext'], + ['file.', '.'], + ['.', ''], + ['./', ''], + ['.file.ext', '.ext'], + ['.file', ''], + ['.file.', '.'], + ['.file..', '.'], + ['..', ''], + ['../', ''], + ['..file.ext', '.ext'], + ['..file', '.file'], + ['..file.', '.'], + ['..file..', '.'], + ['...', '.'], + ['...ext', '.ext'], + ['....', '.'], + ['file.ext/', '.ext'], + ['file.ext//', '.ext'], + ['file/', ''], + ['file//', ''], + ['file./', '.'], + ['file.//', '.'], +].forEach((test) => { + const expected = test[1]; + [path.posix.extname, path.win32.extname].forEach((extname) => { + let input = test[0]; + let os; + if (extname === path.win32.extname) { + input = input.replace(slashRE, '\\'); + os = 'win32'; + } else { + os = 'posix'; + } + const actual = extname(input); + const message = `path.${os}.extname(${JSON.stringify(input)})\n expect=${ + JSON.stringify(expected)}\n actual=${JSON.stringify(actual)}`; + if (actual !== expected) + failures.push(`\n${message}`); + }); + { + const input = `C:${test[0].replace(slashRE, '\\')}`; + const actual = path.win32.extname(input); + const message = `path.win32.extname(${JSON.stringify(input)})\n expect=${ + JSON.stringify(expected)}\n actual=${JSON.stringify(actual)}`; + if (actual !== expected) + failures.push(`\n${message}`); + } +}); +assert.strictEqual(failures.length, 0, failures.join('')); + +// On Windows, backslash is a path separator. +assert.strictEqual(path.win32.extname('.\\'), ''); +assert.strictEqual(path.win32.extname('..\\'), ''); +assert.strictEqual(path.win32.extname('file.ext\\'), '.ext'); +assert.strictEqual(path.win32.extname('file.ext\\\\'), '.ext'); +assert.strictEqual(path.win32.extname('file\\'), ''); +assert.strictEqual(path.win32.extname('file\\\\'), ''); +assert.strictEqual(path.win32.extname('file.\\'), '.'); +assert.strictEqual(path.win32.extname('file.\\\\'), '.'); + +// On *nix, backslash is a valid name component like any other character. +assert.strictEqual(path.posix.extname('.\\'), ''); +assert.strictEqual(path.posix.extname('..\\'), '.\\'); +assert.strictEqual(path.posix.extname('file.ext\\'), '.ext\\'); +assert.strictEqual(path.posix.extname('file.ext\\\\'), '.ext\\\\'); +assert.strictEqual(path.posix.extname('file\\'), ''); +assert.strictEqual(path.posix.extname('file\\\\'), ''); +assert.strictEqual(path.posix.extname('file.\\'), '.\\'); +assert.strictEqual(path.posix.extname('file.\\\\'), '.\\\\'); diff --git a/cli/tests/node_compat/test/parallel/test-path-isabsolute.js b/cli/tests/node_compat/test/parallel/test-path-isabsolute.js new file mode 100644 index 00000000000000..a55dca7a832075 --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-path-isabsolute.js @@ -0,0 +1,35 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +'use strict'; +require('../common'); +const assert = require('assert'); +const path = require('path'); + +assert.strictEqual(path.win32.isAbsolute('/'), true); +assert.strictEqual(path.win32.isAbsolute('//'), true); +assert.strictEqual(path.win32.isAbsolute('//server'), true); +assert.strictEqual(path.win32.isAbsolute('//server/file'), true); +assert.strictEqual(path.win32.isAbsolute('\\\\server\\file'), true); +assert.strictEqual(path.win32.isAbsolute('\\\\server'), true); +assert.strictEqual(path.win32.isAbsolute('\\\\'), true); +assert.strictEqual(path.win32.isAbsolute('c'), false); +assert.strictEqual(path.win32.isAbsolute('c:'), false); +assert.strictEqual(path.win32.isAbsolute('c:\\'), true); +assert.strictEqual(path.win32.isAbsolute('c:/'), true); +assert.strictEqual(path.win32.isAbsolute('c://'), true); +assert.strictEqual(path.win32.isAbsolute('C:/Users/'), true); +assert.strictEqual(path.win32.isAbsolute('C:\\Users\\'), true); +assert.strictEqual(path.win32.isAbsolute('C:cwd/another'), false); +assert.strictEqual(path.win32.isAbsolute('C:cwd\\another'), false); +assert.strictEqual(path.win32.isAbsolute('directory/directory'), false); +assert.strictEqual(path.win32.isAbsolute('directory\\directory'), false); + +assert.strictEqual(path.posix.isAbsolute('/home/foo'), true); +assert.strictEqual(path.posix.isAbsolute('/home/foo/..'), true); +assert.strictEqual(path.posix.isAbsolute('bar/'), false); +assert.strictEqual(path.posix.isAbsolute('./baz'), false); diff --git a/cli/tests/node_compat/test/parallel/test-path-join.js b/cli/tests/node_compat/test/parallel/test-path-join.js new file mode 100644 index 00000000000000..482686538eec82 --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-path-join.js @@ -0,0 +1,150 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +'use strict'; +require('../common'); +const assert = require('assert'); +const path = require('path'); + +const failures = []; +const backslashRE = /\\/g; + +const joinTests = [ + [ [path.posix.join, path.win32.join], + // Arguments result + [[['.', 'x/b', '..', '/b/c.js'], 'x/b/c.js'], + [[], '.'], + [['/.', 'x/b', '..', '/b/c.js'], '/x/b/c.js'], + [['/foo', '../../../bar'], '/bar'], + [['foo', '../../../bar'], '../../bar'], + [['foo/', '../../../bar'], '../../bar'], + [['foo/x', '../../../bar'], '../bar'], + [['foo/x', './bar'], 'foo/x/bar'], + [['foo/x/', './bar'], 'foo/x/bar'], + [['foo/x/', '.', 'bar'], 'foo/x/bar'], + [['./'], './'], + [['.', './'], './'], + [['.', '.', '.'], '.'], + [['.', './', '.'], '.'], + [['.', '/./', '.'], '.'], + [['.', '/////./', '.'], '.'], + [['.'], '.'], + [['', '.'], '.'], + [['', 'foo'], 'foo'], + [['foo', '/bar'], 'foo/bar'], + [['', '/foo'], '/foo'], + [['', '', '/foo'], '/foo'], + [['', '', 'foo'], 'foo'], + [['foo', ''], 'foo'], + [['foo/', ''], 'foo/'], + [['foo', '', '/bar'], 'foo/bar'], + [['./', '..', '/foo'], '../foo'], + [['./', '..', '..', '/foo'], '../../foo'], + [['.', '..', '..', '/foo'], '../../foo'], + [['', '..', '..', '/foo'], '../../foo'], + [['/'], '/'], + [['/', '.'], '/'], + [['/', '..'], '/'], + [['/', '..', '..'], '/'], + [[''], '.'], + [['', ''], '.'], + [[' /foo'], ' /foo'], + [[' ', 'foo'], ' /foo'], + [[' ', '.'], ' '], + [[' ', '/'], ' /'], + [[' ', ''], ' '], + [['/', 'foo'], '/foo'], + [['/', '/foo'], '/foo'], + [['/', '//foo'], '/foo'], + [['/', '', '/foo'], '/foo'], + [['', '/', 'foo'], '/foo'], + [['', '/', '/foo'], '/foo'], + ], + ], +]; + +// Windows-specific join tests +joinTests.push([ + path.win32.join, + joinTests[0][1].slice(0).concat( + [// Arguments result + // UNC path expected + [['//foo/bar'], '\\\\foo\\bar\\'], + [['\\/foo/bar'], '\\\\foo\\bar\\'], + [['\\\\foo/bar'], '\\\\foo\\bar\\'], + // UNC path expected - server and share separate + [['//foo', 'bar'], '\\\\foo\\bar\\'], + [['//foo/', 'bar'], '\\\\foo\\bar\\'], + [['//foo', '/bar'], '\\\\foo\\bar\\'], + // UNC path expected - questionable + [['//foo', '', 'bar'], '\\\\foo\\bar\\'], + [['//foo/', '', 'bar'], '\\\\foo\\bar\\'], + [['//foo/', '', '/bar'], '\\\\foo\\bar\\'], + // UNC path expected - even more questionable + [['', '//foo', 'bar'], '\\\\foo\\bar\\'], + [['', '//foo/', 'bar'], '\\\\foo\\bar\\'], + [['', '//foo/', '/bar'], '\\\\foo\\bar\\'], + // No UNC path expected (no double slash in first component) + [['\\', 'foo/bar'], '\\foo\\bar'], + [['\\', '/foo/bar'], '\\foo\\bar'], + [['', '/', '/foo/bar'], '\\foo\\bar'], + // No UNC path expected (no non-slashes in first component - + // questionable) + [['//', 'foo/bar'], '\\foo\\bar'], + [['//', '/foo/bar'], '\\foo\\bar'], + [['\\\\', '/', '/foo/bar'], '\\foo\\bar'], + [['//'], '\\'], + // No UNC path expected (share name missing - questionable). + [['//foo'], '\\foo'], + [['//foo/'], '\\foo\\'], + [['//foo', '/'], '\\foo\\'], + [['//foo', '', '/'], '\\foo\\'], + // No UNC path expected (too many leading slashes - questionable) + [['///foo/bar'], '\\foo\\bar'], + [['////foo', 'bar'], '\\foo\\bar'], + [['\\\\\\/foo/bar'], '\\foo\\bar'], + // Drive-relative vs drive-absolute paths. This merely describes the + // status quo, rather than being obviously right + [['c:'], 'c:.'], + [['c:.'], 'c:.'], + [['c:', ''], 'c:.'], + [['', 'c:'], 'c:.'], + [['c:.', '/'], 'c:.\\'], + [['c:.', 'file'], 'c:file'], + [['c:', '/'], 'c:\\'], + [['c:', 'file'], 'c:\\file'], + ] + ), +]); +joinTests.forEach((test) => { + if (!Array.isArray(test[0])) + test[0] = [test[0]]; + test[0].forEach((join) => { + test[1].forEach((test) => { + const actual = join.apply(null, test[0]); + const expected = test[1]; + // For non-Windows specific tests with the Windows join(), we need to try + // replacing the slashes since the non-Windows specific tests' `expected` + // use forward slashes + let actualAlt; + let os; + if (join === path.win32.join) { + actualAlt = actual.replace(backslashRE, '/'); + os = 'win32'; + } else { + os = 'posix'; + } + if (actual !== expected && actualAlt !== expected) { + const delimiter = test[0].map(JSON.stringify).join(','); + const message = `path.${os}.join(${delimiter})\n expect=${ + JSON.stringify(expected)}\n actual=${JSON.stringify(actual)}`; + failures.push(`\n${message}`); + } + }); + }); +}); +assert.strictEqual(failures.length, 0, failures.join('')); diff --git a/cli/tests/node_compat/test/parallel/test-path-makelong.js b/cli/tests/node_compat/test/parallel/test-path-makelong.js new file mode 100644 index 00000000000000..be46b2cb70ba1e --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-path-makelong.js @@ -0,0 +1,94 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. + +'use strict'; +const common = require('../common'); +const fixtures = require('../common/fixtures'); +const assert = require('assert'); +const path = require('path'); + +if (common.isWindows) { + const file = fixtures.path('a.js'); + const resolvedFile = path.resolve(file); + + assert.strictEqual(path.toNamespacedPath(file), + `\\\\?\\${resolvedFile}`); + assert.strictEqual(path.toNamespacedPath(`\\\\?\\${file}`), + `\\\\?\\${resolvedFile}`); + assert.strictEqual(path.toNamespacedPath( + '\\\\someserver\\someshare\\somefile'), + '\\\\?\\UNC\\someserver\\someshare\\somefile'); + assert.strictEqual(path.toNamespacedPath( + '\\\\?\\UNC\\someserver\\someshare\\somefile'), + '\\\\?\\UNC\\someserver\\someshare\\somefile'); + assert.strictEqual(path.toNamespacedPath('\\\\.\\pipe\\somepipe'), + '\\\\.\\pipe\\somepipe'); +} + +assert.strictEqual(path.toNamespacedPath(''), ''); +assert.strictEqual(path.toNamespacedPath(null), null); +assert.strictEqual(path.toNamespacedPath(100), 100); +assert.strictEqual(path.toNamespacedPath(path), path); +assert.strictEqual(path.toNamespacedPath(false), false); +assert.strictEqual(path.toNamespacedPath(true), true); + +const emptyObj = {}; +assert.strictEqual(path.posix.toNamespacedPath('/foo/bar'), '/foo/bar'); +assert.strictEqual(path.posix.toNamespacedPath('foo/bar'), 'foo/bar'); +assert.strictEqual(path.posix.toNamespacedPath(null), null); +assert.strictEqual(path.posix.toNamespacedPath(true), true); +assert.strictEqual(path.posix.toNamespacedPath(1), 1); +assert.strictEqual(path.posix.toNamespacedPath(), undefined); +assert.strictEqual(path.posix.toNamespacedPath(emptyObj), emptyObj); +if (common.isWindows) { + // These tests cause resolve() to insert the cwd, so we cannot test them from + // non-Windows platforms (easily) + assert.strictEqual(path.toNamespacedPath(''), ''); + assert.strictEqual(path.win32.toNamespacedPath('foo\\bar').toLowerCase(), + `\\\\?\\${process.cwd().toLowerCase()}\\foo\\bar`); + assert.strictEqual(path.win32.toNamespacedPath('foo/bar').toLowerCase(), + `\\\\?\\${process.cwd().toLowerCase()}\\foo\\bar`); + const currentDeviceLetter = path.parse(process.cwd()).root.substring(0, 2); + assert.strictEqual( + path.win32.toNamespacedPath(currentDeviceLetter).toLowerCase(), + `\\\\?\\${process.cwd().toLowerCase()}`); + assert.strictEqual(path.win32.toNamespacedPath('C').toLowerCase(), + `\\\\?\\${process.cwd().toLowerCase()}\\c`); +} +assert.strictEqual(path.win32.toNamespacedPath('C:\\foo'), '\\\\?\\C:\\foo'); +assert.strictEqual(path.win32.toNamespacedPath('C:/foo'), '\\\\?\\C:\\foo'); +assert.strictEqual(path.win32.toNamespacedPath('\\\\foo\\bar'), + '\\\\?\\UNC\\foo\\bar\\'); +assert.strictEqual(path.win32.toNamespacedPath('//foo//bar'), + '\\\\?\\UNC\\foo\\bar\\'); +assert.strictEqual(path.win32.toNamespacedPath('\\\\?\\foo'), '\\\\?\\foo'); +assert.strictEqual(path.win32.toNamespacedPath(null), null); +assert.strictEqual(path.win32.toNamespacedPath(true), true); +assert.strictEqual(path.win32.toNamespacedPath(1), 1); +assert.strictEqual(path.win32.toNamespacedPath(), undefined); +assert.strictEqual(path.win32.toNamespacedPath(emptyObj), emptyObj); diff --git a/cli/tests/node_compat/test/parallel/test-path-normalize.js b/cli/tests/node_compat/test/parallel/test-path-normalize.js new file mode 100644 index 00000000000000..ec5e4f194542ae --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-path-normalize.js @@ -0,0 +1,79 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +'use strict'; +require('../common'); +const assert = require('assert'); +const path = require('path'); + +assert.strictEqual(path.win32.normalize('./fixtures///b/../b/c.js'), + 'fixtures\\b\\c.js'); +assert.strictEqual(path.win32.normalize('/foo/../../../bar'), '\\bar'); +assert.strictEqual(path.win32.normalize('a//b//../b'), 'a\\b'); +assert.strictEqual(path.win32.normalize('a//b//./c'), 'a\\b\\c'); +assert.strictEqual(path.win32.normalize('a//b//.'), 'a\\b'); +assert.strictEqual(path.win32.normalize('//server/share/dir/file.ext'), + '\\\\server\\share\\dir\\file.ext'); +assert.strictEqual(path.win32.normalize('/a/b/c/../../../x/y/z'), '\\x\\y\\z'); +assert.strictEqual(path.win32.normalize('C:'), 'C:.'); +assert.strictEqual(path.win32.normalize('C:..\\abc'), 'C:..\\abc'); +assert.strictEqual(path.win32.normalize('C:..\\..\\abc\\..\\def'), + 'C:..\\..\\def'); +assert.strictEqual(path.win32.normalize('C:\\.'), 'C:\\'); +assert.strictEqual(path.win32.normalize('file:stream'), 'file:stream'); +assert.strictEqual(path.win32.normalize('bar\\foo..\\..\\'), 'bar\\'); +assert.strictEqual(path.win32.normalize('bar\\foo..\\..'), 'bar'); +assert.strictEqual(path.win32.normalize('bar\\foo..\\..\\baz'), 'bar\\baz'); +assert.strictEqual(path.win32.normalize('bar\\foo..\\'), 'bar\\foo..\\'); +assert.strictEqual(path.win32.normalize('bar\\foo..'), 'bar\\foo..'); +assert.strictEqual(path.win32.normalize('..\\foo..\\..\\..\\bar'), + '..\\..\\bar'); +assert.strictEqual(path.win32.normalize('..\\...\\..\\.\\...\\..\\..\\bar'), + '..\\..\\bar'); +assert.strictEqual(path.win32.normalize('../../../foo/../../../bar'), + '..\\..\\..\\..\\..\\bar'); +assert.strictEqual(path.win32.normalize('../../../foo/../../../bar/../../'), + '..\\..\\..\\..\\..\\..\\'); +assert.strictEqual( + path.win32.normalize('../foobar/barfoo/foo/../../../bar/../../'), + '..\\..\\' +); +assert.strictEqual( + path.win32.normalize('../.../../foobar/../../../bar/../../baz'), + '..\\..\\..\\..\\baz' +); +assert.strictEqual(path.win32.normalize('foo/bar\\baz'), 'foo\\bar\\baz'); + +assert.strictEqual(path.posix.normalize('./fixtures///b/../b/c.js'), + 'fixtures/b/c.js'); +assert.strictEqual(path.posix.normalize('/foo/../../../bar'), '/bar'); +assert.strictEqual(path.posix.normalize('a//b//../b'), 'a/b'); +assert.strictEqual(path.posix.normalize('a//b//./c'), 'a/b/c'); +assert.strictEqual(path.posix.normalize('a//b//.'), 'a/b'); +assert.strictEqual(path.posix.normalize('/a/b/c/../../../x/y/z'), '/x/y/z'); +assert.strictEqual(path.posix.normalize('///..//./foo/.//bar'), '/foo/bar'); +assert.strictEqual(path.posix.normalize('bar/foo../../'), 'bar/'); +assert.strictEqual(path.posix.normalize('bar/foo../..'), 'bar'); +assert.strictEqual(path.posix.normalize('bar/foo../../baz'), 'bar/baz'); +assert.strictEqual(path.posix.normalize('bar/foo../'), 'bar/foo../'); +assert.strictEqual(path.posix.normalize('bar/foo..'), 'bar/foo..'); +assert.strictEqual(path.posix.normalize('../foo../../../bar'), '../../bar'); +assert.strictEqual(path.posix.normalize('../.../.././.../../../bar'), + '../../bar'); +assert.strictEqual(path.posix.normalize('../../../foo/../../../bar'), + '../../../../../bar'); +assert.strictEqual(path.posix.normalize('../../../foo/../../../bar/../../'), + '../../../../../../'); +assert.strictEqual( + path.posix.normalize('../foobar/barfoo/foo/../../../bar/../../'), + '../../' +); +assert.strictEqual( + path.posix.normalize('../.../../foobar/../../../bar/../../baz'), + '../../../../baz' +); +assert.strictEqual(path.posix.normalize('foo/bar\\baz'), 'foo/bar\\baz'); diff --git a/cli/tests/node_compat/test/parallel/test-path-parse-format.js b/cli/tests/node_compat/test/parallel/test-path-parse-format.js new file mode 100644 index 00000000000000..a6fa63d7573e1a --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-path-parse-format.js @@ -0,0 +1,233 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. + +'use strict'; +const common = require('../common'); +const assert = require('assert'); +const path = require('path'); + +const winPaths = [ + // [path, root] + ['C:\\path\\dir\\index.html', 'C:\\'], + ['C:\\another_path\\DIR\\1\\2\\33\\\\index', 'C:\\'], + ['another_path\\DIR with spaces\\1\\2\\33\\index', ''], + ['\\', '\\'], + ['\\foo\\C:', '\\'], + ['file', ''], + ['file:stream', ''], + ['.\\file', ''], + ['C:', 'C:'], + ['C:.', 'C:'], + ['C:..', 'C:'], + ['C:abc', 'C:'], + ['C:\\', 'C:\\'], + ['C:\\abc', 'C:\\' ], + ['', ''], + + // unc + ['\\\\server\\share\\file_path', '\\\\server\\share\\'], + ['\\\\server two\\shared folder\\file path.zip', + '\\\\server two\\shared folder\\'], + ['\\\\teela\\admin$\\system32', '\\\\teela\\admin$\\'], + ['\\\\?\\UNC\\server\\share', '\\\\?\\UNC\\'], +]; + +const winSpecialCaseParseTests = [ + ['t', { base: 't', name: 't', root: '', dir: '', ext: '' }], + ['/foo/bar', { root: '/', dir: '/foo', base: 'bar', ext: '', name: 'bar' }], +]; + +const winSpecialCaseFormatTests = [ + [{ dir: 'some\\dir' }, 'some\\dir\\'], + [{ base: 'index.html' }, 'index.html'], + [{ root: 'C:\\' }, 'C:\\'], + [{ name: 'index', ext: '.html' }, 'index.html'], + [{ dir: 'some\\dir', name: 'index', ext: '.html' }, 'some\\dir\\index.html'], + [{ root: 'C:\\', name: 'index', ext: '.html' }, 'C:\\index.html'], + [{}, ''], +]; + +const unixPaths = [ + // [path, root] + ['/home/user/dir/file.txt', '/'], + ['/home/user/a dir/another File.zip', '/'], + ['/home/user/a dir//another&File.', '/'], + ['/home/user/a$$$dir//another File.zip', '/'], + ['user/dir/another File.zip', ''], + ['file', ''], + ['.\\file', ''], + ['./file', ''], + ['C:\\foo', ''], + ['/', '/'], + ['', ''], + ['.', ''], + ['..', ''], + ['/foo', '/'], + ['/foo.', '/'], + ['/foo.bar', '/'], + ['/.', '/'], + ['/.foo', '/'], + ['/.foo.bar', '/'], + ['/foo/bar.baz', '/'], +]; + +const unixSpecialCaseFormatTests = [ + [{ dir: 'some/dir' }, 'some/dir/'], + [{ base: 'index.html' }, 'index.html'], + [{ root: '/' }, '/'], + [{ name: 'index', ext: '.html' }, 'index.html'], + [{ dir: 'some/dir', name: 'index', ext: '.html' }, 'some/dir/index.html'], + [{ root: '/', name: 'index', ext: '.html' }, '/index.html'], + [{}, ''], +]; + +const errors = [ + { method: 'parse', input: [null] }, + { method: 'parse', input: [{}] }, + { method: 'parse', input: [true] }, + { method: 'parse', input: [1] }, + { method: 'parse', input: [] }, + { method: 'format', input: [null] }, + { method: 'format', input: [''] }, + { method: 'format', input: [true] }, + { method: 'format', input: [1] }, +]; + +checkParseFormat(path.win32, winPaths); +checkParseFormat(path.posix, unixPaths); +checkSpecialCaseParseFormat(path.win32, winSpecialCaseParseTests); +checkErrors(path.win32); +checkErrors(path.posix); +checkFormat(path.win32, winSpecialCaseFormatTests); +checkFormat(path.posix, unixSpecialCaseFormatTests); + +// Test removal of trailing path separators +const trailingTests = [ + [ path.win32.parse, + [['.\\', { root: '', dir: '', base: '.', ext: '', name: '.' }], + ['\\\\', { root: '\\', dir: '\\', base: '', ext: '', name: '' }], + ['\\\\', { root: '\\', dir: '\\', base: '', ext: '', name: '' }], + ['c:\\foo\\\\\\', + { root: 'c:\\', dir: 'c:\\', base: 'foo', ext: '', name: 'foo' }], + ['D:\\foo\\\\\\bar.baz', + { root: 'D:\\', + dir: 'D:\\foo\\\\', + base: 'bar.baz', + ext: '.baz', + name: 'bar' }, + ], + ], + ], + [ path.posix.parse, + [['./', { root: '', dir: '', base: '.', ext: '', name: '.' }], + ['//', { root: '/', dir: '/', base: '', ext: '', name: '' }], + ['///', { root: '/', dir: '/', base: '', ext: '', name: '' }], + ['/foo///', { root: '/', dir: '/', base: 'foo', ext: '', name: 'foo' }], + ['/foo///bar.baz', + { root: '/', dir: '/foo//', base: 'bar.baz', ext: '.baz', name: 'bar' }, + ], + ], + ], +]; +const failures = []; +trailingTests.forEach((test) => { + const parse = test[0]; + const os = parse === path.win32.parse ? 'win32' : 'posix'; + test[1].forEach((test) => { + const actual = parse(test[0]); + const expected = test[1]; + const message = `path.${os}.parse(${JSON.stringify(test[0])})\n expect=${ + JSON.stringify(expected)}\n actual=${JSON.stringify(actual)}`; + const actualKeys = Object.keys(actual); + const expectedKeys = Object.keys(expected); + let failed = (actualKeys.length !== expectedKeys.length); + if (!failed) { + for (let i = 0; i < actualKeys.length; ++i) { + const key = actualKeys[i]; + if (!expectedKeys.includes(key) || actual[key] !== expected[key]) { + failed = true; + break; + } + } + } + if (failed) + failures.push(`\n${message}`); + }); +}); +assert.strictEqual(failures.length, 0, failures.join('')); + +function checkErrors(path) { + errors.forEach(({ method, input }) => { + assert.throws(() => { + path[method].apply(path, input); + }, { + code: 'ERR_INVALID_ARG_TYPE', + name: 'TypeError' + }); + }); +} + +function checkParseFormat(path, paths) { + paths.forEach(([element, root]) => { + const output = path.parse(element); + assert.strictEqual(typeof output.root, 'string'); + assert.strictEqual(typeof output.dir, 'string'); + assert.strictEqual(typeof output.base, 'string'); + assert.strictEqual(typeof output.ext, 'string'); + assert.strictEqual(typeof output.name, 'string'); + assert.strictEqual(path.format(output), element); + assert.strictEqual(output.root, root); + assert(output.dir.startsWith(output.root)); + assert.strictEqual(output.dir, output.dir ? path.dirname(element) : ''); + assert.strictEqual(output.base, path.basename(element)); + assert.strictEqual(output.ext, path.extname(element)); + }); +} + +function checkSpecialCaseParseFormat(path, testCases) { + testCases.forEach(([element, expect]) => { + assert.deepStrictEqual(path.parse(element), expect); + }); +} + +function checkFormat(path, testCases) { + testCases.forEach(([element, expect]) => { + assert.strictEqual(path.format(element), expect); + }); + + [null, undefined, 1, true, false, 'string'].forEach((pathObject) => { + assert.throws(() => { + path.format(pathObject); + }, { + code: 'ERR_INVALID_ARG_TYPE', + name: 'TypeError', + message: 'The "pathObject" argument must be of type object.' + + common.invalidArgTypeHelper(pathObject) + }); + }); +} diff --git a/cli/tests/node_compat/test/parallel/test-path-posix-exists.js b/cli/tests/node_compat/test/parallel/test-path-posix-exists.js new file mode 100644 index 00000000000000..dacac0443abcec --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-path-posix-exists.js @@ -0,0 +1,13 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +'use strict'; + +require('../common'); +const assert = require('assert'); + +assert.strictEqual(require('path/posix'), require('path').posix); diff --git a/cli/tests/node_compat/test/parallel/test-path-relative.js b/cli/tests/node_compat/test/parallel/test-path-relative.js new file mode 100644 index 00000000000000..ae37c6b9230130 --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-path-relative.js @@ -0,0 +1,76 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +'use strict'; +require('../common'); +const assert = require('assert'); +const path = require('path'); + +const failures = []; + +const relativeTests = [ + [ path.win32.relative, + // Arguments result + [['c:/blah\\blah', 'd:/games', 'd:\\games'], + ['c:/aaaa/bbbb', 'c:/aaaa', '..'], + ['c:/aaaa/bbbb', 'c:/cccc', '..\\..\\cccc'], + ['c:/aaaa/bbbb', 'c:/aaaa/bbbb', ''], + ['c:/aaaa/bbbb', 'c:/aaaa/cccc', '..\\cccc'], + ['c:/aaaa/', 'c:/aaaa/cccc', 'cccc'], + ['c:/', 'c:\\aaaa\\bbbb', 'aaaa\\bbbb'], + ['c:/aaaa/bbbb', 'd:\\', 'd:\\'], + ['c:/AaAa/bbbb', 'c:/aaaa/bbbb', ''], + ['c:/aaaaa/', 'c:/aaaa/cccc', '..\\aaaa\\cccc'], + ['C:\\foo\\bar\\baz\\quux', 'C:\\', '..\\..\\..\\..'], + ['C:\\foo\\test', 'C:\\foo\\test\\bar\\package.json', 'bar\\package.json'], + ['C:\\foo\\bar\\baz-quux', 'C:\\foo\\bar\\baz', '..\\baz'], + ['C:\\foo\\bar\\baz', 'C:\\foo\\bar\\baz-quux', '..\\baz-quux'], + ['\\\\foo\\bar', '\\\\foo\\bar\\baz', 'baz'], + ['\\\\foo\\bar\\baz', '\\\\foo\\bar', '..'], + ['\\\\foo\\bar\\baz-quux', '\\\\foo\\bar\\baz', '..\\baz'], + ['\\\\foo\\bar\\baz', '\\\\foo\\bar\\baz-quux', '..\\baz-quux'], + ['C:\\baz-quux', 'C:\\baz', '..\\baz'], + ['C:\\baz', 'C:\\baz-quux', '..\\baz-quux'], + ['\\\\foo\\baz-quux', '\\\\foo\\baz', '..\\baz'], + ['\\\\foo\\baz', '\\\\foo\\baz-quux', '..\\baz-quux'], + ['C:\\baz', '\\\\foo\\bar\\baz', '\\\\foo\\bar\\baz'], + ['\\\\foo\\bar\\baz', 'C:\\baz', 'C:\\baz'], + ], + ], + [ path.posix.relative, + // Arguments result + [['/var/lib', '/var', '..'], + ['/var/lib', '/bin', '../../bin'], + ['/var/lib', '/var/lib', ''], + ['/var/lib', '/var/apache', '../apache'], + ['/var/', '/var/lib', 'lib'], + ['/', '/var/lib', 'var/lib'], + ['/foo/test', '/foo/test/bar/package.json', 'bar/package.json'], + ['/Users/a/web/b/test/mails', '/Users/a/web/b', '../..'], + ['/foo/bar/baz-quux', '/foo/bar/baz', '../baz'], + ['/foo/bar/baz', '/foo/bar/baz-quux', '../baz-quux'], + ['/baz-quux', '/baz', '../baz'], + ['/baz', '/baz-quux', '../baz-quux'], + ['/page1/page2/foo', '/', '../../..'], + ], + ], +]; +relativeTests.forEach((test) => { + const relative = test[0]; + test[1].forEach((test) => { + const actual = relative(test[0], test[1]); + const expected = test[2]; + if (actual !== expected) { + const os = relative === path.win32.relative ? 'win32' : 'posix'; + const message = `path.${os}.relative(${ + test.slice(0, 2).map(JSON.stringify).join(',')})\n expect=${ + JSON.stringify(expected)}\n actual=${JSON.stringify(actual)}`; + failures.push(`\n${message}`); + } + }); +}); +assert.strictEqual(failures.length, 0, failures.join('')); diff --git a/cli/tests/node_compat/test/parallel/test-path-resolve.js b/cli/tests/node_compat/test/parallel/test-path-resolve.js new file mode 100644 index 00000000000000..be010ed83ef007 --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-path-resolve.js @@ -0,0 +1,96 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 16.13.0 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +'use strict'; +const common = require('../common'); +const fixtures = require('../common/fixtures'); +const assert = require('assert'); +const child = require('child_process'); +const path = require('path'); + +const failures = []; +const slashRE = /\//g; +const backslashRE = /\\/g; + +const posixyCwd = common.isWindows ? + (() => { + const _ = process.cwd() + .replaceAll(path.sep, path.posix.sep); + return _.slice(_.indexOf(path.posix.sep)); + })() : + process.cwd(); + +const resolveTests = [ + [ path.win32.resolve, + // Arguments result + [[['c:/blah\\blah', 'd:/games', 'c:../a'], 'c:\\blah\\a'], + [['c:/ignore', 'd:\\a/b\\c/d', '\\e.exe'], 'd:\\e.exe'], + [['c:/ignore', 'c:/some/file'], 'c:\\some\\file'], + [['d:/ignore', 'd:some/dir//'], 'd:\\ignore\\some\\dir'], + [['.'], process.cwd()], + [['//server/share', '..', 'relative\\'], '\\\\server\\share\\relative'], + [['c:/', '//'], 'c:\\'], + [['c:/', '//dir'], 'c:\\dir'], + [['c:/', '//server/share'], '\\\\server\\share\\'], + [['c:/', '//server//share'], '\\\\server\\share\\'], + [['c:/', '///some//dir'], 'c:\\some\\dir'], + [['C:\\foo\\tmp.3\\', '..\\tmp.3\\cycles\\root.js'], + 'C:\\foo\\tmp.3\\cycles\\root.js'], + ], + ], + [ path.posix.resolve, + // Arguments result + [[['/var/lib', '../', 'file/'], '/var/file'], + [['/var/lib', '/../', 'file/'], '/file'], + // TODO(wafuwafu13): Enable this + // [['a/b/c/', '../../..'], posixyCwd], + // [['.'], posixyCwd], + [['/some/dir', '.', '/absolute/'], '/absolute'], + [['/foo/tmp.3/', '../tmp.3/cycles/root.js'], '/foo/tmp.3/cycles/root.js'], + ], + ], +]; +resolveTests.forEach(([resolve, tests]) => { + tests.forEach(([test, expected]) => { + const actual = resolve.apply(null, test); + let actualAlt; + const os = resolve === path.win32.resolve ? 'win32' : 'posix'; + if (resolve === path.win32.resolve && !common.isWindows) + actualAlt = actual.replace(backslashRE, '/'); + else if (resolve !== path.win32.resolve && common.isWindows) + actualAlt = actual.replace(slashRE, '\\'); + + const message = + `path.${os}.resolve(${test.map(JSON.stringify).join(',')})\n expect=${ + JSON.stringify(expected)}\n actual=${JSON.stringify(actual)}`; + if (actual !== expected && actualAlt !== expected) + failures.push(message); + }); +}); +assert.strictEqual(failures.length, 0, failures.join('\n')); + +if (common.isWindows) { + // Test resolving the current Windows drive letter from a spawned process. + // See https://github.com/nodejs/node/issues/7215 + const currentDriveLetter = path.parse(process.cwd()).root.substring(0, 2); + const resolveFixture = fixtures.path('path-resolve.js'); + // TODO(wafuwafu13): Enable this + // const spawnResult = child.spawnSync( + // process.argv[0], [resolveFixture, currentDriveLetter]); + // const resolvedPath = spawnResult.stdout.toString().trim(); + // assert.strictEqual(resolvedPath.toLowerCase(), process.cwd().toLowerCase()); +} + +if (!common.isWindows) { + // Test handling relative paths to be safe when process.cwd() fails. + process.cwd = () => ''; + assert.strictEqual(process.cwd(), ''); + const resolved = path.resolve(); + const expected = '.'; + // TODO(wafuwafu13): Enable this + // assert.strictEqual(resolved, expected); +} diff --git a/cli/tests/node_compat/test/parallel/test-path-win32-exists.js b/cli/tests/node_compat/test/parallel/test-path-win32-exists.js new file mode 100644 index 00000000000000..6062fbacb1d706 --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-path-win32-exists.js @@ -0,0 +1,13 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +'use strict'; + +require('../common'); +const assert = require('assert'); + +assert.strictEqual(require('path/win32'), require('path').win32); diff --git a/cli/tests/node_compat/test/parallel/test-path-zero-length-strings.js b/cli/tests/node_compat/test/parallel/test-path-zero-length-strings.js new file mode 100644 index 00000000000000..d8025224b7f9b1 --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-path-zero-length-strings.js @@ -0,0 +1,46 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +'use strict'; + +// These testcases are specific to one uncommon behavior in path module. Few +// of the functions in path module, treat '' strings as current working +// directory. This test makes sure that the behavior is intact between commits. +// See: https://github.com/nodejs/node/pull/2106 + +require('../common'); +const assert = require('assert'); +const path = require('path'); +const pwd = process.cwd(); + +// Join will internally ignore all the zero-length strings and it will return +// '.' if the joined string is a zero-length string. +assert.strictEqual(path.posix.join(''), '.'); +assert.strictEqual(path.posix.join('', ''), '.'); +assert.strictEqual(path.win32.join(''), '.'); +assert.strictEqual(path.win32.join('', ''), '.'); +assert.strictEqual(path.join(pwd), pwd); +assert.strictEqual(path.join(pwd, ''), pwd); + +// Normalize will return '.' if the input is a zero-length string +assert.strictEqual(path.posix.normalize(''), '.'); +assert.strictEqual(path.win32.normalize(''), '.'); +assert.strictEqual(path.normalize(pwd), pwd); + +// Since '' is not a valid path in any of the common environments, return false +assert.strictEqual(path.posix.isAbsolute(''), false); +assert.strictEqual(path.win32.isAbsolute(''), false); + +// Resolve, internally ignores all the zero-length strings and returns the +// current working directory +assert.strictEqual(path.resolve(''), pwd); +assert.strictEqual(path.resolve('', ''), pwd); + +// Relative, internally calls resolve. So, '' is actually the current directory +assert.strictEqual(path.relative('', pwd), ''); +assert.strictEqual(path.relative(pwd, ''), ''); +assert.strictEqual(path.relative(pwd, pwd), ''); diff --git a/cli/tests/node_compat/test/parallel/test-path.js b/cli/tests/node_compat/test/parallel/test-path.js new file mode 100644 index 00000000000000..03bbfebb237a0e --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-path.js @@ -0,0 +1,81 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 16.13.0 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. + +'use strict'; +const common = require('../common'); +const assert = require('assert'); +const path = require('path'); + +// Test thrown TypeErrors +const typeErrorTests = [true, false, 7, null, {}, undefined, [], NaN]; + +function fail(fn) { + const args = Array.from(arguments).slice(1); + + assert.throws(() => { + fn.apply(null, args); + }, { code: 'ERR_INVALID_ARG_TYPE', name: 'TypeError' }); +} + +typeErrorTests.forEach((test) => { + [path.posix, path.win32].forEach((namespace) => { + fail(namespace.join, test); + fail(namespace.resolve, test); + fail(namespace.normalize, test); + fail(namespace.isAbsolute, test); + fail(namespace.relative, test, 'foo'); + fail(namespace.relative, 'foo', test); + fail(namespace.parse, test); + fail(namespace.dirname, test); + fail(namespace.basename, test); + fail(namespace.extname, test); + + // Undefined is a valid value as the second argument to basename + if (test !== undefined) { + fail(namespace.basename, 'foo', test); + } + }); +}); + +// path.sep tests +// windows +assert.strictEqual(path.win32.sep, '\\'); +// posix +assert.strictEqual(path.posix.sep, '/'); + +// path.delimiter tests +// windows +assert.strictEqual(path.win32.delimiter, ';'); +// posix +assert.strictEqual(path.posix.delimiter, ':'); + +// TODO(wafuwafu13): Enable this +// if (common.isWindows) +// assert.strictEqual(path, path.win32); +// else +// assert.strictEqual(path, path.posix); diff --git a/cli/tests/node_compat/test/parallel/test-process-beforeexit.js b/cli/tests/node_compat/test/parallel/test-process-beforeexit.js new file mode 100644 index 00000000000000..7450bf9449e7b5 --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-process-beforeexit.js @@ -0,0 +1,88 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. + +'use strict'; +const common = require('../common'); +const net = require('net'); + +process.once('beforeExit', common.mustCall(tryImmediate)); + +function tryImmediate() { + setImmediate(common.mustCall(() => { + process.once('beforeExit', common.mustCall(tryTimer)); + })); +} + +function tryTimer() { + setTimeout(common.mustCall(() => { + process.once('beforeExit', common.mustCall(tryListen)); + }), 1); +} + +function tryListen() { + net.createServer() + .listen(0) + .on('listening', common.mustCall(function() { + this.close(); + process.once('beforeExit', common.mustCall(tryRepeatedTimer)); + })); +} + +// Test that a function invoked from the beforeExit handler can use a timer +// to keep the event loop open, which can use another timer to keep the event +// loop open, etc. +// +// After N times, call function `tryNextTick` to test behaviors of the +// `process.nextTick`. +function tryRepeatedTimer() { + const N = 5; + let n = 0; + const repeatedTimer = common.mustCall(function() { + if (++n < N) + setTimeout(repeatedTimer, 1); + else // n == N + process.once('beforeExit', common.mustCall(tryNextTickSetImmediate)); + }, N); + setTimeout(repeatedTimer, 1); +} + +// Test if the callback of `process.nextTick` can be invoked. +function tryNextTickSetImmediate() { + process.nextTick(common.mustCall(function() { + setImmediate(common.mustCall(() => { + process.once('beforeExit', common.mustCall(tryNextTick)); + })); + })); +} + +// Test that `process.nextTick` won't keep the event loop running by itself. +function tryNextTick() { + process.nextTick(common.mustCall(function() { + process.once('beforeExit', common.mustNotCall()); + })); +} diff --git a/cli/tests/node_compat/test/parallel/test-process-binding-internalbinding-allowlist.js b/cli/tests/node_compat/test/parallel/test-process-binding-internalbinding-allowlist.js new file mode 100644 index 00000000000000..291bace0cbd043 --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-process-binding-internalbinding-allowlist.js @@ -0,0 +1,48 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +// Flags: --no-warnings +'use strict'; + +const common = require('../common'); +const assert = require('assert'); + +// Assert that allowed internalBinding modules are accessible via +// process.binding(). +assert(process.binding('async_wrap')); +assert(process.binding('buffer')); +assert(process.binding('cares_wrap')); +assert(process.binding('constants')); +assert(process.binding('contextify')); +if (common.hasCrypto) { // eslint-disable-line node-core/crypto-check + assert(process.binding('crypto')); +} +assert(process.binding('fs')); +assert(process.binding('fs_event_wrap')); +assert(process.binding('http_parser')); +if (common.hasIntl) { + assert(process.binding('icu')); +} +assert(process.binding('inspector')); +assert(process.binding('js_stream')); +assert(process.binding('natives')); +assert(process.binding('os')); +assert(process.binding('pipe_wrap')); +assert(process.binding('signal_wrap')); +assert(process.binding('spawn_sync')); +assert(process.binding('stream_wrap')); +assert(process.binding('tcp_wrap')); +if (common.hasCrypto) { // eslint-disable-line node-core/crypto-check + assert(process.binding('tls_wrap')); +} +assert(process.binding('tty_wrap')); +assert(process.binding('udp_wrap')); +assert(process.binding('url')); +assert(process.binding('util')); +assert(process.binding('uv')); +assert(process.binding('v8')); +assert(process.binding('zlib')); diff --git a/cli/tests/node_compat/test/parallel/test-process-env-allowed-flags.js b/cli/tests/node_compat/test/parallel/test-process-env-allowed-flags.js new file mode 100644 index 00000000000000..3ea6d4dda62e26 --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-process-env-allowed-flags.js @@ -0,0 +1,109 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +'use strict'; + +const common = require('../common'); +const assert = require('assert'); + +// Assert legit flags are allowed, and bogus flags are disallowed +{ + const goodFlags = [ + '--perf_basic_prof', + '--perf-basic-prof', + 'perf-basic-prof', + '--perf_basic-prof', + 'perf_basic-prof', + 'perf_basic_prof', + '-r', + 'r', + '--stack-trace-limit=100', + '--stack-trace-limit=-=xX_nodejs_Xx=-', + ].concat(process.features.inspector ? [ + '--inspect-brk', + 'inspect-brk', + '--inspect_brk', + ] : []); + + const badFlags = [ + 'INSPECT-BRK', + '--INSPECT-BRK', + '--r', + '-R', + '---inspect-brk', + '--cheeseburgers', + ]; + + goodFlags.forEach((flag) => { + assert.strictEqual( + process.allowedNodeEnvironmentFlags.has(flag), + true, + `flag should be in set: ${flag}` + ); + }); + + badFlags.forEach((flag) => { + assert.strictEqual( + process.allowedNodeEnvironmentFlags.has(flag), + false, + `flag should not be in set: ${flag}` + ); + }); +} + +// Assert all "canonical" flags begin with dash(es) +{ + process.allowedNodeEnvironmentFlags.forEach((flag) => { + assert.match(flag, /^--?[a-zA-Z0-9._-]+$/); + }); +} + +// Assert immutability of process.allowedNodeEnvironmentFlags +{ + assert.strictEqual(Object.isFrozen(process.allowedNodeEnvironmentFlags), + true); + + process.allowedNodeEnvironmentFlags.add('foo'); + assert.strictEqual(process.allowedNodeEnvironmentFlags.has('foo'), false); + Set.prototype.add.call(process.allowedNodeEnvironmentFlags, 'foo'); + assert.strictEqual(process.allowedNodeEnvironmentFlags.has('foo'), false); + + const thisArg = {}; + process.allowedNodeEnvironmentFlags.forEach( + common.mustCallAtLeast(function(flag, _, set) { + assert.notStrictEqual(flag, 'foo'); + assert.strictEqual(this, thisArg); + assert.strictEqual(set, process.allowedNodeEnvironmentFlags); + }), + thisArg + ); + + for (const flag of process.allowedNodeEnvironmentFlags.keys()) { + assert.notStrictEqual(flag, 'foo'); + } + for (const flag of process.allowedNodeEnvironmentFlags.values()) { + assert.notStrictEqual(flag, 'foo'); + } + for (const flag of process.allowedNodeEnvironmentFlags) { + assert.notStrictEqual(flag, 'foo'); + } + for (const [flag] of process.allowedNodeEnvironmentFlags.entries()) { + assert.notStrictEqual(flag, 'foo'); + } + + const size = process.allowedNodeEnvironmentFlags.size; + + process.allowedNodeEnvironmentFlags.clear(); + assert.strictEqual(process.allowedNodeEnvironmentFlags.size, size); + Set.prototype.clear.call(process.allowedNodeEnvironmentFlags); + assert.strictEqual(process.allowedNodeEnvironmentFlags.size, size); + + process.allowedNodeEnvironmentFlags.delete('-r'); + assert.strictEqual(process.allowedNodeEnvironmentFlags.size, size); + Set.prototype.delete.call(process.allowedNodeEnvironmentFlags, '-r'); + assert.strictEqual(process.allowedNodeEnvironmentFlags.size, size); +} diff --git a/cli/tests/node_compat/test/parallel/test-process-exit-from-before-exit.js b/cli/tests/node_compat/test/parallel/test-process-exit-from-before-exit.js new file mode 100644 index 00000000000000..bd6e9eedfe1855 --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-process-exit-from-before-exit.js @@ -0,0 +1,37 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. + +'use strict'; +const common = require('../common'); +const assert = require('assert'); + +process.on('beforeExit', common.mustCall(function() { + setTimeout(common.mustNotCall(), 5); + process.exit(0); // Should execute immediately even if we schedule new work. + assert.fail(); +})); diff --git a/cli/tests/node_compat/test/parallel/test-process-exit-handler.js b/cli/tests/node_compat/test/parallel/test-process-exit-handler.js new file mode 100644 index 00000000000000..c191373cc28c3e --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-process-exit-handler.js @@ -0,0 +1,21 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +'use strict'; +const common = require('../common'); + +if (!common.isMainThread) + common.skip('execArgv does not affect Workers'); + +// This test ensures that no asynchronous operations are performed in the 'exit' +// handler. +// https://github.com/nodejs/node/issues/12322 + +process.on('exit', () => { + setTimeout(() => process.abort(), 0); // Should not run. + for (const start = Date.now(); Date.now() - start < 10;); +}); diff --git a/cli/tests/node_compat/test/parallel/test-process-exit-recursive.js b/cli/tests/node_compat/test/parallel/test-process-exit-recursive.js new file mode 100644 index 00000000000000..69080ea9c685a0 --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-process-exit-recursive.js @@ -0,0 +1,44 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. + +'use strict'; +require('../common'); +const assert = require('assert'); + +// Recursively calling .exit() should not overflow the call stack +let nexits = 0; + +process.on('exit', function(code) { + assert.strictEqual(nexits++, 0); + assert.strictEqual(code, 1); + + // Now override the exit code of 1 with 0 so that the test passes + process.exit(0); +}); + +process.exit(1); diff --git a/cli/tests/node_compat/test/parallel/test-process-exit.js b/cli/tests/node_compat/test/parallel/test-process-exit.js new file mode 100644 index 00000000000000..2775776240aef5 --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-process-exit.js @@ -0,0 +1,42 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. + +'use strict'; +require('../common'); +const assert = require('assert'); + +// Calling .exit() from within "exit" should not overflow the call stack +let nexits = 0; + +process.on('exit', function(code) { + assert.strictEqual(nexits++, 0); + assert.strictEqual(code, 0); + process.exit(); +}); + +// "exit" should be emitted unprovoked diff --git a/cli/tests/node_compat/test/parallel/test-process-kill-pid.js b/cli/tests/node_compat/test/parallel/test-process-kill-pid.js new file mode 100644 index 00000000000000..93651eacff5b1d --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-process-kill-pid.js @@ -0,0 +1,116 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. + +'use strict'; +const common = require('../common'); +const assert = require('assert'); + +// Test variants of pid +// +// null: TypeError +// undefined: TypeError +// +// 'SIGTERM': TypeError +// +// String(process.pid): TypeError +// +// Nan, Infinity, -Infinity: TypeError +// +// 0, String(0): our group process +// +// process.pid, String(process.pid): ourself + +['SIGTERM', null, undefined, NaN, Infinity, -Infinity].forEach((val) => { + assert.throws(() => process.kill(val), { + code: 'ERR_INVALID_ARG_TYPE', + name: 'TypeError', + message: 'The "pid" argument must be of type number.' + + common.invalidArgTypeHelper(val) + }); +}); + +// Test that kill throws an error for unknown signal names +assert.throws(() => process.kill(0, 'test'), { + code: 'ERR_UNKNOWN_SIGNAL', + name: 'TypeError', + message: 'Unknown signal: test' +}); + +// Test that kill throws an error for invalid signal numbers +assert.throws(() => process.kill(0, 987), { + code: 'EINVAL', + name: 'Error', + message: 'kill EINVAL' +}); + +// Test kill argument processing in valid cases. +// +// Monkey patch _kill so that we don't actually send any signals, particularly +// that we don't kill our process group, or try to actually send ANY signals on +// windows, which doesn't support them. +function kill(tryPid, trySig, expectPid, expectSig) { + let getPid; + let getSig; + const origKill = process._kill; + process._kill = function(pid, sig) { + getPid = pid; + getSig = sig; + + // un-monkey patch process._kill + process._kill = origKill; + }; + + process.kill(tryPid, trySig); + + assert.strictEqual(getPid.toString(), expectPid.toString()); + assert.strictEqual(getSig, expectSig); +} + +// Note that SIGHUP and SIGTERM map to 1 and 15 respectively, even on Windows +// (for Windows, libuv maps 1 and 15 to the correct behavior). + +kill(0, 'SIGHUP', 0, 1); +kill(0, undefined, 0, 15); +kill('0', 'SIGHUP', 0, 1); +kill('0', undefined, 0, 15); + +// Confirm that numeric signal arguments are supported + +kill(0, 1, 0, 1); +kill(0, 15, 0, 15); + +// Negative numbers are meaningful on unix +kill(-1, 'SIGHUP', -1, 1); +kill(-1, undefined, -1, 15); +kill('-1', 'SIGHUP', -1, 1); +kill('-1', undefined, -1, 15); + +kill(process.pid, 'SIGHUP', process.pid, 1); +kill(process.pid, undefined, process.pid, 15); +kill(String(process.pid), 'SIGHUP', process.pid, 1); +kill(String(process.pid), undefined, process.pid, 15); diff --git a/cli/tests/node_compat/test/parallel/test-process-uptime.js b/cli/tests/node_compat/test/parallel/test-process-uptime.js new file mode 100644 index 00000000000000..3d432f1302da8e --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-process-uptime.js @@ -0,0 +1,44 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. + +'use strict'; +require('../common'); +const assert = require('assert'); + +console.error(process.uptime()); +// Add some wiggle room for different platforms. +// Verify that the returned value is in seconds - +// 15 seconds should be a good estimate. +assert.ok(process.uptime() <= 15); + +const original = process.uptime(); + +setTimeout(function() { + const uptime = process.uptime(); + assert.ok(original < uptime); +}, 10); diff --git a/cli/tests/node_compat/test/parallel/test-promise-unhandled-silent.js b/cli/tests/node_compat/test/parallel/test-promise-unhandled-silent.js new file mode 100644 index 00000000000000..a2b30f74ce2b71 --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-promise-unhandled-silent.js @@ -0,0 +1,28 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +// Flags: --unhandled-rejections=none +'use strict'; + +const common = require('../common'); + +// Verify that ignoring unhandled rejection works fine and that no warning is +// logged. + +new Promise(() => { + throw new Error('One'); +}); + +Promise.reject('test'); + +process.on('warning', common.mustNotCall('warning')); +process.on('uncaughtException', common.mustNotCall('uncaughtException')); +process.on('rejectionHandled', common.mustNotCall('rejectionHandled')); + +process.on('unhandledRejection', common.mustCall(2)); + +setTimeout(common.mustCall(), 2); diff --git a/cli/tests/node_compat/test/parallel/test-promise-unhandled-throw-handler.js b/cli/tests/node_compat/test/parallel/test-promise-unhandled-throw-handler.js new file mode 100644 index 00000000000000..57614c3249da4e --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-promise-unhandled-throw-handler.js @@ -0,0 +1,43 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +// Flags: --unhandled-rejections=throw +'use strict'; + +const common = require('../common'); +const Countdown = require('../common/countdown'); +const assert = require('assert'); + +// Verify that the unhandledRejection handler prevents triggering +// uncaught exceptions + +const err1 = new Error('One'); + +const errors = [err1, null]; + +const ref = new Promise(() => { + throw err1; +}); +// Explicitly reject `null`. +Promise.reject(null); + +process.on('warning', common.mustNotCall('warning')); +process.on('rejectionHandled', common.mustNotCall('rejectionHandled')); +process.on('exit', assert.strictEqual.bind(null, 0)); +process.on('uncaughtException', common.mustNotCall('uncaughtException')); + +const timer = setTimeout(() => console.log(ref), 1000); + +const counter = new Countdown(2, () => { + clearTimeout(timer); +}); + +process.on('unhandledRejection', common.mustCall((err) => { + counter.dec(); + const knownError = errors.shift(); + assert.deepStrictEqual(err, knownError); +}, 2)); diff --git a/cli/tests/node_compat/test/parallel/test-punycode.js b/cli/tests/node_compat/test/parallel/test-punycode.js new file mode 100644 index 00000000000000..cb65954a020c6c --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-punycode.js @@ -0,0 +1,280 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +// Flags: --pending-deprecation + +// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. + +'use strict'; +const common = require('../common'); + +const punycodeWarning = + 'The `punycode` module is deprecated. Please use a userland alternative ' + + 'instead.'; +common.expectWarning('DeprecationWarning', punycodeWarning, 'DEP0040'); + +const punycode = require('punycode'); +const assert = require('assert'); + +assert.strictEqual(punycode.encode('ü'), 'tda'); +assert.strictEqual(punycode.encode('Goethe'), 'Goethe-'); +assert.strictEqual(punycode.encode('Bücher'), 'Bcher-kva'); +assert.strictEqual( + punycode.encode( + 'Willst du die Blüthe des frühen, die Früchte des späteren Jahres' + ), + 'Willst du die Blthe des frhen, die Frchte des spteren Jahres-x9e96lkal' +); +assert.strictEqual(punycode.encode('日本語'), 'wgv71a119e'); +assert.strictEqual(punycode.encode('𩸽'), 'x73l'); + +assert.strictEqual(punycode.decode('tda'), 'ü'); +assert.strictEqual(punycode.decode('Goethe-'), 'Goethe'); +assert.strictEqual(punycode.decode('Bcher-kva'), 'Bücher'); +assert.strictEqual( + punycode.decode( + 'Willst du die Blthe des frhen, die Frchte des spteren Jahres-x9e96lkal' + ), + 'Willst du die Blüthe des frühen, die Früchte des späteren Jahres' +); +assert.strictEqual(punycode.decode('wgv71a119e'), '日本語'); +assert.strictEqual(punycode.decode('x73l'), '𩸽'); +assert.throws(() => { + punycode.decode(' '); +}, /^RangeError: Invalid input$/); +assert.throws(() => { + punycode.decode('α-'); +}, /^RangeError: Illegal input >= 0x80 \(not a basic code point\)$/); +assert.throws(() => { + punycode.decode('あ'); +}, /^RangeError: Overflow: input needs wider integers to process$/); + +// http://tools.ietf.org/html/rfc3492#section-7.1 +const tests = [ + // (A) Arabic (Egyptian) + { + encoded: 'egbpdaj6bu4bxfgehfvwxn', + decoded: '\u0644\u064A\u0647\u0645\u0627\u0628\u062A\u0643\u0644\u0645' + + '\u0648\u0634\u0639\u0631\u0628\u064A\u061F' + }, + + // (B) Chinese (simplified) + { + encoded: 'ihqwcrb4cv8a8dqg056pqjye', + decoded: '\u4ED6\u4EEC\u4E3A\u4EC0\u4E48\u4E0D\u8BF4\u4E2D\u6587' + }, + + // (C) Chinese (traditional) + { + encoded: 'ihqwctvzc91f659drss3x8bo0yb', + decoded: '\u4ED6\u5011\u7232\u4EC0\u9EBD\u4E0D\u8AAA\u4E2D\u6587' + }, + + // (D) Czech: Proprostnemluvesky + { + encoded: 'Proprostnemluvesky-uyb24dma41a', + decoded: '\u0050\u0072\u006F\u010D\u0070\u0072\u006F\u0073\u0074\u011B' + + '\u006E\u0065\u006D\u006C\u0075\u0076\u00ED\u010D\u0065\u0073\u006B\u0079' + }, + + // (E) Hebrew + { + encoded: '4dbcagdahymbxekheh6e0a7fei0b', + decoded: '\u05DC\u05DE\u05D4\u05D4\u05DD\u05E4\u05E9\u05D5\u05D8\u05DC' + + '\u05D0\u05DE\u05D3\u05D1\u05E8\u05D9\u05DD\u05E2\u05D1\u05E8\u05D9\u05EA' + }, + + // (F) Hindi (Devanagari) + { + encoded: 'i1baa7eci9glrd9b2ae1bj0hfcgg6iyaf8o0a1dig0cd', + decoded: '\u092F\u0939\u0932\u094B\u0917\u0939\u093F\u0928\u094D\u0926' + + '\u0940\u0915\u094D\u092F\u094B\u0902\u0928\u0939\u0940\u0902\u092C' + + '\u094B\u0932\u0938\u0915\u0924\u0947\u0939\u0948\u0902' + }, + + // (G) Japanese (kanji and hiragana) + { + encoded: 'n8jok5ay5dzabd5bym9f0cm5685rrjetr6pdxa', + decoded: '\u306A\u305C\u307F\u3093\u306A\u65E5\u672C\u8A9E\u3092\u8A71' + + '\u3057\u3066\u304F\u308C\u306A\u3044\u306E\u304B' + }, + + // (H) Korean (Hangul syllables) + { + encoded: '989aomsvi5e83db1d2a355cv1e0vak1dwrv93d5xbh15a0dt30a5jpsd879' + + 'ccm6fea98c', + decoded: '\uC138\uACC4\uC758\uBAA8\uB4E0\uC0AC\uB78C\uB4E4\uC774\uD55C' + + '\uAD6D\uC5B4\uB97C\uC774\uD574\uD55C\uB2E4\uBA74\uC5BC\uB9C8\uB098' + + '\uC88B\uC744\uAE4C' + }, + + // (I) Russian (Cyrillic) + { + encoded: 'b1abfaaepdrnnbgefbadotcwatmq2g4l', + decoded: '\u043F\u043E\u0447\u0435\u043C\u0443\u0436\u0435\u043E\u043D' + + '\u0438\u043D\u0435\u0433\u043E\u0432\u043E\u0440\u044F\u0442\u043F' + + '\u043E\u0440\u0443\u0441\u0441\u043A\u0438' + }, + + // (J) Spanish: PorqunopuedensimplementehablarenEspaol + { + encoded: 'PorqunopuedensimplementehablarenEspaol-fmd56a', + decoded: '\u0050\u006F\u0072\u0071\u0075\u00E9\u006E\u006F\u0070\u0075' + + '\u0065\u0064\u0065\u006E\u0073\u0069\u006D\u0070\u006C\u0065\u006D' + + '\u0065\u006E\u0074\u0065\u0068\u0061\u0062\u006C\u0061\u0072\u0065' + + '\u006E\u0045\u0073\u0070\u0061\u00F1\u006F\u006C' + }, + + // (K) Vietnamese: Tisaohkhngth + // chnitingVit + { + encoded: 'TisaohkhngthchnitingVit-kjcr8268qyxafd2f1b9g', + decoded: '\u0054\u1EA1\u0069\u0073\u0061\u006F\u0068\u1ECD\u006B\u0068' + + '\u00F4\u006E\u0067\u0074\u0068\u1EC3\u0063\u0068\u1EC9\u006E\u00F3' + + '\u0069\u0074\u0069\u1EBF\u006E\u0067\u0056\u0069\u1EC7\u0074' + }, + + // (L) 3B + { + encoded: '3B-ww4c5e180e575a65lsy2b', + decoded: '\u0033\u5E74\u0042\u7D44\u91D1\u516B\u5148\u751F' + }, + + // (M) -with-SUPER-MONKEYS + { + encoded: '-with-SUPER-MONKEYS-pc58ag80a8qai00g7n9n', + decoded: '\u5B89\u5BA4\u5948\u7F8E\u6075\u002D\u0077\u0069\u0074\u0068' + + '\u002D\u0053\u0055\u0050\u0045\u0052\u002D\u004D\u004F\u004E\u004B' + + '\u0045\u0059\u0053' + }, + + // (N) Hello-Another-Way- + { + encoded: 'Hello-Another-Way--fc4qua05auwb3674vfr0b', + decoded: '\u0048\u0065\u006C\u006C\u006F\u002D\u0041\u006E\u006F\u0074' + + '\u0068\u0065\u0072\u002D\u0057\u0061\u0079\u002D\u305D\u308C\u305E' + + '\u308C\u306E\u5834\u6240' + }, + + // (O) 2 + { + encoded: '2-u9tlzr9756bt3uc0v', + decoded: '\u3072\u3068\u3064\u5C4B\u6839\u306E\u4E0B\u0032' + }, + + // (P) MajiKoi5 + { + encoded: 'MajiKoi5-783gue6qz075azm5e', + decoded: '\u004D\u0061\u006A\u0069\u3067\u004B\u006F\u0069\u3059\u308B' + + '\u0035\u79D2\u524D' + }, + + // (Q) de + { + encoded: 'de-jg4avhby1noc0d', + decoded: '\u30D1\u30D5\u30A3\u30FC\u0064\u0065\u30EB\u30F3\u30D0' + }, + + // (R) + { + encoded: 'd9juau41awczczp', + decoded: '\u305D\u306E\u30B9\u30D4\u30FC\u30C9\u3067' + }, + + // (S) -> $1.00 <- + { + encoded: '-> $1.00 <--', + decoded: '\u002D\u003E\u0020\u0024\u0031\u002E\u0030\u0030\u0020\u003C' + + '\u002D' + }, +]; + +let errors = 0; +const handleError = (error, name) => { + console.error( + `FAIL: ${name} expected ${error.expected}, got ${error.actual}` + ); + errors++; +}; + +const regexNonASCII = /[^\x20-\x7E]/; +const testBattery = { + encode: (test) => assert.strictEqual( + punycode.encode(test.decoded), + test.encoded + ), + decode: (test) => assert.strictEqual( + punycode.decode(test.encoded), + test.decoded + ), + toASCII: (test) => assert.strictEqual( + punycode.toASCII(test.decoded), + regexNonASCII.test(test.decoded) ? + `xn--${test.encoded}` : + test.decoded + ), + toUnicode: (test) => assert.strictEqual( + punycode.toUnicode( + regexNonASCII.test(test.decoded) ? + `xn--${test.encoded}` : + test.decoded + ), + regexNonASCII.test(test.decoded) ? + test.decoded.toLowerCase() : + test.decoded + ) +}; + +tests.forEach((testCase) => { + Object.keys(testBattery).forEach((key) => { + try { + testBattery[key](testCase); + } catch (error) { + handleError(error, key); + } + }); +}); + +// BMP code point +assert.strictEqual(punycode.ucs2.encode([0x61]), 'a'); +// Supplementary code point (surrogate pair) +assert.strictEqual(punycode.ucs2.encode([0x1D306]), '\uD834\uDF06'); +// high surrogate +assert.strictEqual(punycode.ucs2.encode([0xD800]), '\uD800'); +// High surrogate followed by non-surrogates +assert.strictEqual(punycode.ucs2.encode([0xD800, 0x61, 0x62]), '\uD800ab'); +// low surrogate +assert.strictEqual(punycode.ucs2.encode([0xDC00]), '\uDC00'); +// Low surrogate followed by non-surrogates +assert.strictEqual(punycode.ucs2.encode([0xDC00, 0x61, 0x62]), '\uDC00ab'); + +assert.strictEqual(errors, 0); + +// test map domain +assert.strictEqual(punycode.toASCII('Bücher@日本語.com'), + 'Bücher@xn--wgv71a119e.com'); +assert.strictEqual(punycode.toUnicode('Bücher@xn--wgv71a119e.com'), + 'Bücher@日本語.com'); diff --git a/cli/tests/node_compat/test/parallel/test-querystring-escape.js b/cli/tests/node_compat/test/parallel/test-querystring-escape.js new file mode 100644 index 00000000000000..47f0ce1f1b40a4 --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-querystring-escape.js @@ -0,0 +1,48 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +'use strict'; +require('../common'); +const assert = require('assert'); + +const qs = require('querystring'); + +assert.strictEqual(qs.escape(5), '5'); +assert.strictEqual(qs.escape('test'), 'test'); +assert.strictEqual(qs.escape({}), '%5Bobject%20Object%5D'); +assert.strictEqual(qs.escape([5, 10]), '5%2C10'); +assert.strictEqual(qs.escape('Ŋōđĕ'), '%C5%8A%C5%8D%C4%91%C4%95'); +assert.strictEqual(qs.escape('testŊōđĕ'), 'test%C5%8A%C5%8D%C4%91%C4%95'); +assert.strictEqual(qs.escape(`${String.fromCharCode(0xD800 + 1)}test`), + '%F0%90%91%B4est'); + +assert.throws( + () => qs.escape(String.fromCharCode(0xD800 + 1)), + { + code: 'ERR_INVALID_URI', + name: 'URIError', + message: 'URI malformed' + } +); + +// Using toString for objects +assert.strictEqual( + qs.escape({ test: 5, toString: () => 'test', valueOf: () => 10 }), + 'test' +); + +// `toString` is not callable, must throw an error. +// Error message will vary between different JavaScript engines, so only check +// that it is a `TypeError`. +assert.throws(() => qs.escape({ toString: 5 }), TypeError); + +// Should use valueOf instead of non-callable toString. +assert.strictEqual(qs.escape({ toString: 5, valueOf: () => 'test' }), 'test'); + +// Error message will vary between different JavaScript engines, so only check +// that it is a `TypeError`. +assert.throws(() => qs.escape(Symbol('test')), TypeError); diff --git a/cli/tests/node_compat/test/parallel/test-querystring-maxKeys-non-finite.js b/cli/tests/node_compat/test/parallel/test-querystring-maxKeys-non-finite.js new file mode 100644 index 00000000000000..c87889853476bf --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-querystring-maxKeys-non-finite.js @@ -0,0 +1,65 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +'use strict'; +// This test was originally written to test a regression +// that was introduced by +// https://github.com/nodejs/node/pull/2288#issuecomment-179543894 +require('../common'); + +const assert = require('assert'); +const parse = require('querystring').parse; + +// Taken from express-js/body-parser +// https://github.com/expressjs/body-parser/blob/ed25264fb494cf0c8bc992b8257092cd4f694d5e/test/urlencoded.js#L636-L651 +function createManyParams(count) { + let str = ''; + + if (count === 0) { + return str; + } + + str += '0=0'; + + for (let i = 1; i < count; i++) { + const n = i.toString(36); + str += `&${n}=${n}`; + } + + return str; +} + +const count = 10000; +const originalMaxLength = 1000; +const params = createManyParams(count); + +// thealphanerd +// 27def4f introduced a change to parse that would cause Infinity +// to be passed to String.prototype.split as an argument for limit +// In this instance split will always return an empty array +// this test confirms that the output of parse is the expected length +// when passed Infinity as the argument for maxKeys +const resultInfinity = parse(params, undefined, undefined, { + maxKeys: Infinity +}); +const resultNaN = parse(params, undefined, undefined, { + maxKeys: NaN +}); +const resultInfinityString = parse(params, undefined, undefined, { + maxKeys: 'Infinity' +}); +const resultNaNString = parse(params, undefined, undefined, { + maxKeys: 'NaN' +}); + +// Non Finite maxKeys should return the length of input +assert.strictEqual(Object.keys(resultInfinity).length, count); +assert.strictEqual(Object.keys(resultNaN).length, count); +// Strings maxKeys should return the maxLength +// defined by parses internals +assert.strictEqual(Object.keys(resultInfinityString).length, originalMaxLength); +assert.strictEqual(Object.keys(resultNaNString).length, originalMaxLength); diff --git a/cli/tests/node_compat/test/parallel/test-querystring-multichar-separator.js b/cli/tests/node_compat/test/parallel/test-querystring-multichar-separator.js new file mode 100644 index 00000000000000..5d0cf62ca5c78f --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-querystring-multichar-separator.js @@ -0,0 +1,32 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +'use strict'; +require('../common'); +const assert = require('assert'); +const qs = require('querystring'); + +function check(actual, expected) { + assert(!(actual instanceof Object)); + assert.deepStrictEqual(Object.keys(actual).sort(), + Object.keys(expected).sort()); + Object.keys(expected).forEach(function(key) { + assert.deepStrictEqual(actual[key], expected[key]); + }); +} + +check(qs.parse('foo=>bar&&bar=>baz', '&&', '=>'), + { foo: 'bar', bar: 'baz' }); + +check(qs.stringify({ foo: 'bar', bar: 'baz' }, '&&', '=>'), + 'foo=>bar&&bar=>baz'); + +check(qs.parse('foo==>bar, bar==>baz', ', ', '==>'), + { foo: 'bar', bar: 'baz' }); + +check(qs.stringify({ foo: 'bar', bar: 'baz' }, ', ', '==>'), + 'foo==>bar, bar==>baz'); diff --git a/cli/tests/node_compat/test/parallel/test-querystring.js b/cli/tests/node_compat/test/parallel/test-querystring.js new file mode 100644 index 00000000000000..933f6b1ba6a91a --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-querystring.js @@ -0,0 +1,490 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 16.12.0 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. + +'use strict'; +require('../common'); +const assert = require('assert'); +const inspect = require('util').inspect; + +// test using assert +const qs = require('querystring'); + +function createWithNoPrototype(properties) { + const noProto = Object.create(null); + properties.forEach((property) => { + noProto[property.key] = property.value; + }); + return noProto; +} +// Folding block, commented to pass gjslint +// {{{ +// [ wonkyQS, canonicalQS, obj ] +const qsTestCases = [ + ['__proto__=1', + '__proto__=1', + createWithNoPrototype([{ key: '__proto__', value: '1' }])], + ['__defineGetter__=asdf', + '__defineGetter__=asdf', + JSON.parse('{"__defineGetter__":"asdf"}')], + ['foo=918854443121279438895193', + 'foo=918854443121279438895193', + { 'foo': '918854443121279438895193' }], + ['foo=bar', 'foo=bar', { 'foo': 'bar' }], + ['foo=bar&foo=quux', 'foo=bar&foo=quux', { 'foo': ['bar', 'quux'] }], + ['foo=1&bar=2', 'foo=1&bar=2', { 'foo': '1', 'bar': '2' }], + ['my+weird+field=q1%212%22%27w%245%267%2Fz8%29%3F', + 'my%20weird%20field=q1!2%22\'w%245%267%2Fz8)%3F', + { 'my weird field': 'q1!2"\'w$5&7/z8)?' }], + ['foo%3Dbaz=bar', 'foo%3Dbaz=bar', { 'foo=baz': 'bar' }], + ['foo=baz=bar', 'foo=baz%3Dbar', { 'foo': 'baz=bar' }], + ['str=foo&arr=1&arr=2&arr=3&somenull=&undef=', + 'str=foo&arr=1&arr=2&arr=3&somenull=&undef=', + { 'str': 'foo', + 'arr': ['1', '2', '3'], + 'somenull': '', + 'undef': '' }], + [' foo = bar ', '%20foo%20=%20bar%20', { ' foo ': ' bar ' }], + ['foo=%zx', 'foo=%25zx', { 'foo': '%zx' }], + ['foo=%EF%BF%BD', 'foo=%EF%BF%BD', { 'foo': '\ufffd' }], + // See: https://github.com/joyent/node/issues/1707 + ['hasOwnProperty=x&toString=foo&valueOf=bar&__defineGetter__=baz', + 'hasOwnProperty=x&toString=foo&valueOf=bar&__defineGetter__=baz', + { hasOwnProperty: 'x', + toString: 'foo', + valueOf: 'bar', + __defineGetter__: 'baz' }], + // See: https://github.com/joyent/node/issues/3058 + ['foo&bar=baz', 'foo=&bar=baz', { foo: '', bar: 'baz' }], + ['a=b&c&d=e', 'a=b&c=&d=e', { a: 'b', c: '', d: 'e' }], + ['a=b&c=&d=e', 'a=b&c=&d=e', { a: 'b', c: '', d: 'e' }], + ['a=b&=c&d=e', 'a=b&=c&d=e', { 'a': 'b', '': 'c', 'd': 'e' }], + ['a=b&=&c=d', 'a=b&=&c=d', { 'a': 'b', '': '', 'c': 'd' }], + ['&&foo=bar&&', 'foo=bar', { foo: 'bar' }], + ['&', '', {}], + ['&&&&', '', {}], + ['&=&', '=', { '': '' }], + ['&=&=', '=&=', { '': [ '', '' ] }], + ['=', '=', { '': '' }], + ['+', '%20=', { ' ': '' }], + ['+=', '%20=', { ' ': '' }], + ['+&', '%20=', { ' ': '' }], + ['=+', '=%20', { '': ' ' }], + ['+=&', '%20=', { ' ': '' }], + ['a&&b', 'a=&b=', { 'a': '', 'b': '' }], + ['a=a&&b=b', 'a=a&b=b', { 'a': 'a', 'b': 'b' }], + ['&a', 'a=', { 'a': '' }], + ['&=', '=', { '': '' }], + ['a&a&', 'a=&a=', { a: [ '', '' ] }], + ['a&a&a&', 'a=&a=&a=', { a: [ '', '', '' ] }], + ['a&a&a&a&', 'a=&a=&a=&a=', { a: [ '', '', '', '' ] }], + ['a=&a=value&a=', 'a=&a=value&a=', { a: [ '', 'value', '' ] }], + ['foo+bar=baz+quux', 'foo%20bar=baz%20quux', { 'foo bar': 'baz quux' }], + ['+foo=+bar', '%20foo=%20bar', { ' foo': ' bar' }], + ['a+', 'a%20=', { 'a ': '' }], + ['=a+', '=a%20', { '': 'a ' }], + ['a+&', 'a%20=', { 'a ': '' }], + ['=a+&', '=a%20', { '': 'a ' }], + ['%20+', '%20%20=', { ' ': '' }], + ['=%20+', '=%20%20', { '': ' ' }], + ['%20+&', '%20%20=', { ' ': '' }], + ['=%20+&', '=%20%20', { '': ' ' }], + [null, '', {}], + [undefined, '', {}], +]; + +// [ wonkyQS, canonicalQS, obj ] +const qsColonTestCases = [ + ['foo:bar', 'foo:bar', { 'foo': 'bar' }], + ['foo:bar;foo:quux', 'foo:bar;foo:quux', { 'foo': ['bar', 'quux'] }], + ['foo:1&bar:2;baz:quux', + 'foo:1%26bar%3A2;baz:quux', + { 'foo': '1&bar:2', 'baz': 'quux' }], + ['foo%3Abaz:bar', 'foo%3Abaz:bar', { 'foo:baz': 'bar' }], + ['foo:baz:bar', 'foo:baz%3Abar', { 'foo': 'baz:bar' }], +]; + +// [wonkyObj, qs, canonicalObj] +function extendedFunction() {} +extendedFunction.prototype = { a: 'b' }; +const qsWeirdObjects = [ + // eslint-disable-next-line node-core/no-unescaped-regexp-dot + [{ regexp: /./g }, 'regexp=', { 'regexp': '' }], + // eslint-disable-next-line node-core/no-unescaped-regexp-dot + [{ regexp: new RegExp('.', 'g') }, 'regexp=', { 'regexp': '' }], + [{ fn: () => {} }, 'fn=', { 'fn': '' }], + [{ fn: new Function('') }, 'fn=', { 'fn': '' }], + [{ math: Math }, 'math=', { 'math': '' }], + [{ e: extendedFunction }, 'e=', { 'e': '' }], + [{ d: new Date() }, 'd=', { 'd': '' }], + [{ d: Date }, 'd=', { 'd': '' }], + [ + { f: new Boolean(false), t: new Boolean(true) }, + 'f=&t=', + { 'f': '', 't': '' }, + ], + [{ f: false, t: true }, 'f=false&t=true', { 'f': 'false', 't': 'true' }], + [{ n: null }, 'n=', { 'n': '' }], + [{ nan: NaN }, 'nan=', { 'nan': '' }], + [{ inf: Infinity }, 'inf=', { 'inf': '' }], + [{ a: [], b: [] }, '', {}], + [{ a: 1, b: [] }, 'a=1', { 'a': '1' }], +]; + +// TODO(wafuwafu13): Enable this when `vm` is implemented. +// const vm = require('vm'); +// const foreignObject = vm.runInNewContext('({"foo": ["bar", "baz"]})'); + +const qsNoMungeTestCases = [ + ['', {}], + ['foo=bar&foo=baz', { 'foo': ['bar', 'baz'] }], + // ['foo=bar&foo=baz', foreignObject], + ['blah=burp', { 'blah': 'burp' }], + ['a=!-._~\'()*', { 'a': '!-._~\'()*' }], + ['a=abcdefghijklmnopqrstuvwxyz', { 'a': 'abcdefghijklmnopqrstuvwxyz' }], + ['a=ABCDEFGHIJKLMNOPQRSTUVWXYZ', { 'a': 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' }], + ['a=0123456789', { 'a': '0123456789' }], + ['gragh=1&gragh=3&goo=2', { 'gragh': ['1', '3'], 'goo': '2' }], + ['frappucino=muffin&goat%5B%5D=scone&pond=moose', + { 'frappucino': 'muffin', 'goat[]': 'scone', 'pond': 'moose' }], + ['trololol=yes&lololo=no', { 'trololol': 'yes', 'lololo': 'no' }], +]; + +const qsUnescapeTestCases = [ + ['there is nothing to unescape here', + 'there is nothing to unescape here'], + ['there%20are%20several%20spaces%20that%20need%20to%20be%20unescaped', + 'there are several spaces that need to be unescaped'], + ['there%2Qare%0-fake%escaped values in%%%%this%9Hstring', + 'there%2Qare%0-fake%escaped values in%%%%this%9Hstring'], + ['%20%21%22%23%24%25%26%27%28%29%2A%2B%2C%2D%2E%2F%30%31%32%33%34%35%36%37', + ' !"#$%&\'()*+,-./01234567'], + ['%%2a', '%*'], + ['%2sf%2a', '%2sf*'], + ['%2%2af%2a', '%2*f*'], +]; + +assert.strictEqual(qs.parse('id=918854443121279438895193').id, + '918854443121279438895193'); + +function check(actual, expected, input) { + assert(!(actual instanceof Object)); + const actualKeys = Object.keys(actual).sort(); + const expectedKeys = Object.keys(expected).sort(); + let msg; + if (typeof input === 'string') { + msg = `Input: ${inspect(input)}\n` + + `Actual keys: ${inspect(actualKeys)}\n` + + `Expected keys: ${inspect(expectedKeys)}`; + } + assert.deepStrictEqual(actualKeys, expectedKeys, msg); + expectedKeys.forEach((key) => { + if (typeof input === 'string') { + msg = `Input: ${inspect(input)}\n` + + `Key: ${inspect(key)}\n` + + `Actual value: ${inspect(actual[key])}\n` + + `Expected value: ${inspect(expected[key])}`; + } else { + msg = undefined; + } + assert.deepStrictEqual(actual[key], expected[key], msg); + }); +} + +// Test that the canonical qs is parsed properly. +qsTestCases.forEach((testCase) => { + check(qs.parse(testCase[0]), testCase[2], testCase[0]); +}); + +// Test that the colon test cases can do the same +qsColonTestCases.forEach((testCase) => { + check(qs.parse(testCase[0], ';', ':'), testCase[2], testCase[0]); +}); + +// Test the weird objects, that they get parsed properly +qsWeirdObjects.forEach((testCase) => { + check(qs.parse(testCase[1]), testCase[2], testCase[1]); +}); + +qsNoMungeTestCases.forEach((testCase) => { + assert.deepStrictEqual(qs.stringify(testCase[1], '&', '='), testCase[0]); +}); + +// Test the nested qs-in-qs case +{ + const f = qs.parse('a=b&q=x%3Dy%26y%3Dz'); + check(f, createWithNoPrototype([ + { key: 'a', value: 'b' }, + { key: 'q', value: 'x=y&y=z' }, + ])); + + f.q = qs.parse(f.q); + const expectedInternal = createWithNoPrototype([ + { key: 'x', value: 'y' }, + { key: 'y', value: 'z' }, + ]); + check(f.q, expectedInternal); +} + +// nested in colon +{ + const f = qs.parse('a:b;q:x%3Ay%3By%3Az', ';', ':'); + check(f, createWithNoPrototype([ + { key: 'a', value: 'b' }, + { key: 'q', value: 'x:y;y:z' }, + ])); + f.q = qs.parse(f.q, ';', ':'); + const expectedInternal = createWithNoPrototype([ + { key: 'x', value: 'y' }, + { key: 'y', value: 'z' }, + ]); + check(f.q, expectedInternal); +} + +// Now test stringifying + +// basic +qsTestCases.forEach((testCase) => { + assert.strictEqual(qs.stringify(testCase[2]), testCase[1]); +}); + +qsColonTestCases.forEach((testCase) => { + assert.strictEqual(qs.stringify(testCase[2], ';', ':'), testCase[1]); +}); + +qsWeirdObjects.forEach((testCase) => { + assert.strictEqual(qs.stringify(testCase[0]), testCase[1]); +}); + +// BigInt values + +assert.strictEqual(qs.stringify({ foo: 2n ** 1023n }), + 'foo=' + 2n ** 1023n); +assert.strictEqual(qs.stringify([0n, 1n, 2n]), + '0=0&1=1&2=2'); + +assert.strictEqual(qs.stringify({ foo: 2n ** 1023n }, + null, + null, + { encodeURIComponent: (c) => c }), + 'foo=' + 2n ** 1023n); +assert.strictEqual(qs.stringify([0n, 1n, 2n], + null, + null, + { encodeURIComponent: (c) => c }), + '0=0&1=1&2=2'); + +// Invalid surrogate pair throws URIError +assert.throws( + () => qs.stringify({ foo: '\udc00' }), + { + code: 'ERR_INVALID_URI', + name: 'URIError', + message: 'URI malformed' + } +); + +// Coerce numbers to string +assert.strictEqual(qs.stringify({ foo: 0 }), 'foo=0'); +assert.strictEqual(qs.stringify({ foo: -0 }), 'foo=0'); +assert.strictEqual(qs.stringify({ foo: 3 }), 'foo=3'); +assert.strictEqual(qs.stringify({ foo: -72.42 }), 'foo=-72.42'); +assert.strictEqual(qs.stringify({ foo: NaN }), 'foo='); +assert.strictEqual(qs.stringify({ foo: 1e21 }), 'foo=1e%2B21'); +assert.strictEqual(qs.stringify({ foo: Infinity }), 'foo='); + +// nested +{ + const f = qs.stringify({ + a: 'b', + q: qs.stringify({ + x: 'y', + y: 'z' + }) + }); + assert.strictEqual(f, 'a=b&q=x%3Dy%26y%3Dz'); +} + +qs.parse(undefined); // Should not throw. + +// nested in colon +{ + const f = qs.stringify({ + a: 'b', + q: qs.stringify({ + x: 'y', + y: 'z' + }, ';', ':') + }, ';', ':'); + assert.strictEqual(f, 'a:b;q:x%3Ay%3By%3Az'); +} + +// empty string +assert.strictEqual(qs.stringify(), ''); +assert.strictEqual(qs.stringify(0), ''); +assert.strictEqual(qs.stringify([]), ''); +assert.strictEqual(qs.stringify(null), ''); +assert.strictEqual(qs.stringify(true), ''); + +check(qs.parse(), {}); + +// empty sep +check(qs.parse('a', []), { a: '' }); + +// empty eq +check(qs.parse('a', null, []), { '': 'a' }); + +// Test limiting +assert.strictEqual( + Object.keys(qs.parse('a=1&b=1&c=1', null, null, { maxKeys: 1 })).length, + 1); + +// Test limiting with a case that starts from `&` +assert.strictEqual( + Object.keys(qs.parse('&a', null, null, { maxKeys: 1 })).length, + 0); + +// Test removing limit +{ + function testUnlimitedKeys() { + const query = {}; + + for (let i = 0; i < 2000; i++) query[i] = i; + + const url = qs.stringify(query); + + assert.strictEqual( + Object.keys(qs.parse(url, null, null, { maxKeys: 0 })).length, + 2000); + } + + testUnlimitedKeys(); +} + +{ + const b = qs.unescapeBuffer('%d3%f2Ug%1f6v%24%5e%98%cb' + + '%0d%ac%a2%2f%9d%eb%d8%a2%e6'); + // + assert.strictEqual(b[0], 0xd3); + assert.strictEqual(b[1], 0xf2); + assert.strictEqual(b[2], 0x55); + assert.strictEqual(b[3], 0x67); + assert.strictEqual(b[4], 0x1f); + assert.strictEqual(b[5], 0x36); + assert.strictEqual(b[6], 0x76); + assert.strictEqual(b[7], 0x24); + assert.strictEqual(b[8], 0x5e); + assert.strictEqual(b[9], 0x98); + assert.strictEqual(b[10], 0xcb); + assert.strictEqual(b[11], 0x0d); + assert.strictEqual(b[12], 0xac); + assert.strictEqual(b[13], 0xa2); + assert.strictEqual(b[14], 0x2f); + assert.strictEqual(b[15], 0x9d); + assert.strictEqual(b[16], 0xeb); + assert.strictEqual(b[17], 0xd8); + assert.strictEqual(b[18], 0xa2); + assert.strictEqual(b[19], 0xe6); +} + +assert.strictEqual(qs.unescapeBuffer('a+b', true).toString(), 'a b'); +assert.strictEqual(qs.unescapeBuffer('a+b').toString(), 'a+b'); +assert.strictEqual(qs.unescapeBuffer('a%').toString(), 'a%'); +assert.strictEqual(qs.unescapeBuffer('a%2').toString(), 'a%2'); +assert.strictEqual(qs.unescapeBuffer('a%20').toString(), 'a '); +assert.strictEqual(qs.unescapeBuffer('a%2g').toString(), 'a%2g'); +assert.strictEqual(qs.unescapeBuffer('a%%').toString(), 'a%%'); + +// Test invalid encoded string +check(qs.parse('%\u0100=%\u0101'), { '%Ā': '%ā' }); + +// Test custom decode +{ + function demoDecode(str) { + return str + str; + } + + check( + qs.parse('a=a&b=b&c=c', null, null, { decodeURIComponent: demoDecode }), + { aa: 'aa', bb: 'bb', cc: 'cc' }); + check( + qs.parse('a=a&b=b&c=c', null, '==', { decodeURIComponent: (str) => str }), + { 'a=a': '', 'b=b': '', 'c=c': '' }); +} + +// TODO(wafuwafu13): Enable this +// // Test QueryString.unescape +// { +// function errDecode(str) { +// throw new Error('To jump to the catch scope'); +// } + +// check(qs.parse('a=a', null, null, { decodeURIComponent: errDecode }), +// { a: 'a' }); +// } + +// Test custom encode +{ + function demoEncode(str) { + return str[0]; + } + + const obj = { aa: 'aa', bb: 'bb', cc: 'cc' }; + assert.strictEqual( + qs.stringify(obj, null, null, { encodeURIComponent: demoEncode }), + 'a=a&b=b&c=c'); +} + +// Test custom encode for different types +{ + const obj = { number: 1, bigint: 2n, true: true, false: false, object: {} }; + assert.strictEqual( + qs.stringify(obj, null, null, { encodeURIComponent: (v) => v }), + 'number=1&bigint=2&true=true&false=false&object='); +} + +// Test QueryString.unescapeBuffer +qsUnescapeTestCases.forEach((testCase) => { + assert.strictEqual(qs.unescape(testCase[0]), testCase[1]); + assert.strictEqual(qs.unescapeBuffer(testCase[0]).toString(), testCase[1]); +}); + +// TODO(wafuwafu13): Enable this +// // Test overriding .unescape +// { +// const prevUnescape = qs.unescape; +// qs.unescape = (str) => { +// return str.replace(/o/g, '_'); +// }; +// check( +// qs.parse('foo=bor'), +// createWithNoPrototype([{ key: 'f__', value: 'b_r' }])); +// qs.unescape = prevUnescape; +// } + +// Test separator and "equals" parsing order +check(qs.parse('foo&bar', '&', '&'), { foo: '', bar: '' }); diff --git a/cli/tests/node_compat/test/parallel/test-readline-csi.js b/cli/tests/node_compat/test/parallel/test-readline-csi.js new file mode 100644 index 00000000000000..e9a87b138f5937 --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-readline-csi.js @@ -0,0 +1,183 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +// Flags: --expose-internals +'use strict'; + +const common = require('../common'); +const assert = require('assert'); +const readline = require('readline'); +const { Writable } = require('stream'); +const { CSI } = require('internal/readline/utils'); + +{ + assert(CSI); + assert.strictEqual(CSI.kClearToLineBeginning, '\x1b[1K'); + assert.strictEqual(CSI.kClearToLineEnd, '\x1b[0K'); + assert.strictEqual(CSI.kClearLine, '\x1b[2K'); + assert.strictEqual(CSI.kClearScreenDown, '\x1b[0J'); + assert.strictEqual(CSI`1${2}3`, '\x1b[123'); +} + +class TestWritable extends Writable { + constructor() { + super(); + this.data = ''; + } + _write(chunk, encoding, callback) { + this.data += chunk.toString(); + callback(); + } +} + +const writable = new TestWritable(); + +assert.strictEqual(readline.clearScreenDown(writable), true); +assert.deepStrictEqual(writable.data, CSI.kClearScreenDown); +assert.strictEqual(readline.clearScreenDown(writable, common.mustCall()), true); + +// Verify that clearScreenDown() throws on invalid callback. +assert.throws(() => { + readline.clearScreenDown(writable, null); +}, /ERR_INVALID_ARG_TYPE/); + +// Verify that clearScreenDown() does not throw on null or undefined stream. +assert.strictEqual(readline.clearScreenDown(null, common.mustCall((err) => { + assert.strictEqual(err, null); +})), true); +assert.strictEqual(readline.clearScreenDown(undefined, common.mustCall()), + true); + +writable.data = ''; +assert.strictEqual(readline.clearLine(writable, -1), true); +assert.deepStrictEqual(writable.data, CSI.kClearToLineBeginning); + +writable.data = ''; +assert.strictEqual(readline.clearLine(writable, 1), true); +assert.deepStrictEqual(writable.data, CSI.kClearToLineEnd); + +writable.data = ''; +assert.strictEqual(readline.clearLine(writable, 0), true); +assert.deepStrictEqual(writable.data, CSI.kClearLine); + +writable.data = ''; +assert.strictEqual(readline.clearLine(writable, -1, common.mustCall()), true); +assert.deepStrictEqual(writable.data, CSI.kClearToLineBeginning); + +// Verify that clearLine() throws on invalid callback. +assert.throws(() => { + readline.clearLine(writable, 0, null); +}, /ERR_INVALID_ARG_TYPE/); + +// Verify that clearLine() does not throw on null or undefined stream. +assert.strictEqual(readline.clearLine(null, 0), true); +assert.strictEqual(readline.clearLine(undefined, 0), true); +assert.strictEqual(readline.clearLine(null, 0, common.mustCall((err) => { + assert.strictEqual(err, null); +})), true); +assert.strictEqual(readline.clearLine(undefined, 0, common.mustCall()), true); + +// Nothing is written when moveCursor 0, 0 +[ + [0, 0, ''], + [1, 0, '\x1b[1C'], + [-1, 0, '\x1b[1D'], + [0, 1, '\x1b[1B'], + [0, -1, '\x1b[1A'], + [1, 1, '\x1b[1C\x1b[1B'], + [-1, 1, '\x1b[1D\x1b[1B'], + [-1, -1, '\x1b[1D\x1b[1A'], + [1, -1, '\x1b[1C\x1b[1A'], +].forEach((set) => { + writable.data = ''; + assert.strictEqual(readline.moveCursor(writable, set[0], set[1]), true); + assert.deepStrictEqual(writable.data, set[2]); + writable.data = ''; + assert.strictEqual( + readline.moveCursor(writable, set[0], set[1], common.mustCall()), + true + ); + assert.deepStrictEqual(writable.data, set[2]); +}); + +// Verify that moveCursor() throws on invalid callback. +assert.throws(() => { + readline.moveCursor(writable, 1, 1, null); +}, /ERR_INVALID_ARG_TYPE/); + +// Verify that moveCursor() does not throw on null or undefined stream. +assert.strictEqual(readline.moveCursor(null, 1, 1), true); +assert.strictEqual(readline.moveCursor(undefined, 1, 1), true); +assert.strictEqual(readline.moveCursor(null, 1, 1, common.mustCall((err) => { + assert.strictEqual(err, null); +})), true); +assert.strictEqual(readline.moveCursor(undefined, 1, 1, common.mustCall()), + true); + +// Undefined or null as stream should not throw. +assert.strictEqual(readline.cursorTo(null), true); +assert.strictEqual(readline.cursorTo(), true); +assert.strictEqual(readline.cursorTo(null, 1, 1, common.mustCall()), true); +assert.strictEqual(readline.cursorTo(undefined, 1, 1, common.mustCall((err) => { + assert.strictEqual(err, null); +})), true); + +writable.data = ''; +assert.strictEqual(readline.cursorTo(writable, 'a'), true); +assert.strictEqual(writable.data, ''); + +writable.data = ''; +assert.strictEqual(readline.cursorTo(writable, 'a', 'b'), true); +assert.strictEqual(writable.data, ''); + +writable.data = ''; +assert.throws( + () => readline.cursorTo(writable, 'a', 1), + { + name: 'TypeError', + code: 'ERR_INVALID_CURSOR_POS', + message: 'Cannot set cursor row without setting its column' + }); +assert.strictEqual(writable.data, ''); + +writable.data = ''; +assert.strictEqual(readline.cursorTo(writable, 1, 'a'), true); +assert.strictEqual(writable.data, '\x1b[2G'); + +writable.data = ''; +assert.strictEqual(readline.cursorTo(writable, 1), true); +assert.strictEqual(writable.data, '\x1b[2G'); + +writable.data = ''; +assert.strictEqual(readline.cursorTo(writable, 1, 2), true); +assert.strictEqual(writable.data, '\x1b[3;2H'); + +writable.data = ''; +assert.strictEqual(readline.cursorTo(writable, 1, 2, common.mustCall()), true); +assert.strictEqual(writable.data, '\x1b[3;2H'); + +writable.data = ''; +assert.strictEqual(readline.cursorTo(writable, 1, common.mustCall()), true); +assert.strictEqual(writable.data, '\x1b[2G'); + +// Verify that cursorTo() throws on invalid callback. +assert.throws(() => { + readline.cursorTo(writable, 1, 1, null); +}, /ERR_INVALID_ARG_TYPE/); + +// Verify that cursorTo() throws if x or y is NaN. +assert.throws(() => { + readline.cursorTo(writable, NaN); +}, /ERR_INVALID_ARG_VALUE/); + +assert.throws(() => { + readline.cursorTo(writable, 1, NaN); +}, /ERR_INVALID_ARG_VALUE/); + +assert.throws(() => { + readline.cursorTo(writable, NaN, NaN); +}, /ERR_INVALID_ARG_VALUE/); diff --git a/cli/tests/node_compat/test/parallel/test-readline-emit-keypress-events.js b/cli/tests/node_compat/test/parallel/test-readline-emit-keypress-events.js new file mode 100644 index 00000000000000..ad001d603e8282 --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-readline-emit-keypress-events.js @@ -0,0 +1,79 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +'use strict'; +// emitKeypressEvents is thoroughly tested in test-readline-keys.js. +// However, that test calls it implicitly. This is just a quick sanity check +// to verify that it works when called explicitly. + +require('../common'); +const assert = require('assert'); +const readline = require('readline'); +const PassThrough = require('stream').PassThrough; + +const expectedSequence = ['f', 'o', 'o']; +const expectedKeys = [ + { sequence: 'f', name: 'f', ctrl: false, meta: false, shift: false }, + { sequence: 'o', name: 'o', ctrl: false, meta: false, shift: false }, + { sequence: 'o', name: 'o', ctrl: false, meta: false, shift: false }, +]; + +{ + const stream = new PassThrough(); + const sequence = []; + const keys = []; + + readline.emitKeypressEvents(stream); + stream.on('keypress', (s, k) => { + sequence.push(s); + keys.push(k); + }); + stream.write('foo'); + + assert.deepStrictEqual(sequence, expectedSequence); + assert.deepStrictEqual(keys, expectedKeys); +} + +{ + const stream = new PassThrough(); + const sequence = []; + const keys = []; + + stream.on('keypress', (s, k) => { + sequence.push(s); + keys.push(k); + }); + readline.emitKeypressEvents(stream); + stream.write('foo'); + + assert.deepStrictEqual(sequence, expectedSequence); + assert.deepStrictEqual(keys, expectedKeys); +} + +{ + const stream = new PassThrough(); + const sequence = []; + const keys = []; + const keypressListener = (s, k) => { + sequence.push(s); + keys.push(k); + }; + + stream.on('keypress', keypressListener); + readline.emitKeypressEvents(stream); + stream.removeListener('keypress', keypressListener); + stream.write('foo'); + + assert.deepStrictEqual(sequence, []); + assert.deepStrictEqual(keys, []); + + stream.on('keypress', keypressListener); + stream.write('foo'); + + assert.deepStrictEqual(sequence, expectedSequence); + assert.deepStrictEqual(keys, expectedKeys); +} diff --git a/cli/tests/node_compat/test/parallel/test-readline-interface-escapecodetimeout.js b/cli/tests/node_compat/test/parallel/test-readline-interface-escapecodetimeout.js new file mode 100644 index 00000000000000..3a0da7ccb4bdf8 --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-readline-interface-escapecodetimeout.js @@ -0,0 +1,53 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +'use strict'; +require('../common'); + +// This test ensures that the escapeCodeTimeout option set correctly + +const assert = require('assert'); +const readline = require('readline'); +const EventEmitter = require('events').EventEmitter; + +class FakeInput extends EventEmitter { + resume() {} + pause() {} + write() {} + end() {} +} + +{ + const fi = new FakeInput(); + const rli = new readline.Interface({ + input: fi, + output: fi, + escapeCodeTimeout: 50 + }); + assert.strictEqual(rli.escapeCodeTimeout, 50); + rli.close(); +} + +[ + null, + {}, + NaN, + '50', +].forEach((invalidInput) => { + assert.throws(() => { + const fi = new FakeInput(); + const rli = new readline.Interface({ + input: fi, + output: fi, + escapeCodeTimeout: invalidInput + }); + rli.close(); + }, { + name: 'TypeError', + code: 'ERR_INVALID_ARG_VALUE' + }); +}); diff --git a/cli/tests/node_compat/test/parallel/test-readline-interface.js b/cli/tests/node_compat/test/parallel/test-readline-interface.js new file mode 100644 index 00000000000000..e8e48dd1ef8d9b --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-readline-interface.js @@ -0,0 +1,1217 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 16.13.0 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. + +// Flags: --expose-internals +'use strict'; +const common = require('../common'); +common.skipIfDumbTerminal(); + +const assert = require('assert'); +const readline = require('readline'); +const util = require('util'); +const { + getStringWidth, + stripVTControlCharacters +} = require('internal/util/inspect'); +const { EventEmitter, getEventListeners } = require('events'); +const { Writable, Readable } = require('stream'); + +class FakeInput extends EventEmitter { + resume() {} + pause() {} + write() {} + end() {} +} + +function isWarned(emitter) { + for (const name in emitter) { + const listeners = emitter[name]; + if (listeners.warned) return true; + } + return false; +} + +function getInterface(options) { + const fi = new FakeInput(); + const rli = new readline.Interface({ + input: fi, + output: fi, + ...options, + }); + return [rli, fi]; +} + +function assertCursorRowsAndCols(rli, rows, cols) { + const cursorPos = rli.getCursorPos(); + assert.strictEqual(cursorPos.rows, rows); + assert.strictEqual(cursorPos.cols, cols); +} + +{ + const input = new FakeInput(); + const rl = readline.Interface({ input }); + assert(rl instanceof readline.Interface); +} + +[ + undefined, + 50, + 0, + 100.5, + 5000, +].forEach((crlfDelay) => { + const [rli] = getInterface({ crlfDelay }); + assert.strictEqual(rli.crlfDelay, Math.max(crlfDelay || 100, 100)); + rli.close(); +}); + +{ + const input = new FakeInput(); + + // Constructor throws if completer is not a function or undefined + ['not an array', 123, 123n, {}, true, Symbol(), null].forEach((invalid) => { + assert.throws(() => { + readline.createInterface({ + input, + completer: invalid + }); + }, { + name: 'TypeError', + code: 'ERR_INVALID_ARG_VALUE' + }); + }); + + // Constructor throws if history is not an array + ['not an array', 123, 123n, {}, true, Symbol(), null].forEach((history) => { + assert.throws(() => { + readline.createInterface({ + input, + history, + }); + }, { + name: 'TypeError', + code: 'ERR_INVALID_ARG_TYPE' + }); + }); + + // Constructor throws if historySize is not a positive number + ['not a number', -1, NaN, {}, true, Symbol(), null].forEach((historySize) => { + assert.throws(() => { + readline.createInterface({ + input, + historySize, + }); + }, { + name: 'RangeError', + code: 'ERR_INVALID_ARG_VALUE' + }); + }); + + // Check for invalid tab sizes. + assert.throws( + () => new readline.Interface({ + input, + tabSize: 0 + }), + { + message: 'The value of "tabSize" is out of range. ' + + 'It must be >= 1 && < 4294967296. Received 0', + code: 'ERR_OUT_OF_RANGE' + } + ); + + assert.throws( + () => new readline.Interface({ + input, + tabSize: '4' + }), + { code: 'ERR_INVALID_ARG_TYPE' } + ); + + assert.throws( + () => new readline.Interface({ + input, + tabSize: 4.5 + }), + { + code: 'ERR_OUT_OF_RANGE', + message: 'The value of "tabSize" is out of range. ' + + 'It must be an integer. Received 4.5' + } + ); +} + +// Sending a single character with no newline +{ + const fi = new FakeInput(); + const rli = new readline.Interface(fi, {}); + rli.on('line', common.mustNotCall()); + fi.emit('data', 'a'); + rli.close(); +} + +// Sending multiple newlines at once that does not end with a new line and a +// `end` event(last line is). \r should behave like \n when alone. +{ + const [rli, fi] = getInterface({ terminal: true }); + const expectedLines = ['foo', 'bar', 'baz', 'bat']; + rli.on('line', common.mustCall((line) => { + assert.strictEqual(line, expectedLines.shift()); + }, expectedLines.length - 1)); + fi.emit('data', expectedLines.join('\r')); + rli.close(); +} + +// \r at start of input should output blank line +{ + const [rli, fi] = getInterface({ terminal: true }); + const expectedLines = ['', 'foo' ]; + rli.on('line', common.mustCall((line) => { + assert.strictEqual(line, expectedLines.shift()); + }, expectedLines.length)); + fi.emit('data', '\rfoo\r'); + rli.close(); +} + +// \t does not become part of the input when there is a completer function +{ + const completer = (line) => [[], line]; + const [rli, fi] = getInterface({ terminal: true, completer }); + rli.on('line', common.mustCall((line) => { + assert.strictEqual(line, 'foo'); + })); + for (const character of '\tfo\to\t') { + fi.emit('data', character); + } + fi.emit('data', '\n'); + rli.close(); +} + +// \t when there is no completer function should behave like an ordinary +// character +{ + const [rli, fi] = getInterface({ terminal: true }); + rli.on('line', common.mustCall((line) => { + assert.strictEqual(line, '\t'); + })); + fi.emit('data', '\t'); + fi.emit('data', '\n'); + rli.close(); +} + +// Adding history lines should emit the history event with +// the history array +{ + const [rli, fi] = getInterface({ terminal: true }); + const expectedLines = ['foo', 'bar', 'baz', 'bat']; + rli.on('history', common.mustCall((history) => { + const expectedHistory = expectedLines.slice(0, history.length).reverse(); + assert.deepStrictEqual(history, expectedHistory); + }, expectedLines.length)); + for (const line of expectedLines) { + fi.emit('data', `${line}\n`); + } + rli.close(); +} + +// Altering the history array in the listener should not alter +// the line being processed +{ + const [rli, fi] = getInterface({ terminal: true }); + const expectedLine = 'foo'; + rli.on('history', common.mustCall((history) => { + assert.strictEqual(history[0], expectedLine); + history.shift(); + })); + rli.on('line', common.mustCall((line) => { + assert.strictEqual(line, expectedLine); + assert.strictEqual(rli.history.length, 0); + })); + fi.emit('data', `${expectedLine}\n`); + rli.close(); +} + +// Duplicate lines are removed from history when +// `options.removeHistoryDuplicates` is `true` +{ + const [rli, fi] = getInterface({ + terminal: true, + removeHistoryDuplicates: true + }); + const expectedLines = ['foo', 'bar', 'baz', 'bar', 'bat', 'bat']; + // ['foo', 'baz', 'bar', bat']; + let callCount = 0; + rli.on('line', (line) => { + assert.strictEqual(line, expectedLines[callCount]); + callCount++; + }); + fi.emit('data', `${expectedLines.join('\n')}\n`); + assert.strictEqual(callCount, expectedLines.length); + fi.emit('keypress', '.', { name: 'up' }); // 'bat' + assert.strictEqual(rli.line, expectedLines[--callCount]); + fi.emit('keypress', '.', { name: 'up' }); // 'bar' + assert.notStrictEqual(rli.line, expectedLines[--callCount]); + assert.strictEqual(rli.line, expectedLines[--callCount]); + fi.emit('keypress', '.', { name: 'up' }); // 'baz' + assert.strictEqual(rli.line, expectedLines[--callCount]); + fi.emit('keypress', '.', { name: 'up' }); // 'foo' + assert.notStrictEqual(rli.line, expectedLines[--callCount]); + assert.strictEqual(rli.line, expectedLines[--callCount]); + assert.strictEqual(callCount, 0); + fi.emit('keypress', '.', { name: 'down' }); // 'baz' + assert.strictEqual(rli.line, 'baz'); + assert.strictEqual(rli.historyIndex, 2); + fi.emit('keypress', '.', { name: 'n', ctrl: true }); // 'bar' + assert.strictEqual(rli.line, 'bar'); + assert.strictEqual(rli.historyIndex, 1); + fi.emit('keypress', '.', { name: 'n', ctrl: true }); + assert.strictEqual(rli.line, 'bat'); + assert.strictEqual(rli.historyIndex, 0); + // Activate the substring history search. + fi.emit('keypress', '.', { name: 'down' }); // 'bat' + assert.strictEqual(rli.line, 'bat'); + assert.strictEqual(rli.historyIndex, -1); + // Deactivate substring history search. + fi.emit('keypress', '.', { name: 'backspace' }); // 'ba' + assert.strictEqual(rli.historyIndex, -1); + assert.strictEqual(rli.line, 'ba'); + // Activate the substring history search. + fi.emit('keypress', '.', { name: 'down' }); // 'ba' + assert.strictEqual(rli.historyIndex, -1); + assert.strictEqual(rli.line, 'ba'); + fi.emit('keypress', '.', { name: 'down' }); // 'ba' + assert.strictEqual(rli.historyIndex, -1); + assert.strictEqual(rli.line, 'ba'); + fi.emit('keypress', '.', { name: 'up' }); // 'bat' + assert.strictEqual(rli.historyIndex, 0); + assert.strictEqual(rli.line, 'bat'); + fi.emit('keypress', '.', { name: 'up' }); // 'bar' + assert.strictEqual(rli.historyIndex, 1); + assert.strictEqual(rli.line, 'bar'); + fi.emit('keypress', '.', { name: 'up' }); // 'baz' + assert.strictEqual(rli.historyIndex, 2); + assert.strictEqual(rli.line, 'baz'); + fi.emit('keypress', '.', { name: 'up' }); // 'ba' + assert.strictEqual(rli.historyIndex, 4); + assert.strictEqual(rli.line, 'ba'); + fi.emit('keypress', '.', { name: 'up' }); // 'ba' + assert.strictEqual(rli.historyIndex, 4); + assert.strictEqual(rli.line, 'ba'); + // Deactivate substring history search and reset history index. + fi.emit('keypress', '.', { name: 'right' }); // 'ba' + assert.strictEqual(rli.historyIndex, -1); + assert.strictEqual(rli.line, 'ba'); + // Substring history search activated. + fi.emit('keypress', '.', { name: 'up' }); // 'ba' + assert.strictEqual(rli.historyIndex, 0); + assert.strictEqual(rli.line, 'bat'); + rli.close(); +} + +// Duplicate lines are not removed from history when +// `options.removeHistoryDuplicates` is `false` +{ + const [rli, fi] = getInterface({ + terminal: true, + removeHistoryDuplicates: false + }); + const expectedLines = ['foo', 'bar', 'baz', 'bar', 'bat', 'bat']; + let callCount = 0; + rli.on('line', (line) => { + assert.strictEqual(line, expectedLines[callCount]); + callCount++; + }); + fi.emit('data', `${expectedLines.join('\n')}\n`); + assert.strictEqual(callCount, expectedLines.length); + fi.emit('keypress', '.', { name: 'up' }); // 'bat' + assert.strictEqual(rli.line, expectedLines[--callCount]); + fi.emit('keypress', '.', { name: 'up' }); // 'bar' + assert.notStrictEqual(rli.line, expectedLines[--callCount]); + assert.strictEqual(rli.line, expectedLines[--callCount]); + fi.emit('keypress', '.', { name: 'up' }); // 'baz' + assert.strictEqual(rli.line, expectedLines[--callCount]); + fi.emit('keypress', '.', { name: 'up' }); // 'bar' + assert.strictEqual(rli.line, expectedLines[--callCount]); + fi.emit('keypress', '.', { name: 'up' }); // 'foo' + assert.strictEqual(rli.line, expectedLines[--callCount]); + assert.strictEqual(callCount, 0); + rli.close(); +} + +// Regression test for repl freeze, #1968: +// check that nothing fails if 'keypress' event throws. +{ + const [rli, fi] = getInterface({ terminal: true }); + const keys = []; + const err = new Error('bad thing happened'); + fi.on('keypress', (key) => { + keys.push(key); + if (key === 'X') { + throw err; + } + }); + assert.throws( + () => fi.emit('data', 'fooX'), + (e) => { + assert.strictEqual(e, err); + return true; + } + ); + fi.emit('data', 'bar'); + assert.strictEqual(keys.join(''), 'fooXbar'); + rli.close(); +} + +// History is bound +{ + const [rli, fi] = getInterface({ terminal: true, historySize: 2 }); + const lines = ['line 1', 'line 2', 'line 3']; + fi.emit('data', lines.join('\n') + '\n'); + assert.strictEqual(rli.history.length, 2); + assert.strictEqual(rli.history[0], 'line 3'); + assert.strictEqual(rli.history[1], 'line 2'); +} + +// Question +{ + const [rli] = getInterface({ terminal: true }); + const expectedLines = ['foo']; + rli.question(expectedLines[0], () => rli.close()); + assertCursorRowsAndCols(rli, 0, expectedLines[0].length); + rli.close(); +} + +// Sending a multi-line question +{ + const [rli] = getInterface({ terminal: true }); + const expectedLines = ['foo', 'bar']; + rli.question(expectedLines.join('\n'), () => rli.close()); + assertCursorRowsAndCols( + rli, expectedLines.length - 1, expectedLines.slice(-1)[0].length); + rli.close(); +} + +{ + // Beginning and end of line + const [rli, fi] = getInterface({ terminal: true, prompt: '' }); + fi.emit('data', 'the quick brown fox'); + fi.emit('keypress', '.', { ctrl: true, name: 'a' }); + assertCursorRowsAndCols(rli, 0, 0); + fi.emit('keypress', '.', { ctrl: true, name: 'e' }); + assertCursorRowsAndCols(rli, 0, 19); + rli.close(); +} + +{ + // Back and Forward one character + const [rli, fi] = getInterface({ terminal: true, prompt: '' }); + fi.emit('data', 'the quick brown fox'); + assertCursorRowsAndCols(rli, 0, 19); + + // Back one character + fi.emit('keypress', '.', { ctrl: true, name: 'b' }); + assertCursorRowsAndCols(rli, 0, 18); + // Back one character + fi.emit('keypress', '.', { ctrl: true, name: 'b' }); + assertCursorRowsAndCols(rli, 0, 17); + // Forward one character + fi.emit('keypress', '.', { ctrl: true, name: 'f' }); + assertCursorRowsAndCols(rli, 0, 18); + // Forward one character + fi.emit('keypress', '.', { ctrl: true, name: 'f' }); + assertCursorRowsAndCols(rli, 0, 19); + rli.close(); +} + +// Back and Forward one astral character +{ + const [rli, fi] = getInterface({ terminal: true, prompt: '' }); + fi.emit('data', '💻'); + + // Move left one character/code point + fi.emit('keypress', '.', { name: 'left' }); + assertCursorRowsAndCols(rli, 0, 0); + + // Move right one character/code point + fi.emit('keypress', '.', { name: 'right' }); + assertCursorRowsAndCols(rli, 0, 2); + + rli.on('line', common.mustCall((line) => { + assert.strictEqual(line, '💻'); + })); + fi.emit('data', '\n'); + rli.close(); +} + +// Two astral characters left +{ + const [rli, fi] = getInterface({ terminal: true, prompt: '' }); + fi.emit('data', '💻'); + + // Move left one character/code point + fi.emit('keypress', '.', { name: 'left' }); + assertCursorRowsAndCols(rli, 0, 0); + + fi.emit('data', '🐕'); + assertCursorRowsAndCols(rli, 0, 2); + + rli.on('line', common.mustCall((line) => { + assert.strictEqual(line, '🐕💻'); + })); + fi.emit('data', '\n'); + rli.close(); +} + +// Two astral characters right +{ + const [rli, fi] = getInterface({ terminal: true, prompt: '' }); + fi.emit('data', '💻'); + + // Move left one character/code point + fi.emit('keypress', '.', { name: 'right' }); + assertCursorRowsAndCols(rli, 0, 2); + + fi.emit('data', '🐕'); + assertCursorRowsAndCols(rli, 0, 4); + + rli.on('line', common.mustCall((line) => { + assert.strictEqual(line, '💻🐕'); + })); + fi.emit('data', '\n'); + rli.close(); +} + +{ + // `wordLeft` and `wordRight` + const [rli, fi] = getInterface({ terminal: true, prompt: '' }); + fi.emit('data', 'the quick brown fox'); + fi.emit('keypress', '.', { ctrl: true, name: 'left' }); + assertCursorRowsAndCols(rli, 0, 16); + fi.emit('keypress', '.', { meta: true, name: 'b' }); + assertCursorRowsAndCols(rli, 0, 10); + fi.emit('keypress', '.', { ctrl: true, name: 'right' }); + assertCursorRowsAndCols(rli, 0, 16); + fi.emit('keypress', '.', { meta: true, name: 'f' }); + assertCursorRowsAndCols(rli, 0, 19); + rli.close(); +} + +// `deleteWordLeft` +[ + { ctrl: true, name: 'w' }, + { ctrl: true, name: 'backspace' }, + { meta: true, name: 'backspace' }, +].forEach((deleteWordLeftKey) => { + let [rli, fi] = getInterface({ terminal: true, prompt: '' }); + fi.emit('data', 'the quick brown fox'); + fi.emit('keypress', '.', { ctrl: true, name: 'left' }); + rli.on('line', common.mustCall((line) => { + assert.strictEqual(line, 'the quick fox'); + })); + fi.emit('keypress', '.', deleteWordLeftKey); + fi.emit('data', '\n'); + rli.close(); + + // No effect if pressed at beginning of line + [rli, fi] = getInterface({ terminal: true, prompt: '' }); + fi.emit('data', 'the quick brown fox'); + fi.emit('keypress', '.', { ctrl: true, name: 'a' }); + rli.on('line', common.mustCall((line) => { + assert.strictEqual(line, 'the quick brown fox'); + })); + fi.emit('keypress', '.', deleteWordLeftKey); + fi.emit('data', '\n'); + rli.close(); +}); + +// `deleteWordRight` +[ + { ctrl: true, name: 'delete' }, + { meta: true, name: 'delete' }, + { meta: true, name: 'd' }, +].forEach((deleteWordRightKey) => { + let [rli, fi] = getInterface({ terminal: true, prompt: '' }); + fi.emit('data', 'the quick brown fox'); + fi.emit('keypress', '.', { ctrl: true, name: 'left' }); + fi.emit('keypress', '.', { ctrl: true, name: 'left' }); + rli.on('line', common.mustCall((line) => { + assert.strictEqual(line, 'the quick fox'); + })); + fi.emit('keypress', '.', deleteWordRightKey); + fi.emit('data', '\n'); + rli.close(); + + // No effect if pressed at end of line + [rli, fi] = getInterface({ terminal: true, prompt: '' }); + fi.emit('data', 'the quick brown fox'); + rli.on('line', common.mustCall((line) => { + assert.strictEqual(line, 'the quick brown fox'); + })); + fi.emit('keypress', '.', deleteWordRightKey); + fi.emit('data', '\n'); + rli.close(); +}); + +// deleteLeft +{ + const [rli, fi] = getInterface({ terminal: true, prompt: '' }); + fi.emit('data', 'the quick brown fox'); + assertCursorRowsAndCols(rli, 0, 19); + + // Delete left character + fi.emit('keypress', '.', { ctrl: true, name: 'h' }); + assertCursorRowsAndCols(rli, 0, 18); + rli.on('line', common.mustCall((line) => { + assert.strictEqual(line, 'the quick brown fo'); + })); + fi.emit('data', '\n'); + rli.close(); +} + +// deleteLeft astral character +{ + const [rli, fi] = getInterface({ terminal: true, prompt: '' }); + fi.emit('data', '💻'); + assertCursorRowsAndCols(rli, 0, 2); + // Delete left character + fi.emit('keypress', '.', { ctrl: true, name: 'h' }); + assertCursorRowsAndCols(rli, 0, 0); + rli.on('line', common.mustCall((line) => { + assert.strictEqual(line, ''); + })); + fi.emit('data', '\n'); + rli.close(); +} + +// deleteRight +{ + const [rli, fi] = getInterface({ terminal: true, prompt: '' }); + fi.emit('data', 'the quick brown fox'); + + // Go to the start of the line + fi.emit('keypress', '.', { ctrl: true, name: 'a' }); + assertCursorRowsAndCols(rli, 0, 0); + + // Delete right character + fi.emit('keypress', '.', { ctrl: true, name: 'd' }); + assertCursorRowsAndCols(rli, 0, 0); + rli.on('line', common.mustCall((line) => { + assert.strictEqual(line, 'he quick brown fox'); + })); + fi.emit('data', '\n'); + rli.close(); +} + +// deleteRight astral character +{ + const [rli, fi] = getInterface({ terminal: true, prompt: '' }); + fi.emit('data', '💻'); + + // Go to the start of the line + fi.emit('keypress', '.', { ctrl: true, name: 'a' }); + assertCursorRowsAndCols(rli, 0, 0); + + // Delete right character + fi.emit('keypress', '.', { ctrl: true, name: 'd' }); + assertCursorRowsAndCols(rli, 0, 0); + rli.on('line', common.mustCall((line) => { + assert.strictEqual(line, ''); + })); + fi.emit('data', '\n'); + rli.close(); +} + +// deleteLineLeft +{ + const [rli, fi] = getInterface({ terminal: true, prompt: '' }); + fi.emit('data', 'the quick brown fox'); + assertCursorRowsAndCols(rli, 0, 19); + + // Delete from current to start of line + fi.emit('keypress', '.', { ctrl: true, shift: true, name: 'backspace' }); + assertCursorRowsAndCols(rli, 0, 0); + rli.on('line', common.mustCall((line) => { + assert.strictEqual(line, ''); + })); + fi.emit('data', '\n'); + rli.close(); +} + +// deleteLineRight +{ + const [rli, fi] = getInterface({ terminal: true, prompt: '' }); + fi.emit('data', 'the quick brown fox'); + + // Go to the start of the line + fi.emit('keypress', '.', { ctrl: true, name: 'a' }); + assertCursorRowsAndCols(rli, 0, 0); + + // Delete from current to end of line + fi.emit('keypress', '.', { ctrl: true, shift: true, name: 'delete' }); + assertCursorRowsAndCols(rli, 0, 0); + rli.on('line', common.mustCall((line) => { + assert.strictEqual(line, ''); + })); + fi.emit('data', '\n'); + rli.close(); +} + +// Close readline interface +{ + const [rli, fi] = getInterface({ terminal: true, prompt: '' }); + fi.emit('keypress', '.', { ctrl: true, name: 'c' }); + assert(rli.closed); +} + +// Multi-line input cursor position +{ + const [rli, fi] = getInterface({ terminal: true, prompt: '' }); + fi.columns = 10; + fi.emit('data', 'multi-line text'); + assertCursorRowsAndCols(rli, 1, 5); + rli.close(); +} + +// Multi-line input cursor position and long tabs +{ + const [rli, fi] = getInterface({ tabSize: 16, terminal: true, prompt: '' }); + fi.columns = 10; + fi.emit('data', 'multi-line\ttext \t'); + assert.strictEqual(rli.cursor, 17); + assertCursorRowsAndCols(rli, 3, 2); + rli.close(); +} + +// Check for the default tab size. +{ + const [rli, fi] = getInterface({ terminal: true, prompt: '' }); + fi.emit('data', 'the quick\tbrown\tfox'); + assert.strictEqual(rli.cursor, 19); + // The first tab is 7 spaces long, the second one 3 spaces. + assertCursorRowsAndCols(rli, 0, 27); +} + +// Multi-line prompt cursor position +{ + const [rli, fi] = getInterface({ + terminal: true, + prompt: '\nfilledline\nwraping text\n> ' + }); + fi.columns = 10; + fi.emit('data', 't'); + assertCursorRowsAndCols(rli, 4, 3); + rli.close(); +} + +// Clear the whole screen +{ + const [rli, fi] = getInterface({ terminal: true, prompt: '' }); + const lines = ['line 1', 'line 2', 'line 3']; + fi.emit('data', lines.join('\n')); + fi.emit('keypress', '.', { ctrl: true, name: 'l' }); + assertCursorRowsAndCols(rli, 0, 6); + rli.on('line', common.mustCall((line) => { + assert.strictEqual(line, 'line 3'); + })); + fi.emit('data', '\n'); + rli.close(); +} + +// Wide characters should be treated as two columns. +assert.strictEqual(getStringWidth('a'), 1); +assert.strictEqual(getStringWidth('あ'), 2); +assert.strictEqual(getStringWidth('谢'), 2); +assert.strictEqual(getStringWidth('고'), 2); +assert.strictEqual(getStringWidth(String.fromCodePoint(0x1f251)), 2); +assert.strictEqual(getStringWidth('abcde'), 5); +assert.strictEqual(getStringWidth('古池や'), 6); +assert.strictEqual(getStringWidth('ノード.js'), 9); +assert.strictEqual(getStringWidth('你好'), 4); +assert.strictEqual(getStringWidth('안녕하세요'), 10); +assert.strictEqual(getStringWidth('A\ud83c\ude00BC'), 5); +assert.strictEqual(getStringWidth('👨‍👩‍👦‍👦'), 8); +assert.strictEqual(getStringWidth('🐕𐐷あ💻😀'), 9); +// TODO(BridgeAR): This should have a width of 4. +assert.strictEqual(getStringWidth('⓬⓪'), 2); +assert.strictEqual(getStringWidth('\u0301\u200D\u200E'), 0); + +// Check if vt control chars are stripped +assert.strictEqual(stripVTControlCharacters('\u001b[31m> \u001b[39m'), '> '); +assert.strictEqual( + stripVTControlCharacters('\u001b[31m> \u001b[39m> '), + '> > ' +); +assert.strictEqual(stripVTControlCharacters('\u001b[31m\u001b[39m'), ''); +assert.strictEqual(stripVTControlCharacters('> '), '> '); +assert.strictEqual(getStringWidth('\u001b[31m> \u001b[39m'), 2); +assert.strictEqual(getStringWidth('\u001b[31m> \u001b[39m> '), 4); +assert.strictEqual(getStringWidth('\u001b[31m\u001b[39m'), 0); +assert.strictEqual(getStringWidth('> '), 2); + +// FIXME(bartlomieju): this causes hang +// Check EventEmitter memory leak +// for (let i = 0; i < 12; i++) { +// const rl = readline.createInterface({ +// input: process.stdin, +// output: process.stdout +// }); +// rl.close(); +// assert.strictEqual(isWarned(process.stdin._events), false); +// assert.strictEqual(isWarned(process.stdout._events), false); +// } + +[true, false].forEach((terminal) => { + // Disable history + { + const [rli, fi] = getInterface({ terminal, historySize: 0 }); + assert.strictEqual(rli.historySize, 0); + + fi.emit('data', 'asdf\n'); + assert.deepStrictEqual(rli.history, []); + rli.close(); + } + + // Default history size 30 + { + const [rli, fi] = getInterface({ terminal }); + assert.strictEqual(rli.historySize, 30); + + fi.emit('data', 'asdf\n'); + assert.deepStrictEqual(rli.history, terminal ? ['asdf'] : []); + rli.close(); + } + + // Sending a full line + { + const [rli, fi] = getInterface({ terminal }); + rli.on('line', common.mustCall((line) => { + assert.strictEqual(line, 'asdf'); + })); + fi.emit('data', 'asdf\n'); + } + + // Sending a blank line + { + const [rli, fi] = getInterface({ terminal }); + rli.on('line', common.mustCall((line) => { + assert.strictEqual(line, ''); + })); + fi.emit('data', '\n'); + } + + // Sending a single character with no newline and then a newline + { + const [rli, fi] = getInterface({ terminal }); + let called = false; + rli.on('line', (line) => { + called = true; + assert.strictEqual(line, 'a'); + }); + fi.emit('data', 'a'); + assert.ok(!called); + fi.emit('data', '\n'); + assert.ok(called); + rli.close(); + } + + // Sending multiple newlines at once + { + const [rli, fi] = getInterface({ terminal }); + const expectedLines = ['foo', 'bar', 'baz']; + rli.on('line', common.mustCall((line) => { + assert.strictEqual(line, expectedLines.shift()); + }, expectedLines.length)); + fi.emit('data', `${expectedLines.join('\n')}\n`); + rli.close(); + } + + // Sending multiple newlines at once that does not end with a new line + { + const [rli, fi] = getInterface({ terminal }); + const expectedLines = ['foo', 'bar', 'baz', 'bat']; + rli.on('line', common.mustCall((line) => { + assert.strictEqual(line, expectedLines.shift()); + }, expectedLines.length - 1)); + fi.emit('data', expectedLines.join('\n')); + rli.close(); + } + + // Sending multiple newlines at once that does not end with a new(empty) + // line and a `end` event + { + const [rli, fi] = getInterface({ terminal }); + const expectedLines = ['foo', 'bar', 'baz', '']; + rli.on('line', common.mustCall((line) => { + assert.strictEqual(line, expectedLines.shift()); + }, expectedLines.length - 1)); + rli.on('close', common.mustCall()); + fi.emit('data', expectedLines.join('\n')); + fi.emit('end'); + rli.close(); + } + + // Sending a multi-byte utf8 char over multiple writes + { + const buf = Buffer.from('☮', 'utf8'); + const [rli, fi] = getInterface({ terminal }); + let callCount = 0; + rli.on('line', (line) => { + callCount++; + assert.strictEqual(line, buf.toString('utf8')); + }); + for (const i of buf) { + fi.emit('data', Buffer.from([i])); + } + assert.strictEqual(callCount, 0); + fi.emit('data', '\n'); + assert.strictEqual(callCount, 1); + rli.close(); + } + + // Calling readline without `new` + { + const [rli, fi] = getInterface({ terminal }); + rli.on('line', common.mustCall((line) => { + assert.strictEqual(line, 'asdf'); + })); + fi.emit('data', 'asdf\n'); + rli.close(); + } + + // Calling the question callback + { + const [rli] = getInterface({ terminal }); + rli.question('foo?', common.mustCall((answer) => { + assert.strictEqual(answer, 'bar'); + })); + rli.write('bar\n'); + rli.close(); + } + + // Calling the question multiple times + { + const [rli] = getInterface({ terminal }); + rli.question('foo?', common.mustCall((answer) => { + assert.strictEqual(answer, 'baz'); + })); + rli.question('bar?', common.mustNotCall(() => { + })); + rli.write('baz\n'); + rli.close(); + } + + // Calling the promisified question + { + const [rli] = getInterface({ terminal }); + const question = util.promisify(rli.question).bind(rli); + question('foo?') + .then(common.mustCall((answer) => { + assert.strictEqual(answer, 'bar'); + })); + rli.write('bar\n'); + rli.close(); + } + + // Aborting a question + { + const ac = new AbortController(); + const signal = ac.signal; + const [rli] = getInterface({ terminal }); + rli.on('line', common.mustCall((line) => { + assert.strictEqual(line, 'bar'); + })); + rli.question('hello?', { signal }, common.mustNotCall()); + ac.abort(); + rli.write('bar\n'); + rli.close(); + } + + // Aborting a promisified question + { + const ac = new AbortController(); + const signal = ac.signal; + const [rli] = getInterface({ terminal }); + const question = util.promisify(rli.question).bind(rli); + rli.on('line', common.mustCall((line) => { + assert.strictEqual(line, 'bar'); + })); + question('hello?', { signal }) + .then(common.mustNotCall()) + .catch(common.mustCall((error) => { + assert.strictEqual(error.name, 'AbortError'); + })); + ac.abort(); + rli.write('bar\n'); + rli.close(); + } + + // pre-aborted signal + { + const signal = AbortSignal.abort(); + const [rli] = getInterface({ terminal }); + rli.pause(); + rli.on('resume', common.mustNotCall()); + rli.question('hello?', { signal }, common.mustNotCall()); + rli.close(); + } + + // pre-aborted signal promisified question + { + const signal = AbortSignal.abort(); + const [rli] = getInterface({ terminal }); + const question = util.promisify(rli.question).bind(rli); + rli.on('resume', common.mustNotCall()); + rli.pause(); + question('hello?', { signal }) + .then(common.mustNotCall()) + .catch(common.mustCall((error) => { + assert.strictEqual(error.name, 'AbortError'); + })); + rli.close(); + } + + // Can create a new readline Interface with a null output argument + { + const [rli, fi] = getInterface({ output: null, terminal }); + rli.on('line', common.mustCall((line) => { + assert.strictEqual(line, 'asdf'); + })); + fi.emit('data', 'asdf\n'); + + rli.setPrompt('ddd> '); + rli.prompt(); + rli.write("really shouldn't be seeing this"); + rli.question('What do you think of node.js? ', (answer) => { + console.log('Thank you for your valuable feedback:', answer); + rli.close(); + }); + } + + // Calling the getPrompt method + { + const expectedPrompts = ['$ ', '> ']; + const [rli] = getInterface({ terminal }); + for (const prompt of expectedPrompts) { + rli.setPrompt(prompt); + assert.strictEqual(rli.getPrompt(), prompt); + } + } + + { + const expected = terminal ? + ['\u001b[1G', '\u001b[0J', '$ ', '\u001b[3G'] : + ['$ ']; + + const output = new Writable({ + write: common.mustCall((chunk, enc, cb) => { + assert.strictEqual(chunk.toString(), expected.shift()); + cb(); + rl.close(); + }, expected.length) + }); + + const rl = readline.createInterface({ + input: new Readable({ read: common.mustCall() }), + output, + prompt: '$ ', + terminal + }); + + rl.prompt(); + + assert.strictEqual(rl.getPrompt(), '$ '); + } + + { + const fi = new FakeInput(); + assert.deepStrictEqual(fi.listeners(terminal ? 'keypress' : 'data'), []); + } + + // Emit two line events when the delay + // between \r and \n exceeds crlfDelay + { + const crlfDelay = 200; + const [rli, fi] = getInterface({ terminal, crlfDelay }); + let callCount = 0; + rli.on('line', () => { + callCount++; + }); + fi.emit('data', '\r'); + setTimeout(common.mustCall(() => { + fi.emit('data', '\n'); + assert.strictEqual(callCount, 2); + rli.close(); + }), crlfDelay + 10); + } + + // For the purposes of the following tests, we do not care about the exact + // value of crlfDelay, only that the behaviour conforms to what's expected. + // Setting it to Infinity allows the test to succeed even under extreme + // CPU stress. + const crlfDelay = Infinity; + + // Set crlfDelay to `Infinity` is allowed + { + const delay = 200; + const [rli, fi] = getInterface({ terminal, crlfDelay }); + let callCount = 0; + rli.on('line', () => { + callCount++; + }); + fi.emit('data', '\r'); + setTimeout(common.mustCall(() => { + fi.emit('data', '\n'); + assert.strictEqual(callCount, 1); + rli.close(); + }), delay); + } + + // Sending multiple newlines at once that does not end with a new line + // and a `end` event(last line is) + + // \r\n should emit one line event, not two + { + const [rli, fi] = getInterface({ terminal, crlfDelay }); + const expectedLines = ['foo', 'bar', 'baz', 'bat']; + rli.on('line', common.mustCall((line) => { + assert.strictEqual(line, expectedLines.shift()); + }, expectedLines.length - 1)); + fi.emit('data', expectedLines.join('\r\n')); + rli.close(); + } + + // \r\n should emit one line event when split across multiple writes. + { + const [rli, fi] = getInterface({ terminal, crlfDelay }); + const expectedLines = ['foo', 'bar', 'baz', 'bat']; + let callCount = 0; + rli.on('line', common.mustCall((line) => { + assert.strictEqual(line, expectedLines[callCount]); + callCount++; + }, expectedLines.length)); + expectedLines.forEach((line) => { + fi.emit('data', `${line}\r`); + fi.emit('data', '\n'); + }); + rli.close(); + } + + // Emit one line event when the delay between \r and \n is + // over the default crlfDelay but within the setting value. + { + const delay = 125; + const [rli, fi] = getInterface({ terminal, crlfDelay }); + let callCount = 0; + rli.on('line', () => callCount++); + fi.emit('data', '\r'); + setTimeout(common.mustCall(() => { + fi.emit('data', '\n'); + assert.strictEqual(callCount, 1); + rli.close(); + }), delay); + } +}); + +// Ensure that the _wordLeft method works even for large input +{ + const input = new Readable({ + read() { + this.push('\x1B[1;5D'); // CTRL + Left + this.push(null); + }, + }); + const output = new Writable({ + write: common.mustCall((data, encoding, cb) => { + assert.strictEqual(rl.cursor, rl.line.length - 1); + cb(); + }), + }); + const rl = new readline.createInterface({ + input, + output, + terminal: true, + }); + rl.line = `a${' '.repeat(1e6)}a`; + rl.cursor = rl.line.length; +} + +// FIXME(bartlomieju): these tests depend on "event_target" module +// { +// const fi = new FakeInput(); +// const signal = AbortSignal.abort(); + +// const rl = readline.createInterface({ +// input: fi, +// output: fi, +// signal, +// }); +// rl.on('close', common.mustCall()); +// assert.strictEqual(getEventListeners(signal, 'abort').length, 0); +// } + +// { +// const fi = new FakeInput(); +// const ac = new AbortController(); +// const { signal } = ac; +// const rl = readline.createInterface({ +// input: fi, +// output: fi, +// signal, +// }); +// assert.strictEqual(getEventListeners(signal, 'abort').length, 1); +// rl.on('close', common.mustCall()); +// ac.abort(); +// assert.strictEqual(getEventListeners(signal, 'abort').length, 0); +// } + +// { +// const fi = new FakeInput(); +// const ac = new AbortController(); +// const { signal } = ac; +// const rl = readline.createInterface({ +// input: fi, +// output: fi, +// signal, +// }); +// assert.strictEqual(getEventListeners(signal, 'abort').length, 1); +// rl.close(); +// assert.strictEqual(getEventListeners(signal, 'abort').length, 0); +// } + +{ + // Constructor throws if signal is not an abort signal + assert.throws(() => { + readline.createInterface({ + input: new FakeInput(), + signal: {}, + }); + }, { + name: 'TypeError', + code: 'ERR_INVALID_ARG_TYPE' + }); +} diff --git a/cli/tests/node_compat/test/parallel/test-readline-keys.js b/cli/tests/node_compat/test/parallel/test-readline-keys.js new file mode 100644 index 00000000000000..e6c2e4e77822b3 --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-readline-keys.js @@ -0,0 +1,351 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +'use strict'; +const common = require('../common'); +const PassThrough = require('stream').PassThrough; +const assert = require('assert'); +const Interface = require('readline').Interface; + +class FakeInput extends PassThrough {} + +function extend(k) { + return Object.assign({ ctrl: false, meta: false, shift: false }, k); +} + + +const fi = new FakeInput(); +const fo = new FakeInput(); +new Interface({ input: fi, output: fo, terminal: true }); + +let keys = []; +fi.on('keypress', (s, k) => { + keys.push(k); +}); + + +function addTest(sequences, expectedKeys) { + if (!Array.isArray(sequences)) { + sequences = [ sequences ]; + } + + if (!Array.isArray(expectedKeys)) { + expectedKeys = [ expectedKeys ]; + } + + expectedKeys = expectedKeys.map(extend); + + keys = []; + + sequences.forEach((sequence) => { + fi.write(sequence); + }); + assert.deepStrictEqual(keys, expectedKeys); +} + +// Simulate key interval test cases +// Returns a function that takes `next` test case and returns a thunk +// that can be called to run tests in sequence +// e.g. +// addKeyIntervalTest(..) +// (addKeyIntervalTest(..) +// (addKeyIntervalTest(..)(noop)))() +// where noop is a terminal function(() => {}). + +const addKeyIntervalTest = (sequences, expectedKeys, interval = 550, + assertDelay = 550) => { + const fn = common.mustCall((next) => () => { + + if (!Array.isArray(sequences)) { + sequences = [ sequences ]; + } + + if (!Array.isArray(expectedKeys)) { + expectedKeys = [ expectedKeys ]; + } + + expectedKeys = expectedKeys.map(extend); + + const keys = []; + fi.on('keypress', (s, k) => keys.push(k)); + + const emitKeys = ([head, ...tail]) => { + if (head) { + fi.write(head); + setTimeout(() => emitKeys(tail), interval); + } else { + setTimeout(() => { + next(); + assert.deepStrictEqual(keys, expectedKeys); + }, assertDelay); + } + }; + emitKeys(sequences); + }); + return fn; +}; + +// Regular alphanumerics +addTest('io.JS', [ + { name: 'i', sequence: 'i' }, + { name: 'o', sequence: 'o' }, + { name: undefined, sequence: '.' }, + { name: 'j', sequence: 'J', shift: true }, + { name: 's', sequence: 'S', shift: true }, +]); + +// Named characters +addTest('\n\r\t\x1b\n\x1b\r\x1b\t', [ + { name: 'enter', sequence: '\n' }, + { name: 'return', sequence: '\r' }, + { name: 'tab', sequence: '\t' }, + { name: 'enter', sequence: '\x1b\n', meta: true }, + { name: 'return', sequence: '\x1b\r', meta: true }, + { name: 'tab', sequence: '\x1b\t', meta: true }, +]); + +// Space and backspace +addTest('\b\x7f\x1b\b\x1b\x7f\x1b\x1b \x1b ', [ + { name: 'backspace', sequence: '\b' }, + { name: 'backspace', sequence: '\x7f' }, + { name: 'backspace', sequence: '\x1b\b', meta: true }, + { name: 'backspace', sequence: '\x1b\x7f', meta: true }, + { name: 'space', sequence: '\x1b\x1b ', meta: true }, + { name: 'space', sequence: ' ' }, + { name: 'space', sequence: '\x1b ', meta: true }, +]); + +// Escape key +addTest('\x1b\x1b\x1b', [ + { name: 'escape', sequence: '\x1b\x1b\x1b', meta: true }, +]); + +// Escape sequence +addTest('\x1b]', [{ name: undefined, sequence: '\x1B]', meta: true }]); + +// Control keys +addTest('\x01\x0b\x10', [ + { name: 'a', sequence: '\x01', ctrl: true }, + { name: 'k', sequence: '\x0b', ctrl: true }, + { name: 'p', sequence: '\x10', ctrl: true }, +]); + +// Alt keys +addTest('a\x1baA\x1bA', [ + { name: 'a', sequence: 'a' }, + { name: 'a', sequence: '\x1ba', meta: true }, + { name: 'a', sequence: 'A', shift: true }, + { name: 'a', sequence: '\x1bA', meta: true, shift: true }, +]); + +// xterm/gnome ESC [ letter (with modifiers) +addTest('\x1b[2P\x1b[3P\x1b[4P\x1b[5P\x1b[6P\x1b[7P\x1b[8P\x1b[3Q\x1b[8Q\x1b[3R\x1b[8R\x1b[3S\x1b[8S', [ + { name: 'f1', sequence: '\x1b[2P', code: '[P', shift: true, meta: false, ctrl: false }, + { name: 'f1', sequence: '\x1b[3P', code: '[P', shift: false, meta: true, ctrl: false }, + { name: 'f1', sequence: '\x1b[4P', code: '[P', shift: true, meta: true, ctrl: false }, + { name: 'f1', sequence: '\x1b[5P', code: '[P', shift: false, meta: false, ctrl: true }, + { name: 'f1', sequence: '\x1b[6P', code: '[P', shift: true, meta: false, ctrl: true }, + { name: 'f1', sequence: '\x1b[7P', code: '[P', shift: false, meta: true, ctrl: true }, + { name: 'f1', sequence: '\x1b[8P', code: '[P', shift: true, meta: true, ctrl: true }, + { name: 'f2', sequence: '\x1b[3Q', code: '[Q', meta: true }, + { name: 'f2', sequence: '\x1b[8Q', code: '[Q', shift: true, meta: true, ctrl: true }, + { name: 'f3', sequence: '\x1b[3R', code: '[R', meta: true }, + { name: 'f3', sequence: '\x1b[8R', code: '[R', shift: true, meta: true, ctrl: true }, + { name: 'f4', sequence: '\x1b[3S', code: '[S', meta: true }, + { name: 'f4', sequence: '\x1b[8S', code: '[S', shift: true, meta: true, ctrl: true }, +]); + +// xterm/gnome ESC O letter +addTest('\x1bOP\x1bOQ\x1bOR\x1bOS', [ + { name: 'f1', sequence: '\x1bOP', code: 'OP' }, + { name: 'f2', sequence: '\x1bOQ', code: 'OQ' }, + { name: 'f3', sequence: '\x1bOR', code: 'OR' }, + { name: 'f4', sequence: '\x1bOS', code: 'OS' }, +]); + +// xterm/rxvt ESC [ number ~ */ +addTest('\x1b[11~\x1b[12~\x1b[13~\x1b[14~', [ + { name: 'f1', sequence: '\x1b[11~', code: '[11~' }, + { name: 'f2', sequence: '\x1b[12~', code: '[12~' }, + { name: 'f3', sequence: '\x1b[13~', code: '[13~' }, + { name: 'f4', sequence: '\x1b[14~', code: '[14~' }, +]); + +// From Cygwin and used in libuv +addTest('\x1b[[A\x1b[[B\x1b[[C\x1b[[D\x1b[[E', [ + { name: 'f1', sequence: '\x1b[[A', code: '[[A' }, + { name: 'f2', sequence: '\x1b[[B', code: '[[B' }, + { name: 'f3', sequence: '\x1b[[C', code: '[[C' }, + { name: 'f4', sequence: '\x1b[[D', code: '[[D' }, + { name: 'f5', sequence: '\x1b[[E', code: '[[E' }, +]); + +// Common +addTest('\x1b[15~\x1b[17~\x1b[18~\x1b[19~\x1b[20~\x1b[21~\x1b[23~\x1b[24~', [ + { name: 'f5', sequence: '\x1b[15~', code: '[15~' }, + { name: 'f6', sequence: '\x1b[17~', code: '[17~' }, + { name: 'f7', sequence: '\x1b[18~', code: '[18~' }, + { name: 'f8', sequence: '\x1b[19~', code: '[19~' }, + { name: 'f9', sequence: '\x1b[20~', code: '[20~' }, + { name: 'f10', sequence: '\x1b[21~', code: '[21~' }, + { name: 'f11', sequence: '\x1b[23~', code: '[23~' }, + { name: 'f12', sequence: '\x1b[24~', code: '[24~' }, +]); + +// xterm ESC [ letter +addTest('\x1b[A\x1b[B\x1b[C\x1b[D\x1b[E\x1b[F\x1b[H', [ + { name: 'up', sequence: '\x1b[A', code: '[A' }, + { name: 'down', sequence: '\x1b[B', code: '[B' }, + { name: 'right', sequence: '\x1b[C', code: '[C' }, + { name: 'left', sequence: '\x1b[D', code: '[D' }, + { name: 'clear', sequence: '\x1b[E', code: '[E' }, + { name: 'end', sequence: '\x1b[F', code: '[F' }, + { name: 'home', sequence: '\x1b[H', code: '[H' }, +]); + +// xterm/gnome ESC O letter +addTest('\x1bOA\x1bOB\x1bOC\x1bOD\x1bOE\x1bOF\x1bOH', [ + { name: 'up', sequence: '\x1bOA', code: 'OA' }, + { name: 'down', sequence: '\x1bOB', code: 'OB' }, + { name: 'right', sequence: '\x1bOC', code: 'OC' }, + { name: 'left', sequence: '\x1bOD', code: 'OD' }, + { name: 'clear', sequence: '\x1bOE', code: 'OE' }, + { name: 'end', sequence: '\x1bOF', code: 'OF' }, + { name: 'home', sequence: '\x1bOH', code: 'OH' }, +]); + +// Old xterm shift-arrows +addTest('\x1bO2A\x1bO2B', [ + { name: 'up', sequence: '\x1bO2A', code: 'OA', shift: true }, + { name: 'down', sequence: '\x1bO2B', code: 'OB', shift: true }, +]); + +// xterm/rxvt ESC [ number ~ +addTest('\x1b[1~\x1b[2~\x1b[3~\x1b[4~\x1b[5~\x1b[6~', [ + { name: 'home', sequence: '\x1b[1~', code: '[1~' }, + { name: 'insert', sequence: '\x1b[2~', code: '[2~' }, + { name: 'delete', sequence: '\x1b[3~', code: '[3~' }, + { name: 'end', sequence: '\x1b[4~', code: '[4~' }, + { name: 'pageup', sequence: '\x1b[5~', code: '[5~' }, + { name: 'pagedown', sequence: '\x1b[6~', code: '[6~' }, +]); + +// putty +addTest('\x1b[[5~\x1b[[6~', [ + { name: 'pageup', sequence: '\x1b[[5~', code: '[[5~' }, + { name: 'pagedown', sequence: '\x1b[[6~', code: '[[6~' }, +]); + +// rxvt +addTest('\x1b[7~\x1b[8~', [ + { name: 'home', sequence: '\x1b[7~', code: '[7~' }, + { name: 'end', sequence: '\x1b[8~', code: '[8~' }, +]); + +// gnome terminal +addTest('\x1b[A\x1b[B\x1b[2A\x1b[2B', [ + { name: 'up', sequence: '\x1b[A', code: '[A' }, + { name: 'down', sequence: '\x1b[B', code: '[B' }, + { name: 'up', sequence: '\x1b[2A', code: '[A', shift: true }, + { name: 'down', sequence: '\x1b[2B', code: '[B', shift: true }, +]); + +// `rxvt` keys with modifiers. +addTest('\x1b[20~\x1b[2$\x1b[2^\x1b[3$\x1b[3^\x1b[5$\x1b[5^\x1b[6$\x1b[6^\x1b[7$\x1b[7^\x1b[8$\x1b[8^', [ + { name: 'f9', sequence: '\x1b[20~', code: '[20~' }, + { name: 'insert', sequence: '\x1b[2$', code: '[2$', shift: true }, + { name: 'insert', sequence: '\x1b[2^', code: '[2^', ctrl: true }, + { name: 'delete', sequence: '\x1b[3$', code: '[3$', shift: true }, + { name: 'delete', sequence: '\x1b[3^', code: '[3^', ctrl: true }, + { name: 'pageup', sequence: '\x1b[5$', code: '[5$', shift: true }, + { name: 'pageup', sequence: '\x1b[5^', code: '[5^', ctrl: true }, + { name: 'pagedown', sequence: '\x1b[6$', code: '[6$', shift: true }, + { name: 'pagedown', sequence: '\x1b[6^', code: '[6^', ctrl: true }, + { name: 'home', sequence: '\x1b[7$', code: '[7$', shift: true }, + { name: 'home', sequence: '\x1b[7^', code: '[7^', ctrl: true }, + { name: 'end', sequence: '\x1b[8$', code: '[8$', shift: true }, + { name: 'end', sequence: '\x1b[8^', code: '[8^', ctrl: true }, +]); + +// Misc +addTest('\x1b[Z', [ + { name: 'tab', sequence: '\x1b[Z', code: '[Z', shift: true }, +]); + +// xterm + modifiers +addTest('\x1b[20;5~\x1b[6;5^', [ + { name: 'f9', sequence: '\x1b[20;5~', code: '[20~', ctrl: true }, + { name: 'pagedown', sequence: '\x1b[6;5^', code: '[6^', ctrl: true }, +]); + +addTest('\x1b[H\x1b[5H\x1b[1;5H', [ + { name: 'home', sequence: '\x1b[H', code: '[H' }, + { name: 'home', sequence: '\x1b[5H', code: '[H', ctrl: true }, + { name: 'home', sequence: '\x1b[1;5H', code: '[H', ctrl: true }, +]); + +// Escape sequences broken into multiple data chunks +addTest('\x1b[D\x1b[C\x1b[D\x1b[C'.split(''), [ + { name: 'left', sequence: '\x1b[D', code: '[D' }, + { name: 'right', sequence: '\x1b[C', code: '[C' }, + { name: 'left', sequence: '\x1b[D', code: '[D' }, + { name: 'right', sequence: '\x1b[C', code: '[C' }, +]); + +// Escape sequences mixed with regular ones +addTest('\x1b[DD\x1b[2DD\x1b[2^D', [ + { name: 'left', sequence: '\x1b[D', code: '[D' }, + { name: 'd', sequence: 'D', shift: true }, + { name: 'left', sequence: '\x1b[2D', code: '[D', shift: true }, + { name: 'd', sequence: 'D', shift: true }, + { name: 'insert', sequence: '\x1b[2^', code: '[2^', ctrl: true }, + { name: 'd', sequence: 'D', shift: true }, +]); + +// Color sequences +addTest('\x1b[31ma\x1b[39ma', [ + { name: 'undefined', sequence: '\x1b[31m', code: '[31m' }, + { name: 'a', sequence: 'a' }, + { name: 'undefined', sequence: '\x1b[39m', code: '[39m' }, + { name: 'a', sequence: 'a' }, +]); + +// `rxvt` keys with modifiers. +addTest('\x1b[a\x1b[b\x1b[c\x1b[d\x1b[e', [ + { name: 'up', sequence: '\x1b[a', code: '[a', shift: true }, + { name: 'down', sequence: '\x1b[b', code: '[b', shift: true }, + { name: 'right', sequence: '\x1b[c', code: '[c', shift: true }, + { name: 'left', sequence: '\x1b[d', code: '[d', shift: true }, + { name: 'clear', sequence: '\x1b[e', code: '[e', shift: true }, +]); + +addTest('\x1bOa\x1bOb\x1bOc\x1bOd\x1bOe', [ + { name: 'up', sequence: '\x1bOa', code: 'Oa', ctrl: true }, + { name: 'down', sequence: '\x1bOb', code: 'Ob', ctrl: true }, + { name: 'right', sequence: '\x1bOc', code: 'Oc', ctrl: true }, + { name: 'left', sequence: '\x1bOd', code: 'Od', ctrl: true }, + { name: 'clear', sequence: '\x1bOe', code: 'Oe', ctrl: true }, +]); + +// Reduce array of addKeyIntervalTest(..) right to left +// with () => {} as initial function. +const runKeyIntervalTests = [ + // Escape character + addKeyIntervalTest('\x1b', [ + { name: 'escape', sequence: '\x1b', meta: true }, + ]), + // Chain of escape characters. + addKeyIntervalTest('\x1b\x1b\x1b\x1b'.split(''), [ + { name: 'escape', sequence: '\x1b', meta: true }, + { name: 'escape', sequence: '\x1b', meta: true }, + { name: 'escape', sequence: '\x1b', meta: true }, + { name: 'escape', sequence: '\x1b', meta: true }, + ]), +].reverse().reduce((acc, fn) => fn(acc), () => {}); + +// Run key interval tests one after another. +runKeyIntervalTests(); diff --git a/cli/tests/node_compat/test/parallel/test-readline-position.js b/cli/tests/node_compat/test/parallel/test-readline-position.js new file mode 100644 index 00000000000000..0ffdf4b18e1716 --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-readline-position.js @@ -0,0 +1,43 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +// Flags: --expose-internals +'use strict'; +const common = require('../common'); +const { PassThrough } = require('stream'); +const readline = require('readline'); +const assert = require('assert'); + +const ctrlU = { ctrl: true, name: 'u' }; + +common.skipIfDumbTerminal(); + +{ + const input = new PassThrough(); + const rl = readline.createInterface({ + terminal: true, + input: input, + prompt: '' + }); + + const tests = [ + [1, 'a'], + [2, 'ab'], + [2, '丁'], + [0, '\u0301'], // COMBINING ACUTE ACCENT + [1, 'a\u0301'], // á + [0, '\u20DD'], // COMBINING ENCLOSING CIRCLE + [2, 'a\u20DDb'], // a⃝b + [0, '\u200E'], // LEFT-TO-RIGHT MARK + ]; + + for (const [cursor, string] of tests) { + rl.write(string); + assert.strictEqual(rl.getCursorPos().cols, cursor); + rl.write(null, ctrlU); + } +} diff --git a/cli/tests/node_compat/test/parallel/test-readline-promises-csi.mjs b/cli/tests/node_compat/test/parallel/test-readline-promises-csi.mjs new file mode 100644 index 00000000000000..fb61b52d95e05d --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-readline-promises-csi.mjs @@ -0,0 +1,233 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +// Flags: --expose-internals + + +import '../common/index.mjs'; +import assert from 'assert'; +import { Readline } from 'readline/promises'; +import { setImmediate } from 'timers/promises'; +import { Writable } from 'stream'; + +import utils from 'internal/readline/utils'; +const { CSI } = utils; + +const INVALID_ARG = { + name: 'TypeError', + code: 'ERR_INVALID_ARG_TYPE', +}; + +class TestWritable extends Writable { + data = ''; + _write(chunk, encoding, callback) { + this.data += chunk.toString(); + callback(); + } +} + +[ + undefined, null, + 0, 1, 1n, 1.1, NaN, Infinity, + true, false, + Symbol(), + '', '1', + [], {}, () => {}, +].forEach((arg) => + assert.throws(() => new Readline(arg), INVALID_ARG) +); + +{ + const writable = new TestWritable(); + const readline = new Readline(writable); + + await readline.clearScreenDown().commit(); + assert.deepStrictEqual(writable.data, CSI.kClearScreenDown); + await readline.clearScreenDown().commit(); + + writable.data = ''; + await readline.clearScreenDown().rollback(); + assert.strictEqual(writable.data, ''); + + writable.data = ''; + await readline.clearLine(-1).commit(); + assert.deepStrictEqual(writable.data, CSI.kClearToLineBeginning); + + writable.data = ''; + await readline.clearLine(1).commit(); + assert.deepStrictEqual(writable.data, CSI.kClearToLineEnd); + + writable.data = ''; + await readline.clearLine(0).commit(); + assert.deepStrictEqual(writable.data, CSI.kClearLine); + + writable.data = ''; + await readline.clearLine(-1).commit(); + assert.deepStrictEqual(writable.data, CSI.kClearToLineBeginning); + + await readline.clearLine(0, null).commit(); + + // Nothing is written when moveCursor 0, 0 + for (const set of + [ + [0, 0, ''], + [1, 0, '\x1b[1C'], + [-1, 0, '\x1b[1D'], + [0, 1, '\x1b[1B'], + [0, -1, '\x1b[1A'], + [1, 1, '\x1b[1C\x1b[1B'], + [-1, 1, '\x1b[1D\x1b[1B'], + [-1, -1, '\x1b[1D\x1b[1A'], + [1, -1, '\x1b[1C\x1b[1A'], + ]) { + writable.data = ''; + await readline.moveCursor(set[0], set[1]).commit(); + assert.deepStrictEqual(writable.data, set[2]); + writable.data = ''; + await readline.moveCursor(set[0], set[1]).commit(); + assert.deepStrictEqual(writable.data, set[2]); + } + + + await readline.moveCursor(1, 1, null).commit(); + + writable.data = ''; + [ + undefined, null, + true, false, + Symbol(), + '', '1', + [], {}, () => {}, + ].forEach((arg) => + assert.throws(() => readline.cursorTo(arg), INVALID_ARG) + ); + assert.strictEqual(writable.data, ''); + + writable.data = ''; + assert.throws(() => readline.cursorTo('a', 'b'), INVALID_ARG); + assert.strictEqual(writable.data, ''); + + writable.data = ''; + assert.throws(() => readline.cursorTo('a', 1), INVALID_ARG); + assert.strictEqual(writable.data, ''); + + writable.data = ''; + assert.throws(() => readline.cursorTo(1, 'a'), INVALID_ARG); + assert.strictEqual(writable.data, ''); + + writable.data = ''; + await readline.cursorTo(1).commit(); + assert.strictEqual(writable.data, '\x1b[2G'); + + writable.data = ''; + await readline.cursorTo(1, 2).commit(); + assert.strictEqual(writable.data, '\x1b[3;2H'); + + writable.data = ''; + await readline.cursorTo(1, 2).commit(); + assert.strictEqual(writable.data, '\x1b[3;2H'); + + writable.data = ''; + await readline.cursorTo(1).cursorTo(1, 2).commit(); + assert.strictEqual(writable.data, '\x1b[2G\x1b[3;2H'); + + writable.data = ''; + await readline.cursorTo(1).commit(); + assert.strictEqual(writable.data, '\x1b[2G'); + + // Verify that cursorTo() rejects if x or y is NaN. + [1.1, NaN, Infinity].forEach((arg) => { + assert.throws(() => readline.cursorTo(arg), { + code: 'ERR_OUT_OF_RANGE', + name: 'RangeError', + }); + }); + + [1.1, NaN, Infinity].forEach((arg) => { + assert.throws(() => readline.cursorTo(1, arg), { + code: 'ERR_OUT_OF_RANGE', + name: 'RangeError', + }); + }); + + assert.throws(() => readline.cursorTo(NaN, NaN), { + code: 'ERR_OUT_OF_RANGE', + name: 'RangeError', + }); +} + +{ + const error = new Error(); + const writable = new class extends Writable { + _write() { throw error; } + }(); + const readline = new Readline(writable); + + await assert.rejects(readline.cursorTo(1).commit(), error); +} + +{ + const writable = new TestWritable(); + const readline = new Readline(writable, { autoCommit: true }); + + await readline.clearScreenDown(); + await setImmediate(); // Wait for next tick as auto commit is asynchronous. + assert.deepStrictEqual(writable.data, CSI.kClearScreenDown); +} + +{ + const writable = new TestWritable(); + const readline = new Readline(writable, { autoCommit: true }); + for (const [dir, data] of + [ + [-1, CSI.kClearToLineBeginning], + [1, CSI.kClearToLineEnd], + [0, CSI.kClearLine], + ]) { + writable.data = ''; + readline.clearLine(dir); + await setImmediate(); // Wait for next tick as auto commit is asynchronous. + assert.deepStrictEqual(writable.data, data); + } +} + +{ + const writable = new TestWritable(); + const readline = new Readline(writable, { autoCommit: true }); + for (const [x, y, data] of + [ + [0, 0, ''], + [1, 0, '\x1b[1C'], + [-1, 0, '\x1b[1D'], + [0, 1, '\x1b[1B'], + [0, -1, '\x1b[1A'], + [1, 1, '\x1b[1C\x1b[1B'], + [-1, 1, '\x1b[1D\x1b[1B'], + [-1, -1, '\x1b[1D\x1b[1A'], + [1, -1, '\x1b[1C\x1b[1A'], + ]) { + writable.data = ''; + readline.moveCursor(x, y); + await setImmediate(); // Wait for next tick as auto commit is asynchronous. + assert.deepStrictEqual(writable.data, data); + } +} + +{ + const writable = new TestWritable(); + const readline = new Readline(writable, { autoCommit: true }); + for (const [x, y, data] of + [ + [1, undefined, '\x1b[2G'], + [1, 2, '\x1b[3;2H'], + ]) { + writable.data = ''; + readline.cursorTo(x, y); + await setImmediate(); // Wait for next tick as auto commit is asynchronous. + assert.deepStrictEqual(writable.data, data); + } +} diff --git a/cli/tests/node_compat/test/parallel/test-readline-promises-interface.js b/cli/tests/node_compat/test/parallel/test-readline-promises-interface.js new file mode 100644 index 00000000000000..fbbfb46ad34695 --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-readline-promises-interface.js @@ -0,0 +1,1149 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +// Flags: --expose-internals +'use strict'; +const common = require('../common'); +common.skipIfDumbTerminal(); + +const assert = require('assert'); +const readline = require('readline/promises'); +const { + getStringWidth, + stripVTControlCharacters +} = require('internal/util/inspect'); +const EventEmitter = require('events').EventEmitter; +const { Writable, Readable } = require('stream'); + +class FakeInput extends EventEmitter { + resume() {} + pause() {} + write() {} + end() {} +} + +function isWarned(emitter) { + for (const name in emitter) { + const listeners = emitter[name]; + if (listeners.warned) return true; + } + return false; +} + +function getInterface(options) { + const fi = new FakeInput(); + const rli = new readline.Interface({ + input: fi, + output: fi, + ...options, + }); + return [rli, fi]; +} + +function assertCursorRowsAndCols(rli, rows, cols) { + const cursorPos = rli.getCursorPos(); + assert.strictEqual(cursorPos.rows, rows); + assert.strictEqual(cursorPos.cols, cols); +} + +[ + undefined, + 50, + 0, + 100.5, + 5000, +].forEach((crlfDelay) => { + const [rli] = getInterface({ crlfDelay }); + assert.strictEqual(rli.crlfDelay, Math.max(crlfDelay || 100, 100)); + rli.close(); +}); + +{ + const input = new FakeInput(); + + // Constructor throws if completer is not a function or undefined + assert.throws(() => { + readline.createInterface({ + input, + completer: 'string is not valid' + }); + }, { + name: 'TypeError', + code: 'ERR_INVALID_ARG_VALUE' + }); + + assert.throws(() => { + readline.createInterface({ + input, + completer: '' + }); + }, { + name: 'TypeError', + code: 'ERR_INVALID_ARG_VALUE' + }); + + assert.throws(() => { + readline.createInterface({ + input, + completer: false + }); + }, { + name: 'TypeError', + code: 'ERR_INVALID_ARG_VALUE' + }); + + // Constructor throws if history is not an array + ['not an array', 123, 123n, {}, true, Symbol(), null].forEach((history) => { + assert.throws(() => { + readline.createInterface({ + input, + history, + }); + }, { + name: 'TypeError', + code: 'ERR_INVALID_ARG_TYPE' + }); + }); + + // Constructor throws if historySize is not a positive number + ['not a number', -1, NaN, {}, true, Symbol(), null].forEach((historySize) => { + assert.throws(() => { + readline.createInterface({ + input, + historySize, + }); + }, { + name: 'RangeError', + code: 'ERR_INVALID_ARG_VALUE' + }); + }); + + // Check for invalid tab sizes. + assert.throws( + () => new readline.Interface({ + input, + tabSize: 0 + }), + { code: 'ERR_OUT_OF_RANGE' } + ); + + assert.throws( + () => new readline.Interface({ + input, + tabSize: '4' + }), + { code: 'ERR_INVALID_ARG_TYPE' } + ); + + assert.throws( + () => new readline.Interface({ + input, + tabSize: 4.5 + }), + { + code: 'ERR_OUT_OF_RANGE', + message: 'The value of "tabSize" is out of range. ' + + 'It must be an integer. Received 4.5' + } + ); +} + +// Sending a single character with no newline +{ + const fi = new FakeInput(); + const rli = new readline.Interface(fi, {}); + rli.on('line', common.mustNotCall()); + fi.emit('data', 'a'); + rli.close(); +} + +// Sending multiple newlines at once that does not end with a new line and a +// `end` event(last line is). \r should behave like \n when alone. +{ + const [rli, fi] = getInterface({ terminal: true }); + const expectedLines = ['foo', 'bar', 'baz', 'bat']; + rli.on('line', common.mustCall((line) => { + assert.strictEqual(line, expectedLines.shift()); + }, expectedLines.length - 1)); + fi.emit('data', expectedLines.join('\r')); + rli.close(); +} + +// \r at start of input should output blank line +{ + const [rli, fi] = getInterface({ terminal: true }); + const expectedLines = ['', 'foo' ]; + rli.on('line', common.mustCall((line) => { + assert.strictEqual(line, expectedLines.shift()); + }, expectedLines.length)); + fi.emit('data', '\rfoo\r'); + rli.close(); +} + +// \t does not become part of the input when there is a completer function +{ + const completer = (line) => [[], line]; + const [rli, fi] = getInterface({ terminal: true, completer }); + rli.on('line', common.mustCall((line) => { + assert.strictEqual(line, 'foo'); + })); + for (const character of '\tfo\to\t') { + fi.emit('data', character); + } + fi.emit('data', '\n'); + rli.close(); +} + +// \t when there is no completer function should behave like an ordinary +// character +{ + const [rli, fi] = getInterface({ terminal: true }); + rli.on('line', common.mustCall((line) => { + assert.strictEqual(line, '\t'); + })); + fi.emit('data', '\t'); + fi.emit('data', '\n'); + rli.close(); +} + +// Adding history lines should emit the history event with +// the history array +{ + const [rli, fi] = getInterface({ terminal: true }); + const expectedLines = ['foo', 'bar', 'baz', 'bat']; + rli.on('history', common.mustCall((history) => { + const expectedHistory = expectedLines.slice(0, history.length).reverse(); + assert.deepStrictEqual(history, expectedHistory); + }, expectedLines.length)); + for (const line of expectedLines) { + fi.emit('data', `${line}\n`); + } + rli.close(); +} + +// Altering the history array in the listener should not alter +// the line being processed +{ + const [rli, fi] = getInterface({ terminal: true }); + const expectedLine = 'foo'; + rli.on('history', common.mustCall((history) => { + assert.strictEqual(history[0], expectedLine); + history.shift(); + })); + rli.on('line', common.mustCall((line) => { + assert.strictEqual(line, expectedLine); + assert.strictEqual(rli.history.length, 0); + })); + fi.emit('data', `${expectedLine}\n`); + rli.close(); +} + +// Duplicate lines are removed from history when +// `options.removeHistoryDuplicates` is `true` +{ + const [rli, fi] = getInterface({ + terminal: true, + removeHistoryDuplicates: true + }); + const expectedLines = ['foo', 'bar', 'baz', 'bar', 'bat', 'bat']; + // ['foo', 'baz', 'bar', bat']; + let callCount = 0; + rli.on('line', function(line) { + assert.strictEqual(line, expectedLines[callCount]); + callCount++; + }); + fi.emit('data', `${expectedLines.join('\n')}\n`); + assert.strictEqual(callCount, expectedLines.length); + fi.emit('keypress', '.', { name: 'up' }); // 'bat' + assert.strictEqual(rli.line, expectedLines[--callCount]); + fi.emit('keypress', '.', { name: 'up' }); // 'bar' + assert.notStrictEqual(rli.line, expectedLines[--callCount]); + assert.strictEqual(rli.line, expectedLines[--callCount]); + fi.emit('keypress', '.', { name: 'up' }); // 'baz' + assert.strictEqual(rli.line, expectedLines[--callCount]); + fi.emit('keypress', '.', { name: 'up' }); // 'foo' + assert.notStrictEqual(rli.line, expectedLines[--callCount]); + assert.strictEqual(rli.line, expectedLines[--callCount]); + assert.strictEqual(callCount, 0); + fi.emit('keypress', '.', { name: 'down' }); // 'baz' + assert.strictEqual(rli.line, 'baz'); + assert.strictEqual(rli.historyIndex, 2); + fi.emit('keypress', '.', { name: 'n', ctrl: true }); // 'bar' + assert.strictEqual(rli.line, 'bar'); + assert.strictEqual(rli.historyIndex, 1); + fi.emit('keypress', '.', { name: 'n', ctrl: true }); + assert.strictEqual(rli.line, 'bat'); + assert.strictEqual(rli.historyIndex, 0); + // Activate the substring history search. + fi.emit('keypress', '.', { name: 'down' }); // 'bat' + assert.strictEqual(rli.line, 'bat'); + assert.strictEqual(rli.historyIndex, -1); + // Deactivate substring history search. + fi.emit('keypress', '.', { name: 'backspace' }); // 'ba' + assert.strictEqual(rli.historyIndex, -1); + assert.strictEqual(rli.line, 'ba'); + // Activate the substring history search. + fi.emit('keypress', '.', { name: 'down' }); // 'ba' + assert.strictEqual(rli.historyIndex, -1); + assert.strictEqual(rli.line, 'ba'); + fi.emit('keypress', '.', { name: 'down' }); // 'ba' + assert.strictEqual(rli.historyIndex, -1); + assert.strictEqual(rli.line, 'ba'); + fi.emit('keypress', '.', { name: 'up' }); // 'bat' + assert.strictEqual(rli.historyIndex, 0); + assert.strictEqual(rli.line, 'bat'); + fi.emit('keypress', '.', { name: 'up' }); // 'bar' + assert.strictEqual(rli.historyIndex, 1); + assert.strictEqual(rli.line, 'bar'); + fi.emit('keypress', '.', { name: 'up' }); // 'baz' + assert.strictEqual(rli.historyIndex, 2); + assert.strictEqual(rli.line, 'baz'); + fi.emit('keypress', '.', { name: 'up' }); // 'ba' + assert.strictEqual(rli.historyIndex, 4); + assert.strictEqual(rli.line, 'ba'); + fi.emit('keypress', '.', { name: 'up' }); // 'ba' + assert.strictEqual(rli.historyIndex, 4); + assert.strictEqual(rli.line, 'ba'); + // Deactivate substring history search and reset history index. + fi.emit('keypress', '.', { name: 'right' }); // 'ba' + assert.strictEqual(rli.historyIndex, -1); + assert.strictEqual(rli.line, 'ba'); + // Substring history search activated. + fi.emit('keypress', '.', { name: 'up' }); // 'ba' + assert.strictEqual(rli.historyIndex, 0); + assert.strictEqual(rli.line, 'bat'); + rli.close(); +} + +// Duplicate lines are not removed from history when +// `options.removeHistoryDuplicates` is `false` +{ + const [rli, fi] = getInterface({ + terminal: true, + removeHistoryDuplicates: false + }); + const expectedLines = ['foo', 'bar', 'baz', 'bar', 'bat', 'bat']; + let callCount = 0; + rli.on('line', function(line) { + assert.strictEqual(line, expectedLines[callCount]); + callCount++; + }); + fi.emit('data', `${expectedLines.join('\n')}\n`); + assert.strictEqual(callCount, expectedLines.length); + fi.emit('keypress', '.', { name: 'up' }); // 'bat' + assert.strictEqual(rli.line, expectedLines[--callCount]); + fi.emit('keypress', '.', { name: 'up' }); // 'bar' + assert.notStrictEqual(rli.line, expectedLines[--callCount]); + assert.strictEqual(rli.line, expectedLines[--callCount]); + fi.emit('keypress', '.', { name: 'up' }); // 'baz' + assert.strictEqual(rli.line, expectedLines[--callCount]); + fi.emit('keypress', '.', { name: 'up' }); // 'bar' + assert.strictEqual(rli.line, expectedLines[--callCount]); + fi.emit('keypress', '.', { name: 'up' }); // 'foo' + assert.strictEqual(rli.line, expectedLines[--callCount]); + assert.strictEqual(callCount, 0); + rli.close(); +} + +// Regression test for repl freeze, #1968: +// check that nothing fails if 'keypress' event throws. +{ + const [rli, fi] = getInterface({ terminal: true }); + const keys = []; + const err = new Error('bad thing happened'); + fi.on('keypress', function(key) { + keys.push(key); + if (key === 'X') { + throw err; + } + }); + assert.throws( + () => fi.emit('data', 'fooX'), + (e) => { + assert.strictEqual(e, err); + return true; + } + ); + fi.emit('data', 'bar'); + assert.strictEqual(keys.join(''), 'fooXbar'); + rli.close(); +} + +// History is bound +{ + const [rli, fi] = getInterface({ terminal: true, historySize: 2 }); + const lines = ['line 1', 'line 2', 'line 3']; + fi.emit('data', lines.join('\n') + '\n'); + assert.strictEqual(rli.history.length, 2); + assert.strictEqual(rli.history[0], 'line 3'); + assert.strictEqual(rli.history[1], 'line 2'); +} + +// Question +{ + const [rli] = getInterface({ terminal: true }); + const expectedLines = ['foo']; + rli.question(expectedLines[0]).then(() => rli.close()); + assertCursorRowsAndCols(rli, 0, expectedLines[0].length); + rli.close(); +} + +// Sending a multi-line question +{ + const [rli] = getInterface({ terminal: true }); + const expectedLines = ['foo', 'bar']; + rli.question(expectedLines.join('\n')).then(() => rli.close()); + assertCursorRowsAndCols( + rli, expectedLines.length - 1, expectedLines.slice(-1)[0].length); + rli.close(); +} + +{ + // Beginning and end of line + const [rli, fi] = getInterface({ terminal: true, prompt: '' }); + fi.emit('data', 'the quick brown fox'); + fi.emit('keypress', '.', { ctrl: true, name: 'a' }); + assertCursorRowsAndCols(rli, 0, 0); + fi.emit('keypress', '.', { ctrl: true, name: 'e' }); + assertCursorRowsAndCols(rli, 0, 19); + rli.close(); +} + +{ + // Back and Forward one character + const [rli, fi] = getInterface({ terminal: true, prompt: '' }); + fi.emit('data', 'the quick brown fox'); + assertCursorRowsAndCols(rli, 0, 19); + + // Back one character + fi.emit('keypress', '.', { ctrl: true, name: 'b' }); + assertCursorRowsAndCols(rli, 0, 18); + // Back one character + fi.emit('keypress', '.', { ctrl: true, name: 'b' }); + assertCursorRowsAndCols(rli, 0, 17); + // Forward one character + fi.emit('keypress', '.', { ctrl: true, name: 'f' }); + assertCursorRowsAndCols(rli, 0, 18); + // Forward one character + fi.emit('keypress', '.', { ctrl: true, name: 'f' }); + assertCursorRowsAndCols(rli, 0, 19); + rli.close(); +} + +// Back and Forward one astral character +{ + const [rli, fi] = getInterface({ terminal: true, prompt: '' }); + fi.emit('data', '💻'); + + // Move left one character/code point + fi.emit('keypress', '.', { name: 'left' }); + assertCursorRowsAndCols(rli, 0, 0); + + // Move right one character/code point + fi.emit('keypress', '.', { name: 'right' }); + assertCursorRowsAndCols(rli, 0, 2); + + rli.on('line', common.mustCall((line) => { + assert.strictEqual(line, '💻'); + })); + fi.emit('data', '\n'); + rli.close(); +} + +// Two astral characters left +{ + const [rli, fi] = getInterface({ terminal: true, prompt: '' }); + fi.emit('data', '💻'); + + // Move left one character/code point + fi.emit('keypress', '.', { name: 'left' }); + assertCursorRowsAndCols(rli, 0, 0); + + fi.emit('data', '🐕'); + assertCursorRowsAndCols(rli, 0, 2); + + rli.on('line', common.mustCall((line) => { + assert.strictEqual(line, '🐕💻'); + })); + fi.emit('data', '\n'); + rli.close(); +} + +// Two astral characters right +{ + const [rli, fi] = getInterface({ terminal: true, prompt: '' }); + fi.emit('data', '💻'); + + // Move left one character/code point + fi.emit('keypress', '.', { name: 'right' }); + assertCursorRowsAndCols(rli, 0, 2); + + fi.emit('data', '🐕'); + assertCursorRowsAndCols(rli, 0, 4); + + rli.on('line', common.mustCall((line) => { + assert.strictEqual(line, '💻🐕'); + })); + fi.emit('data', '\n'); + rli.close(); +} + +{ + // `wordLeft` and `wordRight` + const [rli, fi] = getInterface({ terminal: true, prompt: '' }); + fi.emit('data', 'the quick brown fox'); + fi.emit('keypress', '.', { ctrl: true, name: 'left' }); + assertCursorRowsAndCols(rli, 0, 16); + fi.emit('keypress', '.', { meta: true, name: 'b' }); + assertCursorRowsAndCols(rli, 0, 10); + fi.emit('keypress', '.', { ctrl: true, name: 'right' }); + assertCursorRowsAndCols(rli, 0, 16); + fi.emit('keypress', '.', { meta: true, name: 'f' }); + assertCursorRowsAndCols(rli, 0, 19); + rli.close(); +} + +// `deleteWordLeft` +[ + { ctrl: true, name: 'w' }, + { ctrl: true, name: 'backspace' }, + { meta: true, name: 'backspace' }, +].forEach((deleteWordLeftKey) => { + let [rli, fi] = getInterface({ terminal: true, prompt: '' }); + fi.emit('data', 'the quick brown fox'); + fi.emit('keypress', '.', { ctrl: true, name: 'left' }); + rli.on('line', common.mustCall((line) => { + assert.strictEqual(line, 'the quick fox'); + })); + fi.emit('keypress', '.', deleteWordLeftKey); + fi.emit('data', '\n'); + rli.close(); + + // No effect if pressed at beginning of line + [rli, fi] = getInterface({ terminal: true, prompt: '' }); + fi.emit('data', 'the quick brown fox'); + fi.emit('keypress', '.', { ctrl: true, name: 'a' }); + rli.on('line', common.mustCall((line) => { + assert.strictEqual(line, 'the quick brown fox'); + })); + fi.emit('keypress', '.', deleteWordLeftKey); + fi.emit('data', '\n'); + rli.close(); +}); + +// `deleteWordRight` +[ + { ctrl: true, name: 'delete' }, + { meta: true, name: 'delete' }, + { meta: true, name: 'd' }, +].forEach((deleteWordRightKey) => { + let [rli, fi] = getInterface({ terminal: true, prompt: '' }); + fi.emit('data', 'the quick brown fox'); + fi.emit('keypress', '.', { ctrl: true, name: 'left' }); + fi.emit('keypress', '.', { ctrl: true, name: 'left' }); + rli.on('line', common.mustCall((line) => { + assert.strictEqual(line, 'the quick fox'); + })); + fi.emit('keypress', '.', deleteWordRightKey); + fi.emit('data', '\n'); + rli.close(); + + // No effect if pressed at end of line + [rli, fi] = getInterface({ terminal: true, prompt: '' }); + fi.emit('data', 'the quick brown fox'); + rli.on('line', common.mustCall((line) => { + assert.strictEqual(line, 'the quick brown fox'); + })); + fi.emit('keypress', '.', deleteWordRightKey); + fi.emit('data', '\n'); + rli.close(); +}); + +// deleteLeft +{ + const [rli, fi] = getInterface({ terminal: true, prompt: '' }); + fi.emit('data', 'the quick brown fox'); + assertCursorRowsAndCols(rli, 0, 19); + + // Delete left character + fi.emit('keypress', '.', { ctrl: true, name: 'h' }); + assertCursorRowsAndCols(rli, 0, 18); + rli.on('line', common.mustCall((line) => { + assert.strictEqual(line, 'the quick brown fo'); + })); + fi.emit('data', '\n'); + rli.close(); +} + +// deleteLeft astral character +{ + const [rli, fi] = getInterface({ terminal: true, prompt: '' }); + fi.emit('data', '💻'); + assertCursorRowsAndCols(rli, 0, 2); + // Delete left character + fi.emit('keypress', '.', { ctrl: true, name: 'h' }); + assertCursorRowsAndCols(rli, 0, 0); + rli.on('line', common.mustCall((line) => { + assert.strictEqual(line, ''); + })); + fi.emit('data', '\n'); + rli.close(); +} + +// deleteRight +{ + const [rli, fi] = getInterface({ terminal: true, prompt: '' }); + fi.emit('data', 'the quick brown fox'); + + // Go to the start of the line + fi.emit('keypress', '.', { ctrl: true, name: 'a' }); + assertCursorRowsAndCols(rli, 0, 0); + + // Delete right character + fi.emit('keypress', '.', { ctrl: true, name: 'd' }); + assertCursorRowsAndCols(rli, 0, 0); + rli.on('line', common.mustCall((line) => { + assert.strictEqual(line, 'he quick brown fox'); + })); + fi.emit('data', '\n'); + rli.close(); +} + +// deleteRight astral character +{ + const [rli, fi] = getInterface({ terminal: true, prompt: '' }); + fi.emit('data', '💻'); + + // Go to the start of the line + fi.emit('keypress', '.', { ctrl: true, name: 'a' }); + assertCursorRowsAndCols(rli, 0, 0); + + // Delete right character + fi.emit('keypress', '.', { ctrl: true, name: 'd' }); + assertCursorRowsAndCols(rli, 0, 0); + rli.on('line', common.mustCall((line) => { + assert.strictEqual(line, ''); + })); + fi.emit('data', '\n'); + rli.close(); +} + +// deleteLineLeft +{ + const [rli, fi] = getInterface({ terminal: true, prompt: '' }); + fi.emit('data', 'the quick brown fox'); + assertCursorRowsAndCols(rli, 0, 19); + + // Delete from current to start of line + fi.emit('keypress', '.', { ctrl: true, shift: true, name: 'backspace' }); + assertCursorRowsAndCols(rli, 0, 0); + rli.on('line', common.mustCall((line) => { + assert.strictEqual(line, ''); + })); + fi.emit('data', '\n'); + rli.close(); +} + +// deleteLineRight +{ + const [rli, fi] = getInterface({ terminal: true, prompt: '' }); + fi.emit('data', 'the quick brown fox'); + + // Go to the start of the line + fi.emit('keypress', '.', { ctrl: true, name: 'a' }); + assertCursorRowsAndCols(rli, 0, 0); + + // Delete from current to end of line + fi.emit('keypress', '.', { ctrl: true, shift: true, name: 'delete' }); + assertCursorRowsAndCols(rli, 0, 0); + rli.on('line', common.mustCall((line) => { + assert.strictEqual(line, ''); + })); + fi.emit('data', '\n'); + rli.close(); +} + +// Close readline interface +{ + const [rli, fi] = getInterface({ terminal: true, prompt: '' }); + fi.emit('keypress', '.', { ctrl: true, name: 'c' }); + assert(rli.closed); +} + +// Multi-line input cursor position +{ + const [rli, fi] = getInterface({ terminal: true, prompt: '' }); + fi.columns = 10; + fi.emit('data', 'multi-line text'); + assertCursorRowsAndCols(rli, 1, 5); + rli.close(); +} + +// Multi-line input cursor position and long tabs +{ + const [rli, fi] = getInterface({ tabSize: 16, terminal: true, prompt: '' }); + fi.columns = 10; + fi.emit('data', 'multi-line\ttext \t'); + assert.strictEqual(rli.cursor, 17); + assertCursorRowsAndCols(rli, 3, 2); + rli.close(); +} + +// Check for the default tab size. +{ + const [rli, fi] = getInterface({ terminal: true, prompt: '' }); + fi.emit('data', 'the quick\tbrown\tfox'); + assert.strictEqual(rli.cursor, 19); + // The first tab is 7 spaces long, the second one 3 spaces. + assertCursorRowsAndCols(rli, 0, 27); +} + +// Multi-line prompt cursor position +{ + const [rli, fi] = getInterface({ + terminal: true, + prompt: '\nfilledline\nwraping text\n> ' + }); + fi.columns = 10; + fi.emit('data', 't'); + assertCursorRowsAndCols(rli, 4, 3); + rli.close(); +} + +// Clear the whole screen +{ + const [rli, fi] = getInterface({ terminal: true, prompt: '' }); + const lines = ['line 1', 'line 2', 'line 3']; + fi.emit('data', lines.join('\n')); + fi.emit('keypress', '.', { ctrl: true, name: 'l' }); + assertCursorRowsAndCols(rli, 0, 6); + rli.on('line', common.mustCall((line) => { + assert.strictEqual(line, 'line 3'); + })); + fi.emit('data', '\n'); + rli.close(); +} + +// Wide characters should be treated as two columns. +assert.strictEqual(getStringWidth('a'), 1); +assert.strictEqual(getStringWidth('あ'), 2); +assert.strictEqual(getStringWidth('谢'), 2); +assert.strictEqual(getStringWidth('고'), 2); +assert.strictEqual(getStringWidth(String.fromCodePoint(0x1f251)), 2); +assert.strictEqual(getStringWidth('abcde'), 5); +assert.strictEqual(getStringWidth('古池や'), 6); +assert.strictEqual(getStringWidth('ノード.js'), 9); +assert.strictEqual(getStringWidth('你好'), 4); +assert.strictEqual(getStringWidth('안녕하세요'), 10); +assert.strictEqual(getStringWidth('A\ud83c\ude00BC'), 5); +assert.strictEqual(getStringWidth('👨‍👩‍👦‍👦'), 8); +assert.strictEqual(getStringWidth('🐕𐐷あ💻😀'), 9); +// TODO(BridgeAR): This should have a width of 4. +assert.strictEqual(getStringWidth('⓬⓪'), 2); +assert.strictEqual(getStringWidth('\u0301\u200D\u200E'), 0); + +// Check if vt control chars are stripped +assert.strictEqual(stripVTControlCharacters('\u001b[31m> \u001b[39m'), '> '); +assert.strictEqual( + stripVTControlCharacters('\u001b[31m> \u001b[39m> '), + '> > ' +); +assert.strictEqual(stripVTControlCharacters('\u001b[31m\u001b[39m'), ''); +assert.strictEqual(stripVTControlCharacters('> '), '> '); +assert.strictEqual(getStringWidth('\u001b[31m> \u001b[39m'), 2); +assert.strictEqual(getStringWidth('\u001b[31m> \u001b[39m> '), 4); +assert.strictEqual(getStringWidth('\u001b[31m\u001b[39m'), 0); +assert.strictEqual(getStringWidth('> '), 2); + +// Check EventEmitter memory leak +for (let i = 0; i < 12; i++) { + const rl = readline.createInterface({ + input: process.stdin, + output: process.stdout + }); + rl.close(); + assert.strictEqual(isWarned(process.stdin._events), false); + assert.strictEqual(isWarned(process.stdout._events), false); +} + +[true, false].forEach(function(terminal) { + // Disable history + { + const [rli, fi] = getInterface({ terminal, historySize: 0 }); + assert.strictEqual(rli.historySize, 0); + + fi.emit('data', 'asdf\n'); + assert.deepStrictEqual(rli.history, []); + rli.close(); + } + + // Default history size 30 + { + const [rli, fi] = getInterface({ terminal }); + assert.strictEqual(rli.historySize, 30); + + fi.emit('data', 'asdf\n'); + assert.deepStrictEqual(rli.history, terminal ? ['asdf'] : []); + rli.close(); + } + + // Sending a full line + { + const [rli, fi] = getInterface({ terminal }); + rli.on('line', common.mustCall((line) => { + assert.strictEqual(line, 'asdf'); + })); + fi.emit('data', 'asdf\n'); + } + + // Ensure that options.signal.removeEventListener was called + { + const ac = new AbortController(); + const signal = ac.signal; + const [rli] = getInterface({ terminal }); + signal.removeEventListener = common.mustCall( + (event, onAbortFn) => { + assert.strictEqual(event, 'abort'); + assert.strictEqual(onAbortFn.name, 'onAbort'); + }); + + rli.question('hello?', { signal }).then(common.mustCall()); + + rli.write('bar\n'); + ac.abort(); + rli.close(); + } + + // Sending a blank line + { + const [rli, fi] = getInterface({ terminal }); + rli.on('line', common.mustCall((line) => { + assert.strictEqual(line, ''); + })); + fi.emit('data', '\n'); + } + + // Sending a single character with no newline and then a newline + { + const [rli, fi] = getInterface({ terminal }); + let called = false; + rli.on('line', (line) => { + called = true; + assert.strictEqual(line, 'a'); + }); + fi.emit('data', 'a'); + assert.ok(!called); + fi.emit('data', '\n'); + assert.ok(called); + rli.close(); + } + + // Sending multiple newlines at once + { + const [rli, fi] = getInterface({ terminal }); + const expectedLines = ['foo', 'bar', 'baz']; + rli.on('line', common.mustCall((line) => { + assert.strictEqual(line, expectedLines.shift()); + }, expectedLines.length)); + fi.emit('data', `${expectedLines.join('\n')}\n`); + rli.close(); + } + + // Sending multiple newlines at once that does not end with a new line + { + const [rli, fi] = getInterface({ terminal }); + const expectedLines = ['foo', 'bar', 'baz', 'bat']; + rli.on('line', common.mustCall((line) => { + assert.strictEqual(line, expectedLines.shift()); + }, expectedLines.length - 1)); + fi.emit('data', expectedLines.join('\n')); + rli.close(); + } + + // Sending multiple newlines at once that does not end with a new(empty) + // line and a `end` event + { + const [rli, fi] = getInterface({ terminal }); + const expectedLines = ['foo', 'bar', 'baz', '']; + rli.on('line', common.mustCall((line) => { + assert.strictEqual(line, expectedLines.shift()); + }, expectedLines.length - 1)); + rli.on('close', common.mustCall()); + fi.emit('data', expectedLines.join('\n')); + fi.emit('end'); + rli.close(); + } + + // Sending a multi-byte utf8 char over multiple writes + { + const buf = Buffer.from('☮', 'utf8'); + const [rli, fi] = getInterface({ terminal }); + let callCount = 0; + rli.on('line', function(line) { + callCount++; + assert.strictEqual(line, buf.toString('utf8')); + }); + for (const i of buf) { + fi.emit('data', Buffer.from([i])); + } + assert.strictEqual(callCount, 0); + fi.emit('data', '\n'); + assert.strictEqual(callCount, 1); + rli.close(); + } + + // Calling readline without `new` + { + const [rli, fi] = getInterface({ terminal }); + rli.on('line', common.mustCall((line) => { + assert.strictEqual(line, 'asdf'); + })); + fi.emit('data', 'asdf\n'); + rli.close(); + } + + // Calling the question callback + { + const [rli] = getInterface({ terminal }); + rli.question('foo?').then(common.mustCall((answer) => { + assert.strictEqual(answer, 'bar'); + })); + rli.write('bar\n'); + rli.close(); + } + + // Calling the question callback with abort signal + { + const [rli] = getInterface({ terminal }); + const { signal } = new AbortController(); + rli.question('foo?', { signal }).then(common.mustCall((answer) => { + assert.strictEqual(answer, 'bar'); + })); + rli.write('bar\n'); + rli.close(); + } + + // Aborting a question + { + const ac = new AbortController(); + const signal = ac.signal; + const [rli] = getInterface({ terminal }); + rli.on('line', common.mustCall((line) => { + assert.strictEqual(line, 'bar'); + })); + assert.rejects(rli.question('hello?', { signal }), { name: 'AbortError' }) + .then(common.mustCall()); + ac.abort(); + rli.write('bar\n'); + rli.close(); + } + + (async () => { + const [rli] = getInterface({ terminal }); + const signal = AbortSignal.abort('boom'); + await assert.rejects(rli.question('hello', { signal }), { + cause: 'boom', + }); + rli.close(); + })().then(common.mustCall()); + + // Throw an error when question is executed with an aborted signal + { + const ac = new AbortController(); + const signal = ac.signal; + ac.abort(); + const [rli] = getInterface({ terminal }); + assert.rejects( + rli.question('hello?', { signal }), + { + name: 'AbortError' + } + ).then(common.mustCall()); + rli.close(); + } + + // Call question after close + { + const [rli, fi] = getInterface({ terminal }); + rli.question('What\'s your name?').then(common.mustCall((name) => { + assert.strictEqual(name, 'Node.js'); + rli.close(); + rli.question('How are you?') + .then(common.mustNotCall(), common.expectsError({ + code: 'ERR_USE_AFTER_CLOSE', + name: 'Error' + })); + assert.notStrictEqual(rli.getPrompt(), 'How are you?'); + })); + fi.emit('data', 'Node.js\n'); + } + + + // Can create a new readline Interface with a null output argument + { + const [rli, fi] = getInterface({ output: null, terminal }); + rli.on('line', common.mustCall((line) => { + assert.strictEqual(line, 'asdf'); + })); + fi.emit('data', 'asdf\n'); + + rli.setPrompt('ddd> '); + rli.prompt(); + rli.write("really shouldn't be seeing this"); + rli.question('What do you think of node.js? ', function(answer) { + console.log('Thank you for your valuable feedback:', answer); + rli.close(); + }); + } + + // Calling the getPrompt method + { + const expectedPrompts = ['$ ', '> ']; + const [rli] = getInterface({ terminal }); + for (const prompt of expectedPrompts) { + rli.setPrompt(prompt); + assert.strictEqual(rli.getPrompt(), prompt); + } + } + + { + const expected = terminal ? + ['\u001b[1G', '\u001b[0J', '$ ', '\u001b[3G'] : + ['$ ']; + + const output = new Writable({ + write: common.mustCall((chunk, enc, cb) => { + assert.strictEqual(chunk.toString(), expected.shift()); + cb(); + rl.close(); + }, expected.length) + }); + + const rl = readline.createInterface({ + input: new Readable({ read: common.mustCall() }), + output, + prompt: '$ ', + terminal + }); + + rl.prompt(); + + assert.strictEqual(rl.getPrompt(), '$ '); + } + + { + const fi = new FakeInput(); + assert.deepStrictEqual(fi.listeners(terminal ? 'keypress' : 'data'), []); + } + + // Emit two line events when the delay + // between \r and \n exceeds crlfDelay + { + const crlfDelay = 200; + const [rli, fi] = getInterface({ terminal, crlfDelay }); + let callCount = 0; + rli.on('line', function(line) { + callCount++; + }); + fi.emit('data', '\r'); + setTimeout(common.mustCall(() => { + fi.emit('data', '\n'); + assert.strictEqual(callCount, 2); + rli.close(); + }), crlfDelay + 10); + } + + // For the purposes of the following tests, we do not care about the exact + // value of crlfDelay, only that the behaviour conforms to what's expected. + // Setting it to Infinity allows the test to succeed even under extreme + // CPU stress. + const crlfDelay = Infinity; + + // Set crlfDelay to `Infinity` is allowed + { + const delay = 200; + const [rli, fi] = getInterface({ terminal, crlfDelay }); + let callCount = 0; + rli.on('line', function(line) { + callCount++; + }); + fi.emit('data', '\r'); + setTimeout(common.mustCall(() => { + fi.emit('data', '\n'); + assert.strictEqual(callCount, 1); + rli.close(); + }), delay); + } + + // Sending multiple newlines at once that does not end with a new line + // and a `end` event(last line is) + + // \r\n should emit one line event, not two + { + const [rli, fi] = getInterface({ terminal, crlfDelay }); + const expectedLines = ['foo', 'bar', 'baz', 'bat']; + rli.on('line', common.mustCall((line) => { + assert.strictEqual(line, expectedLines.shift()); + }, expectedLines.length - 1)); + fi.emit('data', expectedLines.join('\r\n')); + rli.close(); + } + + // \r\n should emit one line event when split across multiple writes. + { + const [rli, fi] = getInterface({ terminal, crlfDelay }); + const expectedLines = ['foo', 'bar', 'baz', 'bat']; + let callCount = 0; + rli.on('line', common.mustCall((line) => { + assert.strictEqual(line, expectedLines[callCount]); + callCount++; + }, expectedLines.length)); + expectedLines.forEach((line) => { + fi.emit('data', `${line}\r`); + fi.emit('data', '\n'); + }); + rli.close(); + } + + // Emit one line event when the delay between \r and \n is + // over the default crlfDelay but within the setting value. + { + const delay = 125; + const [rli, fi] = getInterface({ terminal, crlfDelay }); + let callCount = 0; + rli.on('line', () => callCount++); + fi.emit('data', '\r'); + setTimeout(common.mustCall(() => { + fi.emit('data', '\n'); + assert.strictEqual(callCount, 1); + rli.close(); + }), delay); + } +}); + +// Ensure that the _wordLeft method works even for large input +{ + const input = new Readable({ + read() { + this.push('\x1B[1;5D'); // CTRL + Left + this.push(null); + }, + }); + const output = new Writable({ + write: common.mustCall((data, encoding, cb) => { + assert.strictEqual(rl.cursor, rl.line.length - 1); + cb(); + }), + }); + const rl = new readline.createInterface({ + input, + output, + terminal: true, + }); + rl.line = `a${' '.repeat(1e6)}a`; + rl.cursor = rl.line.length; +} diff --git a/cli/tests/node_compat/test/parallel/test-readline-reopen.js b/cli/tests/node_compat/test/parallel/test-readline-reopen.js new file mode 100644 index 00000000000000..1a9ddcf9ea17f5 --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-readline-reopen.js @@ -0,0 +1,51 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +'use strict'; + +// Regression test for https://github.com/nodejs/node/issues/13557 +// Tests that multiple subsequent readline instances can re-use an input stream. + +const common = require('../common'); +const assert = require('assert'); +const readline = require('readline'); +const { PassThrough } = require('stream'); + +const input = new PassThrough(); +const output = new PassThrough(); + +const rl1 = readline.createInterface({ + input, + output, + terminal: true +}); + +rl1.on('line', common.mustCall(rl1OnLine)); + +// Write a line plus the first byte of a UTF-8 multibyte character to make sure +// that it doesn’t get lost when closing the readline instance. +input.write(Buffer.concat([ + Buffer.from('foo\n'), + Buffer.from([ 0xe2 ]), // Exactly one third of a ☃ snowman. +])); + +function rl1OnLine(line) { + assert.strictEqual(line, 'foo'); + rl1.close(); + const rl2 = readline.createInterface({ + input, + output, + terminal: true + }); + + rl2.on('line', common.mustCall((line) => { + assert.strictEqual(line, '☃bar'); + rl2.close(); + })); + input.write(Buffer.from([0x98, 0x83])); // The rest of the ☃ snowman. + input.write('bar\n'); +} diff --git a/cli/tests/node_compat/test/parallel/test-readline-set-raw-mode.js b/cli/tests/node_compat/test/parallel/test-readline-set-raw-mode.js new file mode 100644 index 00000000000000..0b09cc49351288 --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-readline-set-raw-mode.js @@ -0,0 +1,97 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. + +'use strict'; +require('../common'); +const assert = require('assert'); +const readline = require('readline'); +const Stream = require('stream'); + +const stream = new Stream(); +let expectedRawMode = true; +let rawModeCalled = false; +let resumeCalled = false; +let pauseCalled = false; + +stream.setRawMode = function(mode) { + rawModeCalled = true; + assert.strictEqual(mode, expectedRawMode); +}; +stream.resume = function() { + resumeCalled = true; +}; +stream.pause = function() { + pauseCalled = true; +}; + +// When the "readline" starts in "terminal" mode, +// then setRawMode(true) should be called +const rli = readline.createInterface({ + input: stream, + output: stream, + terminal: true +}); +assert(rli.terminal); +assert(rawModeCalled); +assert(resumeCalled); +assert(!pauseCalled); + + +// pause() should call *not* call setRawMode() +rawModeCalled = false; +resumeCalled = false; +pauseCalled = false; +rli.pause(); +assert(!rawModeCalled); +assert(!resumeCalled); +assert(pauseCalled); + + +// resume() should *not* call setRawMode() +rawModeCalled = false; +resumeCalled = false; +pauseCalled = false; +rli.resume(); +assert(!rawModeCalled); +assert(resumeCalled); +assert(!pauseCalled); + + +// close() should call setRawMode(false) +expectedRawMode = false; +rawModeCalled = false; +resumeCalled = false; +pauseCalled = false; +rli.close(); +assert(rawModeCalled); +assert(!resumeCalled); +assert(pauseCalled); + +assert.deepStrictEqual(stream.listeners('keypress'), []); +// One data listener for the keypress events. +assert.strictEqual(stream.listeners('data').length, 1); diff --git a/cli/tests/node_compat/test/parallel/test-readline-undefined-columns.js b/cli/tests/node_compat/test/parallel/test-readline-undefined-columns.js new file mode 100644 index 00000000000000..b185b0d93076a3 --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-readline-undefined-columns.js @@ -0,0 +1,53 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +'use strict'; + +const common = require('../common'); +const assert = require('assert'); +const PassThrough = require('stream').PassThrough; +const readline = require('readline'); + +common.skipIfDumbTerminal(); + +// Checks that tab completion still works +// when output column size is undefined + +const iStream = new PassThrough(); +const oStream = new PassThrough(); + +readline.createInterface({ + terminal: true, + input: iStream, + output: oStream, + completer: function(line, cb) { + cb(null, [['process.stdout', 'process.stdin', 'process.stderr'], line]); + } +}); + +let output = ''; + +oStream.on('data', function(data) { + output += data; +}); + +oStream.on('end', common.mustCall(() => { + const expect = 'process.stdout\r\n' + + 'process.stdin\r\n' + + 'process.stderr'; + assert.match(output, new RegExp(expect)); +})); + +iStream.write('process.s\t'); + +// Completion works. +assert.match(output, /process\.std\b/); +// Completion doesn’t show all results yet. +assert.doesNotMatch(output, /stdout/); + +iStream.write('\t'); +oStream.end(); diff --git a/cli/tests/node_compat/test/parallel/test-readline.js b/cli/tests/node_compat/test/parallel/test-readline.js new file mode 100644 index 00000000000000..36b729cd592c76 --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-readline.js @@ -0,0 +1,158 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +'use strict'; +const common = require('../common'); +const { PassThrough } = require('stream'); +const readline = require('readline'); +const assert = require('assert'); + +common.skipIfDumbTerminal(); + +{ + const input = new PassThrough(); + const rl = readline.createInterface({ + terminal: true, + input: input + }); + + rl.on('line', common.mustCall((data) => { + assert.strictEqual(data, 'abc'); + })); + + input.end('abc'); +} + +{ + const input = new PassThrough(); + const rl = readline.createInterface({ + terminal: true, + input: input + }); + + rl.on('line', common.mustNotCall('must not be called before newline')); + + input.write('abc'); +} + +{ + const input = new PassThrough(); + const rl = readline.createInterface({ + terminal: true, + input: input + }); + + rl.on('line', common.mustCall((data) => { + assert.strictEqual(data, 'abc'); + })); + + input.write('abc\n'); +} + +{ + const input = new PassThrough(); + const rl = readline.createInterface({ + terminal: true, + input: input + }); + + rl.write('foo'); + assert.strictEqual(rl.cursor, 3); + + const key = { + xterm: { + home: ['\x1b[H', { ctrl: true, name: 'a' }], + end: ['\x1b[F', { ctrl: true, name: 'e' }], + }, + gnome: { + home: ['\x1bOH', { ctrl: true, name: 'a' }], + end: ['\x1bOF', { ctrl: true, name: 'e' }] + }, + rxvt: { + home: ['\x1b[7', { ctrl: true, name: 'a' }], + end: ['\x1b[8', { ctrl: true, name: 'e' }] + }, + putty: { + home: ['\x1b[1~', { ctrl: true, name: 'a' }], + end: ['\x1b[>~', { ctrl: true, name: 'e' }] + } + }; + + [key.xterm, key.gnome, key.rxvt, key.putty].forEach(function(key) { + rl.write.apply(rl, key.home); + assert.strictEqual(rl.cursor, 0); + rl.write.apply(rl, key.end); + assert.strictEqual(rl.cursor, 3); + }); + +} + +{ + const input = new PassThrough(); + const rl = readline.createInterface({ + terminal: true, + input: input + }); + + const key = { + xterm: { + home: ['\x1b[H', { ctrl: true, name: 'a' }], + metab: ['\x1bb', { meta: true, name: 'b' }], + metaf: ['\x1bf', { meta: true, name: 'f' }], + } + }; + + rl.write('foo bar.hop/zoo'); + rl.write.apply(rl, key.xterm.home); + [ + { cursor: 4, key: key.xterm.metaf }, + { cursor: 7, key: key.xterm.metaf }, + { cursor: 8, key: key.xterm.metaf }, + { cursor: 11, key: key.xterm.metaf }, + { cursor: 12, key: key.xterm.metaf }, + { cursor: 15, key: key.xterm.metaf }, + { cursor: 12, key: key.xterm.metab }, + { cursor: 11, key: key.xterm.metab }, + { cursor: 8, key: key.xterm.metab }, + { cursor: 7, key: key.xterm.metab }, + { cursor: 4, key: key.xterm.metab }, + { cursor: 0, key: key.xterm.metab }, + ].forEach(function(action) { + rl.write.apply(rl, action.key); + assert.strictEqual(rl.cursor, action.cursor); + }); +} + +{ + const input = new PassThrough(); + const rl = readline.createInterface({ + terminal: true, + input: input + }); + + const key = { + xterm: { + home: ['\x1b[H', { ctrl: true, name: 'a' }], + metad: ['\x1bd', { meta: true, name: 'd' }] + } + }; + + rl.write('foo bar.hop/zoo'); + rl.write.apply(rl, key.xterm.home); + [ + 'bar.hop/zoo', + '.hop/zoo', + 'hop/zoo', + '/zoo', + 'zoo', + '', + ].forEach(function(expectedLine) { + rl.write.apply(rl, key.xterm.metad); + assert.strictEqual(rl.cursor, 0); + assert.strictEqual(rl.line, expectedLine); + }); +} diff --git a/cli/tests/node_compat/test/parallel/test-stdin-from-file-spawn.js b/cli/tests/node_compat/test/parallel/test-stdin-from-file-spawn.js new file mode 100644 index 00000000000000..89a0860a325da4 --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-stdin-from-file-spawn.js @@ -0,0 +1,52 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.8.0 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +// TODO(cjihrig): 'run -A require.ts' should not be needed in +// execSync() call at the bottom of this test. + +'use strict'; +const common = require('../common'); +const process = require('process'); + +let defaultShell; +if (process.platform === 'linux' || process.platform === 'darwin') { + defaultShell = '/bin/sh'; +} else if (process.platform === 'win32') { + defaultShell = 'cmd.exe'; +} else { + common.skip('This is test exists only on Linux/Win32/OSX'); +} + +const { execSync } = require('child_process'); +const fs = require('fs'); +const path = require('path'); +const tmpdir = require('../common/tmpdir'); + +const tmpDir = tmpdir.path; +tmpdir.refresh(); +const tmpCmdFile = path.join(tmpDir, 'test-stdin-from-file-spawn-cmd'); +const tmpJsFile = path.join(tmpDir, 'test-stdin-from-file-spawn.js'); +fs.writeFileSync(tmpCmdFile, 'echo hello'); +fs.writeFileSync(tmpJsFile, ` +'use strict'; +const { spawn } = require('child_process'); +// Reference the object to invoke the getter +process.stdin; +setTimeout(() => { + let ok = false; + const child = spawn(process.env.SHELL || '${defaultShell}', + [], { stdio: ['inherit', 'pipe'] }); + child.stdout.on('data', () => { + ok = true; + }); + child.on('close', () => { + process.exit(ok ? 0 : -1); + }); +}, 100); +`); + +execSync(`${process.argv[0]} run -A require.ts ${tmpJsFile} < ${tmpCmdFile}`); diff --git a/cli/tests/node_compat/test/parallel/test-stream-add-abort-signal.js b/cli/tests/node_compat/test/parallel/test-stream-add-abort-signal.js new file mode 100644 index 00000000000000..b88f3e981294e9 --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-stream-add-abort-signal.js @@ -0,0 +1,34 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +// Flags: --expose-internals +'use strict'; + +require('../common'); +const assert = require('assert'); +const { addAbortSignal, Readable } = require('stream'); +const { + addAbortSignalNoValidate, +} = require('internal/streams/add-abort-signal'); + +{ + assert.throws(() => { + addAbortSignal('INVALID_SIGNAL'); + }, /ERR_INVALID_ARG_TYPE/); + + const ac = new AbortController(); + assert.throws(() => { + addAbortSignal(ac.signal, 'INVALID_STREAM'); + }, /ERR_INVALID_ARG_TYPE/); +} + +{ + const r = new Readable({ + read: () => {}, + }); + assert.deepStrictEqual(r, addAbortSignalNoValidate('INVALID_SIGNAL', r)); +} diff --git a/cli/tests/node_compat/test/parallel/test-stream-aliases-legacy.js b/cli/tests/node_compat/test/parallel/test-stream-aliases-legacy.js new file mode 100644 index 00000000000000..bcae28143a7bec --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-stream-aliases-legacy.js @@ -0,0 +1,21 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +'use strict'; + +require('../common'); + +const assert = require('assert'); +const stream = require('stream'); + +// Verify that all individual aliases are left in place. + +assert.strictEqual(stream.Readable, require('_stream_readable')); +assert.strictEqual(stream.Writable, require('_stream_writable')); +assert.strictEqual(stream.Duplex, require('_stream_duplex')); +assert.strictEqual(stream.Transform, require('_stream_transform')); +assert.strictEqual(stream.PassThrough, require('_stream_passthrough')); diff --git a/cli/tests/node_compat/test/parallel/test-stream-asIndexedPairs.mjs b/cli/tests/node_compat/test/parallel/test-stream-asIndexedPairs.mjs new file mode 100644 index 00000000000000..f7f8b6d7cad273 --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-stream-asIndexedPairs.mjs @@ -0,0 +1,60 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +import '../common/index.mjs'; +import { Readable } from 'stream'; +import { deepStrictEqual, rejects, throws } from 'assert'; + +{ + // asIndexedPairs with a synchronous stream + const pairs = await Readable.from([1, 2, 3]).asIndexedPairs().toArray(); + deepStrictEqual(pairs, [[0, 1], [1, 2], [2, 3]]); + const empty = await Readable.from([]).asIndexedPairs().toArray(); + deepStrictEqual(empty, []); +} + +{ + // asIndexedPairs works an asynchronous streams + const asyncFrom = (...args) => Readable.from(...args).map(async (x) => x); + const pairs = await asyncFrom([1, 2, 3]).asIndexedPairs().toArray(); + deepStrictEqual(pairs, [[0, 1], [1, 2], [2, 3]]); + const empty = await asyncFrom([]).asIndexedPairs().toArray(); + deepStrictEqual(empty, []); +} + +{ + // Does not enumerate an infinite stream + const infinite = () => Readable.from(async function* () { + while (true) yield 1; + }()); + const pairs = await infinite().asIndexedPairs().take(3).toArray(); + deepStrictEqual(pairs, [[0, 1], [1, 1], [2, 1]]); + const empty = await infinite().asIndexedPairs().take(0).toArray(); + deepStrictEqual(empty, []); +} + +{ + // AbortSignal + await rejects(async () => { + const ac = new AbortController(); + const { signal } = ac; + const p = Readable.from([1, 2, 3]).asIndexedPairs({ signal }).toArray(); + ac.abort(); + await p; + }, { name: 'AbortError' }); + + await rejects(async () => { + const signal = AbortSignal.abort(); + await Readable.from([1, 2, 3]).asIndexedPairs({ signal }).toArray(); + }, /AbortError/); +} + +{ + // Error cases + throws(() => Readable.from([1]).asIndexedPairs(1), /ERR_INVALID_ARG_TYPE/); + throws(() => Readable.from([1]).asIndexedPairs({ signal: true }), /ERR_INVALID_ARG_TYPE/); +} diff --git a/cli/tests/node_compat/test/parallel/test-stream-auto-destroy.js b/cli/tests/node_compat/test/parallel/test-stream-auto-destroy.js new file mode 100644 index 00000000000000..4b47ca88591b93 --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-stream-auto-destroy.js @@ -0,0 +1,119 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +'use strict'; +const common = require('../common'); +const stream = require('stream'); +const assert = require('assert'); + +{ + const r = new stream.Readable({ + autoDestroy: true, + read() { + this.push('hello'); + this.push('world'); + this.push(null); + }, + destroy: common.mustCall((err, cb) => cb()) + }); + + let ended = false; + + r.resume(); + + r.on('end', common.mustCall(() => { + ended = true; + })); + + r.on('close', common.mustCall(() => { + assert(ended); + })); +} + +{ + const w = new stream.Writable({ + autoDestroy: true, + write(data, enc, cb) { + cb(null); + }, + destroy: common.mustCall((err, cb) => cb()) + }); + + let finished = false; + + w.write('hello'); + w.write('world'); + w.end(); + + w.on('finish', common.mustCall(() => { + finished = true; + })); + + w.on('close', common.mustCall(() => { + assert(finished); + })); +} + +{ + const t = new stream.Transform({ + autoDestroy: true, + transform(data, enc, cb) { + cb(null, data); + }, + destroy: common.mustCall((err, cb) => cb()) + }); + + let ended = false; + let finished = false; + + t.write('hello'); + t.write('world'); + t.end(); + + t.resume(); + + t.on('end', common.mustCall(() => { + ended = true; + })); + + t.on('finish', common.mustCall(() => { + finished = true; + })); + + t.on('close', common.mustCall(() => { + assert(ended); + assert(finished); + })); +} + +{ + const r = new stream.Readable({ + read() { + r2.emit('error', new Error('fail')); + } + }); + const r2 = new stream.Readable({ + autoDestroy: true, + destroy: common.mustCall((err, cb) => cb()) + }); + + r.pipe(r2); +} + +{ + const r = new stream.Readable({ + read() { + w.emit('error', new Error('fail')); + } + }); + const w = new stream.Writable({ + autoDestroy: true, + destroy: common.mustCall((err, cb) => cb()) + }); + + r.pipe(w); +} diff --git a/cli/tests/node_compat/test/parallel/test-stream-await-drain-writers-in-synchronously-recursion-write.js b/cli/tests/node_compat/test/parallel/test-stream-await-drain-writers-in-synchronously-recursion-write.js new file mode 100644 index 00000000000000..564c64467fe28e --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-stream-await-drain-writers-in-synchronously-recursion-write.js @@ -0,0 +1,35 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +'use strict'; +const common = require('../common'); +const { PassThrough } = require('stream'); + +const encode = new PassThrough({ + highWaterMark: 1 +}); + +const decode = new PassThrough({ + highWaterMark: 1 +}); + +const send = common.mustCall((buf) => { + encode.write(buf); +}, 4); + +let i = 0; +const onData = common.mustCall(() => { + if (++i === 2) { + send(Buffer.from([0x3])); + send(Buffer.from([0x4])); + } +}, 4); + +encode.pipe(decode).on('data', onData); + +send(Buffer.from([0x1])); +send(Buffer.from([0x2])); diff --git a/cli/tests/node_compat/test/parallel/test-stream-backpressure.js b/cli/tests/node_compat/test/parallel/test-stream-backpressure.js new file mode 100644 index 00000000000000..f971e2ec5b4764 --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-stream-backpressure.js @@ -0,0 +1,46 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +'use strict'; + +const common = require('../common'); +const assert = require('assert'); +const stream = require('stream'); + +let pushes = 0; +const total = 65500 + 40 * 1024; +const rs = new stream.Readable({ + read: common.mustCall(function() { + if (pushes++ === 10) { + this.push(null); + return; + } + + const length = this._readableState.length; + + // We are at most doing two full runs of _reads + // before stopping, because Readable is greedy + // to keep its buffer full + assert(length <= total); + + this.push(Buffer.alloc(65500)); + for (let i = 0; i < 40; i++) { + this.push(Buffer.alloc(1024)); + } + + // We will be over highWaterMark at this point + // but a new call to _read is scheduled anyway. + }, 11) +}); + +const ws = stream.Writable({ + write: common.mustCall(function(data, enc, cb) { + setImmediate(cb); + }, 41 * 10) +}); + +rs.pipe(ws); diff --git a/cli/tests/node_compat/test/parallel/test-stream-big-packet.js b/cli/tests/node_compat/test/parallel/test-stream-big-packet.js new file mode 100644 index 00000000000000..f523acd8b33c3d --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-stream-big-packet.js @@ -0,0 +1,72 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. + +'use strict'; +require('../common'); +const assert = require('assert'); +const stream = require('stream'); + +let passed = false; + +class TestStream extends stream.Transform { + _transform(chunk, encoding, done) { + if (!passed) { + // Char 'a' only exists in the last write + passed = chunk.toString().includes('a'); + } + done(); + } +} + +const s1 = new stream.Transform({ + transform(chunk, encoding, cb) { + process.nextTick(cb, null, chunk); + } +}); +const s2 = new stream.PassThrough(); +const s3 = new TestStream(); +s1.pipe(s3); +// Don't let s2 auto close which may close s3 +s2.pipe(s3, { end: false }); + +// We must write a buffer larger than highWaterMark +const big = Buffer.alloc(s1.writableHighWaterMark + 1, 'x'); + +// Since big is larger than highWaterMark, it will be buffered internally. +assert(!s1.write(big)); +// 'tiny' is small enough to pass through internal buffer. +assert(s2.write('tiny')); + +// Write some small data in next IO loop, which will never be written to s3 +// Because 'drain' event is not emitted from s1 and s1 is still paused +setImmediate(s1.write.bind(s1), 'later'); + +// Assert after two IO loops when all operations have been done. +process.on('exit', function() { + assert(passed, 'Large buffer is not handled properly by Writable Stream'); +}); diff --git a/cli/tests/node_compat/test/parallel/test-stream-big-push.js b/cli/tests/node_compat/test/parallel/test-stream-big-push.js new file mode 100644 index 00000000000000..c14a9c15ec868a --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-stream-big-push.js @@ -0,0 +1,81 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. + +'use strict'; +const common = require('../common'); +const assert = require('assert'); +const stream = require('stream'); +const str = 'asdfasdfasdfasdfasdf'; + +const r = new stream.Readable({ + highWaterMark: 5, + encoding: 'utf8' +}); + +let reads = 0; + +function _read() { + if (reads === 0) { + setTimeout(() => { + r.push(str); + }, 1); + reads++; + } else if (reads === 1) { + const ret = r.push(str); + assert.strictEqual(ret, false); + reads++; + } else { + r.push(null); + } +} + +r._read = common.mustCall(_read, 3); + +r.on('end', common.mustCall()); + +// Push some data in to start. +// We've never gotten any read event at this point. +const ret = r.push(str); +// Should be false. > hwm +assert(!ret); +let chunk = r.read(); +assert.strictEqual(chunk, str); +chunk = r.read(); +assert.strictEqual(chunk, null); + +r.once('readable', () => { + // This time, we'll get *all* the remaining data, because + // it's been added synchronously, as the read WOULD take + // us below the hwm, and so it triggered a _read() again, + // which synchronously added more, which we then return. + chunk = r.read(); + assert.strictEqual(chunk, str + str); + + chunk = r.read(); + assert.strictEqual(chunk, null); +}); diff --git a/cli/tests/node_compat/test/parallel/test-stream-buffer-list.js b/cli/tests/node_compat/test/parallel/test-stream-buffer-list.js new file mode 100644 index 00000000000000..a74dc56ab52d1d --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-stream-buffer-list.js @@ -0,0 +1,91 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +// Flags: --expose-internals +'use strict'; +require('../common'); +const assert = require('assert'); +const BufferList = require('internal/streams/buffer_list'); + +// Test empty buffer list. +const emptyList = new BufferList(); + +emptyList.shift(); +assert.deepStrictEqual(emptyList, new BufferList()); + +assert.strictEqual(emptyList.join(','), ''); + +assert.deepStrictEqual(emptyList.concat(0), Buffer.alloc(0)); + +const buf = Buffer.from('foo'); + +function testIterator(list, count) { + // test iterator + let len = 0; + // eslint-disable-next-line no-unused-vars + for (const x of list) { + len++; + } + assert.strictEqual(len, count); +} + +// Test buffer list with one element. +const list = new BufferList(); +testIterator(list, 0); + +list.push(buf); +testIterator(list, 1); +for (const x of list) { + assert.strictEqual(x, buf); +} + +const copy = list.concat(3); +testIterator(copy, 3); + +assert.notStrictEqual(copy, buf); +assert.deepStrictEqual(copy, buf); + +assert.strictEqual(list.join(','), 'foo'); + +const shifted = list.shift(); +testIterator(list, 0); +assert.strictEqual(shifted, buf); +assert.deepStrictEqual(list, new BufferList()); + +{ + const list = new BufferList(); + list.push('foo'); + list.push('bar'); + list.push('foo'); + list.push('bar'); + assert.strictEqual(list.consume(6, true), 'foobar'); + assert.strictEqual(list.consume(6, true), 'foobar'); +} + +{ + const list = new BufferList(); + list.push('foo'); + list.push('bar'); + assert.strictEqual(list.consume(5, true), 'fooba'); +} + +{ + const list = new BufferList(); + list.push(buf); + list.push(buf); + list.push(buf); + list.push(buf); + assert.strictEqual(list.consume(6).toString(), 'foofoo'); + assert.strictEqual(list.consume(6).toString(), 'foofoo'); +} + +{ + const list = new BufferList(); + list.push(buf); + list.push(buf); + assert.strictEqual(list.consume(5).toString(), 'foofo'); +} diff --git a/cli/tests/node_compat/test/parallel/test-stream-catch-rejections.js b/cli/tests/node_compat/test/parallel/test-stream-catch-rejections.js new file mode 100644 index 00000000000000..066d68193e529f --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-stream-catch-rejections.js @@ -0,0 +1,58 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +'use strict'; + +const common = require('../common'); +const stream = require('stream'); +const assert = require('assert'); + +{ + const r = new stream.Readable({ + captureRejections: true, + read() { + } + }); + r.push('hello'); + r.push('world'); + + const err = new Error('kaboom'); + + r.on('error', common.mustCall((_err) => { + assert.strictEqual(err, _err); + assert.strictEqual(r.destroyed, true); + })); + + r.on('data', async () => { + throw err; + }); +} + +{ + const w = new stream.Writable({ + captureRejections: true, + highWaterMark: 1, + write(chunk, enc, cb) { + process.nextTick(cb); + } + }); + + const err = new Error('kaboom'); + + w.write('hello', () => { + w.write('world'); + }); + + w.on('error', common.mustCall((_err) => { + assert.strictEqual(err, _err); + assert.strictEqual(w.destroyed, true); + })); + + w.on('drain', common.mustCall(async () => { + throw err; + }, 2)); +} diff --git a/cli/tests/node_compat/test/parallel/test-stream-construct.js b/cli/tests/node_compat/test/parallel/test-stream-construct.js new file mode 100644 index 00000000000000..06634c00e6603b --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-stream-construct.js @@ -0,0 +1,287 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +'use strict'; + +const common = require('../common'); +const { Writable, Readable, Duplex } = require('stream'); +const assert = require('assert'); + +{ + // Multiple callback. + new Writable({ + construct: common.mustCall((callback) => { + callback(); + callback(); + }) + }).on('error', common.expectsError({ + name: 'Error', + code: 'ERR_MULTIPLE_CALLBACK' + })); +} + +{ + // Multiple callback. + new Readable({ + construct: common.mustCall((callback) => { + callback(); + callback(); + }) + }).on('error', common.expectsError({ + name: 'Error', + code: 'ERR_MULTIPLE_CALLBACK' + })); +} + +{ + // Synchronous error. + + new Writable({ + construct: common.mustCall((callback) => { + callback(new Error('test')); + }) + }).on('error', common.expectsError({ + name: 'Error', + message: 'test' + })); +} + +{ + // Synchronous error. + + new Readable({ + construct: common.mustCall((callback) => { + callback(new Error('test')); + }) + }).on('error', common.expectsError({ + name: 'Error', + message: 'test' + })); +} + +{ + // Asynchronous error. + + new Writable({ + construct: common.mustCall((callback) => { + process.nextTick(callback, new Error('test')); + }) + }).on('error', common.expectsError({ + name: 'Error', + message: 'test' + })); +} + +{ + // Asynchronous error. + + new Readable({ + construct: common.mustCall((callback) => { + process.nextTick(callback, new Error('test')); + }) + }).on('error', common.expectsError({ + name: 'Error', + message: 'test' + })); +} + +function testDestroy(factory) { + { + let constructed = false; + const s = factory({ + construct: common.mustCall((cb) => { + constructed = true; + process.nextTick(cb); + }) + }); + s.on('close', common.mustCall(() => { + assert.strictEqual(constructed, true); + })); + s.destroy(); + } + + { + let constructed = false; + const s = factory({ + construct: common.mustCall((cb) => { + constructed = true; + process.nextTick(cb); + }) + }); + s.on('close', common.mustCall(() => { + assert.strictEqual(constructed, true); + })); + s.destroy(null, () => { + assert.strictEqual(constructed, true); + }); + } + + { + let constructed = false; + const s = factory({ + construct: common.mustCall((cb) => { + constructed = true; + process.nextTick(cb); + }) + }); + s.on('close', common.mustCall(() => { + assert.strictEqual(constructed, true); + })); + s.destroy(); + } + + + { + let constructed = false; + const s = factory({ + construct: common.mustCall((cb) => { + constructed = true; + process.nextTick(cb); + }) + }); + s.on('close', common.mustCall(() => { + assert.strictEqual(constructed, true); + })); + s.on('error', common.mustCall((err) => { + assert.strictEqual(err.message, 'kaboom'); + })); + s.destroy(new Error('kaboom'), (err) => { + assert.strictEqual(err.message, 'kaboom'); + assert.strictEqual(constructed, true); + }); + } + + { + let constructed = false; + const s = factory({ + construct: common.mustCall((cb) => { + constructed = true; + process.nextTick(cb); + }) + }); + s.on('error', common.mustCall(() => { + assert.strictEqual(constructed, true); + })); + s.on('close', common.mustCall(() => { + assert.strictEqual(constructed, true); + })); + s.destroy(new Error()); + } +} +testDestroy((opts) => new Readable({ + read: common.mustNotCall(), + ...opts +})); +testDestroy((opts) => new Writable({ + write: common.mustNotCall(), + final: common.mustNotCall(), + ...opts +})); + +{ + let constructed = false; + const r = new Readable({ + autoDestroy: true, + construct: common.mustCall((cb) => { + constructed = true; + process.nextTick(cb); + }), + read: common.mustCall(() => { + assert.strictEqual(constructed, true); + r.push(null); + }) + }); + r.on('close', common.mustCall(() => { + assert.strictEqual(constructed, true); + })); + r.on('data', common.mustNotCall()); +} + +{ + let constructed = false; + const w = new Writable({ + autoDestroy: true, + construct: common.mustCall((cb) => { + constructed = true; + process.nextTick(cb); + }), + write: common.mustCall((chunk, encoding, cb) => { + assert.strictEqual(constructed, true); + process.nextTick(cb); + }), + final: common.mustCall((cb) => { + assert.strictEqual(constructed, true); + process.nextTick(cb); + }) + }); + w.on('close', common.mustCall(() => { + assert.strictEqual(constructed, true); + })); + w.end('data'); +} + +{ + let constructed = false; + const w = new Writable({ + autoDestroy: true, + construct: common.mustCall((cb) => { + constructed = true; + process.nextTick(cb); + }), + write: common.mustNotCall(), + final: common.mustCall((cb) => { + assert.strictEqual(constructed, true); + process.nextTick(cb); + }) + }); + w.on('close', common.mustCall(() => { + assert.strictEqual(constructed, true); + })); + w.end(); +} + +{ + new Duplex({ + construct: common.mustCall() + }); +} + +{ + // https://github.com/nodejs/node/issues/34448 + + let constructed = false; + const d = new Duplex({ + readable: false, + construct: common.mustCall((callback) => { + setImmediate(common.mustCall(() => { + constructed = true; + callback(); + })); + }), + write(chunk, encoding, callback) { + callback(); + }, + read() { + this.push(null); + } + }); + d.resume(); + d.end('foo'); + d.on('close', common.mustCall(() => { + assert.strictEqual(constructed, true); + })); +} + +{ + // Construct should not cause stream to read. + new Readable({ + construct: common.mustCall((callback) => { + callback(); + }), + read: common.mustNotCall() + }); +} diff --git a/cli/tests/node_compat/test/parallel/test-stream-destroy-event-order.js b/cli/tests/node_compat/test/parallel/test-stream-destroy-event-order.js new file mode 100644 index 00000000000000..3ad7bd02ac9523 --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-stream-destroy-event-order.js @@ -0,0 +1,31 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +'use strict'; + +const common = require('../common'); +const assert = require('assert'); +const { Readable } = require('stream'); + +const rs = new Readable({ + read() {} +}); + +let closed = false; +let errored = false; + +rs.on('close', common.mustCall(() => { + closed = true; + assert(errored); +})); + +rs.on('error', common.mustCall((err) => { + errored = true; + assert(!closed); +})); + +rs.destroy(new Error('kaboom')); diff --git a/cli/tests/node_compat/test/parallel/test-stream-duplex-destroy.js b/cli/tests/node_compat/test/parallel/test-stream-duplex-destroy.js new file mode 100644 index 00000000000000..fad7ddfd6bca14 --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-stream-duplex-destroy.js @@ -0,0 +1,264 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +'use strict'; + +const common = require('../common'); +const { Duplex } = require('stream'); +const assert = require('assert'); + +{ + const duplex = new Duplex({ + write(chunk, enc, cb) { cb(); }, + read() {} + }); + + duplex.resume(); + + duplex.on('end', common.mustNotCall()); + duplex.on('finish', common.mustNotCall()); + duplex.on('close', common.mustCall()); + + duplex.destroy(); + assert.strictEqual(duplex.destroyed, true); +} + +{ + const duplex = new Duplex({ + write(chunk, enc, cb) { cb(); }, + read() {} + }); + duplex.resume(); + + const expected = new Error('kaboom'); + + duplex.on('end', common.mustNotCall()); + duplex.on('finish', common.mustNotCall()); + duplex.on('error', common.mustCall((err) => { + assert.strictEqual(err, expected); + })); + + duplex.destroy(expected); + assert.strictEqual(duplex.destroyed, true); +} + +{ + const duplex = new Duplex({ + write(chunk, enc, cb) { cb(); }, + read() {} + }); + + duplex._destroy = common.mustCall(function(err, cb) { + assert.strictEqual(err, expected); + cb(err); + }); + + const expected = new Error('kaboom'); + + duplex.on('finish', common.mustNotCall('no finish event')); + duplex.on('error', common.mustCall((err) => { + assert.strictEqual(err, expected); + })); + + duplex.destroy(expected); + assert.strictEqual(duplex.destroyed, true); +} + +{ + const expected = new Error('kaboom'); + const duplex = new Duplex({ + write(chunk, enc, cb) { cb(); }, + read() {}, + destroy: common.mustCall(function(err, cb) { + assert.strictEqual(err, expected); + cb(); + }) + }); + duplex.resume(); + + duplex.on('end', common.mustNotCall('no end event')); + duplex.on('finish', common.mustNotCall('no finish event')); + + // Error is swallowed by the custom _destroy + duplex.on('error', common.mustNotCall('no error event')); + duplex.on('close', common.mustCall()); + + duplex.destroy(expected); + assert.strictEqual(duplex.destroyed, true); +} + +{ + const duplex = new Duplex({ + write(chunk, enc, cb) { cb(); }, + read() {} + }); + + duplex._destroy = common.mustCall(function(err, cb) { + assert.strictEqual(err, null); + cb(); + }); + + duplex.destroy(); + assert.strictEqual(duplex.destroyed, true); +} + +{ + const duplex = new Duplex({ + write(chunk, enc, cb) { cb(); }, + read() {} + }); + duplex.resume(); + + duplex._destroy = common.mustCall(function(err, cb) { + assert.strictEqual(err, null); + process.nextTick(() => { + this.push(null); + this.end(); + cb(); + }); + }); + + const fail = common.mustNotCall('no finish or end event'); + + duplex.on('finish', fail); + duplex.on('end', fail); + + duplex.destroy(); + + duplex.removeListener('end', fail); + duplex.removeListener('finish', fail); + duplex.on('end', common.mustNotCall()); + duplex.on('finish', common.mustNotCall()); + assert.strictEqual(duplex.destroyed, true); +} + +{ + const duplex = new Duplex({ + write(chunk, enc, cb) { cb(); }, + read() {} + }); + + const expected = new Error('kaboom'); + + duplex._destroy = common.mustCall(function(err, cb) { + assert.strictEqual(err, null); + cb(expected); + }); + + duplex.on('finish', common.mustNotCall('no finish event')); + duplex.on('end', common.mustNotCall('no end event')); + duplex.on('error', common.mustCall((err) => { + assert.strictEqual(err, expected); + })); + + duplex.destroy(); + assert.strictEqual(duplex.destroyed, true); +} + +{ + const duplex = new Duplex({ + write(chunk, enc, cb) { cb(); }, + read() {}, + allowHalfOpen: true + }); + duplex.resume(); + + duplex.on('finish', common.mustNotCall()); + duplex.on('end', common.mustNotCall()); + + duplex.destroy(); + assert.strictEqual(duplex.destroyed, true); +} + +{ + const duplex = new Duplex({ + write(chunk, enc, cb) { cb(); }, + read() {}, + }); + + duplex.destroyed = true; + assert.strictEqual(duplex.destroyed, true); + + // The internal destroy() mechanism should not be triggered + duplex.on('finish', common.mustNotCall()); + duplex.on('end', common.mustNotCall()); + duplex.destroy(); +} + +{ + function MyDuplex() { + assert.strictEqual(this.destroyed, false); + this.destroyed = false; + Duplex.call(this); + } + + Object.setPrototypeOf(MyDuplex.prototype, Duplex.prototype); + Object.setPrototypeOf(MyDuplex, Duplex); + + new MyDuplex(); +} + +{ + const duplex = new Duplex({ + writable: false, + autoDestroy: true, + write(chunk, enc, cb) { cb(); }, + read() {}, + }); + duplex.push(null); + duplex.resume(); + duplex.on('close', common.mustCall()); +} + +{ + const duplex = new Duplex({ + readable: false, + autoDestroy: true, + write(chunk, enc, cb) { cb(); }, + read() {}, + }); + duplex.end(); + duplex.on('close', common.mustCall()); +} + +{ + const duplex = new Duplex({ + allowHalfOpen: false, + autoDestroy: true, + write(chunk, enc, cb) { cb(); }, + read() {}, + }); + duplex.push(null); + duplex.resume(); + const orgEnd = duplex.end; + duplex.end = common.mustNotCall(); + duplex.on('end', () => { + // Ensure end() is called in next tick to allow + // any pending writes to be invoked first. + process.nextTick(() => { + duplex.end = common.mustCall(orgEnd); + }); + }); + duplex.on('close', common.mustCall()); +} +{ + // Check abort signal + const controller = new AbortController(); + const { signal } = controller; + const duplex = new Duplex({ + write(chunk, enc, cb) { cb(); }, + read() {}, + signal, + }); + let count = 0; + duplex.on('error', common.mustCall((e) => { + assert.strictEqual(count++, 0); // Ensure not called twice + assert.strictEqual(e.name, 'AbortError'); + })); + duplex.on('close', common.mustCall()); + controller.abort(); +} diff --git a/cli/tests/node_compat/test/parallel/test-stream-duplex-end.js b/cli/tests/node_compat/test/parallel/test-stream-duplex-end.js new file mode 100644 index 00000000000000..d78ca03e9cef6a --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-stream-duplex-end.js @@ -0,0 +1,48 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +'use strict'; + +const common = require('../common'); +const assert = require('assert'); +const Duplex = require('stream').Duplex; + +{ + const stream = new Duplex({ + read() {} + }); + assert.strictEqual(stream.allowHalfOpen, true); + stream.on('finish', common.mustNotCall()); + assert.strictEqual(stream.listenerCount('end'), 0); + stream.resume(); + stream.push(null); +} + +{ + const stream = new Duplex({ + read() {}, + allowHalfOpen: false + }); + assert.strictEqual(stream.allowHalfOpen, false); + stream.on('finish', common.mustCall()); + assert.strictEqual(stream.listenerCount('end'), 0); + stream.resume(); + stream.push(null); +} + +{ + const stream = new Duplex({ + read() {}, + allowHalfOpen: false + }); + assert.strictEqual(stream.allowHalfOpen, false); + stream._writableState.ended = true; + stream.on('finish', common.mustNotCall()); + assert.strictEqual(stream.listenerCount('end'), 0); + stream.resume(); + stream.push(null); +} diff --git a/cli/tests/node_compat/test/parallel/test-stream-duplex-from.js b/cli/tests/node_compat/test/parallel/test-stream-duplex-from.js new file mode 100644 index 00000000000000..2ade73bf14f1e8 --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-stream-duplex-from.js @@ -0,0 +1,287 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +'use strict'; + +const common = require('../common'); +const assert = require('assert'); +const { Duplex, Readable, Writable, pipeline } = require('stream'); +const { Blob } = require('buffer'); + +{ + const d = Duplex.from({ + readable: new Readable({ + read() { + this.push('asd'); + this.push(null); + } + }) + }); + assert.strictEqual(d.readable, true); + assert.strictEqual(d.writable, false); + d.once('readable', common.mustCall(function() { + assert.strictEqual(d.read().toString(), 'asd'); + })); + d.once('end', common.mustCall(function() { + assert.strictEqual(d.readable, false); + })); +} + +{ + const d = Duplex.from(new Readable({ + read() { + this.push('asd'); + this.push(null); + } + })); + assert.strictEqual(d.readable, true); + assert.strictEqual(d.writable, false); + d.once('readable', common.mustCall(function() { + assert.strictEqual(d.read().toString(), 'asd'); + })); + d.once('end', common.mustCall(function() { + assert.strictEqual(d.readable, false); + })); +} + +{ + let ret = ''; + const d = Duplex.from(new Writable({ + write(chunk, encoding, callback) { + ret += chunk; + callback(); + } + })); + assert.strictEqual(d.readable, false); + assert.strictEqual(d.writable, true); + d.end('asd'); + d.on('finish', common.mustCall(function() { + assert.strictEqual(d.writable, false); + assert.strictEqual(ret, 'asd'); + })); +} + +{ + let ret = ''; + const d = Duplex.from({ + writable: new Writable({ + write(chunk, encoding, callback) { + ret += chunk; + callback(); + } + }) + }); + assert.strictEqual(d.readable, false); + assert.strictEqual(d.writable, true); + d.end('asd'); + d.on('finish', common.mustCall(function() { + assert.strictEqual(d.writable, false); + assert.strictEqual(ret, 'asd'); + })); +} + +{ + let ret = ''; + const d = Duplex.from({ + readable: new Readable({ + read() { + this.push('asd'); + this.push(null); + } + }), + writable: new Writable({ + write(chunk, encoding, callback) { + ret += chunk; + callback(); + } + }) + }); + assert.strictEqual(d.readable, true); + assert.strictEqual(d.writable, true); + d.once('readable', common.mustCall(function() { + assert.strictEqual(d.read().toString(), 'asd'); + })); + d.once('end', common.mustCall(function() { + assert.strictEqual(d.readable, false); + })); + d.end('asd'); + d.once('finish', common.mustCall(function() { + assert.strictEqual(d.writable, false); + assert.strictEqual(ret, 'asd'); + })); +} + +{ + const d = Duplex.from(Promise.resolve('asd')); + assert.strictEqual(d.readable, true); + assert.strictEqual(d.writable, false); + d.once('readable', common.mustCall(function() { + assert.strictEqual(d.read().toString(), 'asd'); + })); + d.once('end', common.mustCall(function() { + assert.strictEqual(d.readable, false); + })); +} + +{ + // https://github.com/nodejs/node/issues/40497 + pipeline( + ['abc\ndef\nghi'], + Duplex.from(async function * (source) { + let rest = ''; + for await (const chunk of source) { + const lines = (rest + chunk.toString()).split('\n'); + rest = lines.pop(); + for (const line of lines) { + yield line; + } + } + yield rest; + }), + async function * (source) { // eslint-disable-line require-yield + let ret = ''; + for await (const x of source) { + ret += x; + } + assert.strictEqual(ret, 'abcdefghi'); + }, + common.mustCall(() => {}), + ); +} + +// Ensure that isDuplexNodeStream was called +{ + const duplex = new Duplex(); + assert.strictEqual(Duplex.from(duplex), duplex); +} + +// Ensure that Duplex.from works for blobs +{ + const blob = new Blob(['blob']); + const expectedByteLength = blob.size; + const duplex = Duplex.from(blob); + duplex.on('data', common.mustCall((arrayBuffer) => { + assert.strictEqual(arrayBuffer.byteLength, expectedByteLength); + })); +} + +// Ensure that given a promise rejection it emits an error +{ + const myErrorMessage = 'myCustomError'; + Duplex.from(Promise.reject(myErrorMessage)) + .on('error', common.mustCall((error) => { + assert.strictEqual(error, myErrorMessage); + })); +} + +// Ensure that given a promise rejection on an async function it emits an error +{ + const myErrorMessage = 'myCustomError'; + async function asyncFn() { + return Promise.reject(myErrorMessage); + } + + Duplex.from(asyncFn) + .on('error', common.mustCall((error) => { + assert.strictEqual(error, myErrorMessage); + })); +} + +// Ensure that Duplex.from throws an Invalid return value when function is void +{ + assert.throws(() => Duplex.from(() => {}), { + code: 'ERR_INVALID_RETURN_VALUE', + }); +} + +// Ensure data if a sub object has a readable stream it's duplexified +{ + const msg = Buffer.from('hello'); + const duplex = Duplex.from({ + readable: Readable({ + read() { + this.push(msg); + this.push(null); + } + }) + }).on('data', common.mustCall((data) => { + assert.strictEqual(data, msg); + })); + + assert.strictEqual(duplex.writable, false); +} + +// Ensure data if a sub object has a writable stream it's duplexified +{ + const msg = Buffer.from('hello'); + const duplex = Duplex.from({ + writable: Writable({ + write: common.mustCall((data) => { + assert.strictEqual(data, msg); + }) + }) + }); + + duplex.write(msg); + assert.strictEqual(duplex.readable, false); +} + +// Ensure data if a sub object has a writable and readable stream it's duplexified +{ + const msg = Buffer.from('hello'); + + const duplex = Duplex.from({ + readable: Readable({ + read() { + this.push(msg); + this.push(null); + } + }), + writable: Writable({ + write: common.mustCall((data) => { + assert.strictEqual(data, msg); + }) + }) + }); + + duplex.pipe(duplex) + .on('data', common.mustCall((data) => { + assert.strictEqual(data, msg); + assert.strictEqual(duplex.readable, true); + assert.strictEqual(duplex.writable, true); + })) + .on('end', common.mustCall()); +} + +// Ensure that given readable stream that throws an error it calls destroy +{ + const myErrorMessage = 'error!'; + const duplex = Duplex.from(Readable({ + read() { + throw new Error(myErrorMessage); + } + })); + duplex.on('error', common.mustCall((msg) => { + assert.strictEqual(msg.message, myErrorMessage); + })); +} + +// Ensure that given writable stream that throws an error it calls destroy +{ + const myErrorMessage = 'error!'; + const duplex = Duplex.from(Writable({ + write(chunk, enc, cb) { + cb(myErrorMessage); + } + })); + + duplex.on('error', common.mustCall((msg) => { + assert.strictEqual(msg, myErrorMessage); + })); + + duplex.write('test'); +} diff --git a/cli/tests/node_compat/test/parallel/test-stream-duplex-props.js b/cli/tests/node_compat/test/parallel/test-stream-duplex-props.js new file mode 100644 index 00000000000000..16487c0c2f212e --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-stream-duplex-props.js @@ -0,0 +1,38 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +'use strict'; + +require('../common'); +const assert = require('assert'); +const { Duplex } = require('stream'); + +{ + const d = new Duplex({ + objectMode: true, + highWaterMark: 100 + }); + + assert.strictEqual(d.writableObjectMode, true); + assert.strictEqual(d.writableHighWaterMark, 100); + assert.strictEqual(d.readableObjectMode, true); + assert.strictEqual(d.readableHighWaterMark, 100); +} + +{ + const d = new Duplex({ + readableObjectMode: false, + readableHighWaterMark: 10, + writableObjectMode: true, + writableHighWaterMark: 100 + }); + + assert.strictEqual(d.writableObjectMode, true); + assert.strictEqual(d.writableHighWaterMark, 100); + assert.strictEqual(d.readableObjectMode, false); + assert.strictEqual(d.readableHighWaterMark, 10); +} diff --git a/cli/tests/node_compat/test/parallel/test-stream-duplex-readable-end.js b/cli/tests/node_compat/test/parallel/test-stream-duplex-readable-end.js new file mode 100644 index 00000000000000..84f42014977097 --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-stream-duplex-readable-end.js @@ -0,0 +1,36 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +'use strict'; +// https://github.com/nodejs/node/issues/35926 +const common = require('../common'); +const assert = require('assert'); +const stream = require('stream'); + +let loops = 5; + +const src = new stream.Readable({ + read() { + if (loops--) + this.push(Buffer.alloc(20000)); + } +}); + +const dst = new stream.Transform({ + transform(chunk, output, fn) { + this.push(null); + fn(); + } +}); + +src.pipe(dst); + +dst.on('data', () => { }); +dst.on('end', common.mustCall(() => { + assert.strictEqual(loops, 3); + assert.ok(src.isPaused()); +})); diff --git a/cli/tests/node_compat/test/parallel/test-stream-duplex-writable-finished.js b/cli/tests/node_compat/test/parallel/test-stream-duplex-writable-finished.js new file mode 100644 index 00000000000000..99f7cc85516684 --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-stream-duplex-writable-finished.js @@ -0,0 +1,37 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +'use strict'; + +const common = require('../common'); +const { Duplex } = require('stream'); +const assert = require('assert'); + +// basic +{ + // Find it on Duplex.prototype + assert(Object.hasOwn(Duplex.prototype, 'writableFinished')); +} + +// event +{ + const duplex = new Duplex(); + + duplex._write = (chunk, encoding, cb) => { + // The state finished should start in false. + assert.strictEqual(duplex.writableFinished, false); + cb(); + }; + + duplex.on('finish', common.mustCall(() => { + assert.strictEqual(duplex.writableFinished, true); + })); + + duplex.end('testing finished state', common.mustCall(() => { + assert.strictEqual(duplex.writableFinished, true); + })); +} diff --git a/cli/tests/node_compat/test/parallel/test-stream-duplex.js b/cli/tests/node_compat/test/parallel/test-stream-duplex.js new file mode 100644 index 00000000000000..a03d1f8f2c0deb --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-stream-duplex.js @@ -0,0 +1,140 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. +'use strict'; + +const common = require('../common'); +const assert = require('assert'); +const Duplex = require('stream').Duplex; +const { ReadableStream, WritableStream } = require('stream/web'); + +const stream = new Duplex({ objectMode: true }); + +assert(Duplex() instanceof Duplex); +assert(stream._readableState.objectMode); +assert(stream._writableState.objectMode); +assert(stream.allowHalfOpen); +assert.strictEqual(stream.listenerCount('end'), 0); + +let written; +let read; + +stream._write = (obj, _, cb) => { + written = obj; + cb(); +}; + +stream._read = () => {}; + +stream.on('data', (obj) => { + read = obj; +}); + +stream.push({ val: 1 }); +stream.end({ val: 2 }); + +process.on('exit', () => { + assert.strictEqual(read.val, 1); + assert.strictEqual(written.val, 2); +}); + +// Duplex.fromWeb +{ + const dataToRead = Buffer.from('hello'); + const dataToWrite = Buffer.from('world'); + + const readable = new ReadableStream({ + start(controller) { + controller.enqueue(dataToRead); + }, + }); + + const writable = new WritableStream({ + write: common.mustCall((chunk) => { + assert.strictEqual(chunk, dataToWrite); + }) + }); + + const pair = { readable, writable }; + const duplex = Duplex.fromWeb(pair); + + duplex.write(dataToWrite); + duplex.once('data', common.mustCall((chunk) => { + assert.strictEqual(chunk, dataToRead); + })); +} + +// Duplex.fromWeb - using utf8 and objectMode +{ + const dataToRead = 'hello'; + const dataToWrite = 'world'; + + const readable = new ReadableStream({ + start(controller) { + controller.enqueue(dataToRead); + }, + }); + + const writable = new WritableStream({ + write: common.mustCall((chunk) => { + assert.strictEqual(chunk, dataToWrite); + }) + }); + + const pair = { + readable, + writable + }; + const duplex = Duplex.fromWeb(pair, { encoding: 'utf8', objectMode: true }); + + duplex.write(dataToWrite); + duplex.once('data', common.mustCall((chunk) => { + assert.strictEqual(chunk, dataToRead); + })); +} +// Duplex.toWeb +{ + const dataToRead = Buffer.from('hello'); + const dataToWrite = Buffer.from('world'); + + const duplex = Duplex({ + read() { + this.push(dataToRead); + this.push(null); + }, + write: common.mustCall((chunk) => { + assert.strictEqual(chunk, dataToWrite); + }) + }); + + const { writable, readable } = Duplex.toWeb(duplex); + writable.getWriter().write(dataToWrite); + + readable.getReader().read().then(common.mustCall((result) => { + assert.deepStrictEqual(Buffer.from(result.value), dataToRead); + })); +} diff --git a/cli/tests/node_compat/test/parallel/test-stream-end-paused.js b/cli/tests/node_compat/test/parallel/test-stream-end-paused.js new file mode 100644 index 00000000000000..5cd1947b7bf726 --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-stream-end-paused.js @@ -0,0 +1,57 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. + +'use strict'; +const common = require('../common'); +const assert = require('assert'); + +// Make sure we don't miss the end event for paused 0-length streams + +const Readable = require('stream').Readable; +const stream = new Readable(); +let calledRead = false; +stream._read = function() { + assert(!calledRead); + calledRead = true; + this.push(null); +}; + +stream.on('data', function() { + throw new Error('should not ever get data'); +}); +stream.pause(); + +setTimeout(common.mustCall(function() { + stream.on('end', common.mustCall()); + stream.resume(); +}), 1); + +process.on('exit', function() { + assert(calledRead); + console.log('ok'); +}); diff --git a/cli/tests/node_compat/test/parallel/test-stream-error-once.js b/cli/tests/node_compat/test/parallel/test-stream-error-once.js new file mode 100644 index 00000000000000..e6998c3f2eec8d --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-stream-error-once.js @@ -0,0 +1,26 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +'use strict'; +const common = require('../common'); +const { Writable, Readable } = require('stream'); + +{ + const writable = new Writable(); + writable.on('error', common.mustCall()); + writable.end(); + writable.write('h'); + writable.write('h'); +} + +{ + const readable = new Readable(); + readable.on('error', common.mustCall()); + readable.push(null); + readable.push('h'); + readable.push('h'); +} diff --git a/cli/tests/node_compat/test/parallel/test-stream-events-prepend.js b/cli/tests/node_compat/test/parallel/test-stream-events-prepend.js new file mode 100644 index 00000000000000..8360a63f652d3c --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-stream-events-prepend.js @@ -0,0 +1,33 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +'use strict'; +const common = require('../common'); +const stream = require('stream'); + +class Writable extends stream.Writable { + constructor() { + super(); + this.prependListener = undefined; + } + + _write(chunk, end, cb) { + cb(); + } +} + +class Readable extends stream.Readable { + _read() { + this.push(null); + } +} + +const w = new Writable(); +w.on('pipe', common.mustCall()); + +const r = new Readable(); +r.pipe(w); diff --git a/cli/tests/node_compat/test/parallel/test-stream-inheritance.js b/cli/tests/node_compat/test/parallel/test-stream-inheritance.js new file mode 100644 index 00000000000000..b8f740d155699e --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-stream-inheritance.js @@ -0,0 +1,70 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +'use strict'; +require('../common'); +const assert = require('assert'); +const { Readable, Writable, Duplex, Transform } = require('stream'); + +const readable = new Readable({ read() {} }); +const writable = new Writable({ write() {} }); +const duplex = new Duplex({ read() {}, write() {} }); +const transform = new Transform({ transform() {} }); + +assert.ok(readable instanceof Readable); +assert.ok(!(writable instanceof Readable)); +assert.ok(duplex instanceof Readable); +assert.ok(transform instanceof Readable); + +assert.ok(!(readable instanceof Writable)); +assert.ok(writable instanceof Writable); +assert.ok(duplex instanceof Writable); +assert.ok(transform instanceof Writable); + +assert.ok(!(readable instanceof Duplex)); +assert.ok(!(writable instanceof Duplex)); +assert.ok(duplex instanceof Duplex); +assert.ok(transform instanceof Duplex); + +assert.ok(!(readable instanceof Transform)); +assert.ok(!(writable instanceof Transform)); +assert.ok(!(duplex instanceof Transform)); +assert.ok(transform instanceof Transform); + +assert.ok(!(null instanceof Writable)); +assert.ok(!(undefined instanceof Writable)); + +// Simple inheritance check for `Writable` works fine in a subclass constructor. +function CustomWritable() { + assert.ok( + this instanceof CustomWritable, + `${this} does not inherit from CustomWritable` + ); + assert.ok( + this instanceof Writable, + `${this} does not inherit from Writable` + ); +} + +Object.setPrototypeOf(CustomWritable, Writable); +Object.setPrototypeOf(CustomWritable.prototype, Writable.prototype); + +new CustomWritable(); + +assert.throws( + CustomWritable, + { + code: 'ERR_ASSERTION', + constructor: assert.AssertionError, + message: 'undefined does not inherit from CustomWritable' + } +); + +class OtherCustomWritable extends Writable {} + +assert(!(new OtherCustomWritable() instanceof CustomWritable)); +assert(!(new CustomWritable() instanceof OtherCustomWritable)); diff --git a/cli/tests/node_compat/test/parallel/test-stream-ispaused.js b/cli/tests/node_compat/test/parallel/test-stream-ispaused.js new file mode 100644 index 00000000000000..2f0a5939135a11 --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-stream-ispaused.js @@ -0,0 +1,51 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. + +'use strict'; +require('../common'); +const assert = require('assert'); +const stream = require('stream'); + +const readable = new stream.Readable(); + +// _read is a noop, here. +readable._read = Function(); + +// Default state of a stream is not "paused" +assert.ok(!readable.isPaused()); + +// Make the stream start flowing... +readable.on('data', Function()); + +// still not paused. +assert.ok(!readable.isPaused()); + +readable.pause(); +assert.ok(readable.isPaused()); +readable.resume(); +assert.ok(!readable.isPaused()); diff --git a/cli/tests/node_compat/test/parallel/test-stream-objectmode-undefined.js b/cli/tests/node_compat/test/parallel/test-stream-objectmode-undefined.js new file mode 100644 index 00000000000000..c1ce83e757a684 --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-stream-objectmode-undefined.js @@ -0,0 +1,51 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +'use strict'; +const common = require('../common'); +const assert = require('assert'); +const { Readable, Writable, Transform } = require('stream'); + +{ + const stream = new Readable({ + objectMode: true, + read: common.mustCall(() => { + stream.push(undefined); + stream.push(null); + }) + }); + + stream.on('data', common.mustCall((chunk) => { + assert.strictEqual(chunk, undefined); + })); +} + +{ + const stream = new Writable({ + objectMode: true, + write: common.mustCall((chunk) => { + assert.strictEqual(chunk, undefined); + }) + }); + + stream.write(undefined); +} + +{ + const stream = new Transform({ + objectMode: true, + transform: common.mustCall((chunk) => { + stream.push(chunk); + }) + }); + + stream.on('data', common.mustCall((chunk) => { + assert.strictEqual(chunk, undefined); + })); + + stream.write(undefined); +} diff --git a/cli/tests/node_compat/test/parallel/test-stream-once-readable-pipe.js b/cli/tests/node_compat/test/parallel/test-stream-once-readable-pipe.js new file mode 100644 index 00000000000000..c5722ebca4f485 --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-stream-once-readable-pipe.js @@ -0,0 +1,68 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +'use strict'; + +const common = require('../common'); +const assert = require('assert'); +const { Readable, Writable } = require('stream'); + +// This test ensures that if have 'readable' listener +// on Readable instance it will not disrupt the pipe. + +{ + let receivedData = ''; + const w = new Writable({ + write: (chunk, env, callback) => { + receivedData += chunk; + callback(); + }, + }); + + const data = ['foo', 'bar', 'baz']; + const r = new Readable({ + read: () => {}, + }); + + r.once('readable', common.mustCall()); + + r.pipe(w); + r.push(data[0]); + r.push(data[1]); + r.push(data[2]); + r.push(null); + + w.on('finish', common.mustCall(() => { + assert.strictEqual(receivedData, data.join('')); + })); +} + +{ + let receivedData = ''; + const w = new Writable({ + write: (chunk, env, callback) => { + receivedData += chunk; + callback(); + }, + }); + + const data = ['foo', 'bar', 'baz']; + const r = new Readable({ + read: () => {}, + }); + + r.pipe(w); + r.push(data[0]); + r.push(data[1]); + r.push(data[2]); + r.push(null); + r.once('readable', common.mustCall()); + + w.on('finish', common.mustCall(() => { + assert.strictEqual(receivedData, data.join('')); + })); +} diff --git a/cli/tests/node_compat/test/parallel/test-stream-pipe-after-end.js b/cli/tests/node_compat/test/parallel/test-stream-pipe-after-end.js new file mode 100644 index 00000000000000..bccc52e3e92537 --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-stream-pipe-after-end.js @@ -0,0 +1,76 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. + +'use strict'; +const common = require('../common'); +const assert = require('assert'); +const { Readable, Writable } = require('stream'); + +class TestReadable extends Readable { + constructor(opt) { + super(opt); + this._ended = false; + } + + _read() { + if (this._ended) + this.emit('error', new Error('_read called twice')); + this._ended = true; + this.push(null); + } +} + +class TestWritable extends Writable { + constructor(opt) { + super(opt); + this._written = []; + } + + _write(chunk, encoding, cb) { + this._written.push(chunk); + cb(); + } +} + +// This one should not emit 'end' until we read() from it later. +const ender = new TestReadable(); + +// What happens when you pipe() a Readable that's already ended? +const piper = new TestReadable(); +// pushes EOF null, and length=0, so this will trigger 'end' +piper.read(); + +setTimeout(common.mustCall(function() { + ender.on('end', common.mustCall()); + const c = ender.read(); + assert.strictEqual(c, null); + + const w = new TestWritable(); + w.on('finish', common.mustCall()); + piper.pipe(w); +}), 1); diff --git a/cli/tests/node_compat/test/parallel/test-stream-pipe-await-drain-manual-resume.js b/cli/tests/node_compat/test/parallel/test-stream-pipe-await-drain-manual-resume.js new file mode 100644 index 00000000000000..759172a443514d --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-stream-pipe-await-drain-manual-resume.js @@ -0,0 +1,82 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +'use strict'; +const common = require('../common'); +const stream = require('stream'); +const assert = require('assert'); + +// A consumer stream with a very low highWaterMark, which starts in a state +// where it buffers the chunk it receives rather than indicating that they +// have been consumed. +const writable = new stream.Writable({ + highWaterMark: 5 +}); + +let isCurrentlyBufferingWrites = true; +const queue = []; + +writable._write = (chunk, encoding, cb) => { + if (isCurrentlyBufferingWrites) + queue.push({ chunk, cb }); + else + cb(); +}; + +const readable = new stream.Readable({ + read() {} +}); + +readable.pipe(writable); + +readable.once('pause', common.mustCall(() => { + assert.strictEqual( + readable._readableState.awaitDrainWriters, + writable, + 'Expected awaitDrainWriters to be a Writable but instead got ' + + `${readable._readableState.awaitDrainWriters}` + ); + // First pause, resume manually. The next write() to writable will still + // return false, because chunks are still being buffered, so it will increase + // the awaitDrain counter again. + + process.nextTick(common.mustCall(() => { + readable.resume(); + })); + + readable.once('pause', common.mustCall(() => { + assert.strictEqual( + readable._readableState.awaitDrainWriters, + writable, + '.resume() should not reset the awaitDrainWriters, but instead got ' + + `${readable._readableState.awaitDrainWriters}` + ); + // Second pause, handle all chunks from now on. Once all callbacks that + // are currently queued up are handled, the awaitDrain drain counter should + // fall back to 0 and all chunks that are pending on the readable side + // should be flushed. + isCurrentlyBufferingWrites = false; + for (const queued of queue) + queued.cb(); + })); +})); + +readable.push(Buffer.alloc(100)); // Fill the writable HWM, first 'pause'. +readable.push(Buffer.alloc(100)); // Second 'pause'. +readable.push(Buffer.alloc(100)); // Should get through to the writable. +readable.push(null); + +writable.on('finish', common.mustCall(() => { + assert.strictEqual( + readable._readableState.awaitDrainWriters, + null, + `awaitDrainWriters should be reset to null + after all chunks are written but instead got + ${readable._readableState.awaitDrainWriters}` + ); + // Everything okay, all chunks were written. +})); diff --git a/cli/tests/node_compat/test/parallel/test-stream-pipe-await-drain-push-while-write.js b/cli/tests/node_compat/test/parallel/test-stream-pipe-await-drain-push-while-write.js new file mode 100644 index 00000000000000..b5a4eb247643fa --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-stream-pipe-await-drain-push-while-write.js @@ -0,0 +1,43 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +'use strict'; +const common = require('../common'); +const stream = require('stream'); +const assert = require('assert'); + +const writable = new stream.Writable({ + write: common.mustCall(function(chunk, encoding, cb) { + assert.strictEqual( + readable._readableState.awaitDrainWriters, + null, + ); + + if (chunk.length === 32 * 1024) { // first chunk + readable.push(Buffer.alloc(34 * 1024)); // above hwm + // We should check if awaitDrain counter is increased in the next + // tick, because awaitDrain is incremented after this method finished + process.nextTick(() => { + assert.strictEqual(readable._readableState.awaitDrainWriters, writable); + }); + } + + process.nextTick(cb); + }, 3) +}); + +// A readable stream which produces two buffers. +const bufs = [Buffer.alloc(32 * 1024), Buffer.alloc(33 * 1024)]; // above hwm +const readable = new stream.Readable({ + read: function() { + while (bufs.length > 0) { + this.push(bufs.shift()); + } + } +}); + +readable.pipe(writable); diff --git a/cli/tests/node_compat/test/parallel/test-stream-pipe-await-drain.js b/cli/tests/node_compat/test/parallel/test-stream-pipe-await-drain.js new file mode 100644 index 00000000000000..351bd6f2907a16 --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-stream-pipe-await-drain.js @@ -0,0 +1,74 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +'use strict'; +const common = require('../common'); +const stream = require('stream'); +const assert = require('assert'); + +// This is very similar to test-stream-pipe-cleanup-pause.js. + +const reader = new stream.Readable(); +const writer1 = new stream.Writable(); +const writer2 = new stream.Writable(); +const writer3 = new stream.Writable(); + +// 560000 is chosen here because it is larger than the (default) highWaterMark +// and will cause `.write()` to return false +// See: https://github.com/nodejs/node/issues/5820 +const buffer = Buffer.allocUnsafe(560000); + +reader._read = () => {}; + +writer1._write = common.mustCall(function(chunk, encoding, cb) { + this.emit('chunk-received'); + process.nextTick(cb); +}, 1); + +writer1.once('chunk-received', () => { + assert.strictEqual( + reader._readableState.awaitDrainWriters.size, + 0, + 'awaitDrain initial value should be 0, actual is ' + + reader._readableState.awaitDrainWriters.size + ); + setImmediate(() => { + // This one should *not* get through to writer1 because writer2 is not + // "done" processing. + reader.push(buffer); + }); +}); + +// A "slow" consumer: +writer2._write = common.mustCall((chunk, encoding, cb) => { + assert.strictEqual( + reader._readableState.awaitDrainWriters.size, + 1, + 'awaitDrain should be 1 after first push, actual is ' + + reader._readableState.awaitDrainWriters.size + ); + // Not calling cb here to "simulate" slow stream. + // This should be called exactly once, since the first .write() call + // will return false. +}, 1); + +writer3._write = common.mustCall((chunk, encoding, cb) => { + assert.strictEqual( + reader._readableState.awaitDrainWriters.size, + 2, + 'awaitDrain should be 2 after second push, actual is ' + + reader._readableState.awaitDrainWriters.size + ); + // Not calling cb here to "simulate" slow stream. + // This should be called exactly once, since the first .write() call + // will return false. +}, 1); + +reader.pipe(writer1); +reader.pipe(writer2); +reader.pipe(writer3); +reader.push(buffer); diff --git a/cli/tests/node_compat/test/parallel/test-stream-pipe-cleanup-pause.js b/cli/tests/node_compat/test/parallel/test-stream-pipe-cleanup-pause.js new file mode 100644 index 00000000000000..92a28eb96d5d15 --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-stream-pipe-cleanup-pause.js @@ -0,0 +1,44 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +'use strict'; +const common = require('../common'); +const stream = require('stream'); + +const reader = new stream.Readable(); +const writer1 = new stream.Writable(); +const writer2 = new stream.Writable(); + +// 560000 is chosen here because it is larger than the (default) highWaterMark +// and will cause `.write()` to return false +// See: https://github.com/nodejs/node/issues/2323 +const buffer = Buffer.allocUnsafe(560000); + +reader._read = () => {}; + +writer1._write = common.mustCall(function(chunk, encoding, cb) { + this.emit('chunk-received'); + cb(); +}, 1); +writer1.once('chunk-received', function() { + reader.unpipe(writer1); + reader.pipe(writer2); + reader.push(buffer); + setImmediate(function() { + reader.push(buffer); + setImmediate(function() { + reader.push(buffer); + }); + }); +}); + +writer2._write = common.mustCall(function(chunk, encoding, cb) { + cb(); +}, 3); + +reader.pipe(writer1); +reader.push(buffer); diff --git a/cli/tests/node_compat/test/parallel/test-stream-pipe-cleanup.js b/cli/tests/node_compat/test/parallel/test-stream-pipe-cleanup.js new file mode 100644 index 00000000000000..a26914e41e6712 --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-stream-pipe-cleanup.js @@ -0,0 +1,132 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. + +'use strict'; +// This test asserts that Stream.prototype.pipe does not leave listeners +// hanging on the source or dest. +require('../common'); +const stream = require('stream'); +const assert = require('assert'); + +function Writable() { + this.writable = true; + this.endCalls = 0; + stream.Stream.call(this); +} +Object.setPrototypeOf(Writable.prototype, stream.Stream.prototype); +Object.setPrototypeOf(Writable, stream.Stream); +Writable.prototype.end = function() { + this.endCalls++; +}; + +Writable.prototype.destroy = function() { + this.endCalls++; +}; + +function Readable() { + this.readable = true; + stream.Stream.call(this); +} +Object.setPrototypeOf(Readable.prototype, stream.Stream.prototype); +Object.setPrototypeOf(Readable, stream.Stream); + +function Duplex() { + this.readable = true; + Writable.call(this); +} +Object.setPrototypeOf(Duplex.prototype, Writable.prototype); +Object.setPrototypeOf(Duplex, Writable); + +let i = 0; +const limit = 100; + +let w = new Writable(); + +let r; + +for (i = 0; i < limit; i++) { + r = new Readable(); + r.pipe(w); + r.emit('end'); +} +assert.strictEqual(r.listeners('end').length, 0); +assert.strictEqual(w.endCalls, limit); + +w.endCalls = 0; + +for (i = 0; i < limit; i++) { + r = new Readable(); + r.pipe(w); + r.emit('close'); +} +assert.strictEqual(r.listeners('close').length, 0); +assert.strictEqual(w.endCalls, limit); + +w.endCalls = 0; + +r = new Readable(); + +for (i = 0; i < limit; i++) { + w = new Writable(); + r.pipe(w); + w.emit('close'); +} +assert.strictEqual(w.listeners('close').length, 0); + +r = new Readable(); +w = new Writable(); +const d = new Duplex(); +r.pipe(d); // pipeline A +d.pipe(w); // pipeline B +assert.strictEqual(r.listeners('end').length, 2); // A.onend, A.cleanup +assert.strictEqual(r.listeners('close').length, 2); // A.onclose, A.cleanup +assert.strictEqual(d.listeners('end').length, 2); // B.onend, B.cleanup +// A.cleanup, B.onclose, B.cleanup +assert.strictEqual(d.listeners('close').length, 3); +assert.strictEqual(w.listeners('end').length, 0); +assert.strictEqual(w.listeners('close').length, 1); // B.cleanup + +r.emit('end'); +assert.strictEqual(d.endCalls, 1); +assert.strictEqual(w.endCalls, 0); +assert.strictEqual(r.listeners('end').length, 0); +assert.strictEqual(r.listeners('close').length, 0); +assert.strictEqual(d.listeners('end').length, 2); // B.onend, B.cleanup +assert.strictEqual(d.listeners('close').length, 2); // B.onclose, B.cleanup +assert.strictEqual(w.listeners('end').length, 0); +assert.strictEqual(w.listeners('close').length, 1); // B.cleanup + +d.emit('end'); +assert.strictEqual(d.endCalls, 1); +assert.strictEqual(w.endCalls, 1); +assert.strictEqual(r.listeners('end').length, 0); +assert.strictEqual(r.listeners('close').length, 0); +assert.strictEqual(d.listeners('end').length, 0); +assert.strictEqual(d.listeners('close').length, 0); +assert.strictEqual(w.listeners('end').length, 0); +assert.strictEqual(w.listeners('close').length, 0); diff --git a/cli/tests/node_compat/test/parallel/test-stream-pipe-error-handling.js b/cli/tests/node_compat/test/parallel/test-stream-pipe-error-handling.js new file mode 100644 index 00000000000000..6b5371479980e4 --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-stream-pipe-error-handling.js @@ -0,0 +1,131 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. + +'use strict'; +const common = require('../common'); +const assert = require('assert'); +const { Stream, PassThrough } = require('stream'); + +{ + const source = new Stream(); + const dest = new Stream(); + + source.pipe(dest); + + let gotErr = null; + source.on('error', function(err) { + gotErr = err; + }); + + const err = new Error('This stream turned into bacon.'); + source.emit('error', err); + assert.strictEqual(gotErr, err); +} + +{ + const source = new Stream(); + const dest = new Stream(); + + source.pipe(dest); + + const err = new Error('This stream turned into bacon.'); + + let gotErr = null; + try { + source.emit('error', err); + } catch (e) { + gotErr = e; + } + + assert.strictEqual(gotErr, err); +} + +{ + const R = Stream.Readable; + const W = Stream.Writable; + + const r = new R({ autoDestroy: false }); + const w = new W({ autoDestroy: false }); + let removed = false; + + r._read = common.mustCall(function() { + setTimeout(common.mustCall(function() { + assert(removed); + assert.throws(function() { + w.emit('error', new Error('fail')); + }, /^Error: fail$/); + }), 1); + }); + + w.on('error', myOnError); + r.pipe(w); + w.removeListener('error', myOnError); + removed = true; + + function myOnError() { + throw new Error('this should not happen'); + } +} + +{ + const R = Stream.Readable; + const W = Stream.Writable; + + const r = new R(); + const w = new W(); + let removed = false; + + r._read = common.mustCall(function() { + setTimeout(common.mustCall(function() { + assert(removed); + w.emit('error', new Error('fail')); + }), 1); + }); + + w.on('error', common.mustCall()); + w._write = () => {}; + + r.pipe(w); + // Removing some OTHER random listener should not do anything + w.removeListener('error', () => {}); + removed = true; +} + +{ + const _err = new Error('this should be handled'); + const destination = new PassThrough(); + destination.once('error', common.mustCall((err) => { + assert.strictEqual(err, _err); + })); + + const stream = new Stream(); + stream + .pipe(destination); + + destination.destroy(_err); +} diff --git a/cli/tests/node_compat/test/parallel/test-stream-pipe-event.js b/cli/tests/node_compat/test/parallel/test-stream-pipe-event.js new file mode 100644 index 00000000000000..70dbdf867aa637 --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-stream-pipe-event.js @@ -0,0 +1,58 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. + +'use strict'; +require('../common'); +const stream = require('stream'); +const assert = require('assert'); + +function Writable() { + this.writable = true; + stream.Stream.call(this); +} +Object.setPrototypeOf(Writable.prototype, stream.Stream.prototype); +Object.setPrototypeOf(Writable, stream.Stream); + +function Readable() { + this.readable = true; + stream.Stream.call(this); +} +Object.setPrototypeOf(Readable.prototype, stream.Stream.prototype); +Object.setPrototypeOf(Readable, stream.Stream); + +let passed = false; + +const w = new Writable(); +w.on('pipe', function(src) { + passed = true; +}); + +const r = new Readable(); +r.pipe(w); + +assert.ok(passed); diff --git a/cli/tests/node_compat/test/parallel/test-stream-pipe-flow-after-unpipe.js b/cli/tests/node_compat/test/parallel/test-stream-pipe-flow-after-unpipe.js new file mode 100644 index 00000000000000..a065f506ec4d3a --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-stream-pipe-flow-after-unpipe.js @@ -0,0 +1,36 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +'use strict'; +const common = require('../common'); +const { Readable, Writable } = require('stream'); + +// Tests that calling .unpipe() un-blocks a stream that is paused because +// it is waiting on the writable side to finish a write(). + +const rs = new Readable({ + highWaterMark: 1, + // That this gets called at least 20 times is the real test here. + read: common.mustCallAtLeast(() => rs.push('foo'), 20) +}); + +const ws = new Writable({ + highWaterMark: 1, + write: common.mustCall(() => { + // Ignore the callback, this write() simply never finishes. + setImmediate(() => rs.unpipe(ws)); + }) +}); + +let chunks = 0; +rs.on('data', common.mustCallAtLeast(() => { + chunks++; + if (chunks >= 20) + rs.pause(); // Finish this test. +})); + +rs.pipe(ws); diff --git a/cli/tests/node_compat/test/parallel/test-stream-pipe-flow.js b/cli/tests/node_compat/test/parallel/test-stream-pipe-flow.js new file mode 100644 index 00000000000000..b6c4db0675a07e --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-stream-pipe-flow.js @@ -0,0 +1,97 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +'use strict'; +const common = require('../common'); +const assert = require('assert'); +const { Readable, Writable, PassThrough } = require('stream'); + +{ + let ticks = 17; + + const rs = new Readable({ + objectMode: true, + read: () => { + if (ticks-- > 0) + return process.nextTick(() => rs.push({})); + rs.push({}); + rs.push(null); + } + }); + + const ws = new Writable({ + highWaterMark: 0, + objectMode: true, + write: (data, end, cb) => setImmediate(cb) + }); + + rs.on('end', common.mustCall()); + ws.on('finish', common.mustCall()); + rs.pipe(ws); +} + +{ + let missing = 8; + + const rs = new Readable({ + objectMode: true, + read: () => { + if (missing--) rs.push({}); + else rs.push(null); + } + }); + + const pt = rs + .pipe(new PassThrough({ objectMode: true, highWaterMark: 2 })) + .pipe(new PassThrough({ objectMode: true, highWaterMark: 2 })); + + pt.on('end', () => { + wrapper.push(null); + }); + + const wrapper = new Readable({ + objectMode: true, + read: () => { + process.nextTick(() => { + let data = pt.read(); + if (data === null) { + pt.once('readable', () => { + data = pt.read(); + if (data !== null) wrapper.push(data); + }); + } else { + wrapper.push(data); + } + }); + } + }); + + wrapper.resume(); + wrapper.on('end', common.mustCall()); +} + +{ + // Only register drain if there is backpressure. + const rs = new Readable({ read() {} }); + + const pt = rs + .pipe(new PassThrough({ objectMode: true, highWaterMark: 2 })); + assert.strictEqual(pt.listenerCount('drain'), 0); + pt.on('finish', () => { + assert.strictEqual(pt.listenerCount('drain'), 0); + }); + + rs.push('asd'); + assert.strictEqual(pt.listenerCount('drain'), 0); + + process.nextTick(() => { + rs.push('asd'); + assert.strictEqual(pt.listenerCount('drain'), 0); + rs.push(null); + assert.strictEqual(pt.listenerCount('drain'), 0); + }); +} diff --git a/cli/tests/node_compat/test/parallel/test-stream-pipe-manual-resume.js b/cli/tests/node_compat/test/parallel/test-stream-pipe-manual-resume.js new file mode 100644 index 00000000000000..ca120e60e66245 --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-stream-pipe-manual-resume.js @@ -0,0 +1,42 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +'use strict'; +const common = require('../common'); +const stream = require('stream'); + +function test(throwCodeInbetween) { + // Check that a pipe does not stall if .read() is called unexpectedly + // (i.e. the stream is not resumed by the pipe). + + const n = 1000; + let counter = n; + const rs = stream.Readable({ + objectMode: true, + read: common.mustCallAtLeast(() => { + if (--counter >= 0) + rs.push({ counter }); + else + rs.push(null); + }, n) + }); + + const ws = stream.Writable({ + objectMode: true, + write: common.mustCall((data, enc, cb) => { + setImmediate(cb); + }, n) + }); + + setImmediate(() => throwCodeInbetween(rs, ws)); + + rs.pipe(ws); +} + +test((rs) => rs.read()); +test((rs) => rs.resume()); +test(() => 0); diff --git a/cli/tests/node_compat/test/parallel/test-stream-pipe-multiple-pipes.js b/cli/tests/node_compat/test/parallel/test-stream-pipe-multiple-pipes.js new file mode 100644 index 00000000000000..892eb0a8570fa5 --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-stream-pipe-multiple-pipes.js @@ -0,0 +1,58 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +'use strict'; +const common = require('../common'); +const stream = require('stream'); +const assert = require('assert'); + +const readable = new stream.Readable({ + read: () => {} +}); + +const writables = []; + +for (let i = 0; i < 5; i++) { + const target = new stream.Writable({ + write: common.mustCall((chunk, encoding, callback) => { + target.output.push(chunk); + callback(); + }, 1) + }); + target.output = []; + + target.on('pipe', common.mustCall()); + readable.pipe(target); + + + writables.push(target); +} + +const input = Buffer.from([1, 2, 3, 4, 5]); + +readable.push(input); + +// The pipe() calls will postpone emission of the 'resume' event using nextTick, +// so no data will be available to the writable streams until then. +process.nextTick(common.mustCall(() => { + for (const target of writables) { + assert.deepStrictEqual(target.output, [input]); + + target.on('unpipe', common.mustCall()); + readable.unpipe(target); + } + + readable.push('something else'); // This does not get through. + readable.push(null); + readable.resume(); // Make sure the 'end' event gets emitted. +})); + +readable.on('end', common.mustCall(() => { + for (const target of writables) { + assert.deepStrictEqual(target.output, [input]); + } +})); diff --git a/cli/tests/node_compat/test/parallel/test-stream-pipe-needDrain.js b/cli/tests/node_compat/test/parallel/test-stream-pipe-needDrain.js new file mode 100644 index 00000000000000..3fdb4a8cdbdac3 --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-stream-pipe-needDrain.js @@ -0,0 +1,38 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +'use strict'; + +const common = require('../common'); +const assert = require('assert'); +const { Readable, Writable } = require('stream'); + +// Pipe should pause temporarily if writable needs drain. +{ + const w = new Writable({ + write(buf, encoding, callback) { + process.nextTick(callback); + }, + highWaterMark: 1 + }); + + while (w.write('asd')); + + assert.strictEqual(w.writableNeedDrain, true); + + const r = new Readable({ + read() { + this.push('asd'); + this.push(null); + } + }); + + r.on('pause', common.mustCall(2)); + r.on('end', common.mustCall()); + + r.pipe(w); +} diff --git a/cli/tests/node_compat/test/parallel/test-stream-pipe-same-destination-twice.js b/cli/tests/node_compat/test/parallel/test-stream-pipe-same-destination-twice.js new file mode 100644 index 00000000000000..c037e00b90a368 --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-stream-pipe-same-destination-twice.js @@ -0,0 +1,85 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +'use strict'; +const common = require('../common'); + +// Regression test for https://github.com/nodejs/node/issues/12718. +// Tests that piping a source stream twice to the same destination stream +// works, and that a subsequent unpipe() call only removes the pipe *once*. +const assert = require('assert'); +const { PassThrough, Writable } = require('stream'); + +{ + const passThrough = new PassThrough(); + const dest = new Writable({ + write: common.mustCall((chunk, encoding, cb) => { + assert.strictEqual(`${chunk}`, 'foobar'); + cb(); + }) + }); + + passThrough.pipe(dest); + passThrough.pipe(dest); + + assert.strictEqual(passThrough._events.data.length, 2); + assert.strictEqual(passThrough._readableState.pipes.length, 2); + assert.strictEqual(passThrough._readableState.pipes[0], dest); + assert.strictEqual(passThrough._readableState.pipes[1], dest); + + passThrough.unpipe(dest); + + assert.strictEqual(passThrough._events.data.length, 1); + assert.strictEqual(passThrough._readableState.pipes.length, 1); + assert.deepStrictEqual(passThrough._readableState.pipes, [dest]); + + passThrough.write('foobar'); + passThrough.pipe(dest); +} + +{ + const passThrough = new PassThrough(); + const dest = new Writable({ + write: common.mustCall((chunk, encoding, cb) => { + assert.strictEqual(`${chunk}`, 'foobar'); + cb(); + }, 2) + }); + + passThrough.pipe(dest); + passThrough.pipe(dest); + + assert.strictEqual(passThrough._events.data.length, 2); + assert.strictEqual(passThrough._readableState.pipes.length, 2); + assert.strictEqual(passThrough._readableState.pipes[0], dest); + assert.strictEqual(passThrough._readableState.pipes[1], dest); + + passThrough.write('foobar'); +} + +{ + const passThrough = new PassThrough(); + const dest = new Writable({ + write: common.mustNotCall() + }); + + passThrough.pipe(dest); + passThrough.pipe(dest); + + assert.strictEqual(passThrough._events.data.length, 2); + assert.strictEqual(passThrough._readableState.pipes.length, 2); + assert.strictEqual(passThrough._readableState.pipes[0], dest); + assert.strictEqual(passThrough._readableState.pipes[1], dest); + + passThrough.unpipe(dest); + passThrough.unpipe(dest); + + assert.strictEqual(passThrough._events.data, undefined); + assert.strictEqual(passThrough._readableState.pipes.length, 0); + + passThrough.write('foobar'); +} diff --git a/cli/tests/node_compat/test/parallel/test-stream-pipe-unpipe-streams.js b/cli/tests/node_compat/test/parallel/test-stream-pipe-unpipe-streams.js new file mode 100644 index 00000000000000..3c3f19bd43aef9 --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-stream-pipe-unpipe-streams.js @@ -0,0 +1,103 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +'use strict'; +const common = require('../common'); +const assert = require('assert'); + +const { Readable, Writable } = require('stream'); + +const source = Readable({ read: () => {} }); +const dest1 = Writable({ write: () => {} }); +const dest2 = Writable({ write: () => {} }); + +source.pipe(dest1); +source.pipe(dest2); + +dest1.on('unpipe', common.mustCall()); +dest2.on('unpipe', common.mustCall()); + +assert.strictEqual(source._readableState.pipes[0], dest1); +assert.strictEqual(source._readableState.pipes[1], dest2); +assert.strictEqual(source._readableState.pipes.length, 2); + +// Should be able to unpipe them in the reverse order that they were piped. + +source.unpipe(dest2); + +assert.deepStrictEqual(source._readableState.pipes, [dest1]); +assert.notStrictEqual(source._readableState.pipes, dest2); + +dest2.on('unpipe', common.mustNotCall()); +source.unpipe(dest2); + +source.unpipe(dest1); + +assert.strictEqual(source._readableState.pipes.length, 0); + +{ + // Test `cleanup()` if we unpipe all streams. + const source = Readable({ read: () => {} }); + const dest1 = Writable({ write: () => {} }); + const dest2 = Writable({ write: () => {} }); + + let destCount = 0; + const srcCheckEventNames = ['end', 'data']; + const destCheckEventNames = ['close', 'finish', 'drain', 'error', 'unpipe']; + + const checkSrcCleanup = common.mustCall(() => { + assert.strictEqual(source._readableState.pipes.length, 0); + assert.strictEqual(source._readableState.flowing, false); + + srcCheckEventNames.forEach((eventName) => { + assert.strictEqual( + source.listenerCount(eventName), 0, + `source's '${eventName}' event listeners not removed` + ); + }); + }); + + function checkDestCleanup(dest) { + const currentDestId = ++destCount; + source.pipe(dest); + + const unpipeChecker = common.mustCall(() => { + assert.deepStrictEqual( + dest.listeners('unpipe'), [unpipeChecker], + `destination{${currentDestId}} should have a 'unpipe' event ` + + 'listener which is `unpipeChecker`' + ); + dest.removeListener('unpipe', unpipeChecker); + destCheckEventNames.forEach((eventName) => { + assert.strictEqual( + dest.listenerCount(eventName), 0, + `destination{${currentDestId}}'s '${eventName}' event ` + + 'listeners not removed' + ); + }); + + if (--destCount === 0) + checkSrcCleanup(); + }); + + dest.on('unpipe', unpipeChecker); + } + + checkDestCleanup(dest1); + checkDestCleanup(dest2); + source.unpipe(); +} + +{ + const src = Readable({ read: () => {} }); + const dst = Writable({ write: () => {} }); + src.pipe(dst); + src.on('resume', common.mustCall(() => { + src.on('pause', common.mustCall()); + src.unpipe(dst); + })); +} diff --git a/cli/tests/node_compat/test/parallel/test-stream-pipe-without-listenerCount.js b/cli/tests/node_compat/test/parallel/test-stream-pipe-without-listenerCount.js new file mode 100644 index 00000000000000..b27d966d8522f3 --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-stream-pipe-without-listenerCount.js @@ -0,0 +1,24 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +'use strict'; +const common = require('../common'); +const stream = require('stream'); + +const r = new stream.Stream(); +r.listenerCount = undefined; + +const w = new stream.Stream(); +w.listenerCount = undefined; + +w.on('pipe', function() { + r.emit('error', new Error('Readable Error')); + w.emit('error', new Error('Writable Error')); +}); +r.on('error', common.mustCall()); +w.on('error', common.mustCall()); +r.pipe(w); diff --git a/cli/tests/node_compat/test/parallel/test-stream-pipeline-async-iterator.js b/cli/tests/node_compat/test/parallel/test-stream-pipeline-async-iterator.js new file mode 100644 index 00000000000000..d2498d48c04dba --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-stream-pipeline-async-iterator.js @@ -0,0 +1,38 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +'use strict'; + +const common = require('../common'); +const { Readable, PassThrough, pipeline } = require('stream'); +const assert = require('assert'); + +const _err = new Error('kaboom'); + +async function run() { + const source = new Readable({ + read() { + } + }); + source.push('hello'); + source.push('world'); + + setImmediate(() => { source.destroy(_err); }); + + const iterator = pipeline( + source, + new PassThrough(), + () => {}); + + iterator.setEncoding('utf8'); + + for await (const k of iterator) { + assert.strictEqual(k, 'helloworld'); + } +} + +run().catch(common.mustCall((err) => assert.strictEqual(err, _err))); diff --git a/cli/tests/node_compat/test/parallel/test-stream-pipeline-queued-end-in-destroy.js b/cli/tests/node_compat/test/parallel/test-stream-pipeline-queued-end-in-destroy.js new file mode 100644 index 00000000000000..b6a921de1f0728 --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-stream-pipeline-queued-end-in-destroy.js @@ -0,0 +1,46 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +'use strict'; +const common = require('../common'); +const assert = require('assert'); +const { Readable, Duplex, pipeline } = require('stream'); + +// Test that the callback for pipeline() is called even when the ._destroy() +// method of the stream places an .end() request to itself that does not +// get processed before the destruction of the stream (i.e. the 'close' event). +// Refs: https://github.com/nodejs/node/issues/24456 + +const readable = new Readable({ + read: common.mustCall(() => {}) +}); + +const duplex = new Duplex({ + write(chunk, enc, cb) { + // Simulate messages queueing up. + }, + read() {}, + destroy(err, cb) { + // Call end() from inside the destroy() method, like HTTP/2 streams + // do at the time of writing. + this.end(); + cb(err); + } +}); + +duplex.on('finished', common.mustNotCall()); + +pipeline(readable, duplex, common.mustCall((err) => { + assert.strictEqual(err.code, 'ERR_STREAM_PREMATURE_CLOSE'); +})); + +// Write one chunk of data, and destroy the stream later. +// That should trigger the pipeline destruction. +readable.push('foo'); +setImmediate(() => { + readable.destroy(); +}); diff --git a/cli/tests/node_compat/test/parallel/test-stream-pipeline-with-empty-string.js b/cli/tests/node_compat/test/parallel/test-stream-pipeline-with-empty-string.js new file mode 100644 index 00000000000000..06dcccae8e9e34 --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-stream-pipeline-with-empty-string.js @@ -0,0 +1,25 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +'use strict'; + +const common = require('../common'); +const { + pipeline, + PassThrough +} = require('stream'); + + +async function runTest() { + await pipeline( + '', + new PassThrough({ objectMode: true }), + common.mustCall(() => { }) + ); +} + +runTest().then(common.mustCall(() => {})); diff --git a/cli/tests/node_compat/test/parallel/test-stream-push-strings.js b/cli/tests/node_compat/test/parallel/test-stream-push-strings.js new file mode 100644 index 00000000000000..89ace90811e3af --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-stream-push-strings.js @@ -0,0 +1,74 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. + +'use strict'; +require('../common'); +const assert = require('assert'); + +const Readable = require('stream').Readable; + +class MyStream extends Readable { + constructor(options) { + super(options); + this._chunks = 3; + } + + _read(n) { + switch (this._chunks--) { + case 0: + return this.push(null); + case 1: + return setTimeout(() => { + this.push('last chunk'); + }, 100); + case 2: + return this.push('second to last chunk'); + case 3: + return process.nextTick(() => { + this.push('first chunk'); + }); + default: + throw new Error('?'); + } + } +} + +const ms = new MyStream(); +const results = []; +ms.on('readable', function() { + let chunk; + while (null !== (chunk = ms.read())) + results.push(String(chunk)); +}); + +const expect = [ 'first chunksecond to last chunk', 'last chunk' ]; +process.on('exit', function() { + assert.strictEqual(ms._chunks, -1); + assert.deepStrictEqual(results, expect); + console.log('ok'); +}); diff --git a/cli/tests/node_compat/test/parallel/test-stream-readable-aborted.js b/cli/tests/node_compat/test/parallel/test-stream-readable-aborted.js new file mode 100644 index 00000000000000..8209ee6739828e --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-stream-readable-aborted.js @@ -0,0 +1,73 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +'use strict'; + +const common = require('../common'); +const assert = require('assert'); +const { Readable, Duplex } = require('stream'); + +{ + const readable = new Readable({ + read() { + } + }); + assert.strictEqual(readable.readableAborted, false); + readable.destroy(); + assert.strictEqual(readable.readableAborted, true); +} + +{ + const readable = new Readable({ + read() { + } + }); + assert.strictEqual(readable.readableAborted, false); + readable.push(null); + readable.destroy(); + assert.strictEqual(readable.readableAborted, true); +} + +{ + const readable = new Readable({ + read() { + } + }); + assert.strictEqual(readable.readableAborted, false); + readable.push('asd'); + readable.destroy(); + assert.strictEqual(readable.readableAborted, true); +} + +{ + const readable = new Readable({ + read() { + } + }); + assert.strictEqual(readable.readableAborted, false); + readable.push('asd'); + readable.push(null); + assert.strictEqual(readable.readableAborted, false); + readable.on('end', common.mustCall(() => { + assert.strictEqual(readable.readableAborted, false); + readable.destroy(); + assert.strictEqual(readable.readableAborted, false); + queueMicrotask(() => { + assert.strictEqual(readable.readableAborted, false); + }); + })); + readable.resume(); +} + +{ + const duplex = new Duplex({ + readable: false, + write() {} + }); + duplex.destroy(); + assert.strictEqual(duplex.readableAborted, false); +} diff --git a/cli/tests/node_compat/test/parallel/test-stream-readable-add-chunk-during-data.js b/cli/tests/node_compat/test/parallel/test-stream-readable-add-chunk-during-data.js new file mode 100644 index 00000000000000..e16312b0c5267f --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-stream-readable-add-chunk-during-data.js @@ -0,0 +1,28 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +'use strict'; +const common = require('../common'); +const assert = require('assert'); +const { Readable } = require('stream'); + +// Verify that .push() and .unshift() can be called from 'data' listeners. + +for (const method of ['push', 'unshift']) { + const r = new Readable({ read() {} }); + r.once('data', common.mustCall((chunk) => { + assert.strictEqual(r.readableLength, 0); + r[method](chunk); + assert.strictEqual(r.readableLength, chunk.length); + + r.on('data', common.mustCall((chunk) => { + assert.strictEqual(chunk.toString(), 'Hello, world'); + })); + })); + + r.push('Hello, world'); +} diff --git a/cli/tests/node_compat/test/parallel/test-stream-readable-constructor-set-methods.js b/cli/tests/node_compat/test/parallel/test-stream-readable-constructor-set-methods.js new file mode 100644 index 00000000000000..1f6f14ce3cf9cb --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-stream-readable-constructor-set-methods.js @@ -0,0 +1,18 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +'use strict'; +const common = require('../common'); + +const Readable = require('stream').Readable; + +const _read = common.mustCall(function _read(n) { + this.push(null); +}); + +const r = new Readable({ read: _read }); +r.resume(); diff --git a/cli/tests/node_compat/test/parallel/test-stream-readable-data.js b/cli/tests/node_compat/test/parallel/test-stream-readable-data.js new file mode 100644 index 00000000000000..34928c9671a8b6 --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-stream-readable-data.js @@ -0,0 +1,26 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +'use strict'; +const common = require('../common'); + +const { Readable } = require('stream'); + +const readable = new Readable({ + read() {} +}); + +function read() {} + +readable.setEncoding('utf8'); +readable.on('readable', read); +readable.removeListener('readable', read); + +process.nextTick(function() { + readable.on('data', common.mustCall()); + readable.push('hello'); +}); diff --git a/cli/tests/node_compat/test/parallel/test-stream-readable-destroy.js b/cli/tests/node_compat/test/parallel/test-stream-readable-destroy.js new file mode 100644 index 00000000000000..642c19d600ab6f --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-stream-readable-destroy.js @@ -0,0 +1,412 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +'use strict'; + +const common = require('../common'); +const { Readable, addAbortSignal } = require('stream'); +const assert = require('assert'); + +{ + const read = new Readable({ + read() {} + }); + read.resume(); + + read.on('close', common.mustCall()); + + read.destroy(); + assert.strictEqual(read.errored, null); + assert.strictEqual(read.destroyed, true); +} + +{ + const read = new Readable({ + read() {} + }); + read.resume(); + + const expected = new Error('kaboom'); + + read.on('end', common.mustNotCall('no end event')); + read.on('close', common.mustCall()); + read.on('error', common.mustCall((err) => { + assert.strictEqual(err, expected); + })); + + read.destroy(expected); + assert.strictEqual(read.errored, expected); + assert.strictEqual(read.destroyed, true); +} + +{ + const read = new Readable({ + read() {} + }); + + read._destroy = common.mustCall(function(err, cb) { + assert.strictEqual(err, expected); + cb(err); + }); + + const expected = new Error('kaboom'); + + read.on('end', common.mustNotCall('no end event')); + read.on('close', common.mustCall()); + read.on('error', common.mustCall((err) => { + assert.strictEqual(err, expected); + })); + + read.destroy(expected); + assert.strictEqual(read.destroyed, true); +} + +{ + const read = new Readable({ + read() {}, + destroy: common.mustCall(function(err, cb) { + assert.strictEqual(err, expected); + cb(); + }) + }); + + const expected = new Error('kaboom'); + + read.on('end', common.mustNotCall('no end event')); + + // Error is swallowed by the custom _destroy + read.on('error', common.mustNotCall('no error event')); + read.on('close', common.mustCall()); + + read.destroy(expected); + assert.strictEqual(read.destroyed, true); +} + +{ + const read = new Readable({ + read() {} + }); + + read._destroy = common.mustCall(function(err, cb) { + assert.strictEqual(err, null); + cb(); + }); + + read.destroy(); + assert.strictEqual(read.destroyed, true); +} + +{ + const read = new Readable({ + read() {} + }); + read.resume(); + + read._destroy = common.mustCall(function(err, cb) { + assert.strictEqual(err, null); + process.nextTick(() => { + this.push(null); + cb(); + }); + }); + + const fail = common.mustNotCall('no end event'); + + read.on('end', fail); + read.on('close', common.mustCall()); + + read.destroy(); + + read.removeListener('end', fail); + read.on('end', common.mustNotCall()); + assert.strictEqual(read.destroyed, true); +} + +{ + const read = new Readable({ + read() {} + }); + + const expected = new Error('kaboom'); + + read._destroy = common.mustCall(function(err, cb) { + assert.strictEqual(err, null); + cb(expected); + }); + + let ticked = false; + read.on('end', common.mustNotCall('no end event')); + read.on('error', common.mustCall((err) => { + assert.strictEqual(ticked, true); + assert.strictEqual(read._readableState.errorEmitted, true); + assert.strictEqual(read._readableState.errored, expected); + assert.strictEqual(err, expected); + })); + + read.destroy(); + assert.strictEqual(read._readableState.errorEmitted, false); + assert.strictEqual(read._readableState.errored, expected); + assert.strictEqual(read.destroyed, true); + ticked = true; +} + +{ + const read = new Readable({ + read() {} + }); + read.resume(); + + read.destroyed = true; + assert.strictEqual(read.destroyed, true); + + // The internal destroy() mechanism should not be triggered + read.on('end', common.mustNotCall()); + read.destroy(); +} + +{ + function MyReadable() { + assert.strictEqual(this.destroyed, false); + this.destroyed = false; + Readable.call(this); + } + + Object.setPrototypeOf(MyReadable.prototype, Readable.prototype); + Object.setPrototypeOf(MyReadable, Readable); + + new MyReadable(); +} + +{ + // Destroy and destroy callback + const read = new Readable({ + read() {} + }); + read.resume(); + + const expected = new Error('kaboom'); + + let ticked = false; + read.on('close', common.mustCall(() => { + assert.strictEqual(read._readableState.errorEmitted, true); + assert.strictEqual(ticked, true); + })); + read.on('error', common.mustCall((err) => { + assert.strictEqual(err, expected); + })); + + assert.strictEqual(read._readableState.errored, null); + assert.strictEqual(read._readableState.errorEmitted, false); + + read.destroy(expected, common.mustCall(function(err) { + assert.strictEqual(read._readableState.errored, expected); + assert.strictEqual(err, expected); + })); + assert.strictEqual(read._readableState.errorEmitted, false); + assert.strictEqual(read._readableState.errored, expected); + ticked = true; +} + +{ + const readable = new Readable({ + destroy: common.mustCall(function(err, cb) { + process.nextTick(cb, new Error('kaboom 1')); + }), + read() {} + }); + + let ticked = false; + readable.on('close', common.mustCall(() => { + assert.strictEqual(ticked, true); + assert.strictEqual(readable._readableState.errorEmitted, true); + })); + readable.on('error', common.mustCall((err) => { + assert.strictEqual(ticked, true); + assert.strictEqual(err.message, 'kaboom 1'); + assert.strictEqual(readable._readableState.errorEmitted, true); + })); + + readable.destroy(); + assert.strictEqual(readable.destroyed, true); + assert.strictEqual(readable._readableState.errored, null); + assert.strictEqual(readable._readableState.errorEmitted, false); + + // Test case where `readable.destroy()` is called again with an error before + // the `_destroy()` callback is called. + readable.destroy(new Error('kaboom 2')); + assert.strictEqual(readable._readableState.errorEmitted, false); + assert.strictEqual(readable._readableState.errored, null); + + ticked = true; +} + +{ + const read = new Readable({ + read() {} + }); + + read.destroy(); + read.push('hi'); + read.on('data', common.mustNotCall()); +} + +{ + const read = new Readable({ + read: common.mustNotCall(function() {}) + }); + read.destroy(); + assert.strictEqual(read.destroyed, true); + read.read(); +} + +{ + const read = new Readable({ + autoDestroy: false, + read() { + this.push(null); + this.push('asd'); + } + }); + + read.on('error', common.mustCall(() => { + assert(read._readableState.errored); + })); + read.resume(); +} + +{ + const controller = new AbortController(); + const read = addAbortSignal(controller.signal, new Readable({ + read() { + this.push('asd'); + }, + })); + + read.on('error', common.mustCall((e) => { + assert.strictEqual(e.name, 'AbortError'); + })); + controller.abort(); + read.on('data', common.mustNotCall()); +} + +{ + const controller = new AbortController(); + const read = new Readable({ + signal: controller.signal, + read() { + this.push('asd'); + }, + }); + + read.on('error', common.mustCall((e) => { + assert.strictEqual(e.name, 'AbortError'); + })); + controller.abort(); + read.on('data', common.mustNotCall()); +} + +{ + const controller = new AbortController(); + const read = addAbortSignal(controller.signal, new Readable({ + objectMode: true, + read() { + return false; + } + })); + read.push('asd'); + + read.on('error', common.mustCall((e) => { + assert.strictEqual(e.name, 'AbortError'); + })); + assert.rejects((async () => { + // eslint-disable-next-line no-unused-vars, no-empty + for await (const chunk of read) { } + })(), /AbortError/); + setTimeout(() => controller.abort(), 0); +} + +{ + const read = new Readable({ + read() { + }, + }); + + read.on('data', common.mustNotCall()); + read.on('error', common.mustCall((e) => { + read.push('asd'); + read.read(); + })); + read.on('close', common.mustCall((e) => { + read.push('asd'); + read.read(); + })); + read.destroy(new Error('asd')); +} + +{ + const read = new Readable({ + read() { + }, + }); + + read.on('data', common.mustNotCall()); + read.on('close', common.mustCall((e) => { + read.push('asd'); + read.read(); + })); + read.destroy(); +} + +{ + const read = new Readable({ + read() { + }, + }); + + read.on('data', common.mustNotCall()); + read.on('close', common.mustCall((e) => { + read.push('asd'); + read.unshift('asd'); + })); + read.destroy(); +} + +{ + const read = new Readable({ + read() { + }, + }); + + read.on('data', common.mustNotCall()); + read.destroy(); + read.unshift('asd'); +} + +{ + const read = new Readable({ + read() { + }, + }); + + read.resume(); + read.on('data', common.mustNotCall()); + read.on('close', common.mustCall((e) => { + read.push('asd'); + })); + read.destroy(); +} + +{ + const read = new Readable({ + read() { + }, + }); + + read.on('data', common.mustNotCall()); + read.destroy(); + read.push('asd'); +} diff --git a/cli/tests/node_compat/test/parallel/test-stream-readable-didRead.js b/cli/tests/node_compat/test/parallel/test-stream-readable-didRead.js new file mode 100644 index 00000000000000..0c0d7b0c3bba60 --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-stream-readable-didRead.js @@ -0,0 +1,118 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +'use strict'; +const common = require('../common'); +const assert = require('assert'); +const { isDisturbed, isErrored, Readable } = require('stream'); + +function noop() {} + +function check(readable, data, fn) { + assert.strictEqual(readable.readableDidRead, false); + assert.strictEqual(isDisturbed(readable), false); + assert.strictEqual(isErrored(readable), false); + if (data === -1) { + readable.on('error', common.mustCall(() => { + assert.strictEqual(isErrored(readable), true); + })); + readable.on('data', common.mustNotCall()); + readable.on('end', common.mustNotCall()); + } else { + readable.on('error', common.mustNotCall()); + if (data === -2) { + readable.on('end', common.mustNotCall()); + } else { + readable.on('end', common.mustCall()); + } + if (data > 0) { + readable.on('data', common.mustCallAtLeast(data)); + } else { + readable.on('data', common.mustNotCall()); + } + } + readable.on('close', common.mustCall()); + fn(); + setImmediate(() => { + assert.strictEqual(readable.readableDidRead, data > 0); + if (data > 0) { + assert.strictEqual(isDisturbed(readable), true); + } + }); +} + +{ + const readable = new Readable({ + read() { + this.push(null); + } + }); + check(readable, 0, () => { + readable.read(); + }); +} + +{ + const readable = new Readable({ + read() { + this.push(null); + } + }); + check(readable, 0, () => { + readable.resume(); + }); +} + +{ + const readable = new Readable({ + read() { + this.push(null); + } + }); + check(readable, -2, () => { + readable.destroy(); + }); +} + +{ + const readable = new Readable({ + read() { + this.push(null); + } + }); + + check(readable, -1, () => { + readable.destroy(new Error()); + }); +} + +{ + const readable = new Readable({ + read() { + this.push('data'); + this.push(null); + } + }); + + check(readable, 1, () => { + readable.on('data', noop); + }); +} + +{ + const readable = new Readable({ + read() { + this.push('data'); + this.push(null); + } + }); + + check(readable, 1, () => { + readable.on('data', noop); + readable.off('data', noop); + }); +} diff --git a/cli/tests/node_compat/test/parallel/test-stream-readable-emit-readable-short-stream.js b/cli/tests/node_compat/test/parallel/test-stream-readable-emit-readable-short-stream.js new file mode 100644 index 00000000000000..543199d2cf1866 --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-stream-readable-emit-readable-short-stream.js @@ -0,0 +1,153 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +'use strict'; + +const common = require('../common'); +const stream = require('stream'); +const assert = require('assert'); + +{ + const r = new stream.Readable({ + read: common.mustCall(function() { + this.push('content'); + this.push(null); + }) + }); + + const t = new stream.Transform({ + transform: common.mustCall(function(chunk, encoding, callback) { + this.push(chunk); + return callback(); + }), + flush: common.mustCall(function(callback) { + return callback(); + }) + }); + + r.pipe(t); + t.on('readable', common.mustCall(function() { + while (true) { + const chunk = t.read(); + if (!chunk) + break; + + assert.strictEqual(chunk.toString(), 'content'); + } + }, 2)); +} + +{ + const t = new stream.Transform({ + transform: common.mustCall(function(chunk, encoding, callback) { + this.push(chunk); + return callback(); + }), + flush: common.mustCall(function(callback) { + return callback(); + }) + }); + + t.end('content'); + + t.on('readable', common.mustCall(function() { + while (true) { + const chunk = t.read(); + if (!chunk) + break; + assert.strictEqual(chunk.toString(), 'content'); + } + })); +} + +{ + const t = new stream.Transform({ + transform: common.mustCall(function(chunk, encoding, callback) { + this.push(chunk); + return callback(); + }), + flush: common.mustCall(function(callback) { + return callback(); + }) + }); + + t.write('content'); + t.end(); + + t.on('readable', common.mustCall(function() { + while (true) { + const chunk = t.read(); + if (!chunk) + break; + assert.strictEqual(chunk.toString(), 'content'); + } + })); +} + +{ + const t = new stream.Readable({ + read() { + } + }); + + t.on('readable', common.mustCall(function() { + while (true) { + const chunk = t.read(); + if (!chunk) + break; + assert.strictEqual(chunk.toString(), 'content'); + } + })); + + t.push('content'); + t.push(null); +} + +{ + const t = new stream.Readable({ + read() { + } + }); + + t.on('readable', common.mustCall(function() { + while (true) { + const chunk = t.read(); + if (!chunk) + break; + assert.strictEqual(chunk.toString(), 'content'); + } + }, 2)); + + process.nextTick(() => { + t.push('content'); + t.push(null); + }); +} + +{ + const t = new stream.Transform({ + transform: common.mustCall(function(chunk, encoding, callback) { + this.push(chunk); + return callback(); + }), + flush: common.mustCall(function(callback) { + return callback(); + }) + }); + + t.on('readable', common.mustCall(function() { + while (true) { + const chunk = t.read(); + if (!chunk) + break; + assert.strictEqual(chunk.toString(), 'content'); + } + }, 2)); + + t.write('content'); + t.end(); +} diff --git a/cli/tests/node_compat/test/parallel/test-stream-readable-emittedReadable.js b/cli/tests/node_compat/test/parallel/test-stream-readable-emittedReadable.js new file mode 100644 index 00000000000000..7bab366eea37b4 --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-stream-readable-emittedReadable.js @@ -0,0 +1,80 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +'use strict'; +const common = require('../common'); +const assert = require('assert'); +const Readable = require('stream').Readable; + +const readable = new Readable({ + read: () => {} +}); + +// Initialized to false. +assert.strictEqual(readable._readableState.emittedReadable, false); + +const expected = [Buffer.from('foobar'), Buffer.from('quo'), null]; +readable.on('readable', common.mustCall(() => { + // emittedReadable should be true when the readable event is emitted + assert.strictEqual(readable._readableState.emittedReadable, true); + assert.deepStrictEqual(readable.read(), expected.shift()); + // emittedReadable is reset to false during read() + assert.strictEqual(readable._readableState.emittedReadable, false); +}, 3)); + +// When the first readable listener is just attached, +// emittedReadable should be false +assert.strictEqual(readable._readableState.emittedReadable, false); + +// These trigger a single 'readable', as things are batched up +process.nextTick(common.mustCall(() => { + readable.push('foo'); +})); +process.nextTick(common.mustCall(() => { + readable.push('bar'); +})); + +// These triggers two readable events +setImmediate(common.mustCall(() => { + readable.push('quo'); + process.nextTick(common.mustCall(() => { + readable.push(null); + })); +})); + +const noRead = new Readable({ + read: () => {} +}); + +noRead.on('readable', common.mustCall(() => { + // emittedReadable should be true when the readable event is emitted + assert.strictEqual(noRead._readableState.emittedReadable, true); + noRead.read(0); + // emittedReadable is not reset during read(0) + assert.strictEqual(noRead._readableState.emittedReadable, true); +})); + +noRead.push('foo'); +noRead.push(null); + +const flowing = new Readable({ + read: () => {} +}); + +flowing.on('data', common.mustCall(() => { + // When in flowing mode, emittedReadable is always false. + assert.strictEqual(flowing._readableState.emittedReadable, false); + flowing.read(); + assert.strictEqual(flowing._readableState.emittedReadable, false); +}, 3)); + +flowing.push('foooo'); +flowing.push('bar'); +flowing.push('quo'); +process.nextTick(common.mustCall(() => { + flowing.push(null); +})); diff --git a/cli/tests/node_compat/test/parallel/test-stream-readable-end-destroyed.js b/cli/tests/node_compat/test/parallel/test-stream-readable-end-destroyed.js new file mode 100644 index 00000000000000..4f2bfd1f2eaadd --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-stream-readable-end-destroyed.js @@ -0,0 +1,24 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +'use strict'; + +const common = require('../common'); +const { Readable } = require('stream'); + +{ + // Don't emit 'end' after 'close'. + + const r = new Readable(); + + r.on('end', common.mustNotCall()); + r.resume(); + r.destroy(); + r.on('close', common.mustCall(() => { + r.push(null); + })); +} diff --git a/cli/tests/node_compat/test/parallel/test-stream-readable-ended.js b/cli/tests/node_compat/test/parallel/test-stream-readable-ended.js new file mode 100644 index 00000000000000..5687cb4cf5b131 --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-stream-readable-ended.js @@ -0,0 +1,53 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +'use strict'; + +const common = require('../common'); +const { Readable } = require('stream'); +const assert = require('assert'); + +// basic +{ + // Find it on Readable.prototype + assert(Object.hasOwn(Readable.prototype, 'readableEnded')); +} + +// event +{ + const readable = new Readable(); + + readable._read = () => { + // The state ended should start in false. + assert.strictEqual(readable.readableEnded, false); + readable.push('asd'); + assert.strictEqual(readable.readableEnded, false); + readable.push(null); + assert.strictEqual(readable.readableEnded, false); + }; + + readable.on('end', common.mustCall(() => { + assert.strictEqual(readable.readableEnded, true); + })); + + readable.on('data', common.mustCall(() => { + assert.strictEqual(readable.readableEnded, false); + })); +} + +// Verifies no `error` triggered on multiple .push(null) invocations +{ + const readable = new Readable(); + + readable.on('readable', () => { readable.read(); }); + readable.on('error', common.mustNotCall()); + readable.on('end', common.mustCall()); + + readable.push('a'); + readable.push(null); + readable.push(null); +} diff --git a/cli/tests/node_compat/test/parallel/test-stream-readable-error-end.js b/cli/tests/node_compat/test/parallel/test-stream-readable-error-end.js new file mode 100644 index 00000000000000..fbc6cfae8125b2 --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-stream-readable-error-end.js @@ -0,0 +1,22 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +'use strict'; + +const common = require('../common'); +const { Readable } = require('stream'); + +{ + const r = new Readable({ read() {} }); + + r.on('end', common.mustNotCall()); + r.on('data', common.mustCall()); + r.on('error', common.mustCall()); + r.push('asd'); + r.push(null); + r.destroy(new Error('kaboom')); +} diff --git a/cli/tests/node_compat/test/parallel/test-stream-readable-event.js b/cli/tests/node_compat/test/parallel/test-stream-readable-event.js new file mode 100644 index 00000000000000..a5964ab2c24f17 --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-stream-readable-event.js @@ -0,0 +1,135 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. + +'use strict'; +const common = require('../common'); +const assert = require('assert'); + +const Readable = require('stream').Readable; + +{ + // First test, not reading when the readable is added. + // make sure that on('readable', ...) triggers a readable event. + const r = new Readable({ + highWaterMark: 3 + }); + + r._read = common.mustNotCall(); + + // This triggers a 'readable' event, which is lost. + r.push(Buffer.from('blerg')); + + setTimeout(function() { + // We're testing what we think we are + assert(!r._readableState.reading); + r.on('readable', common.mustCall()); + }, 1); +} + +{ + // Second test, make sure that readable is re-emitted if there's + // already a length, while it IS reading. + + const r = new Readable({ + highWaterMark: 3 + }); + + r._read = common.mustCall(); + + // This triggers a 'readable' event, which is lost. + r.push(Buffer.from('bl')); + + setTimeout(function() { + // Assert we're testing what we think we are + assert(r._readableState.reading); + r.on('readable', common.mustCall()); + }, 1); +} + +{ + // Third test, not reading when the stream has not passed + // the highWaterMark but *has* reached EOF. + const r = new Readable({ + highWaterMark: 30 + }); + + r._read = common.mustNotCall(); + + // This triggers a 'readable' event, which is lost. + r.push(Buffer.from('blerg')); + r.push(null); + + setTimeout(function() { + // Assert we're testing what we think we are + assert(!r._readableState.reading); + r.on('readable', common.mustCall()); + }, 1); +} + +{ + // Pushing an empty string in non-objectMode should + // trigger next `read()`. + const underlyingData = ['', 'x', 'y', '', 'z']; + const expected = underlyingData.filter((data) => data); + const result = []; + + const r = new Readable({ + encoding: 'utf8', + }); + r._read = function() { + process.nextTick(() => { + if (!underlyingData.length) { + this.push(null); + } else { + this.push(underlyingData.shift()); + } + }); + }; + + r.on('readable', () => { + const data = r.read(); + if (data !== null) result.push(data); + }); + + r.on('end', common.mustCall(() => { + assert.deepStrictEqual(result, expected); + })); +} + +{ + // #20923 + const r = new Readable(); + r._read = function() { + // Actually doing thing here + }; + r.on('data', function() {}); + + r.removeAllListeners(); + + assert.strictEqual(r.eventNames().length, 0); +} diff --git a/cli/tests/node_compat/test/parallel/test-stream-readable-flow-recursion.js b/cli/tests/node_compat/test/parallel/test-stream-readable-flow-recursion.js new file mode 100644 index 00000000000000..d37a22af711758 --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-stream-readable-flow-recursion.js @@ -0,0 +1,84 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. + +'use strict'; +require('../common'); +const assert = require('assert'); + +// This test verifies that passing a huge number to read(size) +// will push up the highWaterMark, and cause the stream to read +// more data continuously, but without triggering a nextTick +// warning or RangeError. + +const Readable = require('stream').Readable; + +// Throw an error if we trigger a nextTick warning. +process.throwDeprecation = true; + +const stream = new Readable({ highWaterMark: 2 }); +let reads = 0; +let total = 5000; +stream._read = function(size) { + reads++; + size = Math.min(size, total); + total -= size; + if (size === 0) + stream.push(null); + else + stream.push(Buffer.allocUnsafe(size)); +}; + +let depth = 0; + +function flow(stream, size, callback) { + depth += 1; + const chunk = stream.read(size); + + if (!chunk) + stream.once('readable', flow.bind(null, stream, size, callback)); + else + callback(chunk); + + depth -= 1; + console.log(`flow(${depth}): exit`); +} + +flow(stream, 5000, function() { + console.log(`complete (${depth})`); +}); + +process.on('exit', function(code) { + assert.strictEqual(reads, 2); + // We pushed up the high water mark + assert.strictEqual(stream.readableHighWaterMark, 8192); + // Length is 0 right now, because we pulled it all out. + assert.strictEqual(stream.readableLength, 0); + assert(!code); + assert.strictEqual(depth, 0); + console.log('ok'); +}); diff --git a/cli/tests/node_compat/test/parallel/test-stream-readable-hwm-0-async.js b/cli/tests/node_compat/test/parallel/test-stream-readable-hwm-0-async.js new file mode 100644 index 00000000000000..6ee2b625a57bc8 --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-stream-readable-hwm-0-async.js @@ -0,0 +1,34 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +'use strict'; + +const common = require('../common'); + +// This test ensures that Readable stream will continue to call _read +// for streams with highWaterMark === 0 once the stream returns data +// by calling push() asynchronously. + +const { Readable } = require('stream'); + +let count = 5; + +const r = new Readable({ + // Called 6 times: First 5 return data, last one signals end of stream. + read: common.mustCall(() => { + process.nextTick(common.mustCall(() => { + if (count--) + r.push('a'); + else + r.push(null); + })); + }, 6), + highWaterMark: 0, +}); + +r.on('end', common.mustCall()); +r.on('data', common.mustCall(5)); diff --git a/cli/tests/node_compat/test/parallel/test-stream-readable-hwm-0-no-flow-data.js b/cli/tests/node_compat/test/parallel/test-stream-readable-hwm-0-no-flow-data.js new file mode 100644 index 00000000000000..38b69d2d90de94 --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-stream-readable-hwm-0-no-flow-data.js @@ -0,0 +1,111 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +'use strict'; + +const common = require('../common'); + +// Ensure that subscribing the 'data' event will not make the stream flow. +// The 'data' event will require calling read() by hand. +// +// The test is written for the (somewhat rare) highWaterMark: 0 streams to +// specifically catch any regressions that might occur with these streams. + +const assert = require('assert'); +const { Readable } = require('stream'); + +const streamData = [ 'a', null ]; + +// Track the calls so we can assert their order later. +const calls = []; +const r = new Readable({ + read: common.mustCall(() => { + calls.push('_read:' + streamData[0]); + process.nextTick(() => { + calls.push('push:' + streamData[0]); + r.push(streamData.shift()); + }); + }, streamData.length), + highWaterMark: 0, + + // Object mode is used here just for testing convenience. It really + // shouldn't affect the order of events. Just the data and its format. + objectMode: true, +}); + +assert.strictEqual(r.readableFlowing, null); +r.on('readable', common.mustCall(() => { + calls.push('readable'); +}, 2)); +assert.strictEqual(r.readableFlowing, false); +r.on('data', common.mustCall((data) => { + calls.push('data:' + data); +}, 1)); +r.on('end', common.mustCall(() => { + calls.push('end'); +})); +assert.strictEqual(r.readableFlowing, false); + +// The stream emits the events asynchronously but that's not guaranteed to +// happen on the next tick (especially since the _read implementation above +// uses process.nextTick). +// +// We use setImmediate here to give the stream enough time to emit all the +// events it's about to emit. +setImmediate(() => { + + // Only the _read, push, readable calls have happened. No data must be + // emitted yet. + assert.deepStrictEqual(calls, ['_read:a', 'push:a', 'readable']); + + // Calling 'r.read()' should trigger the data event. + assert.strictEqual(r.read(), 'a'); + assert.deepStrictEqual( + calls, + ['_read:a', 'push:a', 'readable', 'data:a']); + + // The next 'read()' will return null because hwm: 0 does not buffer any + // data and the _read implementation above does the push() asynchronously. + // + // Note: This 'null' signals "no data available". It isn't the end-of-stream + // null value as the stream doesn't know yet that it is about to reach the + // end. + // + // Using setImmediate again to give the stream enough time to emit all the + // events it wants to emit. + assert.strictEqual(r.read(), null); + setImmediate(() => { + + // There's a new 'readable' event after the data has been pushed. + // The 'end' event will be emitted only after a 'read()'. + // + // This is somewhat special for the case where the '_read' implementation + // calls 'push' asynchronously. If 'push' was synchronous, the 'end' event + // would be emitted here _before_ we call read(). + assert.deepStrictEqual( + calls, + ['_read:a', 'push:a', 'readable', 'data:a', + '_read:null', 'push:null', 'readable']); + + assert.strictEqual(r.read(), null); + + // While it isn't really specified whether the 'end' event should happen + // synchronously with read() or not, we'll assert the current behavior + // ('end' event happening on the next tick after read()) so any changes + // to it are noted and acknowledged in the future. + assert.deepStrictEqual( + calls, + ['_read:a', 'push:a', 'readable', 'data:a', + '_read:null', 'push:null', 'readable']); + process.nextTick(() => { + assert.deepStrictEqual( + calls, + ['_read:a', 'push:a', 'readable', 'data:a', + '_read:null', 'push:null', 'readable', 'end']); + }); + }); +}); diff --git a/cli/tests/node_compat/test/parallel/test-stream-readable-hwm-0.js b/cli/tests/node_compat/test/parallel/test-stream-readable-hwm-0.js new file mode 100644 index 00000000000000..17a9c05e568f9d --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-stream-readable-hwm-0.js @@ -0,0 +1,37 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +'use strict'; + +const common = require('../common'); + +// This test ensures that Readable stream will call _read() for streams +// with highWaterMark === 0 upon .read(0) instead of just trying to +// emit 'readable' event. + +const assert = require('assert'); +const { Readable } = require('stream'); + +const r = new Readable({ + // Must be called only once upon setting 'readable' listener + read: common.mustCall(), + highWaterMark: 0, +}); + +let pushedNull = false; +// This will trigger read(0) but must only be called after push(null) +// because the we haven't pushed any data +r.on('readable', common.mustCall(() => { + assert.strictEqual(r.read(), null); + assert.strictEqual(pushedNull, true); +})); +r.on('end', common.mustCall()); +process.nextTick(() => { + assert.strictEqual(r.read(), null); + pushedNull = true; + r.push(null); +}); diff --git a/cli/tests/node_compat/test/parallel/test-stream-readable-infinite-read.js b/cli/tests/node_compat/test/parallel/test-stream-readable-infinite-read.js new file mode 100644 index 00000000000000..5b5b8275eb61cf --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-stream-readable-infinite-read.js @@ -0,0 +1,39 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +'use strict'; + +const common = require('../common'); +const assert = require('assert'); +const { Readable } = require('stream'); + +const buf = Buffer.alloc(8192); + +const readable = new Readable({ + read: common.mustCall(function() { + this.push(buf); + }, 31) +}); + +let i = 0; + +readable.on('readable', common.mustCall(function() { + if (i++ === 10) { + // We will just terminate now. + process.removeAllListeners('readable'); + return; + } + + const data = readable.read(); + // TODO(mcollina): there is something odd in the highWaterMark logic + // investigate. + if (i === 1) { + assert.strictEqual(data.length, 8192 * 2); + } else { + assert.strictEqual(data.length, 8192 * 3); + } +}, 11)); diff --git a/cli/tests/node_compat/test/parallel/test-stream-readable-invalid-chunk.js b/cli/tests/node_compat/test/parallel/test-stream-readable-invalid-chunk.js new file mode 100644 index 00000000000000..ffbe64a8af537c --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-stream-readable-invalid-chunk.js @@ -0,0 +1,41 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +'use strict'; + +const common = require('../common'); +const stream = require('stream'); + +function testPushArg(val) { + const readable = new stream.Readable({ + read: () => {} + }); + readable.on('error', common.expectsError({ + code: 'ERR_INVALID_ARG_TYPE', + name: 'TypeError' + })); + readable.push(val); +} + +testPushArg([]); +testPushArg({}); +testPushArg(0); + +function testUnshiftArg(val) { + const readable = new stream.Readable({ + read: () => {} + }); + readable.on('error', common.expectsError({ + code: 'ERR_INVALID_ARG_TYPE', + name: 'TypeError' + })); + readable.unshift(val); +} + +testUnshiftArg([]); +testUnshiftArg({}); +testUnshiftArg(0); diff --git a/cli/tests/node_compat/test/parallel/test-stream-readable-needReadable.js b/cli/tests/node_compat/test/parallel/test-stream-readable-needReadable.js new file mode 100644 index 00000000000000..49051aed321ae0 --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-stream-readable-needReadable.js @@ -0,0 +1,106 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +'use strict'; +const common = require('../common'); +const assert = require('assert'); +const Readable = require('stream').Readable; + +const readable = new Readable({ + read: () => {} +}); + +// Initialized to false. +assert.strictEqual(readable._readableState.needReadable, false); + +readable.on('readable', common.mustCall(() => { + // When the readable event fires, needReadable is reset. + assert.strictEqual(readable._readableState.needReadable, false); + readable.read(); +})); + +// If a readable listener is attached, then a readable event is needed. +assert.strictEqual(readable._readableState.needReadable, true); + +readable.push('foo'); +readable.push(null); + +readable.on('end', common.mustCall(() => { + // No need to emit readable anymore when the stream ends. + assert.strictEqual(readable._readableState.needReadable, false); +})); + +const asyncReadable = new Readable({ + read: () => {} +}); + +asyncReadable.on('readable', common.mustCall(() => { + if (asyncReadable.read() !== null) { + // After each read(), the buffer is empty. + // If the stream doesn't end now, + // then we need to notify the reader on future changes. + assert.strictEqual(asyncReadable._readableState.needReadable, true); + } +}, 2)); + +process.nextTick(common.mustCall(() => { + asyncReadable.push('foooo'); +})); +process.nextTick(common.mustCall(() => { + asyncReadable.push('bar'); +})); +setImmediate(common.mustCall(() => { + asyncReadable.push(null); + assert.strictEqual(asyncReadable._readableState.needReadable, false); +})); + +const flowing = new Readable({ + read: () => {} +}); + +// Notice this must be above the on('data') call. +flowing.push('foooo'); +flowing.push('bar'); +flowing.push('quo'); +process.nextTick(common.mustCall(() => { + flowing.push(null); +})); + +// When the buffer already has enough data, and the stream is +// in flowing mode, there is no need for the readable event. +flowing.on('data', common.mustCall(function(data) { + assert.strictEqual(flowing._readableState.needReadable, false); +}, 3)); + +const slowProducer = new Readable({ + read: () => {} +}); + +slowProducer.on('readable', common.mustCall(() => { + const chunk = slowProducer.read(8); + const state = slowProducer._readableState; + if (chunk === null) { + // The buffer doesn't have enough data, and the stream is not need, + // we need to notify the reader when data arrives. + assert.strictEqual(state.needReadable, true); + } else { + assert.strictEqual(state.needReadable, false); + } +}, 4)); + +process.nextTick(common.mustCall(() => { + slowProducer.push('foo'); + process.nextTick(common.mustCall(() => { + slowProducer.push('foo'); + process.nextTick(common.mustCall(() => { + slowProducer.push('foo'); + process.nextTick(common.mustCall(() => { + slowProducer.push(null); + })); + })); + })); +})); diff --git a/cli/tests/node_compat/test/parallel/test-stream-readable-next-no-null.js b/cli/tests/node_compat/test/parallel/test-stream-readable-next-no-null.js new file mode 100644 index 00000000000000..4b48d3daaa1e07 --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-stream-readable-next-no-null.js @@ -0,0 +1,26 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +'use strict'; +const { mustNotCall, expectsError } = require('../common'); +const { Readable } = require('stream'); + +async function* generate() { + yield null; +} + +const stream = Readable.from(generate()); + +stream.on('error', expectsError({ + code: 'ERR_STREAM_NULL_VALUES', + name: 'TypeError', + message: 'May not write null values to stream' +})); + +stream.on('data', mustNotCall((chunk) => {})); + +stream.on('end', mustNotCall()); diff --git a/cli/tests/node_compat/test/parallel/test-stream-readable-no-unneeded-readable.js b/cli/tests/node_compat/test/parallel/test-stream-readable-no-unneeded-readable.js new file mode 100644 index 00000000000000..5c1064479638b7 --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-stream-readable-no-unneeded-readable.js @@ -0,0 +1,69 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +'use strict'; +const common = require('../common'); +const { Readable, PassThrough } = require('stream'); + +function test(r) { + const wrapper = new Readable({ + read: () => { + let data = r.read(); + + if (data) { + wrapper.push(data); + return; + } + + r.once('readable', function() { + data = r.read(); + if (data) { + wrapper.push(data); + } + // else: the end event should fire + }); + }, + }); + + r.once('end', function() { + wrapper.push(null); + }); + + wrapper.resume(); + wrapper.once('end', common.mustCall()); +} + +{ + const source = new Readable({ + read: () => {} + }); + source.push('foo'); + source.push('bar'); + source.push(null); + + const pt = source.pipe(new PassThrough()); + test(pt); +} + +{ + // This is the underlying cause of the above test case. + const pushChunks = ['foo', 'bar']; + const r = new Readable({ + read: () => { + const chunk = pushChunks.shift(); + if (chunk) { + // synchronous call + r.push(chunk); + } else { + // asynchronous call + process.nextTick(() => r.push(null)); + } + }, + }); + + test(r); +} diff --git a/cli/tests/node_compat/test/parallel/test-stream-readable-object-multi-push-async.js b/cli/tests/node_compat/test/parallel/test-stream-readable-object-multi-push-async.js new file mode 100644 index 00000000000000..543226e9811b09 --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-stream-readable-object-multi-push-async.js @@ -0,0 +1,190 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +'use strict'; + +const common = require('../common'); +const assert = require('assert'); +const { Readable } = require('stream'); + +const MAX = 42; +const BATCH = 10; + +{ + const readable = new Readable({ + objectMode: true, + read: common.mustCall(function() { + console.log('>> READ'); + fetchData((err, data) => { + if (err) { + this.destroy(err); + return; + } + + if (data.length === 0) { + console.log('pushing null'); + this.push(null); + return; + } + + console.log('pushing'); + data.forEach((d) => this.push(d)); + }); + }, Math.floor(MAX / BATCH) + 2) + }); + + let i = 0; + function fetchData(cb) { + if (i > MAX) { + setTimeout(cb, 10, null, []); + } else { + const array = []; + const max = i + BATCH; + for (; i < max; i++) { + array.push(i); + } + setTimeout(cb, 10, null, array); + } + } + + readable.on('readable', () => { + let data; + console.log('readable emitted'); + while ((data = readable.read()) !== null) { + console.log(data); + } + }); + + readable.on('end', common.mustCall(() => { + assert.strictEqual(i, (Math.floor(MAX / BATCH) + 1) * BATCH); + })); +} + +{ + const readable = new Readable({ + objectMode: true, + read: common.mustCall(function() { + console.log('>> READ'); + fetchData((err, data) => { + if (err) { + this.destroy(err); + return; + } + + if (data.length === 0) { + console.log('pushing null'); + this.push(null); + return; + } + + console.log('pushing'); + data.forEach((d) => this.push(d)); + }); + }, Math.floor(MAX / BATCH) + 2) + }); + + let i = 0; + function fetchData(cb) { + if (i > MAX) { + setTimeout(cb, 10, null, []); + } else { + const array = []; + const max = i + BATCH; + for (; i < max; i++) { + array.push(i); + } + setTimeout(cb, 10, null, array); + } + } + + readable.on('data', (data) => { + console.log('data emitted', data); + }); + + readable.on('end', common.mustCall(() => { + assert.strictEqual(i, (Math.floor(MAX / BATCH) + 1) * BATCH); + })); +} + +{ + const readable = new Readable({ + objectMode: true, + read: common.mustCall(function() { + console.log('>> READ'); + fetchData((err, data) => { + if (err) { + this.destroy(err); + return; + } + + console.log('pushing'); + data.forEach((d) => this.push(d)); + + if (data[BATCH - 1] >= MAX) { + console.log('pushing null'); + this.push(null); + } + }); + }, Math.floor(MAX / BATCH) + 1) + }); + + let i = 0; + function fetchData(cb) { + const array = []; + const max = i + BATCH; + for (; i < max; i++) { + array.push(i); + } + setTimeout(cb, 10, null, array); + } + + readable.on('data', (data) => { + console.log('data emitted', data); + }); + + readable.on('end', common.mustCall(() => { + assert.strictEqual(i, (Math.floor(MAX / BATCH) + 1) * BATCH); + })); +} + +{ + const readable = new Readable({ + objectMode: true, + read: common.mustNotCall() + }); + + readable.on('data', common.mustNotCall()); + + readable.push(null); + + let nextTickPassed = false; + process.nextTick(() => { + nextTickPassed = true; + }); + + readable.on('end', common.mustCall(() => { + assert.strictEqual(nextTickPassed, true); + })); +} + +{ + const readable = new Readable({ + objectMode: true, + read: common.mustCall() + }); + + readable.on('data', (data) => { + console.log('data emitted', data); + }); + + readable.on('end', common.mustCall()); + + setImmediate(() => { + readable.push('aaa'); + readable.push(null); + }); +} diff --git a/cli/tests/node_compat/test/parallel/test-stream-readable-pause-and-resume.js b/cli/tests/node_compat/test/parallel/test-stream-readable-pause-and-resume.js new file mode 100644 index 00000000000000..6c57b0a70a5673 --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-stream-readable-pause-and-resume.js @@ -0,0 +1,81 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +'use strict'; + +const common = require('../common'); +const assert = require('assert'); +const { Readable } = require('stream'); + +let ticks = 18; +let expectedData = 19; + +const rs = new Readable({ + objectMode: true, + read: () => { + if (ticks-- > 0) + return process.nextTick(() => rs.push({})); + rs.push({}); + rs.push(null); + } +}); + +rs.on('end', common.mustCall()); +readAndPause(); + +function readAndPause() { + // Does a on(data) -> pause -> wait -> resume -> on(data) ... loop. + // Expects on(data) to never fire if the stream is paused. + const ondata = common.mustCall((data) => { + rs.pause(); + + expectedData--; + if (expectedData <= 0) + return; + + setImmediate(function() { + rs.removeListener('data', ondata); + readAndPause(); + rs.resume(); + }); + }, 1); // Only call ondata once + + rs.on('data', ondata); +} + +{ + const readable = new Readable({ + read() {} + }); + + function read() {} + + readable.setEncoding('utf8'); + readable.on('readable', read); + readable.removeListener('readable', read); + readable.pause(); + + process.nextTick(function() { + assert(readable.isPaused()); + }); +} + +{ + const { PassThrough } = require('stream'); + + const source3 = new PassThrough(); + const target3 = new PassThrough(); + + const chunk = Buffer.allocUnsafe(1000); + while (target3.write(chunk)); + + source3.pipe(target3); + target3.on('drain', common.mustCall(() => { + assert(!source3.isPaused()); + })); + target3.on('data', () => {}); +} diff --git a/cli/tests/node_compat/test/parallel/test-stream-readable-readable-then-resume.js b/cli/tests/node_compat/test/parallel/test-stream-readable-readable-then-resume.js new file mode 100644 index 00000000000000..f59b635b2b96ac --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-stream-readable-readable-then-resume.js @@ -0,0 +1,38 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +'use strict'; + +const common = require('../common'); +const { Readable } = require('stream'); +const assert = require('assert'); + +// This test verifies that a stream could be resumed after +// removing the readable event in the same tick + +check(new Readable({ + objectMode: true, + highWaterMark: 1, + read() { + if (!this.first) { + this.push('hello'); + this.first = true; + return; + } + + this.push(null); + } +})); + +function check(s) { + const readableListener = common.mustNotCall(); + s.on('readable', readableListener); + s.on('end', common.mustCall()); + assert.strictEqual(s.removeListener, s.off); + s.removeListener('readable', readableListener); + s.resume(); +} diff --git a/cli/tests/node_compat/test/parallel/test-stream-readable-readable.js b/cli/tests/node_compat/test/parallel/test-stream-readable-readable.js new file mode 100644 index 00000000000000..df3223bcd109e8 --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-stream-readable-readable.js @@ -0,0 +1,52 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +'use strict'; +const common = require('../common'); +const assert = require('assert'); + +const { Readable } = require('stream'); + +{ + const r = new Readable({ + read() {} + }); + assert.strictEqual(r.readable, true); + r.destroy(); + assert.strictEqual(r.readable, false); +} + +{ + const mustNotCall = common.mustNotCall(); + const r = new Readable({ + read() {} + }); + assert.strictEqual(r.readable, true); + r.on('end', mustNotCall); + r.resume(); + r.push(null); + assert.strictEqual(r.readable, true); + r.off('end', mustNotCall); + r.on('end', common.mustCall(() => { + assert.strictEqual(r.readable, false); + })); +} + +{ + const r = new Readable({ + read: common.mustCall(() => { + process.nextTick(() => { + r.destroy(new Error()); + assert.strictEqual(r.readable, false); + }); + }) + }); + r.resume(); + r.on('error', common.mustCall(() => { + assert.strictEqual(r.readable, false); + })); +} diff --git a/cli/tests/node_compat/test/parallel/test-stream-readable-reading-readingMore.js b/cli/tests/node_compat/test/parallel/test-stream-readable-reading-readingMore.js new file mode 100644 index 00000000000000..d39752d79cdef7 --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-stream-readable-reading-readingMore.js @@ -0,0 +1,178 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +'use strict'; +const common = require('../common'); +const assert = require('assert'); +const Readable = require('stream').Readable; + +{ + const readable = new Readable({ + read(size) {} + }); + + const state = readable._readableState; + + // Starting off with false initially. + assert.strictEqual(state.reading, false); + assert.strictEqual(state.readingMore, false); + + readable.on('data', common.mustCall((data) => { + // While in a flowing state with a 'readable' listener + // we should not be reading more + if (readable.readableFlowing) + assert.strictEqual(state.readingMore, true); + + // Reading as long as we've not ended + assert.strictEqual(state.reading, !state.ended); + }, 2)); + + function onStreamEnd() { + // End of stream; state.reading is false + // And so should be readingMore. + assert.strictEqual(state.readingMore, false); + assert.strictEqual(state.reading, false); + } + + const expectedReadingMore = [true, true, false]; + readable.on('readable', common.mustCall(() => { + // There is only one readingMore scheduled from on('data'), + // after which everything is governed by the .read() call + assert.strictEqual(state.readingMore, expectedReadingMore.shift()); + + // If the stream has ended, we shouldn't be reading + assert.strictEqual(state.ended, !state.reading); + + // Consume all the data + while (readable.read() !== null); + + if (expectedReadingMore.length === 0) // Reached end of stream + process.nextTick(common.mustCall(onStreamEnd, 1)); + }, 3)); + + readable.on('end', common.mustCall(onStreamEnd)); + readable.push('pushed'); + + readable.read(6); + + // reading + assert.strictEqual(state.reading, true); + assert.strictEqual(state.readingMore, true); + + // add chunk to front + readable.unshift('unshifted'); + + // end + readable.push(null); +} + +{ + const readable = new Readable({ + read(size) {} + }); + + const state = readable._readableState; + + // Starting off with false initially. + assert.strictEqual(state.reading, false); + assert.strictEqual(state.readingMore, false); + + readable.on('data', common.mustCall((data) => { + // While in a flowing state without a 'readable' listener + // we should be reading more + if (readable.readableFlowing) + assert.strictEqual(state.readingMore, true); + + // Reading as long as we've not ended + assert.strictEqual(state.reading, !state.ended); + }, 2)); + + function onStreamEnd() { + // End of stream; state.reading is false + // And so should be readingMore. + assert.strictEqual(state.readingMore, false); + assert.strictEqual(state.reading, false); + } + + readable.on('end', common.mustCall(onStreamEnd)); + readable.push('pushed'); + + // Stop emitting 'data' events + assert.strictEqual(state.flowing, true); + readable.pause(); + + // paused + assert.strictEqual(state.reading, false); + assert.strictEqual(state.flowing, false); + + readable.resume(); + assert.strictEqual(state.reading, false); + assert.strictEqual(state.flowing, true); + + // add chunk to front + readable.unshift('unshifted'); + + // end + readable.push(null); +} + +{ + const readable = new Readable({ + read(size) {} + }); + + const state = readable._readableState; + + // Starting off with false initially. + assert.strictEqual(state.reading, false); + assert.strictEqual(state.readingMore, false); + + const onReadable = common.mustNotCall(); + + readable.on('readable', onReadable); + + readable.on('data', common.mustCall((data) => { + // Reading as long as we've not ended + assert.strictEqual(state.reading, !state.ended); + }, 2)); + + readable.removeListener('readable', onReadable); + + function onStreamEnd() { + // End of stream; state.reading is false + // And so should be readingMore. + assert.strictEqual(state.readingMore, false); + assert.strictEqual(state.reading, false); + } + + readable.on('end', common.mustCall(onStreamEnd)); + readable.push('pushed'); + + // We are still not flowing, we will be resuming in the next tick + assert.strictEqual(state.flowing, false); + + // Wait for nextTick, so the readableListener flag resets + process.nextTick(function() { + readable.resume(); + + // Stop emitting 'data' events + assert.strictEqual(state.flowing, true); + readable.pause(); + + // paused + assert.strictEqual(state.flowing, false); + + readable.resume(); + assert.strictEqual(state.flowing, true); + + // add chunk to front + readable.unshift('unshifted'); + + // end + readable.push(null); + }); +} diff --git a/cli/tests/node_compat/test/parallel/test-stream-readable-resume-hwm.js b/cli/tests/node_compat/test/parallel/test-stream-readable-resume-hwm.js new file mode 100644 index 00000000000000..58a82fe427751b --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-stream-readable-resume-hwm.js @@ -0,0 +1,28 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +'use strict'; +const common = require('../common'); +const { Readable } = require('stream'); + +// readable.resume() should not lead to a ._read() call being scheduled +// when we exceed the high water mark already. + +const readable = new Readable({ + read: common.mustNotCall(), + highWaterMark: 100 +}); + +// Fill up the internal buffer so that we definitely exceed the HWM: +for (let i = 0; i < 10; i++) + readable.push('a'.repeat(200)); + +// Call resume, and pause after one chunk. +// The .pause() is just so that we don’t empty the buffer fully, which would +// be a valid reason to call ._read(). +readable.resume(); +readable.once('data', common.mustCall(() => readable.pause())); diff --git a/cli/tests/node_compat/test/parallel/test-stream-readable-resumeScheduled.js b/cli/tests/node_compat/test/parallel/test-stream-readable-resumeScheduled.js new file mode 100644 index 00000000000000..7c7362e97560e2 --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-stream-readable-resumeScheduled.js @@ -0,0 +1,72 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +'use strict'; +const common = require('../common'); + +// Testing Readable Stream resumeScheduled state + +const assert = require('assert'); +const { Readable, Writable } = require('stream'); + +{ + // pipe() test case + const r = new Readable({ read() {} }); + const w = new Writable(); + + // resumeScheduled should start = `false`. + assert.strictEqual(r._readableState.resumeScheduled, false); + + // Calling pipe() should change the state value = true. + r.pipe(w); + assert.strictEqual(r._readableState.resumeScheduled, true); + + process.nextTick(common.mustCall(() => { + assert.strictEqual(r._readableState.resumeScheduled, false); + })); +} + +{ + // 'data' listener test case + const r = new Readable({ read() {} }); + + // resumeScheduled should start = `false`. + assert.strictEqual(r._readableState.resumeScheduled, false); + + r.push(Buffer.from([1, 2, 3])); + + // Adding 'data' listener should change the state value + r.on('data', common.mustCall(() => { + assert.strictEqual(r._readableState.resumeScheduled, false); + })); + assert.strictEqual(r._readableState.resumeScheduled, true); + + process.nextTick(common.mustCall(() => { + assert.strictEqual(r._readableState.resumeScheduled, false); + })); +} + +{ + // resume() test case + const r = new Readable({ read() {} }); + + // resumeScheduled should start = `false`. + assert.strictEqual(r._readableState.resumeScheduled, false); + + // Calling resume() should change the state value. + r.resume(); + assert.strictEqual(r._readableState.resumeScheduled, true); + + r.on('resume', common.mustCall(() => { + // The state value should be `false` again + assert.strictEqual(r._readableState.resumeScheduled, false); + })); + + process.nextTick(common.mustCall(() => { + assert.strictEqual(r._readableState.resumeScheduled, false); + })); +} diff --git a/cli/tests/node_compat/test/parallel/test-stream-readable-setEncoding-existing-buffers.js b/cli/tests/node_compat/test/parallel/test-stream-readable-setEncoding-existing-buffers.js new file mode 100644 index 00000000000000..87d51504a16926 --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-stream-readable-setEncoding-existing-buffers.js @@ -0,0 +1,67 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +'use strict'; +require('../common'); +const { Readable } = require('stream'); +const assert = require('assert'); + +{ + // Call .setEncoding() while there are bytes already in the buffer. + const r = new Readable({ read() {} }); + + r.push(Buffer.from('a')); + r.push(Buffer.from('b')); + + r.setEncoding('utf8'); + const chunks = []; + r.on('data', (chunk) => chunks.push(chunk)); + + process.nextTick(() => { + assert.deepStrictEqual(chunks, ['ab']); + }); +} + +{ + // Call .setEncoding() while the buffer contains a complete, + // but chunked character. + const r = new Readable({ read() {} }); + + r.push(Buffer.from([0xf0])); + r.push(Buffer.from([0x9f])); + r.push(Buffer.from([0x8e])); + r.push(Buffer.from([0x89])); + + r.setEncoding('utf8'); + const chunks = []; + r.on('data', (chunk) => chunks.push(chunk)); + + process.nextTick(() => { + assert.deepStrictEqual(chunks, ['🎉']); + }); +} + +{ + // Call .setEncoding() while the buffer contains an incomplete character, + // and finish the character later. + const r = new Readable({ read() {} }); + + r.push(Buffer.from([0xf0])); + r.push(Buffer.from([0x9f])); + + r.setEncoding('utf8'); + + r.push(Buffer.from([0x8e])); + r.push(Buffer.from([0x89])); + + const chunks = []; + r.on('data', (chunk) => chunks.push(chunk)); + + process.nextTick(() => { + assert.deepStrictEqual(chunks, ['🎉']); + }); +} diff --git a/cli/tests/node_compat/test/parallel/test-stream-readable-setEncoding-null.js b/cli/tests/node_compat/test/parallel/test-stream-readable-setEncoding-null.js new file mode 100644 index 00000000000000..e0c9cf805e89ee --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-stream-readable-setEncoding-null.js @@ -0,0 +1,22 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +'use strict'; + +require('../common'); +const assert = require('assert'); +const { Readable } = require('stream'); + + +{ + const readable = new Readable({ encoding: 'hex' }); + assert.strictEqual(readable._readableState.encoding, 'hex'); + + readable.setEncoding(null); + + assert.strictEqual(readable._readableState.encoding, 'utf8'); +} diff --git a/cli/tests/node_compat/test/parallel/test-stream-readable-unshift.js b/cli/tests/node_compat/test/parallel/test-stream-readable-unshift.js new file mode 100644 index 00000000000000..114fd7a1ccce54 --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-stream-readable-unshift.js @@ -0,0 +1,177 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +'use strict'; + +const common = require('../common'); +const assert = require('assert'); +const { Readable } = require('stream'); + +{ + // Check that strings are saved as Buffer + const readable = new Readable({ read() {} }); + + const string = 'abc'; + + readable.on('data', common.mustCall((chunk) => { + assert(Buffer.isBuffer(chunk)); + assert.strictEqual(chunk.toString('utf8'), string); + }, 1)); + + readable.unshift(string); + +} + +{ + // Check that data goes at the beginning + const readable = new Readable({ read() {} }); + const unshift = 'front'; + const push = 'back'; + + const expected = [unshift, push]; + readable.on('data', common.mustCall((chunk) => { + assert.strictEqual(chunk.toString('utf8'), expected.shift()); + }, 2)); + + + readable.push(push); + readable.unshift(unshift); +} + +{ + // Check that buffer is saved with correct encoding + const readable = new Readable({ read() {} }); + + const encoding = 'base64'; + const string = Buffer.from('abc').toString(encoding); + + readable.on('data', common.mustCall((chunk) => { + assert.strictEqual(chunk.toString(encoding), string); + }, 1)); + + readable.unshift(string, encoding); + +} + +{ + + const streamEncoding = 'base64'; + + function checkEncoding(readable) { + + // chunk encodings + const encodings = ['utf8', 'binary', 'hex', 'base64']; + const expected = []; + + readable.on('data', common.mustCall((chunk) => { + const { encoding, string } = expected.pop(); + assert.strictEqual(chunk.toString(encoding), string); + }, encodings.length)); + + for (const encoding of encodings) { + const string = 'abc'; + + // If encoding is the same as the state.encoding the string is + // saved as is + const expect = encoding !== streamEncoding ? + Buffer.from(string, encoding).toString(streamEncoding) : string; + + expected.push({ encoding, string: expect }); + + readable.unshift(string, encoding); + } + } + + const r1 = new Readable({ read() {} }); + r1.setEncoding(streamEncoding); + checkEncoding(r1); + + const r2 = new Readable({ read() {}, encoding: streamEncoding }); + checkEncoding(r2); + +} + +{ + // Both .push & .unshift should have the same behaviour + // When setting an encoding, each chunk should be emitted with that encoding + const encoding = 'base64'; + + function checkEncoding(readable) { + const string = 'abc'; + readable.on('data', common.mustCall((chunk) => { + assert.strictEqual(chunk, Buffer.from(string).toString(encoding)); + }, 2)); + + readable.push(string); + readable.unshift(string); + } + + const r1 = new Readable({ read() {} }); + r1.setEncoding(encoding); + checkEncoding(r1); + + const r2 = new Readable({ read() {}, encoding }); + checkEncoding(r2); + +} + +{ + // Check that ObjectMode works + const readable = new Readable({ objectMode: true, read() {} }); + + const chunks = ['a', 1, {}, []]; + + readable.on('data', common.mustCall((chunk) => { + assert.strictEqual(chunk, chunks.pop()); + }, chunks.length)); + + for (const chunk of chunks) { + readable.unshift(chunk); + } +} + +{ + + // Should not throw: https://github.com/nodejs/node/issues/27192 + const highWaterMark = 50; + class ArrayReader extends Readable { + constructor(opt) { + super({ highWaterMark }); + // The error happened only when pushing above hwm + this.buffer = new Array(highWaterMark * 2).fill(0).map(String); + } + _read(size) { + while (this.buffer.length) { + const chunk = this.buffer.shift(); + if (!this.buffer.length) { + this.push(chunk); + this.push(null); + return true; + } + if (!this.push(chunk)) + return; + } + } + } + + function onRead() { + while (null !== (stream.read())) { + // Remove the 'readable' listener before unshifting + stream.removeListener('readable', onRead); + stream.unshift('a'); + stream.on('data', (chunk) => { + console.log(chunk.length); + }); + break; + } + } + + const stream = new ArrayReader(); + stream.once('readable', common.mustCall(onRead)); + stream.on('end', common.mustCall(() => {})); + +} diff --git a/cli/tests/node_compat/test/parallel/test-stream-readable-with-unimplemented-_read.js b/cli/tests/node_compat/test/parallel/test-stream-readable-with-unimplemented-_read.js new file mode 100644 index 00000000000000..d55343536003e2 --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-stream-readable-with-unimplemented-_read.js @@ -0,0 +1,20 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +'use strict'; +const common = require('../common'); +const { Readable } = require('stream'); + +const readable = new Readable(); + +readable.read(); +readable.on('error', common.expectsError({ + code: 'ERR_METHOD_NOT_IMPLEMENTED', + name: 'Error', + message: 'The _read() method is not implemented' +})); +readable.on('close', common.mustCall()); diff --git a/cli/tests/node_compat/test/parallel/test-stream-readableListening-state.js b/cli/tests/node_compat/test/parallel/test-stream-readableListening-state.js new file mode 100644 index 00000000000000..524589acd06147 --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-stream-readableListening-state.js @@ -0,0 +1,41 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +'use strict'; + +const common = require('../common'); +const assert = require('assert'); +const stream = require('stream'); + +const r = new stream.Readable({ + read: () => {} +}); + +// readableListening state should start in `false`. +assert.strictEqual(r._readableState.readableListening, false); + +r.on('readable', common.mustCall(() => { + // Inside the readable event this state should be true. + assert.strictEqual(r._readableState.readableListening, true); +})); + +r.push(Buffer.from('Testing readableListening state')); + +const r2 = new stream.Readable({ + read: () => {} +}); + +// readableListening state should start in `false`. +assert.strictEqual(r2._readableState.readableListening, false); + +r2.on('data', common.mustCall((chunk) => { + // readableListening should be false because we don't have + // a `readable` listener + assert.strictEqual(r2._readableState.readableListening, false); +})); + +r2.push(Buffer.from('Testing readableListening state')); diff --git a/cli/tests/node_compat/test/parallel/test-stream-some-find-every.mjs b/cli/tests/node_compat/test/parallel/test-stream-some-find-every.mjs new file mode 100644 index 00000000000000..61bbce21d48ad7 --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-stream-some-find-every.mjs @@ -0,0 +1,179 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +import * as common from '../common/index.mjs'; +import { setTimeout } from 'timers/promises'; +import { Readable } from 'stream'; +import assert from 'assert'; + + +function oneTo5() { + return Readable.from([1, 2, 3, 4, 5]); +} + +function oneTo5Async() { + return oneTo5().map(async (x) => { + await Promise.resolve(); + return x; + }); +} +{ + // Some, find, and every work with a synchronous stream and predicate + assert.strictEqual(await oneTo5().some((x) => x > 3), true); + assert.strictEqual(await oneTo5().every((x) => x > 3), false); + assert.strictEqual(await oneTo5().find((x) => x > 3), 4); + assert.strictEqual(await oneTo5().some((x) => x > 6), false); + assert.strictEqual(await oneTo5().every((x) => x < 6), true); + assert.strictEqual(await oneTo5().find((x) => x > 6), undefined); + assert.strictEqual(await Readable.from([]).some(() => true), false); + assert.strictEqual(await Readable.from([]).every(() => true), true); + assert.strictEqual(await Readable.from([]).find(() => true), undefined); +} + +{ + // Some, find, and every work with an asynchronous stream and synchronous predicate + assert.strictEqual(await oneTo5Async().some((x) => x > 3), true); + assert.strictEqual(await oneTo5Async().every((x) => x > 3), false); + assert.strictEqual(await oneTo5Async().find((x) => x > 3), 4); + assert.strictEqual(await oneTo5Async().some((x) => x > 6), false); + assert.strictEqual(await oneTo5Async().every((x) => x < 6), true); + assert.strictEqual(await oneTo5Async().find((x) => x > 6), undefined); +} + +{ + // Some, find, and every work on synchronous streams with an asynchronous predicate + assert.strictEqual(await oneTo5().some(async (x) => x > 3), true); + assert.strictEqual(await oneTo5().every(async (x) => x > 3), false); + assert.strictEqual(await oneTo5().find(async (x) => x > 3), 4); + assert.strictEqual(await oneTo5().some(async (x) => x > 6), false); + assert.strictEqual(await oneTo5().every(async (x) => x < 6), true); + assert.strictEqual(await oneTo5().find(async (x) => x > 6), undefined); +} + +{ + // Some, find, and every work on asynchronous streams with an asynchronous predicate + assert.strictEqual(await oneTo5Async().some(async (x) => x > 3), true); + assert.strictEqual(await oneTo5Async().every(async (x) => x > 3), false); + assert.strictEqual(await oneTo5Async().find(async (x) => x > 3), 4); + assert.strictEqual(await oneTo5Async().some(async (x) => x > 6), false); + assert.strictEqual(await oneTo5Async().every(async (x) => x < 6), true); + assert.strictEqual(await oneTo5Async().find(async (x) => x > 6), undefined); +} + +{ + async function checkDestroyed(stream) { + await setTimeout(); + assert.strictEqual(stream.destroyed, true); + } + + { + // Some, find, and every short circuit + const someStream = oneTo5(); + await someStream.some(common.mustCall((x) => x > 2, 3)); + await checkDestroyed(someStream); + + const everyStream = oneTo5(); + await everyStream.every(common.mustCall((x) => x < 3, 3)); + await checkDestroyed(everyStream); + + const findStream = oneTo5(); + await findStream.find(common.mustCall((x) => x > 1, 2)); + await checkDestroyed(findStream); + + // When short circuit isn't possible the whole stream is iterated + await oneTo5().some(common.mustCall(() => false, 5)); + await oneTo5().every(common.mustCall(() => true, 5)); + await oneTo5().find(common.mustCall(() => false, 5)); + } + + { + // Some, find, and every short circuit async stream/predicate + const someStream = oneTo5Async(); + await someStream.some(common.mustCall(async (x) => x > 2, 3)); + await checkDestroyed(someStream); + + const everyStream = oneTo5Async(); + await everyStream.every(common.mustCall(async (x) => x < 3, 3)); + await checkDestroyed(everyStream); + + const findStream = oneTo5Async(); + await findStream.find(common.mustCall(async (x) => x > 1, 2)); + await checkDestroyed(findStream); + + // When short circuit isn't possible the whole stream is iterated + await oneTo5Async().some(common.mustCall(async () => false, 5)); + await oneTo5Async().every(common.mustCall(async () => true, 5)); + await oneTo5Async().find(common.mustCall(async () => false, 5)); + } +} + +{ + // Concurrency doesn't affect which value is found. + const found = await Readable.from([1, 2]).find(async (val) => { + if (val === 1) { + await setTimeout(100); + } + return true; + }, { concurrency: 2 }); + assert.strictEqual(found, 1); +} + +{ + // Support for AbortSignal + for (const op of ['some', 'every', 'find']) { + { + const ac = new AbortController(); + assert.rejects(Readable.from([1, 2, 3])[op]( + () => new Promise(() => { }), + { signal: ac.signal } + ), { + name: 'AbortError', + }, `${op} should abort correctly with sync abort`).then(common.mustCall()); + ac.abort(); + } + { + // Support for pre-aborted AbortSignal + assert.rejects(Readable.from([1, 2, 3])[op]( + () => new Promise(() => { }), + { signal: AbortSignal.abort() } + ), { + name: 'AbortError', + }, `${op} should abort with pre-aborted abort controller`).then(common.mustCall()); + } + } +} +{ + // Error cases + for (const op of ['some', 'every', 'find']) { + assert.rejects(async () => { + await Readable.from([1])[op](1); + }, /ERR_INVALID_ARG_TYPE/, `${op} should throw for invalid function`).then(common.mustCall()); + assert.rejects(async () => { + await Readable.from([1])[op]((x) => x, { + concurrency: 'Foo' + }); + }, /ERR_OUT_OF_RANGE/, `${op} should throw for invalid concurrency`).then(common.mustCall()); + assert.rejects(async () => { + await Readable.from([1])[op]((x) => x, 1); + }, /ERR_INVALID_ARG_TYPE/, `${op} should throw for invalid concurrency`).then(common.mustCall()); + assert.rejects(async () => { + await Readable.from([1])[op]((x) => x, { + signal: true + }); + }, /ERR_INVALID_ARG_TYPE/, `${op} should throw for invalid signal`).then(common.mustCall()); + } +} +{ + for (const op of ['some', 'every', 'find']) { + const stream = oneTo5(); + Object.defineProperty(stream, 'map', { + value: common.mustNotCall(() => {}), + }); + // Check that map isn't getting called. + stream[op](() => {}); + } +} diff --git a/cli/tests/node_compat/test/parallel/test-stream-transform-callback-twice.js b/cli/tests/node_compat/test/parallel/test-stream-transform-callback-twice.js new file mode 100644 index 00000000000000..395090258e2e29 --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-stream-transform-callback-twice.js @@ -0,0 +1,21 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +'use strict'; +const common = require('../common'); +const { Transform } = require('stream'); +const stream = new Transform({ + transform(chunk, enc, cb) { cb(); cb(); } +}); + +stream.on('error', common.expectsError({ + name: 'Error', + message: 'Callback called multiple times', + code: 'ERR_MULTIPLE_CALLBACK' +})); + +stream.write('foo'); diff --git a/cli/tests/node_compat/test/parallel/test-stream-transform-constructor-set-methods.js b/cli/tests/node_compat/test/parallel/test-stream-transform-constructor-set-methods.js new file mode 100644 index 00000000000000..b861de6f52bbd6 --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-stream-transform-constructor-set-methods.js @@ -0,0 +1,50 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +'use strict'; +const common = require('../common'); + +const assert = require('assert'); +const { Transform } = require('stream'); + +const t = new Transform(); + +assert.throws( + () => { + t.end(Buffer.from('blerg')); + }, + { + name: 'Error', + code: 'ERR_METHOD_NOT_IMPLEMENTED', + message: 'The _transform() method is not implemented' + } +); + +const _transform = common.mustCall((chunk, _, next) => { + next(); +}); + +const _final = common.mustCall((next) => { + next(); +}); + +const _flush = common.mustCall((next) => { + next(); +}); + +const t2 = new Transform({ + transform: _transform, + flush: _flush, + final: _final +}); + +assert.strictEqual(t2._transform, _transform); +assert.strictEqual(t2._flush, _flush); +assert.strictEqual(t2._final, _final); + +t2.end(Buffer.from('blerg')); +t2.resume(); diff --git a/cli/tests/node_compat/test/parallel/test-stream-transform-destroy.js b/cli/tests/node_compat/test/parallel/test-stream-transform-destroy.js new file mode 100644 index 00000000000000..878ca71931e208 --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-stream-transform-destroy.js @@ -0,0 +1,150 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +'use strict'; + +const common = require('../common'); +const { Transform } = require('stream'); +const assert = require('assert'); + +{ + const transform = new Transform({ + transform(chunk, enc, cb) {} + }); + + transform.resume(); + + transform.on('end', common.mustNotCall()); + transform.on('close', common.mustCall()); + transform.on('finish', common.mustNotCall()); + + transform.destroy(); +} + +{ + const transform = new Transform({ + transform(chunk, enc, cb) {} + }); + transform.resume(); + + const expected = new Error('kaboom'); + + transform.on('end', common.mustNotCall()); + transform.on('finish', common.mustNotCall()); + transform.on('close', common.mustCall()); + transform.on('error', common.mustCall((err) => { + assert.strictEqual(err, expected); + })); + + transform.destroy(expected); +} + +{ + const transform = new Transform({ + transform(chunk, enc, cb) {} + }); + + transform._destroy = common.mustCall(function(err, cb) { + assert.strictEqual(err, expected); + cb(err); + }, 1); + + const expected = new Error('kaboom'); + + transform.on('finish', common.mustNotCall('no finish event')); + transform.on('close', common.mustCall()); + transform.on('error', common.mustCall((err) => { + assert.strictEqual(err, expected); + })); + + transform.destroy(expected); +} + +{ + const expected = new Error('kaboom'); + const transform = new Transform({ + transform(chunk, enc, cb) {}, + destroy: common.mustCall(function(err, cb) { + assert.strictEqual(err, expected); + cb(); + }, 1) + }); + transform.resume(); + + transform.on('end', common.mustNotCall('no end event')); + transform.on('close', common.mustCall()); + transform.on('finish', common.mustNotCall('no finish event')); + + // Error is swallowed by the custom _destroy + transform.on('error', common.mustNotCall('no error event')); + + transform.destroy(expected); +} + +{ + const transform = new Transform({ + transform(chunk, enc, cb) {} + }); + + transform._destroy = common.mustCall(function(err, cb) { + assert.strictEqual(err, null); + cb(); + }, 1); + + transform.destroy(); +} + +{ + const transform = new Transform({ + transform(chunk, enc, cb) {} + }); + transform.resume(); + + transform._destroy = common.mustCall(function(err, cb) { + assert.strictEqual(err, null); + process.nextTick(() => { + this.push(null); + this.end(); + cb(); + }); + }, 1); + + const fail = common.mustNotCall('no event'); + + transform.on('finish', fail); + transform.on('end', fail); + transform.on('close', common.mustCall()); + + transform.destroy(); + + transform.removeListener('end', fail); + transform.removeListener('finish', fail); + transform.on('end', common.mustCall()); + transform.on('finish', common.mustNotCall()); +} + +{ + const transform = new Transform({ + transform(chunk, enc, cb) {} + }); + + const expected = new Error('kaboom'); + + transform._destroy = common.mustCall(function(err, cb) { + assert.strictEqual(err, null); + cb(expected); + }, 1); + + transform.on('close', common.mustCall()); + transform.on('finish', common.mustNotCall('no finish event')); + transform.on('end', common.mustNotCall('no end event')); + transform.on('error', common.mustCall((err) => { + assert.strictEqual(err, expected); + })); + + transform.destroy(); +} diff --git a/cli/tests/node_compat/test/parallel/test-stream-transform-final-sync.js b/cli/tests/node_compat/test/parallel/test-stream-transform-final-sync.js new file mode 100644 index 00000000000000..019f34d15ce69b --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-stream-transform-final-sync.js @@ -0,0 +1,117 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +'use strict'; +const common = require('../common'); +const assert = require('assert'); + +const stream = require('stream'); +let state = 0; + + +// What you do +// +// const stream = new stream.Transform({ +// transform: function transformCallback(chunk, _, next) { +// // part 1 +// this.push(chunk); +// //part 2 +// next(); +// }, +// final: function endCallback(done) { +// // part 1 +// process.nextTick(function () { +// // part 2 +// done(); +// }); +// }, +// flush: function flushCallback(done) { +// // part 1 +// process.nextTick(function () { +// // part 2 +// done(); +// }); +// } +// }); +// t.on('data', dataListener); +// t.on('end', endListener); +// t.on('finish', finishListener); +// t.write(1); +// t.write(4); +// t.end(7, endMethodCallback); +// +// The order things are called +// +// 1. transformCallback part 1 +// 2. dataListener +// 3. transformCallback part 2 +// 4. transformCallback part 1 +// 5. dataListener +// 6. transformCallback part 2 +// 7. transformCallback part 1 +// 8. dataListener +// 9. transformCallback part 2 +// 10. finalCallback part 1 +// 11. finalCallback part 2 +// 12. flushCallback part 1 +// 13. finishListener +// 14. endMethodCallback +// 15. flushCallback part 2 +// 16. endListener + +const t = new stream.Transform({ + objectMode: true, + transform: common.mustCall(function(chunk, _, next) { + // transformCallback part 1 + assert.strictEqual(++state, chunk); + this.push(state); + // transformCallback part 2 + assert.strictEqual(++state, chunk + 2); + process.nextTick(next); + }, 3), + final: common.mustCall(function(done) { + state++; + // finalCallback part 1 + assert.strictEqual(state, 10); + state++; + // finalCallback part 2 + assert.strictEqual(state, 11); + done(); + }, 1), + flush: common.mustCall(function(done) { + state++; + // fluchCallback part 1 + assert.strictEqual(state, 12); + process.nextTick(function() { + state++; + // fluchCallback part 2 + assert.strictEqual(state, 13); + done(); + }); + }, 1) +}); +t.on('finish', common.mustCall(function() { + state++; + // finishListener + assert.strictEqual(state, 15); +}, 1)); +t.on('end', common.mustCall(function() { + state++; + // endEvent + assert.strictEqual(state, 16); +}, 1)); +t.on('data', common.mustCall(function(d) { + // dataListener + assert.strictEqual(++state, d + 1); +}, 3)); +t.write(1); +t.write(4); +t.end(7, common.mustCall(function() { + state++; + // endMethodCallback + assert.strictEqual(state, 14); +}, 1)); diff --git a/cli/tests/node_compat/test/parallel/test-stream-transform-final.js b/cli/tests/node_compat/test/parallel/test-stream-transform-final.js new file mode 100644 index 00000000000000..9bc9ffec792244 --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-stream-transform-final.js @@ -0,0 +1,119 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +'use strict'; +const common = require('../common'); +const assert = require('assert'); + +const stream = require('stream'); +let state = 0; + + +// What you do: +// +// const stream = new stream.Transform({ +// transform: function transformCallback(chunk, _, next) { +// // part 1 +// this.push(chunk); +// //part 2 +// next(); +// }, +// final: function endCallback(done) { +// // part 1 +// process.nextTick(function () { +// // part 2 +// done(); +// }); +// }, +// flush: function flushCallback(done) { +// // part 1 +// process.nextTick(function () { +// // part 2 +// done(); +// }); +// } +// }); +// t.on('data', dataListener); +// t.on('end', endListener); +// t.on('finish', finishListener); +// t.write(1); +// t.write(4); +// t.end(7, endMethodCallback); +// +// The order things are called + +// 1. transformCallback part 1 +// 2. dataListener +// 3. transformCallback part 2 +// 4. transformCallback part 1 +// 5. dataListener +// 6. transformCallback part 2 +// 7. transformCallback part 1 +// 8. dataListener +// 9. transformCallback part 2 +// 10. finalCallback part 1 +// 11. finalCallback part 2 +// 12. flushCallback part 1 +// 13. finishListener +// 14. endMethodCallback +// 15. flushCallback part 2 +// 16. endListener + +const t = new stream.Transform({ + objectMode: true, + transform: common.mustCall(function(chunk, _, next) { + // transformCallback part 1 + assert.strictEqual(++state, chunk); + this.push(state); + // transformCallback part 2 + assert.strictEqual(++state, chunk + 2); + process.nextTick(next); + }, 3), + final: common.mustCall(function(done) { + state++; + // finalCallback part 1 + assert.strictEqual(state, 10); + setTimeout(function() { + state++; + // finalCallback part 2 + assert.strictEqual(state, 11); + done(); + }, 100); + }, 1), + flush: common.mustCall(function(done) { + state++; + // flushCallback part 1 + assert.strictEqual(state, 12); + process.nextTick(function() { + state++; + // flushCallback part 2 + assert.strictEqual(state, 13); + done(); + }); + }, 1) +}); +t.on('finish', common.mustCall(function() { + state++; + // finishListener + assert.strictEqual(state, 15); +}, 1)); +t.on('end', common.mustCall(function() { + state++; + // end event + assert.strictEqual(state, 16); +}, 1)); +t.on('data', common.mustCall(function(d) { + // dataListener + assert.strictEqual(++state, d + 1); +}, 3)); +t.write(1); +t.write(4); +t.end(7, common.mustCall(function() { + state++; + // endMethodCallback + assert.strictEqual(state, 14); +}, 1)); diff --git a/cli/tests/node_compat/test/parallel/test-stream-transform-flush-data.js b/cli/tests/node_compat/test/parallel/test-stream-transform-flush-data.js new file mode 100644 index 00000000000000..4a831a5d3d3526 --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-stream-transform-flush-data.js @@ -0,0 +1,35 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +'use strict'; + +require('../common'); + +const assert = require('assert'); +const Transform = require('stream').Transform; + + +const expected = 'asdf'; + + +function _transform(d, e, n) { + n(); +} + +function _flush(n) { + n(null, expected); +} + +const t = new Transform({ + transform: _transform, + flush: _flush +}); + +t.end(Buffer.from('blerg')); +t.on('data', (data) => { + assert.strictEqual(data.toString(), expected); +}); diff --git a/cli/tests/node_compat/test/parallel/test-stream-transform-objectmode-falsey-value.js b/cli/tests/node_compat/test/parallel/test-stream-transform-objectmode-falsey-value.js new file mode 100644 index 00000000000000..e692242a90e780 --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-stream-transform-objectmode-falsey-value.js @@ -0,0 +1,58 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. + +'use strict'; +const common = require('../common'); +const assert = require('assert'); + +const stream = require('stream'); +const PassThrough = stream.PassThrough; + +const src = new PassThrough({ objectMode: true }); +const tx = new PassThrough({ objectMode: true }); +const dest = new PassThrough({ objectMode: true }); + +const expect = [ -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 ]; +const results = []; + +dest.on('data', common.mustCall(function(x) { + results.push(x); +}, expect.length)); + +src.pipe(tx).pipe(dest); + +let i = -1; +const int = setInterval(common.mustCall(function() { + if (results.length === expect.length) { + src.end(); + clearInterval(int); + assert.deepStrictEqual(results, expect); + } else { + src.write(i++); + } +}, expect.length + 1), 1); diff --git a/cli/tests/node_compat/test/parallel/test-stream-transform-split-highwatermark.js b/cli/tests/node_compat/test/parallel/test-stream-transform-split-highwatermark.js new file mode 100644 index 00000000000000..c58b863b8c83d2 --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-stream-transform-split-highwatermark.js @@ -0,0 +1,80 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +'use strict'; +require('../common'); +const assert = require('assert'); + +const { Transform, Readable, Writable } = require('stream'); + +const DEFAULT = 16 * 1024; + +function testTransform(expectedReadableHwm, expectedWritableHwm, options) { + const t = new Transform(options); + assert.strictEqual(t._readableState.highWaterMark, expectedReadableHwm); + assert.strictEqual(t._writableState.highWaterMark, expectedWritableHwm); +} + +// Test overriding defaultHwm +testTransform(666, DEFAULT, { readableHighWaterMark: 666 }); +testTransform(DEFAULT, 777, { writableHighWaterMark: 777 }); +testTransform(666, 777, { + readableHighWaterMark: 666, + writableHighWaterMark: 777, +}); + +// Test highWaterMark overriding +testTransform(555, 555, { + highWaterMark: 555, + readableHighWaterMark: 666, +}); +testTransform(555, 555, { + highWaterMark: 555, + writableHighWaterMark: 777, +}); +testTransform(555, 555, { + highWaterMark: 555, + readableHighWaterMark: 666, + writableHighWaterMark: 777, +}); + +// Test undefined, null +[undefined, null].forEach((v) => { + testTransform(DEFAULT, DEFAULT, { readableHighWaterMark: v }); + testTransform(DEFAULT, DEFAULT, { writableHighWaterMark: v }); + testTransform(666, DEFAULT, { highWaterMark: v, readableHighWaterMark: 666 }); + testTransform(DEFAULT, 777, { highWaterMark: v, writableHighWaterMark: 777 }); +}); + +// test NaN +{ + assert.throws(() => { + new Transform({ readableHighWaterMark: NaN }); + }, { + name: 'TypeError', + code: 'ERR_INVALID_ARG_VALUE', + message: "The property 'options.readableHighWaterMark' is invalid. " + + 'Received NaN' + }); + + assert.throws(() => { + new Transform({ writableHighWaterMark: NaN }); + }, { + name: 'TypeError', + code: 'ERR_INVALID_ARG_VALUE', + message: "The property 'options.writableHighWaterMark' is invalid. " + + 'Received NaN' + }); +} + +// Test non Duplex streams ignore the options +{ + const r = new Readable({ readableHighWaterMark: 666 }); + assert.strictEqual(r._readableState.highWaterMark, DEFAULT); + const w = new Writable({ writableHighWaterMark: 777 }); + assert.strictEqual(w._writableState.highWaterMark, DEFAULT); +} diff --git a/cli/tests/node_compat/test/parallel/test-stream-transform-split-objectmode.js b/cli/tests/node_compat/test/parallel/test-stream-transform-split-objectmode.js new file mode 100644 index 00000000000000..84d21a8ccd9e69 --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-stream-transform-split-objectmode.js @@ -0,0 +1,88 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. + +'use strict'; +require('../common'); +const assert = require('assert'); + +const Transform = require('stream').Transform; + +const parser = new Transform({ readableObjectMode: true }); + +assert(parser._readableState.objectMode); +assert(!parser._writableState.objectMode); +assert.strictEqual(parser.readableHighWaterMark, 16); +assert.strictEqual(parser.writableHighWaterMark, 16 * 1024); +assert.strictEqual(parser.readableHighWaterMark, + parser._readableState.highWaterMark); +assert.strictEqual(parser.writableHighWaterMark, + parser._writableState.highWaterMark); + +parser._transform = function(chunk, enc, callback) { + callback(null, { val: chunk[0] }); +}; + +let parsed; + +parser.on('data', function(obj) { + parsed = obj; +}); + +parser.end(Buffer.from([42])); + +process.on('exit', function() { + assert.strictEqual(parsed.val, 42); +}); + + +const serializer = new Transform({ writableObjectMode: true }); + +assert(!serializer._readableState.objectMode); +assert(serializer._writableState.objectMode); +assert.strictEqual(serializer.readableHighWaterMark, 16 * 1024); +assert.strictEqual(serializer.writableHighWaterMark, 16); +assert.strictEqual(parser.readableHighWaterMark, + parser._readableState.highWaterMark); +assert.strictEqual(parser.writableHighWaterMark, + parser._writableState.highWaterMark); + +serializer._transform = function(obj, _, callback) { + callback(null, Buffer.from([obj.val])); +}; + +let serialized; + +serializer.on('data', function(chunk) { + serialized = chunk; +}); + +serializer.write({ val: 42 }); + +process.on('exit', function() { + assert.strictEqual(serialized[0], 42); +}); diff --git a/cli/tests/node_compat/test/parallel/test-stream-uint8array.js b/cli/tests/node_compat/test/parallel/test-stream-uint8array.js new file mode 100644 index 00000000000000..71e143016e4d9b --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-stream-uint8array.js @@ -0,0 +1,108 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +'use strict'; +const common = require('../common'); +const assert = require('assert'); + +const { Readable, Writable } = require('stream'); + +const ABC = new Uint8Array([0x41, 0x42, 0x43]); +const DEF = new Uint8Array([0x44, 0x45, 0x46]); +const GHI = new Uint8Array([0x47, 0x48, 0x49]); + +{ + // Simple Writable test. + + let n = 0; + const writable = new Writable({ + write: common.mustCall((chunk, encoding, cb) => { + assert(chunk instanceof Buffer); + if (n++ === 0) { + assert.strictEqual(String(chunk), 'ABC'); + } else { + assert.strictEqual(String(chunk), 'DEF'); + } + + cb(); + }, 2) + }); + + writable.write(ABC); + writable.end(DEF); +} + +{ + // Writable test, pass in Uint8Array in object mode. + + const writable = new Writable({ + objectMode: true, + write: common.mustCall((chunk, encoding, cb) => { + assert(!(chunk instanceof Buffer)); + assert(chunk instanceof Uint8Array); + assert.strictEqual(chunk, ABC); + assert.strictEqual(encoding, 'utf8'); + cb(); + }) + }); + + writable.end(ABC); +} + +{ + // Writable test, multiple writes carried out via writev. + let callback; + + const writable = new Writable({ + write: common.mustCall((chunk, encoding, cb) => { + assert(chunk instanceof Buffer); + assert.strictEqual(encoding, 'buffer'); + assert.strictEqual(String(chunk), 'ABC'); + callback = cb; + }), + writev: common.mustCall((chunks, cb) => { + assert.strictEqual(chunks.length, 2); + assert.strictEqual(chunks[0].encoding, 'buffer'); + assert.strictEqual(chunks[1].encoding, 'buffer'); + assert.strictEqual(chunks[0].chunk + chunks[1].chunk, 'DEFGHI'); + }) + }); + + writable.write(ABC); + writable.write(DEF); + writable.end(GHI); + callback(); +} + +{ + // Simple Readable test. + const readable = new Readable({ + read() {} + }); + + readable.push(DEF); + readable.unshift(ABC); + + const buf = readable.read(); + assert(buf instanceof Buffer); + assert.deepStrictEqual([...buf], [...ABC, ...DEF]); +} + +{ + // Readable test, setEncoding. + const readable = new Readable({ + read() {} + }); + + readable.setEncoding('utf8'); + + readable.push(DEF); + readable.unshift(ABC); + + const out = readable.read(); + assert.strictEqual(out, 'ABCDEF'); +} diff --git a/cli/tests/node_compat/test/parallel/test-stream-unpipe-event.js b/cli/tests/node_compat/test/parallel/test-stream-unpipe-event.js new file mode 100644 index 00000000000000..7a307321c38021 --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-stream-unpipe-event.js @@ -0,0 +1,92 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +'use strict'; +const common = require('../common'); +const assert = require('assert'); +const { Writable, Readable } = require('stream'); +class NullWriteable extends Writable { + _write(chunk, encoding, callback) { + return callback(); + } +} +class QuickEndReadable extends Readable { + _read() { + this.push(null); + } +} +class NeverEndReadable extends Readable { + _read() {} +} + +{ + const dest = new NullWriteable(); + const src = new QuickEndReadable(); + dest.on('pipe', common.mustCall()); + dest.on('unpipe', common.mustCall()); + src.pipe(dest); + setImmediate(() => { + assert.strictEqual(src._readableState.pipes.length, 0); + }); +} + +{ + const dest = new NullWriteable(); + const src = new NeverEndReadable(); + dest.on('pipe', common.mustCall()); + dest.on('unpipe', common.mustNotCall('unpipe should not have been emitted')); + src.pipe(dest); + setImmediate(() => { + assert.strictEqual(src._readableState.pipes.length, 1); + }); +} + +{ + const dest = new NullWriteable(); + const src = new NeverEndReadable(); + dest.on('pipe', common.mustCall()); + dest.on('unpipe', common.mustCall()); + src.pipe(dest); + src.unpipe(dest); + setImmediate(() => { + assert.strictEqual(src._readableState.pipes.length, 0); + }); +} + +{ + const dest = new NullWriteable(); + const src = new QuickEndReadable(); + dest.on('pipe', common.mustCall()); + dest.on('unpipe', common.mustCall()); + src.pipe(dest, { end: false }); + setImmediate(() => { + assert.strictEqual(src._readableState.pipes.length, 0); + }); +} + +{ + const dest = new NullWriteable(); + const src = new NeverEndReadable(); + dest.on('pipe', common.mustCall()); + dest.on('unpipe', common.mustNotCall('unpipe should not have been emitted')); + src.pipe(dest, { end: false }); + setImmediate(() => { + assert.strictEqual(src._readableState.pipes.length, 1); + }); +} + +{ + const dest = new NullWriteable(); + const src = new NeverEndReadable(); + dest.on('pipe', common.mustCall()); + dest.on('unpipe', common.mustCall()); + src.pipe(dest, { end: false }); + src.unpipe(dest); + setImmediate(() => { + assert.strictEqual(src._readableState.pipes.length, 0); + }); +} diff --git a/cli/tests/node_compat/test/parallel/test-stream-unshift-empty-chunk.js b/cli/tests/node_compat/test/parallel/test-stream-unshift-empty-chunk.js new file mode 100644 index 00000000000000..b57d72eced5c44 --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-stream-unshift-empty-chunk.js @@ -0,0 +1,87 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. + +'use strict'; +require('../common'); +const assert = require('assert'); + +// This test verifies that stream.unshift(Buffer.alloc(0)) or +// stream.unshift('') does not set state.reading=false. +const Readable = require('stream').Readable; + +const r = new Readable(); +let nChunks = 10; +const chunk = Buffer.alloc(10, 'x'); + +r._read = function(n) { + setImmediate(() => { + r.push(--nChunks === 0 ? null : chunk); + }); +}; + +let readAll = false; +const seen = []; +r.on('readable', () => { + let chunk; + while ((chunk = r.read()) !== null) { + seen.push(chunk.toString()); + // Simulate only reading a certain amount of the data, + // and then putting the rest of the chunk back into the + // stream, like a parser might do. We just fill it with + // 'y' so that it's easy to see which bits were touched, + // and which were not. + const putBack = Buffer.alloc(readAll ? 0 : 5, 'y'); + readAll = !readAll; + r.unshift(putBack); + } +}); + +const expect = + [ 'xxxxxxxxxx', + 'yyyyy', + 'xxxxxxxxxx', + 'yyyyy', + 'xxxxxxxxxx', + 'yyyyy', + 'xxxxxxxxxx', + 'yyyyy', + 'xxxxxxxxxx', + 'yyyyy', + 'xxxxxxxxxx', + 'yyyyy', + 'xxxxxxxxxx', + 'yyyyy', + 'xxxxxxxxxx', + 'yyyyy', + 'xxxxxxxxxx', + 'yyyyy' ]; + +r.on('end', () => { + assert.deepStrictEqual(seen, expect); + console.log('ok'); +}); diff --git a/cli/tests/node_compat/test/parallel/test-stream-unshift-read-race.js b/cli/tests/node_compat/test/parallel/test-stream-unshift-read-race.js new file mode 100644 index 00000000000000..6550acf3387d0b --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-stream-unshift-read-race.js @@ -0,0 +1,135 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. + +'use strict'; +const common = require('../common'); +const assert = require('assert'); + +// This test verifies that: +// 1. unshift() does not cause colliding _read() calls. +// 2. unshift() after the 'end' event is an error, but after the EOF +// signalling null, it is ok, and just creates a new readable chunk. +// 3. push() after the EOF signaling null is an error. +// 4. _read() is not called after pushing the EOF null chunk. + +const stream = require('stream'); +const hwm = 10; +const r = stream.Readable({ highWaterMark: hwm, autoDestroy: false }); +const chunks = 10; + +const data = Buffer.allocUnsafe(chunks * hwm + Math.ceil(hwm / 2)); +for (let i = 0; i < data.length; i++) { + const c = 'asdf'.charCodeAt(i % 4); + data[i] = c; +} + +let pos = 0; +let pushedNull = false; +r._read = function(n) { + assert(!pushedNull, '_read after null push'); + + // Every third chunk is fast + push(!(chunks % 3)); + + function push(fast) { + assert(!pushedNull, 'push() after null push'); + const c = pos >= data.length ? null : data.slice(pos, pos + n); + pushedNull = c === null; + if (fast) { + pos += n; + r.push(c); + if (c === null) pushError(); + } else { + setTimeout(function() { + pos += n; + r.push(c); + if (c === null) pushError(); + }, 1); + } + } +}; + +function pushError() { + r.unshift(Buffer.allocUnsafe(1)); + w.end(); + + assert.throws(() => { + r.push(Buffer.allocUnsafe(1)); + }, { + code: 'ERR_STREAM_PUSH_AFTER_EOF', + name: 'Error', + message: 'stream.push() after EOF' + }); +} + + +const w = stream.Writable(); +const written = []; +w._write = function(chunk, encoding, cb) { + written.push(chunk.toString()); + cb(); +}; + +r.on('end', common.mustNotCall()); + +r.on('readable', function() { + let chunk; + while (null !== (chunk = r.read(10))) { + w.write(chunk); + if (chunk.length > 4) + r.unshift(Buffer.from('1234')); + } +}); + +w.on('finish', common.mustCall(function() { + // Each chunk should start with 1234, and then be asfdasdfasdf... + // The first got pulled out before the first unshift('1234'), so it's + // lacking that piece. + assert.strictEqual(written[0], 'asdfasdfas'); + let asdf = 'd'; + console.error(`0: ${written[0]}`); + for (let i = 1; i < written.length; i++) { + console.error(`${i.toString(32)}: ${written[i]}`); + assert.strictEqual(written[i].slice(0, 4), '1234'); + for (let j = 4; j < written[i].length; j++) { + const c = written[i].charAt(j); + assert.strictEqual(c, asdf); + switch (asdf) { + case 'a': asdf = 's'; break; + case 's': asdf = 'd'; break; + case 'd': asdf = 'f'; break; + case 'f': asdf = 'a'; break; + } + } + } +})); + +process.on('exit', function() { + assert.strictEqual(written.length, 18); + console.log('ok'); +}); diff --git a/cli/tests/node_compat/test/parallel/test-stream-writable-change-default-encoding.js b/cli/tests/node_compat/test/parallel/test-stream-writable-change-default-encoding.js new file mode 100644 index 00000000000000..47376988171df0 --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-stream-writable-change-default-encoding.js @@ -0,0 +1,85 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. + +'use strict'; +require('../common'); +const assert = require('assert'); + +const stream = require('stream'); + +class MyWritable extends stream.Writable { + constructor(fn, options) { + super(options); + this.fn = fn; + } + + _write(chunk, encoding, callback) { + this.fn(Buffer.isBuffer(chunk), typeof chunk, encoding); + callback(); + } +} + +(function defaultCondingIsUtf8() { + const m = new MyWritable(function(isBuffer, type, enc) { + assert.strictEqual(enc, 'utf8'); + }, { decodeStrings: false }); + m.write('foo'); + m.end(); +}()); + +(function changeDefaultEncodingToAscii() { + const m = new MyWritable(function(isBuffer, type, enc) { + assert.strictEqual(enc, 'ascii'); + }, { decodeStrings: false }); + m.setDefaultEncoding('ascii'); + m.write('bar'); + m.end(); +}()); + +// Change default encoding to invalid value. +assert.throws(() => { + const m = new MyWritable( + (isBuffer, type, enc) => {}, + { decodeStrings: false }); + m.setDefaultEncoding({}); + m.write('bar'); + m.end(); +}, { + name: 'TypeError', + code: 'ERR_UNKNOWN_ENCODING', + message: 'Unknown encoding: {}' +}); + +(function checkVariableCaseEncoding() { + const m = new MyWritable(function(isBuffer, type, enc) { + assert.strictEqual(enc, 'ascii'); + }, { decodeStrings: false }); + m.setDefaultEncoding('AsCii'); + m.write('bar'); + m.end(); +}()); diff --git a/cli/tests/node_compat/test/parallel/test-stream-writable-clear-buffer.js b/cli/tests/node_compat/test/parallel/test-stream-writable-clear-buffer.js new file mode 100644 index 00000000000000..58f2cd8919508c --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-stream-writable-clear-buffer.js @@ -0,0 +1,42 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +'use strict'; + +// This test ensures that the _writeableState.bufferedRequestCount and +// the actual buffered request count are the same. + +const common = require('../common'); +const Stream = require('stream'); +const assert = require('assert'); + +class StreamWritable extends Stream.Writable { + constructor() { + super({ objectMode: true }); + } + + // Refs: https://github.com/nodejs/node/issues/6758 + // We need a timer like on the original issue thread. + // Otherwise the code will never reach our test case. + _write(chunk, encoding, cb) { + setImmediate(cb); + } +} + +const testStream = new StreamWritable(); +testStream.cork(); + +for (let i = 1; i <= 5; i++) { + testStream.write(i, common.mustCall(() => { + assert.strictEqual( + testStream._writableState.bufferedRequestCount, + testStream._writableState.getBuffer().length + ); + })); +} + +testStream.end(); diff --git a/cli/tests/node_compat/test/parallel/test-stream-writable-constructor-set-methods.js b/cli/tests/node_compat/test/parallel/test-stream-writable-constructor-set-methods.js new file mode 100644 index 00000000000000..290548ebb024c5 --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-stream-writable-constructor-set-methods.js @@ -0,0 +1,48 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +'use strict'; +const common = require('../common'); + +const assert = require('assert'); +const { Writable } = require('stream'); + +const bufferBlerg = Buffer.from('blerg'); +const w = new Writable(); + +assert.throws( + () => { + w.end(bufferBlerg); + }, + { + name: 'Error', + code: 'ERR_METHOD_NOT_IMPLEMENTED', + message: 'The _write() method is not implemented' + } +); + +const _write = common.mustCall((chunk, _, next) => { + next(); +}); + +const _writev = common.mustCall((chunks, next) => { + assert.strictEqual(chunks.length, 2); + next(); +}); + +const w2 = new Writable({ write: _write, writev: _writev }); + +assert.strictEqual(w2._write, _write); +assert.strictEqual(w2._writev, _writev); + +w2.write(bufferBlerg); + +w2.cork(); +w2.write(bufferBlerg); +w2.write(bufferBlerg); + +w2.end(); diff --git a/cli/tests/node_compat/test/parallel/test-stream-writable-decoded-encoding.js b/cli/tests/node_compat/test/parallel/test-stream-writable-decoded-encoding.js new file mode 100644 index 00000000000000..00e9b85eaa6469 --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-stream-writable-decoded-encoding.js @@ -0,0 +1,65 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. + +'use strict'; +require('../common'); +const assert = require('assert'); + +const stream = require('stream'); + +class MyWritable extends stream.Writable { + constructor(fn, options) { + super(options); + this.fn = fn; + } + + _write(chunk, encoding, callback) { + this.fn(Buffer.isBuffer(chunk), typeof chunk, encoding); + callback(); + } +} + +{ + const m = new MyWritable(function(isBuffer, type, enc) { + assert(isBuffer); + assert.strictEqual(type, 'object'); + assert.strictEqual(enc, 'buffer'); + }, { decodeStrings: true }); + m.write('some-text', 'utf8'); + m.end(); +} + +{ + const m = new MyWritable(function(isBuffer, type, enc) { + assert(!isBuffer); + assert.strictEqual(type, 'string'); + assert.strictEqual(enc, 'utf8'); + }, { decodeStrings: false }); + m.write('some-text', 'utf8'); + m.end(); +} diff --git a/cli/tests/node_compat/test/parallel/test-stream-writable-destroy.js b/cli/tests/node_compat/test/parallel/test-stream-writable-destroy.js new file mode 100644 index 00000000000000..904031143aea8f --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-stream-writable-destroy.js @@ -0,0 +1,496 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +'use strict'; + +const common = require('../common'); +const { Writable, addAbortSignal } = require('stream'); +const assert = require('assert'); + +{ + const write = new Writable({ + write(chunk, enc, cb) { cb(); } + }); + + write.on('finish', common.mustNotCall()); + write.on('close', common.mustCall()); + + write.destroy(); + assert.strictEqual(write.destroyed, true); +} + +{ + const write = new Writable({ + write(chunk, enc, cb) { + this.destroy(new Error('asd')); + cb(); + } + }); + + write.on('error', common.mustCall()); + write.on('finish', common.mustNotCall()); + write.end('asd'); + assert.strictEqual(write.destroyed, true); +} + +{ + const write = new Writable({ + write(chunk, enc, cb) { cb(); } + }); + + const expected = new Error('kaboom'); + + write.on('finish', common.mustNotCall()); + write.on('close', common.mustCall()); + write.on('error', common.mustCall((err) => { + assert.strictEqual(err, expected); + })); + + write.destroy(expected); + assert.strictEqual(write.destroyed, true); +} + +{ + const write = new Writable({ + write(chunk, enc, cb) { cb(); } + }); + + write._destroy = function(err, cb) { + assert.strictEqual(err, expected); + cb(err); + }; + + const expected = new Error('kaboom'); + + write.on('finish', common.mustNotCall('no finish event')); + write.on('close', common.mustCall()); + write.on('error', common.mustCall((err) => { + assert.strictEqual(err, expected); + })); + + write.destroy(expected); + assert.strictEqual(write.destroyed, true); +} + +{ + const write = new Writable({ + write(chunk, enc, cb) { cb(); }, + destroy: common.mustCall(function(err, cb) { + assert.strictEqual(err, expected); + cb(); + }) + }); + + const expected = new Error('kaboom'); + + write.on('finish', common.mustNotCall('no finish event')); + write.on('close', common.mustCall()); + + // Error is swallowed by the custom _destroy + write.on('error', common.mustNotCall('no error event')); + + write.destroy(expected); + assert.strictEqual(write.destroyed, true); +} + +{ + const write = new Writable({ + write(chunk, enc, cb) { cb(); } + }); + + write._destroy = common.mustCall(function(err, cb) { + assert.strictEqual(err, null); + cb(); + }); + + write.destroy(); + assert.strictEqual(write.destroyed, true); +} + +{ + const write = new Writable({ + write(chunk, enc, cb) { cb(); } + }); + + write._destroy = common.mustCall(function(err, cb) { + assert.strictEqual(err, null); + process.nextTick(() => { + this.end(); + cb(); + }); + }); + + const fail = common.mustNotCall('no finish event'); + + write.on('finish', fail); + write.on('close', common.mustCall()); + + write.destroy(); + + assert.strictEqual(write.destroyed, true); +} + +{ + const write = new Writable({ + write(chunk, enc, cb) { cb(); } + }); + + const expected = new Error('kaboom'); + + write._destroy = common.mustCall(function(err, cb) { + assert.strictEqual(err, null); + cb(expected); + }); + + write.on('close', common.mustCall()); + write.on('finish', common.mustNotCall('no finish event')); + write.on('error', common.mustCall((err) => { + assert.strictEqual(err, expected); + })); + + write.destroy(); + assert.strictEqual(write.destroyed, true); +} + +{ + // double error case + const write = new Writable({ + write(chunk, enc, cb) { cb(); } + }); + + let ticked = false; + write.on('close', common.mustCall(() => { + assert.strictEqual(ticked, true); + })); + write.on('error', common.mustCall((err) => { + assert.strictEqual(ticked, true); + assert.strictEqual(err.message, 'kaboom 1'); + assert.strictEqual(write._writableState.errorEmitted, true); + })); + + const expected = new Error('kaboom 1'); + write.destroy(expected); + write.destroy(new Error('kaboom 2')); + assert.strictEqual(write._writableState.errored, expected); + assert.strictEqual(write._writableState.errorEmitted, false); + assert.strictEqual(write.destroyed, true); + ticked = true; +} + +{ + const writable = new Writable({ + destroy: common.mustCall(function(err, cb) { + process.nextTick(cb, new Error('kaboom 1')); + }), + write(chunk, enc, cb) { + cb(); + } + }); + + let ticked = false; + writable.on('close', common.mustCall(() => { + writable.on('error', common.mustNotCall()); + writable.destroy(new Error('hello')); + assert.strictEqual(ticked, true); + assert.strictEqual(writable._writableState.errorEmitted, true); + })); + writable.on('error', common.mustCall((err) => { + assert.strictEqual(ticked, true); + assert.strictEqual(err.message, 'kaboom 1'); + assert.strictEqual(writable._writableState.errorEmitted, true); + })); + + writable.destroy(); + assert.strictEqual(writable.destroyed, true); + assert.strictEqual(writable._writableState.errored, null); + assert.strictEqual(writable._writableState.errorEmitted, false); + + // Test case where `writable.destroy()` is called again with an error before + // the `_destroy()` callback is called. + writable.destroy(new Error('kaboom 2')); + assert.strictEqual(writable._writableState.errorEmitted, false); + assert.strictEqual(writable._writableState.errored, null); + + ticked = true; +} + +{ + const write = new Writable({ + write(chunk, enc, cb) { cb(); } + }); + + write.destroyed = true; + assert.strictEqual(write.destroyed, true); + + // The internal destroy() mechanism should not be triggered + write.on('close', common.mustNotCall()); + write.destroy(); +} + +{ + function MyWritable() { + assert.strictEqual(this.destroyed, false); + this.destroyed = false; + Writable.call(this); + } + + Object.setPrototypeOf(MyWritable.prototype, Writable.prototype); + Object.setPrototypeOf(MyWritable, Writable); + + new MyWritable(); +} + +{ + // Destroy and destroy callback + const write = new Writable({ + write(chunk, enc, cb) { cb(); } + }); + + write.destroy(); + + const expected = new Error('kaboom'); + + write.destroy(expected, common.mustCall((err) => { + assert.strictEqual(err, undefined); + })); +} + +{ + // Checks that `._undestroy()` restores the state so that `final` will be + // called again. + const write = new Writable({ + write: common.mustNotCall(), + final: common.mustCall((cb) => cb(), 2), + autoDestroy: true + }); + + write.end(); + write.once('close', common.mustCall(() => { + write._undestroy(); + write.end(); + })); +} + +{ + const write = new Writable(); + + write.destroy(); + write.on('error', common.mustNotCall()); + write.write('asd', common.expectsError({ + name: 'Error', + code: 'ERR_STREAM_DESTROYED', + message: 'Cannot call write after a stream was destroyed' + })); +} + +{ + const write = new Writable({ + write(chunk, enc, cb) { cb(); } + }); + + write.on('error', common.mustNotCall()); + + write.cork(); + write.write('asd', common.mustCall()); + write.uncork(); + + write.cork(); + write.write('asd', common.expectsError({ + name: 'Error', + code: 'ERR_STREAM_DESTROYED', + message: 'Cannot call write after a stream was destroyed' + })); + write.destroy(); + write.write('asd', common.expectsError({ + name: 'Error', + code: 'ERR_STREAM_DESTROYED', + message: 'Cannot call write after a stream was destroyed' + })); + write.uncork(); +} + +{ + // Call end(cb) after error & destroy + + const write = new Writable({ + write(chunk, enc, cb) { cb(new Error('asd')); } + }); + write.on('error', common.mustCall(() => { + write.destroy(); + let ticked = false; + write.end(common.mustCall((err) => { + assert.strictEqual(ticked, true); + assert.strictEqual(err.code, 'ERR_STREAM_DESTROYED'); + })); + ticked = true; + })); + write.write('asd'); +} + +{ + // Call end(cb) after finish & destroy + + const write = new Writable({ + write(chunk, enc, cb) { cb(); } + }); + write.on('finish', common.mustCall(() => { + write.destroy(); + let ticked = false; + write.end(common.mustCall((err) => { + assert.strictEqual(ticked, true); + assert.strictEqual(err.code, 'ERR_STREAM_ALREADY_FINISHED'); + })); + ticked = true; + })); + write.end(); +} + +{ + // Call end(cb) after error & destroy and don't trigger + // unhandled exception. + + const write = new Writable({ + write(chunk, enc, cb) { process.nextTick(cb); } + }); + const _err = new Error('asd'); + write.once('error', common.mustCall((err) => { + assert.strictEqual(err.message, 'asd'); + })); + write.end('asd', common.mustCall((err) => { + assert.strictEqual(err, _err); + })); + write.destroy(_err); +} + +{ + // Call buffered write callback with error + + const _err = new Error('asd'); + const write = new Writable({ + write(chunk, enc, cb) { + process.nextTick(cb, _err); + }, + autoDestroy: false + }); + write.cork(); + write.write('asd', common.mustCall((err) => { + assert.strictEqual(err, _err); + })); + write.write('asd', common.mustCall((err) => { + assert.strictEqual(err, _err); + })); + write.on('error', common.mustCall((err) => { + assert.strictEqual(err, _err); + })); + write.uncork(); +} + +{ + // Ensure callback order. + + let state = 0; + const write = new Writable({ + write(chunk, enc, cb) { + // `setImmediate()` is used on purpose to ensure the callback is called + // after `process.nextTick()` callbacks. + setImmediate(cb); + } + }); + write.write('asd', common.mustCall(() => { + assert.strictEqual(state++, 0); + })); + write.write('asd', common.mustCall((err) => { + assert.strictEqual(err.code, 'ERR_STREAM_DESTROYED'); + assert.strictEqual(state++, 1); + })); + write.destroy(); +} + +{ + const write = new Writable({ + autoDestroy: false, + write(chunk, enc, cb) { + cb(); + cb(); + } + }); + + write.on('error', common.mustCall(() => { + assert(write._writableState.errored); + })); + write.write('asd'); +} + +{ + const ac = new AbortController(); + const write = addAbortSignal(ac.signal, new Writable({ + write(chunk, enc, cb) { cb(); } + })); + + write.on('error', common.mustCall((e) => { + assert.strictEqual(e.name, 'AbortError'); + assert.strictEqual(write.destroyed, true); + })); + write.write('asd'); + ac.abort(); +} + +{ + const ac = new AbortController(); + const write = new Writable({ + signal: ac.signal, + write(chunk, enc, cb) { cb(); } + }); + + write.on('error', common.mustCall((e) => { + assert.strictEqual(e.name, 'AbortError'); + assert.strictEqual(write.destroyed, true); + })); + write.write('asd'); + ac.abort(); +} + +{ + const signal = AbortSignal.abort(); + + const write = new Writable({ + signal, + write(chunk, enc, cb) { cb(); } + }); + + write.on('error', common.mustCall((e) => { + assert.strictEqual(e.name, 'AbortError'); + assert.strictEqual(write.destroyed, true); + })); +} + +{ + // Destroy twice + const write = new Writable({ + write(chunk, enc, cb) { cb(); } + }); + + write.end(common.mustCall()); + write.destroy(); + write.destroy(); +} + +{ + // https://github.com/nodejs/node/issues/39356 + const s = new Writable({ + final() {} + }); + const _err = new Error('oh no'); + // Remove `callback` and it works + s.end(common.mustCall((err) => { + assert.strictEqual(err, _err); + })); + s.on('error', common.mustCall((err) => { + assert.strictEqual(err, _err); + })); + s.destroy(_err); +} diff --git a/cli/tests/node_compat/test/parallel/test-stream-writable-end-cb-error.js b/cli/tests/node_compat/test/parallel/test-stream-writable-end-cb-error.js new file mode 100644 index 00000000000000..b144d0ddfe4b21 --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-stream-writable-end-cb-error.js @@ -0,0 +1,85 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +'use strict'; + +const common = require('../common'); +const assert = require('assert'); +const stream = require('stream'); + +{ + // Invoke end callback on failure. + const writable = new stream.Writable(); + + const _err = new Error('kaboom'); + writable._write = (chunk, encoding, cb) => { + process.nextTick(cb, _err); + }; + + writable.on('error', common.mustCall((err) => { + assert.strictEqual(err, _err); + })); + writable.write('asd'); + writable.end(common.mustCall((err) => { + assert.strictEqual(err, _err); + })); + writable.end(common.mustCall((err) => { + assert.strictEqual(err, _err); + })); +} + +{ + // Don't invoke end callback twice + const writable = new stream.Writable(); + + writable._write = (chunk, encoding, cb) => { + process.nextTick(cb); + }; + + let called = false; + writable.end('asd', common.mustCall((err) => { + called = true; + assert.strictEqual(err, undefined); + })); + + writable.on('error', common.mustCall((err) => { + assert.strictEqual(err.message, 'kaboom'); + })); + writable.on('finish', common.mustCall(() => { + assert.strictEqual(called, true); + writable.emit('error', new Error('kaboom')); + })); +} + +{ + const w = new stream.Writable({ + write(chunk, encoding, callback) { + setImmediate(callback); + }, + finish(callback) { + setImmediate(callback); + } + }); + w.end('testing ended state', common.mustCall((err) => { + assert.strictEqual(err.code, 'ERR_STREAM_WRITE_AFTER_END'); + })); + assert.strictEqual(w.destroyed, false); + assert.strictEqual(w.writableEnded, true); + w.end(common.mustCall((err) => { + assert.strictEqual(err.code, 'ERR_STREAM_WRITE_AFTER_END'); + })); + assert.strictEqual(w.destroyed, false); + assert.strictEqual(w.writableEnded, true); + w.end('end', common.mustCall((err) => { + assert.strictEqual(err.code, 'ERR_STREAM_WRITE_AFTER_END'); + })); + assert.strictEqual(w.destroyed, true); + w.on('error', common.mustCall((err) => { + assert.strictEqual(err.code, 'ERR_STREAM_WRITE_AFTER_END'); + })); + w.on('finish', common.mustNotCall()); +} diff --git a/cli/tests/node_compat/test/parallel/test-stream-writable-end-multiple.js b/cli/tests/node_compat/test/parallel/test-stream-writable-end-multiple.js new file mode 100644 index 00000000000000..22dfc5140870ce --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-stream-writable-end-multiple.js @@ -0,0 +1,29 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +'use strict'; + +const common = require('../common'); + +const assert = require('assert'); +const stream = require('stream'); + +const writable = new stream.Writable(); +writable._write = (chunk, encoding, cb) => { + setTimeout(() => cb(), 10); +}; + +writable.end('testing ended state', common.mustCall()); +writable.end(common.mustCall()); +writable.on('finish', common.mustCall(() => { + let ticked = false; + writable.end(common.mustCall((err) => { + assert.strictEqual(ticked, true); + assert.strictEqual(err.code, 'ERR_STREAM_ALREADY_FINISHED'); + })); + ticked = true; +})); diff --git a/cli/tests/node_compat/test/parallel/test-stream-writable-ended-state.js b/cli/tests/node_compat/test/parallel/test-stream-writable-ended-state.js new file mode 100644 index 00000000000000..4d3719dcd3c3cf --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-stream-writable-ended-state.js @@ -0,0 +1,39 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +'use strict'; + +const common = require('../common'); + +const assert = require('assert'); +const stream = require('stream'); + +const writable = new stream.Writable(); + +writable._write = (chunk, encoding, cb) => { + assert.strictEqual(writable._writableState.ended, false); + assert.strictEqual(writable._writableState.writable, undefined); + assert.strictEqual(writable.writableEnded, false); + cb(); +}; + +assert.strictEqual(writable._writableState.ended, false); +assert.strictEqual(writable._writableState.writable, undefined); +assert.strictEqual(writable.writable, true); +assert.strictEqual(writable.writableEnded, false); + +writable.end('testing ended state', common.mustCall(() => { + assert.strictEqual(writable._writableState.ended, true); + assert.strictEqual(writable._writableState.writable, undefined); + assert.strictEqual(writable.writable, false); + assert.strictEqual(writable.writableEnded, true); +})); + +assert.strictEqual(writable._writableState.ended, true); +assert.strictEqual(writable._writableState.writable, undefined); +assert.strictEqual(writable.writable, false); +assert.strictEqual(writable.writableEnded, true); diff --git a/cli/tests/node_compat/test/parallel/test-stream-writable-finish-destroyed.js b/cli/tests/node_compat/test/parallel/test-stream-writable-finish-destroyed.js new file mode 100644 index 00000000000000..0f32291713ec48 --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-stream-writable-finish-destroyed.js @@ -0,0 +1,50 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +'use strict'; + +const common = require('../common'); +const { Writable } = require('stream'); + +{ + const w = new Writable({ + write: common.mustCall((chunk, encoding, cb) => { + w.on('close', common.mustCall(() => { + cb(); + })); + }) + }); + + w.on('finish', common.mustNotCall()); + w.end('asd'); + w.destroy(); +} + +{ + const w = new Writable({ + write: common.mustCall((chunk, encoding, cb) => { + w.on('close', common.mustCall(() => { + cb(); + w.end(); + })); + }) + }); + + w.on('finish', common.mustNotCall()); + w.write('asd'); + w.destroy(); +} + +{ + const w = new Writable({ + write() { + } + }); + w.on('finish', common.mustNotCall()); + w.end(); + w.destroy(); +} diff --git a/cli/tests/node_compat/test/parallel/test-stream-writable-finished-state.js b/cli/tests/node_compat/test/parallel/test-stream-writable-finished-state.js new file mode 100644 index 00000000000000..09f388bce0be17 --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-stream-writable-finished-state.js @@ -0,0 +1,29 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +'use strict'; + +const common = require('../common'); + +const assert = require('assert'); +const stream = require('stream'); + +const writable = new stream.Writable(); + +writable._write = (chunk, encoding, cb) => { + // The state finished should start in false. + assert.strictEqual(writable._writableState.finished, false); + cb(); +}; + +writable.on('finish', common.mustCall(() => { + assert.strictEqual(writable._writableState.finished, true); +})); + +writable.end('testing finished state', common.mustCall(() => { + assert.strictEqual(writable._writableState.finished, true); +})); diff --git a/cli/tests/node_compat/test/parallel/test-stream-writable-finished.js b/cli/tests/node_compat/test/parallel/test-stream-writable-finished.js new file mode 100644 index 00000000000000..5df7f47700a64d --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-stream-writable-finished.js @@ -0,0 +1,106 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +'use strict'; + +const common = require('../common'); +const { Writable } = require('stream'); +const assert = require('assert'); + +// basic +{ + // Find it on Writable.prototype + assert(Object.hasOwn(Writable.prototype, 'writableFinished')); +} + +// event +{ + const writable = new Writable(); + + writable._write = (chunk, encoding, cb) => { + // The state finished should start in false. + assert.strictEqual(writable.writableFinished, false); + cb(); + }; + + writable.on('finish', common.mustCall(() => { + assert.strictEqual(writable.writableFinished, true); + })); + + writable.end('testing finished state', common.mustCall(() => { + assert.strictEqual(writable.writableFinished, true); + })); +} + +{ + // Emit finish asynchronously. + + const w = new Writable({ + write(chunk, encoding, cb) { + cb(); + } + }); + + w.end(); + w.on('finish', common.mustCall()); +} + +{ + // Emit prefinish synchronously. + + const w = new Writable({ + write(chunk, encoding, cb) { + cb(); + } + }); + + let sync = true; + w.on('prefinish', common.mustCall(() => { + assert.strictEqual(sync, true); + })); + w.end(); + sync = false; +} + +{ + // Emit prefinish synchronously w/ final. + + const w = new Writable({ + write(chunk, encoding, cb) { + cb(); + }, + final(cb) { + cb(); + } + }); + + let sync = true; + w.on('prefinish', common.mustCall(() => { + assert.strictEqual(sync, true); + })); + w.end(); + sync = false; +} + + +{ + // Call _final synchronously. + + let sync = true; + const w = new Writable({ + write(chunk, encoding, cb) { + cb(); + }, + final: common.mustCall((cb) => { + assert.strictEqual(sync, true); + cb(); + }) + }); + + w.end(); + sync = false; +} diff --git a/cli/tests/node_compat/test/parallel/test-stream-writable-invalid-chunk.js b/cli/tests/node_compat/test/parallel/test-stream-writable-invalid-chunk.js new file mode 100644 index 00000000000000..acdc47bf12566e --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-stream-writable-invalid-chunk.js @@ -0,0 +1,43 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +'use strict'; + +const common = require('../common'); +const stream = require('stream'); +const assert = require('assert'); + +function testWriteType(val, objectMode, code) { + const writable = new stream.Writable({ + objectMode, + write: () => {} + }); + writable.on('error', common.mustNotCall()); + if (code) { + assert.throws(() => { + writable.write(val); + }, { code }); + } else { + writable.write(val); + } +} + +testWriteType([], false, 'ERR_INVALID_ARG_TYPE'); +testWriteType({}, false, 'ERR_INVALID_ARG_TYPE'); +testWriteType(0, false, 'ERR_INVALID_ARG_TYPE'); +testWriteType(true, false, 'ERR_INVALID_ARG_TYPE'); +testWriteType(0.0, false, 'ERR_INVALID_ARG_TYPE'); +testWriteType(undefined, false, 'ERR_INVALID_ARG_TYPE'); +testWriteType(null, false, 'ERR_STREAM_NULL_VALUES'); + +testWriteType([], true); +testWriteType({}, true); +testWriteType(0, true); +testWriteType(true, true); +testWriteType(0.0, true); +testWriteType(undefined, true); +testWriteType(null, true, 'ERR_STREAM_NULL_VALUES'); diff --git a/cli/tests/node_compat/test/parallel/test-stream-writable-needdrain-state.js b/cli/tests/node_compat/test/parallel/test-stream-writable-needdrain-state.js new file mode 100644 index 00000000000000..ca43cef234c8fa --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-stream-writable-needdrain-state.js @@ -0,0 +1,32 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +'use strict'; + +const common = require('../common'); +const stream = require('stream'); +const assert = require('assert'); + +const transform = new stream.Transform({ + transform: _transform, + highWaterMark: 1 +}); + +function _transform(chunk, encoding, cb) { + process.nextTick(() => { + assert.strictEqual(transform._writableState.needDrain, true); + cb(); + }); +} + +assert.strictEqual(transform._writableState.needDrain, false); + +transform.write('asdasd', common.mustCall(() => { + assert.strictEqual(transform._writableState.needDrain, false); +})); + +assert.strictEqual(transform._writableState.needDrain, true); diff --git a/cli/tests/node_compat/test/parallel/test-stream-writable-null.js b/cli/tests/node_compat/test/parallel/test-stream-writable-null.js new file mode 100644 index 00000000000000..c8f96067401577 --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-stream-writable-null.js @@ -0,0 +1,54 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +'use strict'; +const common = require('../common'); +const assert = require('assert'); + +const stream = require('stream'); + +class MyWritable extends stream.Writable { + constructor(options) { + super({ autoDestroy: false, ...options }); + } + _write(chunk, encoding, callback) { + assert.notStrictEqual(chunk, null); + callback(); + } +} + +{ + const m = new MyWritable({ objectMode: true }); + m.on('error', common.mustNotCall()); + assert.throws(() => { + m.write(null); + }, { + code: 'ERR_STREAM_NULL_VALUES' + }); +} + +{ + const m = new MyWritable(); + m.on('error', common.mustNotCall()); + assert.throws(() => { + m.write(false); + }, { + code: 'ERR_INVALID_ARG_TYPE' + }); +} + +{ // Should not throw. + const m = new MyWritable({ objectMode: true }); + m.write(false, assert.ifError); +} + +{ // Should not throw. + const m = new MyWritable({ objectMode: true }).on('error', (e) => { + assert.ifError(e || new Error('should not get here')); + }); + m.write(false, assert.ifError); +} diff --git a/cli/tests/node_compat/test/parallel/test-stream-writable-properties.js b/cli/tests/node_compat/test/parallel/test-stream-writable-properties.js new file mode 100644 index 00000000000000..99c0dc0f4ef006 --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-stream-writable-properties.js @@ -0,0 +1,29 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +'use strict'; +require('../common'); +const assert = require('assert'); + +const { Writable } = require('stream'); + +{ + const w = new Writable(); + assert.strictEqual(w.writableCorked, 0); + w.uncork(); + assert.strictEqual(w.writableCorked, 0); + w.cork(); + assert.strictEqual(w.writableCorked, 1); + w.cork(); + assert.strictEqual(w.writableCorked, 2); + w.uncork(); + assert.strictEqual(w.writableCorked, 1); + w.uncork(); + assert.strictEqual(w.writableCorked, 0); + w.uncork(); + assert.strictEqual(w.writableCorked, 0); +} diff --git a/cli/tests/node_compat/test/parallel/test-stream-writable-writable.js b/cli/tests/node_compat/test/parallel/test-stream-writable-writable.js new file mode 100644 index 00000000000000..85f5de25d10303 --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-stream-writable-writable.js @@ -0,0 +1,55 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +'use strict'; +const common = require('../common'); +const assert = require('assert'); + +const { Writable } = require('stream'); + +{ + const w = new Writable({ + write() {} + }); + assert.strictEqual(w.writable, true); + w.destroy(); + assert.strictEqual(w.writable, false); +} + +{ + const w = new Writable({ + write: common.mustCall((chunk, encoding, callback) => { + callback(new Error()); + }) + }); + assert.strictEqual(w.writable, true); + w.write('asd'); + assert.strictEqual(w.writable, false); + w.on('error', common.mustCall()); +} + +{ + const w = new Writable({ + write: common.mustCall((chunk, encoding, callback) => { + process.nextTick(() => { + callback(new Error()); + assert.strictEqual(w.writable, false); + }); + }) + }); + w.write('asd'); + w.on('error', common.mustCall()); +} + +{ + const w = new Writable({ + write: common.mustNotCall() + }); + assert.strictEqual(w.writable, true); + w.end(); + assert.strictEqual(w.writable, false); +} diff --git a/cli/tests/node_compat/test/parallel/test-stream-writable-write-cb-error.js b/cli/tests/node_compat/test/parallel/test-stream-writable-write-cb-error.js new file mode 100644 index 00000000000000..675236b0b4404b --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-stream-writable-write-cb-error.js @@ -0,0 +1,65 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +'use strict'; +const common = require('../common'); +const { Writable } = require('stream'); +const assert = require('assert'); + +// Ensure callback is always invoked before +// error is emitted. Regardless if error was +// sync or async. + +{ + let callbackCalled = false; + // Sync Error + const writable = new Writable({ + write: common.mustCall((buf, enc, cb) => { + cb(new Error()); + }) + }); + writable.on('error', common.mustCall(() => { + assert.strictEqual(callbackCalled, true); + })); + writable.write('hi', common.mustCall(() => { + callbackCalled = true; + })); +} + +{ + let callbackCalled = false; + // Async Error + const writable = new Writable({ + write: common.mustCall((buf, enc, cb) => { + process.nextTick(cb, new Error()); + }) + }); + writable.on('error', common.mustCall(() => { + assert.strictEqual(callbackCalled, true); + })); + writable.write('hi', common.mustCall(() => { + callbackCalled = true; + })); +} + +{ + // Sync Error + const writable = new Writable({ + write: common.mustCall((buf, enc, cb) => { + cb(new Error()); + }) + }); + + writable.on('error', common.mustCall()); + + let cnt = 0; + // Ensure we don't live lock on sync error + while (writable.write('a')) + cnt++; + + assert.strictEqual(cnt, 0); +} diff --git a/cli/tests/node_compat/test/parallel/test-stream-writable-write-cb-twice.js b/cli/tests/node_compat/test/parallel/test-stream-writable-write-cb-twice.js new file mode 100644 index 00000000000000..fd59f43a8ecfc1 --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-stream-writable-write-cb-twice.js @@ -0,0 +1,59 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +'use strict'; +const common = require('../common'); +const { Writable } = require('stream'); + +{ + // Sync + Sync + const writable = new Writable({ + write: common.mustCall((buf, enc, cb) => { + cb(); + cb(); + }) + }); + writable.write('hi'); + writable.on('error', common.expectsError({ + code: 'ERR_MULTIPLE_CALLBACK', + name: 'Error' + })); +} + +{ + // Sync + Async + const writable = new Writable({ + write: common.mustCall((buf, enc, cb) => { + cb(); + process.nextTick(() => { + cb(); + }); + }) + }); + writable.write('hi'); + writable.on('error', common.expectsError({ + code: 'ERR_MULTIPLE_CALLBACK', + name: 'Error' + })); +} + +{ + // Async + Async + const writable = new Writable({ + write: common.mustCall((buf, enc, cb) => { + process.nextTick(cb); + process.nextTick(() => { + cb(); + }); + }) + }); + writable.write('hi'); + writable.on('error', common.expectsError({ + code: 'ERR_MULTIPLE_CALLBACK', + name: 'Error' + })); +} diff --git a/cli/tests/node_compat/test/parallel/test-stream-writable-write-error.js b/cli/tests/node_compat/test/parallel/test-stream-writable-write-error.js new file mode 100644 index 00000000000000..b43b4bf1f370bc --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-stream-writable-write-error.js @@ -0,0 +1,82 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +'use strict'; +const common = require('../common'); +const assert = require('assert'); + +const { Writable } = require('stream'); + +function expectError(w, args, code, sync) { + if (sync) { + if (code) { + assert.throws(() => w.write(...args), { code }); + } else { + w.write(...args); + } + } else { + let errorCalled = false; + let ticked = false; + w.write(...args, common.mustCall((err) => { + assert.strictEqual(ticked, true); + assert.strictEqual(errorCalled, false); + assert.strictEqual(err.code, code); + })); + ticked = true; + w.on('error', common.mustCall((err) => { + errorCalled = true; + assert.strictEqual(err.code, code); + })); + } +} + +function test(autoDestroy) { + { + const w = new Writable({ + autoDestroy, + _write() {} + }); + w.end(); + expectError(w, ['asd'], 'ERR_STREAM_WRITE_AFTER_END'); + } + + { + const w = new Writable({ + autoDestroy, + _write() {} + }); + w.destroy(); + } + + { + const w = new Writable({ + autoDestroy, + _write() {} + }); + expectError(w, [null], 'ERR_STREAM_NULL_VALUES', true); + } + + { + const w = new Writable({ + autoDestroy, + _write() {} + }); + expectError(w, [{}], 'ERR_INVALID_ARG_TYPE', true); + } + + { + const w = new Writable({ + decodeStrings: false, + autoDestroy, + _write() {} + }); + expectError(w, ['asd', 'noencoding'], 'ERR_UNKNOWN_ENCODING', true); + } +} + +test(false); +test(true); diff --git a/cli/tests/node_compat/test/parallel/test-stream-writable-write-writev-finish.js b/cli/tests/node_compat/test/parallel/test-stream-writable-write-writev-finish.js new file mode 100644 index 00000000000000..c12877fd49a1c1 --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-stream-writable-write-writev-finish.js @@ -0,0 +1,159 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +'use strict'; + +const common = require('../common'); +const assert = require('assert'); +const stream = require('stream'); + +// Ensure consistency between the finish event when using cork() +// and writev and when not using them + +{ + const writable = new stream.Writable(); + + writable._write = (chunks, encoding, cb) => { + cb(new Error('write test error')); + }; + + writable.on('finish', common.mustNotCall()); + writable.on('prefinish', common.mustNotCall()); + writable.on('error', common.mustCall((er) => { + assert.strictEqual(er.message, 'write test error'); + })); + + writable.end('test'); +} + +{ + const writable = new stream.Writable(); + + writable._write = (chunks, encoding, cb) => { + setImmediate(cb, new Error('write test error')); + }; + + writable.on('finish', common.mustNotCall()); + writable.on('prefinish', common.mustNotCall()); + writable.on('error', common.mustCall((er) => { + assert.strictEqual(er.message, 'write test error'); + })); + + writable.end('test'); +} + +{ + const writable = new stream.Writable(); + + writable._write = (chunks, encoding, cb) => { + cb(new Error('write test error')); + }; + + writable._writev = (chunks, cb) => { + cb(new Error('writev test error')); + }; + + writable.on('finish', common.mustNotCall()); + writable.on('prefinish', common.mustNotCall()); + writable.on('error', common.mustCall((er) => { + assert.strictEqual(er.message, 'writev test error'); + })); + + writable.cork(); + writable.write('test'); + + setImmediate(function() { + writable.end('test'); + }); +} + +{ + const writable = new stream.Writable(); + + writable._write = (chunks, encoding, cb) => { + setImmediate(cb, new Error('write test error')); + }; + + writable._writev = (chunks, cb) => { + setImmediate(cb, new Error('writev test error')); + }; + + writable.on('finish', common.mustNotCall()); + writable.on('prefinish', common.mustNotCall()); + writable.on('error', common.mustCall((er) => { + assert.strictEqual(er.message, 'writev test error'); + })); + + writable.cork(); + writable.write('test'); + + setImmediate(function() { + writable.end('test'); + }); +} + +// Regression test for +// https://github.com/nodejs/node/issues/13812 + +{ + const rs = new stream.Readable(); + rs.push('ok'); + rs.push(null); + rs._read = () => {}; + + const ws = new stream.Writable(); + + ws.on('finish', common.mustNotCall()); + ws.on('error', common.mustCall()); + + ws._write = (chunk, encoding, done) => { + setImmediate(done, new Error()); + }; + rs.pipe(ws); +} + +{ + const rs = new stream.Readable(); + rs.push('ok'); + rs.push(null); + rs._read = () => {}; + + const ws = new stream.Writable(); + + ws.on('finish', common.mustNotCall()); + ws.on('error', common.mustCall()); + + ws._write = (chunk, encoding, done) => { + done(new Error()); + }; + rs.pipe(ws); +} + +{ + const w = new stream.Writable(); + w._write = (chunk, encoding, cb) => { + process.nextTick(cb); + }; + w.on('error', common.mustCall()); + w.on('finish', common.mustNotCall()); + w.on('prefinish', () => { + w.write("shouldn't write in prefinish listener"); + }); + w.end(); +} + +{ + const w = new stream.Writable(); + w._write = (chunk, encoding, cb) => { + process.nextTick(cb); + }; + w.on('error', common.mustCall()); + w.on('finish', () => { + w.write("shouldn't write in finish listener"); + }); + w.end(); +} diff --git a/cli/tests/node_compat/test/parallel/test-stream-writableState-ending.js b/cli/tests/node_compat/test/parallel/test-stream-writableState-ending.js new file mode 100644 index 00000000000000..1c45d09ecdaa3d --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-stream-writableState-ending.js @@ -0,0 +1,44 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +'use strict'; + +require('../common'); + +const assert = require('assert'); +const stream = require('stream'); + +const writable = new stream.Writable(); + +function testStates(ending, finished, ended) { + assert.strictEqual(writable._writableState.ending, ending); + assert.strictEqual(writable._writableState.finished, finished); + assert.strictEqual(writable._writableState.ended, ended); +} + +writable._write = (chunk, encoding, cb) => { + // Ending, finished, ended start in false. + testStates(false, false, false); + cb(); +}; + +writable.on('finish', () => { + // Ending, finished, ended = true. + testStates(true, true, true); +}); + +const result = writable.end('testing function end()', () => { + // Ending, finished, ended = true. + testStates(true, true, true); +}); + +// End returns the writable instance +assert.strictEqual(result, writable); + +// Ending, ended = true. +// finished = false. +testStates(true, false, true); diff --git a/cli/tests/node_compat/test/parallel/test-stream-writableState-uncorked-bufferedRequestCount.js b/cli/tests/node_compat/test/parallel/test-stream-writableState-uncorked-bufferedRequestCount.js new file mode 100644 index 00000000000000..b4775a1f19b3f9 --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-stream-writableState-uncorked-bufferedRequestCount.js @@ -0,0 +1,64 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +'use strict'; + +const common = require('../common'); +const assert = require('assert'); +const stream = require('stream'); + +const writable = new stream.Writable(); + +writable._writev = common.mustCall((chunks, cb) => { + assert.strictEqual(chunks.length, 2); + cb(); +}, 1); + +writable._write = common.mustCall((chunk, encoding, cb) => { + cb(); +}, 1); + +// first cork +writable.cork(); +assert.strictEqual(writable._writableState.corked, 1); +assert.strictEqual(writable._writableState.bufferedRequestCount, 0); + +// cork again +writable.cork(); +assert.strictEqual(writable._writableState.corked, 2); + +// The first chunk is buffered +writable.write('first chunk'); +assert.strictEqual(writable._writableState.bufferedRequestCount, 1); + +// First uncork does nothing +writable.uncork(); +assert.strictEqual(writable._writableState.corked, 1); +assert.strictEqual(writable._writableState.bufferedRequestCount, 1); + +process.nextTick(uncork); + +// The second chunk is buffered, because we uncork at the end of tick +writable.write('second chunk'); +assert.strictEqual(writable._writableState.corked, 1); +assert.strictEqual(writable._writableState.bufferedRequestCount, 2); + +function uncork() { + // Second uncork flushes the buffer + writable.uncork(); + assert.strictEqual(writable._writableState.corked, 0); + assert.strictEqual(writable._writableState.bufferedRequestCount, 0); + + // Verify that end() uncorks correctly + writable.cork(); + writable.write('third chunk'); + writable.end(); + + // End causes an uncork() as well + assert.strictEqual(writable._writableState.corked, 0); + assert.strictEqual(writable._writableState.bufferedRequestCount, 0); +} diff --git a/cli/tests/node_compat/test/parallel/test-stream-write-destroy.js b/cli/tests/node_compat/test/parallel/test-stream-write-destroy.js new file mode 100644 index 00000000000000..3df93095bd8bf1 --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-stream-write-destroy.js @@ -0,0 +1,69 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +'use strict'; +require('../common'); +const assert = require('assert'); +const { Writable } = require('stream'); + +// Test interaction between calling .destroy() on a writable and pending +// writes. + +for (const withPendingData of [ false, true ]) { + for (const useEnd of [ false, true ]) { + const callbacks = []; + + const w = new Writable({ + write(data, enc, cb) { + callbacks.push(cb); + }, + // Effectively disable the HWM to observe 'drain' events more easily. + highWaterMark: 1 + }); + + let chunksWritten = 0; + let drains = 0; + w.on('drain', () => drains++); + + function onWrite(err) { + if (err) { + assert.strictEqual(w.destroyed, true); + assert.strictEqual(err.code, 'ERR_STREAM_DESTROYED'); + } else { + chunksWritten++; + } + } + + w.write('abc', onWrite); + assert.strictEqual(chunksWritten, 0); + assert.strictEqual(drains, 0); + callbacks.shift()(); + assert.strictEqual(chunksWritten, 1); + assert.strictEqual(drains, 1); + + if (withPendingData) { + // Test 2 cases: There either is or is not data still in the write queue. + // (The second write will never actually get executed either way.) + w.write('def', onWrite); + } + if (useEnd) { + // Again, test 2 cases: Either we indicate that we want to end the + // writable or not. + w.end('ghi', onWrite); + } else { + w.write('ghi', onWrite); + } + + assert.strictEqual(chunksWritten, 1); + w.destroy(); + assert.strictEqual(chunksWritten, 1); + callbacks.shift()(); + assert.strictEqual(chunksWritten, useEnd && !withPendingData ? 1 : 2); + assert.strictEqual(callbacks.length, 0); + assert.strictEqual(drains, 1); + } +} diff --git a/cli/tests/node_compat/test/parallel/test-stream-write-drain.js b/cli/tests/node_compat/test/parallel/test-stream-write-drain.js new file mode 100644 index 00000000000000..a04bf99345e9b9 --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-stream-write-drain.js @@ -0,0 +1,23 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +'use strict'; +const common = require('../common'); +const { Writable } = require('stream'); + +// Don't emit 'drain' if ended + +const w = new Writable({ + write(data, enc, cb) { + process.nextTick(cb); + }, + highWaterMark: 1 +}); + +w.on('drain', common.mustNotCall()); +w.write('asd'); +w.end(); diff --git a/cli/tests/node_compat/test/parallel/test-stream-write-final.js b/cli/tests/node_compat/test/parallel/test-stream-write-final.js new file mode 100644 index 00000000000000..1cdd64f46eaa0b --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-stream-write-final.js @@ -0,0 +1,31 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +'use strict'; +const common = require('../common'); +const assert = require('assert'); + +const stream = require('stream'); +let shutdown = false; + +const w = new stream.Writable({ + final: common.mustCall(function(cb) { + assert.strictEqual(this, w); + setTimeout(function() { + shutdown = true; + cb(); + }, 100); + }), + write: function(chunk, e, cb) { + process.nextTick(cb); + } +}); +w.on('finish', common.mustCall(function() { + assert(shutdown); +})); +w.write(Buffer.allocUnsafe(1)); +w.end(Buffer.allocUnsafe(0)); diff --git a/cli/tests/node_compat/test/parallel/test-stream-writev.js b/cli/tests/node_compat/test/parallel/test-stream-writev.js new file mode 100644 index 00000000000000..f7fa7b9a012dc9 --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-stream-writev.js @@ -0,0 +1,137 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. + +'use strict'; +const common = require('../common'); +const assert = require('assert'); + +const stream = require('stream'); + +const queue = []; +for (let decode = 0; decode < 2; decode++) { + for (let uncork = 0; uncork < 2; uncork++) { + for (let multi = 0; multi < 2; multi++) { + queue.push([!!decode, !!uncork, !!multi]); + } + } +} + +run(); + +function run() { + const t = queue.pop(); + if (t) + test(t[0], t[1], t[2], run); + else + console.log('ok'); +} + +function test(decode, uncork, multi, next) { + console.log(`# decode=${decode} uncork=${uncork} multi=${multi}`); + let counter = 0; + let expectCount = 0; + function cnt(msg) { + expectCount++; + const expect = expectCount; + return function(er) { + assert.ifError(er); + counter++; + assert.strictEqual(counter, expect); + }; + } + + const w = new stream.Writable({ decodeStrings: decode }); + w._write = common.mustNotCall('Should not call _write'); + + const expectChunks = decode ? [ + { encoding: 'buffer', + chunk: [104, 101, 108, 108, 111, 44, 32] }, + { encoding: 'buffer', + chunk: [119, 111, 114, 108, 100] }, + { encoding: 'buffer', + chunk: [33] }, + { encoding: 'buffer', + chunk: [10, 97, 110, 100, 32, 116, 104, 101, 110, 46, 46, 46] }, + { encoding: 'buffer', + chunk: [250, 206, 190, 167, 222, 173, 190, 239, 222, 202, 251, 173] }, + ] : [ + { encoding: 'ascii', chunk: 'hello, ' }, + { encoding: 'utf8', chunk: 'world' }, + { encoding: 'buffer', chunk: [33] }, + { encoding: 'latin1', chunk: '\nand then...' }, + { encoding: 'hex', chunk: 'facebea7deadbeefdecafbad' }, + ]; + + let actualChunks; + w._writev = function(chunks, cb) { + actualChunks = chunks.map(function(chunk) { + return { + encoding: chunk.encoding, + chunk: Buffer.isBuffer(chunk.chunk) ? + Array.prototype.slice.call(chunk.chunk) : chunk.chunk + }; + }); + cb(); + }; + + w.cork(); + w.write('hello, ', 'ascii', cnt('hello')); + w.write('world', 'utf8', cnt('world')); + + if (multi) + w.cork(); + + w.write(Buffer.from('!'), 'buffer', cnt('!')); + w.write('\nand then...', 'latin1', cnt('and then')); + + if (multi) + w.uncork(); + + w.write('facebea7deadbeefdecafbad', 'hex', cnt('hex')); + + if (uncork) + w.uncork(); + + w.end(cnt('end')); + + w.on('finish', function() { + // Make sure finish comes after all the write cb + cnt('finish')(); + assert.deepStrictEqual(actualChunks, expectChunks); + next(); + }); +} + +{ + const w = new stream.Writable({ + writev: common.mustCall(function(chunks, cb) { + cb(); + }) + }); + w.write('asd', common.mustCall()); +} diff --git a/cli/tests/node_compat/test/parallel/test-stream2-base64-single-char-read-end.js b/cli/tests/node_compat/test/parallel/test-stream2-base64-single-char-read-end.js new file mode 100644 index 00000000000000..37f7129132d947 --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-stream2-base64-single-char-read-end.js @@ -0,0 +1,63 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. + +'use strict'; +require('../common'); +const { Readable: R, Writable: W } = require('stream'); +const assert = require('assert'); + +const src = new R({ encoding: 'base64' }); +const dst = new W(); +let hasRead = false; +const accum = []; + +src._read = function(n) { + if (!hasRead) { + hasRead = true; + process.nextTick(function() { + src.push(Buffer.from('1')); + src.push(null); + }); + } +}; + +dst._write = function(chunk, enc, cb) { + accum.push(chunk); + cb(); +}; + +src.on('end', function() { + assert.strictEqual(String(Buffer.concat(accum)), 'MQ=='); + clearTimeout(timeout); +}); + +src.pipe(dst); + +const timeout = setTimeout(function() { + assert.fail('timed out waiting for _write'); +}, 100); diff --git a/cli/tests/node_compat/test/parallel/test-stream2-basic.js b/cli/tests/node_compat/test/parallel/test-stream2-basic.js new file mode 100644 index 00000000000000..538ee89f6ffc40 --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-stream2-basic.js @@ -0,0 +1,452 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. + +'use strict'; + +const common = require('../common'); +const { Readable: R, Writable: W } = require('stream'); +const assert = require('assert'); + +const EE = require('events').EventEmitter; + +class TestReader extends R { + constructor(n) { + super(); + this._buffer = Buffer.alloc(n || 100, 'x'); + this._pos = 0; + this._bufs = 10; + } + + _read(n) { + const max = this._buffer.length - this._pos; + n = Math.max(n, 0); + const toRead = Math.min(n, max); + if (toRead === 0) { + // Simulate the read buffer filling up with some more bytes some time + // in the future. + setTimeout(() => { + this._pos = 0; + this._bufs -= 1; + if (this._bufs <= 0) { + // read them all! + if (!this.ended) + this.push(null); + } else { + // now we have more. + // kinda cheating by calling _read, but whatever, + // it's just fake anyway. + this._read(n); + } + }, 10); + return; + } + + const ret = this._buffer.slice(this._pos, this._pos + toRead); + this._pos += toRead; + this.push(ret); + } +} + +class TestWriter extends EE { + constructor() { + super(); + this.received = []; + this.flush = false; + } + + write(c) { + this.received.push(c.toString()); + this.emit('write', c); + return true; + } + + end(c) { + if (c) this.write(c); + this.emit('end', this.received); + } +} + +{ + // Test basic functionality + const r = new TestReader(20); + + const reads = []; + const expect = [ 'x', + 'xx', + 'xxx', + 'xxxx', + 'xxxxx', + 'xxxxxxxxx', + 'xxxxxxxxxx', + 'xxxxxxxxxxxx', + 'xxxxxxxxxxxxx', + 'xxxxxxxxxxxxxxx', + 'xxxxxxxxxxxxxxxxx', + 'xxxxxxxxxxxxxxxxxxx', + 'xxxxxxxxxxxxxxxxxxxxx', + 'xxxxxxxxxxxxxxxxxxxxxxx', + 'xxxxxxxxxxxxxxxxxxxxxxxxx', + 'xxxxxxxxxxxxxxxxxxxxx' ]; + + r.on('end', common.mustCall(function() { + assert.deepStrictEqual(reads, expect); + })); + + let readSize = 1; + function flow() { + let res; + while (null !== (res = r.read(readSize++))) { + reads.push(res.toString()); + } + r.once('readable', flow); + } + + flow(); +} + +{ + // Verify pipe + const r = new TestReader(5); + + const expect = [ 'xxxxx', + 'xxxxx', + 'xxxxx', + 'xxxxx', + 'xxxxx', + 'xxxxx', + 'xxxxx', + 'xxxxx', + 'xxxxx', + 'xxxxx' ]; + + const w = new TestWriter(); + + w.on('end', common.mustCall(function(received) { + assert.deepStrictEqual(received, expect); + })); + + r.pipe(w); +} + + +[1, 2, 3, 4, 5, 6, 7, 8, 9].forEach(function(SPLIT) { + // Verify unpipe + const r = new TestReader(5); + + // Unpipe after 3 writes, then write to another stream instead. + let expect = [ 'xxxxx', + 'xxxxx', + 'xxxxx', + 'xxxxx', + 'xxxxx', + 'xxxxx', + 'xxxxx', + 'xxxxx', + 'xxxxx', + 'xxxxx' ]; + expect = [ expect.slice(0, SPLIT), expect.slice(SPLIT) ]; + + const w = [ new TestWriter(), new TestWriter() ]; + + let writes = SPLIT; + w[0].on('write', function() { + if (--writes === 0) { + r.unpipe(); + assert.deepStrictEqual(r._readableState.pipes, []); + w[0].end(); + r.pipe(w[1]); + assert.deepStrictEqual(r._readableState.pipes, [w[1]]); + } + }); + + let ended = 0; + + w[0].on('end', common.mustCall(function(results) { + ended++; + assert.strictEqual(ended, 1); + assert.deepStrictEqual(results, expect[0]); + })); + + w[1].on('end', common.mustCall(function(results) { + ended++; + assert.strictEqual(ended, 2); + assert.deepStrictEqual(results, expect[1]); + })); + + r.pipe(w[0]); +}); + + +{ + // Verify both writers get the same data when piping to destinations + const r = new TestReader(5); + const w = [ new TestWriter(), new TestWriter() ]; + + const expect = [ 'xxxxx', + 'xxxxx', + 'xxxxx', + 'xxxxx', + 'xxxxx', + 'xxxxx', + 'xxxxx', + 'xxxxx', + 'xxxxx', + 'xxxxx' ]; + + w[0].on('end', common.mustCall(function(received) { + assert.deepStrictEqual(received, expect); + })); + w[1].on('end', common.mustCall(function(received) { + assert.deepStrictEqual(received, expect); + })); + + r.pipe(w[0]); + r.pipe(w[1]); +} + + +[1, 2, 3, 4, 5, 6, 7, 8, 9].forEach(function(SPLIT) { + // Verify multi-unpipe + const r = new TestReader(5); + + // Unpipe after 3 writes, then write to another stream instead. + let expect = [ 'xxxxx', + 'xxxxx', + 'xxxxx', + 'xxxxx', + 'xxxxx', + 'xxxxx', + 'xxxxx', + 'xxxxx', + 'xxxxx', + 'xxxxx' ]; + expect = [ expect.slice(0, SPLIT), expect.slice(SPLIT) ]; + + const w = [ new TestWriter(), new TestWriter(), new TestWriter() ]; + + let writes = SPLIT; + w[0].on('write', function() { + if (--writes === 0) { + r.unpipe(); + w[0].end(); + r.pipe(w[1]); + } + }); + + let ended = 0; + + w[0].on('end', common.mustCall(function(results) { + ended++; + assert.strictEqual(ended, 1); + assert.deepStrictEqual(results, expect[0]); + })); + + w[1].on('end', common.mustCall(function(results) { + ended++; + assert.strictEqual(ended, 2); + assert.deepStrictEqual(results, expect[1]); + })); + + r.pipe(w[0]); + r.pipe(w[2]); +}); + +{ + // Verify that back pressure is respected + const r = new R({ objectMode: true }); + r._read = common.mustNotCall(); + let counter = 0; + r.push(['one']); + r.push(['two']); + r.push(['three']); + r.push(['four']); + r.push(null); + + const w1 = new R(); + w1.write = function(chunk) { + assert.strictEqual(chunk[0], 'one'); + w1.emit('close'); + process.nextTick(function() { + r.pipe(w2); + r.pipe(w3); + }); + }; + w1.end = common.mustNotCall(); + + r.pipe(w1); + + const expected = ['two', 'two', 'three', 'three', 'four', 'four']; + + const w2 = new R(); + w2.write = function(chunk) { + assert.strictEqual(chunk[0], expected.shift()); + assert.strictEqual(counter, 0); + + counter++; + + if (chunk[0] === 'four') { + return true; + } + + setTimeout(function() { + counter--; + w2.emit('drain'); + }, 10); + + return false; + }; + w2.end = common.mustCall(); + + const w3 = new R(); + w3.write = function(chunk) { + assert.strictEqual(chunk[0], expected.shift()); + assert.strictEqual(counter, 1); + + counter++; + + if (chunk[0] === 'four') { + return true; + } + + setTimeout(function() { + counter--; + w3.emit('drain'); + }, 50); + + return false; + }; + w3.end = common.mustCall(function() { + assert.strictEqual(counter, 2); + assert.strictEqual(expected.length, 0); + }); +} + +{ + // Verify read(0) behavior for ended streams + const r = new R(); + let written = false; + let ended = false; + r._read = common.mustNotCall(); + + r.push(Buffer.from('foo')); + r.push(null); + + const v = r.read(0); + + assert.strictEqual(v, null); + + const w = new R(); + w.write = function(buffer) { + written = true; + assert.strictEqual(ended, false); + assert.strictEqual(buffer.toString(), 'foo'); + }; + + w.end = common.mustCall(function() { + ended = true; + assert.strictEqual(written, true); + }); + + r.pipe(w); +} + +{ + // Verify synchronous _read ending + const r = new R(); + let called = false; + r._read = function(n) { + r.push(null); + }; + + r.once('end', function() { + // Verify that this is called before the next tick + called = true; + }); + + r.read(); + + process.nextTick(function() { + assert.strictEqual(called, true); + }); +} + +{ + // Verify that adding readable listeners trigger data flow + const r = new R({ highWaterMark: 5 }); + let onReadable = false; + let readCalled = 0; + + r._read = function(n) { + if (readCalled++ === 2) + r.push(null); + else + r.push(Buffer.from('asdf')); + }; + + r.on('readable', function() { + onReadable = true; + r.read(); + }); + + r.on('end', common.mustCall(function() { + assert.strictEqual(readCalled, 3); + assert.ok(onReadable); + })); +} + +{ + // Verify that streams are chainable + const r = new R(); + r._read = common.mustCall(); + const r2 = r.setEncoding('utf8').pause().resume().pause(); + assert.strictEqual(r, r2); +} + +{ + // Verify readableEncoding property + assert(Object.hasOwn(R.prototype, 'readableEncoding')); + + const r = new R({ encoding: 'utf8' }); + assert.strictEqual(r.readableEncoding, 'utf8'); +} + +{ + // Verify readableObjectMode property + assert(Object.hasOwn(R.prototype, 'readableObjectMode')); + + const r = new R({ objectMode: true }); + assert.strictEqual(r.readableObjectMode, true); +} + +{ + // Verify writableObjectMode property + assert(Object.hasOwn(W.prototype, 'writableObjectMode')); + + const w = new W({ objectMode: true }); + assert.strictEqual(w.writableObjectMode, true); +} diff --git a/cli/tests/node_compat/test/parallel/test-stream2-compatibility.js b/cli/tests/node_compat/test/parallel/test-stream2-compatibility.js new file mode 100644 index 00000000000000..3128cf2887e376 --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-stream2-compatibility.js @@ -0,0 +1,77 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. + +'use strict'; +require('../common'); +const { Readable: R, Writable: W } = require('stream'); +const assert = require('assert'); + +let ondataCalled = 0; + +class TestReader extends R { + constructor() { + super(); + this._buffer = Buffer.alloc(100, 'x'); + + this.on('data', () => { + ondataCalled++; + }); + } + + _read(n) { + this.push(this._buffer); + this._buffer = Buffer.alloc(0); + } +} + +const reader = new TestReader(); +setImmediate(function() { + assert.strictEqual(ondataCalled, 1); + console.log('ok'); + reader.push(null); +}); + +class TestWriter extends W { + constructor() { + super(); + this.write('foo'); + this.end(); + } + + _write(chunk, enc, cb) { + cb(); + } +} + +const writer = new TestWriter(); + +process.on('exit', function() { + assert.strictEqual(reader.readable, false); + assert.strictEqual(writer.writable, false); + console.log('ok'); +}); diff --git a/cli/tests/node_compat/test/parallel/test-stream2-decode-partial.js b/cli/tests/node_compat/test/parallel/test-stream2-decode-partial.js new file mode 100644 index 00000000000000..78791ce9c12567 --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-stream2-decode-partial.js @@ -0,0 +1,30 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +'use strict'; +require('../common'); +const { Readable } = require('stream'); +const assert = require('assert'); + +let buf = ''; +const euro = Buffer.from([0xE2, 0x82, 0xAC]); +const cent = Buffer.from([0xC2, 0xA2]); +const source = Buffer.concat([euro, cent]); + +const readable = Readable({ encoding: 'utf8' }); +readable.push(source.slice(0, 2)); +readable.push(source.slice(2, 4)); +readable.push(source.slice(4, 6)); +readable.push(null); + +readable.on('data', function(data) { + buf += data; +}); + +process.on('exit', function() { + assert.strictEqual(buf, '€¢'); +}); diff --git a/cli/tests/node_compat/test/parallel/test-stream2-finish-pipe.js b/cli/tests/node_compat/test/parallel/test-stream2-finish-pipe.js new file mode 100644 index 00000000000000..6ff68882a34661 --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-stream2-finish-pipe.js @@ -0,0 +1,51 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. + +'use strict'; +require('../common'); +const stream = require('stream'); + +const r = new stream.Readable(); +r._read = function(size) { + r.push(Buffer.allocUnsafe(size)); +}; + +const w = new stream.Writable(); +w._write = function(data, encoding, cb) { + process.nextTick(cb, null); +}; + +r.pipe(w); + +// end() must be called in nextTick or a WRITE_AFTER_END error occurs. +process.nextTick(() => { + // This might sound unrealistic, but it happens in net.js. When + // socket.allowHalfOpen === false, EOF will cause .destroySoon() call which + // ends the writable side of net.Socket. + w.end(); +}); diff --git a/cli/tests/node_compat/test/parallel/test-stream2-large-read-stall.js b/cli/tests/node_compat/test/parallel/test-stream2-large-read-stall.js new file mode 100644 index 00000000000000..b1a173a3cddd0f --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-stream2-large-read-stall.js @@ -0,0 +1,81 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. + +'use strict'; +const common = require('../common'); +const assert = require('assert'); + +// If everything aligns so that you do a read(n) of exactly the +// remaining buffer, then make sure that 'end' still emits. + +const READSIZE = 100; +const PUSHSIZE = 20; +const PUSHCOUNT = 1000; +const HWM = 50; + +const Readable = require('stream').Readable; +const r = new Readable({ + highWaterMark: HWM +}); +const rs = r._readableState; + +r._read = push; + +r.on('readable', function() { + console.error('>> readable'); + let ret; + do { + console.error(` > read(${READSIZE})`); + ret = r.read(READSIZE); + console.error(` < ${ret && ret.length} (${rs.length} remain)`); + } while (ret && ret.length === READSIZE); + + console.error('<< after read()', + ret && ret.length, + rs.needReadable, + rs.length); +}); + +r.on('end', common.mustCall(function() { + assert.strictEqual(pushes, PUSHCOUNT + 1); +})); + +let pushes = 0; +function push() { + if (pushes > PUSHCOUNT) + return; + + if (pushes++ === PUSHCOUNT) { + console.error(' push(EOF)'); + return r.push(null); + } + + console.error(` push #${pushes}`); + if (r.push(Buffer.allocUnsafe(PUSHSIZE))) + setTimeout(push, 1); +} diff --git a/cli/tests/node_compat/test/parallel/test-stream2-objects.js b/cli/tests/node_compat/test/parallel/test-stream2-objects.js new file mode 100644 index 00000000000000..a2cb775eb18521 --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-stream2-objects.js @@ -0,0 +1,304 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. + +'use strict'; + +const common = require('../common'); +const { Readable, Writable } = require('stream'); +const assert = require('assert'); + +function toArray(callback) { + const stream = new Writable({ objectMode: true }); + const list = []; + stream.write = function(chunk) { + list.push(chunk); + }; + + stream.end = common.mustCall(function() { + callback(list); + }); + + return stream; +} + +function fromArray(list) { + const r = new Readable({ objectMode: true }); + r._read = common.mustNotCall(); + list.forEach(function(chunk) { + r.push(chunk); + }); + r.push(null); + + return r; +} + +{ + // Verify that objects can be read from the stream + const r = fromArray([{ one: '1' }, { two: '2' }]); + + const v1 = r.read(); + const v2 = r.read(); + const v3 = r.read(); + + assert.deepStrictEqual(v1, { one: '1' }); + assert.deepStrictEqual(v2, { two: '2' }); + assert.strictEqual(v3, null); +} + +{ + // Verify that objects can be piped into the stream + const r = fromArray([{ one: '1' }, { two: '2' }]); + + r.pipe(toArray(common.mustCall(function(list) { + assert.deepStrictEqual(list, [ + { one: '1' }, + { two: '2' }, + ]); + }))); +} + +{ + // Verify that read(n) is ignored + const r = fromArray([{ one: '1' }, { two: '2' }]); + const value = r.read(2); + + assert.deepStrictEqual(value, { one: '1' }); +} + +{ + // Verify that objects can be synchronously read + const r = new Readable({ objectMode: true }); + const list = [{ one: '1' }, { two: '2' }]; + r._read = function(n) { + const item = list.shift(); + r.push(item || null); + }; + + r.pipe(toArray(common.mustCall(function(list) { + assert.deepStrictEqual(list, [ + { one: '1' }, + { two: '2' }, + ]); + }))); +} + +{ + // Verify that objects can be asynchronously read + const r = new Readable({ objectMode: true }); + const list = [{ one: '1' }, { two: '2' }]; + r._read = function(n) { + const item = list.shift(); + process.nextTick(function() { + r.push(item || null); + }); + }; + + r.pipe(toArray(common.mustCall(function(list) { + assert.deepStrictEqual(list, [ + { one: '1' }, + { two: '2' }, + ]); + }))); +} + +{ + // Verify that strings can be read as objects + const r = new Readable({ + objectMode: true + }); + r._read = common.mustNotCall(); + const list = ['one', 'two', 'three']; + list.forEach(function(str) { + r.push(str); + }); + r.push(null); + + r.pipe(toArray(common.mustCall(function(array) { + assert.deepStrictEqual(array, list); + }))); +} + +{ + // Verify read(0) behavior for object streams + const r = new Readable({ + objectMode: true + }); + r._read = common.mustNotCall(); + + r.push('foobar'); + r.push(null); + + r.pipe(toArray(common.mustCall(function(array) { + assert.deepStrictEqual(array, ['foobar']); + }))); +} + +{ + // Verify the behavior of pushing falsey values + const r = new Readable({ + objectMode: true + }); + r._read = common.mustNotCall(); + + r.push(false); + r.push(0); + r.push(''); + r.push(null); + + r.pipe(toArray(common.mustCall(function(array) { + assert.deepStrictEqual(array, [false, 0, '']); + }))); +} + +{ + // Verify high watermark _read() behavior + const r = new Readable({ + highWaterMark: 6, + objectMode: true + }); + let calls = 0; + const list = ['1', '2', '3', '4', '5', '6', '7', '8']; + + r._read = function(n) { + calls++; + }; + + list.forEach(function(c) { + r.push(c); + }); + + const v = r.read(); + + assert.strictEqual(calls, 0); + assert.strictEqual(v, '1'); + + const v2 = r.read(); + assert.strictEqual(v2, '2'); + + const v3 = r.read(); + assert.strictEqual(v3, '3'); + + assert.strictEqual(calls, 1); +} + +{ + // Verify high watermark push behavior + const r = new Readable({ + highWaterMark: 6, + objectMode: true + }); + r._read = common.mustNotCall(); + for (let i = 0; i < 6; i++) { + const bool = r.push(i); + assert.strictEqual(bool, i !== 5); + } +} + +{ + // Verify that objects can be written to stream + const w = new Writable({ objectMode: true }); + + w._write = function(chunk, encoding, cb) { + assert.deepStrictEqual(chunk, { foo: 'bar' }); + cb(); + }; + + w.on('finish', common.mustCall()); + w.write({ foo: 'bar' }); + w.end(); +} + +{ + // Verify that multiple objects can be written to stream + const w = new Writable({ objectMode: true }); + const list = []; + + w._write = function(chunk, encoding, cb) { + list.push(chunk); + cb(); + }; + + w.on('finish', common.mustCall(function() { + assert.deepStrictEqual(list, [0, 1, 2, 3, 4]); + })); + + w.write(0); + w.write(1); + w.write(2); + w.write(3); + w.write(4); + w.end(); +} + +{ + // Verify that strings can be written as objects + const w = new Writable({ + objectMode: true + }); + const list = []; + + w._write = function(chunk, encoding, cb) { + list.push(chunk); + process.nextTick(cb); + }; + + w.on('finish', common.mustCall(function() { + assert.deepStrictEqual(list, ['0', '1', '2', '3', '4']); + })); + + w.write('0'); + w.write('1'); + w.write('2'); + w.write('3'); + w.write('4'); + w.end(); +} + +{ + // Verify that stream buffers finish until callback is called + const w = new Writable({ + objectMode: true + }); + let called = false; + + w._write = function(chunk, encoding, cb) { + assert.strictEqual(chunk, 'foo'); + + process.nextTick(function() { + called = true; + cb(); + }); + }; + + w.on('finish', common.mustCall(function() { + assert.strictEqual(called, true); + })); + + w.write('foo'); + w.end(); +} diff --git a/cli/tests/node_compat/test/parallel/test-stream2-pipe-error-handling.js b/cli/tests/node_compat/test/parallel/test-stream2-pipe-error-handling.js new file mode 100644 index 00000000000000..ef3bb0575e5edc --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-stream2-pipe-error-handling.js @@ -0,0 +1,113 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. + +'use strict'; +require('../common'); +const assert = require('assert'); +const stream = require('stream'); + +{ + let count = 1000; + + const source = new stream.Readable(); + source._read = function(n) { + n = Math.min(count, n); + count -= n; + source.push(Buffer.allocUnsafe(n)); + }; + + let unpipedDest; + source.unpipe = function(dest) { + unpipedDest = dest; + stream.Readable.prototype.unpipe.call(this, dest); + }; + + const dest = new stream.Writable(); + dest._write = function(chunk, encoding, cb) { + cb(); + }; + + source.pipe(dest); + + let gotErr = null; + dest.on('error', function(err) { + gotErr = err; + }); + + let unpipedSource; + dest.on('unpipe', function(src) { + unpipedSource = src; + }); + + const err = new Error('This stream turned into bacon.'); + dest.emit('error', err); + assert.strictEqual(gotErr, err); + assert.strictEqual(unpipedSource, source); + assert.strictEqual(unpipedDest, dest); +} + +{ + let count = 1000; + + const source = new stream.Readable(); + source._read = function(n) { + n = Math.min(count, n); + count -= n; + source.push(Buffer.allocUnsafe(n)); + }; + + let unpipedDest; + source.unpipe = function(dest) { + unpipedDest = dest; + stream.Readable.prototype.unpipe.call(this, dest); + }; + + const dest = new stream.Writable({ autoDestroy: false }); + dest._write = function(chunk, encoding, cb) { + cb(); + }; + + source.pipe(dest); + + let unpipedSource; + dest.on('unpipe', function(src) { + unpipedSource = src; + }); + + const err = new Error('This stream turned into bacon.'); + + let gotErr = null; + try { + dest.emit('error', err); + } catch (e) { + gotErr = e; + } + assert.strictEqual(gotErr, err); + assert.strictEqual(unpipedSource, source); + assert.strictEqual(unpipedDest, dest); +} diff --git a/cli/tests/node_compat/test/parallel/test-stream2-pipe-error-once-listener.js b/cli/tests/node_compat/test/parallel/test-stream2-pipe-error-once-listener.js new file mode 100644 index 00000000000000..160381b086bc30 --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-stream2-pipe-error-once-listener.js @@ -0,0 +1,60 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. + +'use strict'; + +require('../common'); +const stream = require('stream'); + +class Read extends stream.Readable { + _read(size) { + this.push('x'); + this.push(null); + } +} + +class Write extends stream.Writable { + _write(buffer, encoding, cb) { + this.emit('error', new Error('boom')); + this.emit('alldone'); + } +} + +const read = new Read(); +const write = new Write(); + +write.once('error', () => {}); +write.once('alldone', function(err) { + console.log('ok'); +}); + +process.on('exit', function(c) { + console.error('error thrown even with listener'); +}); + +read.pipe(write); diff --git a/cli/tests/node_compat/test/parallel/test-stream2-push.js b/cli/tests/node_compat/test/parallel/test-stream2-push.js new file mode 100644 index 00000000000000..1ccfda2ca0e3d3 --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-stream2-push.js @@ -0,0 +1,143 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. + +'use strict'; +require('../common'); +const assert = require('assert'); +const { Readable, Writable } = require('stream'); + +const EE = require('events').EventEmitter; + + +// A mock thing a bit like the net.Socket/tcp_wrap.handle interaction + +const stream = new Readable({ + highWaterMark: 16, + encoding: 'utf8' +}); + +const source = new EE(); + +stream._read = function() { + console.error('stream._read'); + readStart(); +}; + +let ended = false; +stream.on('end', function() { + ended = true; +}); + +source.on('data', function(chunk) { + const ret = stream.push(chunk); + console.error('data', stream.readableLength); + if (!ret) + readStop(); +}); + +source.on('end', function() { + stream.push(null); +}); + +let reading = false; + +function readStart() { + console.error('readStart'); + reading = true; +} + +function readStop() { + console.error('readStop'); + reading = false; + process.nextTick(function() { + const r = stream.read(); + if (r !== null) + writer.write(r); + }); +} + +const writer = new Writable({ + decodeStrings: false +}); + +const written = []; + +const expectWritten = + [ 'asdfgasdfgasdfgasdfg', + 'asdfgasdfgasdfgasdfg', + 'asdfgasdfgasdfgasdfg', + 'asdfgasdfgasdfgasdfg', + 'asdfgasdfgasdfgasdfg', + 'asdfgasdfgasdfgasdfg' ]; + +writer._write = function(chunk, encoding, cb) { + console.error(`WRITE ${chunk}`); + written.push(chunk); + process.nextTick(cb); +}; + +writer.on('finish', finish); + + +// Now emit some chunks. + +const chunk = 'asdfg'; + +let set = 0; +readStart(); +data(); +function data() { + assert(reading); + source.emit('data', chunk); + assert(reading); + source.emit('data', chunk); + assert(reading); + source.emit('data', chunk); + assert(reading); + source.emit('data', chunk); + assert(!reading); + if (set++ < 5) + setTimeout(data, 10); + else + end(); +} + +function finish() { + console.error('finish'); + assert.deepStrictEqual(written, expectWritten); + console.log('ok'); +} + +function end() { + source.emit('end'); + assert(!reading); + writer.end(stream.read()); + setImmediate(function() { + assert(ended); + }); +} diff --git a/cli/tests/node_compat/test/parallel/test-stream2-read-sync-stack.js b/cli/tests/node_compat/test/parallel/test-stream2-read-sync-stack.js new file mode 100644 index 00000000000000..2630e1c29aabec --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-stream2-read-sync-stack.js @@ -0,0 +1,53 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. + +'use strict'; +const common = require('../common'); +const Readable = require('stream').Readable; + +// This tests synchronous read callbacks and verifies that even if they nest +// heavily the process handles it without an error + +const r = new Readable(); +const N = 256 * 1024; + +let reads = 0; +r._read = function(n) { + const chunk = reads++ === N ? null : Buffer.allocUnsafe(1); + r.push(chunk); +}; + +r.on('readable', function onReadable() { + if (!(r.readableLength % 256)) + console.error('readable', r.readableLength); + r.read(N * 2); +}); + +r.on('end', common.mustCall()); + +r.read(0); diff --git a/cli/tests/node_compat/test/parallel/test-stream2-readable-empty-buffer-no-eof.js b/cli/tests/node_compat/test/parallel/test-stream2-readable-empty-buffer-no-eof.js new file mode 100644 index 00000000000000..15fad698083909 --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-stream2-readable-empty-buffer-no-eof.js @@ -0,0 +1,124 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. + +'use strict'; +require('../common'); +const assert = require('assert'); + +const Readable = require('stream').Readable; + +test1(); +test2(); + +function test1() { + const r = new Readable(); + + // Should not end when we get a Buffer.alloc(0) or '' as the _read + // result that just means that there is *temporarily* no data, but to + // go ahead and try again later. + // + // note that this is very unusual. it only works for crypto streams + // because the other side of the stream will call read(0) to cycle + // data through openssl. that's why setImmediate() is used to call + // r.read(0) again later, otherwise there is no more work being done + // and the process just exits. + + const buf = Buffer.alloc(5, 'x'); + let reads = 5; + r._read = function(n) { + switch (reads--) { + case 5: + return setImmediate(() => { + return r.push(buf); + }); + case 4: + setImmediate(() => { + return r.push(Buffer.alloc(0)); + }); + return setImmediate(r.read.bind(r, 0)); + case 3: + setImmediate(r.read.bind(r, 0)); + return process.nextTick(() => { + return r.push(Buffer.alloc(0)); + }); + case 2: + setImmediate(r.read.bind(r, 0)); + return r.push(Buffer.alloc(0)); // Not-EOF! + case 1: + return r.push(buf); + case 0: + return r.push(null); // EOF + default: + throw new Error('unreachable'); + } + }; + + const results = []; + function flow() { + let chunk; + while (null !== (chunk = r.read())) + results.push(String(chunk)); + } + r.on('readable', flow); + r.on('end', () => { + results.push('EOF'); + }); + flow(); + + process.on('exit', () => { + assert.deepStrictEqual(results, [ 'xxxxx', 'xxxxx', 'EOF' ]); + console.log('ok'); + }); +} + +function test2() { + const r = new Readable({ encoding: 'base64' }); + let reads = 5; + r._read = function(n) { + if (!reads--) + return r.push(null); // EOF + return r.push(Buffer.from('x')); + }; + + const results = []; + function flow() { + let chunk; + while (null !== (chunk = r.read())) + results.push(String(chunk)); + } + r.on('readable', flow); + r.on('end', () => { + results.push('EOF'); + }); + flow(); + + process.on('exit', () => { + assert.deepStrictEqual(results, [ 'eHh4', 'eHg=', 'EOF' ]); + console.log('ok'); + }); +} diff --git a/cli/tests/node_compat/test/parallel/test-stream2-readable-from-list.js b/cli/tests/node_compat/test/parallel/test-stream2-readable-from-list.js new file mode 100644 index 00000000000000..72c3b15aab71ba --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-stream2-readable-from-list.js @@ -0,0 +1,108 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. + +// Flags: --expose-internals +'use strict'; +require('../common'); +const assert = require('assert'); +const fromList = require('stream').Readable._fromList; +const BufferList = require('internal/streams/buffer_list'); +const util = require('util'); + +function bufferListFromArray(arr) { + const bl = new BufferList(); + for (let i = 0; i < arr.length; ++i) + bl.push(arr[i]); + return bl; +} + +{ + // Verify behavior with buffers + let list = [ Buffer.from('foog'), + Buffer.from('bark'), + Buffer.from('bazy'), + Buffer.from('kuel') ]; + list = bufferListFromArray(list); + + assert.strictEqual( + util.inspect([ list ], { compact: false }), + `[ + BufferList { + head: [Object], + tail: [Object], + length: 4 + } +]`); + + // Read more than the first element. + let ret = fromList(6, { buffer: list, length: 16 }); + assert.strictEqual(ret.toString(), 'foogba'); + + // Read exactly the first element. + ret = fromList(2, { buffer: list, length: 10 }); + assert.strictEqual(ret.toString(), 'rk'); + + // Read less than the first element. + ret = fromList(2, { buffer: list, length: 8 }); + assert.strictEqual(ret.toString(), 'ba'); + + // Read more than we have. + ret = fromList(100, { buffer: list, length: 6 }); + assert.strictEqual(ret.toString(), 'zykuel'); + + // all consumed. + assert.deepStrictEqual(list, new BufferList()); +} + +{ + // Verify behavior with strings + let list = [ 'foog', + 'bark', + 'bazy', + 'kuel' ]; + list = bufferListFromArray(list); + + // Read more than the first element. + let ret = fromList(6, { buffer: list, length: 16, decoder: true }); + assert.strictEqual(ret, 'foogba'); + + // Read exactly the first element. + ret = fromList(2, { buffer: list, length: 10, decoder: true }); + assert.strictEqual(ret, 'rk'); + + // Read less than the first element. + ret = fromList(2, { buffer: list, length: 8, decoder: true }); + assert.strictEqual(ret, 'ba'); + + // Read more than we have. + ret = fromList(100, { buffer: list, length: 6, decoder: true }); + assert.strictEqual(ret, 'zykuel'); + + // all consumed. + assert.deepStrictEqual(list, new BufferList()); +} diff --git a/cli/tests/node_compat/test/parallel/test-stream2-readable-legacy-drain.js b/cli/tests/node_compat/test/parallel/test-stream2-readable-legacy-drain.js new file mode 100644 index 00000000000000..e05af718d7b993 --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-stream2-readable-legacy-drain.js @@ -0,0 +1,62 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. + +'use strict'; +const common = require('../common'); +const assert = require('assert'); + +const Stream = require('stream'); +const Readable = Stream.Readable; + +const r = new Readable(); +const N = 256; +let reads = 0; +r._read = function(n) { + return r.push(++reads === N ? null : Buffer.allocUnsafe(1)); +}; + +r.on('end', common.mustCall()); + +const w = new Stream(); +w.writable = true; +let buffered = 0; +w.write = function(c) { + buffered += c.length; + process.nextTick(drain); + return false; +}; + +function drain() { + assert(buffered <= 3); + buffered = 0; + w.emit('drain'); +} + +w.end = common.mustCall(); + +r.pipe(w); diff --git a/cli/tests/node_compat/test/parallel/test-stream2-readable-non-empty-end.js b/cli/tests/node_compat/test/parallel/test-stream2-readable-non-empty-end.js new file mode 100644 index 00000000000000..3b6a1e4d1758b9 --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-stream2-readable-non-empty-end.js @@ -0,0 +1,79 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. + +'use strict'; +const common = require('../common'); +const assert = require('assert'); +const { Readable } = require('stream'); + +let len = 0; +const chunks = new Array(10); +for (let i = 1; i <= 10; i++) { + chunks[i - 1] = Buffer.allocUnsafe(i); + len += i; +} + +const test = new Readable(); +let n = 0; +test._read = function(size) { + const chunk = chunks[n++]; + setTimeout(function() { + test.push(chunk === undefined ? null : chunk); + }, 1); +}; + +test.on('end', thrower); +function thrower() { + throw new Error('this should not happen!'); +} + +let bytesread = 0; +test.on('readable', function() { + const b = len - bytesread - 1; + const res = test.read(b); + if (res) { + bytesread += res.length; + console.error(`br=${bytesread} len=${len}`); + setTimeout(next, 1); + } + test.read(0); +}); +test.read(0); + +function next() { + // Now let's make 'end' happen + test.removeListener('end', thrower); + test.on('end', common.mustCall()); + + // One to get the last byte + let r = test.read(); + assert(r); + assert.strictEqual(r.length, 1); + r = test.read(); + assert.strictEqual(r, null); +} diff --git a/cli/tests/node_compat/test/parallel/test-stream2-readable-wrap-destroy.js b/cli/tests/node_compat/test/parallel/test-stream2-readable-wrap-destroy.js new file mode 100644 index 00000000000000..1282cbffabdecf --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-stream2-readable-wrap-destroy.js @@ -0,0 +1,34 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +'use strict'; +const common = require('../common'); + +const { Readable } = require('stream'); +const EE = require('events').EventEmitter; + +const oldStream = new EE(); +oldStream.pause = () => {}; +oldStream.resume = () => {}; + +{ + new Readable({ + autoDestroy: false, + destroy: common.mustCall() + }) + .wrap(oldStream); + oldStream.emit('destroy'); +} + +{ + new Readable({ + autoDestroy: false, + destroy: common.mustCall() + }) + .wrap(oldStream); + oldStream.emit('close'); +} diff --git a/cli/tests/node_compat/test/parallel/test-stream2-readable-wrap-empty.js b/cli/tests/node_compat/test/parallel/test-stream2-readable-wrap-empty.js new file mode 100644 index 00000000000000..235c999a02ed90 --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-stream2-readable-wrap-empty.js @@ -0,0 +1,45 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. + +'use strict'; +const common = require('../common'); + +const { Readable } = require('stream'); +const EE = require('events').EventEmitter; + +const oldStream = new EE(); +oldStream.pause = () => {}; +oldStream.resume = () => {}; + +const newStream = new Readable().wrap(oldStream); + +newStream + .on('readable', () => {}) + .on('end', common.mustCall()); + +oldStream.emit('end'); diff --git a/cli/tests/node_compat/test/parallel/test-stream2-readable-wrap-error.js b/cli/tests/node_compat/test/parallel/test-stream2-readable-wrap-error.js new file mode 100644 index 00000000000000..2de9cba5c9a0f1 --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-stream2-readable-wrap-error.js @@ -0,0 +1,44 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +'use strict'; +const common = require('../common'); +const assert = require('assert'); + +const { Readable } = require('stream'); +const EE = require('events').EventEmitter; + +class LegacyStream extends EE { + pause() {} + resume() {} +} + +{ + const err = new Error(); + const oldStream = new LegacyStream(); + const r = new Readable({ autoDestroy: true }) + .wrap(oldStream) + .on('error', common.mustCall(() => { + assert.strictEqual(r._readableState.errorEmitted, true); + assert.strictEqual(r._readableState.errored, err); + assert.strictEqual(r.destroyed, true); + })); + oldStream.emit('error', err); +} + +{ + const err = new Error(); + const oldStream = new LegacyStream(); + const r = new Readable({ autoDestroy: false }) + .wrap(oldStream) + .on('error', common.mustCall(() => { + assert.strictEqual(r._readableState.errorEmitted, true); + assert.strictEqual(r._readableState.errored, err); + assert.strictEqual(r.destroyed, false); + })); + oldStream.emit('error', err); +} diff --git a/cli/tests/node_compat/test/parallel/test-stream2-readable-wrap.js b/cli/tests/node_compat/test/parallel/test-stream2-readable-wrap.js new file mode 100644 index 00000000000000..bc131fc8b54eb4 --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-stream2-readable-wrap.js @@ -0,0 +1,107 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. + +'use strict'; +const common = require('../common'); +const assert = require('assert'); +const { Readable, Writable } = require('stream'); +const EE = require('events').EventEmitter; + +function runTest(highWaterMark, objectMode, produce) { + + const old = new EE(); + const r = new Readable({ highWaterMark, objectMode }); + assert.strictEqual(r, r.wrap(old)); + + r.on('end', common.mustCall()); + + old.pause = function() { + old.emit('pause'); + flowing = false; + }; + + old.resume = function() { + old.emit('resume'); + flow(); + }; + + // Make sure pause is only emitted once. + let pausing = false; + r.on('pause', () => { + assert.strictEqual(pausing, false); + pausing = true; + process.nextTick(() => { + pausing = false; + }); + }); + + let flowing; + let chunks = 10; + let oldEnded = false; + const expected = []; + function flow() { + flowing = true; + while (flowing && chunks-- > 0) { + const item = produce(); + expected.push(item); + old.emit('data', item); + } + if (chunks <= 0) { + oldEnded = true; + old.emit('end'); + } + } + + const w = new Writable({ highWaterMark: highWaterMark * 2, + objectMode }); + const written = []; + w._write = function(chunk, encoding, cb) { + written.push(chunk); + setTimeout(cb, 1); + }; + + w.on('finish', common.mustCall(function() { + performAsserts(); + })); + + r.pipe(w); + + flow(); + + function performAsserts() { + assert(oldEnded); + assert.deepStrictEqual(written, expected); + } +} + +runTest(100, false, function() { return Buffer.allocUnsafe(100); }); +runTest(10, false, function() { return Buffer.from('xxxxxxxxxx'); }); +runTest(1, true, function() { return { foo: 'bar' }; }); + +const objectChunks = [ 5, 'a', false, 0, '', 'xyz', { x: 4 }, 7, [], 555 ]; +runTest(1, true, function() { return objectChunks.shift(); }); diff --git a/cli/tests/node_compat/test/parallel/test-stream2-set-encoding.js b/cli/tests/node_compat/test/parallel/test-stream2-set-encoding.js new file mode 100644 index 00000000000000..ae8d04da9aafe9 --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-stream2-set-encoding.js @@ -0,0 +1,330 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. + +'use strict'; +const common = require('../common'); +const assert = require('assert'); +const { Readable: R } = require('stream'); + +class TestReader extends R { + constructor(n, opts) { + super(opts); + this.pos = 0; + this.len = n || 100; + } + + _read(n) { + setTimeout(() => { + if (this.pos >= this.len) { + // Double push(null) to test eos handling + this.push(null); + return this.push(null); + } + + n = Math.min(n, this.len - this.pos); + if (n <= 0) { + // Double push(null) to test eos handling + this.push(null); + return this.push(null); + } + + this.pos += n; + const ret = Buffer.alloc(n, 'a'); + + return this.push(ret); + }, 1); + } +} + +{ + // Verify utf8 encoding + const tr = new TestReader(100); + tr.setEncoding('utf8'); + const out = []; + const expect = + [ 'aaaaaaaaaa', + 'aaaaaaaaaa', + 'aaaaaaaaaa', + 'aaaaaaaaaa', + 'aaaaaaaaaa', + 'aaaaaaaaaa', + 'aaaaaaaaaa', + 'aaaaaaaaaa', + 'aaaaaaaaaa', + 'aaaaaaaaaa' ]; + + tr.on('readable', function flow() { + let chunk; + while (null !== (chunk = tr.read(10))) + out.push(chunk); + }); + + tr.on('end', common.mustCall(function() { + assert.deepStrictEqual(out, expect); + })); +} + + +{ + // Verify hex encoding + const tr = new TestReader(100); + tr.setEncoding('hex'); + const out = []; + const expect = + [ '6161616161', + '6161616161', + '6161616161', + '6161616161', + '6161616161', + '6161616161', + '6161616161', + '6161616161', + '6161616161', + '6161616161', + '6161616161', + '6161616161', + '6161616161', + '6161616161', + '6161616161', + '6161616161', + '6161616161', + '6161616161', + '6161616161', + '6161616161' ]; + + tr.on('readable', function flow() { + let chunk; + while (null !== (chunk = tr.read(10))) + out.push(chunk); + }); + + tr.on('end', common.mustCall(function() { + assert.deepStrictEqual(out, expect); + })); +} + +{ + // Verify hex encoding with read(13) + const tr = new TestReader(100); + tr.setEncoding('hex'); + const out = []; + const expect = + [ '6161616161616', + '1616161616161', + '6161616161616', + '1616161616161', + '6161616161616', + '1616161616161', + '6161616161616', + '1616161616161', + '6161616161616', + '1616161616161', + '6161616161616', + '1616161616161', + '6161616161616', + '1616161616161', + '6161616161616', + '16161' ]; + + tr.on('readable', function flow() { + let chunk; + while (null !== (chunk = tr.read(13))) + out.push(chunk); + }); + + tr.on('end', common.mustCall(function() { + assert.deepStrictEqual(out, expect); + })); +} + +{ + // Verify base64 encoding + const tr = new TestReader(100); + tr.setEncoding('base64'); + const out = []; + const expect = + [ 'YWFhYWFhYW', + 'FhYWFhYWFh', + 'YWFhYWFhYW', + 'FhYWFhYWFh', + 'YWFhYWFhYW', + 'FhYWFhYWFh', + 'YWFhYWFhYW', + 'FhYWFhYWFh', + 'YWFhYWFhYW', + 'FhYWFhYWFh', + 'YWFhYWFhYW', + 'FhYWFhYWFh', + 'YWFhYWFhYW', + 'FhYQ==' ]; + + tr.on('readable', function flow() { + let chunk; + while (null !== (chunk = tr.read(10))) + out.push(chunk); + }); + + tr.on('end', common.mustCall(function() { + assert.deepStrictEqual(out, expect); + })); +} + +{ + // Verify utf8 encoding + const tr = new TestReader(100, { encoding: 'utf8' }); + const out = []; + const expect = + [ 'aaaaaaaaaa', + 'aaaaaaaaaa', + 'aaaaaaaaaa', + 'aaaaaaaaaa', + 'aaaaaaaaaa', + 'aaaaaaaaaa', + 'aaaaaaaaaa', + 'aaaaaaaaaa', + 'aaaaaaaaaa', + 'aaaaaaaaaa' ]; + + tr.on('readable', function flow() { + let chunk; + while (null !== (chunk = tr.read(10))) + out.push(chunk); + }); + + tr.on('end', common.mustCall(function() { + assert.deepStrictEqual(out, expect); + })); +} + + +{ + // Verify hex encoding + const tr = new TestReader(100, { encoding: 'hex' }); + const out = []; + const expect = + [ '6161616161', + '6161616161', + '6161616161', + '6161616161', + '6161616161', + '6161616161', + '6161616161', + '6161616161', + '6161616161', + '6161616161', + '6161616161', + '6161616161', + '6161616161', + '6161616161', + '6161616161', + '6161616161', + '6161616161', + '6161616161', + '6161616161', + '6161616161' ]; + + tr.on('readable', function flow() { + let chunk; + while (null !== (chunk = tr.read(10))) + out.push(chunk); + }); + + tr.on('end', common.mustCall(function() { + assert.deepStrictEqual(out, expect); + })); +} + +{ + // Verify hex encoding with read(13) + const tr = new TestReader(100, { encoding: 'hex' }); + const out = []; + const expect = + [ '6161616161616', + '1616161616161', + '6161616161616', + '1616161616161', + '6161616161616', + '1616161616161', + '6161616161616', + '1616161616161', + '6161616161616', + '1616161616161', + '6161616161616', + '1616161616161', + '6161616161616', + '1616161616161', + '6161616161616', + '16161' ]; + + tr.on('readable', function flow() { + let chunk; + while (null !== (chunk = tr.read(13))) + out.push(chunk); + }); + + tr.on('end', common.mustCall(function() { + assert.deepStrictEqual(out, expect); + })); +} + +{ + // Verify base64 encoding + const tr = new TestReader(100, { encoding: 'base64' }); + const out = []; + const expect = + [ 'YWFhYWFhYW', + 'FhYWFhYWFh', + 'YWFhYWFhYW', + 'FhYWFhYWFh', + 'YWFhYWFhYW', + 'FhYWFhYWFh', + 'YWFhYWFhYW', + 'FhYWFhYWFh', + 'YWFhYWFhYW', + 'FhYWFhYWFh', + 'YWFhYWFhYW', + 'FhYWFhYWFh', + 'YWFhYWFhYW', + 'FhYQ==' ]; + + tr.on('readable', function flow() { + let chunk; + while (null !== (chunk = tr.read(10))) + out.push(chunk); + }); + + tr.on('end', common.mustCall(function() { + assert.deepStrictEqual(out, expect); + })); +} + +{ + // Verify chaining behavior + const tr = new TestReader(100); + assert.deepStrictEqual(tr.setEncoding('utf8'), tr); +} diff --git a/cli/tests/node_compat/test/parallel/test-stream2-transform.js b/cli/tests/node_compat/test/parallel/test-stream2-transform.js new file mode 100644 index 00000000000000..72823369de99ac --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-stream2-transform.js @@ -0,0 +1,477 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. + +'use strict'; +const common = require('../common'); +const assert = require('assert'); +const { PassThrough, Transform } = require('stream'); + +{ + // Verify writable side consumption + const tx = new Transform({ + highWaterMark: 10 + }); + + let transformed = 0; + tx._transform = function(chunk, encoding, cb) { + transformed += chunk.length; + tx.push(chunk); + cb(); + }; + + for (let i = 1; i <= 10; i++) { + tx.write(Buffer.allocUnsafe(i)); + } + tx.end(); + + assert.strictEqual(tx.readableLength, 10); + assert.strictEqual(transformed, 10); + assert.deepStrictEqual(tx.writableBuffer.map(function(c) { + return c.chunk.length; + }), [5, 6, 7, 8, 9, 10]); +} + +{ + // Verify passthrough behavior + const pt = new PassThrough(); + + pt.write(Buffer.from('foog')); + pt.write(Buffer.from('bark')); + pt.write(Buffer.from('bazy')); + pt.write(Buffer.from('kuel')); + pt.end(); + + assert.strictEqual(pt.read(5).toString(), 'foogb'); + assert.strictEqual(pt.read(5).toString(), 'arkba'); + assert.strictEqual(pt.read(5).toString(), 'zykue'); + assert.strictEqual(pt.read(5).toString(), 'l'); +} + +{ + // Verify object passthrough behavior + const pt = new PassThrough({ objectMode: true }); + + pt.write(1); + pt.write(true); + pt.write(false); + pt.write(0); + pt.write('foo'); + pt.write(''); + pt.write({ a: 'b' }); + pt.end(); + + assert.strictEqual(pt.read(), 1); + assert.strictEqual(pt.read(), true); + assert.strictEqual(pt.read(), false); + assert.strictEqual(pt.read(), 0); + assert.strictEqual(pt.read(), 'foo'); + assert.strictEqual(pt.read(), ''); + assert.deepStrictEqual(pt.read(), { a: 'b' }); +} + +{ + // Verify passthrough constructor behavior + const pt = PassThrough(); + + assert(pt instanceof PassThrough); +} + +{ + // Verify transform constructor behavior + const pt = Transform(); + + assert(pt instanceof Transform); +} + +{ + // Perform a simple transform + const pt = new Transform(); + pt._transform = function(c, e, cb) { + const ret = Buffer.alloc(c.length, 'x'); + pt.push(ret); + cb(); + }; + + pt.write(Buffer.from('foog')); + pt.write(Buffer.from('bark')); + pt.write(Buffer.from('bazy')); + pt.write(Buffer.from('kuel')); + pt.end(); + + assert.strictEqual(pt.read(5).toString(), 'xxxxx'); + assert.strictEqual(pt.read(5).toString(), 'xxxxx'); + assert.strictEqual(pt.read(5).toString(), 'xxxxx'); + assert.strictEqual(pt.read(5).toString(), 'x'); +} + +{ + // Verify simple object transform + const pt = new Transform({ objectMode: true }); + pt._transform = function(c, e, cb) { + pt.push(JSON.stringify(c)); + cb(); + }; + + pt.write(1); + pt.write(true); + pt.write(false); + pt.write(0); + pt.write('foo'); + pt.write(''); + pt.write({ a: 'b' }); + pt.end(); + + assert.strictEqual(pt.read(), '1'); + assert.strictEqual(pt.read(), 'true'); + assert.strictEqual(pt.read(), 'false'); + assert.strictEqual(pt.read(), '0'); + assert.strictEqual(pt.read(), '"foo"'); + assert.strictEqual(pt.read(), '""'); + assert.strictEqual(pt.read(), '{"a":"b"}'); +} + +{ + // Verify async passthrough + const pt = new Transform(); + pt._transform = function(chunk, encoding, cb) { + setTimeout(function() { + pt.push(chunk); + cb(); + }, 10); + }; + + pt.write(Buffer.from('foog')); + pt.write(Buffer.from('bark')); + pt.write(Buffer.from('bazy')); + pt.write(Buffer.from('kuel')); + pt.end(); + + pt.on('finish', common.mustCall(function() { + assert.strictEqual(pt.read(5).toString(), 'foogb'); + assert.strictEqual(pt.read(5).toString(), 'arkba'); + assert.strictEqual(pt.read(5).toString(), 'zykue'); + assert.strictEqual(pt.read(5).toString(), 'l'); + })); +} + +{ + // Verify asymmetric transform (expand) + const pt = new Transform(); + + // Emit each chunk 2 times. + pt._transform = function(chunk, encoding, cb) { + setTimeout(function() { + pt.push(chunk); + setTimeout(function() { + pt.push(chunk); + cb(); + }, 10); + }, 10); + }; + + pt.write(Buffer.from('foog')); + pt.write(Buffer.from('bark')); + pt.write(Buffer.from('bazy')); + pt.write(Buffer.from('kuel')); + pt.end(); + + pt.on('finish', common.mustCall(function() { + assert.strictEqual(pt.read(5).toString(), 'foogf'); + assert.strictEqual(pt.read(5).toString(), 'oogba'); + assert.strictEqual(pt.read(5).toString(), 'rkbar'); + assert.strictEqual(pt.read(5).toString(), 'kbazy'); + assert.strictEqual(pt.read(5).toString(), 'bazyk'); + assert.strictEqual(pt.read(5).toString(), 'uelku'); + assert.strictEqual(pt.read(5).toString(), 'el'); + })); +} + +{ + // Verify asymmetric transform (compress) + const pt = new Transform(); + + // Each output is the first char of 3 consecutive chunks, + // or whatever's left. + pt.state = ''; + + pt._transform = function(chunk, encoding, cb) { + if (!chunk) + chunk = ''; + const s = chunk.toString(); + setTimeout(() => { + this.state += s.charAt(0); + if (this.state.length === 3) { + pt.push(Buffer.from(this.state)); + this.state = ''; + } + cb(); + }, 10); + }; + + pt._flush = function(cb) { + // Just output whatever we have. + pt.push(Buffer.from(this.state)); + this.state = ''; + cb(); + }; + + pt.write(Buffer.from('aaaa')); + pt.write(Buffer.from('bbbb')); + pt.write(Buffer.from('cccc')); + pt.write(Buffer.from('dddd')); + pt.write(Buffer.from('eeee')); + pt.write(Buffer.from('aaaa')); + pt.write(Buffer.from('bbbb')); + pt.write(Buffer.from('cccc')); + pt.write(Buffer.from('dddd')); + pt.write(Buffer.from('eeee')); + pt.write(Buffer.from('aaaa')); + pt.write(Buffer.from('bbbb')); + pt.write(Buffer.from('cccc')); + pt.write(Buffer.from('dddd')); + pt.end(); + + // 'abcdeabcdeabcd' + pt.on('finish', common.mustCall(function() { + assert.strictEqual(pt.read(5).toString(), 'abcde'); + assert.strictEqual(pt.read(5).toString(), 'abcde'); + assert.strictEqual(pt.read(5).toString(), 'abcd'); + })); +} + +// This tests for a stall when data is written to a full stream +// that has empty transforms. +{ + // Verify complex transform behavior + let count = 0; + let saved = null; + const pt = new Transform({ highWaterMark: 3 }); + pt._transform = function(c, e, cb) { + if (count++ === 1) + saved = c; + else { + if (saved) { + pt.push(saved); + saved = null; + } + pt.push(c); + } + + cb(); + }; + + pt.once('readable', function() { + process.nextTick(function() { + pt.write(Buffer.from('d')); + pt.write(Buffer.from('ef'), common.mustCall(function() { + pt.end(); + })); + assert.strictEqual(pt.read().toString(), 'abcdef'); + assert.strictEqual(pt.read(), null); + }); + }); + + pt.write(Buffer.from('abc')); +} + + +{ + // Verify passthrough event emission + const pt = new PassThrough(); + let emits = 0; + pt.on('readable', function() { + emits++; + }); + + pt.write(Buffer.from('foog')); + pt.write(Buffer.from('bark')); + + assert.strictEqual(emits, 0); + assert.strictEqual(pt.read(5).toString(), 'foogb'); + assert.strictEqual(String(pt.read(5)), 'null'); + assert.strictEqual(emits, 0); + + pt.write(Buffer.from('bazy')); + pt.write(Buffer.from('kuel')); + + assert.strictEqual(emits, 0); + assert.strictEqual(pt.read(5).toString(), 'arkba'); + assert.strictEqual(pt.read(5).toString(), 'zykue'); + assert.strictEqual(pt.read(5), null); + + pt.end(); + + assert.strictEqual(emits, 1); + assert.strictEqual(pt.read(5).toString(), 'l'); + assert.strictEqual(pt.read(5), null); + assert.strictEqual(emits, 1); +} + +{ + // Verify passthrough event emission reordering + const pt = new PassThrough(); + let emits = 0; + pt.on('readable', function() { + emits++; + }); + + pt.write(Buffer.from('foog')); + pt.write(Buffer.from('bark')); + + assert.strictEqual(emits, 0); + assert.strictEqual(pt.read(5).toString(), 'foogb'); + assert.strictEqual(pt.read(5), null); + + pt.once('readable', common.mustCall(function() { + assert.strictEqual(pt.read(5).toString(), 'arkba'); + assert.strictEqual(pt.read(5), null); + + pt.once('readable', common.mustCall(function() { + assert.strictEqual(pt.read(5).toString(), 'zykue'); + assert.strictEqual(pt.read(5), null); + pt.once('readable', common.mustCall(function() { + assert.strictEqual(pt.read(5).toString(), 'l'); + assert.strictEqual(pt.read(5), null); + assert.strictEqual(emits, 3); + })); + pt.end(); + })); + pt.write(Buffer.from('kuel')); + })); + + pt.write(Buffer.from('bazy')); +} + +{ + // Verify passthrough facade + const pt = new PassThrough(); + const datas = []; + pt.on('data', function(chunk) { + datas.push(chunk.toString()); + }); + + pt.on('end', common.mustCall(function() { + assert.deepStrictEqual(datas, ['foog', 'bark', 'bazy', 'kuel']); + })); + + pt.write(Buffer.from('foog')); + setTimeout(function() { + pt.write(Buffer.from('bark')); + setTimeout(function() { + pt.write(Buffer.from('bazy')); + setTimeout(function() { + pt.write(Buffer.from('kuel')); + setTimeout(function() { + pt.end(); + }, 10); + }, 10); + }, 10); + }, 10); +} + +{ + // Verify object transform (JSON parse) + const jp = new Transform({ objectMode: true }); + jp._transform = function(data, encoding, cb) { + try { + jp.push(JSON.parse(data)); + cb(); + } catch (er) { + cb(er); + } + }; + + // Anything except null/undefined is fine. + // those are "magic" in the stream API, because they signal EOF. + const objects = [ + { foo: 'bar' }, + 100, + 'string', + { nested: { things: [ { foo: 'bar' }, 100, 'string' ] } }, + ]; + + let ended = false; + jp.on('end', function() { + ended = true; + }); + + objects.forEach(function(obj) { + jp.write(JSON.stringify(obj)); + const res = jp.read(); + assert.deepStrictEqual(res, obj); + }); + + jp.end(); + // Read one more time to get the 'end' event + jp.read(); + + process.nextTick(common.mustCall(function() { + assert.strictEqual(ended, true); + })); +} + +{ + // Verify object transform (JSON stringify) + const js = new Transform({ objectMode: true }); + js._transform = function(data, encoding, cb) { + try { + js.push(JSON.stringify(data)); + cb(); + } catch (er) { + cb(er); + } + }; + + // Anything except null/undefined is fine. + // those are "magic" in the stream API, because they signal EOF. + const objects = [ + { foo: 'bar' }, + 100, + 'string', + { nested: { things: [ { foo: 'bar' }, 100, 'string' ] } }, + ]; + + let ended = false; + js.on('end', function() { + ended = true; + }); + + objects.forEach(function(obj) { + js.write(obj); + const res = js.read(); + assert.strictEqual(res, JSON.stringify(obj)); + }); + + js.end(); + // Read one more time to get the 'end' event + js.read(); + + process.nextTick(common.mustCall(function() { + assert.strictEqual(ended, true); + })); +} diff --git a/cli/tests/node_compat/test/parallel/test-stream2-unpipe-drain.js b/cli/tests/node_compat/test/parallel/test-stream2-unpipe-drain.js new file mode 100644 index 00000000000000..65420120c2c66e --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-stream2-unpipe-drain.js @@ -0,0 +1,79 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. + +'use strict'; +require('../common'); +const assert = require('assert'); + +const stream = require('stream'); + +class TestWriter extends stream.Writable { + _write(buffer, encoding, callback) { + console.log('write called'); + // Super slow write stream (callback never called) + } +} + +const dest = new TestWriter(); + +class TestReader extends stream.Readable { + constructor() { + super(); + this.reads = 0; + } + + _read(size) { + this.reads += 1; + this.push(Buffer.alloc(size)); + } +} + +const src1 = new TestReader(); +const src2 = new TestReader(); + +src1.pipe(dest); + +src1.once('readable', () => { + process.nextTick(() => { + + src2.pipe(dest); + + src2.once('readable', () => { + process.nextTick(() => { + + src1.unpipe(dest); + }); + }); + }); +}); + + +process.on('exit', () => { + assert.strictEqual(src1.reads, 2); + assert.strictEqual(src2.reads, 2); +}); diff --git a/cli/tests/node_compat/test/parallel/test-stream2-unpipe-leak.js b/cli/tests/node_compat/test/parallel/test-stream2-unpipe-leak.js new file mode 100644 index 00000000000000..ddafb5a9ce5db7 --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-stream2-unpipe-leak.js @@ -0,0 +1,80 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. + +'use strict'; +require('../common'); +const assert = require('assert'); +const stream = require('stream'); + +const chunk = Buffer.from('hallo'); + +class TestWriter extends stream.Writable { + _write(buffer, encoding, callback) { + callback(null); + } +} + +const dest = new TestWriter(); + +// Set this high so that we'd trigger a nextTick warning +// and/or RangeError if we do maybeReadMore wrong. +class TestReader extends stream.Readable { + constructor() { + super({ + highWaterMark: 0x10000 + }); + } + + _read(size) { + this.push(chunk); + } +} + +const src = new TestReader(); + +for (let i = 0; i < 10; i++) { + src.pipe(dest); + src.unpipe(dest); +} + +assert.strictEqual(src.listeners('end').length, 0); +assert.strictEqual(src.listeners('readable').length, 0); + +assert.strictEqual(dest.listeners('unpipe').length, 0); +assert.strictEqual(dest.listeners('drain').length, 0); +assert.strictEqual(dest.listeners('error').length, 0); +assert.strictEqual(dest.listeners('close').length, 0); +assert.strictEqual(dest.listeners('finish').length, 0); + +console.error(src._readableState); +process.on('exit', function() { + src.readableBuffer.length = 0; + console.error(src._readableState); + assert(src.readableLength >= src.readableHighWaterMark); + console.log('ok'); +}); diff --git a/cli/tests/node_compat/test/parallel/test-stream2-writable.js b/cli/tests/node_compat/test/parallel/test-stream2-writable.js new file mode 100644 index 00000000000000..7ffa829698a3a6 --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-stream2-writable.js @@ -0,0 +1,466 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. + +'use strict'; + +const common = require('../common'); +const { Writable: W, Duplex: D } = require('stream'); +const assert = require('assert'); + +class TestWriter extends W { + constructor(opts) { + super(opts); + this.buffer = []; + this.written = 0; + } + + _write(chunk, encoding, cb) { + // Simulate a small unpredictable latency + setTimeout(() => { + this.buffer.push(chunk.toString()); + this.written += chunk.length; + cb(); + }, Math.floor(Math.random() * 10)); + } +} + +const chunks = new Array(50); +for (let i = 0; i < chunks.length; i++) { + chunks[i] = 'x'.repeat(i); +} + +{ + // Verify fast writing + const tw = new TestWriter({ + highWaterMark: 100 + }); + + tw.on('finish', common.mustCall(function() { + // Got chunks in the right order + assert.deepStrictEqual(tw.buffer, chunks); + })); + + chunks.forEach(function(chunk) { + // Ignore backpressure. Just buffer it all up. + tw.write(chunk); + }); + tw.end(); +} + +{ + // Verify slow writing + const tw = new TestWriter({ + highWaterMark: 100 + }); + + tw.on('finish', common.mustCall(function() { + // Got chunks in the right order + assert.deepStrictEqual(tw.buffer, chunks); + })); + + let i = 0; + (function W() { + tw.write(chunks[i++]); + if (i < chunks.length) + setTimeout(W, 10); + else + tw.end(); + })(); +} + +{ + // Verify write backpressure + const tw = new TestWriter({ + highWaterMark: 50 + }); + + let drains = 0; + + tw.on('finish', common.mustCall(function() { + // Got chunks in the right order + assert.deepStrictEqual(tw.buffer, chunks); + assert.strictEqual(drains, 17); + })); + + tw.on('drain', function() { + drains++; + }); + + let i = 0; + (function W() { + let ret; + do { + ret = tw.write(chunks[i++]); + } while (ret !== false && i < chunks.length); + + if (i < chunks.length) { + assert(tw.writableLength >= 50); + tw.once('drain', W); + } else { + tw.end(); + } + })(); +} + +{ + // Verify write buffersize + const tw = new TestWriter({ + highWaterMark: 100 + }); + + const encodings = + [ 'hex', + 'utf8', + 'utf-8', + 'ascii', + 'latin1', + 'binary', + 'base64', + 'ucs2', + 'ucs-2', + 'utf16le', + 'utf-16le', + undefined ]; + + tw.on('finish', function() { + // Got the expected chunks + assert.deepStrictEqual(tw.buffer, chunks); + }); + + chunks.forEach(function(chunk, i) { + const enc = encodings[i % encodings.length]; + chunk = Buffer.from(chunk); + tw.write(chunk.toString(enc), enc); + }); +} + +{ + // Verify write with no buffersize + const tw = new TestWriter({ + highWaterMark: 100, + decodeStrings: false + }); + + tw._write = function(chunk, encoding, cb) { + assert.strictEqual(typeof chunk, 'string'); + chunk = Buffer.from(chunk, encoding); + return TestWriter.prototype._write.call(this, chunk, encoding, cb); + }; + + const encodings = + [ 'hex', + 'utf8', + 'utf-8', + 'ascii', + 'latin1', + 'binary', + 'base64', + 'ucs2', + 'ucs-2', + 'utf16le', + 'utf-16le', + undefined ]; + + tw.on('finish', function() { + // Got the expected chunks + assert.deepStrictEqual(tw.buffer, chunks); + }); + + chunks.forEach(function(chunk, i) { + const enc = encodings[i % encodings.length]; + chunk = Buffer.from(chunk); + tw.write(chunk.toString(enc), enc); + }); +} + +{ + // Verify write callbacks + const callbacks = chunks.map(function(chunk, i) { + return [i, function() { + callbacks._called[i] = chunk; + }]; + }).reduce(function(set, x) { + set[`callback-${x[0]}`] = x[1]; + return set; + }, {}); + callbacks._called = []; + + const tw = new TestWriter({ + highWaterMark: 100 + }); + + tw.on('finish', common.mustCall(function() { + process.nextTick(common.mustCall(function() { + // Got chunks in the right order + assert.deepStrictEqual(tw.buffer, chunks); + // Called all callbacks + assert.deepStrictEqual(callbacks._called, chunks); + })); + })); + + chunks.forEach(function(chunk, i) { + tw.write(chunk, callbacks[`callback-${i}`]); + }); + tw.end(); +} + +{ + // Verify end() callback + const tw = new TestWriter(); + tw.end(common.mustCall()); +} + +const helloWorldBuffer = Buffer.from('hello world'); + +{ + // Verify end() callback with chunk + const tw = new TestWriter(); + tw.end(helloWorldBuffer, common.mustCall()); +} + +{ + // Verify end() callback with chunk and encoding + const tw = new TestWriter(); + tw.end('hello world', 'ascii', common.mustCall()); +} + +{ + // Verify end() callback after write() call + const tw = new TestWriter(); + tw.write(helloWorldBuffer); + tw.end(common.mustCall()); +} + +{ + // Verify end() callback after write() callback + const tw = new TestWriter(); + let writeCalledback = false; + tw.write(helloWorldBuffer, function() { + writeCalledback = true; + }); + tw.end(common.mustCall(function() { + assert.strictEqual(writeCalledback, true); + })); +} + +{ + // Verify encoding is ignored for buffers + const tw = new W(); + const hex = '018b5e9a8f6236ffe30e31baf80d2cf6eb'; + tw._write = common.mustCall(function(chunk) { + assert.strictEqual(chunk.toString('hex'), hex); + }); + const buf = Buffer.from(hex, 'hex'); + tw.write(buf, 'latin1'); +} + +{ + // Verify writables cannot be piped + const w = new W({ autoDestroy: false }); + w._write = common.mustNotCall(); + let gotError = false; + w.on('error', function() { + gotError = true; + }); + w.pipe(process.stdout); + assert.strictEqual(gotError, true); +} + +{ + // Verify that duplex streams cannot be piped + const d = new D(); + d._read = common.mustCall(); + d._write = common.mustNotCall(); + let gotError = false; + d.on('error', function() { + gotError = true; + }); + d.pipe(process.stdout); + assert.strictEqual(gotError, false); +} + +{ + // Verify that end(chunk) twice is an error + const w = new W(); + w._write = common.mustCall((msg) => { + assert.strictEqual(msg.toString(), 'this is the end'); + }); + let gotError = false; + w.on('error', function(er) { + gotError = true; + assert.strictEqual(er.message, 'write after end'); + }); + w.end('this is the end'); + w.end('and so is this'); + process.nextTick(common.mustCall(function() { + assert.strictEqual(gotError, true); + })); +} + +{ + // Verify stream doesn't end while writing + const w = new W(); + let wrote = false; + w._write = function(chunk, e, cb) { + assert.strictEqual(this.writing, undefined); + wrote = true; + this.writing = true; + setTimeout(() => { + this.writing = false; + cb(); + }, 1); + }; + w.on('finish', common.mustCall(function() { + assert.strictEqual(wrote, true); + assert.strictEqual(this.writing, false); + })); + w.write(Buffer.alloc(0)); + w.end(); +} + +{ + // Verify finish does not come before write() callback + const w = new W(); + let writeCb = false; + w._write = function(chunk, e, cb) { + setTimeout(function() { + writeCb = true; + cb(); + }, 10); + }; + w.on('finish', common.mustCall(function() { + assert.strictEqual(writeCb, true); + })); + w.write(Buffer.alloc(0)); + w.end(); +} + +{ + // Verify finish does not come before synchronous _write() callback + const w = new W(); + let writeCb = false; + w._write = function(chunk, e, cb) { + cb(); + }; + w.on('finish', common.mustCall(function() { + assert.strictEqual(writeCb, true); + })); + w.write(Buffer.alloc(0), function() { + writeCb = true; + }); + w.end(); +} + +{ + // Verify finish is emitted if the last chunk is empty + const w = new W(); + w._write = function(chunk, e, cb) { + process.nextTick(cb); + }; + w.on('finish', common.mustCall()); + w.write(Buffer.allocUnsafe(1)); + w.end(Buffer.alloc(0)); +} + +{ + // Verify that finish is emitted after shutdown + const w = new W(); + let shutdown = false; + + w._final = common.mustCall(function(cb) { + assert.strictEqual(this, w); + setTimeout(function() { + shutdown = true; + cb(); + }, 100); + }); + w._write = function(chunk, e, cb) { + process.nextTick(cb); + }; + w.on('finish', common.mustCall(function() { + assert.strictEqual(shutdown, true); + })); + w.write(Buffer.allocUnsafe(1)); + w.end(Buffer.allocUnsafe(0)); +} + +{ + // Verify that error is only emitted once when failing in _finish. + const w = new W(); + + w._final = common.mustCall(function(cb) { + cb(new Error('test')); + }); + w.on('error', common.mustCall((err) => { + assert.strictEqual(w._writableState.errorEmitted, true); + assert.strictEqual(err.message, 'test'); + w.on('error', common.mustNotCall()); + w.destroy(new Error()); + })); + w.end(); +} + +{ + // Verify that error is only emitted once when failing in write. + const w = new W(); + w.on('error', common.mustNotCall()); + assert.throws(() => { + w.write(null); + }, { + code: 'ERR_STREAM_NULL_VALUES' + }); +} + +{ + // Verify that error is only emitted once when failing in write after end. + const w = new W(); + w.on('error', common.mustCall((err) => { + assert.strictEqual(w._writableState.errorEmitted, true); + assert.strictEqual(err.code, 'ERR_STREAM_WRITE_AFTER_END'); + })); + w.end(); + w.write('hello'); + w.destroy(new Error()); +} + +{ + // Verify that finish is not emitted after error + const w = new W(); + + w._final = common.mustCall(function(cb) { + cb(new Error()); + }); + w._write = function(chunk, e, cb) { + process.nextTick(cb); + }; + w.on('error', common.mustCall()); + w.on('prefinish', common.mustNotCall()); + w.on('finish', common.mustNotCall()); + w.write(Buffer.allocUnsafe(1)); + w.end(Buffer.allocUnsafe(0)); +} diff --git a/cli/tests/node_compat/test/parallel/test-stream3-cork-end.js b/cli/tests/node_compat/test/parallel/test-stream3-cork-end.js new file mode 100644 index 00000000000000..3ada5dad8bf415 --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-stream3-cork-end.js @@ -0,0 +1,98 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +'use strict'; +require('../common'); +const assert = require('assert'); +const stream = require('stream'); +const Writable = stream.Writable; + +// Test the buffering behavior of Writable streams. +// +// The call to cork() triggers storing chunks which are flushed +// on calling end() and the stream subsequently ended. +// +// node version target: 0.12 + +const expectedChunks = ['please', 'buffer', 'me', 'kindly']; +const inputChunks = expectedChunks.slice(0); +let seenChunks = []; +let seenEnd = false; + +const w = new Writable(); +// Let's arrange to store the chunks. +w._write = function(chunk, encoding, cb) { + // Stream end event is not seen before the last write. + assert.ok(!seenEnd); + // Default encoding given none was specified. + assert.strictEqual(encoding, 'buffer'); + + seenChunks.push(chunk); + cb(); +}; +// Let's record the stream end event. +w.on('finish', () => { + seenEnd = true; +}); + +function writeChunks(remainingChunks, callback) { + const writeChunk = remainingChunks.shift(); + let writeState; + + if (writeChunk) { + setImmediate(() => { + writeState = w.write(writeChunk); + // We were not told to stop writing. + assert.ok(writeState); + + writeChunks(remainingChunks, callback); + }); + } else { + callback(); + } +} + +// Do an initial write. +w.write('stuff'); +// The write was immediate. +assert.strictEqual(seenChunks.length, 1); +// Reset the seen chunks. +seenChunks = []; + +// Trigger stream buffering. +w.cork(); + +// Write the bufferedChunks. +writeChunks(inputChunks, () => { + // Should not have seen anything yet. + assert.strictEqual(seenChunks.length, 0); + + // Trigger flush and ending the stream. + w.end(); + + // Stream should not ended in current tick. + assert.ok(!seenEnd); + + // Buffered bytes should be seen in current tick. + assert.strictEqual(seenChunks.length, 4); + + // Did the chunks match. + for (let i = 0, l = expectedChunks.length; i < l; i++) { + const seen = seenChunks[i]; + // There was a chunk. + assert.ok(seen); + + const expected = Buffer.from(expectedChunks[i]); + // It was what we expected. + assert.ok(seen.equals(expected)); + } + + setImmediate(() => { + // Stream should have ended in next tick. + assert.ok(seenEnd); + }); +}); diff --git a/cli/tests/node_compat/test/parallel/test-stream3-cork-uncork.js b/cli/tests/node_compat/test/parallel/test-stream3-cork-uncork.js new file mode 100644 index 00000000000000..eb2365b0146838 --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-stream3-cork-uncork.js @@ -0,0 +1,93 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +'use strict'; +require('../common'); +const assert = require('assert'); +const stream = require('stream'); +const Writable = stream.Writable; + +// Test the buffering behavior of Writable streams. +// +// The call to cork() triggers storing chunks which are flushed +// on calling uncork() in the same tick. +// +// node version target: 0.12 + +const expectedChunks = ['please', 'buffer', 'me', 'kindly']; +const inputChunks = expectedChunks.slice(0); +let seenChunks = []; +let seenEnd = false; + +const w = new Writable(); +// Let's arrange to store the chunks. +w._write = function(chunk, encoding, cb) { + // Default encoding given none was specified. + assert.strictEqual(encoding, 'buffer'); + + seenChunks.push(chunk); + cb(); +}; +// Let's record the stream end event. +w.on('finish', () => { + seenEnd = true; +}); + +function writeChunks(remainingChunks, callback) { + const writeChunk = remainingChunks.shift(); + let writeState; + + if (writeChunk) { + setImmediate(() => { + writeState = w.write(writeChunk); + // We were not told to stop writing. + assert.ok(writeState); + + writeChunks(remainingChunks, callback); + }); + } else { + callback(); + } +} + +// Do an initial write. +w.write('stuff'); +// The write was immediate. +assert.strictEqual(seenChunks.length, 1); +// Reset the chunks seen so far. +seenChunks = []; + +// Trigger stream buffering. +w.cork(); + +// Write the bufferedChunks. +writeChunks(inputChunks, () => { + // Should not have seen anything yet. + assert.strictEqual(seenChunks.length, 0); + + // Trigger writing out the buffer. + w.uncork(); + + // Buffered bytes should be seen in current tick. + assert.strictEqual(seenChunks.length, 4); + + // Did the chunks match. + for (let i = 0, l = expectedChunks.length; i < l; i++) { + const seen = seenChunks[i]; + // There was a chunk. + assert.ok(seen); + + const expected = Buffer.from(expectedChunks[i]); + // It was what we expected. + assert.ok(seen.equals(expected)); + } + + setImmediate(() => { + // The stream should not have been ended. + assert.ok(!seenEnd); + }); +}); diff --git a/cli/tests/node_compat/test/parallel/test-stream3-pause-then-read.js b/cli/tests/node_compat/test/parallel/test-stream3-pause-then-read.js new file mode 100644 index 00000000000000..583e2ff8081033 --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-stream3-pause-then-read.js @@ -0,0 +1,177 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. + +'use strict'; +require('../common'); +const assert = require('assert'); + +const stream = require('stream'); +const Readable = stream.Readable; +const Writable = stream.Writable; + +const totalChunks = 100; +const chunkSize = 99; +const expectTotalData = totalChunks * chunkSize; +let expectEndingData = expectTotalData; + +const r = new Readable({ highWaterMark: 1000 }); +let chunks = totalChunks; +r._read = function(n) { + console.log('_read called', chunks); + if (!(chunks % 2)) + setImmediate(push); + else if (!(chunks % 3)) + process.nextTick(push); + else + push(); +}; + +let totalPushed = 0; +function push() { + const chunk = chunks-- > 0 ? Buffer.alloc(chunkSize, 'x') : null; + if (chunk) { + totalPushed += chunk.length; + } + console.log('chunks', chunks); + r.push(chunk); +} + +read100(); + +// First we read 100 bytes. +function read100() { + readn(100, onData); +} + +function readn(n, then) { + console.error(`read ${n}`); + expectEndingData -= n; + (function read() { + const c = r.read(n); + console.error('c', c); + if (!c) + r.once('readable', read); + else { + assert.strictEqual(c.length, n); + assert(!r.readableFlowing); + then(); + } + })(); +} + +// Then we listen to some data events. +function onData() { + expectEndingData -= 100; + console.error('onData'); + let seen = 0; + r.on('data', function od(c) { + seen += c.length; + if (seen >= 100) { + // Seen enough + r.removeListener('data', od); + r.pause(); + if (seen > 100) { + // Oh no, seen too much! + // Put the extra back. + const diff = seen - 100; + r.unshift(c.slice(c.length - diff)); + console.error('seen too much', seen, diff); + } + + // Nothing should be lost in-between. + setImmediate(pipeLittle); + } + }); +} + +// Just pipe 200 bytes, then unshift the extra and unpipe. +function pipeLittle() { + expectEndingData -= 200; + console.error('pipe a little'); + const w = new Writable(); + let written = 0; + w.on('finish', () => { + assert.strictEqual(written, 200); + setImmediate(read1234); + }); + w._write = function(chunk, encoding, cb) { + written += chunk.length; + if (written >= 200) { + r.unpipe(w); + w.end(); + cb(); + if (written > 200) { + const diff = written - 200; + written -= diff; + r.unshift(chunk.slice(chunk.length - diff)); + } + } else { + setImmediate(cb); + } + }; + r.pipe(w); +} + +// Now read 1234 more bytes. +function read1234() { + readn(1234, resumePause); +} + +function resumePause() { + console.error('resumePause'); + // Don't read anything, just resume and re-pause a whole bunch. + r.resume(); + r.pause(); + r.resume(); + r.pause(); + r.resume(); + r.pause(); + r.resume(); + r.pause(); + r.resume(); + r.pause(); + setImmediate(pipe); +} + + +function pipe() { + console.error('pipe the rest'); + const w = new Writable(); + let written = 0; + w._write = function(chunk, encoding, cb) { + written += chunk.length; + cb(); + }; + w.on('finish', () => { + console.error('written', written, totalPushed); + assert.strictEqual(written, expectEndingData); + assert.strictEqual(totalPushed, expectTotalData); + console.log('ok'); + }); + r.pipe(w); +} diff --git a/cli/tests/node_compat/test/parallel/test-streams-highwatermark.js b/cli/tests/node_compat/test/parallel/test-streams-highwatermark.js new file mode 100644 index 00000000000000..623d7450a44baa --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-streams-highwatermark.js @@ -0,0 +1,94 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +'use strict'; +const common = require('../common'); + +const assert = require('assert'); +const stream = require('stream'); +const { inspect } = require('util'); + +{ + // This test ensures that the stream implementation correctly handles values + // for highWaterMark which exceed the range of signed 32 bit integers and + // rejects invalid values. + + // This number exceeds the range of 32 bit integer arithmetic but should still + // be handled correctly. + const ovfl = Number.MAX_SAFE_INTEGER; + + const readable = stream.Readable({ highWaterMark: ovfl }); + assert.strictEqual(readable._readableState.highWaterMark, ovfl); + + const writable = stream.Writable({ highWaterMark: ovfl }); + assert.strictEqual(writable._writableState.highWaterMark, ovfl); + + for (const invalidHwm of [true, false, '5', {}, -5, NaN]) { + for (const type of [stream.Readable, stream.Writable]) { + assert.throws(() => { + type({ highWaterMark: invalidHwm }); + }, { + name: 'TypeError', + code: 'ERR_INVALID_ARG_VALUE', + message: "The property 'options.highWaterMark' is invalid. " + + `Received ${inspect(invalidHwm)}` + }); + } + } +} + +{ + // This test ensures that the push method's implementation + // correctly handles the edge case where the highWaterMark and + // the state.length are both zero + + const readable = stream.Readable({ highWaterMark: 0 }); + + for (let i = 0; i < 3; i++) { + const needMoreData = readable.push(); + assert.strictEqual(needMoreData, true); + } +} + +{ + // This test ensures that the read(n) method's implementation + // correctly handles the edge case where the highWaterMark, state.length + // and n are all zero + + const readable = stream.Readable({ highWaterMark: 0 }); + + readable._read = common.mustCall(); + readable.read(0); +} + +{ + // Parse size as decimal integer + ['1', '1.0', 1].forEach((size) => { + const readable = new stream.Readable({ + read: common.mustCall(), + highWaterMark: 0, + }); + readable.read(size); + + assert.strictEqual(readable._readableState.highWaterMark, Number(size)); + }); +} + +{ + // Test highwatermark limit + const hwm = 0x40000000 + 1; + const readable = stream.Readable({ + read() {}, + }); + + assert.throws(() => readable.read(hwm), common.expectsError({ + code: 'ERR_OUT_OF_RANGE', + message: 'The value of "size" is out of range.' + + ' It must be <= 1GiB. Received ' + + hwm, + })); +} diff --git a/cli/tests/node_compat/test/parallel/test-timers-api-refs.js b/cli/tests/node_compat/test/parallel/test-timers-api-refs.js new file mode 100644 index 00000000000000..091b69f9f18511 --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-timers-api-refs.js @@ -0,0 +1,28 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +'use strict'; +const common = require('../common'); +const timers = require('timers'); + +// Delete global APIs to make sure they're not relied on by the internal timers +// code +delete global.setTimeout; +delete global.clearTimeout; +delete global.setInterval; +delete global.clearInterval; +delete global.setImmediate; +delete global.clearImmediate; + +const timeoutCallback = () => { timers.clearTimeout(timeout); }; +const timeout = timers.setTimeout(common.mustCall(timeoutCallback), 1); + +const intervalCallback = () => { timers.clearInterval(interval); }; +const interval = timers.setInterval(common.mustCall(intervalCallback), 1); + +const immediateCallback = () => { timers.clearImmediate(immediate); }; +const immediate = timers.setImmediate(immediateCallback); diff --git a/cli/tests/node_compat/test/parallel/test-timers-args.js b/cli/tests/node_compat/test/parallel/test-timers-args.js new file mode 100644 index 00000000000000..d9a77bbc27d468 --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-timers-args.js @@ -0,0 +1,38 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +'use strict'; +require('../common'); +const assert = require('assert'); + +function range(n) { + return 'x'.repeat(n + 1).split('').map(function(_, i) { return i; }); +} + +function timeout(nargs) { + const args = range(nargs); + setTimeout.apply(null, [callback, 1].concat(args)); + + function callback() { + assert.deepStrictEqual([].slice.call(arguments), args); + if (nargs < 128) timeout(nargs + 1); + } +} + +function interval(nargs) { + const args = range(nargs); + const timer = setTimeout.apply(null, [callback, 1].concat(args)); + + function callback() { + clearInterval(timer); + assert.deepStrictEqual([].slice.call(arguments), args); + if (nargs < 128) interval(nargs + 1); + } +} + +timeout(0); +interval(0); diff --git a/cli/tests/node_compat/test/parallel/test-timers-clear-null-does-not-throw-error.js b/cli/tests/node_compat/test/parallel/test-timers-clear-null-does-not-throw-error.js new file mode 100644 index 00000000000000..ab0335d23ce161 --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-timers-clear-null-does-not-throw-error.js @@ -0,0 +1,18 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +'use strict'; +require('../common'); + +// This test makes sure clearing timers with +// 'null' or no input does not throw error +clearInterval(null); +clearInterval(); +clearTimeout(null); +clearTimeout(); +clearImmediate(null); +clearImmediate(); diff --git a/cli/tests/node_compat/test/parallel/test-timers-clear-object-does-not-throw-error.js b/cli/tests/node_compat/test/parallel/test-timers-clear-object-does-not-throw-error.js new file mode 100644 index 00000000000000..5df30724066a87 --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-timers-clear-object-does-not-throw-error.js @@ -0,0 +1,15 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +'use strict'; +require('../common'); + +// This test makes sure clearing timers with +// objects doesn't throw +clearImmediate({}); +clearTimeout({}); +clearInterval({}); diff --git a/cli/tests/node_compat/test/parallel/test-timers-clear-timeout-interval-equivalent.js b/cli/tests/node_compat/test/parallel/test-timers-clear-timeout-interval-equivalent.js new file mode 100644 index 00000000000000..5620ad1a007978 --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-timers-clear-timeout-interval-equivalent.js @@ -0,0 +1,25 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +'use strict'; +const common = require('../common'); + +// This test makes sure that timers created with setTimeout can be disarmed by +// clearInterval and that timers created with setInterval can be disarmed by +// clearTimeout. +// +// This behavior is documented in the HTML Living Standard: +// +// * Refs: https://html.spec.whatwg.org/multipage/timers-and-user-prompts.html#dom-setinterval + +// Disarm interval with clearTimeout. +const interval = setInterval(common.mustNotCall(), 1); +clearTimeout(interval); + +// Disarm timeout with clearInterval. +const timeout = setTimeout(common.mustNotCall(), 1); +clearInterval(timeout); diff --git a/cli/tests/node_compat/test/parallel/test-timers-clearImmediate.js b/cli/tests/node_compat/test/parallel/test-timers-clearImmediate.js new file mode 100644 index 00000000000000..c6e55cf6b8204e --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-timers-clearImmediate.js @@ -0,0 +1,20 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +'use strict'; +const common = require('../common'); + +const N = 3; + +function next() { + const fn = common.mustCall(() => clearImmediate(immediate)); + const immediate = setImmediate(fn); +} + +for (let i = 0; i < N; i++) { + next(); +} diff --git a/cli/tests/node_compat/test/parallel/test-timers-interval-throw.js b/cli/tests/node_compat/test/parallel/test-timers-interval-throw.js new file mode 100644 index 00000000000000..f5db9f62d73184 --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-timers-interval-throw.js @@ -0,0 +1,24 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +'use strict'; + +const common = require('../common'); +const assert = require('assert'); + +// To match browser behaviour, interval should continue +// being rescheduled even if it throws. + +let count = 2; +const interval = setInterval(() => { throw new Error('IntervalError'); }, 1); + +process.on('uncaughtException', common.mustCall((err) => { + assert.strictEqual(err.message, 'IntervalError'); + if (--count === 0) { + clearInterval(interval); + } +}, 2)); diff --git a/cli/tests/node_compat/test/parallel/test-timers-non-integer-delay.js b/cli/tests/node_compat/test/parallel/test-timers-non-integer-delay.js new file mode 100644 index 00000000000000..158870f1a3a6a5 --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-timers-non-integer-delay.js @@ -0,0 +1,88 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. + +'use strict'; +const common = require('../common'); +const assert = require('assert'); + +// This test makes sure that non-integer timer delays do not make the process +// hang. See https://github.com/joyent/node/issues/8065 and +// https://github.com/joyent/node/issues/8068 which have been fixed by +// https://github.com/joyent/node/pull/8073. +// +// If the process hangs, this test will make the tests suite timeout, +// otherwise it will exit very quickly (after 50 timers with a short delay +// fire). +// +// We have to set at least several timers with a non-integer delay to +// reproduce the issue. Sometimes, a timer with a non-integer delay will +// expire correctly. 50 timers has always been more than enough to reproduce +// it 100%. + +const TIMEOUT_DELAY = 1.1; +let N = 50; + +const interval = setInterval(common.mustCall(() => { + if (--N === 0) { + clearInterval(interval); + } +}, N), TIMEOUT_DELAY); + +// Test non-integer delay ordering +{ + const ordering = []; + + setTimeout(common.mustCall(() => { + ordering.push(1); + }), 1); + + setTimeout(common.mustCall(() => { + ordering.push(2); + }), 1.8); + + setTimeout(common.mustCall(() => { + ordering.push(3); + }), 1.1); + + setTimeout(common.mustCall(() => { + ordering.push(4); + }), 1); + + setTimeout(common.mustCall(() => { + const expected = [1, 2, 3, 4]; + + assert.deepStrictEqual( + ordering, + expected, + `Non-integer delay ordering should be ${expected}, but got ${ordering}` + ); + + // 2 should always be last of these delays due to ordering guarantees by + // the implementation. + }), 2); +} diff --git a/cli/tests/node_compat/test/parallel/test-timers-refresh.js b/cli/tests/node_compat/test/parallel/test-timers-refresh.js new file mode 100644 index 00000000000000..d2e49268c6c3fc --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-timers-refresh.js @@ -0,0 +1,109 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +// Flags: --expose-internals + +'use strict'; + +const common = require('../common'); + +const { strictEqual, throws } = require('assert'); +const { setUnrefTimeout } = require('internal/timers'); + +// Schedule the unrefed cases first so that the later case keeps the event loop +// active. + +// Every case in this test relies on implicit sorting within either Node's or +// libuv's timers storage data structures. + +// unref()'d timer +{ + let called = false; + const timer = setTimeout(common.mustCall(() => { + called = true; + }), 1); + timer.unref(); + + // This relies on implicit timers handle sorting within libuv. + + setTimeout(common.mustCall(() => { + strictEqual(called, false, 'unref()\'d timer returned before check'); + }), 1); + + strictEqual(timer.refresh(), timer); +} + +// Should throw with non-functions +{ + [null, true, false, 0, 1, NaN, '', 'foo', {}, Symbol()].forEach((cb) => { + throws( + () => setUnrefTimeout(cb), + { + code: 'ERR_INVALID_ARG_TYPE', + } + ); + }); +} + +// unref pooled timer +{ + let called = false; + const timer = setUnrefTimeout(common.mustCall(() => { + called = true; + }), 1); + + setUnrefTimeout(common.mustCall(() => { + strictEqual(called, false, 'unref pooled timer returned before check'); + }), 1); + + strictEqual(timer.refresh(), timer); +} + +// regular timer +{ + let called = false; + const timer = setTimeout(common.mustCall(() => { + called = true; + }), 1); + + setTimeout(common.mustCall(() => { + strictEqual(called, false, 'pooled timer returned before check'); + }), 1); + + strictEqual(timer.refresh(), timer); +} + +// regular timer +{ + let called = false; + const timer = setTimeout(common.mustCall(() => { + if (!called) { + called = true; + process.nextTick(common.mustCall(() => { + timer.refresh(); + strictEqual(timer.hasRef(), true); + })); + } + }, 2), 1); +} + +// interval +{ + let called = 0; + const timer = setInterval(common.mustCall(() => { + called += 1; + if (called === 2) { + clearInterval(timer); + } + }, 2), 1); + + setTimeout(common.mustCall(() => { + strictEqual(called, 0, 'pooled timer returned before check'); + }), 1); + + strictEqual(timer.refresh(), timer); +} diff --git a/cli/tests/node_compat/test/parallel/test-timers-same-timeout-wrong-list-deleted.js b/cli/tests/node_compat/test/parallel/test-timers-same-timeout-wrong-list-deleted.js new file mode 100644 index 00000000000000..d9d65f1bd0b2a8 --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-timers-same-timeout-wrong-list-deleted.js @@ -0,0 +1,41 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +'use strict'; + +// This is a regression test for https://github.com/nodejs/node/issues/7722. +// +// When nested timers have the same timeout, calling clearTimeout on the +// older timer after it has fired causes the list the newer timer is in +// to be deleted. Since the newer timer was not cleared, it still blocks +// the event loop completing for the duration of its timeout, however, since +// no reference exists to it in its list, it cannot be canceled and its +// callback is not called when the timeout elapses. + +const common = require('../common'); + +const TIMEOUT = common.platformTimeout(100); + +const handle1 = setTimeout(common.mustCall(function() { + // Cause the old TIMEOUT list to be deleted + clearTimeout(handle1); + + // Cause a new list with the same key (TIMEOUT) to be created for this timer + const handle2 = setTimeout(common.mustNotCall(), TIMEOUT); + + setTimeout(common.mustCall(function() { + // Attempt to cancel the second timer. Fix for this bug will keep the + // newer timer from being dereferenced by keeping its list from being + // erroneously deleted. If we are able to cancel the timer successfully, + // the bug is fixed. + clearTimeout(handle2); + }), 1); + + // When this callback completes, `listOnTimeout` should now look at the + // correct list and refrain from removing the new TIMEOUT list which + // contains the reference to the newer timer. +}), TIMEOUT); diff --git a/cli/tests/node_compat/test/parallel/test-timers-timeout-with-non-integer.js b/cli/tests/node_compat/test/parallel/test-timers-timeout-with-non-integer.js new file mode 100644 index 00000000000000..e823f9f51c235e --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-timers-timeout-with-non-integer.js @@ -0,0 +1,22 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +'use strict'; +const common = require('../common'); + +/** + * This test is for https://github.com/nodejs/node/issues/24203 + */ +let count = 50; +const time = 1.00000000000001; +const exec = common.mustCall(() => { + if (--count === 0) { + return; + } + setTimeout(exec, time); +}, count); +exec(); diff --git a/cli/tests/node_compat/test/parallel/test-timers-uncaught-exception.js b/cli/tests/node_compat/test/parallel/test-timers-uncaught-exception.js new file mode 100644 index 00000000000000..11d2f58e8e165e --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-timers-uncaught-exception.js @@ -0,0 +1,46 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. + +'use strict'; +const common = require('../common'); +const assert = require('assert'); +const errorMsg = 'BAM!'; + +// The first timer throws... +setTimeout(common.mustCall(function() { + throw new Error(errorMsg); +}), 1); + +// ...but the second one should still run +setTimeout(common.mustCall(), 1); + +function uncaughtException(err) { + assert.strictEqual(err.message, errorMsg); +} + +process.on('uncaughtException', common.mustCall(uncaughtException)); diff --git a/cli/tests/node_compat/test/parallel/test-timers-unref-throw-then-ref.js b/cli/tests/node_compat/test/parallel/test-timers-unref-throw-then-ref.js new file mode 100644 index 00000000000000..a050640a472f29 --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-timers-unref-throw-then-ref.js @@ -0,0 +1,26 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +'use strict'; +const common = require('../common'); +const assert = require('assert'); + +process.once('uncaughtException', common.mustCall((err) => { + common.expectsError({ + message: 'Timeout Error' + })(err); +})); + +let called = false; +const t = setTimeout(() => { + assert(!called); + called = true; + t.ref(); + throw new Error('Timeout Error'); +}, 1).unref(); + +setTimeout(common.mustCall(), 1); diff --git a/cli/tests/node_compat/test/parallel/test-timers-user-call.js b/cli/tests/node_compat/test/parallel/test-timers-user-call.js new file mode 100644 index 00000000000000..eefc8e6c804e81 --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-timers-user-call.js @@ -0,0 +1,47 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +// Make sure `setTimeout()` and friends don't throw if the user-supplied +// function has .call() and .apply() monkey-patched to undesirable values. + +// Refs: https://github.com/nodejs/node/issues/12956 + +'use strict'; + +const common = require('../common'); + +{ + const fn = common.mustCall(10); + fn.call = 'not a function'; + fn.apply = 'also not a function'; + setTimeout(fn, 1); + setTimeout(fn, 1, 'oneArg'); + setTimeout(fn, 1, 'two', 'args'); + setTimeout(fn, 1, 'three', '(3)', 'args'); + setTimeout(fn, 1, 'more', 'than', 'three', 'args'); + + setImmediate(fn, 1); + setImmediate(fn, 1, 'oneArg'); + setImmediate(fn, 1, 'two', 'args'); + setImmediate(fn, 1, 'three', '(3)', 'args'); + setImmediate(fn, 1, 'more', 'than', 'three', 'args'); +} + +{ + const testInterval = (...args) => { + const fn = common.mustCall(() => { clearInterval(interval); }); + fn.call = 'not a function'; + fn.apply = 'also not a function'; + const interval = setInterval(fn, 1, ...args); + }; + + testInterval(); + testInterval('oneArg'); + testInterval('two', 'args'); + testInterval('three', '(3)', 'args'); + testInterval('more', 'than', 'three', 'args'); +} diff --git a/cli/tests/node_compat/test/parallel/test-timers-zero-timeout.js b/cli/tests/node_compat/test/parallel/test-timers-zero-timeout.js new file mode 100644 index 00000000000000..3ff385d51dd682 --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-timers-zero-timeout.js @@ -0,0 +1,56 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. + +'use strict'; +const common = require('../common'); +const assert = require('assert'); + +// https://github.com/joyent/node/issues/2079 - zero timeout drops extra args +{ + setTimeout(common.mustCall(f), 0, 'foo', 'bar', 'baz'); + setTimeout(() => {}, 0); + + function f(a, b, c) { + assert.strictEqual(a, 'foo'); + assert.strictEqual(b, 'bar'); + assert.strictEqual(c, 'baz'); + } +} + +{ + let ncalled = 3; + + const f = common.mustCall((a, b, c) => { + assert.strictEqual(a, 'foo'); + assert.strictEqual(b, 'bar'); + assert.strictEqual(c, 'baz'); + if (--ncalled === 0) clearTimeout(iv); + }, ncalled); + + const iv = setInterval(f, 0, 'foo', 'bar', 'baz'); +} diff --git a/cli/tests/node_compat/test/parallel/test-tls-connect-simple.js b/cli/tests/node_compat/test/parallel/test-tls-connect-simple.js new file mode 100644 index 00000000000000..01a688f3f5738e --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-tls-connect-simple.js @@ -0,0 +1,69 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. + +'use strict'; +const common = require('../common'); + +if (!common.hasCrypto) + common.skip('missing crypto'); + +const tls = require('tls'); +const fixtures = require('../common/fixtures'); + +let serverConnected = 0; + +const options = { + key: fixtures.readKey('agent1-key.pem'), + cert: fixtures.readKey('agent1-cert.pem') +}; + +const server = tls.Server(options, common.mustCall(function(socket) { + if (++serverConnected === 2) { + server.close(common.mustCall()); + server.on('close', common.mustCall()); + } +}, 2)); + +server.listen(0, function() { + const client1options = { + port: this.address().port, + rejectUnauthorized: false + }; + const client1 = tls.connect(client1options, common.mustCall(function() { + client1.end(); + })); + + const client2options = { + port: this.address().port, + rejectUnauthorized: false + }; + const client2 = tls.connect(client2options); + client2.on('secureConnect', common.mustCall(function() { + client2.end(); + })); +}); diff --git a/cli/tests/node_compat/test/parallel/test-url-domain-ascii-unicode.js b/cli/tests/node_compat/test/parallel/test-url-domain-ascii-unicode.js new file mode 100644 index 00000000000000..fb622070ba29c4 --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-url-domain-ascii-unicode.js @@ -0,0 +1,38 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +'use strict'; + +const common = require('../common'); +if (!common.hasIntl) + common.skip('missing Intl'); + +const strictEqual = require('assert').strictEqual; +const url = require('url'); + +const domainToASCII = url.domainToASCII; +const domainToUnicode = url.domainToUnicode; + +const domainWithASCII = [ + ['ıíd', 'xn--d-iga7r'], + ['يٴ', 'xn--mhb8f'], + ['www.ϧƽəʐ.com', 'www.xn--cja62apfr6c.com'], + ['новини.com', 'xn--b1amarcd.com'], + ['名がドメイン.com', 'xn--v8jxj3d1dzdz08w.com'], + ['افغانستا.icom.museum', 'xn--mgbaal8b0b9b2b.icom.museum'], + ['الجزائر.icom.fake', 'xn--lgbbat1ad8j.icom.fake'], + ['भारत.org', 'xn--h2brj9c.org'], +]; + +domainWithASCII.forEach((pair) => { + const domain = pair[0]; + const ascii = pair[1]; + const domainConvertedToASCII = domainToASCII(domain); + strictEqual(domainConvertedToASCII, ascii); + const asciiConvertedToUnicode = domainToUnicode(ascii); + strictEqual(asciiConvertedToUnicode, domain); +}); diff --git a/cli/tests/node_compat/test/parallel/test-url-fileurltopath.js b/cli/tests/node_compat/test/parallel/test-url-fileurltopath.js new file mode 100644 index 00000000000000..ae679bb5ae7f7b --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-url-fileurltopath.js @@ -0,0 +1,161 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +'use strict'; +const { isWindows } = require('../common'); +const assert = require('assert'); +const url = require('url'); + +function testInvalidArgs(...args) { + for (const arg of args) { + assert.throws(() => url.fileURLToPath(arg), { + code: 'ERR_INVALID_ARG_TYPE' + }); + } +} + +// Input must be string or URL +testInvalidArgs(null, undefined, 1, {}, true); + +// Input must be a file URL +assert.throws(() => url.fileURLToPath('https://a/b/c'), { + code: 'ERR_INVALID_URL_SCHEME' +}); + +{ + const withHost = new URL('file://host/a'); + + if (isWindows) { + assert.strictEqual(url.fileURLToPath(withHost), '\\\\host\\a'); + } else { + assert.throws(() => url.fileURLToPath(withHost), { + code: 'ERR_INVALID_FILE_URL_HOST' + }); + } +} + +{ + if (isWindows) { + assert.throws(() => url.fileURLToPath('file:///C:/a%2F/'), { + code: 'ERR_INVALID_FILE_URL_PATH' + }); + assert.throws(() => url.fileURLToPath('file:///C:/a%5C/'), { + code: 'ERR_INVALID_FILE_URL_PATH' + }); + assert.throws(() => url.fileURLToPath('file:///?:/'), { + code: 'ERR_INVALID_FILE_URL_PATH' + }); + } else { + assert.throws(() => url.fileURLToPath('file:///a%2F/'), { + code: 'ERR_INVALID_FILE_URL_PATH' + }); + } +} + +{ + let testCases; + if (isWindows) { + testCases = [ + // Lowercase ascii alpha + { path: 'C:\\foo', fileURL: 'file:///C:/foo' }, + // Uppercase ascii alpha + { path: 'C:\\FOO', fileURL: 'file:///C:/FOO' }, + // dir + { path: 'C:\\dir\\foo', fileURL: 'file:///C:/dir/foo' }, + // trailing separator + { path: 'C:\\dir\\', fileURL: 'file:///C:/dir/' }, + // dot + { path: 'C:\\foo.mjs', fileURL: 'file:///C:/foo.mjs' }, + // space + { path: 'C:\\foo bar', fileURL: 'file:///C:/foo%20bar' }, + // question mark + { path: 'C:\\foo?bar', fileURL: 'file:///C:/foo%3Fbar' }, + // number sign + { path: 'C:\\foo#bar', fileURL: 'file:///C:/foo%23bar' }, + // ampersand + { path: 'C:\\foo&bar', fileURL: 'file:///C:/foo&bar' }, + // equals + { path: 'C:\\foo=bar', fileURL: 'file:///C:/foo=bar' }, + // colon + { path: 'C:\\foo:bar', fileURL: 'file:///C:/foo:bar' }, + // semicolon + { path: 'C:\\foo;bar', fileURL: 'file:///C:/foo;bar' }, + // percent + { path: 'C:\\foo%bar', fileURL: 'file:///C:/foo%25bar' }, + // backslash + { path: 'C:\\foo\\bar', fileURL: 'file:///C:/foo/bar' }, + // backspace + { path: 'C:\\foo\bbar', fileURL: 'file:///C:/foo%08bar' }, + // tab + { path: 'C:\\foo\tbar', fileURL: 'file:///C:/foo%09bar' }, + // newline + { path: 'C:\\foo\nbar', fileURL: 'file:///C:/foo%0Abar' }, + // carriage return + { path: 'C:\\foo\rbar', fileURL: 'file:///C:/foo%0Dbar' }, + // latin1 + { path: 'C:\\fóóbàr', fileURL: 'file:///C:/f%C3%B3%C3%B3b%C3%A0r' }, + // Euro sign (BMP code point) + { path: 'C:\\€', fileURL: 'file:///C:/%E2%82%AC' }, + // Rocket emoji (non-BMP code point) + { path: 'C:\\🚀', fileURL: 'file:///C:/%F0%9F%9A%80' }, + // UNC path (see https://docs.microsoft.com/en-us/archive/blogs/ie/file-uris-in-windows) + { path: '\\\\nas\\My Docs\\File.doc', fileURL: 'file://nas/My%20Docs/File.doc' }, + ]; + } else { + testCases = [ + // Lowercase ascii alpha + { path: '/foo', fileURL: 'file:///foo' }, + // Uppercase ascii alpha + { path: '/FOO', fileURL: 'file:///FOO' }, + // dir + { path: '/dir/foo', fileURL: 'file:///dir/foo' }, + // trailing separator + { path: '/dir/', fileURL: 'file:///dir/' }, + // dot + { path: '/foo.mjs', fileURL: 'file:///foo.mjs' }, + // space + { path: '/foo bar', fileURL: 'file:///foo%20bar' }, + // question mark + { path: '/foo?bar', fileURL: 'file:///foo%3Fbar' }, + // number sign + { path: '/foo#bar', fileURL: 'file:///foo%23bar' }, + // ampersand + { path: '/foo&bar', fileURL: 'file:///foo&bar' }, + // equals + { path: '/foo=bar', fileURL: 'file:///foo=bar' }, + // colon + { path: '/foo:bar', fileURL: 'file:///foo:bar' }, + // semicolon + { path: '/foo;bar', fileURL: 'file:///foo;bar' }, + // percent + { path: '/foo%bar', fileURL: 'file:///foo%25bar' }, + // backslash + { path: '/foo\\bar', fileURL: 'file:///foo%5Cbar' }, + // backspace + { path: '/foo\bbar', fileURL: 'file:///foo%08bar' }, + // tab + { path: '/foo\tbar', fileURL: 'file:///foo%09bar' }, + // newline + { path: '/foo\nbar', fileURL: 'file:///foo%0Abar' }, + // carriage return + { path: '/foo\rbar', fileURL: 'file:///foo%0Dbar' }, + // latin1 + { path: '/fóóbàr', fileURL: 'file:///f%C3%B3%C3%B3b%C3%A0r' }, + // Euro sign (BMP code point) + { path: '/€', fileURL: 'file:///%E2%82%AC' }, + // Rocket emoji (non-BMP code point) + { path: '/🚀', fileURL: 'file:///%F0%9F%9A%80' }, + ]; + } + + for (const { path, fileURL } of testCases) { + const fromString = url.fileURLToPath(fileURL); + assert.strictEqual(fromString, path); + const fromURL = url.fileURLToPath(new URL(fileURL)); + assert.strictEqual(fromURL, path); + } +} diff --git a/cli/tests/node_compat/test/parallel/test-url-format-invalid-input.js b/cli/tests/node_compat/test/parallel/test-url-format-invalid-input.js new file mode 100644 index 00000000000000..c42ee2c09f6a89 --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-url-format-invalid-input.js @@ -0,0 +1,34 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +'use strict'; +const common = require('../common'); +const assert = require('assert'); +const url = require('url'); + +const throwsObjsAndReportTypes = [ + undefined, + null, + true, + false, + 0, + function() {}, + Symbol('foo'), +]; + +for (const urlObject of throwsObjsAndReportTypes) { + assert.throws(() => { + url.format(urlObject); + }, { + code: 'ERR_INVALID_ARG_TYPE', + name: 'TypeError', + message: 'The "urlObject" argument must be one of type object or string.' + + common.invalidArgTypeHelper(urlObject) + }); +} +assert.strictEqual(url.format(''), ''); +assert.strictEqual(url.format({}), ''); diff --git a/cli/tests/node_compat/test/parallel/test-url-format-whatwg.js b/cli/tests/node_compat/test/parallel/test-url-format-whatwg.js new file mode 100644 index 00000000000000..ea099f494c4e95 --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-url-format-whatwg.js @@ -0,0 +1,149 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +'use strict'; + +const common = require('../common'); +if (!common.hasIntl) + common.skip('missing Intl'); + +const assert = require('assert'); +const url = require('url'); + +const myURL = new URL('http://user:pass@xn--lck1c3crb1723bpq4a.com/a?a=b#c'); + +assert.strictEqual( + url.format(myURL), + 'http://user:pass@xn--lck1c3crb1723bpq4a.com/a?a=b#c' +); + +assert.strictEqual( + url.format(myURL, {}), + 'http://user:pass@xn--lck1c3crb1723bpq4a.com/a?a=b#c' +); + +{ + [true, 1, 'test', Infinity].forEach((value) => { + assert.throws( + () => url.format(myURL, value), + { + code: 'ERR_INVALID_ARG_TYPE', + name: 'TypeError', + message: 'The "options" argument must be of type object.' + + common.invalidArgTypeHelper(value) + } + ); + }); +} + +// Any falsy value other than undefined will be treated as false. +// Any truthy value will be treated as true. + +assert.strictEqual( + url.format(myURL, { auth: false }), + 'http://xn--lck1c3crb1723bpq4a.com/a?a=b#c' +); + +assert.strictEqual( + url.format(myURL, { auth: '' }), + 'http://xn--lck1c3crb1723bpq4a.com/a?a=b#c' +); + +assert.strictEqual( + url.format(myURL, { auth: 0 }), + 'http://xn--lck1c3crb1723bpq4a.com/a?a=b#c' +); + +assert.strictEqual( + url.format(myURL, { auth: 1 }), + 'http://user:pass@xn--lck1c3crb1723bpq4a.com/a?a=b#c' +); + +assert.strictEqual( + url.format(myURL, { auth: {} }), + 'http://user:pass@xn--lck1c3crb1723bpq4a.com/a?a=b#c' +); + +assert.strictEqual( + url.format(myURL, { fragment: false }), + 'http://user:pass@xn--lck1c3crb1723bpq4a.com/a?a=b' +); + +assert.strictEqual( + url.format(myURL, { fragment: '' }), + 'http://user:pass@xn--lck1c3crb1723bpq4a.com/a?a=b' +); + +assert.strictEqual( + url.format(myURL, { fragment: 0 }), + 'http://user:pass@xn--lck1c3crb1723bpq4a.com/a?a=b' +); + +assert.strictEqual( + url.format(myURL, { fragment: 1 }), + 'http://user:pass@xn--lck1c3crb1723bpq4a.com/a?a=b#c' +); + +assert.strictEqual( + url.format(myURL, { fragment: {} }), + 'http://user:pass@xn--lck1c3crb1723bpq4a.com/a?a=b#c' +); + +assert.strictEqual( + url.format(myURL, { search: false }), + 'http://user:pass@xn--lck1c3crb1723bpq4a.com/a#c' +); + +assert.strictEqual( + url.format(myURL, { search: '' }), + 'http://user:pass@xn--lck1c3crb1723bpq4a.com/a#c' +); + +assert.strictEqual( + url.format(myURL, { search: 0 }), + 'http://user:pass@xn--lck1c3crb1723bpq4a.com/a#c' +); + +assert.strictEqual( + url.format(myURL, { search: 1 }), + 'http://user:pass@xn--lck1c3crb1723bpq4a.com/a?a=b#c' +); + +assert.strictEqual( + url.format(myURL, { search: {} }), + 'http://user:pass@xn--lck1c3crb1723bpq4a.com/a?a=b#c' +); + +assert.strictEqual( + url.format(myURL, { unicode: true }), + 'http://user:pass@理容ナカムラ.com/a?a=b#c' +); + +assert.strictEqual( + url.format(myURL, { unicode: 1 }), + 'http://user:pass@理容ナカムラ.com/a?a=b#c' +); + +assert.strictEqual( + url.format(myURL, { unicode: {} }), + 'http://user:pass@理容ナカムラ.com/a?a=b#c' +); + +assert.strictEqual( + url.format(myURL, { unicode: false }), + 'http://user:pass@xn--lck1c3crb1723bpq4a.com/a?a=b#c' +); + +assert.strictEqual( + url.format(myURL, { unicode: 0 }), + 'http://user:pass@xn--lck1c3crb1723bpq4a.com/a?a=b#c' +); + +assert.strictEqual( + url.format(new URL('http://user:pass@xn--0zwm56d.com:8080/path'), { unicode: true }), + 'http://user:pass@测试.com:8080/path' +); diff --git a/cli/tests/node_compat/test/parallel/test-url-format.js b/cli/tests/node_compat/test/parallel/test-url-format.js new file mode 100644 index 00000000000000..1437bdb879e6db --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-url-format.js @@ -0,0 +1,284 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +'use strict'; +const common = require('../common'); +const assert = require('assert'); +const url = require('url'); + +if (!common.hasIntl) + common.skip('missing Intl'); + +// Formatting tests to verify that it'll format slightly wonky content to a +// valid URL. +const formatTests = { + 'http://example.com?': { + href: 'http://example.com/?', + protocol: 'http:', + slashes: true, + host: 'example.com', + hostname: 'example.com', + search: '?', + query: {}, + pathname: '/' + }, + 'http://example.com?foo=bar#frag': { + href: 'http://example.com/?foo=bar#frag', + protocol: 'http:', + host: 'example.com', + hostname: 'example.com', + hash: '#frag', + search: '?foo=bar', + query: 'foo=bar', + pathname: '/' + }, + 'http://example.com?foo=@bar#frag': { + href: 'http://example.com/?foo=@bar#frag', + protocol: 'http:', + host: 'example.com', + hostname: 'example.com', + hash: '#frag', + search: '?foo=@bar', + query: 'foo=@bar', + pathname: '/' + }, + 'http://example.com?foo=/bar/#frag': { + href: 'http://example.com/?foo=/bar/#frag', + protocol: 'http:', + host: 'example.com', + hostname: 'example.com', + hash: '#frag', + search: '?foo=/bar/', + query: 'foo=/bar/', + pathname: '/' + }, + 'http://example.com?foo=?bar/#frag': { + href: 'http://example.com/?foo=?bar/#frag', + protocol: 'http:', + host: 'example.com', + hostname: 'example.com', + hash: '#frag', + search: '?foo=?bar/', + query: 'foo=?bar/', + pathname: '/' + }, + 'http://example.com#frag=?bar/#frag': { + href: 'http://example.com/#frag=?bar/#frag', + protocol: 'http:', + host: 'example.com', + hostname: 'example.com', + hash: '#frag=?bar/#frag', + pathname: '/' + }, + 'http://google.com" onload="alert(42)/': { + href: 'http://google.com/%22%20onload=%22alert(42)/', + protocol: 'http:', + host: 'google.com', + pathname: '/%22%20onload=%22alert(42)/' + }, + 'http://a.com/a/b/c?s#h': { + href: 'http://a.com/a/b/c?s#h', + protocol: 'http', + host: 'a.com', + pathname: 'a/b/c', + hash: 'h', + search: 's' + }, + 'xmpp:isaacschlueter@jabber.org': { + href: 'xmpp:isaacschlueter@jabber.org', + protocol: 'xmpp:', + host: 'jabber.org', + auth: 'isaacschlueter', + hostname: 'jabber.org' + }, + 'http://atpass:foo%40bar@127.0.0.1/': { + href: 'http://atpass:foo%40bar@127.0.0.1/', + auth: 'atpass:foo@bar', + hostname: '127.0.0.1', + protocol: 'http:', + pathname: '/' + }, + 'http://atslash%2F%40:%2F%40@foo/': { + href: 'http://atslash%2F%40:%2F%40@foo/', + auth: 'atslash/@:/@', + hostname: 'foo', + protocol: 'http:', + pathname: '/' + }, + 'svn+ssh://foo/bar': { + href: 'svn+ssh://foo/bar', + hostname: 'foo', + protocol: 'svn+ssh:', + pathname: '/bar', + slashes: true + }, + 'dash-test://foo/bar': { + href: 'dash-test://foo/bar', + hostname: 'foo', + protocol: 'dash-test:', + pathname: '/bar', + slashes: true + }, + 'dash-test:foo/bar': { + href: 'dash-test:foo/bar', + hostname: 'foo', + protocol: 'dash-test:', + pathname: '/bar' + }, + 'dot.test://foo/bar': { + href: 'dot.test://foo/bar', + hostname: 'foo', + protocol: 'dot.test:', + pathname: '/bar', + slashes: true + }, + 'dot.test:foo/bar': { + href: 'dot.test:foo/bar', + hostname: 'foo', + protocol: 'dot.test:', + pathname: '/bar' + }, + // IPv6 support + 'coap:u:p@[::1]:61616/.well-known/r?n=Temperature': { + href: 'coap:u:p@[::1]:61616/.well-known/r?n=Temperature', + protocol: 'coap:', + auth: 'u:p', + hostname: '::1', + port: '61616', + pathname: '/.well-known/r', + search: 'n=Temperature' + }, + 'coap:[fedc:ba98:7654:3210:fedc:ba98:7654:3210]:61616/s/stopButton': { + href: 'coap:[fedc:ba98:7654:3210:fedc:ba98:7654:3210]:61616/s/stopButton', + protocol: 'coap', + host: '[fedc:ba98:7654:3210:fedc:ba98:7654:3210]:61616', + pathname: '/s/stopButton' + }, + 'http://[::]/': { + href: 'http://[::]/', + protocol: 'http:', + hostname: '[::]', + pathname: '/' + }, + + // Encode context-specific delimiters in path and query, but do not touch + // other non-delimiter chars like `%`. + // + + // `#`,`?` in path + '/path/to/%%23%3F+=&.txt?foo=theA1#bar': { + href: '/path/to/%%23%3F+=&.txt?foo=theA1#bar', + pathname: '/path/to/%#?+=&.txt', + query: { + foo: 'theA1' + }, + hash: '#bar' + }, + + // `#`,`?` in path + `#` in query + '/path/to/%%23%3F+=&.txt?foo=the%231#bar': { + href: '/path/to/%%23%3F+=&.txt?foo=the%231#bar', + pathname: '/path/to/%#?+=&.txt', + query: { + foo: 'the#1' + }, + hash: '#bar' + }, + + // `#` in path end + `#` in query + '/path/to/%%23?foo=the%231#bar': { + href: '/path/to/%%23?foo=the%231#bar', + pathname: '/path/to/%#', + query: { + foo: 'the#1' + }, + hash: '#bar' + }, + + // `?` and `#` in path and search + 'http://ex.com/foo%3F100%m%23r?abc=the%231?&foo=bar#frag': { + href: 'http://ex.com/foo%3F100%m%23r?abc=the%231?&foo=bar#frag', + protocol: 'http:', + hostname: 'ex.com', + hash: '#frag', + search: '?abc=the#1?&foo=bar', + pathname: '/foo?100%m#r', + }, + + // `?` and `#` in search only + 'http://ex.com/fooA100%mBr?abc=the%231?&foo=bar#frag': { + href: 'http://ex.com/fooA100%mBr?abc=the%231?&foo=bar#frag', + protocol: 'http:', + hostname: 'ex.com', + hash: '#frag', + search: '?abc=the#1?&foo=bar', + pathname: '/fooA100%mBr', + }, + + // Multiple `#` in search + 'http://example.com/?foo=bar%231%232%233&abc=%234%23%235#frag': { + href: 'http://example.com/?foo=bar%231%232%233&abc=%234%23%235#frag', + protocol: 'http:', + slashes: true, + host: 'example.com', + hostname: 'example.com', + hash: '#frag', + search: '?foo=bar#1#2#3&abc=#4##5', + query: {}, + pathname: '/' + }, + + // More than 255 characters in hostname which exceeds the limit + [`http://${'a'.repeat(255)}.com/node`]: { + href: 'http:///node', + protocol: 'http:', + slashes: true, + host: '', + hostname: '', + pathname: '/node', + path: '/node' + }, + + // Greater than or equal to 63 characters after `.` in hostname + [`http://www.${'z'.repeat(63)}example.com/node`]: { + href: `http://www.${'z'.repeat(63)}example.com/node`, + protocol: 'http:', + slashes: true, + host: `www.${'z'.repeat(63)}example.com`, + hostname: `www.${'z'.repeat(63)}example.com`, + pathname: '/node', + path: '/node' + }, + + // https://github.com/nodejs/node/issues/3361 + 'file:///home/user': { + href: 'file:///home/user', + protocol: 'file', + pathname: '/home/user', + path: '/home/user' + }, + + // surrogate in auth + 'http://%F0%9F%98%80@www.example.com/': { + href: 'http://%F0%9F%98%80@www.example.com/', + protocol: 'http:', + auth: '\uD83D\uDE00', + hostname: 'www.example.com', + pathname: '/' + } +}; +for (const u in formatTests) { + const expect = formatTests[u].href; + delete formatTests[u].href; + const actual = url.format(u); + const actualObj = url.format(formatTests[u]); + assert.strictEqual(actual, expect, + `wonky format(${u}) == ${expect}\nactual:${actual}`); + assert.strictEqual(actualObj, expect, + `wonky format(${JSON.stringify(formatTests[u])}) == ${ + expect}\nactual: ${actualObj}`); +} diff --git a/cli/tests/node_compat/test/parallel/test-url-parse-format.js b/cli/tests/node_compat/test/parallel/test-url-parse-format.js new file mode 100644 index 00000000000000..7079857bd54d73 --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-url-parse-format.js @@ -0,0 +1,1053 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +'use strict'; +const common = require('../common'); + +if (!common.hasIntl) + common.skip('missing Intl'); + +const assert = require('assert'); +const inspect = require('util').inspect; + +const url = require('url'); + +// URLs to parse, and expected data +// { url : parsed } +const parseTests = { + '//some_path': { + href: '//some_path', + pathname: '//some_path', + path: '//some_path' + }, + + 'http:\\\\evil-phisher\\foo.html#h\\a\\s\\h': { + protocol: 'http:', + slashes: true, + host: 'evil-phisher', + hostname: 'evil-phisher', + pathname: '/foo.html', + path: '/foo.html', + hash: '#h%5Ca%5Cs%5Ch', + href: 'http://evil-phisher/foo.html#h%5Ca%5Cs%5Ch' + }, + + 'http:\\\\evil-phisher\\foo.html?json="\\"foo\\""#h\\a\\s\\h': { + protocol: 'http:', + slashes: true, + host: 'evil-phisher', + hostname: 'evil-phisher', + pathname: '/foo.html', + search: '?json=%22%5C%22foo%5C%22%22', + query: 'json=%22%5C%22foo%5C%22%22', + path: '/foo.html?json=%22%5C%22foo%5C%22%22', + hash: '#h%5Ca%5Cs%5Ch', + href: 'http://evil-phisher/foo.html?json=%22%5C%22foo%5C%22%22#h%5Ca%5Cs%5Ch' + }, + + 'http:\\\\evil-phisher\\foo.html#h\\a\\s\\h?blarg': { + protocol: 'http:', + slashes: true, + host: 'evil-phisher', + hostname: 'evil-phisher', + pathname: '/foo.html', + path: '/foo.html', + hash: '#h%5Ca%5Cs%5Ch?blarg', + href: 'http://evil-phisher/foo.html#h%5Ca%5Cs%5Ch?blarg' + }, + + + 'http:\\\\evil-phisher\\foo.html': { + protocol: 'http:', + slashes: true, + host: 'evil-phisher', + hostname: 'evil-phisher', + pathname: '/foo.html', + path: '/foo.html', + href: 'http://evil-phisher/foo.html' + }, + + 'HTTP://www.example.com/': { + href: 'http://www.example.com/', + protocol: 'http:', + slashes: true, + host: 'www.example.com', + hostname: 'www.example.com', + pathname: '/', + path: '/' + }, + + 'HTTP://www.example.com': { + href: 'http://www.example.com/', + protocol: 'http:', + slashes: true, + host: 'www.example.com', + hostname: 'www.example.com', + pathname: '/', + path: '/' + }, + + 'http://www.ExAmPlE.com/': { + href: 'http://www.example.com/', + protocol: 'http:', + slashes: true, + host: 'www.example.com', + hostname: 'www.example.com', + pathname: '/', + path: '/' + }, + + 'http://user:pw@www.ExAmPlE.com/': { + href: 'http://user:pw@www.example.com/', + protocol: 'http:', + slashes: true, + auth: 'user:pw', + host: 'www.example.com', + hostname: 'www.example.com', + pathname: '/', + path: '/' + }, + + 'http://USER:PW@www.ExAmPlE.com/': { + href: 'http://USER:PW@www.example.com/', + protocol: 'http:', + slashes: true, + auth: 'USER:PW', + host: 'www.example.com', + hostname: 'www.example.com', + pathname: '/', + path: '/' + }, + + 'http://user@www.example.com/': { + href: 'http://user@www.example.com/', + protocol: 'http:', + slashes: true, + auth: 'user', + host: 'www.example.com', + hostname: 'www.example.com', + pathname: '/', + path: '/' + }, + + 'http://user%3Apw@www.example.com/': { + href: 'http://user:pw@www.example.com/', + protocol: 'http:', + slashes: true, + auth: 'user:pw', + host: 'www.example.com', + hostname: 'www.example.com', + pathname: '/', + path: '/' + }, + + 'http://x.com/path?that\'s#all, folks': { + href: 'http://x.com/path?that%27s#all,%20folks', + protocol: 'http:', + slashes: true, + host: 'x.com', + hostname: 'x.com', + search: '?that%27s', + query: 'that%27s', + pathname: '/path', + hash: '#all,%20folks', + path: '/path?that%27s' + }, + + 'HTTP://X.COM/Y': { + href: 'http://x.com/Y', + protocol: 'http:', + slashes: true, + host: 'x.com', + hostname: 'x.com', + pathname: '/Y', + path: '/Y' + }, + + // Whitespace in the front + ' http://www.example.com/': { + href: 'http://www.example.com/', + protocol: 'http:', + slashes: true, + host: 'www.example.com', + hostname: 'www.example.com', + pathname: '/', + path: '/' + }, + + // + not an invalid host character + // per https://url.spec.whatwg.org/#host-parsing + 'http://x.y.com+a/b/c': { + href: 'http://x.y.com+a/b/c', + protocol: 'http:', + slashes: true, + host: 'x.y.com+a', + hostname: 'x.y.com+a', + pathname: '/b/c', + path: '/b/c' + }, + + // An unexpected invalid char in the hostname. + 'HtTp://x.y.cOm;a/b/c?d=e#f gi': { + href: 'http://x.y.com/;a/b/c?d=e#f%20g%3Ch%3Ei', + protocol: 'http:', + slashes: true, + host: 'x.y.com', + hostname: 'x.y.com', + pathname: ';a/b/c', + search: '?d=e', + query: 'd=e', + hash: '#f%20g%3Ch%3Ei', + path: ';a/b/c?d=e' + }, + + // Make sure that we don't accidentally lcast the path parts. + 'HtTp://x.y.cOm;A/b/c?d=e#f gi': { + href: 'http://x.y.com/;A/b/c?d=e#f%20g%3Ch%3Ei', + protocol: 'http:', + slashes: true, + host: 'x.y.com', + hostname: 'x.y.com', + pathname: ';A/b/c', + search: '?d=e', + query: 'd=e', + hash: '#f%20g%3Ch%3Ei', + path: ';A/b/c?d=e' + }, + + 'http://x...y...#p': { + href: 'http://x...y.../#p', + protocol: 'http:', + slashes: true, + host: 'x...y...', + hostname: 'x...y...', + hash: '#p', + pathname: '/', + path: '/' + }, + + 'http://x/p/"quoted"': { + href: 'http://x/p/%22quoted%22', + protocol: 'http:', + slashes: true, + host: 'x', + hostname: 'x', + pathname: '/p/%22quoted%22', + path: '/p/%22quoted%22' + }, + + ' Is a URL!': { + href: '%3Chttp://goo.corn/bread%3E%20Is%20a%20URL!', + pathname: '%3Chttp://goo.corn/bread%3E%20Is%20a%20URL!', + path: '%3Chttp://goo.corn/bread%3E%20Is%20a%20URL!' + }, + + 'http://www.narwhaljs.org/blog/categories?id=news': { + href: 'http://www.narwhaljs.org/blog/categories?id=news', + protocol: 'http:', + slashes: true, + host: 'www.narwhaljs.org', + hostname: 'www.narwhaljs.org', + search: '?id=news', + query: 'id=news', + pathname: '/blog/categories', + path: '/blog/categories?id=news' + }, + + 'http://mt0.google.com/vt/lyrs=m@114&hl=en&src=api&x=2&y=2&z=3&s=': { + href: 'http://mt0.google.com/vt/lyrs=m@114&hl=en&src=api&x=2&y=2&z=3&s=', + protocol: 'http:', + slashes: true, + host: 'mt0.google.com', + hostname: 'mt0.google.com', + pathname: '/vt/lyrs=m@114&hl=en&src=api&x=2&y=2&z=3&s=', + path: '/vt/lyrs=m@114&hl=en&src=api&x=2&y=2&z=3&s=' + }, + + 'http://mt0.google.com/vt/lyrs=m@114???&hl=en&src=api&x=2&y=2&z=3&s=': { + href: 'http://mt0.google.com/vt/lyrs=m@114???&hl=en&src=api' + + '&x=2&y=2&z=3&s=', + protocol: 'http:', + slashes: true, + host: 'mt0.google.com', + hostname: 'mt0.google.com', + search: '???&hl=en&src=api&x=2&y=2&z=3&s=', + query: '??&hl=en&src=api&x=2&y=2&z=3&s=', + pathname: '/vt/lyrs=m@114', + path: '/vt/lyrs=m@114???&hl=en&src=api&x=2&y=2&z=3&s=' + }, + + 'http://user:pass@mt0.google.com/vt/lyrs=m@114???&hl=en&src=api&x=2&y=2&z=3&s=': { + href: 'http://user:pass@mt0.google.com/vt/lyrs=m@114???&hl=en&src=api&x=2&y=2&z=3&s=', + protocol: 'http:', + slashes: true, + host: 'mt0.google.com', + auth: 'user:pass', + hostname: 'mt0.google.com', + search: '???&hl=en&src=api&x=2&y=2&z=3&s=', + query: '??&hl=en&src=api&x=2&y=2&z=3&s=', + pathname: '/vt/lyrs=m@114', + path: '/vt/lyrs=m@114???&hl=en&src=api&x=2&y=2&z=3&s=' + }, + + 'file:///etc/passwd': { + href: 'file:///etc/passwd', + slashes: true, + protocol: 'file:', + pathname: '/etc/passwd', + hostname: '', + host: '', + path: '/etc/passwd' + }, + + 'file://localhost/etc/passwd': { + href: 'file://localhost/etc/passwd', + protocol: 'file:', + slashes: true, + pathname: '/etc/passwd', + hostname: 'localhost', + host: 'localhost', + path: '/etc/passwd' + }, + + 'file://foo/etc/passwd': { + href: 'file://foo/etc/passwd', + protocol: 'file:', + slashes: true, + pathname: '/etc/passwd', + hostname: 'foo', + host: 'foo', + path: '/etc/passwd' + }, + + 'file:///etc/node/': { + href: 'file:///etc/node/', + slashes: true, + protocol: 'file:', + pathname: '/etc/node/', + hostname: '', + host: '', + path: '/etc/node/' + }, + + 'file://localhost/etc/node/': { + href: 'file://localhost/etc/node/', + protocol: 'file:', + slashes: true, + pathname: '/etc/node/', + hostname: 'localhost', + host: 'localhost', + path: '/etc/node/' + }, + + 'file://foo/etc/node/': { + href: 'file://foo/etc/node/', + protocol: 'file:', + slashes: true, + pathname: '/etc/node/', + hostname: 'foo', + host: 'foo', + path: '/etc/node/' + }, + + 'http:/baz/../foo/bar': { + href: 'http:/baz/../foo/bar', + protocol: 'http:', + pathname: '/baz/../foo/bar', + path: '/baz/../foo/bar' + }, + + 'http://user:pass@example.com:8000/foo/bar?baz=quux#frag': { + href: 'http://user:pass@example.com:8000/foo/bar?baz=quux#frag', + protocol: 'http:', + slashes: true, + host: 'example.com:8000', + auth: 'user:pass', + port: '8000', + hostname: 'example.com', + hash: '#frag', + search: '?baz=quux', + query: 'baz=quux', + pathname: '/foo/bar', + path: '/foo/bar?baz=quux' + }, + + '//user:pass@example.com:8000/foo/bar?baz=quux#frag': { + href: '//user:pass@example.com:8000/foo/bar?baz=quux#frag', + slashes: true, + host: 'example.com:8000', + auth: 'user:pass', + port: '8000', + hostname: 'example.com', + hash: '#frag', + search: '?baz=quux', + query: 'baz=quux', + pathname: '/foo/bar', + path: '/foo/bar?baz=quux' + }, + + '/foo/bar?baz=quux#frag': { + href: '/foo/bar?baz=quux#frag', + hash: '#frag', + search: '?baz=quux', + query: 'baz=quux', + pathname: '/foo/bar', + path: '/foo/bar?baz=quux' + }, + + 'http:/foo/bar?baz=quux#frag': { + href: 'http:/foo/bar?baz=quux#frag', + protocol: 'http:', + hash: '#frag', + search: '?baz=quux', + query: 'baz=quux', + pathname: '/foo/bar', + path: '/foo/bar?baz=quux' + }, + + 'mailto:foo@bar.com?subject=hello': { + href: 'mailto:foo@bar.com?subject=hello', + protocol: 'mailto:', + host: 'bar.com', + auth: 'foo', + hostname: 'bar.com', + search: '?subject=hello', + query: 'subject=hello', + path: '?subject=hello' + }, + + 'javascript:alert(\'hello\');': { + href: 'javascript:alert(\'hello\');', + protocol: 'javascript:', + pathname: 'alert(\'hello\');', + path: 'alert(\'hello\');' + }, + + 'xmpp:isaacschlueter@jabber.org': { + href: 'xmpp:isaacschlueter@jabber.org', + protocol: 'xmpp:', + host: 'jabber.org', + auth: 'isaacschlueter', + hostname: 'jabber.org' + }, + + 'http://atpass:foo%40bar@127.0.0.1:8080/path?search=foo#bar': { + href: 'http://atpass:foo%40bar@127.0.0.1:8080/path?search=foo#bar', + protocol: 'http:', + slashes: true, + host: '127.0.0.1:8080', + auth: 'atpass:foo@bar', + hostname: '127.0.0.1', + port: '8080', + pathname: '/path', + search: '?search=foo', + query: 'search=foo', + hash: '#bar', + path: '/path?search=foo' + }, + + 'svn+ssh://foo/bar': { + href: 'svn+ssh://foo/bar', + host: 'foo', + hostname: 'foo', + protocol: 'svn+ssh:', + pathname: '/bar', + path: '/bar', + slashes: true + }, + + 'dash-test://foo/bar': { + href: 'dash-test://foo/bar', + host: 'foo', + hostname: 'foo', + protocol: 'dash-test:', + pathname: '/bar', + path: '/bar', + slashes: true + }, + + 'dash-test:foo/bar': { + href: 'dash-test:foo/bar', + host: 'foo', + hostname: 'foo', + protocol: 'dash-test:', + pathname: '/bar', + path: '/bar' + }, + + 'dot.test://foo/bar': { + href: 'dot.test://foo/bar', + host: 'foo', + hostname: 'foo', + protocol: 'dot.test:', + pathname: '/bar', + path: '/bar', + slashes: true + }, + + 'dot.test:foo/bar': { + href: 'dot.test:foo/bar', + host: 'foo', + hostname: 'foo', + protocol: 'dot.test:', + pathname: '/bar', + path: '/bar' + }, + + // IDNA tests + 'http://www.日本語.com/': { + href: 'http://www.xn--wgv71a119e.com/', + protocol: 'http:', + slashes: true, + host: 'www.xn--wgv71a119e.com', + hostname: 'www.xn--wgv71a119e.com', + pathname: '/', + path: '/' + }, + + 'http://example.Bücher.com/': { + href: 'http://example.xn--bcher-kva.com/', + protocol: 'http:', + slashes: true, + host: 'example.xn--bcher-kva.com', + hostname: 'example.xn--bcher-kva.com', + pathname: '/', + path: '/' + }, + + 'http://www.Äffchen.com/': { + href: 'http://www.xn--ffchen-9ta.com/', + protocol: 'http:', + slashes: true, + host: 'www.xn--ffchen-9ta.com', + hostname: 'www.xn--ffchen-9ta.com', + pathname: '/', + path: '/' + }, + + 'http://www.Äffchen.cOm;A/b/c?d=e#f gi': { + href: 'http://www.xn--ffchen-9ta.com/;A/b/c?d=e#f%20g%3Ch%3Ei', + protocol: 'http:', + slashes: true, + host: 'www.xn--ffchen-9ta.com', + hostname: 'www.xn--ffchen-9ta.com', + pathname: ';A/b/c', + search: '?d=e', + query: 'd=e', + hash: '#f%20g%3Ch%3Ei', + path: ';A/b/c?d=e' + }, + + 'http://SÉLIER.COM/': { + href: 'http://xn--slier-bsa.com/', + protocol: 'http:', + slashes: true, + host: 'xn--slier-bsa.com', + hostname: 'xn--slier-bsa.com', + pathname: '/', + path: '/' + }, + + 'http://ليهمابتكلموشعربي؟.ي؟/': { + href: 'http://xn--egbpdaj6bu4bxfgehfvwxn.xn--egb9f/', + protocol: 'http:', + slashes: true, + host: 'xn--egbpdaj6bu4bxfgehfvwxn.xn--egb9f', + hostname: 'xn--egbpdaj6bu4bxfgehfvwxn.xn--egb9f', + pathname: '/', + path: '/' + }, + + 'http://➡.ws/➡': { + href: 'http://xn--hgi.ws/➡', + protocol: 'http:', + slashes: true, + host: 'xn--hgi.ws', + hostname: 'xn--hgi.ws', + pathname: '/➡', + path: '/➡' + }, + + 'http://bucket_name.s3.amazonaws.com/image.jpg': { + protocol: 'http:', + slashes: true, + host: 'bucket_name.s3.amazonaws.com', + hostname: 'bucket_name.s3.amazonaws.com', + pathname: '/image.jpg', + href: 'http://bucket_name.s3.amazonaws.com/image.jpg', + path: '/image.jpg' + }, + + 'git+http://github.com/joyent/node.git': { + protocol: 'git+http:', + slashes: true, + host: 'github.com', + hostname: 'github.com', + pathname: '/joyent/node.git', + path: '/joyent/node.git', + href: 'git+http://github.com/joyent/node.git' + }, + + // If local1@domain1 is uses as a relative URL it may + // be parse into auth@hostname, but here there is no + // way to make it work in url.parse, I add the test to be explicit + 'local1@domain1': { + pathname: 'local1@domain1', + path: 'local1@domain1', + href: 'local1@domain1' + }, + + // While this may seem counter-intuitive, a browser will parse + // as a path. + 'www.example.com': { + href: 'www.example.com', + pathname: 'www.example.com', + path: 'www.example.com' + }, + + // ipv6 support + '[fe80::1]': { + href: '[fe80::1]', + pathname: '[fe80::1]', + path: '[fe80::1]' + }, + + 'coap://[FEDC:BA98:7654:3210:FEDC:BA98:7654:3210]': { + protocol: 'coap:', + slashes: true, + host: '[fedc:ba98:7654:3210:fedc:ba98:7654:3210]', + hostname: 'fedc:ba98:7654:3210:fedc:ba98:7654:3210', + href: 'coap://[fedc:ba98:7654:3210:fedc:ba98:7654:3210]/', + pathname: '/', + path: '/' + }, + + 'coap://[1080:0:0:0:8:800:200C:417A]:61616/': { + protocol: 'coap:', + slashes: true, + host: '[1080:0:0:0:8:800:200c:417a]:61616', + port: '61616', + hostname: '1080:0:0:0:8:800:200c:417a', + href: 'coap://[1080:0:0:0:8:800:200c:417a]:61616/', + pathname: '/', + path: '/' + }, + + 'http://user:password@[3ffe:2a00:100:7031::1]:8080': { + protocol: 'http:', + slashes: true, + auth: 'user:password', + host: '[3ffe:2a00:100:7031::1]:8080', + port: '8080', + hostname: '3ffe:2a00:100:7031::1', + href: 'http://user:password@[3ffe:2a00:100:7031::1]:8080/', + pathname: '/', + path: '/' + }, + + 'coap://u:p@[::192.9.5.5]:61616/.well-known/r?n=Temperature': { + protocol: 'coap:', + slashes: true, + auth: 'u:p', + host: '[::192.9.5.5]:61616', + port: '61616', + hostname: '::192.9.5.5', + href: 'coap://u:p@[::192.9.5.5]:61616/.well-known/r?n=Temperature', + search: '?n=Temperature', + query: 'n=Temperature', + pathname: '/.well-known/r', + path: '/.well-known/r?n=Temperature' + }, + + // empty port + 'http://example.com:': { + protocol: 'http:', + slashes: true, + host: 'example.com', + hostname: 'example.com', + href: 'http://example.com/', + pathname: '/', + path: '/' + }, + + 'http://example.com:/a/b.html': { + protocol: 'http:', + slashes: true, + host: 'example.com', + hostname: 'example.com', + href: 'http://example.com/a/b.html', + pathname: '/a/b.html', + path: '/a/b.html' + }, + + 'http://example.com:?a=b': { + protocol: 'http:', + slashes: true, + host: 'example.com', + hostname: 'example.com', + href: 'http://example.com/?a=b', + search: '?a=b', + query: 'a=b', + pathname: '/', + path: '/?a=b' + }, + + 'http://example.com:#abc': { + protocol: 'http:', + slashes: true, + host: 'example.com', + hostname: 'example.com', + href: 'http://example.com/#abc', + hash: '#abc', + pathname: '/', + path: '/' + }, + + 'http://[fe80::1]:/a/b?a=b#abc': { + protocol: 'http:', + slashes: true, + host: '[fe80::1]', + hostname: 'fe80::1', + href: 'http://[fe80::1]/a/b?a=b#abc', + search: '?a=b', + query: 'a=b', + hash: '#abc', + pathname: '/a/b', + path: '/a/b?a=b' + }, + + 'http://-lovemonsterz.tumblr.com/rss': { + protocol: 'http:', + slashes: true, + host: '-lovemonsterz.tumblr.com', + hostname: '-lovemonsterz.tumblr.com', + href: 'http://-lovemonsterz.tumblr.com/rss', + pathname: '/rss', + path: '/rss', + }, + + 'http://-lovemonsterz.tumblr.com:80/rss': { + protocol: 'http:', + slashes: true, + port: '80', + host: '-lovemonsterz.tumblr.com:80', + hostname: '-lovemonsterz.tumblr.com', + href: 'http://-lovemonsterz.tumblr.com:80/rss', + pathname: '/rss', + path: '/rss', + }, + + 'http://user:pass@-lovemonsterz.tumblr.com/rss': { + protocol: 'http:', + slashes: true, + auth: 'user:pass', + host: '-lovemonsterz.tumblr.com', + hostname: '-lovemonsterz.tumblr.com', + href: 'http://user:pass@-lovemonsterz.tumblr.com/rss', + pathname: '/rss', + path: '/rss', + }, + + 'http://user:pass@-lovemonsterz.tumblr.com:80/rss': { + protocol: 'http:', + slashes: true, + auth: 'user:pass', + port: '80', + host: '-lovemonsterz.tumblr.com:80', + hostname: '-lovemonsterz.tumblr.com', + href: 'http://user:pass@-lovemonsterz.tumblr.com:80/rss', + pathname: '/rss', + path: '/rss', + }, + + 'http://_jabber._tcp.google.com/test': { + protocol: 'http:', + slashes: true, + host: '_jabber._tcp.google.com', + hostname: '_jabber._tcp.google.com', + href: 'http://_jabber._tcp.google.com/test', + pathname: '/test', + path: '/test', + }, + + 'http://user:pass@_jabber._tcp.google.com/test': { + protocol: 'http:', + slashes: true, + auth: 'user:pass', + host: '_jabber._tcp.google.com', + hostname: '_jabber._tcp.google.com', + href: 'http://user:pass@_jabber._tcp.google.com/test', + pathname: '/test', + path: '/test', + }, + + 'http://_jabber._tcp.google.com:80/test': { + protocol: 'http:', + slashes: true, + port: '80', + host: '_jabber._tcp.google.com:80', + hostname: '_jabber._tcp.google.com', + href: 'http://_jabber._tcp.google.com:80/test', + pathname: '/test', + path: '/test', + }, + + 'http://user:pass@_jabber._tcp.google.com:80/test': { + protocol: 'http:', + slashes: true, + auth: 'user:pass', + port: '80', + host: '_jabber._tcp.google.com:80', + hostname: '_jabber._tcp.google.com', + href: 'http://user:pass@_jabber._tcp.google.com:80/test', + pathname: '/test', + path: '/test', + }, + + 'http://x:1/\' <>"`/{}|\\^~`/': { + protocol: 'http:', + slashes: true, + host: 'x:1', + port: '1', + hostname: 'x', + pathname: '/%27%20%3C%3E%22%60/%7B%7D%7C/%5E~%60/', + path: '/%27%20%3C%3E%22%60/%7B%7D%7C/%5E~%60/', + href: 'http://x:1/%27%20%3C%3E%22%60/%7B%7D%7C/%5E~%60/' + }, + + 'http://a@b@c/': { + protocol: 'http:', + slashes: true, + auth: 'a@b', + host: 'c', + hostname: 'c', + href: 'http://a%40b@c/', + path: '/', + pathname: '/' + }, + + 'http://a@b?@c': { + protocol: 'http:', + slashes: true, + auth: 'a', + host: 'b', + hostname: 'b', + href: 'http://a@b/?@c', + path: '/?@c', + pathname: '/', + search: '?@c', + query: '@c' + }, + + 'http://a.b/\tbc\ndr\ref g"hq\'j?mn\\op^q=r`99{st|uv}wz': { + protocol: 'http:', + slashes: true, + host: 'a.b', + port: null, + hostname: 'a.b', + hash: null, + pathname: '/%09bc%0Adr%0Def%20g%22hq%27j%3Ckl%3E', + path: '/%09bc%0Adr%0Def%20g%22hq%27j%3Ckl%3E?mn%5Cop%5Eq=r%6099%7Bst%7Cuv%7Dwz', + search: '?mn%5Cop%5Eq=r%6099%7Bst%7Cuv%7Dwz', + query: 'mn%5Cop%5Eq=r%6099%7Bst%7Cuv%7Dwz', + href: 'http://a.b/%09bc%0Adr%0Def%20g%22hq%27j%3Ckl%3E?mn%5Cop%5Eq=r%6099%7Bst%7Cuv%7Dwz' + }, + + 'http://a\r" \t\n<\'b:b@c\r\nd/e?f': { + protocol: 'http:', + slashes: true, + auth: 'a\r" \t\n<\'b:b', + host: 'c', + port: null, + hostname: 'c', + hash: null, + search: '?f', + query: 'f', + pathname: '%0D%0Ad/e', + path: '%0D%0Ad/e?f', + href: 'http://a%0D%22%20%09%0A%3C\'b:b@c/%0D%0Ad/e?f' + }, + + // Git urls used by npm + 'git+ssh://git@github.com:npm/npm': { + protocol: 'git+ssh:', + slashes: true, + auth: 'git', + host: 'github.com', + port: null, + hostname: 'github.com', + hash: null, + search: null, + query: null, + pathname: '/:npm/npm', + path: '/:npm/npm', + href: 'git+ssh://git@github.com/:npm/npm' + }, + + 'https://*': { + protocol: 'https:', + slashes: true, + auth: null, + host: '', + port: null, + hostname: '', + hash: null, + search: null, + query: null, + pathname: '/*', + path: '/*', + href: 'https:///*' + }, + + // The following two URLs are the same, but they differ for a capital A. + // Verify that the protocol is checked in a case-insensitive manner. + 'javascript:alert(1);a=\x27@white-listed.com\x27': { + protocol: 'javascript:', + slashes: null, + auth: null, + host: null, + port: null, + hostname: null, + hash: null, + search: null, + query: null, + pathname: "alert(1);a='@white-listed.com'", + path: "alert(1);a='@white-listed.com'", + href: "javascript:alert(1);a='@white-listed.com'" + }, + + 'javAscript:alert(1);a=\x27@white-listed.com\x27': { + protocol: 'javascript:', + slashes: null, + auth: null, + host: null, + port: null, + hostname: null, + hash: null, + search: null, + query: null, + pathname: "alert(1);a='@white-listed.com'", + path: "alert(1);a='@white-listed.com'", + href: "javascript:alert(1);a='@white-listed.com'" + }, + + 'ws://www.example.com': { + protocol: 'ws:', + slashes: true, + hostname: 'www.example.com', + host: 'www.example.com', + pathname: '/', + path: '/', + href: 'ws://www.example.com/' + }, + + 'wss://www.example.com': { + protocol: 'wss:', + slashes: true, + hostname: 'www.example.com', + host: 'www.example.com', + pathname: '/', + path: '/', + href: 'wss://www.example.com/' + }, + + '//fhqwhgads@example.com/everybody-to-the-limit': { + protocol: null, + slashes: true, + auth: 'fhqwhgads', + host: 'example.com', + port: null, + hostname: 'example.com', + hash: null, + search: null, + query: null, + pathname: '/everybody-to-the-limit', + path: '/everybody-to-the-limit', + href: '//fhqwhgads@example.com/everybody-to-the-limit' + }, + + '//fhqwhgads@example.com/everybody#to-the-limit': { + protocol: null, + slashes: true, + auth: 'fhqwhgads', + host: 'example.com', + port: null, + hostname: 'example.com', + hash: '#to-the-limit', + search: null, + query: null, + pathname: '/everybody', + path: '/everybody', + href: '//fhqwhgads@example.com/everybody#to-the-limit' + }, + + '\bhttp://example.com/\b': { + protocol: 'http:', + slashes: true, + auth: null, + host: 'example.com', + port: null, + hostname: 'example.com', + hash: null, + search: null, + query: null, + pathname: '/', + path: '/', + href: 'http://example.com/' + } +}; + +for (const u in parseTests) { + let actual = url.parse(u); + const spaced = url.parse(` \t ${u}\n\t`); + let expected = Object.assign(new url.Url(), parseTests[u]); + + Object.keys(actual).forEach(function(i) { + if (expected[i] === undefined && actual[i] === null) { + expected[i] = null; + } + }); + + assert.deepStrictEqual( + actual, + expected, + `expected ${inspect(expected)}, got ${inspect(actual)}` + ); + assert.deepStrictEqual( + spaced, + expected, + `expected ${inspect(expected)}, got ${inspect(spaced)}` + ); + + expected = parseTests[u].href; + actual = url.format(parseTests[u]); + + assert.strictEqual(actual, expected, + `format(${u}) == ${u}\nactual:${actual}`); +} + +{ + const parsed = url.parse('http://nodejs.org/') + .resolveObject('jAvascript:alert(1);a=\x27@white-listed.com\x27'); + + const expected = Object.assign(new url.Url(), { + protocol: 'javascript:', + slashes: null, + auth: null, + host: null, + port: null, + hostname: null, + hash: null, + search: null, + query: null, + pathname: "alert(1);a='@white-listed.com'", + path: "alert(1);a='@white-listed.com'", + href: "javascript:alert(1);a='@white-listed.com'" + }); + + assert.deepStrictEqual(parsed, expected); +} diff --git a/cli/tests/node_compat/test/parallel/test-url-parse-invalid-input.js b/cli/tests/node_compat/test/parallel/test-url-parse-invalid-input.js new file mode 100644 index 00000000000000..b4c75ba4994992 --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-url-parse-invalid-input.js @@ -0,0 +1,83 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +'use strict'; +const common = require('../common'); +const assert = require('assert'); +const url = require('url'); + +// https://github.com/joyent/node/issues/568 +[ + [undefined, 'undefined'], + [null, 'object'], + [true, 'boolean'], + [false, 'boolean'], + [0.0, 'number'], + [0, 'number'], + [[], 'object'], + [{}, 'object'], + [() => {}, 'function'], + [Symbol('foo'), 'symbol'], +].forEach(([val, type]) => { + assert.throws(() => { + url.parse(val); + }, { + code: 'ERR_INVALID_ARG_TYPE', + name: 'TypeError', + message: 'The "url" argument must be of type string.' + + common.invalidArgTypeHelper(val) + }); +}); + +assert.throws(() => { url.parse('http://%E0%A4%A@fail'); }, + (e) => { + // The error should be a URIError. + if (!(e instanceof URIError)) + return false; + + // The error should be from the JS engine and not from Node.js. + // JS engine errors do not have the `code` property. + return e.code === undefined; + }); + +assert.throws(() => { url.parse('http://[127.0.0.1\x00c8763]:8000/'); }, + { code: 'ERR_INVALID_URL', input: 'http://[127.0.0.1\x00c8763]:8000/' } +); + +if (common.hasIntl) { + // An array of Unicode code points whose Unicode NFKD contains a "bad + // character". + const badIDNA = (() => { + const BAD_CHARS = '#%/:?@[\\]^|'; + const out = []; + for (let i = 0x80; i < 0x110000; i++) { + const cp = String.fromCodePoint(i); + for (const badChar of BAD_CHARS) { + if (cp.normalize('NFKD').includes(badChar)) { + out.push(cp); + } + } + } + return out; + })(); + + // The generation logic above should at a minimum produce these two + // characters. + assert(badIDNA.includes('℀')); + assert(badIDNA.includes('@')); + + for (const badCodePoint of badIDNA) { + const badURL = `http://fail${badCodePoint}fail.com/`; + assert.throws(() => { url.parse(badURL); }, + (e) => e.code === 'ERR_INVALID_URL', + `parsing ${badURL}`); + } + + assert.throws(() => { url.parse('http://\u00AD/bad.com/'); }, + (e) => e.code === 'ERR_INVALID_URL', + 'parsing http://\u00AD/bad.com/'); +} diff --git a/cli/tests/node_compat/test/parallel/test-url-parse-query.js b/cli/tests/node_compat/test/parallel/test-url-parse-query.js new file mode 100644 index 00000000000000..5fdbf2c053f1af --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-url-parse-query.js @@ -0,0 +1,97 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +'use strict'; +require('../common'); +const assert = require('assert'); +const url = require('url'); + +function createWithNoPrototype(properties = []) { + const noProto = Object.create(null); + properties.forEach((property) => { + noProto[property.key] = property.value; + }); + return noProto; +} + +function check(actual, expected) { + assert.notStrictEqual(Object.getPrototypeOf(actual), Object.prototype); + assert.deepStrictEqual(Object.keys(actual).sort(), + Object.keys(expected).sort()); + Object.keys(expected).forEach(function(key) { + assert.deepStrictEqual(actual[key], expected[key]); + }); +} + +const parseTestsWithQueryString = { + '/foo/bar?baz=quux#frag': { + href: '/foo/bar?baz=quux#frag', + hash: '#frag', + search: '?baz=quux', + query: createWithNoPrototype([{ key: 'baz', value: 'quux' }]), + pathname: '/foo/bar', + path: '/foo/bar?baz=quux' + }, + 'http://example.com': { + href: 'http://example.com/', + protocol: 'http:', + slashes: true, + host: 'example.com', + hostname: 'example.com', + query: createWithNoPrototype(), + search: null, + pathname: '/', + path: '/' + }, + '/example': { + protocol: null, + slashes: null, + auth: undefined, + host: null, + port: null, + hostname: null, + hash: null, + search: null, + query: createWithNoPrototype(), + pathname: '/example', + path: '/example', + href: '/example' + }, + '/example?query=value': { + protocol: null, + slashes: null, + auth: undefined, + host: null, + port: null, + hostname: null, + hash: null, + search: '?query=value', + query: createWithNoPrototype([{ key: 'query', value: 'value' }]), + pathname: '/example', + path: '/example?query=value', + href: '/example?query=value' + } +}; +for (const u in parseTestsWithQueryString) { + const actual = url.parse(u, true); + const expected = Object.assign(new url.Url(), parseTestsWithQueryString[u]); + for (const i in actual) { + if (actual[i] === null && expected[i] === undefined) { + expected[i] = null; + } + } + + const properties = Object.keys(actual).sort(); + assert.deepStrictEqual(properties, Object.keys(expected).sort()); + properties.forEach((property) => { + if (property === 'query') { + check(actual[property], expected[property]); + } else { + assert.deepStrictEqual(actual[property], expected[property]); + } + }); +} diff --git a/cli/tests/node_compat/test/parallel/test-url-pathtofileurl.js b/cli/tests/node_compat/test/parallel/test-url-pathtofileurl.js new file mode 100644 index 00000000000000..f13ca7307d2ca0 --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-url-pathtofileurl.js @@ -0,0 +1,153 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +'use strict'; +const { isWindows } = require('../common'); +const assert = require('assert'); +const url = require('url'); + +{ + const fileURL = url.pathToFileURL('test/').href; + assert.ok(fileURL.startsWith('file:///')); + assert.ok(fileURL.endsWith('/')); +} + +{ + const fileURL = url.pathToFileURL('test\\').href; + assert.ok(fileURL.startsWith('file:///')); + if (isWindows) + assert.ok(fileURL.endsWith('/')); + else + assert.ok(fileURL.endsWith('%5C')); +} + +{ + const fileURL = url.pathToFileURL('test/%').href; + assert.ok(fileURL.includes('%25')); +} + +{ + if (isWindows) { + // UNC path: \\server\share\resource + + // Missing server: + assert.throws(() => url.pathToFileURL('\\\\\\no-server'), { + code: 'ERR_INVALID_ARG_VALUE' + }); + + // Missing share or resource: + assert.throws(() => url.pathToFileURL('\\\\host'), { + code: 'ERR_INVALID_ARG_VALUE' + }); + } else { + // UNC paths on posix are considered a single path that has backslashes: + const fileURL = url.pathToFileURL('\\\\nas\\share\\path.txt').href; + assert.match(fileURL, /file:\/\/.+%5C%5Cnas%5Cshare%5Cpath\.txt$/); + } +} + +{ + let testCases; + if (isWindows) { + testCases = [ + // Lowercase ascii alpha + { path: 'C:\\foo', expected: 'file:///C:/foo' }, + // Uppercase ascii alpha + { path: 'C:\\FOO', expected: 'file:///C:/FOO' }, + // dir + { path: 'C:\\dir\\foo', expected: 'file:///C:/dir/foo' }, + // trailing separator + { path: 'C:\\dir\\', expected: 'file:///C:/dir/' }, + // dot + { path: 'C:\\foo.mjs', expected: 'file:///C:/foo.mjs' }, + // space + { path: 'C:\\foo bar', expected: 'file:///C:/foo%20bar' }, + // question mark + { path: 'C:\\foo?bar', expected: 'file:///C:/foo%3Fbar' }, + // number sign + { path: 'C:\\foo#bar', expected: 'file:///C:/foo%23bar' }, + // ampersand + { path: 'C:\\foo&bar', expected: 'file:///C:/foo&bar' }, + // equals + { path: 'C:\\foo=bar', expected: 'file:///C:/foo=bar' }, + // colon + { path: 'C:\\foo:bar', expected: 'file:///C:/foo:bar' }, + // semicolon + { path: 'C:\\foo;bar', expected: 'file:///C:/foo;bar' }, + // percent + { path: 'C:\\foo%bar', expected: 'file:///C:/foo%25bar' }, + // backslash + { path: 'C:\\foo\\bar', expected: 'file:///C:/foo/bar' }, + // backspace + { path: 'C:\\foo\bbar', expected: 'file:///C:/foo%08bar' }, + // tab + { path: 'C:\\foo\tbar', expected: 'file:///C:/foo%09bar' }, + // newline + { path: 'C:\\foo\nbar', expected: 'file:///C:/foo%0Abar' }, + // carriage return + { path: 'C:\\foo\rbar', expected: 'file:///C:/foo%0Dbar' }, + // latin1 + { path: 'C:\\fóóbàr', expected: 'file:///C:/f%C3%B3%C3%B3b%C3%A0r' }, + // Euro sign (BMP code point) + { path: 'C:\\€', expected: 'file:///C:/%E2%82%AC' }, + // Rocket emoji (non-BMP code point) + { path: 'C:\\🚀', expected: 'file:///C:/%F0%9F%9A%80' }, + // UNC path (see https://docs.microsoft.com/en-us/archive/blogs/ie/file-uris-in-windows) + { path: '\\\\nas\\My Docs\\File.doc', expected: 'file://nas/My%20Docs/File.doc' }, + ]; + } else { + testCases = [ + // Lowercase ascii alpha + { path: '/foo', expected: 'file:///foo' }, + // Uppercase ascii alpha + { path: '/FOO', expected: 'file:///FOO' }, + // dir + { path: '/dir/foo', expected: 'file:///dir/foo' }, + // trailing separator + { path: '/dir/', expected: 'file:///dir/' }, + // dot + { path: '/foo.mjs', expected: 'file:///foo.mjs' }, + // space + { path: '/foo bar', expected: 'file:///foo%20bar' }, + // question mark + { path: '/foo?bar', expected: 'file:///foo%3Fbar' }, + // number sign + { path: '/foo#bar', expected: 'file:///foo%23bar' }, + // ampersand + { path: '/foo&bar', expected: 'file:///foo&bar' }, + // equals + { path: '/foo=bar', expected: 'file:///foo=bar' }, + // colon + { path: '/foo:bar', expected: 'file:///foo:bar' }, + // semicolon + { path: '/foo;bar', expected: 'file:///foo;bar' }, + // percent + { path: '/foo%bar', expected: 'file:///foo%25bar' }, + // backslash + { path: '/foo\\bar', expected: 'file:///foo%5Cbar' }, + // backspace + { path: '/foo\bbar', expected: 'file:///foo%08bar' }, + // tab + { path: '/foo\tbar', expected: 'file:///foo%09bar' }, + // newline + { path: '/foo\nbar', expected: 'file:///foo%0Abar' }, + // carriage return + { path: '/foo\rbar', expected: 'file:///foo%0Dbar' }, + // latin1 + { path: '/fóóbàr', expected: 'file:///f%C3%B3%C3%B3b%C3%A0r' }, + // Euro sign (BMP code point) + { path: '/€', expected: 'file:///%E2%82%AC' }, + // Rocket emoji (non-BMP code point) + { path: '/🚀', expected: 'file:///%F0%9F%9A%80' }, + ]; + } + + for (const { path, expected } of testCases) { + const actual = url.pathToFileURL(path).href; + assert.strictEqual(actual, expected); + } +} diff --git a/cli/tests/node_compat/test/parallel/test-url-relative.js b/cli/tests/node_compat/test/parallel/test-url-relative.js new file mode 100644 index 00000000000000..0436f0821ac604 --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-url-relative.js @@ -0,0 +1,441 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +'use strict'; +require('../common'); +const assert = require('assert'); +const inspect = require('util').inspect; +const url = require('url'); + +// When source is false +assert.strictEqual(url.resolveObject('', 'foo'), 'foo'); + +// [from, path, expected] +const relativeTests = [ + ['/foo/bar/baz', 'quux', '/foo/bar/quux'], + ['/foo/bar/baz', 'quux/asdf', '/foo/bar/quux/asdf'], + ['/foo/bar/baz', 'quux/baz', '/foo/bar/quux/baz'], + ['/foo/bar/baz', '../quux/baz', '/foo/quux/baz'], + ['/foo/bar/baz', '/bar', '/bar'], + ['/foo/bar/baz/', 'quux', '/foo/bar/baz/quux'], + ['/foo/bar/baz/', 'quux/baz', '/foo/bar/baz/quux/baz'], + ['/foo/bar/baz', '../../../../../../../../quux/baz', '/quux/baz'], + ['/foo/bar/baz', '../../../../../../../quux/baz', '/quux/baz'], + ['/foo', '.', '/'], + ['/foo', '..', '/'], + ['/foo/', '.', '/foo/'], + ['/foo/', '..', '/'], + ['/foo/bar', '.', '/foo/'], + ['/foo/bar', '..', '/'], + ['/foo/bar/', '.', '/foo/bar/'], + ['/foo/bar/', '..', '/foo/'], + ['foo/bar', '../../../baz', '../../baz'], + ['foo/bar/', '../../../baz', '../baz'], + ['http://example.com/b//c//d;p?q#blarg', 'https:#hash2', 'https:///#hash2'], + ['http://example.com/b//c//d;p?q#blarg', + 'https:/p/a/t/h?s#hash2', + 'https://p/a/t/h?s#hash2'], + ['http://example.com/b//c//d;p?q#blarg', + 'https://u:p@h.com/p/a/t/h?s#hash2', + 'https://u:p@h.com/p/a/t/h?s#hash2'], + ['http://example.com/b//c//d;p?q#blarg', + 'https:/a/b/c/d', + 'https://a/b/c/d'], + ['http://example.com/b//c//d;p?q#blarg', + 'http:#hash2', + 'http://example.com/b//c//d;p?q#hash2'], + ['http://example.com/b//c//d;p?q#blarg', + 'http:/p/a/t/h?s#hash2', + 'http://example.com/p/a/t/h?s#hash2'], + ['http://example.com/b//c//d;p?q#blarg', + 'http://u:p@h.com/p/a/t/h?s#hash2', + 'http://u:p@h.com/p/a/t/h?s#hash2'], + ['http://example.com/b//c//d;p?q#blarg', + 'http:/a/b/c/d', + 'http://example.com/a/b/c/d'], + ['/foo/bar/baz', '/../etc/passwd', '/etc/passwd'], + ['http://localhost', 'file:///Users/foo', 'file:///Users/foo'], + ['http://localhost', 'file://foo/Users', 'file://foo/Users'], + ['https://registry.npmjs.org', '@foo/bar', 'https://registry.npmjs.org/@foo/bar'], +]; +relativeTests.forEach(function(relativeTest) { + const a = url.resolve(relativeTest[0], relativeTest[1]); + const e = relativeTest[2]; + assert.strictEqual(a, e, + `resolve(${relativeTest[0]}, ${relativeTest[1]})` + + ` == ${e}\n actual=${a}`); +}); + +// +// Tests below taken from Chiron +// http://code.google.com/p/chironjs/source/browse/trunk/src/test/http/url.js +// +// Copyright (c) 2002-2008 Kris Kowal +// used with permission under MIT License +// +// Changes marked with @isaacs + +const bases = [ + 'http://a/b/c/d;p?q', + 'http://a/b/c/d;p?q=1/2', + 'http://a/b/c/d;p=1/2?q', + 'fred:///s//a/b/c', + 'http:///s//a/b/c', +]; + +// [to, from, result] +const relativeTests2 = [ + // http://lists.w3.org/Archives/Public/uri/2004Feb/0114.html + ['../c', 'foo:a/b', 'foo:c'], + ['foo:.', 'foo:a', 'foo:'], + ['/foo/../../../bar', 'zz:abc', 'zz:/bar'], + ['/foo/../bar', 'zz:abc', 'zz:/bar'], + // @isaacs Disagree. Not how web browsers resolve this. + ['foo/../../../bar', 'zz:abc', 'zz:bar'], + // ['foo/../../../bar', 'zz:abc', 'zz:../../bar'], // @isaacs Added + ['foo/../bar', 'zz:abc', 'zz:bar'], + ['zz:.', 'zz:abc', 'zz:'], + ['/.', bases[0], 'http://a/'], + ['/.foo', bases[0], 'http://a/.foo'], + ['.foo', bases[0], 'http://a/b/c/.foo'], + + // http://gbiv.com/protocols/uri/test/rel_examples1.html + // examples from RFC 2396 + ['g:h', bases[0], 'g:h'], + ['g', bases[0], 'http://a/b/c/g'], + ['./g', bases[0], 'http://a/b/c/g'], + ['g/', bases[0], 'http://a/b/c/g/'], + ['/g', bases[0], 'http://a/g'], + ['//g', bases[0], 'http://g/'], + // Changed with RFC 2396bis + // ('?y', bases[0], 'http://a/b/c/d;p?y'], + ['?y', bases[0], 'http://a/b/c/d;p?y'], + ['g?y', bases[0], 'http://a/b/c/g?y'], + // Changed with RFC 2396bis + // ('#s', bases[0], CURRENT_DOC_URI + '#s'], + ['#s', bases[0], 'http://a/b/c/d;p?q#s'], + ['g#s', bases[0], 'http://a/b/c/g#s'], + ['g?y#s', bases[0], 'http://a/b/c/g?y#s'], + [';x', bases[0], 'http://a/b/c/;x'], + ['g;x', bases[0], 'http://a/b/c/g;x'], + ['g;x?y#s', bases[0], 'http://a/b/c/g;x?y#s'], + // Changed with RFC 2396bis + // ('', bases[0], CURRENT_DOC_URI], + ['', bases[0], 'http://a/b/c/d;p?q'], + ['.', bases[0], 'http://a/b/c/'], + ['./', bases[0], 'http://a/b/c/'], + ['..', bases[0], 'http://a/b/'], + ['../', bases[0], 'http://a/b/'], + ['../g', bases[0], 'http://a/b/g'], + ['../..', bases[0], 'http://a/'], + ['../../', bases[0], 'http://a/'], + ['../../g', bases[0], 'http://a/g'], + ['../../../g', bases[0], ('http://a/../g', 'http://a/g')], + ['../../../../g', bases[0], ('http://a/../../g', 'http://a/g')], + // Changed with RFC 2396bis + // ('/./g', bases[0], 'http://a/./g'], + ['/./g', bases[0], 'http://a/g'], + // Changed with RFC 2396bis + // ('/../g', bases[0], 'http://a/../g'], + ['/../g', bases[0], 'http://a/g'], + ['g.', bases[0], 'http://a/b/c/g.'], + ['.g', bases[0], 'http://a/b/c/.g'], + ['g..', bases[0], 'http://a/b/c/g..'], + ['..g', bases[0], 'http://a/b/c/..g'], + ['./../g', bases[0], 'http://a/b/g'], + ['./g/.', bases[0], 'http://a/b/c/g/'], + ['g/./h', bases[0], 'http://a/b/c/g/h'], + ['g/../h', bases[0], 'http://a/b/c/h'], + ['g;x=1/./y', bases[0], 'http://a/b/c/g;x=1/y'], + ['g;x=1/../y', bases[0], 'http://a/b/c/y'], + ['g?y/./x', bases[0], 'http://a/b/c/g?y/./x'], + ['g?y/../x', bases[0], 'http://a/b/c/g?y/../x'], + ['g#s/./x', bases[0], 'http://a/b/c/g#s/./x'], + ['g#s/../x', bases[0], 'http://a/b/c/g#s/../x'], + ['http:g', bases[0], ('http:g', 'http://a/b/c/g')], + ['http:', bases[0], ('http:', bases[0])], + // Not sure where this one originated + ['/a/b/c/./../../g', bases[0], 'http://a/a/g'], + + // http://gbiv.com/protocols/uri/test/rel_examples2.html + // slashes in base URI's query args + ['g', bases[1], 'http://a/b/c/g'], + ['./g', bases[1], 'http://a/b/c/g'], + ['g/', bases[1], 'http://a/b/c/g/'], + ['/g', bases[1], 'http://a/g'], + ['//g', bases[1], 'http://g/'], + // Changed in RFC 2396bis + // ('?y', bases[1], 'http://a/b/c/?y'], + ['?y', bases[1], 'http://a/b/c/d;p?y'], + ['g?y', bases[1], 'http://a/b/c/g?y'], + ['g?y/./x', bases[1], 'http://a/b/c/g?y/./x'], + ['g?y/../x', bases[1], 'http://a/b/c/g?y/../x'], + ['g#s', bases[1], 'http://a/b/c/g#s'], + ['g#s/./x', bases[1], 'http://a/b/c/g#s/./x'], + ['g#s/../x', bases[1], 'http://a/b/c/g#s/../x'], + ['./', bases[1], 'http://a/b/c/'], + ['../', bases[1], 'http://a/b/'], + ['../g', bases[1], 'http://a/b/g'], + ['../../', bases[1], 'http://a/'], + ['../../g', bases[1], 'http://a/g'], + + // http://gbiv.com/protocols/uri/test/rel_examples3.html + // slashes in path params + // all of these changed in RFC 2396bis + ['g', bases[2], 'http://a/b/c/d;p=1/g'], + ['./g', bases[2], 'http://a/b/c/d;p=1/g'], + ['g/', bases[2], 'http://a/b/c/d;p=1/g/'], + ['g?y', bases[2], 'http://a/b/c/d;p=1/g?y'], + [';x', bases[2], 'http://a/b/c/d;p=1/;x'], + ['g;x', bases[2], 'http://a/b/c/d;p=1/g;x'], + ['g;x=1/./y', bases[2], 'http://a/b/c/d;p=1/g;x=1/y'], + ['g;x=1/../y', bases[2], 'http://a/b/c/d;p=1/y'], + ['./', bases[2], 'http://a/b/c/d;p=1/'], + ['../', bases[2], 'http://a/b/c/'], + ['../g', bases[2], 'http://a/b/c/g'], + ['../../', bases[2], 'http://a/b/'], + ['../../g', bases[2], 'http://a/b/g'], + + // http://gbiv.com/protocols/uri/test/rel_examples4.html + // double and triple slash, unknown scheme + ['g:h', bases[3], 'g:h'], + ['g', bases[3], 'fred:///s//a/b/g'], + ['./g', bases[3], 'fred:///s//a/b/g'], + ['g/', bases[3], 'fred:///s//a/b/g/'], + ['/g', bases[3], 'fred:///g'], // May change to fred:///s//a/g + ['//g', bases[3], 'fred://g'], // May change to fred:///s//g + ['//g/x', bases[3], 'fred://g/x'], // May change to fred:///s//g/x + ['///g', bases[3], 'fred:///g'], + ['./', bases[3], 'fred:///s//a/b/'], + ['../', bases[3], 'fred:///s//a/'], + ['../g', bases[3], 'fred:///s//a/g'], + + ['../../', bases[3], 'fred:///s//'], + ['../../g', bases[3], 'fred:///s//g'], + ['../../../g', bases[3], 'fred:///s/g'], + // May change to fred:///s//a/../../../g + ['../../../../g', bases[3], 'fred:///g'], + + // http://gbiv.com/protocols/uri/test/rel_examples5.html + // double and triple slash, well-known scheme + ['g:h', bases[4], 'g:h'], + ['g', bases[4], 'http:///s//a/b/g'], + ['./g', bases[4], 'http:///s//a/b/g'], + ['g/', bases[4], 'http:///s//a/b/g/'], + ['/g', bases[4], 'http:///g'], // May change to http:///s//a/g + ['//g', bases[4], 'http://g/'], // May change to http:///s//g + ['//g/x', bases[4], 'http://g/x'], // May change to http:///s//g/x + ['///g', bases[4], 'http:///g'], + ['./', bases[4], 'http:///s//a/b/'], + ['../', bases[4], 'http:///s//a/'], + ['../g', bases[4], 'http:///s//a/g'], + ['../../', bases[4], 'http:///s//'], + ['../../g', bases[4], 'http:///s//g'], + // May change to http:///s//a/../../g + ['../../../g', bases[4], 'http:///s/g'], + // May change to http:///s//a/../../../g + ['../../../../g', bases[4], 'http:///g'], + + // From Dan Connelly's tests in http://www.w3.org/2000/10/swap/uripath.py + ['bar:abc', 'foo:xyz', 'bar:abc'], + ['../abc', 'http://example/x/y/z', 'http://example/x/abc'], + ['http://example/x/abc', 'http://example2/x/y/z', 'http://example/x/abc'], + ['../r', 'http://ex/x/y/z', 'http://ex/x/r'], + ['q/r', 'http://ex/x/y', 'http://ex/x/q/r'], + ['q/r#s', 'http://ex/x/y', 'http://ex/x/q/r#s'], + ['q/r#s/t', 'http://ex/x/y', 'http://ex/x/q/r#s/t'], + ['ftp://ex/x/q/r', 'http://ex/x/y', 'ftp://ex/x/q/r'], + ['', 'http://ex/x/y', 'http://ex/x/y'], + ['', 'http://ex/x/y/', 'http://ex/x/y/'], + ['', 'http://ex/x/y/pdq', 'http://ex/x/y/pdq'], + ['z/', 'http://ex/x/y/', 'http://ex/x/y/z/'], + ['#Animal', + 'file:/swap/test/animal.rdf', + 'file:/swap/test/animal.rdf#Animal'], + ['../abc', 'file:/e/x/y/z', 'file:/e/x/abc'], + ['/example/x/abc', 'file:/example2/x/y/z', 'file:/example/x/abc'], + ['../r', 'file:/ex/x/y/z', 'file:/ex/x/r'], + ['/r', 'file:/ex/x/y/z', 'file:/r'], + ['q/r', 'file:/ex/x/y', 'file:/ex/x/q/r'], + ['q/r#s', 'file:/ex/x/y', 'file:/ex/x/q/r#s'], + ['q/r#', 'file:/ex/x/y', 'file:/ex/x/q/r#'], + ['q/r#s/t', 'file:/ex/x/y', 'file:/ex/x/q/r#s/t'], + ['ftp://ex/x/q/r', 'file:/ex/x/y', 'ftp://ex/x/q/r'], + ['', 'file:/ex/x/y', 'file:/ex/x/y'], + ['', 'file:/ex/x/y/', 'file:/ex/x/y/'], + ['', 'file:/ex/x/y/pdq', 'file:/ex/x/y/pdq'], + ['z/', 'file:/ex/x/y/', 'file:/ex/x/y/z/'], + ['file://meetings.example.com/cal#m1', + 'file:/devel/WWW/2000/10/swap/test/reluri-1.n3', + 'file://meetings.example.com/cal#m1'], + ['file://meetings.example.com/cal#m1', + 'file:/home/connolly/w3ccvs/WWW/2000/10/swap/test/reluri-1.n3', + 'file://meetings.example.com/cal#m1'], + ['./#blort', 'file:/some/dir/foo', 'file:/some/dir/#blort'], + ['./#', 'file:/some/dir/foo', 'file:/some/dir/#'], + // Ryan Lee + ['./', 'http://example/x/abc.efg', 'http://example/x/'], + + + // Graham Klyne's tests + // http://www.ninebynine.org/Software/HaskellUtils/Network/UriTest.xls + // 01-31 are from Connelly's cases + + // 32-49 + ['./q:r', 'http://ex/x/y', 'http://ex/x/q:r'], + ['./p=q:r', 'http://ex/x/y', 'http://ex/x/p=q:r'], + ['?pp/rr', 'http://ex/x/y?pp/qq', 'http://ex/x/y?pp/rr'], + ['y/z', 'http://ex/x/y?pp/qq', 'http://ex/x/y/z'], + ['local/qual@domain.org#frag', + 'mailto:local', + 'mailto:local/qual@domain.org#frag'], + ['more/qual2@domain2.org#frag', + 'mailto:local/qual1@domain1.org', + 'mailto:local/more/qual2@domain2.org#frag'], + ['y?q', 'http://ex/x/y?q', 'http://ex/x/y?q'], + ['/x/y?q', 'http://ex?p', 'http://ex/x/y?q'], + ['c/d', 'foo:a/b', 'foo:a/c/d'], + ['/c/d', 'foo:a/b', 'foo:/c/d'], + ['', 'foo:a/b?c#d', 'foo:a/b?c'], + ['b/c', 'foo:a', 'foo:b/c'], + ['../b/c', 'foo:/a/y/z', 'foo:/a/b/c'], + ['./b/c', 'foo:a', 'foo:b/c'], + ['/./b/c', 'foo:a', 'foo:/b/c'], + ['../../d', 'foo://a//b/c', 'foo://a/d'], + ['.', 'foo:a', 'foo:'], + ['..', 'foo:a', 'foo:'], + + // 50-57[cf. TimBL comments -- + // http://lists.w3.org/Archives/Public/uri/2003Feb/0028.html, + // http://lists.w3.org/Archives/Public/uri/2003Jan/0008.html) + ['abc', 'http://example/x/y%2Fz', 'http://example/x/abc'], + ['../../x%2Fabc', 'http://example/a/x/y/z', 'http://example/a/x%2Fabc'], + ['../x%2Fabc', 'http://example/a/x/y%2Fz', 'http://example/a/x%2Fabc'], + ['abc', 'http://example/x%2Fy/z', 'http://example/x%2Fy/abc'], + ['q%3Ar', 'http://ex/x/y', 'http://ex/x/q%3Ar'], + ['/x%2Fabc', 'http://example/x/y%2Fz', 'http://example/x%2Fabc'], + ['/x%2Fabc', 'http://example/x/y/z', 'http://example/x%2Fabc'], + ['/x%2Fabc', 'http://example/x/y%2Fz', 'http://example/x%2Fabc'], + + // 70-77 + ['local2@domain2', 'mailto:local1@domain1?query1', 'mailto:local2@domain2'], + ['local2@domain2?query2', + 'mailto:local1@domain1', + 'mailto:local2@domain2?query2'], + ['local2@domain2?query2', + 'mailto:local1@domain1?query1', + 'mailto:local2@domain2?query2'], + ['?query2', 'mailto:local@domain?query1', 'mailto:local@domain?query2'], + ['local@domain?query2', 'mailto:?query1', 'mailto:local@domain?query2'], + ['?query2', 'mailto:local@domain?query1', 'mailto:local@domain?query2'], + ['http://example/a/b?c/../d', 'foo:bar', 'http://example/a/b?c/../d'], + ['http://example/a/b#c/../d', 'foo:bar', 'http://example/a/b#c/../d'], + + // 82-88 + // @isaacs Disagree. Not how browsers do it. + // ['http:this', 'http://example.org/base/uri', 'http:this'], + // @isaacs Added + ['http:this', 'http://example.org/base/uri', 'http://example.org/base/this'], + ['http:this', 'http:base', 'http:this'], + ['.//g', 'f:/a', 'f://g'], + ['b/c//d/e', 'f://example.org/base/a', 'f://example.org/base/b/c//d/e'], + ['m2@example.ord/c2@example.org', + 'mid:m@example.ord/c@example.org', + 'mid:m@example.ord/m2@example.ord/c2@example.org'], + ['mini1.xml', + 'file:///C:/DEV/Haskell/lib/HXmlToolbox-3.01/examples/', + 'file:///C:/DEV/Haskell/lib/HXmlToolbox-3.01/examples/mini1.xml'], + ['../b/c', 'foo:a/y/z', 'foo:a/b/c'], + + // changeing auth + ['http://diff:auth@www.example.com', + 'http://asdf:qwer@www.example.com', + 'http://diff:auth@www.example.com/'], + + // changing port + ['https://example.com:81/', + 'https://example.com:82/', + 'https://example.com:81/'], + + // https://github.com/nodejs/node/issues/1435 + ['https://another.host.com/', + 'https://user:password@example.org/', + 'https://another.host.com/'], + ['//another.host.com/', + 'https://user:password@example.org/', + 'https://another.host.com/'], + ['http://another.host.com/', + 'https://user:password@example.org/', + 'http://another.host.com/'], + ['mailto:another.host.com', + 'mailto:user@example.org', + 'mailto:another.host.com'], + ['https://example.com/foo', + 'https://user:password@example.com', + 'https://user:password@example.com/foo'], + + // No path at all + ['#hash1', '#hash2', '#hash1'], +]; +relativeTests2.forEach(function(relativeTest) { + const a = url.resolve(relativeTest[1], relativeTest[0]); + const e = url.format(relativeTest[2]); + assert.strictEqual(a, e, + `resolve(${relativeTest[0]}, ${relativeTest[1]})` + + ` == ${e}\n actual=${a}`); +}); + +// If format and parse are inverse operations then +// resolveObject(parse(x), y) == parse(resolve(x, y)) + +// format: [from, path, expected] +relativeTests.forEach(function(relativeTest) { + let actual = url.resolveObject(url.parse(relativeTest[0]), relativeTest[1]); + let expected = url.parse(relativeTest[2]); + + + assert.deepStrictEqual(actual, expected); + + expected = relativeTest[2]; + actual = url.format(actual); + + assert.strictEqual(actual, expected, + `format(${actual}) == ${expected}\n` + + `actual: ${actual}`); +}); + +// format: [to, from, result] +// the test: ['.//g', 'f:/a', 'f://g'] is a fundamental problem +// url.parse('f:/a') does not have a host +// url.resolve('f:/a', './/g') does not have a host because you have moved +// down to the g directory. i.e. f: //g, however when this url is parsed +// f:// will indicate that the host is g which is not the case. +// it is unclear to me how to keep this information from being lost +// it may be that a pathname of ////g should collapse to /g but this seems +// to be a lot of work for an edge case. Right now I remove the test +if (relativeTests2[181][0] === './/g' && + relativeTests2[181][1] === 'f:/a' && + relativeTests2[181][2] === 'f://g') { + relativeTests2.splice(181, 1); +} +relativeTests2.forEach(function(relativeTest) { + let actual = url.resolveObject(url.parse(relativeTest[1]), relativeTest[0]); + let expected = url.parse(relativeTest[2]); + + assert.deepStrictEqual( + actual, + expected, + `expected ${inspect(expected)} but got ${inspect(actual)}` + ); + + expected = url.format(relativeTest[2]); + actual = url.format(actual); + + assert.strictEqual(actual, expected, + `format(${relativeTest[1]}) == ${expected}\n` + + `actual: ${actual}`); +}); diff --git a/cli/tests/node_compat/test/parallel/test-url-urltooptions.js b/cli/tests/node_compat/test/parallel/test-url-urltooptions.js new file mode 100644 index 00000000000000..05813f0ae58154 --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-url-urltooptions.js @@ -0,0 +1,45 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 16.11.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +'use strict'; +require('../common'); +const assert = require('assert'); +const { urlToHttpOptions } = require('url'); + +// Test urlToHttpOptions +const urlObj = new URL('http://user:pass@foo.bar.com:21/aaa/zzz?l=24#test'); +const opts = urlToHttpOptions(urlObj); +assert.strictEqual(opts instanceof URL, false); +assert.strictEqual(opts.protocol, 'http:'); +assert.strictEqual(opts.auth, 'user:pass'); +assert.strictEqual(opts.hostname, 'foo.bar.com'); +assert.strictEqual(opts.port, 21); +assert.strictEqual(opts.path, '/aaa/zzz?l=24'); +assert.strictEqual(opts.pathname, '/aaa/zzz'); +assert.strictEqual(opts.search, '?l=24'); +assert.strictEqual(opts.hash, '#test'); + +const { hostname } = urlToHttpOptions(new URL('http://[::1]:21')); +assert.strictEqual(hostname, '::1'); + +// If a WHATWG URL object is copied, it is possible that the resulting copy +// contains the Symbols that Node uses for brand checking, but not the data +// properties, which are getters. Verify that urlToHttpOptions() can handle +// such a case. +const copiedUrlObj = { ...urlObj }; +const copiedOpts = urlToHttpOptions(copiedUrlObj); +assert.strictEqual(copiedOpts instanceof URL, false); +assert.strictEqual(copiedOpts.protocol, undefined); +assert.strictEqual(copiedOpts.auth, undefined); +assert.strictEqual(copiedOpts.hostname, undefined); +// TODO(wafuwafu13): Fix `AssertionError: Values have the same structure but are not reference-equal` +// assert.strictEqual(copiedOpts.port, NaN); +assert.strictEqual(copiedOpts.path, ''); +assert.strictEqual(copiedOpts.pathname, undefined); +assert.strictEqual(copiedOpts.search, undefined); +assert.strictEqual(copiedOpts.hash, undefined); +assert.strictEqual(copiedOpts.href, undefined); diff --git a/cli/tests/node_compat/test/parallel/test-util-deprecate-invalid-code.js b/cli/tests/node_compat/test/parallel/test-util-deprecate-invalid-code.js new file mode 100644 index 00000000000000..18a5d58cbbf1eb --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-util-deprecate-invalid-code.js @@ -0,0 +1,21 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +'use strict'; + +const common = require('../common'); +const assert = require('assert'); +const util = require('util'); + +[1, true, false, null, {}].forEach((notString) => { + assert.throws(() => util.deprecate(() => {}, 'message', notString), { + code: 'ERR_INVALID_ARG_TYPE', + name: 'TypeError', + message: 'The "code" argument must be of type string.' + + common.invalidArgTypeHelper(notString) + }); +}); diff --git a/cli/tests/node_compat/test/parallel/test-util-deprecate.js b/cli/tests/node_compat/test/parallel/test-util-deprecate.js new file mode 100644 index 00000000000000..973f70e6b5a898 --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-util-deprecate.js @@ -0,0 +1,64 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +'use strict'; + +require('../common'); + +// Tests basic functionality of util.deprecate(). + +const assert = require('assert'); +const util = require('util'); + +const expectedWarnings = new Map(); + +// Emits deprecation only once if same function is called. +{ + const msg = 'fhqwhgads'; + const fn = util.deprecate(() => {}, msg); + expectedWarnings.set(msg, { code: undefined, count: 1 }); + fn(); + fn(); +} + +// Emits deprecation twice for different functions. +{ + const msg = 'sterrance'; + const fn1 = util.deprecate(() => {}, msg); + const fn2 = util.deprecate(() => {}, msg); + expectedWarnings.set(msg, { code: undefined, count: 2 }); + fn1(); + fn2(); +} + +// Emits deprecation only once if optional code is the same, even for different +// functions. +{ + const msg = 'cannonmouth'; + const code = 'deprecatesque'; + const fn1 = util.deprecate(() => {}, msg, code); + const fn2 = util.deprecate(() => {}, msg, code); + expectedWarnings.set(msg, { code, count: 1 }); + fn1(); + fn2(); + fn1(); + fn2(); +} + +process.on('warning', (warning) => { + assert.strictEqual(warning.name, 'DeprecationWarning'); + assert.ok(expectedWarnings.has(warning.message)); + const expected = expectedWarnings.get(warning.message); + assert.strictEqual(warning.code, expected.code); + expected.count = expected.count - 1; + if (expected.count === 0) + expectedWarnings.delete(warning.message); +}); + +process.on('exit', () => { + assert.deepStrictEqual(expectedWarnings, new Map()); +}); diff --git a/cli/tests/node_compat/test/parallel/test-util-format.js b/cli/tests/node_compat/test/parallel/test-util-format.js new file mode 100644 index 00000000000000..9d474c481cda62 --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-util-format.js @@ -0,0 +1,504 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 16.13.0 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. + +'use strict'; +require('../common'); +const assert = require('assert'); +const util = require('util'); +const symbol = Symbol('foo'); + +assert.strictEqual(util.format(), ''); +assert.strictEqual(util.format(''), ''); +assert.strictEqual(util.format([]), '[]'); +assert.strictEqual(util.format([0]), '[ 0 ]'); +assert.strictEqual(util.format({}), '{}'); +assert.strictEqual(util.format({ foo: 42 }), '{ foo: 42 }'); +assert.strictEqual(util.format(null), 'null'); +assert.strictEqual(util.format(true), 'true'); +assert.strictEqual(util.format(false), 'false'); +assert.strictEqual(util.format('test'), 'test'); + +// CHECKME this is for console.log() compatibility - but is it *right*? +assert.strictEqual(util.format('foo', 'bar', 'baz'), 'foo bar baz'); + +// ES6 Symbol handling +assert.strictEqual(util.format(symbol), 'Symbol(foo)'); +assert.strictEqual(util.format('foo', symbol), 'foo Symbol(foo)'); +assert.strictEqual(util.format('%s', symbol), 'Symbol(foo)'); +assert.strictEqual(util.format('%j', symbol), 'undefined'); + +// Number format specifier +assert.strictEqual(util.format('%d'), '%d'); +assert.strictEqual(util.format('%d', 42.0), '42'); +assert.strictEqual(util.format('%d', 42), '42'); +assert.strictEqual(util.format('%d', '42'), '42'); +assert.strictEqual(util.format('%d', '42.0'), '42'); +assert.strictEqual(util.format('%d', 1.5), '1.5'); +assert.strictEqual(util.format('%d', -0.5), '-0.5'); +assert.strictEqual(util.format('%d', -0.0), '-0'); +assert.strictEqual(util.format('%d', ''), '0'); +assert.strictEqual(util.format('%d', ' -0.000'), '-0'); +assert.strictEqual(util.format('%d', Symbol()), 'NaN'); +assert.strictEqual(util.format('%d', Infinity), 'Infinity'); +assert.strictEqual(util.format('%d', -Infinity), '-Infinity'); +assert.strictEqual(util.format('%d %d', 42, 43), '42 43'); +assert.strictEqual(util.format('%d %d', 42), '42 %d'); +assert.strictEqual( + util.format('%d', 1180591620717411303424), + '1.1805916207174113e+21' +); +assert.strictEqual( + util.format('%d', 1180591620717411303424n), + '1180591620717411303424n' +); +assert.strictEqual( + util.format('%d %d', 1180591620717411303424n, 12345678901234567890123n), + '1180591620717411303424n 12345678901234567890123n' +); + +// Integer format specifier +assert.strictEqual(util.format('%i'), '%i'); +assert.strictEqual(util.format('%i', 42.0), '42'); +assert.strictEqual(util.format('%i', 42), '42'); +assert.strictEqual(util.format('%i', '42'), '42'); +assert.strictEqual(util.format('%i', '42.0'), '42'); +assert.strictEqual(util.format('%i', 1.5), '1'); +assert.strictEqual(util.format('%i', -0.5), '-0'); +assert.strictEqual(util.format('%i', ''), 'NaN'); +assert.strictEqual(util.format('%i', Infinity), 'NaN'); +assert.strictEqual(util.format('%i', -Infinity), 'NaN'); +assert.strictEqual(util.format('%i', Symbol()), 'NaN'); +assert.strictEqual(util.format('%i %i', 42, 43), '42 43'); +assert.strictEqual(util.format('%i %i', 42), '42 %i'); +assert.strictEqual( + util.format('%i', 1180591620717411303424), + '1' +); +assert.strictEqual( + util.format('%i', 1180591620717411303424n), + '1180591620717411303424n' +); +assert.strictEqual( + util.format('%i %i', 1180591620717411303424n, 12345678901234567890123n), + '1180591620717411303424n 12345678901234567890123n' +); + +assert.strictEqual( + util.format('%d %i', 1180591620717411303424n, 12345678901234567890123n), + '1180591620717411303424n 12345678901234567890123n' +); + +assert.strictEqual( + util.format('%i %d', 1180591620717411303424n, 12345678901234567890123n), + '1180591620717411303424n 12345678901234567890123n' +); + +// Float format specifier +assert.strictEqual(util.format('%f'), '%f'); +assert.strictEqual(util.format('%f', 42.0), '42'); +assert.strictEqual(util.format('%f', 42), '42'); +assert.strictEqual(util.format('%f', '42'), '42'); +assert.strictEqual(util.format('%f', '-0.0'), '-0'); +assert.strictEqual(util.format('%f', '42.0'), '42'); +assert.strictEqual(util.format('%f', 1.5), '1.5'); +assert.strictEqual(util.format('%f', -0.5), '-0.5'); +assert.strictEqual(util.format('%f', Math.PI), '3.141592653589793'); +assert.strictEqual(util.format('%f', ''), 'NaN'); +assert.strictEqual(util.format('%f', Symbol('foo')), 'NaN'); +assert.strictEqual(util.format('%f', 5n), '5'); +assert.strictEqual(util.format('%f', Infinity), 'Infinity'); +assert.strictEqual(util.format('%f', -Infinity), '-Infinity'); +assert.strictEqual(util.format('%f %f', 42, 43), '42 43'); +assert.strictEqual(util.format('%f %f', 42), '42 %f'); + +// String format specifier +assert.strictEqual(util.format('%s'), '%s'); +assert.strictEqual(util.format('%s', undefined), 'undefined'); +assert.strictEqual(util.format('%s', null), 'null'); +assert.strictEqual(util.format('%s', 'foo'), 'foo'); +assert.strictEqual(util.format('%s', 42), '42'); +assert.strictEqual(util.format('%s', '42'), '42'); +assert.strictEqual(util.format('%s', -0), '-0'); +assert.strictEqual(util.format('%s', '-0.0'), '-0.0'); +assert.strictEqual(util.format('%s %s', 42, 43), '42 43'); +assert.strictEqual(util.format('%s %s', 42), '42 %s'); +assert.strictEqual(util.format('%s', 42n), '42n'); +assert.strictEqual(util.format('%s', Symbol('foo')), 'Symbol(foo)'); +assert.strictEqual(util.format('%s', true), 'true'); +assert.strictEqual(util.format('%s', { a: [1, 2, 3] }), '{ a: [Array] }'); +assert.strictEqual(util.format('%s', { toString() { return 'Foo'; } }), 'Foo'); +assert.strictEqual(util.format('%s', { toString: 5 }), '{ toString: 5 }'); +assert.strictEqual(util.format('%s', () => 5), '() => 5'); +assert.strictEqual(util.format('%s', Infinity), 'Infinity'); +assert.strictEqual(util.format('%s', -Infinity), '-Infinity'); + +// TODO(wafuwafu13): Fix +// // String format specifier including `toString` properties on the prototype. +// { +// class Foo { toString() { return 'Bar'; } } +// assert.strictEqual(util.format('%s', new Foo()), 'Bar'); +// assert.strictEqual( +// util.format('%s', Object.setPrototypeOf(new Foo(), null)), +// '[Foo: null prototype] {}' +// ); +// global.Foo = Foo; +// assert.strictEqual(util.format('%s', new Foo()), 'Bar'); +// delete global.Foo; +// class Bar { abc = true; } +// assert.strictEqual(util.format('%s', new Bar()), 'Bar { abc: true }'); +// class Foobar extends Array { aaa = true; } +// assert.strictEqual( +// util.format('%s', new Foobar(5)), +// 'Foobar(5) [ <5 empty items>, aaa: true ]' +// ); + +// // Subclassing: +// class B extends Foo {} + +// function C() {} +// C.prototype.toString = function() { +// return 'Custom'; +// }; + +// function D() { +// C.call(this); +// } +// D.prototype = Object.create(C.prototype); + +// assert.strictEqual( +// util.format('%s', new B()), +// 'Bar' +// ); +// assert.strictEqual( +// util.format('%s', new C()), +// 'Custom' +// ); +// assert.strictEqual( +// util.format('%s', new D()), +// 'Custom' +// ); + +// D.prototype.constructor = D; +// assert.strictEqual( +// util.format('%s', new D()), +// 'Custom' +// ); + +// D.prototype.constructor = null; +// assert.strictEqual( +// util.format('%s', new D()), +// 'Custom' +// ); + +// D.prototype.constructor = { name: 'Foobar' }; +// assert.strictEqual( +// util.format('%s', new D()), +// 'Custom' +// ); + +// Object.defineProperty(D.prototype, 'constructor', { +// get() { +// throw new Error(); +// }, +// configurable: true +// }); +// assert.strictEqual( +// util.format('%s', new D()), +// 'Custom' +// ); + +// assert.strictEqual( +// util.format('%s', Object.create(null)), +// '[Object: null prototype] {}' +// ); +// } + +// JSON format specifier +assert.strictEqual(util.format('%j'), '%j'); +assert.strictEqual(util.format('%j', 42), '42'); +assert.strictEqual(util.format('%j', '42'), '"42"'); +assert.strictEqual(util.format('%j %j', 42, 43), '42 43'); +assert.strictEqual(util.format('%j %j', 42), '42 %j'); + +// Object format specifier +const obj = { + foo: 'bar', + foobar: 1, + func: function() {} +}; +const nestedObj = { + foo: 'bar', + foobar: { + foo: 'bar', + func: function() {} + } +}; +const nestedObj2 = { + foo: 'bar', + foobar: 1, + func: [{ a: function() {} }] +}; +assert.strictEqual(util.format('%o'), '%o'); +assert.strictEqual(util.format('%o', 42), '42'); +assert.strictEqual(util.format('%o', 'foo'), '\'foo\''); +assert.strictEqual( + util.format('%o', obj), + '{\n' + + ' foo: \'bar\',\n' + + ' foobar: 1,\n' + + ' func: [Function: func] {\n' + + ' [length]: 0,\n' + + ' [name]: \'func\',\n' + + ' [prototype]: { [constructor]: [Circular *1] }\n' + + ' }\n' + + '}'); +assert.strictEqual( + util.format('%o', nestedObj2), + '{\n' + + ' foo: \'bar\',\n' + + ' foobar: 1,\n' + + ' func: [\n' + + ' {\n' + + ' a: [Function: a] {\n' + + ' [length]: 0,\n' + + ' [name]: \'a\',\n' + + ' [prototype]: { [constructor]: [Circular *1] }\n' + + ' }\n' + + ' },\n' + + ' [length]: 1\n' + + ' ]\n' + + '}'); +assert.strictEqual( + util.format('%o', nestedObj), + '{\n' + + ' foo: \'bar\',\n' + + ' foobar: {\n' + + ' foo: \'bar\',\n' + + ' func: [Function: func] {\n' + + ' [length]: 0,\n' + + ' [name]: \'func\',\n' + + ' [prototype]: { [constructor]: [Circular *1] }\n' + + ' }\n' + + ' }\n' + + '}'); +assert.strictEqual( + util.format('%o %o', obj, obj), + '{\n' + + ' foo: \'bar\',\n' + + ' foobar: 1,\n' + + ' func: [Function: func] {\n' + + ' [length]: 0,\n' + + ' [name]: \'func\',\n' + + ' [prototype]: { [constructor]: [Circular *1] }\n' + + ' }\n' + + '} {\n' + + ' foo: \'bar\',\n' + + ' foobar: 1,\n' + + ' func: [Function: func] {\n' + + ' [length]: 0,\n' + + ' [name]: \'func\',\n' + + ' [prototype]: { [constructor]: [Circular *1] }\n' + + ' }\n' + + '}'); +assert.strictEqual( + util.format('%o %o', obj), + '{\n' + + ' foo: \'bar\',\n' + + ' foobar: 1,\n' + + ' func: [Function: func] {\n' + + ' [length]: 0,\n' + + ' [name]: \'func\',\n' + + ' [prototype]: { [constructor]: [Circular *1] }\n' + + ' }\n' + + '} %o'); + +assert.strictEqual(util.format('%O'), '%O'); +assert.strictEqual(util.format('%O', 42), '42'); +assert.strictEqual(util.format('%O', 'foo'), '\'foo\''); +assert.strictEqual( + util.format('%O', obj), + '{ foo: \'bar\', foobar: 1, func: [Function: func] }'); +assert.strictEqual( + util.format('%O', nestedObj), + '{ foo: \'bar\', foobar: { foo: \'bar\', func: [Function: func] } }'); +assert.strictEqual( + util.format('%O %O', obj, obj), + '{ foo: \'bar\', foobar: 1, func: [Function: func] } ' + + '{ foo: \'bar\', foobar: 1, func: [Function: func] }'); +assert.strictEqual( + util.format('%O %O', obj), + '{ foo: \'bar\', foobar: 1, func: [Function: func] } %O'); + +// Various format specifiers +assert.strictEqual(util.format('%%s%s', 'foo'), '%sfoo'); +assert.strictEqual(util.format('%s:%s'), '%s:%s'); +assert.strictEqual(util.format('%s:%s', undefined), 'undefined:%s'); +assert.strictEqual(util.format('%s:%s', 'foo'), 'foo:%s'); +assert.strictEqual(util.format('%s:%i', 'foo'), 'foo:%i'); +assert.strictEqual(util.format('%s:%f', 'foo'), 'foo:%f'); +assert.strictEqual(util.format('%s:%s', 'foo', 'bar'), 'foo:bar'); +assert.strictEqual(util.format('%s:%s', 'foo', 'bar', 'baz'), 'foo:bar baz'); +assert.strictEqual(util.format('%%%s%%', 'hi'), '%hi%'); +assert.strictEqual(util.format('%%%s%%%%', 'hi'), '%hi%%'); +assert.strictEqual(util.format('%sbc%%def', 'a'), 'abc%def'); +assert.strictEqual(util.format('%d:%d', 12, 30), '12:30'); +assert.strictEqual(util.format('%d:%d', 12), '12:%d'); +assert.strictEqual(util.format('%d:%d'), '%d:%d'); +assert.strictEqual(util.format('%i:%i', 12, 30), '12:30'); +assert.strictEqual(util.format('%i:%i', 12), '12:%i'); +assert.strictEqual(util.format('%i:%i'), '%i:%i'); +assert.strictEqual(util.format('%f:%f', 12, 30), '12:30'); +assert.strictEqual(util.format('%f:%f', 12), '12:%f'); +assert.strictEqual(util.format('%f:%f'), '%f:%f'); +assert.strictEqual(util.format('o: %j, a: %j', {}, []), 'o: {}, a: []'); +assert.strictEqual(util.format('o: %j, a: %j', {}), 'o: {}, a: %j'); +assert.strictEqual(util.format('o: %j, a: %j'), 'o: %j, a: %j'); +assert.strictEqual(util.format('o: %o, a: %O', {}, []), 'o: {}, a: []'); +assert.strictEqual(util.format('o: %o, a: %o', {}), 'o: {}, a: %o'); +assert.strictEqual(util.format('o: %O, a: %O'), 'o: %O, a: %O'); + + +// Invalid format specifiers +assert.strictEqual(util.format('a% b', 'x'), 'a% b x'); +assert.strictEqual(util.format('percent: %d%, fraction: %d', 10, 0.1), + 'percent: 10%, fraction: 0.1'); +assert.strictEqual(util.format('abc%', 1), 'abc% 1'); + +// Additional arguments after format specifiers +assert.strictEqual(util.format('%i', 1, 'number'), '1 number'); +assert.strictEqual(util.format('%i', 1, () => {}), '1 [Function (anonymous)]'); + +// %c from https://console.spec.whatwg.org/ +assert.strictEqual(util.format('%c'), '%c'); +assert.strictEqual(util.format('%cab'), '%cab'); +assert.strictEqual(util.format('%cab', 'color: blue'), 'ab'); +assert.strictEqual(util.format('%cab', 'color: blue', 'c'), 'ab c'); + +{ + const o = {}; + o.o = o; + assert.strictEqual(util.format('%j', o), '[Circular]'); +} + +{ + const o = { + toJSON() { + throw new Error('Not a circular object but still not serializable'); + } + }; + assert.throws(() => util.format('%j', o), + /^Error: Not a circular object but still not serializable$/); +} + +// Errors +const err = new Error('foo'); +assert.strictEqual(util.format(err), err.stack); +class CustomError extends Error { + constructor(msg) { + super(); + Object.defineProperty(this, 'message', + { value: msg, enumerable: false }); + Object.defineProperty(this, 'name', + { value: 'CustomError', enumerable: false }); + Error.captureStackTrace(this, CustomError); + } +} +const customError = new CustomError('bar'); +assert.strictEqual(util.format(customError), customError.stack); +// Doesn't capture stack trace +function BadCustomError(msg) { + Error.call(this); + Object.defineProperty(this, 'message', + { value: msg, enumerable: false }); + Object.defineProperty(this, 'name', + { value: 'BadCustomError', enumerable: false }); +} +Object.setPrototypeOf(BadCustomError.prototype, Error.prototype); +Object.setPrototypeOf(BadCustomError, Error); +assert.strictEqual(util.format(new BadCustomError('foo')), + '[BadCustomError: foo]'); + +// The format of arguments should not depend on type of the first argument +assert.strictEqual(util.format('1', '1'), '1 1'); +assert.strictEqual(util.format(1, '1'), '1 1'); +assert.strictEqual(util.format('1', 1), '1 1'); +assert.strictEqual(util.format(1, -0), '1 -0'); +assert.strictEqual(util.format('1', () => {}), '1 [Function (anonymous)]'); +assert.strictEqual(util.format(1, () => {}), '1 [Function (anonymous)]'); +assert.strictEqual(util.format('1', "'"), "1 '"); +assert.strictEqual(util.format(1, "'"), "1 '"); +assert.strictEqual(util.format('1', 'number'), '1 number'); +assert.strictEqual(util.format(1, 'number'), '1 number'); +assert.strictEqual(util.format(5n), '5n'); +assert.strictEqual(util.format(5n, 5n), '5n 5n'); + +// Check `formatWithOptions`. +assert.strictEqual( + util.formatWithOptions( + { colors: true }, + true, undefined, Symbol(), 1, 5n, null, 'foobar' + ), + '\u001b[33mtrue\u001b[39m ' + + '\u001b[90mundefined\u001b[39m ' + + '\u001b[32mSymbol()\u001b[39m ' + + '\u001b[33m1\u001b[39m ' + + '\u001b[33m5n\u001b[39m ' + + '\u001b[1mnull\u001b[22m ' + + 'foobar' +); + +// TODO(wafuwafu13): Fix +// assert.strictEqual( +// util.format(new SharedArrayBuffer(4)), +// 'SharedArrayBuffer { [Uint8Contents]: <00 00 00 00>, byteLength: 4 }' +// ); + +assert.strictEqual( + util.formatWithOptions( + { colors: true, compact: 3 }, + '%s', [ 1, { a: true }] + ), + '[ 1, [Object] ]' +); + +[ + undefined, + null, + false, + 5n, + 5, + 'test', + Symbol(), +].forEach((invalidOptions) => { + assert.throws(() => { + util.formatWithOptions(invalidOptions, { a: true }); + }, { + code: 'ERR_INVALID_ARG_TYPE', + message: /"inspectOptions".+object/ + }); +}); diff --git a/cli/tests/node_compat/test/parallel/test-util-inherits.js b/cli/tests/node_compat/test/parallel/test-util-inherits.js new file mode 100644 index 00000000000000..8403f475dafda4 --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-util-inherits.js @@ -0,0 +1,117 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +'use strict'; + +require('../common'); +const assert = require('assert'); +const { inherits } = require('util'); + +// Super constructor +function A() { + this._a = 'a'; +} +A.prototype.a = function() { return this._a; }; + +// One level of inheritance +function B(value) { + A.call(this); + this._b = value; +} +inherits(B, A); +B.prototype.b = function() { return this._b; }; + +assert.deepStrictEqual( + Object.getOwnPropertyDescriptor(B, 'super_'), + { + value: A, + enumerable: false, + configurable: true, + writable: true + } +); + +const b = new B('b'); +assert.strictEqual(b.a(), 'a'); +assert.strictEqual(b.b(), 'b'); +assert.strictEqual(b.constructor, B); + +// Two levels of inheritance +function C() { + B.call(this, 'b'); + this._c = 'c'; +} +inherits(C, B); +C.prototype.c = function() { return this._c; }; +C.prototype.getValue = function() { return this.a() + this.b() + this.c(); }; + +assert.strictEqual(C.super_, B); + +const c = new C(); +assert.strictEqual(c.getValue(), 'abc'); +assert.strictEqual(c.constructor, C); + +// Inherits can be called after setting prototype properties +function D() { + C.call(this); + this._d = 'd'; +} + +D.prototype.d = function() { return this._d; }; +inherits(D, C); + +assert.strictEqual(D.super_, C); + +const d = new D(); +assert.strictEqual(d.c(), 'c'); +assert.strictEqual(d.d(), 'd'); +assert.strictEqual(d.constructor, D); + +// ES6 classes can inherit from a constructor function +class E { + constructor() { + D.call(this); + this._e = 'e'; + } + e() { return this._e; } +} +inherits(E, D); + +assert.strictEqual(E.super_, D); + +const e = new E(); +assert.strictEqual(e.getValue(), 'abc'); +assert.strictEqual(e.d(), 'd'); +assert.strictEqual(e.e(), 'e'); +assert.strictEqual(e.constructor, E); + +// Should throw with invalid arguments +assert.throws(() => { + inherits(A, {}); +}, { + code: 'ERR_INVALID_ARG_TYPE', + name: 'TypeError', + message: 'The "superCtor.prototype" property must be of type object. ' + + 'Received undefined' +}); + +assert.throws(() => { + inherits(A, null); +}, { + code: 'ERR_INVALID_ARG_TYPE', + name: 'TypeError', + message: 'The "superCtor" argument must be of type function. ' + + 'Received null' +}); + +assert.throws(() => { + inherits(null, A); +}, { + code: 'ERR_INVALID_ARG_TYPE', + name: 'TypeError', + message: 'The "ctor" argument must be of type function. Received null' +}); diff --git a/cli/tests/node_compat/test/parallel/test-util-inspect-long-running.js b/cli/tests/node_compat/test/parallel/test-util-inspect-long-running.js new file mode 100644 index 00000000000000..c4e6ec559e4910 --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-util-inspect-long-running.js @@ -0,0 +1,27 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +'use strict'; + +require('../common'); + +// Test that huge objects don't crash due to exceeding the maximum heap size. + +const util = require('util'); + +// Create a difficult to stringify object. Without the artificial limitation +// this would crash or throw an maximum string size error. +let last = {}; +const obj = last; + +for (let i = 0; i < 1000; i++) { + last.next = { circular: obj, last, obj: { a: 1, b: 2, c: true } }; + last = last.next; + obj[i] = last; +} + +util.inspect(obj, { depth: Infinity }); diff --git a/cli/tests/node_compat/test/parallel/test-util-inspect-namespace.js b/cli/tests/node_compat/test/parallel/test-util-inspect-namespace.js new file mode 100644 index 00000000000000..786f0567198677 --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-util-inspect-namespace.js @@ -0,0 +1,28 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 16.13.0 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +// Flags: --experimental-vm-modules +'use strict'; +const common = require('../common'); +const assert = require('assert'); + +// TODO(wafuwafu13): Implement 'vm' +// const { SourceTextModule } = require('vm'); +const { inspect } = require('util'); + +// (async () => { +// const m = new SourceTextModule('export const a = 1; export var b = 2'); +// await m.link(() => 0); +// assert.strictEqual( +// inspect(m.namespace), +// '[Module: null prototype] { a: , b: undefined }'); +// await m.evaluate(); +// assert.strictEqual( +// inspect(m.namespace), +// '[Module: null prototype] { a: 1, b: 2 }' +// ); +// })().then(common.mustCall()); diff --git a/cli/tests/node_compat/test/parallel/test-util-inspect-proxy.js b/cli/tests/node_compat/test/parallel/test-util-inspect-proxy.js new file mode 100644 index 00000000000000..ef78ab07a2f5c3 --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-util-inspect-proxy.js @@ -0,0 +1,172 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 16.13.0 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +// Flags: --expose-internals +'use strict'; + +require('../common'); +const assert = require('assert'); +const util = require('util'); +// TODO(wafuwafu13): Implement 'internal/test/binding' +// const { internalBinding } = require('internal/test/binding'); +// const processUtil = internalBinding('util'); +const opts = { showProxy: true }; + +// let proxyObj; +// let called = false; +// const target = { +// [util.inspect.custom](depth, { showProxy }) { +// if (showProxy === false) { +// called = true; +// if (proxyObj !== this) { +// throw new Error('Failed'); +// } +// } +// return [1, 2, 3]; +// } +// }; + +// // TODO(wafuwafu13): Fix Uncaught Error +// const handler = { +// getPrototypeOf() { throw new Error('getPrototypeOf'); }, +// setPrototypeOf() { throw new Error('setPrototypeOf'); }, +// isExtensible() { throw new Error('isExtensible'); }, +// preventExtensions() { throw new Error('preventExtensions'); }, +// getOwnPropertyDescriptor() { throw new Error('getOwnPropertyDescriptor'); }, +// defineProperty() { throw new Error('defineProperty'); }, +// has() { throw new Error('has'); }, +// get() { throw new Error('get'); }, +// set() { throw new Error('set'); }, +// deleteProperty() { throw new Error('deleteProperty'); }, +// ownKeys() { throw new Error('ownKeys'); }, +// apply() { throw new Error('apply'); }, +// construct() { throw new Error('construct'); } +// }; +// proxyObj = new Proxy(target, handler); + +// // Inspecting the proxy should not actually walk it's properties +// util.inspect(proxyObj, opts); + +// // Make sure inspecting object does not trigger any proxy traps. +// util.format('%s', proxyObj); + +// TODO(wafuwafu13): Implement processUtil +// // getProxyDetails is an internal method, not intended for public use. +// // This is here to test that the internals are working correctly. +// let details = processUtil.getProxyDetails(proxyObj, true); +// assert.strictEqual(target, details[0]); +// assert.strictEqual(handler, details[1]); + +// details = processUtil.getProxyDetails(proxyObj); +// assert.strictEqual(target, details[0]); +// assert.strictEqual(handler, details[1]); + +// details = processUtil.getProxyDetails(proxyObj, false); +// assert.strictEqual(target, details); + +// assert.strictEqual( +// util.inspect(proxyObj, opts), +// 'Proxy [\n' + +// ' [ 1, 2, 3 ],\n' + +// ' {\n' + +// ' getPrototypeOf: [Function: getPrototypeOf],\n' + +// ' setPrototypeOf: [Function: setPrototypeOf],\n' + +// ' isExtensible: [Function: isExtensible],\n' + +// ' preventExtensions: [Function: preventExtensions],\n' + +// ' getOwnPropertyDescriptor: [Function: getOwnPropertyDescriptor],\n' + +// ' defineProperty: [Function: defineProperty],\n' + +// ' has: [Function: has],\n' + +// ' get: [Function: get],\n' + +// ' set: [Function: set],\n' + +// ' deleteProperty: [Function: deleteProperty],\n' + +// ' ownKeys: [Function: ownKeys],\n' + +// ' apply: [Function: apply],\n' + +// ' construct: [Function: construct]\n' + +// ' }\n' + +// ']' +// ); + +// TODO(wafuwafu13): Implement processUtil +// // Using getProxyDetails with non-proxy returns undefined +// assert.strictEqual(processUtil.getProxyDetails({}), undefined); + +// // Inspecting a proxy without the showProxy option set to true should not +// // trigger any proxy handlers. +// assert.strictEqual(util.inspect(proxyObj), '[ 1, 2, 3 ]'); +// assert(called); + +// Yo dawg, I heard you liked Proxy so I put a Proxy +// inside your Proxy that proxies your Proxy's Proxy. +const proxy1 = new Proxy({}, {}); +const proxy2 = new Proxy(proxy1, {}); +const proxy3 = new Proxy(proxy2, proxy1); +const proxy4 = new Proxy(proxy1, proxy2); +const proxy5 = new Proxy(proxy3, proxy4); +const proxy6 = new Proxy(proxy5, proxy5); +const expected0 = '{}'; +const expected1 = 'Proxy [ {}, {} ]'; +const expected2 = 'Proxy [ Proxy [ {}, {} ], {} ]'; +const expected3 = 'Proxy [ Proxy [ Proxy [ {}, {} ], {} ], Proxy [ {}, {} ] ]'; +const expected4 = 'Proxy [ Proxy [ {}, {} ], Proxy [ Proxy [ {}, {} ], {} ] ]'; +const expected5 = 'Proxy [\n ' + + 'Proxy [ Proxy [ Proxy [Array], {} ], Proxy [ {}, {} ] ],\n' + + ' Proxy [ Proxy [ {}, {} ], Proxy [ Proxy [Array], {} ] ]' + + '\n]'; +const expected6 = 'Proxy [\n' + + ' Proxy [\n' + + ' Proxy [ Proxy [Array], Proxy [Array] ],\n' + + ' Proxy [ Proxy [Array], Proxy [Array] ]\n' + + ' ],\n' + + ' Proxy [\n' + + ' Proxy [ Proxy [Array], Proxy [Array] ],\n' + + ' Proxy [ Proxy [Array], Proxy [Array] ]\n' + + ' ]\n' + + ']'; +// assert.strictEqual( +// util.inspect(proxy1, { showProxy: 1, depth: null }), +// expected1); +// assert.strictEqual(util.inspect(proxy2, opts), expected2); +// assert.strictEqual(util.inspect(proxy3, opts), expected3); +// assert.strictEqual(util.inspect(proxy4, opts), expected4); +// assert.strictEqual(util.inspect(proxy5, opts), expected5); +// assert.strictEqual(util.inspect(proxy6, opts), expected6); +// assert.strictEqual(util.inspect(proxy1), expected0); +// assert.strictEqual(util.inspect(proxy2), expected0); +// assert.strictEqual(util.inspect(proxy3), expected0); +// assert.strictEqual(util.inspect(proxy4), expected0); +// assert.strictEqual(util.inspect(proxy5), expected0); +// assert.strictEqual(util.inspect(proxy6), expected0); + +// // Just for fun, let's create a Proxy using Arrays. +// const proxy7 = new Proxy([], []); +// const expected7 = 'Proxy [ [], [] ]'; +// assert.strictEqual(util.inspect(proxy7, opts), expected7); +// assert.strictEqual(util.inspect(proxy7), '[]'); + +// // Now we're just getting silly, right? +// const proxy8 = new Proxy(Date, []); +// const proxy9 = new Proxy(Date, String); +// const expected8 = 'Proxy [ [Function: Date], [] ]'; +// const expected9 = 'Proxy [ [Function: Date], [Function: String] ]'; +// assert.strictEqual(util.inspect(proxy8, opts), expected8); +// assert.strictEqual(util.inspect(proxy9, opts), expected9); +// assert.strictEqual(util.inspect(proxy8), '[Function: Date]'); +// assert.strictEqual(util.inspect(proxy9), '[Function: Date]'); + +// const proxy10 = new Proxy(() => {}, {}); +// const proxy11 = new Proxy(() => {}, { +// get() { +// return proxy11; +// }, +// apply() { +// return proxy11; +// } +// }); +// const expected10 = '[Function (anonymous)]'; +// const expected11 = '[Function (anonymous)]'; +// assert.strictEqual(util.inspect(proxy10), expected10); +// assert.strictEqual(util.inspect(proxy11), expected11); diff --git a/cli/tests/node_compat/test/parallel/test-util-inspect.js b/cli/tests/node_compat/test/parallel/test-util-inspect.js new file mode 100644 index 00000000000000..fd3243ec5e9112 --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-util-inspect.js @@ -0,0 +1,3200 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 16.13.0 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +// Flags: --expose-internals +// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. +'use strict'; +const common = require('../common'); +const assert = require('assert'); +// TODO(wafuwafu13): Implement 'internal/test/binding' +// const { internalBinding } = require('internal/test/binding'); +// const JSStream = internalBinding('js_stream').JSStream; +const util = require('util'); +// TODO(wafuwafu13): Implement 'vm' +// const vm = require('vm'); +// TODO(wafuwafu13): Implement 'v8' +// const v8 = require('v8'); +// TODO(wafuwafu13): Implement 'internal/test/binding' +// const { previewEntries } = internalBinding('util'); +const { inspect } = util; +// TODO(wafuwafu13): Implement MessageChannel +// const { MessageChannel } = require('worker_threads'); + +assert.strictEqual(util.inspect(1), '1'); +assert.strictEqual(util.inspect(false), 'false'); +assert.strictEqual(util.inspect(''), "''"); +assert.strictEqual(util.inspect('hello'), "'hello'"); +assert.strictEqual(util.inspect(function abc() {}), '[Function: abc]'); +assert.strictEqual(util.inspect(() => {}), '[Function (anonymous)]'); +assert.strictEqual( + util.inspect(async function() {}), + '[AsyncFunction (anonymous)]' +); +assert.strictEqual(util.inspect(async () => {}), '[AsyncFunction (anonymous)]'); + +// Special function inspection. +{ + const fn = (() => function*() {})(); + assert.strictEqual( + util.inspect(fn), + '[GeneratorFunction (anonymous)]' + ); + // TODO(wafuwafu13): Fix + // assert.strictEqual( + // util.inspect(async function* abc() {}), + // '[AsyncGeneratorFunction: abc]' + // ); + Object.setPrototypeOf(fn, Object.getPrototypeOf(async () => {})); + // TODO(wafuwafu13): Fix + // assert.strictEqual( + // util.inspect(fn), + // '[GeneratorFunction (anonymous)] AsyncFunction' + // ); + Object.defineProperty(fn, 'name', { value: 5, configurable: true }); + // TODO(wafuwafu13): Fix + // assert.strictEqual( + // util.inspect(fn), + // '[GeneratorFunction: 5] AsyncFunction' + // ); + Object.defineProperty(fn, Symbol.toStringTag, { + value: 'Foobar', + configurable: true + }); + // TODO(wafuwafu13): Fix + // assert.strictEqual( + // util.inspect({ ['5']: fn }), + // "{ '5': [GeneratorFunction: 5] AsyncFunction [Foobar] }" + // ); + Object.defineProperty(fn, 'name', { value: '5', configurable: true }); + Object.setPrototypeOf(fn, null); + // TODO(wafuwafu13): Fix + // assert.strictEqual( + // util.inspect(fn), + // '[GeneratorFunction (null prototype): 5] [Foobar]' + // ); + // assert.strictEqual( + // util.inspect({ ['5']: fn }), + // "{ '5': [GeneratorFunction (null prototype): 5] [Foobar] }" + // ); +} + +assert.strictEqual(util.inspect(undefined), 'undefined'); +assert.strictEqual(util.inspect(null), 'null'); +assert.strictEqual(util.inspect(/foo(bar\n)?/gi), '/foo(bar\\n)?/gi'); +assert.strictEqual( + util.inspect(new Date('Sun, 14 Feb 2010 11:48:40 GMT')), + new Date('2010-02-14T12:48:40+01:00').toISOString() +); +assert.strictEqual(util.inspect(new Date('')), (new Date('')).toString()); +assert.strictEqual(util.inspect('\n\x01'), "'\\n\\x01'"); +assert.strictEqual( + util.inspect(`${Array(75).fill(1)}'\n\x1d\n\x03\x85\x7f\x7e\x9f\xa0`), + // eslint-disable-next-line no-irregular-whitespace + `"${Array(75).fill(1)}'\\n" +\n '\\x1D\\n' +\n '\\x03\\x85\\x7F~\\x9F '` +); +assert.strictEqual(util.inspect([]), '[]'); +assert.strictEqual(util.inspect(Object.create([])), 'Array {}'); +assert.strictEqual(util.inspect([1, 2]), '[ 1, 2 ]'); +assert.strictEqual(util.inspect([1, [2, 3]]), '[ 1, [ 2, 3 ] ]'); +assert.strictEqual(util.inspect({}), '{}'); +assert.strictEqual(util.inspect({ a: 1 }), '{ a: 1 }'); +assert.strictEqual(util.inspect({ a: function() {} }), '{ a: [Function: a] }'); +assert.strictEqual(util.inspect({ a: () => {} }), '{ a: [Function: a] }'); +// eslint-disable-next-line func-name-matching +assert.strictEqual(util.inspect({ a: async function abc() {} }), + '{ a: [AsyncFunction: abc] }'); +assert.strictEqual(util.inspect({ a: async () => {} }), + '{ a: [AsyncFunction: a] }'); +assert.strictEqual(util.inspect({ a: function*() {} }), + '{ a: [GeneratorFunction: a] }'); +assert.strictEqual(util.inspect({ a: 1, b: 2 }), '{ a: 1, b: 2 }'); +assert.strictEqual(util.inspect({ 'a': {} }), '{ a: {} }'); +assert.strictEqual(util.inspect({ 'a': { 'b': 2 } }), '{ a: { b: 2 } }'); +assert.strictEqual(util.inspect({ 'a': { 'b': { 'c': { 'd': 2 } } } }), + '{ a: { b: { c: [Object] } } }'); +assert.strictEqual( + util.inspect({ 'a': { 'b': { 'c': { 'd': 2 } } } }, false, null), + '{\n a: { b: { c: { d: 2 } } }\n}'); +// TODO(wafuwafu13): Fix +// assert.strictEqual(util.inspect([1, 2, 3], true), '[ 1, 2, 3, [length]: 3 ]'); +assert.strictEqual(util.inspect({ 'a': { 'b': { 'c': 2 } } }, false, 0), + '{ a: [Object] }'); +assert.strictEqual(util.inspect({ 'a': { 'b': { 'c': 2 } } }, false, 1), + '{ a: { b: [Object] } }'); +assert.strictEqual(util.inspect({ 'a': { 'b': ['c'] } }, false, 1), + '{ a: { b: [Array] } }'); +// TODO(wafuwafu13): Fix +// assert.strictEqual(util.inspect(new Uint8Array(0)), 'Uint8Array(0) []'); +// assert(inspect(new Uint8Array(0), { showHidden: true }).includes('[buffer]')); +assert.strictEqual( + util.inspect( + Object.create( + {}, + { visible: { value: 1, enumerable: true }, hidden: { value: 2 } } + ) + ), + '{ visible: 1 }' +); +// TODO(wafuwafu13): Fix +// assert.strictEqual( +// util.inspect( +// Object.assign(new String('hello'), { [Symbol('foo')]: 123 }), +// { showHidden: true } +// ), +// "[String: 'hello'] { [length]: 5, [Symbol(foo)]: 123 }" +// ); + +// TODO(wafuwafu13): Implement JSStream +// assert.match(util.inspect((new JSStream())._externalStream), +// /^\[External: [0-9a-f]+\]$/); + +{ + const regexp = /regexp/; + regexp.aprop = 42; + assert.strictEqual(util.inspect({ a: regexp }, false, 0), '{ a: /regexp/ }'); +} + +assert.match( + util.inspect({ a: { a: { a: { a: {} } } } }, undefined, undefined, true), + /Object/ +); +assert.doesNotMatch( + util.inspect({ a: { a: { a: { a: {} } } } }, undefined, null, true), + /Object/ +); + +// TODO(wafuwafu13): Fix +// { +// const showHidden = true; +// const ab = new Uint8Array([1, 2, 3, 4]).buffer; +// const dv = new DataView(ab, 1, 2); +// assert.strictEqual( +// util.inspect(ab, showHidden), +// 'ArrayBuffer { [Uint8Contents]: <01 02 03 04>, byteLength: 4 }' +// ); +// assert.strictEqual(util.inspect(new DataView(ab, 1, 2), showHidden), +// 'DataView {\n' + +// ' byteLength: 2,\n' + +// ' byteOffset: 1,\n' + +// ' buffer: ArrayBuffer {' + +// ' [Uint8Contents]: <01 02 03 04>, byteLength: 4 }\n}'); +// assert.strictEqual( +// util.inspect(ab, showHidden), +// 'ArrayBuffer { [Uint8Contents]: <01 02 03 04>, byteLength: 4 }' +// ); +// assert.strictEqual(util.inspect(dv, showHidden), +// 'DataView {\n' + +// ' byteLength: 2,\n' + +// ' byteOffset: 1,\n' + +// ' buffer: ArrayBuffer { [Uint8Contents]: ' + +// '<01 02 03 04>, byteLength: 4 }\n}'); +// ab.x = 42; +// dv.y = 1337; +// assert.strictEqual(util.inspect(ab, showHidden), +// 'ArrayBuffer { [Uint8Contents]: <01 02 03 04>, ' + +// 'byteLength: 4, x: 42 }'); +// assert.strictEqual(util.inspect(dv, showHidden), +// 'DataView {\n' + +// ' byteLength: 2,\n' + +// ' byteOffset: 1,\n' + +// ' buffer: ArrayBuffer { [Uint8Contents]: <01 02 03 04>,' + +// ' byteLength: 4, x: 42 },\n' + +// ' y: 1337\n}'); +// } + +// TODO(wafuwafu13): Implement `MessageChannel` +// { +// const ab = new ArrayBuffer(42); +// assert.strictEqual(ab.byteLength, 42); +// new MessageChannel().port1.postMessage(ab, [ ab ]); +// assert.strictEqual(ab.byteLength, 0); +// assert.strictEqual(util.inspect(ab), +// 'ArrayBuffer { (detached), byteLength: 0 }'); +// } + +// TODO(wafuwafu13): Fix +// // Truncate output for ArrayBuffers using plural or singular bytes +// { +// const ab = new ArrayBuffer(3); +// assert.strictEqual(util.inspect(ab, { showHidden: true, maxArrayLength: 2 }), +// 'ArrayBuffer { [Uint8Contents]' + +// ': <00 00 ... 1 more byte>, byteLength: 3 }'); +// assert.strictEqual(util.inspect(ab, { showHidden: true, maxArrayLength: 1 }), +// 'ArrayBuffer { [Uint8Contents]' + +// ': <00 ... 2 more bytes>, byteLength: 3 }'); +// } + +// TODO(wafuwafu13): Implement 'vm' +// // Now do the same checks but from a different context. +// { +// const showHidden = false; +// const ab = vm.runInNewContext('new ArrayBuffer(4)'); +// const dv = vm.runInNewContext('new DataView(ab, 1, 2)', { ab }); +// assert.strictEqual( +// util.inspect(ab, showHidden), +// 'ArrayBuffer { [Uint8Contents]: <00 00 00 00>, byteLength: 4 }' +// ); +// assert.strictEqual(util.inspect(new DataView(ab, 1, 2), showHidden), +// 'DataView {\n' + +// ' byteLength: 2,\n' + +// ' byteOffset: 1,\n' + +// ' buffer: ArrayBuffer { [Uint8Contents]: <00 00 00 00>,' + +// ' byteLength: 4 }\n}'); +// assert.strictEqual( +// util.inspect(ab, showHidden), +// 'ArrayBuffer { [Uint8Contents]: <00 00 00 00>, byteLength: 4 }' +// ); +// assert.strictEqual(util.inspect(dv, showHidden), +// 'DataView {\n' + +// ' byteLength: 2,\n' + +// ' byteOffset: 1,\n' + +// ' buffer: ArrayBuffer { [Uint8Contents]: <00 00 00 00>,' + +// ' byteLength: 4 }\n}'); +// ab.x = 42; +// dv.y = 1337; +// assert.strictEqual(util.inspect(ab, showHidden), +// 'ArrayBuffer { [Uint8Contents]: <00 00 00 00>, ' + +// 'byteLength: 4, x: 42 }'); +// assert.strictEqual(util.inspect(dv, showHidden), +// 'DataView {\n' + +// ' byteLength: 2,\n' + +// ' byteOffset: 1,\n' + +// ' buffer: ArrayBuffer { [Uint8Contents]: <00 00 00 00>,' + +// ' byteLength: 4, x: 42 },\n' + +// ' y: 1337\n}'); +// } + +// TODO(wafuwafu13): Fix +// [ Float32Array, +// Float64Array, +// Int16Array, +// Int32Array, +// Int8Array, +// Uint16Array, +// Uint32Array, +// Uint8Array, +// Uint8ClampedArray ].forEach((constructor) => { +// const length = 2; +// const byteLength = length * constructor.BYTES_PER_ELEMENT; +// const array = new constructor(new ArrayBuffer(byteLength), 0, length); +// array[0] = 65; +// array[1] = 97; +// assert.strictEqual( +// util.inspect(array, { showHidden: true }), +// `${constructor.name}(${length}) [\n` + +// ' 65,\n' + +// ' 97,\n' + +// ` [BYTES_PER_ELEMENT]: ${constructor.BYTES_PER_ELEMENT},\n` + +// ` [length]: ${length},\n` + +// ` [byteLength]: ${byteLength},\n` + +// ' [byteOffset]: 0,\n' + +// ` [buffer]: ArrayBuffer { byteLength: ${byteLength} }\n]`); +// assert.strictEqual( +// util.inspect(array, false), +// `${constructor.name}(${length}) [ 65, 97 ]` +// ); +// }); + +// TODO(wafuwafu13): Implement 'vm' +// // Now check that declaring a TypedArray in a different context works the same. +// [ Float32Array, +// Float64Array, +// Int16Array, +// Int32Array, +// Int8Array, +// Uint16Array, +// Uint32Array, +// Uint8Array, +// Uint8ClampedArray ].forEach((constructor) => { +// const length = 2; +// const byteLength = length * constructor.BYTES_PER_ELEMENT; +// const array = vm.runInNewContext( +// 'new constructor(new ArrayBuffer(byteLength), 0, length)', +// { constructor, byteLength, length } +// ); +// array[0] = 65; +// array[1] = 97; +// assert.strictEqual( +// util.inspect(array, true), +// `${constructor.name}(${length}) [\n` + +// ' 65,\n' + +// ' 97,\n' + +// ` [BYTES_PER_ELEMENT]: ${constructor.BYTES_PER_ELEMENT},\n` + +// ` [length]: ${length},\n` + +// ` [byteLength]: ${byteLength},\n` + +// ' [byteOffset]: 0,\n' + +// ` [buffer]: ArrayBuffer { byteLength: ${byteLength} }\n]`); +// assert.strictEqual( +// util.inspect(array, false), +// `${constructor.name}(${length}) [ 65, 97 ]` +// ); +// }); + +// TODO(wafuwafu13): Fix +// { +// const brokenLength = new Float32Array(2); +// Object.defineProperty(brokenLength, 'length', { value: -1 }); +// assert.strictEqual(inspect(brokenLength), 'Float32Array(2) [ 0n, 0n ]'); +// } + +assert.strictEqual( + util.inspect(Object.create({}, { + visible: { value: 1, enumerable: true }, + hidden: { value: 2 } + }), { showHidden: true }), + '{ visible: 1, [hidden]: 2 }' +); +// Objects without prototype. +assert.strictEqual( + util.inspect(Object.create(null, { + name: { value: 'Tim', enumerable: true }, + hidden: { value: 'secret' } + }), { showHidden: true }), + "[Object: null prototype] { name: 'Tim', [hidden]: 'secret' }" +); + +assert.strictEqual( + util.inspect(Object.create(null, { + name: { value: 'Tim', enumerable: true }, + hidden: { value: 'secret' } + })), + "[Object: null prototype] { name: 'Tim' }" +); + +// Dynamic properties. +{ + assert.strictEqual( + util.inspect({ get readonly() { return 1; } }), + '{ readonly: [Getter] }'); + + assert.strictEqual( + util.inspect({ get readwrite() { return 1; }, set readwrite(val) {} }), + '{ readwrite: [Getter/Setter] }'); + + assert.strictEqual( + // eslint-disable-next-line accessor-pairs + util.inspect({ set writeonly(val) {} }), + '{ writeonly: [Setter] }'); + + const value = {}; + value.a = value; + assert.strictEqual(util.inspect(value), ' { a: [Circular *1] }'); + const getterFn = { + get one() { + return null; + } + }; + assert.strictEqual( + util.inspect(getterFn, { getters: true }), + '{ one: [Getter: null] }' + ); +} + +// TODO(wafuwafu13): Fix +// // Array with dynamic properties. +// { +// const value = [1, 2, 3]; +// Object.defineProperty( +// value, +// 'growingLength', +// { +// enumerable: true, +// get: function() { this.push(true); return this.length; } +// } +// ); +// Object.defineProperty( +// value, +// '-1', +// { +// enumerable: true, +// value: -1 +// } +// ); +// assert.strictEqual(util.inspect(value), +// "[ 1, 2, 3, growingLength: [Getter], '-1': -1 ]"); +// } + +// Array with inherited number properties. +{ + class CustomArray extends Array {} + CustomArray.prototype[5] = 'foo'; + CustomArray.prototype[49] = 'bar'; + CustomArray.prototype.foo = true; + const arr = new CustomArray(50); + arr[49] = 'I win'; + assert.strictEqual( + util.inspect(arr), + "CustomArray(50) [ <49 empty items>, 'I win' ]" + ); + // TODO(wafuwafu13): Fix + // assert.strictEqual( + // util.inspect(arr, { showHidden: true }), + // 'CustomArray(50) [\n' + + // ' <49 empty items>,\n' + + // " 'I win',\n" + + // ' [length]: 50,\n' + + // " '5': 'foo',\n" + + // ' foo: true\n' + + // ']' + // ); +} + +// TODO(wafuwafu13): Fix +// // Array with extra properties. +// { +// const arr = [1, 2, 3, , ]; +// arr.foo = 'bar'; +// assert.strictEqual(util.inspect(arr), +// "[ 1, 2, 3, <1 empty item>, foo: 'bar' ]"); + +// const arr2 = []; +// assert.strictEqual(util.inspect([], { showHidden: true }), '[ [length]: 0 ]'); +// arr2['00'] = 1; +// assert.strictEqual(util.inspect(arr2), "[ '00': 1 ]"); +// assert.strictEqual(util.inspect(arr2, { showHidden: true }), +// "[ [length]: 0, '00': 1 ]"); +// arr2[1] = 0; +// assert.strictEqual(util.inspect(arr2), "[ <1 empty item>, 0, '00': 1 ]"); +// assert.strictEqual(util.inspect(arr2, { showHidden: true }), +// "[ <1 empty item>, 0, [length]: 2, '00': 1 ]"); +// delete arr2[1]; +// assert.strictEqual(util.inspect(arr2), "[ <2 empty items>, '00': 1 ]"); +// assert.strictEqual(util.inspect(arr2, { showHidden: true }), +// "[ <2 empty items>, [length]: 2, '00': 1 ]"); +// arr2['01'] = 2; +// assert.strictEqual(util.inspect(arr2), +// "[ <2 empty items>, '00': 1, '01': 2 ]"); +// assert.strictEqual(util.inspect(arr2, { showHidden: true }), +// "[ <2 empty items>, [length]: 2, '00': 1, '01': 2 ]"); +// delete arr2['00']; +// arr2[0] = 0; +// assert.strictEqual(util.inspect(arr2), +// "[ 0, <1 empty item>, '01': 2 ]"); +// assert.strictEqual(util.inspect(arr2, { showHidden: true }), +// "[ 0, <1 empty item>, [length]: 2, '01': 2 ]"); +// delete arr2['01']; +// arr2[2 ** 32 - 2] = 'max'; +// arr2[2 ** 32 - 1] = 'too far'; +// assert.strictEqual( +// util.inspect(arr2), +// "[ 0, <4294967293 empty items>, 'max', '4294967295': 'too far' ]" +// ); + +// const arr3 = []; +// arr3[-1] = -1; +// assert.strictEqual(util.inspect(arr3), "[ '-1': -1 ]"); +// } + +// TODO(wafuwafu13): Fix +// // Indices out of bounds. +// { +// const arr = []; +// arr[2 ** 32] = true; // Not a valid array index. +// assert.strictEqual(util.inspect(arr), "[ '4294967296': true ]"); +// arr[0] = true; +// arr[10] = true; +// assert.strictEqual(util.inspect(arr), +// "[ true, <9 empty items>, true, '4294967296': true ]"); +// arr[2 ** 32 - 2] = true; +// arr[2 ** 32 - 1] = true; +// arr[2 ** 32 + 1] = true; +// delete arr[0]; +// delete arr[10]; +// assert.strictEqual(util.inspect(arr), +// ['[', +// '<4294967294 empty items>,', +// 'true,', +// "'4294967296': true,", +// "'4294967295': true,", +// "'4294967297': true\n]", +// ].join('\n ')); +// } + +// Function with properties. +{ + const value = () => {}; + value.aprop = 42; + assert.strictEqual(util.inspect(value), '[Function: value] { aprop: 42 }'); +} + +// Anonymous function with properties. +{ + const value = (() => function() {})(); + value.aprop = 42; + assert.strictEqual( + util.inspect(value), + '[Function (anonymous)] { aprop: 42 }' + ); +} + +// Regular expressions with properties. +{ + const value = /123/ig; + value.aprop = 42; + assert.strictEqual(util.inspect(value), '/123/gi { aprop: 42 }'); +} + +// Dates with properties. +{ + const value = new Date('Sun, 14 Feb 2010 11:48:40 GMT'); + value.aprop = 42; + assert.strictEqual(util.inspect(value), + '2010-02-14T11:48:40.000Z { aprop: 42 }'); +} + +// TODO(wafuwafu13): Implement 'vm' +// // Test the internal isDate implementation. +// { +// const Date2 = vm.runInNewContext('Date'); +// const d = new Date2(); +// const orig = util.inspect(d); +// Date2.prototype.foo = 'bar'; +// const after = util.inspect(d); +// assert.strictEqual(orig, after); +// } + +// Test positive/negative zero. +assert.strictEqual(util.inspect(0), '0'); +assert.strictEqual(util.inspect(-0), '-0'); +// Edge case from check. +assert.strictEqual(util.inspect(-5e-324), '-5e-324'); + +// Test for sparse array. +{ + const a = ['foo', 'bar', 'baz']; + assert.strictEqual(util.inspect(a), "[ 'foo', 'bar', 'baz' ]"); + delete a[1]; + assert.strictEqual(util.inspect(a), "[ 'foo', <1 empty item>, 'baz' ]"); + // TODO(wafuwafu13): Fix + // assert.strictEqual( + // util.inspect(a, true), + // "[ 'foo', <1 empty item>, 'baz', [length]: 3 ]" + // ); + assert.strictEqual(util.inspect(new Array(5)), '[ <5 empty items> ]'); + a[3] = 'bar'; + a[100] = 'qux'; + assert.strictEqual( + util.inspect(a, { breakLength: Infinity }), + "[ 'foo', <1 empty item>, 'baz', 'bar', <96 empty items>, 'qux' ]" + ); + delete a[3]; + assert.strictEqual( + util.inspect(a, { maxArrayLength: 4 }), + "[ 'foo', <1 empty item>, 'baz', <97 empty items>, ... 1 more item ]" + ); + // test 4 special case + assert.strictEqual(util.inspect(a, { + maxArrayLength: 2 + }), "[ 'foo', <1 empty item>, ... 99 more items ]"); +} + +// TODO(wafuwafu13): Implement `previewEntries` +// Test for Array constructor in different context. +// { +// const map = new Map(); +// map.set(1, 2); +// // Passing only a single argument to indicate a set iterator. +// const valsSetIterator = previewEntries(map.entries()); +// // Passing through true to indicate a map iterator. +// const valsMapIterEntries = previewEntries(map.entries(), true); +// const valsMapIterKeys = previewEntries(map.keys(), true); + +// assert.strictEqual(util.inspect(valsSetIterator), '[ 1, 2 ]'); +// assert.strictEqual(util.inspect(valsMapIterEntries), '[ [ 1, 2 ], true ]'); +// assert.strictEqual(util.inspect(valsMapIterKeys), '[ [ 1 ], false ]'); +// } + +// TODO(wafuwafu13): Implement 'vm' +// // Test for other constructors in different context. +// { +// let obj = vm.runInNewContext('(function(){return {}})()', {}); +// assert.strictEqual(util.inspect(obj), '{}'); +// obj = vm.runInNewContext('const m=new Map();m.set(1,2);m', {}); +// assert.strictEqual(util.inspect(obj), 'Map(1) { 1 => 2 }'); +// obj = vm.runInNewContext('const s=new Set();s.add(1);s.add(2);s', {}); +// assert.strictEqual(util.inspect(obj), 'Set(2) { 1, 2 }'); +// obj = vm.runInNewContext('fn=function(){};new Promise(fn,fn)', {}); +// assert.strictEqual(util.inspect(obj), 'Promise { }'); +// } + +// Test for property descriptors. +{ + const getter = Object.create(null, { + a: { + get: function() { return 'aaa'; } + } + }); + const setter = Object.create(null, { + b: { // eslint-disable-line accessor-pairs + set: function() {} + } + }); + const getterAndSetter = Object.create(null, { + c: { + get: function() { return 'ccc'; }, + set: function() {} + } + }); + assert.strictEqual( + util.inspect(getter, true), + '[Object: null prototype] { [a]: [Getter] }' + ); + assert.strictEqual( + util.inspect(setter, true), + '[Object: null prototype] { [b]: [Setter] }' + ); + assert.strictEqual( + util.inspect(getterAndSetter, true), + '[Object: null prototype] { [c]: [Getter/Setter] }' + ); +} + +// Exceptions should print the error message, not '{}'. +{ + [ + new Error(), + new Error('FAIL'), + new TypeError('FAIL'), + new SyntaxError('FAIL'), + ].forEach((err) => { + assert.strictEqual(util.inspect(err), err.stack); + }); + assert.throws( + () => undef(), // eslint-disable-line no-undef + (e) => { + assert.strictEqual(util.inspect(e), e.stack); + return true; + } + ); + + const ex = util.inspect(new Error('FAIL'), true); + assert(ex.includes('Error: FAIL')); + assert(ex.includes('[stack]')); + assert(ex.includes('[message]')); +} + +{ + const tmp = Error.stackTraceLimit; + Error.stackTraceLimit = 0; + const err = new Error('foo'); + const err2 = new Error('foo\nbar'); + assert.strictEqual(util.inspect(err, { compact: true }), '[Error: foo]'); + assert(err.stack); + delete err.stack; + assert(!err.stack); + // TODO(wafuwafu13): Fix + // assert.strictEqual(util.inspect(err, { compact: true }), '[Error: foo]'); + // assert.strictEqual( + // util.inspect(err2, { compact: true }), + // '[Error: foo\nbar]' + // ); + + // err.bar = true; + // err2.bar = true; + + // assert.strictEqual( + // util.inspect(err, { compact: true }), + // '{ [Error: foo] bar: true }' + // ); + // assert.strictEqual( + // util.inspect(err2, { compact: true }), + // '{ [Error: foo\nbar]\n bar: true }' + // ); + // assert.strictEqual( + // util.inspect(err, { compact: true, breakLength: 5 }), + // '{ [Error: foo]\n bar: true }' + // ); + // assert.strictEqual( + // util.inspect(err, { compact: true, breakLength: 1 }), + // '{ [Error: foo]\n bar:\n true }' + // ); + // assert.strictEqual( + // util.inspect(err2, { compact: true, breakLength: 5 }), + // '{ [Error: foo\nbar]\n bar: true }' + // ); + // assert.strictEqual( + // util.inspect(err, { compact: false }), + // '[Error: foo] {\n bar: true\n}' + // ); + // assert.strictEqual( + // util.inspect(err2, { compact: false }), + // '[Error: foo\nbar] {\n bar: true\n}' + // ); + + // Error.stackTraceLimit = tmp; +} + +// TODO(wafuwafu13): Fix +// // Prevent enumerable error properties from being printed. +// { +// let err = new Error(); +// err.message = 'foobar'; +// let out = util.inspect(err).split('\n'); +// assert.strictEqual(out[0], 'Error: foobar'); +// assert(out[out.length - 1].startsWith(' at ')); +// // Reset the error, the stack is otherwise not recreated. +// err = new Error(); +// err.message = 'foobar'; +// err.name = 'Unique'; +// Object.defineProperty(err, 'stack', { value: err.stack, enumerable: true }); +// out = util.inspect(err).split('\n'); +// assert.strictEqual(out[0], 'Unique: foobar'); +// assert(out[out.length - 1].startsWith(' at ')); +// err.name = 'Baz'; +// out = util.inspect(err).split('\n'); +// assert.strictEqual(out[0], 'Unique: foobar'); +// assert.strictEqual(out[out.length - 2], " name: 'Baz'"); +// assert.strictEqual(out[out.length - 1], '}'); +// } + +// // Doesn't capture stack trace. +{ + function BadCustomError(msg) { + Error.call(this); + Object.defineProperty(this, 'message', + { value: msg, enumerable: false }); + Object.defineProperty(this, 'name', + { value: 'BadCustomError', enumerable: false }); + } + Object.setPrototypeOf(BadCustomError.prototype, Error.prototype); + Object.setPrototypeOf(BadCustomError, Error); + assert.strictEqual( + util.inspect(new BadCustomError('foo')), + '[BadCustomError: foo]' + ); +} + +// TODO(wafuwafu13): Fix +// // Tampered error stack or name property (different type than string). +// // Note: Symbols are not supported by `Error#toString()` which is called by +// // accessing the `stack` property. +// [ +// [404, '404: foo', '[404]'], +// [0, '0: foo', '[RangeError: foo]'], +// [0n, '0: foo', '[RangeError: foo]'], +// [null, 'null: foo', '[RangeError: foo]'], +// [undefined, 'RangeError: foo', '[RangeError: foo]'], +// [false, 'false: foo', '[RangeError: foo]'], +// ['', 'foo', '[RangeError: foo]'], +// [[1, 2, 3], '1,2,3: foo', '[1,2,3]'], +// ].forEach(([value, outputStart, stack]) => { +// let err = new RangeError('foo'); +// err.name = value; +// assert( +// util.inspect(err).startsWith(outputStart), +// util.format( +// 'The name set to %o did not result in the expected output "%s"', +// value, +// outputStart +// ) +// ); + +// err = new RangeError('foo'); +// err.stack = value; +// assert.strictEqual(util.inspect(err), stack); +// }); + +// https://github.com/nodejs/node-v0.x-archive/issues/1941 +assert.strictEqual(util.inspect(Object.create(Date.prototype)), 'Date {}'); + +// https://github.com/nodejs/node-v0.x-archive/issues/1944 +{ + const d = new Date(); + d.toUTCString = null; + util.inspect(d); +} + +// TODO(wafuwafu13): Fix +// // Should not throw. +// { +// const d = new Date(); +// d.toISOString = null; +// util.inspect(d); +// } + +// TODO(wafuwafu13): Fix +// // Should not throw. +// { +// const r = /regexp/; +// r.toString = null; +// util.inspect(r); +// } + +// TODO(wafuwafu13): Fix +// // See https://github.com/nodejs/node-v0.x-archive/issues/2225 +// { +// const x = { [util.inspect.custom]: util.inspect }; +// assert(util.inspect(x).includes( +// '[Symbol(nodejs.util.inspect.custom)]: [Function: inspect] {\n')); +// } + +// TODO(wafuwafu13): Fix +// // `util.inspect` should display the escaped value of a key. +// { +// const w = { +// '\\': 1, +// '\\\\': 2, +// '\\\\\\': 3, +// '\\\\\\\\': 4, +// '\n': 5, +// '\r': 6 +// }; + +// const y = ['a', 'b', 'c']; +// y['\\\\'] = 'd'; +// y['\n'] = 'e'; +// y['\r'] = 'f'; + +// assert.strictEqual( +// util.inspect(w), +// "{ '\\\\': 1, '\\\\\\\\': 2, '\\\\\\\\\\\\': 3, " + +// "'\\\\\\\\\\\\\\\\': 4, '\\n': 5, '\\r': 6 }" +// ); +// assert.strictEqual( +// util.inspect(y), +// "[ 'a', 'b', 'c', '\\\\\\\\': 'd', " + +// "'\\n': 'e', '\\r': 'f' ]" +// ); +// } + +// Test util.inspect.styles and util.inspect.colors. +{ + function testColorStyle(style, input, implicit) { + const colorName = util.inspect.styles[style]; + let color = ['', '']; + if (util.inspect.colors[colorName]) + color = util.inspect.colors[colorName]; + + const withoutColor = util.inspect(input, false, 0, false); + const withColor = util.inspect(input, false, 0, true); + const expect = `\u001b[${color[0]}m${withoutColor}\u001b[${color[1]}m`; + assert.strictEqual( + withColor, + expect, + `util.inspect color for style ${style}`); + } + + testColorStyle('special', function() {}); + testColorStyle('number', 123.456); + testColorStyle('boolean', true); + testColorStyle('undefined', undefined); + testColorStyle('null', null); + testColorStyle('string', 'test string'); + testColorStyle('date', new Date()); + testColorStyle('regexp', /regexp/); +} + +// An object with "hasOwnProperty" overwritten should not throw. +util.inspect({ hasOwnProperty: null }); + +// New API, accepts an "options" object. +{ + const subject = { foo: 'bar', hello: 31, a: { b: { c: { d: 0 } } } }; + Object.defineProperty(subject, 'hidden', { enumerable: false, value: null }); + + assert.strictEqual( + util.inspect(subject, { showHidden: false }).includes('hidden'), + false + ); + assert.strictEqual( + util.inspect(subject, { showHidden: true }).includes('hidden'), + true + ); + assert.strictEqual( + util.inspect(subject, { colors: false }).includes('\u001b[32m'), + false + ); + assert.strictEqual( + util.inspect(subject, { colors: true }).includes('\u001b[32m'), + true + ); + assert.strictEqual( + util.inspect(subject, { depth: 2 }).includes('c: [Object]'), + true + ); + assert.strictEqual( + util.inspect(subject, { depth: 0 }).includes('a: [Object]'), + true + ); + assert.strictEqual( + util.inspect(subject, { depth: null }).includes('{ d: 0 }'), + true + ); + assert.strictEqual( + util.inspect(subject, { depth: undefined }).includes('{ d: 0 }'), + true + ); +} + +{ + // "customInspect" option can enable/disable calling [util.inspect.custom](). + const subject = { [util.inspect.custom]: () => 123 }; + + assert.strictEqual( + util.inspect(subject, { customInspect: true }).includes('123'), + true + ); + assert.strictEqual( + util.inspect(subject, { customInspect: true }).includes('inspect'), + false + ); + assert.strictEqual( + util.inspect(subject, { customInspect: false }).includes('123'), + false + ); + // TODO(wafuwafu13): Fix + // assert.strictEqual( + // util.inspect(subject, { customInspect: false }).includes('inspect'), + // true + // ); + + // A custom [util.inspect.custom]() should be able to return other Objects. + subject[util.inspect.custom] = () => ({ foo: 'bar' }); + + assert.strictEqual(util.inspect(subject), "{ foo: 'bar' }"); + + subject[util.inspect.custom] = common.mustCall((depth, opts) => { + const clone = { ...opts }; + // This might change at some point but for now we keep the stylize function. + // The function should either be documented or an alternative should be + // implemented. + assert.strictEqual(typeof opts.stylize, 'function'); + assert.strictEqual(opts.seen, undefined); + assert.strictEqual(opts.budget, undefined); + assert.strictEqual(opts.indentationLvl, undefined); + assert.strictEqual(opts.showHidden, false); + assert.deepStrictEqual( + new Set(Object.keys(util.inspect.defaultOptions).concat(['stylize'])), + new Set(Object.keys(opts)) + ); + opts.showHidden = true; + return { [util.inspect.custom]: common.mustCall((depth, opts2) => { + assert.deepStrictEqual(clone, opts2); + }) }; + }); + + util.inspect(subject); + + // util.inspect.custom is a shared symbol which can be accessed as + // Symbol.for("nodejs.util.inspect.custom"). + const inspect = Symbol.for('nodejs.util.inspect.custom'); + + subject[inspect] = () => ({ baz: 'quux' }); + + assert.strictEqual(util.inspect(subject), '{ baz: \'quux\' }'); + + subject[inspect] = (depth, opts) => { + assert.strictEqual(opts.customInspectOptions, true); + assert.strictEqual(opts.seen, null); + return {}; + }; + + util.inspect(subject, { customInspectOptions: true, seen: null }); +} + +{ + const subject = { [util.inspect.custom]: common.mustCall((depth, opts) => { + assert.strictEqual(depth, null); + assert.strictEqual(opts.compact, true); + }) }; + util.inspect(subject, { depth: null, compact: true }); +} + +// TODO(wafuwafu13): Fix +// { +// // Returning `this` from a custom inspection function works. +// const subject = { a: 123, [util.inspect.custom]() { return this; } }; +// const UIC = 'nodejs.util.inspect.custom'; +// assert.strictEqual( +// util.inspect(subject), +// `{\n a: 123,\n [Symbol(${UIC})]: [Function: [${UIC}]]\n}` +// ); +// } + +// Verify that it's possible to use the stylize function to manipulate input. +assert.strictEqual( + util.inspect([1, 2, 3], { stylize() { return 'x'; } }), + '[ x, x, x ]' +); + +// Using `util.inspect` with "colors" option should produce as many lines as +// without it. +{ + function testLines(input) { + const countLines = (str) => (str.match(/\n/g) || []).length; + const withoutColor = util.inspect(input); + const withColor = util.inspect(input, { colors: true }); + assert.strictEqual(countLines(withoutColor), countLines(withColor)); + } + + const bigArray = new Array(100).fill().map((value, index) => index); + + testLines([1, 2, 3, 4, 5, 6, 7]); + testLines(bigArray); + testLines({ foo: 'bar', baz: 35, b: { a: 35 } }); + testLines({ a: { a: 3, b: 1, c: 1, d: 1, e: 1, f: 1, g: 1, h: 1 }, b: 1 }); + testLines({ + foo: 'bar', + baz: 35, + b: { a: 35 }, + veryLongKey: 'very long value', + evenLongerKey: ['with even longer value in array'] + }); +} + +// Test boxed primitives output the correct values. +assert.strictEqual(util.inspect(new String('test')), "[String: 'test']"); +assert.strictEqual( + util.inspect(new String('test'), { colors: true }), + "\u001b[32m[String: 'test']\u001b[39m" +); +assert.strictEqual( + util.inspect(Object(Symbol('test'))), + '[Symbol: Symbol(test)]' +); +assert.strictEqual(util.inspect(new Boolean(false)), '[Boolean: false]'); +// TODO(wafuwafu13): Fix +// assert.strictEqual( +// util.inspect(Object.setPrototypeOf(new Boolean(true), null)), +// '[Boolean (null prototype): true]' +// ); +// assert.strictEqual(util.inspect(new Number(0)), '[Number: 0]'); +// assert.strictEqual( +// util.inspect( +// Object.defineProperty( +// Object.setPrototypeOf(new Number(-0), Array.prototype), +// Symbol.toStringTag, +// { value: 'Foobar' } +// ) +// ), +// '[Number (Array): -0] [Foobar]' +// ); +assert.strictEqual(util.inspect(new Number(-1.1)), '[Number: -1.1]'); +assert.strictEqual(util.inspect(new Number(13.37)), '[Number: 13.37]'); + +// Test boxed primitives with own properties. +{ + const str = new String('baz'); + str.foo = 'bar'; + assert.strictEqual(util.inspect(str), "[String: 'baz'] { foo: 'bar' }"); + + const bool = new Boolean(true); + bool.foo = 'bar'; + assert.strictEqual(util.inspect(bool), "[Boolean: true] { foo: 'bar' }"); + + const num = new Number(13.37); + num.foo = 'bar'; + assert.strictEqual(util.inspect(num), "[Number: 13.37] { foo: 'bar' }"); + + const sym = Object(Symbol('foo')); + sym.foo = 'bar'; + assert.strictEqual(util.inspect(sym), "[Symbol: Symbol(foo)] { foo: 'bar' }"); + + const big = Object(BigInt(55)); + big.foo = 'bar'; + assert.strictEqual(util.inspect(big), "[BigInt: 55n] { foo: 'bar' }"); +} + +// Test es6 Symbol. +if (typeof Symbol !== 'undefined') { + assert.strictEqual(util.inspect(Symbol()), 'Symbol()'); + assert.strictEqual(util.inspect(Symbol(123)), 'Symbol(123)'); + assert.strictEqual(util.inspect(Symbol('hi')), 'Symbol(hi)'); + assert.strictEqual(util.inspect([Symbol()]), '[ Symbol() ]'); + assert.strictEqual(util.inspect({ foo: Symbol() }), '{ foo: Symbol() }'); + + const options = { showHidden: true }; + let subject = {}; + + subject[Symbol('sym\nbol')] = 42; + + // TODO(wafuwafu13): Fix + // assert.strictEqual(util.inspect(subject), '{ [Symbol(sym\\nbol)]: 42 }'); + // assert.strictEqual( + // util.inspect(subject, options), + // '{ [Symbol(sym\\nbol)]: 42 }' + // ); + + // Object.defineProperty( + // subject, + // Symbol(), + // { enumerable: false, value: 'non-enum' }); + // assert.strictEqual(util.inspect(subject), '{ [Symbol(sym\\nbol)]: 42 }'); + // assert.strictEqual( + // util.inspect(subject, options), + // "{ [Symbol(sym\\nbol)]: 42, [Symbol()]: 'non-enum' }" + // ); + + // subject = [1, 2, 3]; + // subject[Symbol('symbol')] = 42; + + // assert.strictEqual(util.inspect(subject), + // '[ 1, 2, 3, [Symbol(symbol)]: 42 ]'); +} + +// Test Set. +{ + assert.strictEqual(util.inspect(new Set()), 'Set(0) {}'); + // TODO(wafuwafu13): Fix + // assert.strictEqual(util.inspect(new Set([1, 2, 3])), 'Set(3) { 1, 2, 3 }'); + // const set = new Set(['foo']); + // set.bar = 42; + // assert.strictEqual( + // util.inspect(set, { showHidden: true }), + // "Set(1) { 'foo', bar: 42 }" + // ); +} + +// TODO(wafuwafu13): Fix +// // Test circular Set. +// { +// const set = new Set(); +// set.add(set); +// assert.strictEqual(util.inspect(set), ' Set(1) { [Circular *1] }'); +// } + +// Test Map. +{ + assert.strictEqual(util.inspect(new Map()), 'Map(0) {}'); + assert.strictEqual(util.inspect(new Map([[1, 'a'], [2, 'b'], [3, 'c']])), + "Map(3) { 1 => 'a', 2 => 'b', 3 => 'c' }"); + const map = new Map([['foo', null]]); + map.bar = 42; + assert.strictEqual(util.inspect(map, true), + "Map(1) { 'foo' => null, bar: 42 }"); +} + +// Test circular Map. +{ + const map = new Map(); + map.set(map, 'map'); + assert.strictEqual( + inspect(map), + " Map(1) { [Circular *1] => 'map' }" + ); + map.set(map, map); + assert.strictEqual( + inspect(map), + ' Map(1) { [Circular *1] => [Circular *1] }' + ); + map.delete(map); + map.set('map', map); + assert.strictEqual( + inspect(map), + " Map(1) { 'map' => [Circular *1] }" + ); +} + +// Test multiple circular references. +{ + const obj = {}; + obj.a = [obj]; + obj.b = {}; + obj.b.inner = obj.b; + obj.b.obj = obj; + + assert.strictEqual( + inspect(obj), + ' {\n' + + ' a: [ [Circular *1] ],\n' + + ' b: { inner: [Circular *2], obj: [Circular *1] }\n' + + '}' + ); +} + +// TODO(wafuwafu13): Fix +// // Test Promise. +// { +// const resolved = Promise.resolve(3); +// assert.strictEqual(util.inspect(resolved), 'Promise { 3 }'); + +// const rejected = Promise.reject(3); +// assert.strictEqual(util.inspect(rejected), 'Promise { 3 }'); +// // Squelch UnhandledPromiseRejection. +// rejected.catch(() => {}); + +// const pending = new Promise(() => {}); +// assert.strictEqual(util.inspect(pending), 'Promise { }'); + +// const promiseWithProperty = Promise.resolve('foo'); +// promiseWithProperty.bar = 42; +// assert.strictEqual(util.inspect(promiseWithProperty), +// "Promise { 'foo', bar: 42 }"); +// } + +// Make sure it doesn't choke on polyfills. Unlike Set/Map, there is no standard +// interface to synchronously inspect a Promise, so our techniques only work on +// a bonafide native Promise. +{ + const oldPromise = Promise; + global.Promise = function() { this.bar = 42; }; + assert.strictEqual(util.inspect(new Promise()), '{ bar: 42 }'); + global.Promise = oldPromise; +} + +// TODO(wafuwafu13): Fix +// // Test Map iterators. +// { +// const map = new Map([['foo', 'bar']]); +// assert.strictEqual(util.inspect(map.keys()), '[Map Iterator] { \'foo\' }'); +// const mapValues = map.values(); +// Object.defineProperty(mapValues, Symbol.toStringTag, { value: 'Foo' }); +// assert.strictEqual( +// util.inspect(mapValues), +// '[Foo] [Map Iterator] { \'bar\' }' +// ); +// map.set('A', 'B!'); +// assert.strictEqual(util.inspect(map.entries(), { maxArrayLength: 1 }), +// "[Map Entries] { [ 'foo', 'bar' ], ... 1 more item }"); +// // Make sure the iterator doesn't get consumed. +// const keys = map.keys(); +// assert.strictEqual(util.inspect(keys), "[Map Iterator] { 'foo', 'A' }"); +// assert.strictEqual(util.inspect(keys), "[Map Iterator] { 'foo', 'A' }"); +// keys.extra = true; +// assert.strictEqual( +// util.inspect(keys, { maxArrayLength: 0 }), +// '[Map Iterator] { ... 2 more items, extra: true }'); +// } + +// TODO(wafuwafu13): Fix +// // Test Set iterators. +// { +// const aSet = new Set([1]); +// assert.strictEqual(util.inspect(aSet.entries(), { compact: false }), +// '[Set Entries] {\n [\n 1,\n 1\n ]\n}'); +// aSet.add(3); +// assert.strictEqual(util.inspect(aSet.keys()), '[Set Iterator] { 1, 3 }'); +// assert.strictEqual(util.inspect(aSet.values()), '[Set Iterator] { 1, 3 }'); +// const setEntries = aSet.entries(); +// Object.defineProperty(setEntries, Symbol.toStringTag, { value: 'Foo' }); +// assert.strictEqual(util.inspect(setEntries), +// '[Foo] [Set Entries] { [ 1, 1 ], [ 3, 3 ] }'); +// // Make sure the iterator doesn't get consumed. +// const keys = aSet.keys(); +// Object.defineProperty(keys, Symbol.toStringTag, { value: null }); +// assert.strictEqual(util.inspect(keys), '[Set Iterator] { 1, 3 }'); +// assert.strictEqual(util.inspect(keys), '[Set Iterator] { 1, 3 }'); +// keys.extra = true; +// assert.strictEqual( +// util.inspect(keys, { maxArrayLength: 1 }), +// '[Set Iterator] { 1, ... 1 more item, extra: true }'); +// } + +// Minimal inspection should still return as much information as possible about +// the constructor and Symbol.toStringTag. +{ + class Foo { + get [Symbol.toStringTag]() { + return 'ABC'; + } + } + const a = new Foo(); + assert.strictEqual(inspect(a, { depth: -1 }), 'Foo [ABC] {}'); + a.foo = true; + assert.strictEqual(inspect(a, { depth: -1 }), '[Foo [ABC]]'); + Object.defineProperty(a, Symbol.toStringTag, { + value: 'Foo', + configurable: true, + writable: true + }); + assert.strictEqual(inspect(a, { depth: -1 }), '[Foo]'); + delete a[Symbol.toStringTag]; + Object.setPrototypeOf(a, null); + // TODO(wafuwafu13): Fix + // assert.strictEqual(inspect(a, { depth: -1 }), '[Foo: null prototype]'); + // delete a.foo; + // assert.strictEqual(inspect(a, { depth: -1 }), '[Foo: null prototype] {}'); + // Object.defineProperty(a, Symbol.toStringTag, { + // value: 'ABC', + // configurable: true + // }); + // assert.strictEqual( + // inspect(a, { depth: -1 }), + // '[Foo: null prototype] [ABC] {}' + // ); + // Object.defineProperty(a, Symbol.toStringTag, { + // value: 'Foo', + // configurable: true + // }); + // assert.strictEqual( + // inspect(a, { depth: -1 }), + // '[Object: null prototype] [Foo] {}' + // ); +} + +// Test alignment of items in container. +// Assumes that the first numeric character is the start of an item. +{ + function checkAlignment(container, start, lineX, end) { + const lines = util.inspect(container).split('\n'); + lines.forEach((line, i) => { + if (i === 0) { + assert.strictEqual(line, start); + } else if (i === lines.length - 1) { + assert.strictEqual(line, end); + } else { + let expected = lineX.replace('X', i - 1); + if (i !== lines.length - 2) + expected += ','; + assert.strictEqual(line, expected); + } + }); + } + + const bigArray = []; + for (let i = 0; i < 100; i++) { + bigArray.push(i); + } + + const obj = {}; + bigArray.forEach((prop) => { + obj[prop] = null; + }); + + checkAlignment(obj, '{', " 'X': null", '}'); + // TODO(wafuwafu13): Fix + // checkAlignment(new Set(bigArray), 'Set(100) {', ' X', '}'); + checkAlignment( + new Map(bigArray.map((number) => [number, null])), + 'Map(100) {', ' X => null', '}' + ); +} + + +// Test display of constructors. +{ + class ObjectSubclass {} + class ArraySubclass extends Array {} + class SetSubclass extends Set {} + class MapSubclass extends Map {} + class PromiseSubclass extends Promise {} + + const x = new ObjectSubclass(); + x.foo = 42; + assert.strictEqual(util.inspect(x), + 'ObjectSubclass { foo: 42 }'); + assert.strictEqual(util.inspect(new ArraySubclass(1, 2, 3)), + 'ArraySubclass(3) [ 1, 2, 3 ]'); + // TODO(wafuwafu13): Fix + // assert.strictEqual(util.inspect(new SetSubclass([1, 2, 3])), + // 'SetSubclass(3) [Set] { 1, 2, 3 }'); + assert.strictEqual(util.inspect(new MapSubclass([['foo', 42]])), + "MapSubclass(1) [Map] { 'foo' => 42 }"); + // TODO(wafuwafu13): Fix + // assert.strictEqual(util.inspect(new PromiseSubclass(() => {})), + // 'PromiseSubclass [Promise] { }'); + assert.strictEqual( + util.inspect({ a: { b: new ArraySubclass([1, [2], 3]) } }, { depth: 1 }), + '{ a: { b: [ArraySubclass] } }' + ); + // TODO(wafuwafu13): Fix + // assert.strictEqual( + // util.inspect(Object.setPrototypeOf(x, null)), + // '[ObjectSubclass: null prototype] { foo: 42 }' + // ); +} + +// Empty and circular before depth. +{ + const arr = [[[[]]]]; + assert.strictEqual(util.inspect(arr), '[ [ [ [] ] ] ]'); + arr[0][0][0][0] = []; + assert.strictEqual(util.inspect(arr), '[ [ [ [Array] ] ] ]'); + arr[0][0][0] = {}; + assert.strictEqual(util.inspect(arr), '[ [ [ {} ] ] ]'); + arr[0][0][0] = { a: 2 }; + assert.strictEqual(util.inspect(arr), '[ [ [ [Object] ] ] ]'); + arr[0][0][0] = arr; + assert.strictEqual(util.inspect(arr), ' [ [ [ [Circular *1] ] ] ]'); + arr[0][0][0] = arr[0][0]; + assert.strictEqual(util.inspect(arr), '[ [ [ [Circular *1] ] ] ]'); +} + +// Corner cases. +{ + const x = { constructor: 42 }; + assert.strictEqual(util.inspect(x), '{ constructor: 42 }'); +} + +{ + const x = {}; + Object.defineProperty(x, 'constructor', { + get: function() { + throw new Error('should not access constructor'); + }, + enumerable: true + }); + assert.strictEqual(util.inspect(x), '{ constructor: [Getter] }'); +} + +{ + const x = new function() {}; // eslint-disable-line new-parens + assert.strictEqual(util.inspect(x), '{}'); +} + +{ + const x = Object.create(null); + assert.strictEqual(util.inspect(x), '[Object: null prototype] {}'); +} + +// TODO(wafuwafu13): Fix +// { +// const x = []; +// x[''] = 1; +// assert.strictEqual(util.inspect(x), "[ '': 1 ]"); +// } + +// TODO(wafuwafu13): Fix +// // The following maxArrayLength tests were introduced after v6.0.0 was released. +// // Do not backport to v5/v4 unless all of +// // https://github.com/nodejs/node/pull/6334 is backported. +// { +// const x = new Array(101).fill(); +// assert(util.inspect(x).endsWith('1 more item\n]')); +// assert(!util.inspect(x, { maxArrayLength: 101 }).endsWith('1 more item\n]')); +// assert.strictEqual( +// util.inspect(x, { maxArrayLength: -1 }), +// '[ ... 101 more items ]' +// ); +// assert.strictEqual(util.inspect(x, { maxArrayLength: 0 }), +// '[ ... 101 more items ]'); +// } + +{ + const x = Array(101); + assert.strictEqual(util.inspect(x, { maxArrayLength: 0 }), + '[ ... 101 more items ]'); + assert(!util.inspect(x, { maxArrayLength: null }).endsWith('1 more item\n]')); + assert(!util.inspect( + x, { maxArrayLength: Infinity } + ).endsWith('1 more item ]')); +} + +{ + const x = new Uint8Array(101); + // TODO(wafuwafu13): Fix + // assert(util.inspect(x).endsWith('1 more item\n]')); + assert(!util.inspect(x, { maxArrayLength: 101 }).includes('1 more item')); + // TODO(wafuwafu13): Fix + // assert.strictEqual(util.inspect(x, { maxArrayLength: 0 }), + // 'Uint8Array(101) [ ... 101 more items ]'); + assert(!util.inspect(x, { maxArrayLength: null }).includes('1 more item')); + // TODO(wafuwafu13): Fix + // assert(util.inspect(x, { maxArrayLength: Infinity }).endsWith(' 0, 0\n]')); +} + +{ + const obj = { foo: 'abc', bar: 'xyz' }; + const oneLine = util.inspect(obj, { breakLength: Infinity }); + // Subtract four for the object's two curly braces and two spaces of padding. + // Add one more to satisfy the strictly greater than condition in the code. + const breakpoint = oneLine.length - 5; + const twoLines = util.inspect(obj, { breakLength: breakpoint }); + + assert.strictEqual(oneLine, "{ foo: 'abc', bar: 'xyz' }"); + assert.strictEqual( + util.inspect(obj, { breakLength: breakpoint + 1 }), + twoLines + ); + assert.strictEqual(twoLines, "{\n foo: 'abc',\n bar: 'xyz'\n}"); +} + +// util.inspect.defaultOptions tests. +{ + const arr = new Array(101).fill(); + const obj = { a: { a: { a: { a: 1 } } } }; + + const oldOptions = { ...util.inspect.defaultOptions }; + + // Set single option through property assignment. + util.inspect.defaultOptions.maxArrayLength = null; + assert.doesNotMatch(util.inspect(arr), /1 more item/); + util.inspect.defaultOptions.maxArrayLength = oldOptions.maxArrayLength; + // TODO(wafuwafu13): Fix + // assert.match(util.inspect(arr), /1 more item/); + util.inspect.defaultOptions.depth = null; + assert.doesNotMatch(util.inspect(obj), /Object/); + util.inspect.defaultOptions.depth = oldOptions.depth; + assert.match(util.inspect(obj), /Object/); + assert.strictEqual( + JSON.stringify(util.inspect.defaultOptions), + JSON.stringify(oldOptions) + ); + + // Set multiple options through object assignment. + util.inspect.defaultOptions = { maxArrayLength: null, depth: 2 }; + assert.doesNotMatch(util.inspect(arr), /1 more item/); + assert.match(util.inspect(obj), /Object/); + util.inspect.defaultOptions = oldOptions; + // assert.match(util.inspect(arr), /1 more item/); + assert.match(util.inspect(obj), /Object/); + assert.strictEqual( + JSON.stringify(util.inspect.defaultOptions), + JSON.stringify(oldOptions) + ); + + assert.throws(() => { + util.inspect.defaultOptions = null; + }, { + code: 'ERR_INVALID_ARG_TYPE', + name: 'TypeError', + message: 'The "options" argument must be of type object. ' + + 'Received null' + } + ); + + assert.throws(() => { + util.inspect.defaultOptions = 'bad'; + }, { + code: 'ERR_INVALID_ARG_TYPE', + name: 'TypeError', + message: 'The "options" argument must be of type object. ' + + "Received type string ('bad')" + } + ); +} + +util.inspect(process); + +// TODO(wafuwafu13): Fix +// // Setting custom inspect property to a non-function should do nothing. +// { +// const obj = { [util.inspect.custom]: 'fhqwhgads' }; +// assert.strictEqual( +// util.inspect(obj), +// "{ [Symbol(nodejs.util.inspect.custom)]: 'fhqwhgads' }" +// ); +// } + +{ + // @@toStringTag + const obj = { [Symbol.toStringTag]: 'a' }; + // TODO(wafuwafu13): Fix + // assert.strictEqual( + // util.inspect(obj), + // "{ [Symbol(Symbol.toStringTag)]: 'a' }" + // ); + Object.defineProperty(obj, Symbol.toStringTag, { + value: 'a', + enumerable: false + }); + assert.strictEqual(util.inspect(obj), 'Object [a] {}'); + // TODO(wafuwafu13): Fix + // assert.strictEqual( + // util.inspect(obj, { showHidden: true }), + // "{ [Symbol(Symbol.toStringTag)]: 'a' }" + // ); + + class Foo { + constructor() { + this.foo = 'bar'; + } + + get [Symbol.toStringTag]() { + return this.foo; + } + } + + // TODO(wafuwafu13): Fix + // assert.strictEqual(util.inspect( + // Object.create(null, { [Symbol.toStringTag]: { value: 'foo' } })), + // '[Object: null prototype] [foo] {}'); + + assert.strictEqual(util.inspect(new Foo()), "Foo [bar] { foo: 'bar' }"); + + assert.strictEqual( + util.inspect(new (class extends Foo {})()), + "Foo [bar] { foo: 'bar' }"); + + assert.strictEqual( + util.inspect(Object.create(Object.create(Foo.prototype), { + foo: { value: 'bar', enumerable: true } + })), + "Foo [bar] { foo: 'bar' }"); + + class ThrowingClass { + get [Symbol.toStringTag]() { + throw new Error('toStringTag error'); + } + } + + assert.throws(() => util.inspect(new ThrowingClass()), /toStringTag error/); + + class NotStringClass { + get [Symbol.toStringTag]() { + return null; + } + } + + assert.strictEqual(util.inspect(new NotStringClass()), + 'NotStringClass {}'); +} + +{ + const o = { + a: [1, 2, [[ + 'Lorem ipsum dolor\nsit amet,\tconsectetur adipiscing elit, sed do ' + + 'eiusmod tempor incididunt ut labore et dolore magna aliqua.', + 'test', + 'foo']], 4], + b: new Map([['za', 1], ['zb', 'test']]) + }; + + let out = util.inspect(o, { compact: true, depth: 5, breakLength: 80 }); + let expect = [ + '{ a:', + ' [ 1,', + ' 2,', + " [ [ 'Lorem ipsum dolor\\nsit amet,\\tconsectetur adipiscing elit, " + + "sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.',", + " 'test',", + " 'foo' ] ],", + ' 4 ],', + " b: Map(2) { 'za' => 1, 'zb' => 'test' } }", + ].join('\n'); + assert.strictEqual(out, expect); + + out = util.inspect(o, { compact: false, depth: 5, breakLength: 60 }); + expect = [ + '{', + ' a: [', + ' 1,', + ' 2,', + ' [', + ' [', + " 'Lorem ipsum dolor\\n' +", + " 'sit amet,\\tconsectetur adipiscing elit, sed do eiusmod " + + "tempor incididunt ut labore et dolore magna aliqua.',", + " 'test',", + " 'foo'", + ' ]', + ' ],', + ' 4', + ' ],', + ' b: Map(2) {', + " 'za' => 1,", + " 'zb' => 'test'", + ' }', + '}', + ].join('\n'); + assert.strictEqual(out, expect); + + out = util.inspect(o.a[2][0][0], { compact: false, breakLength: 30 }); + expect = [ + "'Lorem ipsum dolor\\n' +", + " 'sit amet,\\tconsectetur adipiscing elit, sed do eiusmod tempor " + + "incididunt ut labore et dolore magna aliqua.'", + ].join('\n'); + assert.strictEqual(out, expect); + + out = util.inspect( + '12345678901234567890123456789012345678901234567890', + { compact: false, breakLength: 3 }); + expect = "'12345678901234567890123456789012345678901234567890'"; + assert.strictEqual(out, expect); + + out = util.inspect( + '12 45 78 01 34 67 90 23 56 89 123456789012345678901234567890', + { compact: false, breakLength: 3 }); + expect = [ + "'12 45 78 01 34 67 90 23 56 89 123456789012345678901234567890'", + ].join('\n'); + assert.strictEqual(out, expect); + + // TODO(wafuwafu13): Fix + o.a = () => {}; + o.b = new Number(3); + out = util.inspect(o, { compact: false, breakLength: 3 }); + expect = [ + '{', + ' a: [Function (anonymous)],', + ' b: [Number: 3]', + '}', + ].join('\n'); + assert.strictEqual(out, expect); + + out = util.inspect(o, { compact: false, breakLength: 3, showHidden: true }); + expect = [ + '{', + ' a: [Function (anonymous)] {', + ' [length]: 0,', + " [name]: ''", + ' },', + ' b: [Number: 3]', + '}', + ].join('\n'); + assert.strictEqual(out, expect); + + o[util.inspect.custom] = () => 42; + out = util.inspect(o, { compact: false, breakLength: 3 }); + expect = '42'; + assert.strictEqual(out, expect); + + o[util.inspect.custom] = () => '12 45 78 01 34 67 90 23'; + out = util.inspect(o, { compact: false, breakLength: 3 }); + expect = '12 45 78 01 34 67 90 23'; + assert.strictEqual(out, expect); + + o[util.inspect.custom] = () => ({ a: '12 45 78 01 34 67 90 23' }); + out = util.inspect(o, { compact: false, breakLength: 3 }); + expect = "{\n a: '12 45 78 01 34 67 90 23'\n}"; + assert.strictEqual(out, expect); +} + +// TODO(wafuwafu13): Fix +// // Check compact indentation. +// { +// const typed = new Uint8Array(); +// typed.buffer.foo = true; +// const set = new Set([[1, 2]]); +// const promise = Promise.resolve([[1, set]]); +// const map = new Map([[promise, typed]]); +// map.set(set.values(), map.values()); + +// let out = util.inspect(map, { compact: false, showHidden: true, depth: 9 }); +// let expected = [ +// 'Map(2) {', +// ' Promise {', +// ' [', +// ' [', +// ' 1,', +// ' Set(1) {', +// ' [', +// ' 1,', +// ' 2,', +// ' [length]: 2', +// ' ]', +// ' },', +// ' [length]: 2', +// ' ],', +// ' [length]: 1', +// ' ]', +// ' } => Uint8Array(0) [', +// ' [BYTES_PER_ELEMENT]: 1,', +// ' [length]: 0,', +// ' [byteLength]: 0,', +// ' [byteOffset]: 0,', +// ' [buffer]: ArrayBuffer {', +// ' byteLength: 0,', +// ' foo: true', +// ' }', +// ' ],', +// ' [Set Iterator] {', +// ' [', +// ' 1,', +// ' 2,', +// ' [length]: 2', +// ' ],', +// " [Symbol(Symbol.toStringTag)]: 'Set Iterator'", +// ' } => [Map Iterator] {', +// ' Uint8Array(0) [', +// ' [BYTES_PER_ELEMENT]: 1,', +// ' [length]: 0,', +// ' [byteLength]: 0,', +// ' [byteOffset]: 0,', +// ' [buffer]: ArrayBuffer {', +// ' byteLength: 0,', +// ' foo: true', +// ' }', +// ' ],', +// ' [Circular *1],', +// " [Symbol(Symbol.toStringTag)]: 'Map Iterator'", +// ' }', +// '}', +// ].join('\n'); + +// assert.strict.equal(out, expected); + +// out = util.inspect(map, { compact: 2, showHidden: true, depth: 9 }); + +// expected = [ +// 'Map(2) {', +// ' Promise {', +// ' [', +// ' [', +// ' 1,', +// ' Set(1) { [ 1, 2, [length]: 2 ] },', +// ' [length]: 2', +// ' ],', +// ' [length]: 1', +// ' ]', +// ' } => Uint8Array(0) [', +// ' [BYTES_PER_ELEMENT]: 1,', +// ' [length]: 0,', +// ' [byteLength]: 0,', +// ' [byteOffset]: 0,', +// ' [buffer]: ArrayBuffer { byteLength: 0, foo: true }', +// ' ],', +// ' [Set Iterator] {', +// ' [ 1, 2, [length]: 2 ],', +// " [Symbol(Symbol.toStringTag)]: 'Set Iterator'", +// ' } => [Map Iterator] {', +// ' Uint8Array(0) [', +// ' [BYTES_PER_ELEMENT]: 1,', +// ' [length]: 0,', +// ' [byteLength]: 0,', +// ' [byteOffset]: 0,', +// ' [buffer]: ArrayBuffer { byteLength: 0, foo: true }', +// ' ],', +// ' [Circular *1],', +// " [Symbol(Symbol.toStringTag)]: 'Map Iterator'", +// ' }', +// '}', +// ].join('\n'); + +// assert.strict.equal(out, expected); + +// out = util.inspect(map, { +// showHidden: true, depth: 9, breakLength: 4, compact: true +// }); +// expected = [ +// 'Map(2) {', +// ' Promise {', +// ' [ [ 1,', +// ' Set(1) {', +// ' [ 1,', +// ' 2,', +// ' [length]: 2 ] },', +// ' [length]: 2 ],', +// ' [length]: 1 ] } => Uint8Array(0) [', +// ' [BYTES_PER_ELEMENT]: 1,', +// ' [length]: 0,', +// ' [byteLength]: 0,', +// ' [byteOffset]: 0,', +// ' [buffer]: ArrayBuffer {', +// ' byteLength: 0,', +// ' foo: true } ],', +// ' [Set Iterator] {', +// ' [ 1,', +// ' 2,', +// ' [length]: 2 ],', +// ' [Symbol(Symbol.toStringTag)]:', +// " 'Set Iterator' } => [Map Iterator] {", +// ' Uint8Array(0) [', +// ' [BYTES_PER_ELEMENT]: 1,', +// ' [length]: 0,', +// ' [byteLength]: 0,', +// ' [byteOffset]: 0,', +// ' [buffer]: ArrayBuffer {', +// ' byteLength: 0,', +// ' foo: true } ],', +// ' [Circular *1],', +// ' [Symbol(Symbol.toStringTag)]:', +// " 'Map Iterator' } }", +// ].join('\n'); + +// assert.strict.equal(out, expected); +// } + +// TODO(wafuwafu13): Fix +// { // Test WeakMap && WeakSet +// const obj = {}; +// const arr = []; +// const weakMap = new WeakMap([[obj, arr], [arr, obj]]); +// let out = util.inspect(weakMap, { showHidden: true }); +// let expect = 'WeakMap { [ [length]: 0 ] => {}, {} => [ [length]: 0 ] }'; +// assert.strictEqual(out, expect); + +// out = util.inspect(weakMap); +// expect = 'WeakMap { }'; +// assert.strictEqual(out, expect); + +// out = util.inspect(weakMap, { maxArrayLength: 0, showHidden: true }); +// expect = 'WeakMap { ... 2 more items }'; +// assert.strictEqual(out, expect); + +// weakMap.extra = true; +// out = util.inspect(weakMap, { maxArrayLength: 1, showHidden: true }); +// // It is not possible to determine the output reliable. +// expect = 'WeakMap { [ [length]: 0 ] => {}, ... 1 more item, extra: true }'; +// let expectAlt = 'WeakMap { {} => [ [length]: 0 ], ... 1 more item, ' + +// 'extra: true }'; +// assert(out === expect || out === expectAlt, +// `Found: "${out}"\nrather than: "${expect}"\nor: "${expectAlt}"`); + +// // Test WeakSet +// arr.push(1); +// const weakSet = new WeakSet([obj, arr]); +// out = util.inspect(weakSet, { showHidden: true }); +// expect = 'WeakSet { [ 1, [length]: 1 ], {} }'; +// assert.strictEqual(out, expect); + +// out = util.inspect(weakSet); +// expect = 'WeakSet { }'; +// assert.strictEqual(out, expect); + +// out = util.inspect(weakSet, { maxArrayLength: -2, showHidden: true }); +// expect = 'WeakSet { ... 2 more items }'; +// assert.strictEqual(out, expect); + +// weakSet.extra = true; +// out = util.inspect(weakSet, { maxArrayLength: 1, showHidden: true }); +// // It is not possible to determine the output reliable. +// expect = 'WeakSet { {}, ... 1 more item, extra: true }'; +// expectAlt = 'WeakSet { [ 1, [length]: 1 ], ... 1 more item, extra: true }'; +// assert(out === expect || out === expectAlt, +// `Found: "${out}"\nrather than: "${expect}"\nor: "${expectAlt}"`); +// // Keep references to the WeakMap entries, otherwise they could be GCed too +// // early. +// assert(obj && arr); +// } + +{ // Test argument objects. + const args = (function() { return arguments; })('a'); + assert.strictEqual(util.inspect(args), "[Arguments] { '0': 'a' }"); +} + +{ + // Test that a long linked list can be inspected without throwing an error. + const list = {}; + let head = list; + // A linked list of length 100k should be inspectable in some way, even though + // the real cutoff value is much lower than 100k. + for (let i = 0; i < 100000; i++) + head = head.next = {}; + assert.strictEqual( + util.inspect(list), + '{ next: { next: { next: [Object] } } }' + ); + const longList = util.inspect(list, { depth: Infinity }); + const match = longList.match(/next/g); + assert(match.length > 500 && match.length < 10000); + // TODO(wafuwafu13): Fix + // assert(longList.includes('[Object: Inspection interrupted ' + + // 'prematurely. Maximum call stack size exceeded.]')); +} + +// Do not escape single quotes if no double quote or backtick is present. +assert.strictEqual(util.inspect("'"), '"\'"'); +assert.strictEqual(util.inspect('"\''), '`"\'`'); +// eslint-disable-next-line no-template-curly-in-string +assert.strictEqual(util.inspect('"\'${a}'), "'\"\\'${a}'"); + +// TODO(wafuwafu13): Fix +// // Errors should visualize as much information as possible. +// // If the name is not included in the stack, visualize it as well. +// [ +// [class Foo extends TypeError {}, 'test'], +// [class Foo extends TypeError {}, undefined], +// [class BarError extends Error {}, 'test'], +// [class BazError extends Error { +// get name() { +// return 'BazError'; +// } +// }, undefined], +// ].forEach(([Class, message], i) => { +// console.log('Test %i', i); +// const foo = new Class(message); +// const name = foo.name; +// const extra = Class.name.includes('Error') ? '' : ` [${foo.name}]`; +// assert( +// util.inspect(foo).startsWith( +// `${Class.name}${extra}${message ? `: ${message}` : '\n'}`), +// util.inspect(foo) +// ); +// Object.defineProperty(foo, Symbol.toStringTag, { +// value: 'WOW', +// writable: true, +// configurable: true +// }); +// const stack = foo.stack; +// foo.stack = 'This is a stack'; +// assert.strictEqual( +// util.inspect(foo), +// '[This is a stack]' +// ); +// foo.stack = stack; +// assert( +// util.inspect(foo).startsWith( +// `${Class.name} [WOW]${extra}${message ? `: ${message}` : '\n'}`), +// util.inspect(foo) +// ); +// Object.setPrototypeOf(foo, null); +// assert( +// util.inspect(foo).startsWith( +// `[${name}: null prototype] [WOW]${message ? `: ${message}` : '\n'}` +// ), +// util.inspect(foo) +// ); +// foo.bar = true; +// delete foo[Symbol.toStringTag]; +// assert( +// util.inspect(foo).startsWith( +// `[${name}: null prototype]${message ? `: ${message}` : '\n'}`), +// util.inspect(foo) +// ); +// foo.stack = 'This is a stack'; +// assert.strictEqual( +// util.inspect(foo), +// '[[Error: null prototype]: This is a stack] { bar: true }' +// ); +// foo.stack = stack.split('\n')[0]; +// assert.strictEqual( +// util.inspect(foo), +// `[[${name}: null prototype]${message ? `: ${message}` : ''}] { bar: true }` +// ); +// }); + +// TODO(wafuwafu13): Fix +// // Verify that classes are properly inspected. +// [ +// /* eslint-disable spaced-comment, no-multi-spaces, brace-style */ +// // The whitespace is intentional. +// [class { }, '[class (anonymous)]'], +// [class extends Error { log() {} }, '[class (anonymous) extends Error]'], +// [class A { constructor(a) { this.a = a; } log() { return this.a; } }, +// '[class A]'], +// [class +// // Random { // comments /* */ are part of the toString() result +// /* eslint-disable-next-line space-before-blocks */ +// äß/**/extends/*{*/TypeError{}, '[class äß extends TypeError]'], +// /* The whitespace and new line is intended! */ +// // Foobar !!! +// [class X extends /****/ Error +// // More comments +// {}, '[class X extends Error]'], +// /* eslint-enable spaced-comment, no-multi-spaces, brace-style */ +// ].forEach(([clazz, string]) => { +// const inspected = util.inspect(clazz); +// assert.strictEqual(inspected, string); +// Object.defineProperty(clazz, Symbol.toStringTag, { +// value: 'Woohoo' +// }); +// const parts = inspected.slice(0, -1).split(' '); +// const [, name, ...rest] = parts; +// rest.unshift('[Woohoo]'); +// if (rest.length) { +// rest[rest.length - 1] += ']'; +// } +// assert.strictEqual( +// util.inspect(clazz), +// ['[class', name, ...rest].join(' ') +// ); +// if (rest.length) { +// rest[rest.length - 1] = rest[rest.length - 1].slice(0, -1); +// rest.length = 1; +// } +// Object.setPrototypeOf(clazz, Map.prototype); +// assert.strictEqual( +// util.inspect(clazz), +// ['[class', name, '[Map]', ...rest].join(' ') + ']' +// ); +// Object.setPrototypeOf(clazz, null); +// assert.strictEqual( +// util.inspect(clazz), +// ['[class', name, ...rest, 'extends [null prototype]]'].join(' ') +// ); +// Object.defineProperty(clazz, 'name', { value: 'Foo' }); +// const res = ['[class', 'Foo', ...rest, 'extends [null prototype]]'].join(' '); +// assert.strictEqual(util.inspect(clazz), res); +// clazz.foo = true; +// assert.strictEqual(util.inspect(clazz), `${res} { foo: true }`); +// }); + +// "class" properties should not be detected as "class". +{ + // eslint-disable-next-line space-before-function-paren + let obj = { class () {} }; + assert.strictEqual( + util.inspect(obj), + '{ class: [Function: class] }' + ); + obj = { class: () => {} }; + assert.strictEqual( + util.inspect(obj), + '{ class: [Function: class] }' + ); + obj = { ['class Foo {}']() {} }; + assert.strictEqual( + util.inspect(obj), + "{ 'class Foo {}': [Function: class Foo {}] }" + ); + function Foo() {} + Object.defineProperty(Foo, 'toString', { value: () => 'class Foo {}' }); + assert.strictEqual( + util.inspect(Foo), + '[Function: Foo]' + ); + function fn() {} + Object.defineProperty(fn, 'name', { value: 'class Foo {}' }); + assert.strictEqual( + util.inspect(fn), + '[Function: class Foo {}]' + ); +} + +// TODO(wafuwafu13): Fix +// // Verify that throwing in valueOf and toString still produces nice results. +// [ +// [new String(55), "[String: '55']"], +// [new Boolean(true), '[Boolean: true]'], +// [new Number(55), '[Number: 55]'], +// [Object(BigInt(55)), '[BigInt: 55n]'], +// [Object(Symbol('foo')), '[Symbol: Symbol(foo)]'], +// [function() {}, '[Function (anonymous)]'], +// [() => {}, '[Function (anonymous)]'], +// [[1, 2], '[ 1, 2 ]'], +// [[, , 5, , , , ], '[ <2 empty items>, 5, <3 empty items> ]'], +// [{ a: 5 }, '{ a: 5 }'], +// [new Set([1, 2]), 'Set(2) { 1, 2 }'], +// [new Map([[1, 2]]), 'Map(1) { 1 => 2 }'], +// [new Set([1, 2]).entries(), '[Set Entries] { [ 1, 1 ], [ 2, 2 ] }'], +// [new Map([[1, 2]]).keys(), '[Map Iterator] { 1 }'], +// [new Date(2000), '1970-01-01T00:00:02.000Z'], +// [new Uint8Array(2), 'Uint8Array(2) [ 0, 0 ]'], +// [new Promise((resolve) => setTimeout(resolve, 10)), 'Promise { }'], +// [new WeakSet(), 'WeakSet { }'], +// [new WeakMap(), 'WeakMap { }'], +// [/foobar/g, '/foobar/g'], +// ].forEach(([value, expected]) => { +// Object.defineProperty(value, 'valueOf', { +// get() { +// throw new Error('valueOf'); +// } +// }); +// Object.defineProperty(value, 'toString', { +// get() { +// throw new Error('toString'); +// } +// }); +// assert.strictEqual(util.inspect(value), expected); +// value.foo = 'bar'; +// assert.notStrictEqual(util.inspect(value), expected); +// delete value.foo; +// value[Symbol('foo')] = 'yeah'; +// assert.notStrictEqual(util.inspect(value), expected); +// }); + +// TODO(wafuwafu13): Fix +// // Verify that having no prototype still produces nice results. +// [ +// [[1, 3, 4], '[Array(3): null prototype] [ 1, 3, 4 ]'], +// [new Set([1, 2]), '[Set(2): null prototype] { 1, 2 }'], +// [new Map([[1, 2]]), '[Map(1): null prototype] { 1 => 2 }'], +// [new Promise((resolve) => setTimeout(resolve, 10)), +// '[Promise: null prototype] { }'], +// [new WeakSet(), '[WeakSet: null prototype] { }'], +// [new WeakMap(), '[WeakMap: null prototype] { }'], +// [new Uint8Array(2), '[Uint8Array(2): null prototype] [ 0, 0 ]'], +// [new Uint16Array(2), '[Uint16Array(2): null prototype] [ 0, 0 ]'], +// [new Uint32Array(2), '[Uint32Array(2): null prototype] [ 0, 0 ]'], +// [new Int8Array(2), '[Int8Array(2): null prototype] [ 0, 0 ]'], +// [new Int16Array(2), '[Int16Array(2): null prototype] [ 0, 0 ]'], +// [new Int32Array(2), '[Int32Array(2): null prototype] [ 0, 0 ]'], +// [new Float32Array(2), '[Float32Array(2): null prototype] [ 0, 0 ]'], +// [new Float64Array(2), '[Float64Array(2): null prototype] [ 0, 0 ]'], +// [new BigInt64Array(2), '[BigInt64Array(2): null prototype] [ 0n, 0n ]'], +// [new BigUint64Array(2), '[BigUint64Array(2): null prototype] [ 0n, 0n ]'], +// [new ArrayBuffer(16), '[ArrayBuffer: null prototype] {\n' + +// ' [Uint8Contents]: <00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00>,\n' + +// ' byteLength: undefined\n}'], +// [new DataView(new ArrayBuffer(16)), +// '[DataView: null prototype] {\n byteLength: undefined,\n ' + +// 'byteOffset: undefined,\n buffer: undefined\n}'], +// [new SharedArrayBuffer(2), '[SharedArrayBuffer: null prototype] ' + +// '{\n [Uint8Contents]: <00 00>,\n byteLength: undefined\n}'], +// [/foobar/, '[RegExp: null prototype] /foobar/'], +// [new Date('Sun, 14 Feb 2010 11:48:40 GMT'), +// '[Date: null prototype] 2010-02-14T11:48:40.000Z'], +// ].forEach(([value, expected]) => { +// assert.strictEqual( +// util.inspect(Object.setPrototypeOf(value, null)), +// expected +// ); +// value.foo = 'bar'; +// assert.notStrictEqual(util.inspect(value), expected); +// delete value.foo; +// value[Symbol('foo')] = 'yeah'; +// assert.notStrictEqual(util.inspect(value), expected); +// }); + +// TODO(wafuwafu13): Fix +// // Verify that subclasses with and without prototype produce nice results. +// [ +// [RegExp, ['foobar', 'g'], '/foobar/g'], +// [WeakSet, [[{}]], '{ }'], +// [WeakMap, [[[{}, {}]]], '{ }'], +// [BigInt64Array, +// [10], +// '[\n 0n, 0n, 0n, 0n, 0n,\n 0n, 0n, 0n, 0n, 0n\n]'], +// [Date, ['Sun, 14 Feb 2010 11:48:40 GMT'], '2010-02-14T11:48:40.000Z'], +// [Date, ['invalid_date'], 'Invalid Date'], +// ].forEach(([base, input, rawExpected]) => { +// class Foo extends base {} +// const value = new Foo(...input); +// const symbol = value[Symbol.toStringTag]; +// const size = base.name.includes('Array') ? `(${input[0]})` : ''; +// const expected = `Foo${size} ${symbol ? `[${symbol}] ` : ''}${rawExpected}`; +// const expectedWithoutProto = +// `[${base.name}${size}: null prototype] ${rawExpected}`; +// assert.strictEqual(util.inspect(value), expected); +// value.foo = 'bar'; +// assert.notStrictEqual(util.inspect(value), expected); +// delete value.foo; +// assert.strictEqual( +// util.inspect(Object.setPrototypeOf(value, null)), +// expectedWithoutProto +// ); +// value.foo = 'bar'; +// let res = util.inspect(value); +// assert.notStrictEqual(res, expectedWithoutProto); +// assert.match(res, /foo: 'bar'/); +// delete value.foo; +// value[Symbol('foo')] = 'yeah'; +// res = util.inspect(value); +// assert.notStrictEqual(res, expectedWithoutProto); +// assert.match(res, /\[Symbol\(foo\)]: 'yeah'/); +// }); + +assert.strictEqual(inspect(1n), '1n'); +assert.strictEqual(inspect(Object(-1n)), '[BigInt: -1n]'); +assert.strictEqual(inspect(Object(13n)), '[BigInt: 13n]'); +// TODO(wafuwafu13): Fix +// assert.strictEqual(inspect(new BigInt64Array([0n])), 'BigInt64Array(1) [ 0n ]'); +// assert.strictEqual( +// inspect(new BigUint64Array([0n])), 'BigUint64Array(1) [ 0n ]'); + +// Verify non-enumerable keys get escaped. +{ + const obj = {}; + Object.defineProperty(obj, 'Non\nenumerable\tkey', { value: true }); + assert.strictEqual( + util.inspect(obj, { showHidden: true }), + '{ [Non\\nenumerable\\tkey]: true }' + ); +} + +// Check for special colors. +{ + const special = inspect.colors[inspect.styles.special]; + const string = inspect.colors[inspect.styles.string]; + + assert.strictEqual( + inspect(new WeakSet(), { colors: true }), + `WeakSet { \u001b[${special[0]}m\u001b[${special[1]}m }` + ); + assert.strictEqual( + inspect(new WeakMap(), { colors: true }), + `WeakMap { \u001b[${special[0]}m\u001b[${special[1]}m }` + ); + // TODO(wafuwafu13): Fix + // assert.strictEqual( + // inspect(new Promise(() => {}), { colors: true }), + // `Promise { \u001b[${special[0]}m\u001b[${special[1]}m }` + // ); + + // const rejection = Promise.reject('Oh no!'); + // assert.strictEqual( + // inspect(rejection, { colors: true }), + // `Promise { \u001b[${special[0]}m\u001b[${special[1]}m ` + + // `\u001b[${string[0]}m'Oh no!'\u001b[${string[1]}m }` + // ); + // rejection.catch(() => {}); + + // Verify that aliases do not show up as key while checking `inspect.colors`. + const colors = Object.keys(inspect.colors); + const aliases = Object.getOwnPropertyNames(inspect.colors) + .filter((c) => !colors.includes(c)); + assert(!colors.includes('grey')); + assert(colors.includes('gray')); + // Verify that all aliases are correctly mapped. + for (const alias of aliases) { + assert(Array.isArray(inspect.colors[alias])); + } + // Check consistent naming. + [ + 'black', + 'red', + 'green', + 'yellow', + 'blue', + 'magenta', + 'cyan', + 'white', + ].forEach((color, i) => { + assert.deepStrictEqual(inspect.colors[color], [30 + i, 39]); + assert.deepStrictEqual(inspect.colors[`${color}Bright`], [90 + i, 39]); + const bgColor = `bg${color[0].toUpperCase()}${color.slice(1)}`; + assert.deepStrictEqual(inspect.colors[bgColor], [40 + i, 49]); + assert.deepStrictEqual(inspect.colors[`${bgColor}Bright`], [100 + i, 49]); + }); + + // Unknown colors are handled gracefully: + const stringStyle = inspect.styles.string; + inspect.styles.string = 'UNKNOWN'; + assert.strictEqual(inspect('foobar', { colors: true }), "'foobar'"); + inspect.styles.string = stringStyle; +} + +assert.strictEqual( + inspect([1, 3, 2], { sorted: true }), + inspect([1, 3, 2]) +); +assert.strictEqual( + inspect({ c: 3, a: 1, b: 2 }, { sorted: true }), + '{ a: 1, b: 2, c: 3 }' +); +assert.strictEqual( + inspect( + { a200: 4, a100: 1, a102: 3, a101: 2 }, + { sorted(a, b) { return b.localeCompare(a); } } + ), + '{ a200: 4, a102: 3, a101: 2, a100: 1 }' +); + +// TODO(wafuwafu13): Fix +// // Non-indices array properties are sorted as well. +// { +// const arr = [3, 2, 1]; +// arr.b = 2; +// arr.c = 3; +// arr.a = 1; +// arr[Symbol('b')] = true; +// arr[Symbol('a')] = false; +// assert.strictEqual( +// inspect(arr, { sorted: true }), +// '[ 3, 2, 1, [Symbol(a)]: false, [Symbol(b)]: true, a: 1, b: 2, c: 3 ]' +// ); +// } + +// TODO(wafuwafu13): Fix +// // Manipulate the prototype in weird ways. +// { +// let obj = { a: true }; +// let value = (function() { return function() {}; })(); +// Object.setPrototypeOf(value, null); +// Object.setPrototypeOf(obj, value); +// assert.strictEqual( +// util.inspect(obj), +// 'Object <[Function (null prototype) (anonymous)]> { a: true }' +// ); +// assert.strictEqual( +// util.inspect(obj, { colors: true }), +// 'Object <\u001b[36m[Function (null prototype) (anonymous)]\u001b[39m> ' + +// '{ a: \u001b[33mtrue\u001b[39m }' +// ); + +// obj = { a: true }; +// value = []; +// Object.setPrototypeOf(value, null); +// Object.setPrototypeOf(obj, value); +// assert.strictEqual( +// util.inspect(obj), +// 'Object <[Array(0): null prototype] []> { a: true }' +// ); + +// function StorageObject() {} +// StorageObject.prototype = Object.create(null); +// assert.strictEqual( +// util.inspect(new StorageObject()), +// 'StorageObject <[Object: null prototype] {}> {}' +// ); + +// obj = [1, 2, 3]; +// Object.setPrototypeOf(obj, Number.prototype); +// assert.strictEqual(inspect(obj), "Number { '0': 1, '1': 2, '2': 3 }"); + +// Object.setPrototypeOf(obj, Object.create(null)); +// assert.strictEqual( +// inspect(obj), +// "Array <[Object: null prototype] {}> { '0': 1, '1': 2, '2': 3 }" +// ); + +// StorageObject.prototype = Object.create(null); +// Object.setPrototypeOf(StorageObject.prototype, Object.create(null)); +// Object.setPrototypeOf( +// Object.getPrototypeOf(StorageObject.prototype), +// Object.create(null) +// ); +// assert.strictEqual( +// util.inspect(new StorageObject()), +// 'StorageObject >> {}' +// ); +// assert.strictEqual( +// util.inspect(new StorageObject(), { depth: 1 }), +// 'StorageObject >> {}' +// ); +// } + +// TODO(wafuwafu13): Fix +// // Check that the fallback always works. +// { +// const obj = new Set([1, 2]); +// const iterator = obj[Symbol.iterator]; +// Object.setPrototypeOf(obj, null); +// Object.defineProperty(obj, Symbol.iterator, { +// value: iterator, +// configurable: true +// }); +// assert.strictEqual(util.inspect(obj), '[Set(2): null prototype] { 1, 2 }'); +// Object.defineProperty(obj, Symbol.iterator, { +// value: true, +// configurable: true +// }); +// Object.defineProperty(obj, 'size', { +// value: NaN, +// configurable: true, +// enumerable: true +// }); +// assert.strictEqual( +// util.inspect(obj), +// '[Set(2): null prototype] { 1, 2, size: NaN }' +// ); +// } + +// TODO(wafuwafu13): Fix +// Check the getter option. +{ + let foo = 1; + const get = { get foo() { return foo; } }; + const getset = { + get foo() { return foo; }, + set foo(val) { foo = val; }, + get inc() { return ++foo; } + }; + const thrower = { get foo() { throw new Error('Oops'); } }; + assert.strictEqual( + inspect(get, { getters: true, colors: true }), + '{ foo: \u001b[36m[Getter:\u001b[39m ' + + '\u001b[33m1\u001b[39m\u001b[36m]\u001b[39m }'); + assert.strictEqual( + inspect(thrower, { getters: true }), + '{ foo: [Getter: ] }'); + assert.strictEqual( + inspect(getset, { getters: true }), + '{ foo: [Getter/Setter: 1], inc: [Getter: 2] }'); + assert.strictEqual( + inspect(getset, { getters: 'get' }), + '{ foo: [Getter/Setter], inc: [Getter: 3] }'); + assert.strictEqual( + inspect(getset, { getters: 'set' }), + '{ foo: [Getter/Setter: 3], inc: [Getter] }'); + getset.foo = new Set([[{ a: true }, 2, {}], 'foobar', { x: 1 }]); + // assert.strictEqual( + // inspect(getset, { getters: true }), + // '{\n foo: [Getter/Setter] Set(3) { [ [Object], 2, {} ], ' + + // "'foobar', { x: 1 } },\n inc: [Getter: NaN]\n}"); +} + +// Check compact number mode. +{ + let obj = { + a: { + b: { + x: 5, + c: { + x: '10000000000000000 00000000000000000 '.repeat(1e1), + d: 2, + e: 3 + } + } + }, + b: [ + 1, + 2, + [ 1, 2, { a: 1, b: 2, c: 3 } ], + ], + c: ['foo', 4, 444444], + d: Array.from({ length: 101 }).map((e, i) => { + return i % 2 === 0 ? i * i : i; + }), + e: Array(6).fill('foobar'), + f: Array(9).fill('foobar'), + g: Array(21).fill('foobar baz'), + h: [100].concat(Array.from({ length: 9 }).map((e, n) => (n))), + long: Array(9).fill('This text is too long for grouping!') + }; + + let out = util.inspect(obj, { compact: 3, depth: 10, breakLength: 60 }); + let expected = [ + '{', + ' a: {', + ' b: {', + ' x: 5,', + ' c: {', + " x: '10000000000000000 00000000000000000 10000000000000000 " + + '00000000000000000 10000000000000000 00000000000000000 ' + + '10000000000000000 00000000000000000 10000000000000000 ' + + '00000000000000000 10000000000000000 00000000000000000 ' + + '10000000000000000 00000000000000000 10000000000000000 ' + + '00000000000000000 10000000000000000 00000000000000000 ' + + "10000000000000000 00000000000000000 ',", + ' d: 2,', + ' e: 3', + ' }', + ' }', + ' },', + ' b: [ 1, 2, [ 1, 2, { a: 1, b: 2, c: 3 } ] ],', + " c: [ 'foo', 4, 444444 ],", + ' d: [', + ' 0, 1, 4, 3, 16, 5, 36, 7, 64,', + ' 9, 100, 11, 144, 13, 196, 15, 256, 17,', + ' 324, 19, 400, 21, 484, 23, 576, 25, 676,', + ' 27, 784, 29, 900, 31, 1024, 33, 1156, 35,', + ' 1296, 37, 1444, 39, 1600, 41, 1764, 43, 1936,', + ' 45, 2116, 47, 2304, 49, 2500, 51, 2704, 53,', + ' 2916, 55, 3136, 57, 3364, 59, 3600, 61, 3844,', + ' 63, 4096, 65, 4356, 67, 4624, 69, 4900, 71,', + ' 5184, 73, 5476, 75, 5776, 77, 6084, 79, 6400,', + ' 81, 6724, 83, 7056, 85, 7396, 87, 7744, 89,', + ' 8100, 91, 8464, 93, 8836, 95, 9216, 97, 9604,', + ' 99,', + ' ... 1 more item', + ' ],', + ' e: [', + " 'foobar',", + " 'foobar',", + " 'foobar',", + " 'foobar',", + " 'foobar',", + " 'foobar'", + ' ],', + ' f: [', + " 'foobar', 'foobar',", + " 'foobar', 'foobar',", + " 'foobar', 'foobar',", + " 'foobar', 'foobar',", + " 'foobar'", + ' ],', + ' g: [', + " 'foobar baz', 'foobar baz',", + " 'foobar baz', 'foobar baz',", + " 'foobar baz', 'foobar baz',", + " 'foobar baz', 'foobar baz',", + " 'foobar baz', 'foobar baz',", + " 'foobar baz', 'foobar baz',", + " 'foobar baz', 'foobar baz',", + " 'foobar baz', 'foobar baz',", + " 'foobar baz', 'foobar baz',", + " 'foobar baz', 'foobar baz',", + " 'foobar baz'", + ' ],', + ' h: [', + ' 100, 0, 1, 2, 3,', + ' 4, 5, 6, 7, 8', + ' ],', + ' long: [', + " 'This text is too long for grouping!',", + " 'This text is too long for grouping!',", + " 'This text is too long for grouping!',", + " 'This text is too long for grouping!',", + " 'This text is too long for grouping!',", + " 'This text is too long for grouping!',", + " 'This text is too long for grouping!',", + " 'This text is too long for grouping!',", + " 'This text is too long for grouping!'", + ' ]', + '}', + ].join('\n'); + + // TODO(wafuwafu13): Fix + // assert.strictEqual(out, expected); + + obj = [ + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 123456789, + ]; + + out = util.inspect(obj, { compact: 3 }); + + expected = [ + '[', + ' 1, 1, 1, 1,', + ' 1, 1, 1, 1,', + ' 1, 1, 1, 1,', + ' 1, 1, 1, 1,', + ' 1, 1, 1, 1,', + ' 1, 1, 1, 1,', + ' 1, 1, 123456789', + ']', + ].join('\n'); + + // TODO(wafuwafu13): Fix + // assert.strictEqual(out, expected); + + // Unicode support. あ has a length of one and a width of two. + obj = [ + '123', '123', '123', '123', 'あああ', + '123', '123', '123', '123', 'あああ', + ]; + + out = util.inspect(obj, { compact: 3 }); + + expected = [ + '[', + " '123', '123',", + " '123', '123',", + " 'あああ', '123',", + " '123', '123',", + " '123', 'あああ'", + ']', + ].join('\n'); + + // TODO(wafuwafu13): Fix + // assert.strictEqual(out, expected); + + // Verify that array grouping and line consolidation does not happen together. + obj = { + a: { + b: { + x: 5, + c: { + d: 2, + e: 3 + } + } + }, + b: Array.from({ length: 9 }).map((e, n) => { + return n % 2 === 0 ? 'foobar' : 'baz'; + }) + }; + + out = util.inspect(obj, { compact: 1, breakLength: Infinity, colors: true }); + + expected = [ + '{', + ' a: {', + ' b: { x: \u001b[33m5\u001b[39m, c: \u001b[36m[Object]\u001b[39m }', + ' },', + ' b: [', + " \u001b[32m'foobar'\u001b[39m, \u001b[32m'baz'\u001b[39m,", + " \u001b[32m'foobar'\u001b[39m, \u001b[32m'baz'\u001b[39m,", + " \u001b[32m'foobar'\u001b[39m, \u001b[32m'baz'\u001b[39m,", + " \u001b[32m'foobar'\u001b[39m, \u001b[32m'baz'\u001b[39m,", + " \u001b[32m'foobar'\u001b[39m", + ' ]', + '}', + ].join('\n'); + + // TODO(wafuwafu13): Fix + // assert.strictEqual(out, expected); + + obj = Array.from({ length: 60 }).map((e, i) => i); + out = util.inspect(obj, { compact: 1, breakLength: Infinity, colors: true }); + + expected = [ + '[', + /* eslint-disable max-len */ + ' \u001b[33m0\u001b[39m, \u001b[33m1\u001b[39m, \u001b[33m2\u001b[39m, \u001b[33m3\u001b[39m,', + ' \u001b[33m4\u001b[39m, \u001b[33m5\u001b[39m, \u001b[33m6\u001b[39m, \u001b[33m7\u001b[39m,', + ' \u001b[33m8\u001b[39m, \u001b[33m9\u001b[39m, \u001b[33m10\u001b[39m, \u001b[33m11\u001b[39m,', + ' \u001b[33m12\u001b[39m, \u001b[33m13\u001b[39m, \u001b[33m14\u001b[39m, \u001b[33m15\u001b[39m,', + ' \u001b[33m16\u001b[39m, \u001b[33m17\u001b[39m, \u001b[33m18\u001b[39m, \u001b[33m19\u001b[39m,', + ' \u001b[33m20\u001b[39m, \u001b[33m21\u001b[39m, \u001b[33m22\u001b[39m, \u001b[33m23\u001b[39m,', + ' \u001b[33m24\u001b[39m, \u001b[33m25\u001b[39m, \u001b[33m26\u001b[39m, \u001b[33m27\u001b[39m,', + ' \u001b[33m28\u001b[39m, \u001b[33m29\u001b[39m, \u001b[33m30\u001b[39m, \u001b[33m31\u001b[39m,', + ' \u001b[33m32\u001b[39m, \u001b[33m33\u001b[39m, \u001b[33m34\u001b[39m, \u001b[33m35\u001b[39m,', + ' \u001b[33m36\u001b[39m, \u001b[33m37\u001b[39m, \u001b[33m38\u001b[39m, \u001b[33m39\u001b[39m,', + ' \u001b[33m40\u001b[39m, \u001b[33m41\u001b[39m, \u001b[33m42\u001b[39m, \u001b[33m43\u001b[39m,', + ' \u001b[33m44\u001b[39m, \u001b[33m45\u001b[39m, \u001b[33m46\u001b[39m, \u001b[33m47\u001b[39m,', + ' \u001b[33m48\u001b[39m, \u001b[33m49\u001b[39m, \u001b[33m50\u001b[39m, \u001b[33m51\u001b[39m,', + ' \u001b[33m52\u001b[39m, \u001b[33m53\u001b[39m, \u001b[33m54\u001b[39m, \u001b[33m55\u001b[39m,', + ' \u001b[33m56\u001b[39m, \u001b[33m57\u001b[39m, \u001b[33m58\u001b[39m, \u001b[33m59\u001b[39m', + /* eslint-enable max-len */ + ']', + ].join('\n'); + + // TODO(wafuwafu13): Fix + // assert.strictEqual(out, expected); + + out = util.inspect([1, 2, 3, 4], { compact: 1, colors: true }); + expected = '[ \u001b[33m1\u001b[39m, \u001b[33m2\u001b[39m, ' + + '\u001b[33m3\u001b[39m, \u001b[33m4\u001b[39m ]'; + + assert.strictEqual(out, expected); + + obj = [ + 'Object', 'Function', 'Array', + 'Number', 'parseFloat', 'parseInt', + 'Infinity', 'NaN', 'undefined', + 'Boolean', 'String', 'Symbol', + 'Date', 'Promise', 'RegExp', + 'Error', 'EvalError', 'RangeError', + 'ReferenceError', 'SyntaxError', 'TypeError', + 'URIError', 'JSON', 'Math', + 'console', 'Intl', 'ArrayBuffer', + 'Uint8Array', 'Int8Array', 'Uint16Array', + 'Int16Array', 'Uint32Array', 'Int32Array', + 'Float32Array', 'Float64Array', 'Uint8ClampedArray', + 'BigUint64Array', 'BigInt64Array', 'DataView', + 'Map', 'BigInt', 'Set', + 'WeakMap', 'WeakSet', 'Proxy', + 'Reflect', 'decodeURI', 'decodeURIComponent', + 'encodeURI', 'encodeURIComponent', 'escape', + 'unescape', 'eval', 'isFinite', + 'isNaN', 'SharedArrayBuffer', 'Atomics', + 'globalThis', 'WebAssembly', 'global', + 'process', 'Buffer', 'URL', + 'URLSearchParams', 'TextEncoder', 'TextDecoder', + 'clearInterval', 'clearTimeout', 'setInterval', + 'setTimeout', 'queueMicrotask', 'clearImmediate', + 'setImmediate', 'module', 'require', + 'assert', 'async_hooks', 'buffer', + 'child_process', 'cluster', 'crypto', + 'dgram', 'dns', 'domain', + 'events', 'fs', 'http', + 'http2', 'https', 'inspector', + 'net', 'os', 'path', + 'perf_hooks', 'punycode', 'querystring', + 'readline', 'repl', 'stream', + 'string_decoder', 'tls', 'trace_events', + 'tty', 'url', 'v8', + 'vm', 'worker_threads', 'zlib', + '_', '_error', 'util', + ]; + + out = util.inspect( + obj, + { compact: 3, breakLength: 80, maxArrayLength: 250 } + ); + expected = [ + '[', + " 'Object', 'Function', 'Array',", + " 'Number', 'parseFloat', 'parseInt',", + " 'Infinity', 'NaN', 'undefined',", + " 'Boolean', 'String', 'Symbol',", + " 'Date', 'Promise', 'RegExp',", + " 'Error', 'EvalError', 'RangeError',", + " 'ReferenceError', 'SyntaxError', 'TypeError',", + " 'URIError', 'JSON', 'Math',", + " 'console', 'Intl', 'ArrayBuffer',", + " 'Uint8Array', 'Int8Array', 'Uint16Array',", + " 'Int16Array', 'Uint32Array', 'Int32Array',", + " 'Float32Array', 'Float64Array', 'Uint8ClampedArray',", + " 'BigUint64Array', 'BigInt64Array', 'DataView',", + " 'Map', 'BigInt', 'Set',", + " 'WeakMap', 'WeakSet', 'Proxy',", + " 'Reflect', 'decodeURI', 'decodeURIComponent',", + " 'encodeURI', 'encodeURIComponent', 'escape',", + " 'unescape', 'eval', 'isFinite',", + " 'isNaN', 'SharedArrayBuffer', 'Atomics',", + " 'globalThis', 'WebAssembly', 'global',", + " 'process', 'Buffer', 'URL',", + " 'URLSearchParams', 'TextEncoder', 'TextDecoder',", + " 'clearInterval', 'clearTimeout', 'setInterval',", + " 'setTimeout', 'queueMicrotask', 'clearImmediate',", + " 'setImmediate', 'module', 'require',", + " 'assert', 'async_hooks', 'buffer',", + " 'child_process', 'cluster', 'crypto',", + " 'dgram', 'dns', 'domain',", + " 'events', 'fs', 'http',", + " 'http2', 'https', 'inspector',", + " 'net', 'os', 'path',", + " 'perf_hooks', 'punycode', 'querystring',", + " 'readline', 'repl', 'stream',", + " 'string_decoder', 'tls', 'trace_events',", + " 'tty', 'url', 'v8',", + " 'vm', 'worker_threads', 'zlib',", + " '_', '_error', 'util'", + ']', + ].join('\n'); + + // TODO(wafuwafu13): Fix + // assert.strictEqual(out, expected); +} + +// TODO(wafuwafu13): Fix +// { +// // Use a fake stack to verify the expected colored outcome. +// const stack = [ +// 'TypedError: Wonderful message!', +// ' at A. (/test/node_modules/foo/node_modules/bar/baz.js:2:7)', +// ' at Module._compile (node:internal/modules/cjs/loader:827:30)', +// ' at Fancy (node:vm:697:32)', +// // This file is not an actual Node.js core file. +// ' at tryModuleLoad (node:internal/modules/cjs/foo:629:12)', +// ' at Function.Module._load (node:internal/modules/cjs/loader:621:3)', +// // This file is not an actual Node.js core file. +// ' at Module.require [as weird/name] (node:internal/aaaaa/loader:735:19)', +// ' at require (node:internal/modules/cjs/helpers:14:16)', +// ' at /test/test-util-inspect.js:2239:9', +// ' at getActual (node:assert:592:5)', +// ]; +// const isNodeCoreFile = [ +// false, false, true, true, false, true, false, true, false, true, +// ]; +// const err = new TypeError('Wonderful message!'); +// err.stack = stack.join('\n'); +// util.inspect(err, { colors: true }).split('\n').forEach((line, i) => { +// let actual = stack[i].replace(/node_modules\/([a-z]+)/g, (a, m) => { +// return `node_modules/\u001b[4m${m}\u001b[24m`; +// }); +// if (isNodeCoreFile[i]) { +// actual = `\u001b[90m${actual}\u001b[39m`; +// } +// assert.strictEqual(actual, line); +// }); +// } + +// { +// // Cross platform checks. +// const err = new Error('foo'); +// util.inspect(err, { colors: true }).split('\n').forEach((line, i) => { +// assert(i < 2 || line.startsWith('\u001b[90m')); +// }); +// } + +// TODO(wafuwafu13): Implement "trace_events" +// { +// // Tracing class respects inspect depth. +// try { +// const trace = require('trace_events').createTracing({ categories: ['fo'] }); +// const actualDepth0 = util.inspect({ trace }, { depth: 0 }); +// assert.strictEqual(actualDepth0, '{ trace: [Tracing] }'); +// const actualDepth1 = util.inspect({ trace }, { depth: 1 }); +// assert.strictEqual( +// actualDepth1, +// "{ trace: Tracing { enabled: false, categories: 'fo' } }" +// ); +// } catch (err) { +// if (err.code !== 'ERR_TRACE_EVENTS_UNAVAILABLE') +// throw err; +// } +// } + +// Inspect prototype properties. +{ + class Foo extends Map { + prop = false; + prop2 = true; + get abc() { + return true; + } + get def() { + return false; + } + set def(v) {} + get xyz() { + return 'Should be ignored'; + } + func(a) {} + [util.inspect.custom]() { + return this; + } + } + + class Bar extends Foo { + abc = true; + prop = true; + get xyz() { + return 'YES!'; + } + [util.inspect.custom]() { + return this; + } + } + + const bar = new Bar(); + + assert.strictEqual( + inspect(bar), + 'Bar(0) [Map] { prop: true, prop2: true, abc: true }' + ); + // TODO(wafuwafu13): Fix + // assert.strictEqual( + // inspect(bar, { showHidden: true, getters: true, colors: false }), + // 'Bar(0) [Map] {\n' + + // ' prop: true,\n' + + // ' prop2: true,\n' + + // ' abc: true,\n' + + // " [xyz]: [Getter: 'YES!'],\n" + + // ' [def]: [Getter/Setter: false]\n' + + // '}' + // ); + // assert.strictEqual( + // inspect(bar, { showHidden: true, getters: false, colors: true }), + // 'Bar(0) [Map] {\n' + + // ' prop: \x1B[33mtrue\x1B[39m,\n' + + // ' prop2: \x1B[33mtrue\x1B[39m,\n' + + // ' abc: \x1B[33mtrue\x1B[39m,\n' + + // ' \x1B[2m[xyz]: \x1B[36m[Getter]\x1B[39m\x1B[22m,\n' + + // ' \x1B[2m[def]: \x1B[36m[Getter/Setter]\x1B[39m\x1B[22m\n' + + // '}' + // ); + + // const obj = Object.create({ abc: true, def: 5, toString() {} }); + // assert.strictEqual( + // inspect(obj, { showHidden: true, colors: true }), + // '{ \x1B[2mabc: \x1B[33mtrue\x1B[39m\x1B[22m, ' + + // '\x1B[2mdef: \x1B[33m5\x1B[39m\x1B[22m }' + // ); + + // assert.strictEqual( + // inspect(Object.getPrototypeOf(bar), { showHidden: true, getters: true }), + // ' Foo [Map] {\n' + + // ' [constructor]: [class Bar extends Foo] {\n' + + // ' [length]: 0,\n' + + // " [name]: 'Bar',\n" + + // ' [prototype]: [Circular *1],\n' + + // ' [Symbol(Symbol.species)]: [Getter: ]\n" + + // ' },\n' + + // " [xyz]: [Getter: 'YES!'],\n" + + // ' [Symbol(nodejs.util.inspect.custom)]: ' + + // '[Function: [nodejs.util.inspect.custom]] {\n' + + // ' [length]: 0,\n' + + // " [name]: '[nodejs.util.inspect.custom]'\n" + + // ' },\n' + + // ' [abc]: [Getter: true],\n' + + // ' [def]: [Getter/Setter: false]\n' + + // ' }' + // ); + + // assert.strictEqual( + // inspect(Object.getPrototypeOf(bar)), + // 'Foo [Map] {}' + // ); + + // assert.strictEqual( + // inspect(Object.getPrototypeOf(new Foo())), + // 'Map {}' + // ); +} + +// Check that prototypes with a null prototype are inspectable. +// Regression test for https://github.com/nodejs/node/issues/35730 +{ + function Func() {} + Func.prototype = null; + const object = {}; + object.constructor = Func; + + assert.strictEqual(util.inspect(object), '{ constructor: [Function: Func] }'); +} + +// Test changing util.inspect.colors colors and aliases. +{ + const colors = util.inspect.colors; + + const originalValue = colors.gray; + + // "grey" is reference-equal alias of "gray". + assert.strictEqual(colors.grey, colors.gray); + + // Assigninging one should assign the other. This tests that the alias setter + // function keeps things reference-equal. + colors.gray = [0, 0]; + assert.deepStrictEqual(colors.gray, [0, 0]); + assert.strictEqual(colors.grey, colors.gray); + + colors.grey = [1, 1]; + assert.deepStrictEqual(colors.grey, [1, 1]); + assert.strictEqual(colors.grey, colors.gray); + + // Restore original value to avoid side effects in other tests. + colors.gray = originalValue; + assert.deepStrictEqual(colors.gray, originalValue); + assert.strictEqual(colors.grey, colors.gray); +} + +// TODO(wafuwafu13): Implement 'vm' +// // https://github.com/nodejs/node/issues/31889 +// { +// v8.setFlagsFromString('--allow-natives-syntax'); +// const undetectable = vm.runInThisContext('%GetUndetectable()'); +// v8.setFlagsFromString('--no-allow-natives-syntax'); +// assert.strictEqual(inspect(undetectable), '{}'); +// } + +// Truncate output for Primitives with 1 character left +{ + assert.strictEqual(util.inspect('bl', { maxStringLength: 1 }), + "'b'... 1 more character"); +} + +{ + const x = 'a'.repeat(1e6); + assert(util.inspect(x).endsWith('... 990000 more characters')); + assert.strictEqual( + util.inspect(x, { maxStringLength: 4 }), + "'aaaa'... 999996 more characters" + ); + assert.match(util.inspect(x, { maxStringLength: null }), /a'$/); +} + +// TODO(wafuwafu13): Implement 'vm' +// { +// // Verify that util.inspect() invokes custom inspect functions on objects +// // from other vm.Contexts but does not pass data from its own Context to that +// // function. +// const target = vm.runInNewContext(` +// ({ +// [Symbol.for('nodejs.util.inspect.custom')](depth, ctx) { +// this.depth = depth; +// this.ctx = ctx; +// try { +// this.stylized = ctx.stylize('🐈'); +// } catch (e) { +// this.stylizeException = e; +// } +// return this.stylized; +// } +// }) +// `, Object.create(null)); +// assert.strictEqual(target.ctx, undefined); + +// { +// // Subtest 1: Just try to inspect the object with default options. +// assert.strictEqual(util.inspect(target), '🐈'); +// assert.strictEqual(typeof target.ctx, 'object'); +// const objectGraph = fullObjectGraph(target); +// assert(!objectGraph.has(Object)); +// assert(!objectGraph.has(Function)); +// } + +// { +// // Subtest 2: Use a stylize function that returns a non-primitive. +// const output = util.inspect(target, { +// stylize: common.mustCall((str) => { +// return {}; +// }) +// }); +// assert.strictEqual(output, '[object Object]'); +// assert.strictEqual(typeof target.ctx, 'object'); +// const objectGraph = fullObjectGraph(target); +// assert(!objectGraph.has(Object)); +// assert(!objectGraph.has(Function)); +// } + +// { +// // Subtest 3: Use a stylize function that throws an exception. +// const output = util.inspect(target, { +// stylize: common.mustCall((str) => { +// throw new Error('oops'); +// }) +// }); +// assert.strictEqual(output, '🐈'); +// assert.strictEqual(typeof target.ctx, 'object'); +// const objectGraph = fullObjectGraph(target); +// assert(!objectGraph.has(Object)); +// assert(!objectGraph.has(Function)); +// } + +// function fullObjectGraph(value) { +// const graph = new Set([value]); + +// for (const entry of graph) { +// if ((typeof entry !== 'object' && typeof entry !== 'function') || +// entry === null) { +// continue; +// } + +// graph.add(Object.getPrototypeOf(entry)); +// const descriptors = Object.values( +// Object.getOwnPropertyDescriptors(entry)); +// for (const descriptor of descriptors) { +// graph.add(descriptor.value); +// graph.add(descriptor.set); +// graph.add(descriptor.get); +// } +// } + +// return graph; +// } + +// // Consistency check. +// assert(fullObjectGraph(global).has(Function.prototype)); +// } + +{ + // Confirm that own constructor value displays correctly. + + function Fhqwhgads() {} + + const sterrance = new Fhqwhgads(); + sterrance.constructor = Fhqwhgads; + + assert.strictEqual( + util.inspect(sterrance, { showHidden: true }), + 'Fhqwhgads {\n' + + ' constructor: [Function: Fhqwhgads] {\n' + + ' [length]: 0,\n' + + " [name]: 'Fhqwhgads',\n" + + ' [prototype]: { [constructor]: [Circular *1] }\n' + + ' }\n' + + '}' + ); +} + +// TODO(wafuwafu13): Fix TypeError: main.hasOwnProperty is not a function +// { +// // Confirm null prototype of generator prototype displays as expected. + +// function getProtoOfProto() { +// return Object.getPrototypeOf(Object.getPrototypeOf(function* () {})); +// } + +// function* generator() {} + +// const generatorPrototype = Object.getPrototypeOf(generator); +// const originalProtoOfProto = Object.getPrototypeOf(generatorPrototype); +// assert.strictEqual(getProtoOfProto(), originalProtoOfProto); +// Object.setPrototypeOf(generatorPrototype, null); +// assert.notStrictEqual(getProtoOfProto, originalProtoOfProto); + +// // This is the actual test. The other assertions in this block are about +// // making sure the test is set up correctly and isn't polluting other tests. +// assert.strictEqual( +// util.inspect(generator, { showHidden: true }), +// '[GeneratorFunction: generator] {\n' + +// ' [length]: 0,\n' + +// " [name]: 'generator',\n" + +// " [prototype]: Object [Generator] { [Symbol(Symbol.toStringTag)]: 'Generator' },\n" + // eslint-disable-line max-len +// " [Symbol(Symbol.toStringTag)]: 'GeneratorFunction'\n" + +// '}' +// ); + +// // Reset so we don't pollute other tests +// Object.setPrototypeOf(generatorPrototype, originalProtoOfProto); +// assert.strictEqual(getProtoOfProto(), originalProtoOfProto); +// } + +{ + // Test for when breakLength results in a single column. + const obj = Array(9).fill('fhqwhgadshgnsdhjsdbkhsdabkfabkveybvf'); + assert.strictEqual( + util.inspect(obj, { breakLength: 256 }), + '[\n' + + " 'fhqwhgadshgnsdhjsdbkhsdabkfabkveybvf',\n" + + " 'fhqwhgadshgnsdhjsdbkhsdabkfabkveybvf',\n" + + " 'fhqwhgadshgnsdhjsdbkhsdabkfabkveybvf',\n" + + " 'fhqwhgadshgnsdhjsdbkhsdabkfabkveybvf',\n" + + " 'fhqwhgadshgnsdhjsdbkhsdabkfabkveybvf',\n" + + " 'fhqwhgadshgnsdhjsdbkhsdabkfabkveybvf',\n" + + " 'fhqwhgadshgnsdhjsdbkhsdabkfabkveybvf',\n" + + " 'fhqwhgadshgnsdhjsdbkhsdabkfabkveybvf',\n" + + " 'fhqwhgadshgnsdhjsdbkhsdabkfabkveybvf'\n" + + ']' + ); +} + +{ + assert.strictEqual( + util.inspect({ ['__proto__']: { a: 1 } }), + "{ ['__proto__']: { a: 1 } }" + ); +} diff --git a/cli/tests/node_compat/test/parallel/test-util-isDeepStrictEqual.js b/cli/tests/node_compat/test/parallel/test-util-isDeepStrictEqual.js new file mode 100644 index 00000000000000..25caac1f7021db --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-util-isDeepStrictEqual.js @@ -0,0 +1,608 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 16.13.0 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +"use strict"; + +// Confirm functionality of `util.isDeepStrictEqual()`. + +require("../common"); + +const assert = require("assert"); +const util = require("util"); + +class MyDate extends Date { + constructor(...args) { + super(...args); + this[0] = "1"; + } +} + +class MyRegExp extends RegExp { + constructor(...args) { + super(...args); + this[0] = "1"; + } +} + +{ + const arr = new Uint8Array([120, 121, 122, 10]); + const buf = Buffer.from(arr); + // They have different [[Prototype]] + assert.strictEqual(util.isDeepStrictEqual(arr, buf), false); + + const buf2 = Buffer.from(arr); + buf2.prop = 1; + + assert.strictEqual(util.isDeepStrictEqual(buf2, buf), false); + + const arr2 = new Uint8Array([120, 121, 122, 10]); + arr2.prop = 5; + assert.strictEqual(util.isDeepStrictEqual(arr, arr2), false); +} + +{ + const date = new Date("2016"); + + const date2 = new MyDate("2016"); + + // deepStrictEqual checks own properties + assert.strictEqual(util.isDeepStrictEqual(date, date2), false); + assert.strictEqual(util.isDeepStrictEqual(date2, date), false); +} + +{ + const re1 = new RegExp("test"); + const re2 = new MyRegExp("test"); + + // deepStrictEqual checks all properties + assert.strictEqual(util.isDeepStrictEqual(re1, re2), false); +} + +{ + // For these cases, deepStrictEqual should throw. + const similar = new Set([ + { 0: "1" }, // Object + { 0: 1 }, // Object + new String("1"), // Object + ["1"], // Array + [1], // Array + new MyDate("2016"), // Date with this[0] = '1' + new MyRegExp("test"), // RegExp with this[0] = '1' + new Int8Array([1]), // Int8Array + new Uint8Array([1]), // Uint8Array + new Int16Array([1]), // Int16Array + new Uint16Array([1]), // Uint16Array + new Int32Array([1]), // Int32Array + new Uint32Array([1]), // Uint32Array + Buffer.from([1]), // Buffer + ]); + + for (const a of similar) { + for (const b of similar) { + if (a !== b) { + assert.strictEqual(util.isDeepStrictEqual(a, b), false); + } + } + } +} + +function utilIsDeepStrict(a, b) { + assert.strictEqual(util.isDeepStrictEqual(a, b), true); + assert.strictEqual(util.isDeepStrictEqual(b, a), true); +} +function notUtilIsDeepStrict(a, b) { + assert.strictEqual(util.isDeepStrictEqual(a, b), false); + assert.strictEqual(util.isDeepStrictEqual(b, a), false); +} + +// es6 Maps and Sets +utilIsDeepStrict(new Set(), new Set()); +utilIsDeepStrict(new Map(), new Map()); + +utilIsDeepStrict(new Set([1, 2, 3]), new Set([1, 2, 3])); +notUtilIsDeepStrict(new Set([1, 2, 3]), new Set([1, 2, 3, 4])); +notUtilIsDeepStrict(new Set([1, 2, 3, 4]), new Set([1, 2, 3])); +utilIsDeepStrict(new Set(["1", "2", "3"]), new Set(["1", "2", "3"])); +utilIsDeepStrict( + new Set([ + [1, 2], + [3, 4], + ]), + new Set([ + [3, 4], + [1, 2], + ]) +); + +{ + const a = [1, 2]; + const b = [3, 4]; + const c = [1, 2]; + const d = [3, 4]; + + utilIsDeepStrict( + { a: a, b: b, s: new Set([a, b]) }, + { a: c, b: d, s: new Set([d, c]) } + ); +} + +utilIsDeepStrict( + new Map([ + [1, 1], + [2, 2], + ]), + new Map([ + [1, 1], + [2, 2], + ]) +); +utilIsDeepStrict( + new Map([ + [1, 1], + [2, 2], + ]), + new Map([ + [2, 2], + [1, 1], + ]) +); +notUtilIsDeepStrict( + new Map([ + [1, 1], + [2, 2], + ]), + new Map([ + [1, 2], + [2, 1], + ]) +); +notUtilIsDeepStrict( + new Map([ + [[1], 1], + [{}, 2], + ]), + new Map([ + [[1], 2], + [{}, 1], + ]) +); + +notUtilIsDeepStrict(new Set([1]), [1]); +notUtilIsDeepStrict(new Set(), []); +notUtilIsDeepStrict(new Set(), {}); + +notUtilIsDeepStrict(new Map([["a", 1]]), { a: 1 }); +notUtilIsDeepStrict(new Map(), []); +notUtilIsDeepStrict(new Map(), {}); + +notUtilIsDeepStrict(new Set(["1"]), new Set([1])); + +notUtilIsDeepStrict(new Map([["1", "a"]]), new Map([[1, "a"]])); +notUtilIsDeepStrict(new Map([["a", "1"]]), new Map([["a", 1]])); +notUtilIsDeepStrict(new Map([["a", "1"]]), new Map([["a", 2]])); + +utilIsDeepStrict(new Set([{}]), new Set([{}])); + +// Ref: https://github.com/nodejs/node/issues/13347 +notUtilIsDeepStrict( + new Set([{ a: 1 }, { a: 1 }]), + new Set([{ a: 1 }, { a: 2 }]) +); +notUtilIsDeepStrict( + new Set([{ a: 1 }, { a: 1 }, { a: 2 }]), + new Set([{ a: 1 }, { a: 2 }, { a: 2 }]) +); +notUtilIsDeepStrict( + new Map([ + [{ x: 1 }, 5], + [{ x: 1 }, 5], + ]), + new Map([ + [{ x: 1 }, 5], + [{ x: 2 }, 5], + ]) +); + +notUtilIsDeepStrict(new Set([3, "3"]), new Set([3, 4])); +notUtilIsDeepStrict( + new Map([ + [3, 0], + ["3", 0], + ]), + new Map([ + [3, 0], + [4, 0], + ]) +); + +notUtilIsDeepStrict( + new Set([{ a: 1 }, { a: 1 }, { a: 2 }]), + new Set([{ a: 1 }, { a: 2 }, { a: 2 }]) +); + +// Mixed primitive and object keys +utilIsDeepStrict( + new Map([ + [1, "a"], + [{}, "a"], + ]), + new Map([ + [1, "a"], + [{}, "a"], + ]) +); +utilIsDeepStrict(new Set([1, "a", [{}, "a"]]), new Set([1, "a", [{}, "a"]])); + +// This is an awful case, where a map contains multiple equivalent keys: +notUtilIsDeepStrict( + new Map([ + [1, "a"], + ["1", "b"], + ]), + new Map([ + ["1", "a"], + [true, "b"], + ]) +); +notUtilIsDeepStrict(new Set(["a"]), new Set(["b"])); +utilIsDeepStrict( + new Map([ + [{}, "a"], + [{}, "b"], + ]), + new Map([ + [{}, "b"], + [{}, "a"], + ]) +); +notUtilIsDeepStrict( + new Map([ + [true, "a"], + ["1", "b"], + [1, "a"], + ]), + new Map([ + ["1", "a"], + [1, "b"], + [true, "a"], + ]) +); +notUtilIsDeepStrict( + new Map([ + [true, "a"], + ["1", "b"], + [1, "c"], + ]), + new Map([ + ["1", "a"], + [1, "b"], + [true, "a"], + ]) +); + +// Similar object keys +notUtilIsDeepStrict(new Set([{}, {}]), new Set([{}, 1])); +notUtilIsDeepStrict( + new Set([ + [{}, 1], + [{}, 1], + ]), + new Set([ + [{}, 1], + [1, 1], + ]) +); +notUtilIsDeepStrict( + new Map([ + [{}, 1], + [{}, 1], + ]), + new Map([ + [{}, 1], + [1, 1], + ]) +); +notUtilIsDeepStrict( + new Map([ + [{}, 1], + [true, 1], + ]), + new Map([ + [{}, 1], + [1, 1], + ]) +); + +// Similar primitive key / values +notUtilIsDeepStrict(new Set([1, true, false]), new Set(["1", 0, "0"])); +notUtilIsDeepStrict( + new Map([ + [1, 5], + [true, 5], + [false, 5], + ]), + new Map([ + ["1", 5], + [0, 5], + ["0", 5], + ]) +); + +// Undefined value in Map +utilIsDeepStrict(new Map([[1, undefined]]), new Map([[1, undefined]])); +notUtilIsDeepStrict(new Map([[1, null]]), new Map([["1", undefined]])); +notUtilIsDeepStrict(new Map([[1, undefined]]), new Map([[2, undefined]])); + +// null as key +utilIsDeepStrict(new Map([[null, 3]]), new Map([[null, 3]])); +notUtilIsDeepStrict(new Map([[null, undefined]]), new Map([[undefined, null]])); +notUtilIsDeepStrict(new Set([null]), new Set([undefined])); + +// GH-6416. Make sure circular refs don't throw. +{ + const b = {}; + b.b = b; + const c = {}; + c.b = c; + + utilIsDeepStrict(b, c); + + const d = {}; + d.a = 1; + d.b = d; + const e = {}; + e.a = 1; + e.b = {}; + + notUtilIsDeepStrict(d, e); +} + +// GH-14441. Circular structures should be consistent +{ + const a = {}; + const b = {}; + a.a = a; + b.a = {}; + b.a.a = a; + utilIsDeepStrict(a, b); +} + +{ + const a = new Set(); + const b = new Set(); + const c = new Set(); + a.add(a); + b.add(b); + c.add(a); + utilIsDeepStrict(b, c); +} + +// GH-7178. Ensure reflexivity of deepEqual with `arguments` objects. +{ + const args = (function () { + return arguments; + })(); + notUtilIsDeepStrict([], args); +} + +// More checking that arguments objects are handled correctly +{ + // eslint-disable-next-line func-style + const returnArguments = function () { + return arguments; + }; + + const someArgs = returnArguments("a"); + const sameArgs = returnArguments("a"); + const diffArgs = returnArguments("b"); + + notUtilIsDeepStrict(someArgs, ["a"]); + notUtilIsDeepStrict(someArgs, { 0: "a" }); + notUtilIsDeepStrict(someArgs, diffArgs); + utilIsDeepStrict(someArgs, sameArgs); +} + +{ + const values = [ + 123, + Infinity, + 0, + null, + undefined, + false, + true, + {}, + [], + () => {}, + ]; + utilIsDeepStrict(new Set(values), new Set(values)); + utilIsDeepStrict(new Set(values), new Set(values.reverse())); + + const mapValues = values.map((v) => [v, { a: 5 }]); + utilIsDeepStrict(new Map(mapValues), new Map(mapValues)); + utilIsDeepStrict(new Map(mapValues), new Map(mapValues.reverse())); +} + +{ + const s1 = new Set(); + const s2 = new Set(); + s1.add(1); + s1.add(2); + s2.add(2); + s2.add(1); + utilIsDeepStrict(s1, s2); +} + +{ + const m1 = new Map(); + const m2 = new Map(); + const obj = { a: 5, b: 6 }; + m1.set(1, obj); + m1.set(2, "hi"); + m1.set(3, [1, 2, 3]); + + m2.set(2, "hi"); // different order + m2.set(1, obj); + m2.set(3, [1, 2, 3]); // Deep equal, but not reference equal. + + utilIsDeepStrict(m1, m2); +} + +{ + const m1 = new Map(); + const m2 = new Map(); + + // m1 contains itself. + m1.set(1, m1); + m2.set(1, new Map()); + + notUtilIsDeepStrict(m1, m2); +} + +{ + const map1 = new Map([[1, 1]]); + const map2 = new Map([[1, "1"]]); + assert.strictEqual(util.isDeepStrictEqual(map1, map2), false); +} + +{ + // Two equivalent sets / maps with different key/values applied shouldn't be + // the same. This is a terrible idea to do in practice, but deepEqual should + // still check for it. + const s1 = new Set(); + const s2 = new Set(); + s1.x = 5; + notUtilIsDeepStrict(s1, s2); + + const m1 = new Map(); + const m2 = new Map(); + m1.x = 5; + notUtilIsDeepStrict(m1, m2); +} + +{ + // Circular references. + const s1 = new Set(); + s1.add(s1); + const s2 = new Set(); + s2.add(s2); + utilIsDeepStrict(s1, s2); + + const m1 = new Map(); + m1.set(2, m1); + const m2 = new Map(); + m2.set(2, m2); + utilIsDeepStrict(m1, m2); + + const m3 = new Map(); + m3.set(m3, 2); + const m4 = new Map(); + m4.set(m4, 2); + utilIsDeepStrict(m3, m4); +} + +// Handle sparse arrays +utilIsDeepStrict([1, , , 3], [1, , , 3]); +notUtilIsDeepStrict([1, , , 3], [1, , , 3, , ,]); + +// Handle different error messages +{ + const err1 = new Error("foo1"); + const err2 = new Error("foo2"); + const err3 = new TypeError("foo1"); + notUtilIsDeepStrict(err1, err2, assert.AssertionError); + notUtilIsDeepStrict(err1, err3, assert.AssertionError); + notUtilIsDeepStrict(err1, {}, assert.AssertionError); +} + +// Handle NaN +assert.strictEqual(util.isDeepStrictEqual(NaN, NaN), true); +assert.strictEqual(util.isDeepStrictEqual({ a: NaN }, { a: NaN }), true); +assert.strictEqual( + util.isDeepStrictEqual([1, 2, NaN, 4], [1, 2, NaN, 4]), + true +); + +// Handle boxed primitives +{ + const boxedString = new String("test"); + const boxedSymbol = Object(Symbol()); + notUtilIsDeepStrict(new Boolean(true), Object(false)); + notUtilIsDeepStrict(Object(true), new Number(1)); + notUtilIsDeepStrict(new Number(2), new Number(1)); + notUtilIsDeepStrict(boxedSymbol, Object(Symbol())); + notUtilIsDeepStrict(boxedSymbol, {}); + utilIsDeepStrict(boxedSymbol, boxedSymbol); + utilIsDeepStrict(Object(true), Object(true)); + utilIsDeepStrict(Object(2), Object(2)); + utilIsDeepStrict(boxedString, Object("test")); + boxedString.slow = true; + notUtilIsDeepStrict(boxedString, Object("test")); + boxedSymbol.slow = true; + notUtilIsDeepStrict(boxedSymbol, {}); + utilIsDeepStrict(Object(BigInt(1)), Object(BigInt(1))); + notUtilIsDeepStrict(Object(BigInt(1)), Object(BigInt(2))); + + const booleanish = new Boolean(true); + Object.defineProperty(booleanish, Symbol.toStringTag, { value: "String" }); + Object.setPrototypeOf(booleanish, String.prototype); + notUtilIsDeepStrict(booleanish, new String("true")); + + const numberish = new Number(42); + Object.defineProperty(numberish, Symbol.toStringTag, { value: "String" }); + Object.setPrototypeOf(numberish, String.prototype); + notUtilIsDeepStrict(numberish, new String("42")); + + const stringish = new String("0"); + Object.defineProperty(stringish, Symbol.toStringTag, { value: "Number" }); + Object.setPrototypeOf(stringish, Number.prototype); + notUtilIsDeepStrict(stringish, new Number(0)); + + const bigintish = new Object(BigInt(42)); + Object.defineProperty(bigintish, Symbol.toStringTag, { value: "String" }); + Object.setPrototypeOf(bigintish, String.prototype); + notUtilIsDeepStrict(bigintish, new String("42")); + + const symbolish = new Object(Symbol("fhqwhgads")); + Object.defineProperty(symbolish, Symbol.toStringTag, { value: "String" }); + Object.setPrototypeOf(symbolish, String.prototype); + notUtilIsDeepStrict(symbolish, new String("fhqwhgads")); +} + +// Minus zero +notUtilIsDeepStrict(0, -0); +utilIsDeepStrict(-0, -0); + +// Handle symbols (enumerable only) +{ + const symbol1 = Symbol(); + const obj1 = { [symbol1]: 1 }; + const obj2 = { [symbol1]: 1 }; + const obj3 = { [Symbol()]: 1 }; + const obj4 = {}; + // Add a non enumerable symbol as well. It is going to be ignored! + Object.defineProperty(obj2, Symbol(), { value: 1 }); + Object.defineProperty(obj4, symbol1, { value: 1 }); + notUtilIsDeepStrict(obj1, obj3); + utilIsDeepStrict(obj1, obj2); + notUtilIsDeepStrict(obj1, obj4); + // TypedArrays have a fast path. Test for this as well. + const a = new Uint8Array(4); + const b = new Uint8Array(4); + a[symbol1] = true; + b[symbol1] = false; + notUtilIsDeepStrict(a, b); + b[symbol1] = true; + utilIsDeepStrict(a, b); + // The same as TypedArrays is valid for boxed primitives + const boxedStringA = new String("test"); + const boxedStringB = new String("test"); + boxedStringA[symbol1] = true; + notUtilIsDeepStrict(boxedStringA, boxedStringB); + boxedStringA[symbol1] = true; + utilIsDeepStrict(a, b); +} diff --git a/cli/tests/node_compat/test/parallel/test-util-promisify.js b/cli/tests/node_compat/test/parallel/test-util-promisify.js new file mode 100644 index 00000000000000..0c5b64349c247e --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-util-promisify.js @@ -0,0 +1,219 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 16.13.0 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +'use strict'; +// Flags: --expose-internals +const common = require('../common'); +const assert = require('assert'); +const fs = require('fs'); +const vm = require('vm'); +const { promisify } = require('util'); +const { customPromisifyArgs } = require('internal/util'); + +const stat = promisify(fs.stat); + +// TODO(wafuwafu13): Fix +// { +// const promise = stat(__filename); +// assert(promise instanceof Promise); +// promise.then(common.mustCall((value) => { +// assert.deepStrictEqual(value, fs.statSync(__filename)); +// })); +// } + +{ + const promise = stat('/dontexist'); + promise.catch(common.mustCall((error) => { + assert(error.message.includes('ENOENT: no such file or directory, stat')); + })); +} + +{ + function fn() {} + + function promisifedFn() {} + fn[promisify.custom] = promisifedFn; + assert.strictEqual(promisify(fn), promisifedFn); + assert.strictEqual(promisify(promisify(fn)), promisifedFn); +} + +{ + function fn() {} + + function promisifiedFn() {} + + // util.promisify.custom is a shared symbol which can be accessed + // as `Symbol.for("nodejs.util.promisify.custom")`. + const kCustomPromisifiedSymbol = Symbol.for('nodejs.util.promisify.custom'); + fn[kCustomPromisifiedSymbol] = promisifiedFn; + + assert.strictEqual(kCustomPromisifiedSymbol, promisify.custom); + assert.strictEqual(promisify(fn), promisifiedFn); + assert.strictEqual(promisify(promisify(fn)), promisifiedFn); +} + +{ + function fn() {} + fn[promisify.custom] = 42; + assert.throws( + () => promisify(fn), + { code: 'ERR_INVALID_ARG_TYPE', name: 'TypeError' } + ); +} + +// TODO(wafuwafu13): Fix +// { +// const firstValue = 5; +// const secondValue = 17; + +// function fn(callback) { +// callback(null, firstValue, secondValue); +// } + +// fn[customPromisifyArgs] = ['first', 'second']; + +// promisify(fn)().then(common.mustCall((obj) => { +// assert.deepStrictEqual(obj, { first: firstValue, second: secondValue }); +// })); +// } + +// TODO(wafuwafu13): Implement "vm.runInNewContext" +// { +// const fn = vm.runInNewContext('(function() {})'); +// assert.notStrictEqual(Object.getPrototypeOf(promisify(fn)), +// Function.prototype); +// } + +{ + function fn(callback) { + callback(null, 'foo', 'bar'); + } + promisify(fn)().then(common.mustCall((value) => { + assert.deepStrictEqual(value, 'foo'); + })); +} + +{ + function fn(callback) { + callback(null); + } + promisify(fn)().then(common.mustCall((value) => { + assert.strictEqual(value, undefined); + })); +} + +{ + function fn(callback) { + callback(); + } + promisify(fn)().then(common.mustCall((value) => { + assert.strictEqual(value, undefined); + })); +} + +{ + function fn(err, val, callback) { + callback(err, val); + } + promisify(fn)(null, 42).then(common.mustCall((value) => { + assert.strictEqual(value, 42); + })); +} + +{ + function fn(err, val, callback) { + callback(err, val); + } + promisify(fn)(new Error('oops'), null).catch(common.mustCall((err) => { + assert.strictEqual(err.message, 'oops'); + })); +} + +{ + function fn(err, val, callback) { + callback(err, val); + } + + (async () => { + const value = await promisify(fn)(null, 42); + assert.strictEqual(value, 42); + })().then(common.mustCall()); +} + +{ + const o = {}; + const fn = promisify(function(cb) { + + cb(null, this === o); + }); + + o.fn = fn; + + o.fn().then(common.mustCall((val) => assert(val))); +} + +{ + const err = new Error('Should not have called the callback with the error.'); + const stack = err.stack; + + const fn = promisify(function(cb) { + cb(null); + cb(err); + }); + + (async () => { + await fn(); + await Promise.resolve(); + return assert.strictEqual(stack, err.stack); + })().then(common.mustCall()); +} + +{ + function c() { } + const a = promisify(function() { }); + const b = promisify(a); + assert.notStrictEqual(c, a); + assert.strictEqual(a, b); +} + +{ + let errToThrow; + const thrower = promisify(function(a, b, c, cb) { + errToThrow = new Error(); + throw errToThrow; + }); + thrower(1, 2, 3) + .then(assert.fail) + .then(assert.fail, (e) => assert.strictEqual(e, errToThrow)); +} + +{ + const err = new Error(); + + const a = promisify((cb) => cb(err))(); + const b = promisify(() => { throw err; })(); + + Promise.all([ + a.then(assert.fail, function(e) { + assert.strictEqual(err, e); + }), + b.then(assert.fail, function(e) { + assert.strictEqual(err, e); + }), + ]); +} + +[undefined, null, true, 0, 'str', {}, [], Symbol()].forEach((input) => { + assert.throws( + () => promisify(input), + { + code: 'ERR_INVALID_ARG_TYPE', + name: 'TypeError', + message: 'The "original" argument must be of type function.' + + common.invalidArgTypeHelper(input) + }); +}); diff --git a/cli/tests/node_compat/test/parallel/test-util-types-exists.js b/cli/tests/node_compat/test/parallel/test-util-types-exists.js new file mode 100644 index 00000000000000..805d3e11e5aebd --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-util-types-exists.js @@ -0,0 +1,13 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +'use strict'; + +require('../common'); +const assert = require('assert'); + +assert.strictEqual(require('util/types'), require('util').types); diff --git a/cli/tests/node_compat/test/parallel/test-util-types.js b/cli/tests/node_compat/test/parallel/test-util-types.js new file mode 100644 index 00000000000000..00e3d0fd063f84 --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-util-types.js @@ -0,0 +1,306 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 16.13.0 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +// Flags: --experimental-vm-modules --expose-internals +'use strict'; +const common = require('../common'); +const assert = require('assert'); +const { types, inspect } = require('util'); +// TODO(wafuwafu13): Implement "vm" +// const vm = require('vm'); +// TODO(wafuwafu13): Implement "internalBinding" +// const { internalBinding } = require('internal/test/binding'); +// const { JSStream } = internalBinding('js_stream'); + +// TODO(wafuwafu13): Implement "JSStream" +// const external = (new JSStream())._externalStream; + +for (const [ value, _method ] of [ + // TODO(wafuwafu13): Implement "JSStream" + // [ external, 'isExternal' ], + [ new Date() ], + [ (function() { return arguments; })(), 'isArgumentsObject' ], + [ new Boolean(), 'isBooleanObject' ], + [ new Number(), 'isNumberObject' ], + [ new String(), 'isStringObject' ], + [ Object(Symbol()), 'isSymbolObject' ], + [ Object(BigInt(0)), 'isBigIntObject' ], + [ new Error(), 'isNativeError' ], + [ new RegExp() ], + [ async function() {}, 'isAsyncFunction' ], + [ function*() {}, 'isGeneratorFunction' ], + [ (function*() {})(), 'isGeneratorObject' ], + [ Promise.resolve() ], + [ new Map() ], + [ new Set() ], + [ (new Map())[Symbol.iterator](), 'isMapIterator' ], + [ (new Set())[Symbol.iterator](), 'isSetIterator' ], + [ new WeakMap() ], + [ new WeakSet() ], + [ new ArrayBuffer() ], + [ new Uint8Array() ], + [ new Uint8ClampedArray() ], + [ new Uint16Array() ], + [ new Uint32Array() ], + [ new Int8Array() ], + [ new Int16Array() ], + [ new Int32Array() ], + [ new Float32Array() ], + [ new Float64Array() ], + [ new BigInt64Array() ], + [ new BigUint64Array() ], + // [ Object.defineProperty(new Uint8Array(), + // Symbol.toStringTag, + // { value: 'foo' }) ], + [ new DataView(new ArrayBuffer()) ], + [ new SharedArrayBuffer() ], + [ new Proxy({}, {}), 'isProxy' ], +]) { + const method = _method || `is${value.constructor.name}`; + assert(method in types, `Missing ${method} for ${inspect(value)}`); + assert(types[method](value), `Want ${inspect(value)} to match ${method}`); + + for (const key of Object.keys(types)) { + if ((types.isArrayBufferView(value) || + types.isAnyArrayBuffer(value)) && key.includes('Array') || + key === 'isBoxedPrimitive') { + continue; + } + + assert.strictEqual(types[key](value), + key === method, + `${inspect(value)}: ${key}, ` + + `${method}, ${types[key](value)}`); + } +} + +// Check boxed primitives. +[ + new Boolean(), + new Number(), + new String(), + Object(Symbol()), + Object(BigInt(0)), +].forEach((entry) => assert(types.isBoxedPrimitive(entry))); + +// TODO(wafuwafu13): Fix +// { +// assert(!types.isUint8Array({ [Symbol.toStringTag]: 'Uint8Array' })); +// assert(types.isUint8Array(vm.runInNewContext('new Uint8Array'))); + +// assert(!types.isUint8ClampedArray({ +// [Symbol.toStringTag]: 'Uint8ClampedArray' +// })); +// assert(types.isUint8ClampedArray( +// vm.runInNewContext('new Uint8ClampedArray') +// )); + +// assert(!types.isUint16Array({ [Symbol.toStringTag]: 'Uint16Array' })); +// assert(types.isUint16Array(vm.runInNewContext('new Uint16Array'))); + +// assert(!types.isUint32Array({ [Symbol.toStringTag]: 'Uint32Array' })); +// assert(types.isUint32Array(vm.runInNewContext('new Uint32Array'))); + +// assert(!types.isInt8Array({ [Symbol.toStringTag]: 'Int8Array' })); +// assert(types.isInt8Array(vm.runInNewContext('new Int8Array'))); + +// assert(!types.isInt16Array({ [Symbol.toStringTag]: 'Int16Array' })); +// assert(types.isInt16Array(vm.runInNewContext('new Int16Array'))); + +// assert(!types.isInt32Array({ [Symbol.toStringTag]: 'Int32Array' })); +// assert(types.isInt32Array(vm.runInNewContext('new Int32Array'))); + +// assert(!types.isFloat32Array({ [Symbol.toStringTag]: 'Float32Array' })); +// assert(types.isFloat32Array(vm.runInNewContext('new Float32Array'))); + +// assert(!types.isFloat64Array({ [Symbol.toStringTag]: 'Float64Array' })); +// assert(types.isFloat64Array(vm.runInNewContext('new Float64Array'))); + +// assert(!types.isBigInt64Array({ [Symbol.toStringTag]: 'BigInt64Array' })); +// assert(types.isBigInt64Array(vm.runInNewContext('new BigInt64Array'))); + +// assert(!types.isBigUint64Array({ [Symbol.toStringTag]: 'BigUint64Array' })); +// assert(types.isBigUint64Array(vm.runInNewContext('new BigUint64Array'))); +// } + +{ + const primitive = true; + const arrayBuffer = new ArrayBuffer(); + const buffer = Buffer.from(arrayBuffer); + const dataView = new DataView(arrayBuffer); + const uint8Array = new Uint8Array(arrayBuffer); + const uint8ClampedArray = new Uint8ClampedArray(arrayBuffer); + const uint16Array = new Uint16Array(arrayBuffer); + const uint32Array = new Uint32Array(arrayBuffer); + const int8Array = new Int8Array(arrayBuffer); + const int16Array = new Int16Array(arrayBuffer); + const int32Array = new Int32Array(arrayBuffer); + const float32Array = new Float32Array(arrayBuffer); + const float64Array = new Float64Array(arrayBuffer); + const bigInt64Array = new BigInt64Array(arrayBuffer); + const bigUint64Array = new BigUint64Array(arrayBuffer); + + const fakeBuffer = Object.create(Buffer.prototype); + const fakeDataView = Object.create(DataView.prototype); + const fakeUint8Array = Object.create(Uint8Array.prototype); + const fakeUint8ClampedArray = Object.create(Uint8ClampedArray.prototype); + const fakeUint16Array = Object.create(Uint16Array.prototype); + const fakeUint32Array = Object.create(Uint32Array.prototype); + const fakeInt8Array = Object.create(Int8Array.prototype); + const fakeInt16Array = Object.create(Int16Array.prototype); + const fakeInt32Array = Object.create(Int32Array.prototype); + const fakeFloat32Array = Object.create(Float32Array.prototype); + const fakeFloat64Array = Object.create(Float64Array.prototype); + const fakeBigInt64Array = Object.create(BigInt64Array.prototype); + const fakeBigUint64Array = Object.create(BigUint64Array.prototype); + + const stealthyDataView = + Object.setPrototypeOf(new DataView(arrayBuffer), Uint8Array.prototype); + const stealthyUint8Array = + Object.setPrototypeOf(new Uint8Array(arrayBuffer), ArrayBuffer.prototype); + const stealthyUint8ClampedArray = + Object.setPrototypeOf( + new Uint8ClampedArray(arrayBuffer), ArrayBuffer.prototype + ); + const stealthyUint16Array = + Object.setPrototypeOf(new Uint16Array(arrayBuffer), Uint16Array.prototype); + const stealthyUint32Array = + Object.setPrototypeOf(new Uint32Array(arrayBuffer), Uint32Array.prototype); + const stealthyInt8Array = + Object.setPrototypeOf(new Int8Array(arrayBuffer), Int8Array.prototype); + const stealthyInt16Array = + Object.setPrototypeOf(new Int16Array(arrayBuffer), Int16Array.prototype); + const stealthyInt32Array = + Object.setPrototypeOf(new Int32Array(arrayBuffer), Int32Array.prototype); + const stealthyFloat32Array = + Object.setPrototypeOf( + new Float32Array(arrayBuffer), Float32Array.prototype + ); + const stealthyFloat64Array = + Object.setPrototypeOf( + new Float64Array(arrayBuffer), Float64Array.prototype + ); + const stealthyBigInt64Array = + Object.setPrototypeOf( + new BigInt64Array(arrayBuffer), BigInt64Array.prototype + ); + const stealthyBigUint64Array = + Object.setPrototypeOf( + new BigUint64Array(arrayBuffer), BigUint64Array.prototype + ); + + const all = [ + primitive, arrayBuffer, buffer, fakeBuffer, + dataView, fakeDataView, stealthyDataView, + uint8Array, fakeUint8Array, stealthyUint8Array, + uint8ClampedArray, fakeUint8ClampedArray, stealthyUint8ClampedArray, + uint16Array, fakeUint16Array, stealthyUint16Array, + uint32Array, fakeUint32Array, stealthyUint32Array, + int8Array, fakeInt8Array, stealthyInt8Array, + int16Array, fakeInt16Array, stealthyInt16Array, + int32Array, fakeInt32Array, stealthyInt32Array, + float32Array, fakeFloat32Array, stealthyFloat32Array, + float64Array, fakeFloat64Array, stealthyFloat64Array, + bigInt64Array, fakeBigInt64Array, stealthyBigInt64Array, + bigUint64Array, fakeBigUint64Array, stealthyBigUint64Array, + ]; + + const expected = { + isArrayBufferView: [ + buffer, + dataView, stealthyDataView, + uint8Array, stealthyUint8Array, + uint8ClampedArray, stealthyUint8ClampedArray, + uint16Array, stealthyUint16Array, + uint32Array, stealthyUint32Array, + int8Array, stealthyInt8Array, + int16Array, stealthyInt16Array, + int32Array, stealthyInt32Array, + float32Array, stealthyFloat32Array, + float64Array, stealthyFloat64Array, + bigInt64Array, stealthyBigInt64Array, + bigUint64Array, stealthyBigUint64Array, + ], + isTypedArray: [ + buffer, + uint8Array, stealthyUint8Array, + uint8ClampedArray, stealthyUint8ClampedArray, + uint16Array, stealthyUint16Array, + uint32Array, stealthyUint32Array, + int8Array, stealthyInt8Array, + int16Array, stealthyInt16Array, + int32Array, stealthyInt32Array, + float32Array, stealthyFloat32Array, + float64Array, stealthyFloat64Array, + bigInt64Array, stealthyBigInt64Array, + bigUint64Array, stealthyBigUint64Array, + ], + isUint8Array: [ + buffer, uint8Array, stealthyUint8Array, + ], + isUint8ClampedArray: [ + uint8ClampedArray, stealthyUint8ClampedArray, + ], + isUint16Array: [ + uint16Array, stealthyUint16Array, + ], + isUint32Array: [ + uint32Array, stealthyUint32Array, + ], + isInt8Array: [ + int8Array, stealthyInt8Array, + ], + isInt16Array: [ + int16Array, stealthyInt16Array, + ], + isInt32Array: [ + int32Array, stealthyInt32Array, + ], + isFloat32Array: [ + float32Array, stealthyFloat32Array, + ], + isFloat64Array: [ + float64Array, stealthyFloat64Array, + ], + isBigInt64Array: [ + bigInt64Array, stealthyBigInt64Array, + ], + isBigUint64Array: [ + bigUint64Array, stealthyBigUint64Array, + ] + }; + + for (const testedFunc of Object.keys(expected)) { + const func = types[testedFunc]; + const yup = []; + for (const value of all) { + if (func(value)) { + yup.push(value); + } + } + console.log('Testing', testedFunc); + assert.deepStrictEqual(yup, expected[testedFunc]); + } +} + +// TODO(wafuwafu13): Implement "vm" +// (async () => { +// const m = new vm.SourceTextModule(''); +// await m.link(() => 0); +// await m.evaluate(); +// assert.ok(types.isModuleNamespaceObject(m.namespace)); +// })().then(common.mustCall()); + +{ + // eslint-disable-next-line node-core/crypto-check + if (common.hasCrypto) { + const crypto = require('crypto'); + assert.ok(!types.isKeyObject(crypto.createHash('sha1'))); + } + assert.ok(!types.isCryptoKey()); + assert.ok(!types.isKeyObject()); +} diff --git a/cli/tests/node_compat/test/parallel/test-util.js b/cli/tests/node_compat/test/parallel/test-util.js new file mode 100644 index 00000000000000..5d1ed9b301af00 --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-util.js @@ -0,0 +1,203 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 16.13.0 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. + +'use strict'; +// Flags: --expose-internals +const common = require('../common'); +const assert = require('assert'); +const util = require('util'); +const errors = require('internal/errors'); +// TODO(wafuwafu13): Enable this when "vm" is ready. +// const context = require('vm').runInNewContext; + +// isArray +assert.strictEqual(util.isArray([]), true); +assert.strictEqual(util.isArray(Array()), true); +assert.strictEqual(util.isArray(new Array()), true); +assert.strictEqual(util.isArray(new Array(5)), true); +assert.strictEqual(util.isArray(new Array('with', 'some', 'entries')), true); +// TODO(wafuwafu13): Enable this when "vm" is ready. +// assert.strictEqual(util.isArray(context('Array')()), true); +assert.strictEqual(util.isArray({}), false); +assert.strictEqual(util.isArray({ push: function() {} }), false); +assert.strictEqual(util.isArray(/regexp/), false); +assert.strictEqual(util.isArray(new Error()), false); +assert.strictEqual(util.isArray(Object.create(Array.prototype)), false); + +// isRegExp +assert.strictEqual(util.isRegExp(/regexp/), true); +assert.strictEqual(util.isRegExp(RegExp(), 'foo'), true); +assert.strictEqual(util.isRegExp(new RegExp()), true); +// TODO(wafuwafu13): Enable this when "vm" is ready. +// assert.strictEqual(util.isRegExp(context('RegExp')()), true); +assert.strictEqual(util.isRegExp({}), false); +assert.strictEqual(util.isRegExp([]), false); +assert.strictEqual(util.isRegExp(new Date()), false); +// TODO(wafuwafu13): Enable this. +assert.strictEqual(util.isRegExp(Object.create(RegExp.prototype)), false); + +// isDate +assert.strictEqual(util.isDate(new Date()), true); +assert.strictEqual(util.isDate(new Date(0), 'foo'), true); +// TODO(wafuwafu13): Enable this when "vm" is ready. +// assert.strictEqual(util.isDate(new (context('Date'))()), true); +assert.strictEqual(util.isDate(Date()), false); +assert.strictEqual(util.isDate({}), false); +assert.strictEqual(util.isDate([]), false); +assert.strictEqual(util.isDate(new Error()), false); +assert.strictEqual(util.isDate(Object.create(Date.prototype)), false); + +// isError +assert.strictEqual(util.isError(new Error()), true); +assert.strictEqual(util.isError(new TypeError()), true); +assert.strictEqual(util.isError(new SyntaxError()), true); +// TODO(wafuwafu13): Enable this when "vm" is ready. +// assert.strictEqual(util.isError(new (context('Error'))()), true); +// assert.strictEqual(util.isError(new (context('TypeError'))()), true); +// assert.strictEqual(util.isError(new (context('SyntaxError'))()), true); +assert.strictEqual(util.isError({}), false); +assert.strictEqual(util.isError({ name: 'Error', message: '' }), false); +assert.strictEqual(util.isError([]), false); +assert.strictEqual(util.isError(Object.create(Error.prototype)), true); + +// isObject +assert.strictEqual(util.isObject({}), true); +assert.strictEqual(util.isObject([]), true); +assert.strictEqual(util.isObject(new Number(3)), true); +assert.strictEqual(util.isObject(Number(4)), false); +assert.strictEqual(util.isObject(1), false); + +// isPrimitive +assert.strictEqual(util.isPrimitive({}), false); +assert.strictEqual(util.isPrimitive(new Error()), false); +assert.strictEqual(util.isPrimitive(new Date()), false); +assert.strictEqual(util.isPrimitive([]), false); +assert.strictEqual(util.isPrimitive(/regexp/), false); +assert.strictEqual(util.isPrimitive(function() {}), false); +assert.strictEqual(util.isPrimitive(new Number(1)), false); +assert.strictEqual(util.isPrimitive(new String('bla')), false); +assert.strictEqual(util.isPrimitive(new Boolean(true)), false); +assert.strictEqual(util.isPrimitive(1), true); +assert.strictEqual(util.isPrimitive('bla'), true); +assert.strictEqual(util.isPrimitive(true), true); +assert.strictEqual(util.isPrimitive(undefined), true); +assert.strictEqual(util.isPrimitive(null), true); +assert.strictEqual(util.isPrimitive(Infinity), true); +assert.strictEqual(util.isPrimitive(NaN), true); +assert.strictEqual(util.isPrimitive(Symbol('symbol')), true); + +// isBuffer +assert.strictEqual(util.isBuffer('foo'), false); +assert.strictEqual(util.isBuffer(Buffer.from('foo')), true); + +// _extend +assert.deepStrictEqual(util._extend({ a: 1 }), { a: 1 }); +assert.deepStrictEqual(util._extend({ a: 1 }, []), { a: 1 }); +assert.deepStrictEqual(util._extend({ a: 1 }, null), { a: 1 }); +assert.deepStrictEqual(util._extend({ a: 1 }, true), { a: 1 }); +assert.deepStrictEqual(util._extend({ a: 1 }, false), { a: 1 }); +assert.deepStrictEqual(util._extend({ a: 1 }, { b: 2 }), { a: 1, b: 2 }); +assert.deepStrictEqual(util._extend({ a: 1, b: 2 }, { b: 3 }), { a: 1, b: 3 }); + +// deprecated +assert.strictEqual(util.isBoolean(true), true); +assert.strictEqual(util.isBoolean(false), true); +assert.strictEqual(util.isBoolean('string'), false); + +assert.strictEqual(util.isNull(null), true); +assert.strictEqual(util.isNull(undefined), false); +assert.strictEqual(util.isNull(), false); +assert.strictEqual(util.isNull('string'), false); + +assert.strictEqual(util.isUndefined(undefined), true); +assert.strictEqual(util.isUndefined(), true); +assert.strictEqual(util.isUndefined(null), false); +assert.strictEqual(util.isUndefined('string'), false); + +assert.strictEqual(util.isNullOrUndefined(null), true); +assert.strictEqual(util.isNullOrUndefined(undefined), true); +assert.strictEqual(util.isNullOrUndefined(), true); +assert.strictEqual(util.isNullOrUndefined('string'), false); + +assert.strictEqual(util.isNumber(42), true); +assert.strictEqual(util.isNumber(), false); +assert.strictEqual(util.isNumber('string'), false); + +assert.strictEqual(util.isString('string'), true); +assert.strictEqual(util.isString(), false); +assert.strictEqual(util.isString(42), false); + +assert.strictEqual(util.isSymbol(Symbol()), true); +assert.strictEqual(util.isSymbol(), false); +assert.strictEqual(util.isSymbol('string'), false); + +assert.strictEqual(util.isFunction(() => {}), true); +assert.strictEqual(util.isFunction(function() {}), true); +assert.strictEqual(util.isFunction(), false); +assert.strictEqual(util.isFunction('string'), false); + +// TODO(wafuwafu13): Enable this when `toUSVString` is ready. +// assert.strictEqual(util.toUSVString('string\ud801'), 'string\ufffd'); + +{ + assert.strictEqual(util.types.isNativeError(new Error()), true); + assert.strictEqual(util.types.isNativeError(new TypeError()), true); + assert.strictEqual(util.types.isNativeError(new SyntaxError()), true); + // TODO(wafuwafu13): Enable this when "vm" is ready. + // assert.strictEqual(util.types.isNativeError(new (context('Error'))()), true); + // assert.strictEqual( + // util.types.isNativeError(new (context('TypeError'))()), + // true + // ); + // assert.strictEqual( + // util.types.isNativeError(new (context('SyntaxError'))()), + // true + // ); + assert.strictEqual(util.types.isNativeError({}), false); + assert.strictEqual( + util.types.isNativeError({ name: 'Error', message: '' }), + false + ); + assert.strictEqual(util.types.isNativeError([]), false); + assert.strictEqual( + util.types.isNativeError(Object.create(Error.prototype)), + false + ); + assert.strictEqual( + util.types.isNativeError(new errors.codes.ERR_IPC_CHANNEL_CLOSED()), + true + ); +} + +assert.throws(() => { + util.stripVTControlCharacters({}); +}, { + code: 'ERR_INVALID_ARG_TYPE', + message: 'The "str" argument must be of type string.' + + common.invalidArgTypeHelper({}) +}); diff --git a/cli/tests/node_compat/test/parallel/test-vm-static-this.js b/cli/tests/node_compat/test/parallel/test-vm-static-this.js new file mode 100644 index 00000000000000..c2511bc217ba4d --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-vm-static-this.js @@ -0,0 +1,72 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. + +/* eslint-disable strict */ +const common = require('../common'); +const assert = require('assert'); +const vm = require('vm'); + +// Run a string +const result = vm.runInThisContext('\'passed\';'); +assert.strictEqual(result, 'passed'); + +// thrown error +assert.throws(function() { + vm.runInThisContext('throw new Error(\'test\');'); +}, /test/); + +global.hello = 5; +vm.runInThisContext('hello = 2'); +assert.strictEqual(global.hello, 2); + + +// pass values +const code = 'foo = 1;' + + 'bar = 2;' + + 'if (typeof baz !== \'undefined\')' + + 'throw new Error(\'test fail\');'; +global.foo = 2; +global.obj = { foo: 0, baz: 3 }; +/* eslint-disable no-unused-vars */ +const baz = vm.runInThisContext(code); +/* eslint-enable no-unused-vars */ +assert.strictEqual(global.obj.foo, 0); +assert.strictEqual(global.bar, 2); +assert.strictEqual(global.foo, 1); + +// call a function +global.f = function() { global.foo = 100; }; +vm.runInThisContext('f()'); +assert.strictEqual(global.foo, 100); + +common.allowGlobals( + global.hello, + global.foo, + global.obj, + global.f +); diff --git a/cli/tests/node_compat/test/parallel/test-webcrypto-sign-verify.js b/cli/tests/node_compat/test/parallel/test-webcrypto-sign-verify.js new file mode 100644 index 00000000000000..23df883ee31a6d --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-webcrypto-sign-verify.js @@ -0,0 +1,154 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 16.13.0 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +'use strict'; + +const common = require('../common'); + +if (!common.hasCrypto) + common.skip('missing crypto'); + +const assert = require('assert'); +const { subtle } = require('crypto').webcrypto; + +// This is only a partial test. The WebCrypto Web Platform Tests +// will provide much greater coverage. + +// Test Sign/Verify RSASSA-PKCS1-v1_5 +{ + async function test(data) { + const ec = new TextEncoder(); + const { publicKey, privateKey } = await subtle.generateKey({ + name: 'RSASSA-PKCS1-v1_5', + modulusLength: 1024, + publicExponent: new Uint8Array([1, 0, 1]), + hash: 'SHA-256' + }, true, ['sign', 'verify']); + + const signature = await subtle.sign({ + name: 'RSASSA-PKCS1-v1_5' + }, privateKey, ec.encode(data)); + + assert(await subtle.verify({ + name: 'RSASSA-PKCS1-v1_5' + }, publicKey, signature, ec.encode(data))); + } + + test('hello world').then(common.mustCall()); +} + +// Test Sign/Verify RSA-PSS +{ + async function test(data) { + const ec = new TextEncoder(); + const { publicKey, privateKey } = await subtle.generateKey({ + name: 'RSA-PSS', + modulusLength: 4096, + publicExponent: new Uint8Array([1, 0, 1]), + hash: 'SHA-256' + }, true, ['sign', 'verify']); + + const signature = await subtle.sign({ + name: 'RSA-PSS', + saltLength: 256, + }, privateKey, ec.encode(data)); + + assert(await subtle.verify({ + name: 'RSA-PSS', + saltLength: 256, + }, publicKey, signature, ec.encode(data))); + } + + test('hello world').then(common.mustCall()); +} + +// Test Sign/Verify ECDSA +{ + async function test(data) { + const ec = new TextEncoder(); + const { publicKey, privateKey } = await subtle.generateKey({ + name: 'ECDSA', + namedCurve: 'P-384', + }, true, ['sign', 'verify']); + + const signature = await subtle.sign({ + name: 'ECDSA', + hash: 'SHA-384', + }, privateKey, ec.encode(data)); + + assert(await subtle.verify({ + name: 'ECDSA', + hash: 'SHA-384', + }, publicKey, signature, ec.encode(data))); + } + + test('hello world').then(common.mustCall()); +} + +// Test Sign/Verify HMAC +{ + async function test(data) { + const ec = new TextEncoder(); + + const key = await subtle.generateKey({ + name: 'HMAC', + length: 256, + hash: 'SHA-256' + }, true, ['sign', 'verify']); + + const signature = await subtle.sign({ + name: 'HMAC', + }, key, ec.encode(data)); + + assert(await subtle.verify({ + name: 'HMAC', + }, key, signature, ec.encode(data))); + } + + test('hello world').then(common.mustCall()); +} + +// Test Sign/Verify Ed25519 +{ + async function test(data) { + const ec = new TextEncoder(); + const { publicKey, privateKey } = await subtle.generateKey({ + name: 'Ed25519', + }, true, ['sign', 'verify']); + + const signature = await subtle.sign({ + name: 'Ed25519', + }, privateKey, ec.encode(data)); + + assert(await subtle.verify({ + name: 'Ed25519', + }, publicKey, signature, ec.encode(data))); + } + + test('hello world').then(common.mustCall()); +} + +// Test Sign/Verify Ed448 +// TODO(cjihrig): Pending support in Deno core. +// { +// async function test(data) { +// const ec = new TextEncoder(); +// const { publicKey, privateKey } = await subtle.generateKey({ +// name: 'Ed448', +// }, true, ['sign', 'verify']); + +// const signature = await subtle.sign({ +// name: 'Ed448', +// }, privateKey, ec.encode(data)); + +// assert(await subtle.verify({ +// name: 'Ed448', +// }, publicKey, signature, ec.encode(data))); +// } + +// test('hello world').then(common.mustCall()); +// } diff --git a/cli/tests/node_compat/test/parallel/test-whatwg-encoding-custom-api-basics.js b/cli/tests/node_compat/test/parallel/test-whatwg-encoding-custom-api-basics.js new file mode 100644 index 00000000000000..5d404285b647cb --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-whatwg-encoding-custom-api-basics.js @@ -0,0 +1,68 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +'use strict'; + +// From: https://github.com/w3c/web-platform-tests/blob/master/encoding/api-basics.html +// This is the part that can be run without ICU + +require('../common'); + +const assert = require('assert'); + +function testDecodeSample(encoding, string, bytes) { + assert.strictEqual( + new TextDecoder(encoding).decode(new Uint8Array(bytes)), + string); + assert.strictEqual( + new TextDecoder(encoding).decode(new Uint8Array(bytes).buffer), + string); +} + +// `z` (ASCII U+007A), cent (Latin-1 U+00A2), CJK water (BMP U+6C34), +// G-Clef (non-BMP U+1D11E), PUA (BMP U+F8FF), PUA (non-BMP U+10FFFD) +// byte-swapped BOM (non-character U+FFFE) +const sample = 'z\xA2\u6C34\uD834\uDD1E\uF8FF\uDBFF\uDFFD\uFFFE'; + +{ + const encoding = 'utf-8'; + const string = sample; + const bytes = [ + 0x7A, 0xC2, 0xA2, 0xE6, 0xB0, 0xB4, + 0xF0, 0x9D, 0x84, 0x9E, 0xEF, 0xA3, + 0xBF, 0xF4, 0x8F, 0xBF, 0xBD, 0xEF, + 0xBF, 0xBE, + ]; + const encoded = new TextEncoder().encode(string); + assert.deepStrictEqual([].slice.call(encoded), bytes); + assert.strictEqual( + new TextDecoder(encoding).decode(new Uint8Array(bytes)), + string); + assert.strictEqual( + new TextDecoder(encoding).decode(new Uint8Array(bytes).buffer), + string); +} + +testDecodeSample( + 'utf-16le', + sample, + [ + 0x7A, 0x00, 0xA2, 0x00, 0x34, 0x6C, + 0x34, 0xD8, 0x1E, 0xDD, 0xFF, 0xF8, + 0xFF, 0xDB, 0xFD, 0xDF, 0xFE, 0xFF, + ] +); + +testDecodeSample( + 'utf-16', + sample, + [ + 0x7A, 0x00, 0xA2, 0x00, 0x34, 0x6C, + 0x34, 0xD8, 0x1E, 0xDD, 0xFF, 0xF8, + 0xFF, 0xDB, 0xFD, 0xDF, 0xFE, 0xFF, + ] +); diff --git a/cli/tests/node_compat/test/parallel/test-whatwg-encoding-custom-fatal-streaming.js b/cli/tests/node_compat/test/parallel/test-whatwg-encoding-custom-fatal-streaming.js new file mode 100644 index 00000000000000..b3ad5f05859f65 --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-whatwg-encoding-custom-fatal-streaming.js @@ -0,0 +1,68 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +'use strict'; + +// From: https://github.com/w3c/web-platform-tests/blob/d74324b53c/encoding/textdecoder-fatal-streaming.html +// With the twist that we specifically test for Node.js error codes + +const common = require('../common'); +const assert = require('assert'); + +if (!common.hasIntl) + common.skip('missing Intl'); + +{ + [ + { encoding: 'utf-8', sequence: [0xC0] }, + { encoding: 'utf-16le', sequence: [0x00] }, + { encoding: 'utf-16be', sequence: [0x00] }, + ].forEach((testCase) => { + const data = new Uint8Array([testCase.sequence]); + assert.throws( + () => { + const decoder = new TextDecoder(testCase.encoding, { fatal: true }); + decoder.decode(data); + }, { + code: 'ERR_ENCODING_INVALID_ENCODED_DATA', + name: 'TypeError', + message: + `The encoded data was not valid for encoding ${testCase.encoding}` + } + ); + }); +} + +{ + const decoder = new TextDecoder('utf-16le', { fatal: true }); + const odd = new Uint8Array([0x00]); + const even = new Uint8Array([0x00, 0x00]); + + assert.throws( + () => { + decoder.decode(even, { stream: true }); + decoder.decode(odd); + }, { + code: 'ERR_ENCODING_INVALID_ENCODED_DATA', + name: 'TypeError', + message: + 'The encoded data was not valid for encoding utf-16le' + } + ); + + assert.throws( + () => { + decoder.decode(odd, { stream: true }); + decoder.decode(even); + }, { + code: 'ERR_ENCODING_INVALID_ENCODED_DATA', + name: 'TypeError', + message: + 'The encoded data was not valid for encoding utf-16le' + } + ); +} diff --git a/cli/tests/node_compat/test/parallel/test-whatwg-encoding-custom-textdecoder-fatal.js b/cli/tests/node_compat/test/parallel/test-whatwg-encoding-custom-textdecoder-fatal.js new file mode 100644 index 00000000000000..3a8aac4003b380 --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-whatwg-encoding-custom-textdecoder-fatal.js @@ -0,0 +1,91 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +'use strict'; + +// From: https://github.com/w3c/web-platform-tests/blob/39a67e2fff/encoding/textdecoder-fatal.html +// With the twist that we specifically test for Node.js error codes + +const common = require('../common'); + +if (!common.hasIntl) + common.skip('missing Intl'); + +const assert = require('assert'); + +const bad = [ + { encoding: 'utf-8', input: [0xFF], name: 'invalid code' }, + { encoding: 'utf-8', input: [0xC0], name: 'ends early' }, + { encoding: 'utf-8', input: [0xE0], name: 'ends early 2' }, + { encoding: 'utf-8', input: [0xC0, 0x00], name: 'invalid trail' }, + { encoding: 'utf-8', input: [0xC0, 0xC0], name: 'invalid trail 2' }, + { encoding: 'utf-8', input: [0xE0, 0x00], name: 'invalid trail 3' }, + { encoding: 'utf-8', input: [0xE0, 0xC0], name: 'invalid trail 4' }, + { encoding: 'utf-8', input: [0xE0, 0x80, 0x00], name: 'invalid trail 5' }, + { encoding: 'utf-8', input: [0xE0, 0x80, 0xC0], name: 'invalid trail 6' }, + { encoding: 'utf-8', input: [0xFC, 0x80, 0x80, 0x80, 0x80, 0x80], + name: '> 0x10FFFF' }, + { encoding: 'utf-8', input: [0xFE, 0x80, 0x80, 0x80, 0x80, 0x80], + name: 'obsolete lead byte' }, + // Overlong encodings + { encoding: 'utf-8', input: [0xC0, 0x80], name: 'overlong U+0000 - 2 bytes' }, + { encoding: 'utf-8', input: [0xE0, 0x80, 0x80], + name: 'overlong U+0000 - 3 bytes' }, + { encoding: 'utf-8', input: [0xF0, 0x80, 0x80, 0x80], + name: 'overlong U+0000 - 4 bytes' }, + { encoding: 'utf-8', input: [0xF8, 0x80, 0x80, 0x80, 0x80], + name: 'overlong U+0000 - 5 bytes' }, + { encoding: 'utf-8', input: [0xFC, 0x80, 0x80, 0x80, 0x80, 0x80], + name: 'overlong U+0000 - 6 bytes' }, + { encoding: 'utf-8', input: [0xC1, 0xBF], name: 'overlong U+007F - 2 bytes' }, + { encoding: 'utf-8', input: [0xE0, 0x81, 0xBF], + name: 'overlong U+007F - 3 bytes' }, + { encoding: 'utf-8', input: [0xF0, 0x80, 0x81, 0xBF], + name: 'overlong U+007F - 4 bytes' }, + { encoding: 'utf-8', input: [0xF8, 0x80, 0x80, 0x81, 0xBF], + name: 'overlong U+007F - 5 bytes' }, + { encoding: 'utf-8', input: [0xFC, 0x80, 0x80, 0x80, 0x81, 0xBF], + name: 'overlong U+007F - 6 bytes' }, + { encoding: 'utf-8', input: [0xE0, 0x9F, 0xBF], + name: 'overlong U+07FF - 3 bytes' }, + { encoding: 'utf-8', input: [0xF0, 0x80, 0x9F, 0xBF], + name: 'overlong U+07FF - 4 bytes' }, + { encoding: 'utf-8', input: [0xF8, 0x80, 0x80, 0x9F, 0xBF], + name: 'overlong U+07FF - 5 bytes' }, + { encoding: 'utf-8', input: [0xFC, 0x80, 0x80, 0x80, 0x9F, 0xBF], + name: 'overlong U+07FF - 6 bytes' }, + { encoding: 'utf-8', input: [0xF0, 0x8F, 0xBF, 0xBF], + name: 'overlong U+FFFF - 4 bytes' }, + { encoding: 'utf-8', input: [0xF8, 0x80, 0x8F, 0xBF, 0xBF], + name: 'overlong U+FFFF - 5 bytes' }, + { encoding: 'utf-8', input: [0xFC, 0x80, 0x80, 0x8F, 0xBF, 0xBF], + name: 'overlong U+FFFF - 6 bytes' }, + { encoding: 'utf-8', input: [0xF8, 0x84, 0x8F, 0xBF, 0xBF], + name: 'overlong U+10FFFF - 5 bytes' }, + { encoding: 'utf-8', input: [0xFC, 0x80, 0x84, 0x8F, 0xBF, 0xBF], + name: 'overlong U+10FFFF - 6 bytes' }, + // UTF-16 surrogates encoded as code points in UTF-8 + { encoding: 'utf-8', input: [0xED, 0xA0, 0x80], name: 'lead surrogate' }, + { encoding: 'utf-8', input: [0xED, 0xB0, 0x80], name: 'trail surrogate' }, + { encoding: 'utf-8', input: [0xED, 0xA0, 0x80, 0xED, 0xB0, 0x80], + name: 'surrogate pair' }, + { encoding: 'utf-16le', input: [0x00], name: 'truncated code unit' }, + // Mismatched UTF-16 surrogates are exercised in utf16-surrogates.html + // FIXME: Add legacy encoding cases +]; + +bad.forEach((t) => { + assert.throws( + () => { + new TextDecoder(t.encoding, { fatal: true }) + .decode(new Uint8Array(t.input)); + }, { + code: 'ERR_ENCODING_INVALID_ENCODED_DATA', + name: 'TypeError' + } + ); +}); diff --git a/cli/tests/node_compat/test/parallel/test-whatwg-encoding-custom-textdecoder-ignorebom.js b/cli/tests/node_compat/test/parallel/test-whatwg-encoding-custom-textdecoder-ignorebom.js new file mode 100644 index 00000000000000..73469bad530048 --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-whatwg-encoding-custom-textdecoder-ignorebom.js @@ -0,0 +1,37 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +'use strict'; + +// From: https://github.com/w3c/web-platform-tests/blob/7f567fa29c/encoding/textdecoder-ignorebom.html +// This is the part that can be run without ICU + +require('../common'); + +const assert = require('assert'); + +const cases = [ + { + encoding: 'utf-8', + bytes: [0xEF, 0xBB, 0xBF, 0x61, 0x62, 0x63] + }, + { + encoding: 'utf-16le', + bytes: [0xFF, 0xFE, 0x61, 0x00, 0x62, 0x00, 0x63, 0x00] + }, +]; + +cases.forEach((testCase) => { + const BOM = '\uFEFF'; + let decoder = new TextDecoder(testCase.encoding, { ignoreBOM: true }); + const bytes = new Uint8Array(testCase.bytes); + assert.strictEqual(decoder.decode(bytes), `${BOM}abc`); + decoder = new TextDecoder(testCase.encoding, { ignoreBOM: false }); + assert.strictEqual(decoder.decode(bytes), 'abc'); + decoder = new TextDecoder(testCase.encoding); + assert.strictEqual(decoder.decode(bytes), 'abc'); +}); diff --git a/cli/tests/node_compat/test/parallel/test-whatwg-encoding-custom-textdecoder-streaming.js b/cli/tests/node_compat/test/parallel/test-whatwg-encoding-custom-textdecoder-streaming.js new file mode 100644 index 00000000000000..5fc7ece1142db0 --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-whatwg-encoding-custom-textdecoder-streaming.js @@ -0,0 +1,45 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +'use strict'; + +// From: https://github.com/w3c/web-platform-tests/blob/fa9436d12c/encoding/textdecoder-streaming.html +// This is the part that can be run without ICU + +require('../common'); + +const assert = require('assert'); + +const string = + '\x00123ABCabc\x80\xFF\u0100\u1000\uFFFD\uD800\uDC00\uDBFF\uDFFF'; +const octets = { + 'utf-8': [ + 0x00, 0x31, 0x32, 0x33, 0x41, 0x42, 0x43, 0x61, 0x62, 0x63, 0xc2, 0x80, + 0xc3, 0xbf, 0xc4, 0x80, 0xe1, 0x80, 0x80, 0xef, 0xbf, 0xbd, 0xf0, 0x90, + 0x80, 0x80, 0xf4, 0x8f, 0xbf, 0xbf], + 'utf-16le': [ + 0x00, 0x00, 0x31, 0x00, 0x32, 0x00, 0x33, 0x00, 0x41, 0x00, 0x42, 0x00, + 0x43, 0x00, 0x61, 0x00, 0x62, 0x00, 0x63, 0x00, 0x80, 0x00, 0xFF, 0x00, + 0x00, 0x01, 0x00, 0x10, 0xFD, 0xFF, 0x00, 0xD8, 0x00, 0xDC, 0xFF, 0xDB, + 0xFF, 0xDF] +}; + +Object.keys(octets).forEach((encoding) => { + for (let len = 1; len <= 5; ++len) { + const encoded = octets[encoding]; + const decoder = new TextDecoder(encoding); + let out = ''; + for (let i = 0; i < encoded.length; i += len) { + const sub = []; + for (let j = i; j < encoded.length && j < i + len; ++j) + sub.push(encoded[j]); + out += decoder.decode(new Uint8Array(sub), { stream: true }); + } + out += decoder.decode(); + assert.strictEqual(out, string); + } +}); diff --git a/cli/tests/node_compat/test/parallel/test-whatwg-encoding-custom-textdecoder-utf16-surrogates.js b/cli/tests/node_compat/test/parallel/test-whatwg-encoding-custom-textdecoder-utf16-surrogates.js new file mode 100644 index 00000000000000..afe542dfd97929 --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-whatwg-encoding-custom-textdecoder-utf16-surrogates.js @@ -0,0 +1,63 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +'use strict'; + +// From: https://github.com/w3c/web-platform-tests/blob/39a67e2fff/encoding/textdecoder-utf16-surrogates.html +// With the twist that we specifically test for Node.js error codes + +const common = require('../common'); + +if (!common.hasIntl) + common.skip('missing Intl'); + +const assert = require('assert'); + +const bad = [ + { + encoding: 'utf-16le', + input: [0x00, 0xd8], + expected: '\uFFFD', + name: 'lone surrogate lead' + }, + { + encoding: 'utf-16le', + input: [0x00, 0xdc], + expected: '\uFFFD', + name: 'lone surrogate trail' + }, + { + encoding: 'utf-16le', + input: [0x00, 0xd8, 0x00, 0x00], + expected: '\uFFFD\u0000', + name: 'unmatched surrogate lead' + }, + { + encoding: 'utf-16le', + input: [0x00, 0xdc, 0x00, 0x00], + expected: '\uFFFD\u0000', + name: 'unmatched surrogate trail' + }, + { + encoding: 'utf-16le', + input: [0x00, 0xdc, 0x00, 0xd8], + expected: '\uFFFD\uFFFD', + name: 'swapped surrogate pair' + }, +]; + +bad.forEach((t) => { + assert.throws( + () => { + new TextDecoder(t.encoding, { fatal: true }) + .decode(new Uint8Array(t.input)); + }, { + code: 'ERR_ENCODING_INVALID_ENCODED_DATA', + name: 'TypeError' + } + ); +}); diff --git a/cli/tests/node_compat/test/parallel/test-whatwg-events-add-event-listener-options-passive.js b/cli/tests/node_compat/test/parallel/test-whatwg-events-add-event-listener-options-passive.js new file mode 100644 index 00000000000000..b6330e8e106313 --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-whatwg-events-add-event-listener-options-passive.js @@ -0,0 +1,72 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +'use strict'; + +const common = require('../common'); + +// Manually converted from https://github.com/web-platform-tests/wpt/blob/master/dom/events/AddEventListenerOptions-passive.html +// in order to define the `document` ourselves + +const { + fail, + ok, + strictEqual +} = require('assert'); + +{ + const document = new EventTarget(); + let supportsPassive = false; + const query_options = { + get passive() { + supportsPassive = true; + return false; + }, + get dummy() { + fail('dummy value getter invoked'); + return false; + } + }; + + document.addEventListener('test_event', null, query_options); + ok(supportsPassive); + + supportsPassive = false; + document.removeEventListener('test_event', null, query_options); + strictEqual(supportsPassive, false); +} +{ + function testPassiveValue(optionsValue, expectedDefaultPrevented) { + const document = new EventTarget(); + let defaultPrevented; + function handler(e) { + if (e.defaultPrevented) { + fail('Event prematurely marked defaultPrevented'); + } + e.preventDefault(); + defaultPrevented = e.defaultPrevented; + } + document.addEventListener('test', handler, optionsValue); + // TODO the WHATWG test is more extensive here and tests dispatching on + // document.body, if we ever support getParent we should amend this + const ev = new Event('test', { bubbles: true, cancelable: true }); + const uncanceled = document.dispatchEvent(ev); + + strictEqual(defaultPrevented, expectedDefaultPrevented); + strictEqual(uncanceled, !expectedDefaultPrevented); + + document.removeEventListener('test', handler, optionsValue); + } + testPassiveValue(undefined, true); + testPassiveValue({}, true); + testPassiveValue({ passive: false }, true); + + common.skip('TODO: passive listeners is still broken'); + testPassiveValue({ passive: 1 }, false); + testPassiveValue({ passive: true }, false); + testPassiveValue({ passive: 0 }, true); +} diff --git a/cli/tests/node_compat/test/parallel/test-whatwg-events-add-event-listener-options-signal.js b/cli/tests/node_compat/test/parallel/test-whatwg-events-add-event-listener-options-signal.js new file mode 100644 index 00000000000000..a512facb57d837 --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-whatwg-events-add-event-listener-options-signal.js @@ -0,0 +1,166 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +'use strict'; + +require('../common'); + +const { + strictEqual, +} = require('assert'); + +// Manually ported from: wpt@dom/events/AddEventListenerOptions-signal.any.js + +{ + // Passing an AbortSignal to addEventListener does not prevent + // removeEventListener + let count = 0; + function handler() { + count++; + } + const et = new EventTarget(); + const controller = new AbortController(); + et.addEventListener('test', handler, { signal: controller.signal }); + et.dispatchEvent(new Event('test')); + strictEqual(count, 1, 'Adding a signal still adds a listener'); + et.dispatchEvent(new Event('test')); + strictEqual(count, 2, 'The listener was not added with the once flag'); + controller.abort(); + et.dispatchEvent(new Event('test')); + strictEqual(count, 2, 'Aborting on the controller removes the listener'); + // See: https://github.com/nodejs/node/pull/37696 , adding an event listener + // should always return undefined. + strictEqual( + et.addEventListener('test', handler, { signal: controller.signal }), + undefined); + et.dispatchEvent(new Event('test')); + strictEqual(count, 2, 'Passing an aborted signal never adds the handler'); +} + +{ + // Passing an AbortSignal to addEventListener works with the once flag + let count = 0; + function handler() { + count++; + } + const et = new EventTarget(); + const controller = new AbortController(); + et.addEventListener('test', handler, { signal: controller.signal }); + et.removeEventListener('test', handler); + et.dispatchEvent(new Event('test')); + strictEqual(count, 0, 'The listener was still removed'); +} + +{ + // Removing a once listener works with a passed signal + let count = 0; + function handler() { + count++; + } + const et = new EventTarget(); + const controller = new AbortController(); + const options = { signal: controller.signal, once: true }; + et.addEventListener('test', handler, options); + controller.abort(); + et.dispatchEvent(new Event('test')); + strictEqual(count, 0, 'The listener was still removed'); +} + +{ + let count = 0; + function handler() { + count++; + } + const et = new EventTarget(); + const controller = new AbortController(); + const options = { signal: controller.signal, once: true }; + et.addEventListener('test', handler, options); + et.removeEventListener('test', handler); + et.dispatchEvent(new Event('test')); + strictEqual(count, 0, 'The listener was still removed'); +} + +{ + // Passing an AbortSignal to multiple listeners + let count = 0; + function handler() { + count++; + } + const et = new EventTarget(); + const controller = new AbortController(); + const options = { signal: controller.signal, once: true }; + et.addEventListener('first', handler, options); + et.addEventListener('second', handler, options); + controller.abort(); + et.dispatchEvent(new Event('first')); + et.dispatchEvent(new Event('second')); + strictEqual(count, 0, 'The listener was still removed'); +} + +{ + // Passing an AbortSignal to addEventListener works with the capture flag + let count = 0; + function handler() { + count++; + } + const et = new EventTarget(); + const controller = new AbortController(); + const options = { signal: controller.signal, capture: true }; + et.addEventListener('test', handler, options); + controller.abort(); + et.dispatchEvent(new Event('test')); + strictEqual(count, 0, 'The listener was still removed'); +} + +{ + // Aborting from a listener does not call future listeners + let count = 0; + function handler() { + count++; + } + const et = new EventTarget(); + const controller = new AbortController(); + const options = { signal: controller.signal }; + et.addEventListener('test', () => { + controller.abort(); + }, options); + et.addEventListener('test', handler, options); + et.dispatchEvent(new Event('test')); + strictEqual(count, 0, 'The listener was still removed'); +} + +{ + // Adding then aborting a listener in another listener does not call it + let count = 0; + function handler() { + count++; + } + const et = new EventTarget(); + const controller = new AbortController(); + et.addEventListener('test', () => { + et.addEventListener('test', handler, { signal: controller.signal }); + controller.abort(); + }, { signal: controller.signal }); + et.dispatchEvent(new Event('test')); + strictEqual(count, 0, 'The listener was still removed'); +} + +{ + // Aborting from a nested listener should remove it + const et = new EventTarget(); + const ac = new AbortController(); + let count = 0; + et.addEventListener('foo', () => { + et.addEventListener('foo', () => { + count++; + if (count > 5) ac.abort(); + et.dispatchEvent(new Event('foo')); + }, { signal: ac.signal }); + et.dispatchEvent(new Event('foo')); + }, { once: true }); + et.dispatchEvent(new Event('foo')); +} diff --git a/cli/tests/node_compat/test/parallel/test-whatwg-events-customevent.js b/cli/tests/node_compat/test/parallel/test-whatwg-events-customevent.js new file mode 100644 index 00000000000000..9c6fbca6250bb5 --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-whatwg-events-customevent.js @@ -0,0 +1,40 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +'use strict'; + +const common = require('../common'); + +const { strictEqual, throws, equal } = require('assert'); + +// Manually converted from https://github.com/web-platform-tests/wpt/blob/master/dom/events/CustomEvent.html +// in order to define the `document` ourselves + +{ + const type = 'foo'; + const target = new EventTarget(); + + target.addEventListener(type, common.mustCall((evt) => { + strictEqual(evt.type, type); + })); + + target.dispatchEvent(new Event(type)); +} + +{ + throws(() => { + new Event(); + }, TypeError); +} + +{ + const event = new Event('foo'); + equal(event.type, 'foo'); + equal(event.bubbles, false); + equal(event.cancelable, false); + equal(event.detail, null); +} diff --git a/cli/tests/node_compat/test/parallel/test-whatwg-url-custom-deepequal.js b/cli/tests/node_compat/test/parallel/test-whatwg-url-custom-deepequal.js new file mode 100644 index 00000000000000..f851699a8d2569 --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-whatwg-url-custom-deepequal.js @@ -0,0 +1,25 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +'use strict'; +// This tests that the internal flags in URL objects are consistent, as manifest +// through assert libraries. +// See https://github.com/nodejs/node/issues/24211 + +// Tests below are not from WPT. + +require('../common'); +const assert = require('assert'); + +assert.deepStrictEqual( + new URL('./foo', 'https://example.com/'), + new URL('https://example.com/foo') +); +assert.deepStrictEqual( + new URL('./foo', 'https://user:pass@example.com/'), + new URL('https://user:pass@example.com/foo') +); diff --git a/cli/tests/node_compat/test/parallel/test-whatwg-url-custom-domainto.js b/cli/tests/node_compat/test/parallel/test-whatwg-url-custom-domainto.js new file mode 100644 index 00000000000000..225f8a05c9fd0e --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-whatwg-url-custom-domainto.js @@ -0,0 +1,64 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +'use strict'; + +// Tests below are not from WPT. + +const common = require('../common'); + +if (!common.hasIntl) + common.skip('missing Intl'); + +const assert = require('assert'); +const { domainToASCII, domainToUnicode } = require('url'); + +const tests = require('../fixtures/url-idna'); +const fixtures = require('../common/fixtures'); +const wptToASCIITests = require( + fixtures.path('wpt', 'url', 'resources', 'toascii.json') +); + +{ + const expectedError = { code: 'ERR_MISSING_ARGS', name: 'TypeError' }; + assert.throws(() => domainToASCII(), expectedError); + assert.throws(() => domainToUnicode(), expectedError); + assert.strictEqual(domainToASCII(undefined), 'undefined'); + assert.strictEqual(domainToUnicode(undefined), 'undefined'); +} + +{ + for (const [i, { ascii, unicode }] of tests.entries()) { + assert.strictEqual(ascii, domainToASCII(unicode), + `domainToASCII(${i + 1})`); + assert.strictEqual(unicode, domainToUnicode(ascii), + `domainToUnicode(${i + 1})`); + assert.strictEqual(ascii, domainToASCII(domainToUnicode(ascii)), + `domainToASCII(domainToUnicode(${i + 1}))`); + assert.strictEqual(unicode, domainToUnicode(domainToASCII(unicode)), + `domainToUnicode(domainToASCII(${i + 1}))`); + } +} + +{ + for (const [i, test] of wptToASCIITests.entries()) { + if (typeof test === 'string') + continue; // skip comments + const { comment, input, output } = test; + let caseComment = `Case ${i + 1}`; + if (comment) + caseComment += ` (${comment})`; + if (output === null) { + assert.strictEqual(domainToASCII(input), '', caseComment); + assert.strictEqual(domainToUnicode(input), '', caseComment); + } else { + assert.strictEqual(domainToASCII(input), output, caseComment); + const roundtripped = domainToASCII(domainToUnicode(input)); + assert.strictEqual(roundtripped, output, caseComment); + } + } +} diff --git a/cli/tests/node_compat/test/parallel/test-whatwg-url-custom-global.js b/cli/tests/node_compat/test/parallel/test-whatwg-url-custom-global.js new file mode 100644 index 00000000000000..0a6a8e40399960 --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-whatwg-url-custom-global.js @@ -0,0 +1,33 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +'use strict'; + +// Tests below are not from WPT. + +require('../common'); +const assert = require('assert'); + +assert.deepStrictEqual( + Object.getOwnPropertyDescriptor(global, 'URL'), + { + value: URL, + writable: true, + configurable: true, + enumerable: false + } +); + +assert.deepStrictEqual( + Object.getOwnPropertyDescriptor(global, 'URLSearchParams'), + { + value: URLSearchParams, + writable: true, + configurable: true, + enumerable: false + } +); diff --git a/cli/tests/node_compat/test/parallel/test-whatwg-url-custom-href-side-effect.js b/cli/tests/node_compat/test/parallel/test-whatwg-url-custom-href-side-effect.js new file mode 100644 index 00000000000000..12c3ef002e500d --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-whatwg-url-custom-href-side-effect.js @@ -0,0 +1,22 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +'use strict'; + +// Tests below are not from WPT. +require('../common'); +const assert = require('assert'); + +const ref = new URL('http://example.com/path'); +const url = new URL('http://example.com/path'); +assert.throws(() => { + url.href = ''; +}, { + name: 'TypeError' +}); + +assert.deepStrictEqual(url, ref); diff --git a/cli/tests/node_compat/test/parallel/test-whatwg-url-custom-inspect.js b/cli/tests/node_compat/test/parallel/test-whatwg-url-custom-inspect.js new file mode 100644 index 00000000000000..7a92d5ea3e57c4 --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-whatwg-url-custom-inspect.js @@ -0,0 +1,75 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +'use strict'; + +// Tests below are not from WPT. + +const common = require('../common'); +if (!common.hasIntl) { + // A handful of the tests fail when ICU is not included. + common.skip('missing Intl'); +} + +const util = require('util'); +const assert = require('assert'); + +const url = new URL('https://username:password@host.name:8080/path/name/?que=ry#hash'); + +assert.strictEqual( + util.inspect(url), + `URL { + href: 'https://username:password@host.name:8080/path/name/?que=ry#hash', + origin: 'https://host.name:8080', + protocol: 'https:', + username: 'username', + password: 'password', + host: 'host.name:8080', + hostname: 'host.name', + port: '8080', + pathname: '/path/name/', + search: '?que=ry', + searchParams: URLSearchParams { 'que' => 'ry' }, + hash: '#hash' +}`); + +assert.strictEqual( + util.inspect(url, { showHidden: true }), + `URL { + href: 'https://username:password@host.name:8080/path/name/?que=ry#hash', + origin: 'https://host.name:8080', + protocol: 'https:', + username: 'username', + password: 'password', + host: 'host.name:8080', + hostname: 'host.name', + port: '8080', + pathname: '/path/name/', + search: '?que=ry', + searchParams: URLSearchParams { 'que' => 'ry' }, + hash: '#hash', + cannotBeBase: false, + special: true, + [Symbol(context)]: URLContext { + flags: 2032, + scheme: 'https:', + username: 'username', + password: 'password', + host: 'host.name', + port: 8080, + path: [ 'path', 'name', '', [length]: 3 ], + query: 'que=ry', + fragment: 'hash' + } +}`); + +assert.strictEqual( + util.inspect({ a: url }, { depth: 0 }), + '{ a: [URL] }'); + +class MyURL extends URL {} +assert(util.inspect(new MyURL(url.href)).startsWith('MyURL {')); diff --git a/cli/tests/node_compat/test/parallel/test-whatwg-url-custom-parsing.js b/cli/tests/node_compat/test/parallel/test-whatwg-url-custom-parsing.js new file mode 100644 index 00000000000000..7af0759566b611 --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-whatwg-url-custom-parsing.js @@ -0,0 +1,87 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +'use strict'; + +// Tests below are not from WPT. + +const common = require('../common'); +if (!common.hasIntl) { + // A handful of the tests fail when ICU is not included. + common.skip('missing Intl'); +} + +const assert = require('assert'); +const fixtures = require('../common/fixtures'); + +const tests = require( + fixtures.path('wpt', 'url', 'resources', 'urltestdata.json') +); + +const originalFailures = tests.filter((test) => test.failure); + +const typeFailures = [ + { input: '' }, + { input: 'test' }, + { input: undefined }, + { input: 0 }, + { input: true }, + { input: false }, + { input: null }, + { input: new Date() }, + { input: new RegExp() }, + { input: 'test', base: null }, + { input: 'http://nodejs.org', base: null }, + { input: () => {} }, +]; + +// See https://github.com/w3c/web-platform-tests/pull/10955 +// > If `failure` is true, parsing `about:blank` against `base` +// > must give failure. This tests that the logic for converting +// > base URLs into strings properly fails the whole parsing +// > algorithm if the base URL cannot be parsed. +const aboutBlankFailures = originalFailures + .map((test) => ({ + input: 'about:blank', + base: test.input, + failure: true + })); + +const failureTests = originalFailures + .concat(typeFailures) + .concat(aboutBlankFailures); + +const expectedError = { code: 'ERR_INVALID_URL', name: 'TypeError' }; + +for (const test of failureTests) { + assert.throws( + () => new URL(test.input, test.base), + (error) => { + assert.throws(() => { throw error; }, expectedError); + assert.strictEqual(`${error}`, 'TypeError [ERR_INVALID_URL]: Invalid URL'); + assert.strictEqual(error.message, 'Invalid URL'); + return true; + }); +} + +const additional_tests = + require(fixtures.path('url-tests-additional.js')); + +for (const test of additional_tests) { + const url = new URL(test.url); + if (test.href) assert.strictEqual(url.href, test.href); + if (test.origin) assert.strictEqual(url.origin, test.origin); + if (test.protocol) assert.strictEqual(url.protocol, test.protocol); + if (test.username) assert.strictEqual(url.username, test.username); + if (test.password) assert.strictEqual(url.password, test.password); + if (test.hostname) assert.strictEqual(url.hostname, test.hostname); + if (test.host) assert.strictEqual(url.host, test.host); + if (test.port !== undefined) assert.strictEqual(url.port, test.port); + if (test.pathname) assert.strictEqual(url.pathname, test.pathname); + if (test.search) assert.strictEqual(url.search, test.search); + if (test.hash) assert.strictEqual(url.hash, test.hash); +} diff --git a/cli/tests/node_compat/test/parallel/test-whatwg-url-custom-setters.js b/cli/tests/node_compat/test/parallel/test-whatwg-url-custom-setters.js new file mode 100644 index 00000000000000..c0b4e41bdc3ee5 --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-whatwg-url-custom-setters.js @@ -0,0 +1,67 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +'use strict'; + +// Tests below are not from WPT. + +const common = require('../common'); +if (!common.hasIntl) { + // A handful of the tests fail when ICU is not included. + common.skip('missing Intl'); +} + +const assert = require('assert'); +const { test, assert_equals } = require('../common/wpt').harness; +const fixtures = require('../common/fixtures'); + +// TODO(joyeecheung): we should submit these to the upstream +const additionalTestCases = + require(fixtures.path('url-setter-tests-additional.js')); + +{ + for (const attributeToBeSet in additionalTestCases) { + if (attributeToBeSet === 'comment') { + continue; + } + const testCases = additionalTestCases[attributeToBeSet]; + for (const testCase of testCases) { + let name = `Setting <${testCase.href}>.${attributeToBeSet}` + + ` = "${testCase.new_value}"`; + if ('comment' in testCase) { + name += ` ${testCase.comment}`; + } + test(function() { + const url = new URL(testCase.href); + url[attributeToBeSet] = testCase.new_value; + for (const attribute in testCase.expected) { + assert_equals(url[attribute], testCase.expected[attribute]); + } + }, `URL: ${name}`); + } + } +} + +{ + const url = new URL('http://example.com/'); + const obj = { + toString() { throw new Error('toString'); }, + valueOf() { throw new Error('valueOf'); } + }; + const sym = Symbol(); + const props = Object.getOwnPropertyDescriptors(Object.getPrototypeOf(url)); + for (const [name, { set }] of Object.entries(props)) { + if (set) { + assert.throws(() => url[name] = obj, + /^Error: toString$/, + `url.${name} = { toString() { throw ... } }`); + assert.throws(() => url[name] = sym, + /^TypeError: Cannot convert a Symbol value to a string$/, + `url.${name} = ${String(sym)}`); + } + } +} diff --git a/cli/tests/node_compat/test/parallel/test-whatwg-url-custom-tostringtag.js b/cli/tests/node_compat/test/parallel/test-whatwg-url-custom-tostringtag.js new file mode 100644 index 00000000000000..7455add352c9f5 --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-whatwg-url-custom-tostringtag.js @@ -0,0 +1,39 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +'use strict'; + +// Tests below are not from WPT. + +require('../common'); +const assert = require('assert'); + +const toString = Object.prototype.toString; + +const url = new URL('http://example.org'); +const sp = url.searchParams; +const spIterator = sp.entries(); + +const test = [ + [url, 'URL'], + [sp, 'URLSearchParams'], + [spIterator, 'URLSearchParams Iterator'], + // Web IDL spec says we have to return 'URLPrototype', but it is too + // expensive to implement; therefore, use Chrome's behavior for now, until + // spec is changed. + [Object.getPrototypeOf(url), 'URL'], + [Object.getPrototypeOf(sp), 'URLSearchParams'], + [Object.getPrototypeOf(spIterator), 'URLSearchParams Iterator'], +]; + +test.forEach(([obj, expected]) => { + assert.strictEqual(obj[Symbol.toStringTag], expected, + `${obj[Symbol.toStringTag]} !== ${expected}`); + const str = toString.call(obj); + assert.strictEqual(str, `[object ${expected}]`, + `${str} !== [object ${expected}]`); +}); diff --git a/cli/tests/node_compat/test/parallel/test-whatwg-url-override-hostname.js b/cli/tests/node_compat/test/parallel/test-whatwg-url-override-hostname.js new file mode 100644 index 00000000000000..b60ffba564c2f5 --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-whatwg-url-override-hostname.js @@ -0,0 +1,27 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +'use strict'; + +require('../common'); +const assert = require('assert'); + +{ + const url = new (class extends URL { get hostname() { return 'bar.com'; } })('http://foo.com/'); + assert.strictEqual(url.href, 'http://foo.com/'); + assert.strictEqual(url.toString(), 'http://foo.com/'); + assert.strictEqual(url.toJSON(), 'http://foo.com/'); + assert.strictEqual(url.hash, ''); + assert.strictEqual(url.host, 'foo.com'); + assert.strictEqual(url.hostname, 'bar.com'); + assert.strictEqual(url.origin, 'http://foo.com'); + assert.strictEqual(url.password, ''); + assert.strictEqual(url.protocol, 'http:'); + assert.strictEqual(url.username, ''); + assert.strictEqual(url.search, ''); + assert.strictEqual(url.searchParams.toString(), ''); +} diff --git a/cli/tests/node_compat/test/parallel/test-whatwg-url-properties.js b/cli/tests/node_compat/test/parallel/test-whatwg-url-properties.js new file mode 100644 index 00000000000000..8a4f4e57b86b75 --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-whatwg-url-properties.js @@ -0,0 +1,111 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 16.13.0 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +'use strict'; +require('../common'); +const assert = require('assert'); +const { URL, URLSearchParams } = require('url'); + +[ + { name: 'toString' }, + { name: 'toJSON' }, + // Deno's URL doesn't support nodejs custom inspect + // { name: Symbol.for('nodejs.util.inspect.custom') }, +].forEach(({ name }) => { + testMethod(URL.prototype, name); +}); + +[ + { name: 'href' }, + { name: 'protocol' }, + { name: 'username' }, + { name: 'password' }, + { name: 'host' }, + { name: 'hostname' }, + { name: 'port' }, + { name: 'pathname' }, + { name: 'search' }, + { name: 'hash' }, + { name: 'origin', readonly: true }, + { name: 'searchParams', readonly: true }, +].forEach(({ name, readonly = false }) => { + testAccessor(URL.prototype, name, readonly); +}); + +[ + { name: 'append' }, + { name: 'delete' }, + { name: 'get' }, + { name: 'getAll' }, + { name: 'has' }, + { name: 'set' }, + { name: 'sort' }, + { name: 'entries' }, + { name: 'forEach' }, + { name: 'keys' }, + { name: 'values' }, + { name: 'toString' }, + { name: Symbol.iterator, methodName: 'entries' }, + // Deno's URL doesn't support nodejs custom inspect + // { name: Symbol.for('nodejs.util.inspect.custom') }, +].forEach(({ name, methodName }) => { + testMethod(URLSearchParams.prototype, name, methodName); +}); + +function stringifyName(name) { + if (typeof name === 'symbol') { + const { description } = name; + if (description === undefined) { + return ''; + } + return `[${description}]`; + } + + return name; +} + +function testMethod(target, name, methodName = stringifyName(name)) { + const desc = Object.getOwnPropertyDescriptor(target, name); + assert.notStrictEqual(desc, undefined); + assert.strictEqual(desc.enumerable, typeof name === 'string'); + + const { value } = desc; + assert.strictEqual(typeof value, 'function'); + assert.strictEqual(value.name, methodName); + /* This can't pass with Deno's URL/URLSearchParams + assert.strictEqual( + Object.prototype.hasOwnProperty.call(value, 'prototype'), + false, + ); + */ +} + +function testAccessor(target, name, readonly = false) { + const desc = Object.getOwnPropertyDescriptor(target, name); + assert.notStrictEqual(desc, undefined); + assert.strictEqual(desc.enumerable, typeof name === 'string'); + + const methodName = stringifyName(name); + const { get, set } = desc; + assert.strictEqual(typeof get, 'function'); + assert.strictEqual(get.name, `get ${methodName}`); + assert.strictEqual( + Object.prototype.hasOwnProperty.call(get, 'prototype'), + false, + ); + + if (readonly) { + assert.strictEqual(set, undefined); + } else { + assert.strictEqual(typeof set, 'function'); + assert.strictEqual(set.name, `set ${methodName}`); + assert.strictEqual( + Object.prototype.hasOwnProperty.call(set, 'prototype'), + false, + ); + } +} diff --git a/cli/tests/node_compat/test/parallel/test-whatwg-url-toascii.js b/cli/tests/node_compat/test/parallel/test-whatwg-url-toascii.js new file mode 100644 index 00000000000000..82ac527f1f964a --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-whatwg-url-toascii.js @@ -0,0 +1,93 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +'use strict'; +const common = require('../common'); +if (!common.hasIntl) { + // A handful of the tests fail when ICU is not included. + common.skip('missing Intl'); +} + +const fixtures = require('../common/fixtures'); +const { test, assert_equals, assert_throws } = require('../common/wpt').harness; + +const request = { + response: require( + fixtures.path('wpt', 'url', 'resources', 'toascii.json') + ) +}; + +// The following tests are copied from WPT. Modifications to them should be +// upstreamed first. +// Refs: https://github.com/w3c/web-platform-tests/blob/4839a0a804/url/toascii.window.js +// License: http://www.w3.org/Consortium/Legal/2008/04-testsuite-copyright.html + +/* eslint-disable */ +// async_test(t => { +// const request = new XMLHttpRequest() +// request.open("GET", "toascii.json") +// request.send() +// request.responseType = "json" +// request.onload = t.step_func_done(() => { + runTests(request.response) +// }) +// }, "Loading data…") + +function makeURL(type, input) { + input = "https://" + input + "/x" + if(type === "url") { + return new URL(input) + } else { + const url = document.createElement(type) + url.href = input + return url + } +} + +function runTests(tests) { + for(var i = 0, l = tests.length; i < l; i++) { + let hostTest = tests[i] + if (typeof hostTest === "string") { + continue // skip comments + } + const typeName = { "url": "URL", "a": "", "area": "" } + // ;["url", "a", "area"].forEach((type) => { + ;["url"].forEach((type) => { + test(() => { + if(hostTest.output !== null) { + const url = makeURL("url", hostTest.input) + assert_equals(url.host, hostTest.output) + assert_equals(url.hostname, hostTest.output) + assert_equals(url.pathname, "/x") + assert_equals(url.href, "https://" + hostTest.output + "/x") + } else { + if(type === "url") { + assert_throws(new TypeError, () => makeURL("url", hostTest.input)) + } else { + const url = makeURL(type, hostTest.input) + assert_equals(url.host, "") + assert_equals(url.hostname, "") + assert_equals(url.pathname, "") + assert_equals(url.href, "https://" + hostTest.input + "/x") + } + } + }, hostTest.input + " (using " + typeName[type] + ")") + ;["host", "hostname"].forEach((val) => { + test(() => { + const url = makeURL(type, "x") + url[val] = hostTest.input + if(hostTest.output !== null) { + assert_equals(url[val], hostTest.output) + } else { + assert_equals(url[val], "x") + } + }, hostTest.input + " (using " + typeName[type] + "." + val + ")") + }) + }) + } +} +/* eslint-enable */ diff --git a/cli/tests/node_compat/test/parallel/test-zlib-close-after-error.js b/cli/tests/node_compat/test/parallel/test-zlib-close-after-error.js new file mode 100644 index 00000000000000..c4de28ae1d0862 --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-zlib-close-after-error.js @@ -0,0 +1,23 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +'use strict'; +// https://github.com/nodejs/node/issues/6034 + +const common = require('../common'); +const assert = require('assert'); +const zlib = require('zlib'); + +const decompress = zlib.createGunzip(15); + +decompress.on('error', common.mustCall((err) => { + assert.strictEqual(decompress._closed, true); + decompress.close(); +})); + +assert.strictEqual(decompress._closed, false); +decompress.write('something invalid'); diff --git a/cli/tests/node_compat/test/parallel/test-zlib-close-after-write.js b/cli/tests/node_compat/test/parallel/test-zlib-close-after-write.js new file mode 100644 index 00000000000000..26c07c266e7ed2 --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-zlib-close-after-write.js @@ -0,0 +1,37 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. + +'use strict'; +const common = require('../common'); +const zlib = require('zlib'); + +zlib.gzip('hello', common.mustCall((err, out) => { + const unzip = zlib.createGunzip(); + unzip.write(out); + unzip.close(common.mustCall()); +})); diff --git a/cli/tests/node_compat/test/parallel/test-zlib-convenience-methods.js b/cli/tests/node_compat/test/parallel/test-zlib-convenience-methods.js new file mode 100644 index 00000000000000..cf6694b1fc5909 --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-zlib-convenience-methods.js @@ -0,0 +1,144 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 16.13.0 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. + +'use strict'; +// Test convenience methods with and without options supplied + +const common = require('../common'); +const assert = require('assert'); +const zlib = require('zlib'); + +// Must be a multiple of 4 characters in total to test all ArrayBufferView +// types. +const expectStr = 'blah'.repeat(8); +const expectBuf = Buffer.from(expectStr); + +const opts = { + level: 9, + chunkSize: 1024, +}; + +const optsInfo = { + info: true +}; + +for (const [type, expect] of [ + ['string', expectStr], + ['Buffer', expectBuf], + // FIXME(bartlomieju): + // ...common.getBufferSources(expectBuf).map((obj) => + // [obj[Symbol.toStringTag], obj] + // ), +]) { + for (const method of [ + ['gzip', 'gunzip', 'Gzip', 'Gunzip'], + ['gzip', 'unzip', 'Gzip', 'Unzip'], + ['deflate', 'inflate', 'Deflate', 'Inflate'], + ['deflateRaw', 'inflateRaw', 'DeflateRaw', 'InflateRaw'], + // FIXME(bartlomieju): + // ['brotliCompress', 'brotliDecompress', + // 'BrotliCompress', 'BrotliDecompress'], + ]) { + zlib[method[0]](expect, opts, common.mustCall((err, result) => { + zlib[method[1]](result, opts, common.mustCall((err, result) => { + assert.strictEqual(result.toString(), expectStr, + `Should get original string after ${method[0]}/` + + `${method[1]} ${type} with options.`); + })); + })); + + zlib[method[0]](expect, common.mustCall((err, result) => { + zlib[method[1]](result, common.mustCall((err, result) => { + assert.strictEqual(result.toString(), expectStr, + `Should get original string after ${method[0]}/` + + `${method[1]} ${type} without options.`); + })); + })); + + // FIXME(bartlomieju): + // zlib[method[0]](expect, optsInfo, common.mustCall((err, result) => { + // assert.ok(result.engine instanceof zlib[method[2]], + // `Should get engine ${method[2]} after ${method[0]} ` + + // `${type} with info option.`); + + // const compressed = result.buffer; + // zlib[method[1]](compressed, optsInfo, common.mustCall((err, result) => { + // assert.strictEqual(result.buffer.toString(), expectStr, + // `Should get original string after ${method[0]}/` + + // `${method[1]} ${type} with info option.`); + // assert.ok(result.engine instanceof zlib[method[3]], + // `Should get engine ${method[3]} after ${method[0]} ` + + // `${type} with info option.`); + // })); + // })); + + { + const compressed = zlib[`${method[0]}Sync`](expect, opts); + const decompressed = zlib[`${method[1]}Sync`](compressed, opts); + assert.strictEqual(decompressed.toString(), expectStr, + `Should get original string after ${method[0]}Sync/` + + `${method[1]}Sync ${type} with options.`); + } + + + { + const compressed = zlib[`${method[0]}Sync`](expect); + const decompressed = zlib[`${method[1]}Sync`](compressed); + assert.strictEqual(decompressed.toString(), expectStr, + `Should get original string after ${method[0]}Sync/` + + `${method[1]}Sync ${type} without options.`); + } + + // FIXME(bartlomieju): + // { + // const compressed = zlib[`${method[0]}Sync`](expect, optsInfo); + // assert.ok(compressed.engine instanceof zlib[method[2]], + // `Should get engine ${method[2]} after ${method[0]} ` + + // `${type} with info option.`); + // const decompressed = zlib[`${method[1]}Sync`](compressed.buffer, + // optsInfo); + // assert.strictEqual(decompressed.buffer.toString(), expectStr, + // `Should get original string after ${method[0]}Sync/` + + // `${method[1]}Sync ${type} without options.`); + // assert.ok(decompressed.engine instanceof zlib[method[3]], + // `Should get engine ${method[3]} after ${method[0]} ` + + // `${type} with info option.`); + // } + } +} + +// FIXME(bartlomieju): +// assert.throws( +// () => zlib.gzip('abc'), +// { +// code: 'ERR_INVALID_ARG_TYPE', +// name: 'TypeError', +// message: 'The "callback" argument must be of type function. ' + +// 'Received undefined' +// } +// ); diff --git a/cli/tests/node_compat/test/parallel/test-zlib-deflate-raw-inherits.js b/cli/tests/node_compat/test/parallel/test-zlib-deflate-raw-inherits.js new file mode 100644 index 00000000000000..5af18a1b4ce749 --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-zlib-deflate-raw-inherits.js @@ -0,0 +1,34 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +'use strict'; + +require('../common'); +const { DeflateRaw } = require('zlib'); +const { Readable } = require('stream'); + +// Validates that zlib.DeflateRaw can be inherited +// with Object.setPrototypeOf + +function NotInitialized(options) { + DeflateRaw.call(this, options); + this.prop = true; +} +Object.setPrototypeOf(NotInitialized.prototype, DeflateRaw.prototype); +Object.setPrototypeOf(NotInitialized, DeflateRaw); + +const dest = new NotInitialized(); + +const read = new Readable({ + read() { + this.push(Buffer.from('a test string')); + this.push(null); + } +}); + +read.pipe(dest); +dest.resume(); diff --git a/cli/tests/node_compat/test/parallel/test-zlib-destroy-pipe.js b/cli/tests/node_compat/test/parallel/test-zlib-destroy-pipe.js new file mode 100644 index 00000000000000..d4fa85a924c3db --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-zlib-destroy-pipe.js @@ -0,0 +1,28 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +'use strict'; + +const common = require('../common'); +const zlib = require('zlib'); +const { Writable } = require('stream'); + +// Verify that the zlib transform does not error in case +// it is destroyed with data still in flight + +const ts = zlib.createGzip(); + +const ws = new Writable({ + write: common.mustCall((chunk, enc, cb) => { + setImmediate(cb); + ts.destroy(); + }) +}); + +const buf = Buffer.allocUnsafe(1024 * 1024 * 20); +ts.end(buf); +ts.pipe(ws); diff --git a/cli/tests/node_compat/test/parallel/test-zlib-empty-buffer.js b/cli/tests/node_compat/test/parallel/test-zlib-empty-buffer.js new file mode 100644 index 00000000000000..2281ba88e5574b --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-zlib-empty-buffer.js @@ -0,0 +1,35 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 16.13.0 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +'use strict'; +const common = require('../common'); +const zlib = require('zlib'); +const { inspect, promisify } = require('util'); +const assert = require('assert'); +const emptyBuffer = Buffer.alloc(0); + +(async function() { + for (const [ compress, decompress, method ] of [ + [ zlib.deflateRawSync, zlib.inflateRawSync, 'raw sync' ], + [ zlib.deflateSync, zlib.inflateSync, 'deflate sync' ], + [ zlib.gzipSync, zlib.gunzipSync, 'gzip sync' ], + // FIXME(bartlomieju): + // [ zlib.brotliCompressSync, zlib.brotliDecompressSync, 'br sync' ], + [ promisify(zlib.deflateRaw), promisify(zlib.inflateRaw), 'raw' ], + [ promisify(zlib.deflate), promisify(zlib.inflate), 'deflate' ], + [ promisify(zlib.gzip), promisify(zlib.gunzip), 'gzip' ], + // FIXME(bartlomieju): + // [ promisify(zlib.brotliCompress), promisify(zlib.brotliDecompress), 'br' ], + ]) { + const compressed = await compress(emptyBuffer); + const decompressed = await decompress(compressed); + assert.deepStrictEqual( + emptyBuffer, decompressed, + `Expected ${inspect(compressed)} to match ${inspect(decompressed)} ` + + `to match for ${method}`); + } +})().then(common.mustCall()); diff --git a/cli/tests/node_compat/test/parallel/test-zlib-from-string.js b/cli/tests/node_compat/test/parallel/test-zlib-from-string.js new file mode 100644 index 00000000000000..e1003f214f5929 --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-zlib-from-string.js @@ -0,0 +1,90 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. + +'use strict'; +// Test compressing and uncompressing a string with zlib + +const common = require('../common'); +const assert = require('assert'); +const zlib = require('zlib'); + +const inputString = 'ΩΩLorem ipsum dolor sit amet, consectetur adipiscing eli' + + 't. Morbi faucibus, purus at gravida dictum, libero arcu ' + + 'convallis lacus, in commodo libero metus eu nisi. Nullam' + + ' commodo, neque nec porta placerat, nisi est fermentum a' + + 'ugue, vitae gravida tellus sapien sit amet tellus. Aenea' + + 'n non diam orci. Proin quis elit turpis. Suspendisse non' + + ' diam ipsum. Suspendisse nec ullamcorper odio. Vestibulu' + + 'm arcu mi, sodales non suscipit id, ultrices ut massa. S' + + 'ed ac sem sit amet arcu malesuada fermentum. Nunc sed. '; +const expectedBase64Deflate = 'eJxdUUtOQzEMvMoc4OndgT0gJCT2buJWlpI4jePeqZfpmX' + + 'AKLRKbLOzx/HK73q6vOrhCunlF1qIDJhNUeW5I2ozT5OkD' + + 'lKWLJWkncJG5403HQXAkT3Jw29B9uIEmToMukglZ0vS6oc' + + 'iBh4JG8sV4oVLEUCitK2kxq1WzPnChHDzsaGKy491LofoA' + + 'bWh8do43oeuYhB5EPCjcLjzYJo48KrfQBvnJecNFJvHT1+' + + 'RSQsGoC7dn2t/xjhduTA1NWyQIZR0pbHwMDatnD+crPqKS' + + 'qGPHp1vnlsWM/07ubf7bheF7kqSj84Bm0R1fYTfaK8vqqq' + + 'fKBtNMhe3OZh6N95CTvMX5HJJi4xOVzCgUOIMSLH7wmeOH' + + 'aFE4RdpnGavKtrB5xzfO/Ll9'; +const expectedBase64Gzip = 'H4sIAAAAAAAAA11RS05DMQy8yhzg6d2BPSAkJPZu4laWkjiN4' + + '96pl+mZcAotEpss7PH8crverq86uEK6eUXWogMmE1R5bkjajN' + + 'Pk6QOUpYslaSdwkbnjTcdBcCRPcnDb0H24gSZOgy6SCVnS9Lq' + + 'hyIGHgkbyxXihUsRQKK0raTGrVbM+cKEcPOxoYrLj3Uuh+gBt' + + 'aHx2jjeh65iEHkQ8KNwuPNgmjjwqt9AG+cl5w0Um8dPX5FJCw' + + 'agLt2fa3/GOF25MDU1bJAhlHSlsfAwNq2cP5ys+opKoY8enW+' + + 'eWxYz/Tu5t/tuF4XuSpKPzgGbRHV9hN9ory+qqp8oG00yF7c5' + + 'mHo33kJO8xfkckmLjE5XMKBQ4gxIsfvCZ44doUThF2mcZq8q2' + + 'sHnHNzRtagj5AQAA'; + +zlib.deflate(inputString, common.mustCall((err, buffer) => { + zlib.inflate(buffer, common.mustCall((err, inflated) => { + assert.strictEqual(inflated.toString(), inputString); + })); +})); + +zlib.gzip(inputString, common.mustCall((err, buffer) => { + // Can't actually guarantee that we'll get exactly the same + // deflated bytes when we compress a string, since the header + // depends on stuff other than the input string itself. + // However, decrypting it should definitely yield the same + // result that we're expecting, and this should match what we get + // from inflating the known valid deflate data. + zlib.gunzip(buffer, common.mustCall((err, gunzipped) => { + assert.strictEqual(gunzipped.toString(), inputString); + })); +})); + +let buffer = Buffer.from(expectedBase64Deflate, 'base64'); +zlib.unzip(buffer, common.mustCall((err, buffer) => { + assert.strictEqual(buffer.toString(), inputString); +})); + +buffer = Buffer.from(expectedBase64Gzip, 'base64'); +zlib.unzip(buffer, common.mustCall((err, buffer) => { + assert.strictEqual(buffer.toString(), inputString); +})); diff --git a/cli/tests/node_compat/test/parallel/test-zlib-invalid-input.js b/cli/tests/node_compat/test/parallel/test-zlib-invalid-input.js new file mode 100644 index 00000000000000..d8ecae521cae2a --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-zlib-invalid-input.js @@ -0,0 +1,68 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 16.13.0 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. + +'use strict'; +// Test uncompressing invalid input + +const common = require('../common'); +const assert = require('assert'); +const zlib = require('zlib'); + +const nonStringInputs = [ + 1, + true, + { a: 1 }, + ['a'], +]; + +// zlib.Unzip classes need to get valid data, or else they'll throw. +const unzips = [ + zlib.Unzip(), + zlib.Gunzip(), + zlib.Inflate(), + zlib.InflateRaw(), + // FIXME(bartlomieju): + // zlib.BrotliDecompress(), +]; + +nonStringInputs.forEach(common.mustCall((input) => { + assert.throws(() => { + zlib.gunzip(input); + }, { + name: 'TypeError', + code: 'ERR_INVALID_ARG_TYPE' + }); +}, nonStringInputs.length)); + +unzips.forEach(common.mustCall((uz, i) => { + uz.on('error', common.mustCall()); + uz.on('end', common.mustNotCall); + + // This will trigger error event + uz.write('this is not valid compressed data.'); +}, unzips.length)); diff --git a/cli/tests/node_compat/test/parallel/test-zlib-no-stream.js b/cli/tests/node_compat/test/parallel/test-zlib-no-stream.js new file mode 100644 index 00000000000000..9e5a6b1a2ee1a5 --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-zlib-no-stream.js @@ -0,0 +1,21 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +/* eslint-disable node-core/required-modules */ +/* eslint-disable node-core/require-common-first */ + +'use strict'; + +// We are not loading common because it will load the stream module, +// defeating the purpose of this test. + +const { gzipSync } = require('zlib'); + +// Avoid regressions such as https://github.com/nodejs/node/issues/36615 + +// This must not throw +gzipSync('fooobar'); diff --git a/cli/tests/node_compat/test/parallel/test-zlib-random-byte-pipes.js b/cli/tests/node_compat/test/parallel/test-zlib-random-byte-pipes.js new file mode 100644 index 00000000000000..56409d4117766f --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-zlib-random-byte-pipes.js @@ -0,0 +1,166 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 16.13.0 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. + +'use strict'; +const common = require('../common'); +if (!common.hasCrypto) + common.skip('missing crypto'); + +const assert = require('assert'); +const crypto = require('crypto'); +const stream = require('stream'); +const zlib = require('zlib'); + +const Stream = stream.Stream; + +// Emit random bytes, and keep a shasum +class RandomReadStream extends Stream { + constructor(opt) { + super(); + + this.readable = true; + this._paused = false; + this._processing = false; + + this._hasher = crypto.createHash('sha1'); + opt = opt || {}; + + // base block size. + opt.block = opt.block || 256 * 1024; + + // Total number of bytes to emit + opt.total = opt.total || 256 * 1024 * 1024; + this._remaining = opt.total; + + // How variable to make the block sizes + opt.jitter = opt.jitter || 1024; + + this._opt = opt; + + this._process = this._process.bind(this); + + process.nextTick(this._process); + } + + pause() { + this._paused = true; + this.emit('pause'); + } + + resume() { + // console.error("rrs resume"); + this._paused = false; + this.emit('resume'); + this._process(); + } + + _process() { + if (this._processing) return; + if (this._paused) return; + + this._processing = true; + + if (!this._remaining) { + this._hash = this._hasher.digest('hex').toLowerCase().trim(); + this._processing = false; + + this.emit('end'); + return; + } + + // Figure out how many bytes to output + // if finished, then just emit end. + let block = this._opt.block; + const jitter = this._opt.jitter; + if (jitter) { + block += Math.ceil(Math.random() * jitter - (jitter / 2)); + } + block = Math.min(block, this._remaining); + const buf = Buffer.allocUnsafe(block); + for (let i = 0; i < block; i++) { + buf[i] = Math.random() * 256; + } + + this._hasher.update(buf); + + this._remaining -= block; + + this._processing = false; + + this.emit('data', buf); + process.nextTick(this._process); + } +} + +// A filter that just verifies a shasum +class HashStream extends Stream { + constructor() { + super(); + this.readable = this.writable = true; + this._hasher = crypto.createHash('sha1'); + } + + write(c) { + // Simulate the way that an fs.ReadStream returns false + // on *every* write, only to resume a moment later. + this._hasher.update(c); + process.nextTick(() => this.resume()); + return false; + } + + resume() { + this.emit('resume'); + process.nextTick(() => this.emit('drain')); + } + + end(c) { + if (c) { + this.write(c); + } + this._hash = this._hasher.digest('hex').toLowerCase().trim(); + this.emit('data', this._hash); + this.emit('end'); + } +} + +for (const [ createCompress, createDecompress ] of [ + [ zlib.createGzip, zlib.createGunzip ], + // TODO(kt3k): Enable this when we support brotli in zlib + // [ zlib.createBrotliCompress, zlib.createBrotliDecompress ], +]) { + const inp = new RandomReadStream({ total: 1024, block: 256, jitter: 16 }); + const out = new HashStream(); + const gzip = createCompress(); + const gunz = createDecompress(); + + inp.pipe(gzip).pipe(gunz).pipe(out); + + out.on('data', common.mustCall((c) => { + assert.strictEqual(c, inp._hash, `Hash '${c}' equals '${inp._hash}'.`); + })); +} diff --git a/cli/tests/node_compat/test/parallel/test-zlib-sync-no-event.js b/cli/tests/node_compat/test/parallel/test-zlib-sync-no-event.js new file mode 100644 index 00000000000000..eaf058a9ca09a3 --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-zlib-sync-no-event.js @@ -0,0 +1,26 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +'use strict'; +const common = require('../common'); +const zlib = require('zlib'); +const assert = require('assert'); + +const message = 'Come on, Fhqwhgads.'; +const buffer = Buffer.from(message); + +const zipper = new zlib.Gzip(); +zipper.on('close', common.mustNotCall()); + +const zipped = zipper._processChunk(buffer, zlib.constants.Z_FINISH); + +const unzipper = new zlib.Gunzip(); +unzipper.on('close', common.mustNotCall()); + +const unzipped = unzipper._processChunk(zipped, zlib.constants.Z_FINISH); +assert.notStrictEqual(zipped.toString(), message); +assert.strictEqual(unzipped.toString(), message); diff --git a/cli/tests/node_compat/test/parallel/test-zlib-truncated.js b/cli/tests/node_compat/test/parallel/test-zlib-truncated.js new file mode 100644 index 00000000000000..6d9f95b0c3a157 --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-zlib-truncated.js @@ -0,0 +1,71 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +'use strict'; +// Tests zlib streams with truncated compressed input + +require('../common'); +const assert = require('assert'); +const zlib = require('zlib'); + +const inputString = 'ΩΩLorem ipsum dolor sit amet, consectetur adipiscing eli' + + 't. Morbi faucibus, purus at gravida dictum, libero arcu ' + + 'convallis lacus, in commodo libero metus eu nisi. Nullam' + + ' commodo, neque nec porta placerat, nisi est fermentum a' + + 'ugue, vitae gravida tellus sapien sit amet tellus. Aenea' + + 'n non diam orci. Proin quis elit turpis. Suspendisse non' + + ' diam ipsum. Suspendisse nec ullamcorper odio. Vestibulu' + + 'm arcu mi, sodales non suscipit id, ultrices ut massa. S' + + 'ed ac sem sit amet arcu malesuada fermentum. Nunc sed. '; + +const errMessage = /unexpected end of file/; + +[ + { comp: 'gzip', decomp: 'gunzip', decompSync: 'gunzipSync' }, + { comp: 'gzip', decomp: 'unzip', decompSync: 'unzipSync' }, + { comp: 'deflate', decomp: 'inflate', decompSync: 'inflateSync' }, + { comp: 'deflateRaw', decomp: 'inflateRaw', decompSync: 'inflateRawSync' }, +].forEach(function(methods) { + zlib[methods.comp](inputString, function(err, compressed) { + assert.ifError(err); + const truncated = compressed.slice(0, compressed.length / 2); + const toUTF8 = (buffer) => buffer.toString('utf-8'); + + // sync sanity + const decompressed = zlib[methods.decompSync](compressed); + assert.strictEqual(toUTF8(decompressed), inputString); + + // async sanity + zlib[methods.decomp](compressed, function(err, result) { + assert.ifError(err); + assert.strictEqual(toUTF8(result), inputString); + }); + + // Sync truncated input test + assert.throws(function() { + zlib[methods.decompSync](truncated); + }, errMessage); + + // Async truncated input test + zlib[methods.decomp](truncated, function(err, result) { + assert.match(err.message, errMessage); + }); + + const syncFlushOpt = { finishFlush: zlib.constants.Z_SYNC_FLUSH }; + + // Sync truncated input test, finishFlush = Z_SYNC_FLUSH + const result = toUTF8(zlib[methods.decompSync](truncated, syncFlushOpt)); + assert.strictEqual(result, inputString.substr(0, result.length)); + + // Async truncated input test, finishFlush = Z_SYNC_FLUSH + zlib[methods.decomp](truncated, syncFlushOpt, function(err, decompressed) { + assert.ifError(err); + const result = toUTF8(decompressed); + assert.strictEqual(result, inputString.substr(0, result.length)); + }); + }); +}); diff --git a/cli/tests/node_compat/test/parallel/test-zlib-unzip-one-byte-chunks.js b/cli/tests/node_compat/test/parallel/test-zlib-unzip-one-byte-chunks.js new file mode 100644 index 00000000000000..bc66b6696b01b9 --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-zlib-unzip-one-byte-chunks.js @@ -0,0 +1,37 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +'use strict'; +const common = require('../common'); +const assert = require('assert'); +const zlib = require('zlib'); + +const data = Buffer.concat([ + zlib.gzipSync('abc'), + zlib.gzipSync('def'), +]); + +const resultBuffers = []; + +const unzip = zlib.createUnzip() + .on('error', (err) => { + assert.ifError(err); + }) + .on('data', (data) => resultBuffers.push(data)) + .on('finish', common.mustCall(() => { + const unzipped = Buffer.concat(resultBuffers).toString(); + assert.strictEqual(unzipped, 'abcdef', + `'${unzipped}' should match 'abcdef' after zipping ` + + 'and unzipping'); + })); + +for (let i = 0; i < data.length; i++) { + // Write each single byte individually. + unzip.write(Buffer.from([data[i]])); +} + +unzip.end(); diff --git a/cli/tests/node_compat/test/parallel/test-zlib-write-after-end.js b/cli/tests/node_compat/test/parallel/test-zlib-write-after-end.js new file mode 100644 index 00000000000000..8a9e9fbaa4e25b --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-zlib-write-after-end.js @@ -0,0 +1,23 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +'use strict'; +const common = require('../common'); +const zlib = require('zlib'); + +// Regression test for https://github.com/nodejs/node/issues/30976 +// Writes to a stream should finish even after the readable side has been ended. + +const data = zlib.deflateRawSync('Welcome'); + +const inflate = zlib.createInflateRaw(); + +inflate.resume(); +inflate.write(data, common.mustCall()); +inflate.write(Buffer.from([0x00]), common.mustCall()); +inflate.write(Buffer.from([0x00]), common.mustCall()); +inflate.flush(common.mustCall()); diff --git a/cli/tests/node_compat/test/parallel/test-zlib-write-after-flush.js b/cli/tests/node_compat/test/parallel/test-zlib-write-after-flush.js new file mode 100644 index 00000000000000..6f33668c75e466 --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-zlib-write-after-flush.js @@ -0,0 +1,57 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 16.13.0 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. + +'use strict'; +const common = require('../common'); +const assert = require('assert'); +const zlib = require('zlib'); + +for (const [ createCompress, createDecompress ] of [ + [ zlib.createGzip, zlib.createGunzip ], + // FIXME(bartlomieju): + // [ zlib.createBrotliCompress, zlib.createBrotliDecompress ], +]) { + const gzip = createCompress(); + const gunz = createDecompress(); + + gzip.pipe(gunz); + + let output = ''; + const input = 'A line of data\n'; + gunz.setEncoding('utf8'); + gunz.on('data', (c) => output += c); + gunz.on('end', common.mustCall(() => { + assert.strictEqual(output, input); + })); + + // Make sure that flush/write doesn't trigger an assert failure + gzip.flush(); + gzip.write(input); + gzip.end(); + gunz.read(0); +} diff --git a/cli/tests/node_compat/test/parallel/test-zlib-zero-byte.js b/cli/tests/node_compat/test/parallel/test-zlib-zero-byte.js new file mode 100644 index 00000000000000..fb12b22803524e --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-zlib-zero-byte.js @@ -0,0 +1,53 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 16.13.0 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. + +'use strict'; +const common = require('../common'); +const assert = require('assert'); +const zlib = require('zlib'); + +for (const Compressor of [ zlib.Gzip, + // FIXME(bartlomieju): + // zlib.BrotliCompress +]) { + const gz = Compressor(); + const emptyBuffer = Buffer.alloc(0); + let received = 0; + gz.on('data', function(c) { + received += c.length; + }); + + gz.on('end', common.mustCall(function() { + const expected = Compressor === zlib.Gzip ? 20 : 1; + assert.strictEqual(received, expected, + `${received}, ${expected}, ${Compressor.name}`); + })); + gz.on('finish', common.mustCall()); + gz.write(emptyBuffer); + gz.end(); +} diff --git a/cli/tests/node_compat/test/parallel/test-zlib-zero-windowBits.js b/cli/tests/node_compat/test/parallel/test-zlib-zero-windowBits.js new file mode 100644 index 00000000000000..fe74fe6d88b840 --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-zlib-zero-windowBits.js @@ -0,0 +1,41 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 16.13.0 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +'use strict'; + +require('../common'); +const assert = require('assert'); +const zlib = require('zlib'); + + +// windowBits is a special case in zlib. On the compression side, 0 is invalid. +// On the decompression side, it indicates that zlib should use the value from +// the header of the compressed stream. +{ + const inflate = zlib.createInflate({ windowBits: 0 }); + assert(inflate instanceof zlib.Inflate); +} + +{ + const gunzip = zlib.createGunzip({ windowBits: 0 }); + assert(gunzip instanceof zlib.Gunzip); +} + +{ + const unzip = zlib.createUnzip({ windowBits: 0 }); + assert(unzip instanceof zlib.Unzip); +} + +// FIXME(bartlomieju): +// { +// assert.throws(() => zlib.createGzip({ windowBits: 0 }), { +// code: 'ERR_OUT_OF_RANGE', +// name: 'RangeError', +// message: 'The value of "options.windowBits" is out of range. ' + +// 'It must be >= 9 and <= 15. Received 0' +// }); +// } diff --git a/cli/tests/node_compat/test/pseudo-tty/console-dumb-tty.js b/cli/tests/node_compat/test/pseudo-tty/console-dumb-tty.js new file mode 100644 index 00000000000000..8ce0268ea0e256 --- /dev/null +++ b/cli/tests/node_compat/test/pseudo-tty/console-dumb-tty.js @@ -0,0 +1,16 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +'use strict'; +require('../common'); + +process.env.TERM = 'dumb'; + +console.log({ foo: 'bar' }); +console.dir({ foo: 'bar' }); +console.log('%s q', 'string'); +console.log('%o with object format param', { foo: 'bar' }); diff --git a/cli/tests/node_compat/test/pseudo-tty/console_colors.js b/cli/tests/node_compat/test/pseudo-tty/console_colors.js new file mode 100644 index 00000000000000..bfb718dbbb64b0 --- /dev/null +++ b/cli/tests/node_compat/test/pseudo-tty/console_colors.js @@ -0,0 +1,29 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +'use strict'; +require('../common'); +const vm = require('vm'); +// Make this test OS-independent by overriding stdio getColorDepth(). +process.stdout.getColorDepth = () => 8; +process.stderr.getColorDepth = () => 8; + +console.log({ foo: 'bar' }); +console.log('%s q', 'string'); +console.log('%o with object format param', { foo: 'bar' }); + +console.log( + new Error('test\n at abc (../fixtures/node_modules/bar.js:4:4)\nfoobar') +); + +try { + require('../fixtures/node_modules/node_modules/bar.js'); +} catch (err) { + console.log(err); +} + +vm.runInThisContext('console.log(new Error())'); diff --git a/cli/tests/node_compat/test/pseudo-tty/no_dropped_stdio.js b/cli/tests/node_compat/test/pseudo-tty/no_dropped_stdio.js new file mode 100644 index 00000000000000..7772d8254b0119 --- /dev/null +++ b/cli/tests/node_compat/test/pseudo-tty/no_dropped_stdio.js @@ -0,0 +1,26 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +// https://github.com/nodejs/node/issues/6456#issuecomment-219320599 +// https://gist.github.com/isaacs/1495b91ec66b21d30b10572d72ad2cdd +'use strict'; +const common = require('../common'); + +// 1000 bytes wrapped at 50 columns +// \n turns into a double-byte character +// (48 + {2}) * 20 = 1000 +let out = `${'o'.repeat(48)}\n`.repeat(20); +// Add the remaining 24 bytes and terminate with an 'O'. +// This results in 1025 bytes, just enough to overflow the 1kb OS X TTY buffer. +out += `${'o'.repeat(24)}O`; + +// In AIX, the child exits even before the python parent +// can setup the readloop. Provide a reasonable delay. +setTimeout(function() { + process.stdout.write(out); + process.exit(0); +}, common.isAIX ? 200 : 0); diff --git a/cli/tests/node_compat/test/pseudo-tty/no_interleaved_stdio.js b/cli/tests/node_compat/test/pseudo-tty/no_interleaved_stdio.js new file mode 100644 index 00000000000000..fe5f046b6ae5f5 --- /dev/null +++ b/cli/tests/node_compat/test/pseudo-tty/no_interleaved_stdio.js @@ -0,0 +1,28 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +// https://github.com/nodejs/node/issues/6456#issuecomment-219320599 +// https://gist.github.com/isaacs/1495b91ec66b21d30b10572d72ad2cdd +'use strict'; +const common = require('../common'); + +// 1000 bytes wrapped at 50 columns +// \n turns into a double-byte character +// (48 + {2}) * 20 = 1000 +let out = `${'o'.repeat(48)}\n`.repeat(20); +// Add the remaining 24 bytes and terminate with an 'O'. +// This results in 1025 bytes, just enough to overflow the 1kb OS X TTY buffer. +out += `${'o'.repeat(24)}O`; + +const err = '__This is some stderr__'; + +// In AIX, the child exits even before the python parent +// can setup the readloop. Provide a reasonable delay. +setTimeout(function() { + process.stdout.write(out); + process.stderr.write(err); +}, common.isAIX ? 200 : 0); diff --git a/cli/tests/node_compat/test/pseudo-tty/package.json b/cli/tests/node_compat/test/pseudo-tty/package.json new file mode 100644 index 00000000000000..0967ef424bce67 --- /dev/null +++ b/cli/tests/node_compat/test/pseudo-tty/package.json @@ -0,0 +1 @@ +{} diff --git a/cli/tests/node_compat/test/pseudo-tty/test-tty-color-support-warning-2.js b/cli/tests/node_compat/test/pseudo-tty/test-tty-color-support-warning-2.js new file mode 100644 index 00000000000000..4f865e9ee4c857 --- /dev/null +++ b/cli/tests/node_compat/test/pseudo-tty/test-tty-color-support-warning-2.js @@ -0,0 +1,15 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +'use strict'; + +require('../common'); + +process.env.NODE_DISABLE_COLORS = '1'; +process.env.FORCE_COLOR = '3'; + +console.log(); diff --git a/cli/tests/node_compat/test/pseudo-tty/test-tty-color-support-warning.js b/cli/tests/node_compat/test/pseudo-tty/test-tty-color-support-warning.js new file mode 100644 index 00000000000000..effcdb2b10ab75 --- /dev/null +++ b/cli/tests/node_compat/test/pseudo-tty/test-tty-color-support-warning.js @@ -0,0 +1,16 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +'use strict'; + +require('../common'); + +process.env.NO_COLOR = '1'; +process.env.NODE_DISABLE_COLORS = '1'; +process.env.FORCE_COLOR = '3'; + +console.log(); diff --git a/cli/tests/node_compat/test/pseudo-tty/test-tty-stdin-end.js b/cli/tests/node_compat/test/pseudo-tty/test-tty-stdin-end.js new file mode 100644 index 00000000000000..2acf2f223fd0ab --- /dev/null +++ b/cli/tests/node_compat/test/pseudo-tty/test-tty-stdin-end.js @@ -0,0 +1,14 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +'use strict'; +require('../common'); + +// This test ensures that Node.js doesn't crash on `process.stdin.emit("end")`. +// https://github.com/nodejs/node/issues/1068 + +process.stdin.emit('end'); diff --git a/cli/tests/node_compat/test/pseudo-tty/test-tty-stdout-end.js b/cli/tests/node_compat/test/pseudo-tty/test-tty-stdout-end.js new file mode 100644 index 00000000000000..a75fc9a4486031 --- /dev/null +++ b/cli/tests/node_compat/test/pseudo-tty/test-tty-stdout-end.js @@ -0,0 +1,11 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +'use strict'; +require('../common'); + +process.stdout.end(); diff --git a/cli/tests/node_compat/test/pummel/package.json b/cli/tests/node_compat/test/pummel/package.json new file mode 100644 index 00000000000000..0967ef424bce67 --- /dev/null +++ b/cli/tests/node_compat/test/pummel/package.json @@ -0,0 +1 @@ +{} diff --git a/cli/tests/node_compat/test/pummel/test-net-bytes-per-incoming-chunk-overhead.js b/cli/tests/node_compat/test/pummel/test-net-bytes-per-incoming-chunk-overhead.js new file mode 100644 index 00000000000000..950fc74ec014d9 --- /dev/null +++ b/cli/tests/node_compat/test/pummel/test-net-bytes-per-incoming-chunk-overhead.js @@ -0,0 +1,58 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 16.13.0 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +// Flags: --expose-gc +'use strict'; + +const common = require('../common'); + +if (process.config.variables.asan) { + common.skip('ASAN messes with memory measurements'); +} + +if (process.config.variables.arm_version === '7') { + common.skip('Too slow for armv7 bots'); +} + +const assert = require('assert'); +const net = require('net'); + +// Tests that, when receiving small chunks, we do not keep the full length +// of the original allocation for the libuv read call in memory. + +let client; +let baseRSS; +const receivedChunks = []; +const N = 250000; + +const server = net.createServer(common.mustCall((socket) => { + baseRSS = process.memoryUsage.rss(); + + socket.setNoDelay(true); + socket.on('data', (chunk) => { + receivedChunks.push(chunk); + if (receivedChunks.length < N) { + client.write('a'); + } else { + client.end(); + server.close(); + } + }); +})).listen(0, common.mustCall(() => { + client = net.connect(server.address().port); + client.setNoDelay(true); + client.write('hello!'); +})); + +process.on('exit', () => { + // TODO: support global.gc() compat + // global.gc(); + const bytesPerChunk = + (process.memoryUsage.rss() - baseRSS) / receivedChunks.length; + // We should always have less than one page (usually ~ 4 kB) per chunk. + assert(bytesPerChunk < 650, `measured ${bytesPerChunk} bytes per chunk`); +}); diff --git a/cli/tests/node_compat/test/pummel/test-net-pingpong-delay.js b/cli/tests/node_compat/test/pummel/test-net-pingpong-delay.js new file mode 100644 index 00000000000000..50f509b8e40541 --- /dev/null +++ b/cli/tests/node_compat/test/pummel/test-net-pingpong-delay.js @@ -0,0 +1,114 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. + +'use strict'; +const common = require('../common'); +const assert = require('assert'); +const net = require('net'); + +function pingPongTest(host, on_complete) { + const N = 100; + const DELAY = 1; + let count = 0; + let client_ended = false; + + const server = net.createServer({ allowHalfOpen: true }, function(socket) { + socket.setEncoding('utf8'); + + socket.on('data', function(data) { + console.log(data); + assert.strictEqual(data, 'PING'); + assert.strictEqual(socket.readyState, 'open'); + assert.strictEqual(count <= N, true); + setTimeout(function() { + assert.strictEqual(socket.readyState, 'open'); + socket.write('PONG'); + }, DELAY); + }); + + socket.on('timeout', function() { + console.error('server-side timeout!!'); + assert.strictEqual(false, true); + }); + + socket.on('end', function() { + console.log('server-side socket EOF'); + assert.strictEqual(socket.readyState, 'writeOnly'); + socket.end(); + }); + + socket.on('close', function(had_error) { + console.log('server-side socket.end'); + assert.strictEqual(had_error, false); + assert.strictEqual(socket.readyState, 'closed'); + socket.server.close(); + }); + }); + + server.listen(0, host, common.mustCall(function() { + const client = net.createConnection(server.address().port, host); + + client.setEncoding('utf8'); + + client.on('connect', function() { + assert.strictEqual(client.readyState, 'open'); + client.write('PING'); + }); + + client.on('data', function(data) { + console.log(data); + assert.strictEqual(data, 'PONG'); + assert.strictEqual(client.readyState, 'open'); + + setTimeout(function() { + assert.strictEqual(client.readyState, 'open'); + if (count++ < N) { + client.write('PING'); + } else { + console.log('closing client'); + client.end(); + client_ended = true; + } + }, DELAY); + }); + + client.on('timeout', function() { + console.error('client-side timeout!!'); + assert.strictEqual(false, true); + }); + + client.on('close', common.mustCall(function() { + console.log('client.end'); + assert.strictEqual(count, N + 1); + assert.ok(client_ended); + if (on_complete) on_complete(); + })); + })); +} + +pingPongTest(); diff --git a/cli/tests/node_compat/test/pummel/test-net-write-callbacks.js b/cli/tests/node_compat/test/pummel/test-net-write-callbacks.js new file mode 100644 index 00000000000000..7f6528107705ea --- /dev/null +++ b/cli/tests/node_compat/test/pummel/test-net-write-callbacks.js @@ -0,0 +1,80 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 16.13.0 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. + +'use strict'; +require('../common'); +const net = require('net'); +const assert = require('assert'); + +let cbcount = 0; +const N = 500000; + +// TODO: support net.Server() without new + +const server = new net.Server(function(socket) { + socket.on('data', function(d) { + console.error(`got ${d.length} bytes`); + }); + + socket.on('end', function() { + console.error('end'); + socket.destroy(); + server.close(); + }); +}); + +let lastCalled = -1; +function makeCallback(c) { + let called = false; + return function() { + if (called) + throw new Error(`called callback #${c} more than once`); + called = true; + if (c < lastCalled) { + throw new Error( + `callbacks out of order. last=${lastCalled} current=${c}`); + } + lastCalled = c; + cbcount++; + }; +} + +server.listen(0, function() { + const client = net.createConnection(server.address().port); + + client.on('connect', function() { + for (let i = 0; i < N; i++) { + client.write('hello world', makeCallback(i)); + } + client.end(); + }); +}); + +process.on('exit', function() { + assert.strictEqual(cbcount, N); +}); diff --git a/cli/tests/node_compat/test/sequential/package.json b/cli/tests/node_compat/test/sequential/package.json new file mode 100644 index 00000000000000..0967ef424bce67 --- /dev/null +++ b/cli/tests/node_compat/test/sequential/package.json @@ -0,0 +1 @@ +{} diff --git a/cli/tests/node_compat/test/sequential/test-child-process-exit.js b/cli/tests/node_compat/test/sequential/test-child-process-exit.js new file mode 100644 index 00000000000000..c8930b05917e13 --- /dev/null +++ b/cli/tests/node_compat/test/sequential/test-child-process-exit.js @@ -0,0 +1,69 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. + +// TODO(PolarETech): The process.argv[3] to be assigned to gen should be argv[2], +// and the arguments array passed to spawn() should not need to include "require.ts". + +'use strict'; +require('../common'); + +// Open a chain of five Node processes each a child of the next. The final +// process exits immediately. Each process in the chain is instructed to exit +// when its child exits. +// https://github.com/joyent/node/issues/1726 + +const assert = require('assert'); +const ch = require('child_process'); + +const gen = +(process.argv[3] || 0); +const maxGen = 5; + + +if (gen === maxGen) { + console.error('hit maxGen, exiting', maxGen); + return; +} + +const child = ch.spawn(process.execPath, ['require.ts', __filename, gen + 1], { + stdio: [ 'ignore', 'pipe', 'ignore' ] +}); +assert.ok(!child.stdin); +assert.ok(child.stdout); +assert.ok(!child.stderr); + +console.error('gen=%d, pid=%d', gen, process.pid); + +child.on('exit', function(code) { + console.error('exit %d from gen %d', code, gen + 1); +}); + +child.stdout.pipe(process.stdout); + +child.stdout.on('close', function() { + console.error('child.stdout close gen=%d', gen); +}); diff --git a/cli/tests/testdata/.gitignore b/cli/tests/testdata/.gitignore new file mode 100644 index 00000000000000..3c3629e647f5dd --- /dev/null +++ b/cli/tests/testdata/.gitignore @@ -0,0 +1 @@ +node_modules diff --git a/cli/tests/testdata/bench/pass.json.out b/cli/tests/testdata/bench/pass.json.out new file mode 100644 index 00000000000000..73daa72028c94e --- /dev/null +++ b/cli/tests/testdata/bench/pass.json.out @@ -0,0 +1,28 @@ +Check file:///[WILDCARD]testdata/bench/pass.ts +{ + "runtime": "Deno/[WILDCARD]", + "cpu": "[WILDCARD]", + "benches": [ + { + "origin": "file:///[WILDCARD]testdata/bench/pass.ts", + "group": null, + "name": "bench0", + "baseline": false, + "results": [ + { + "ok": { + "n": [WILDCARD], + "min": [WILDCARD], + "max": [WILDCARD], + "avg": [WILDCARD], + "p75": [WILDCARD], + "p99": [WILDCARD], + "p995": [WILDCARD], + "p999": [WILDCARD] + } + } + ] + }, +[WILDCARD] + ] +} diff --git a/cli/tests/testdata/bundle/shebang_file.bundle.out b/cli/tests/testdata/bundle/shebang_file.bundle.out index df425f6566d266..978fe6d97d533b 100644 --- a/cli/tests/testdata/bundle/shebang_file.bundle.out +++ b/cli/tests/testdata/bundle/shebang_file.bundle.out @@ -1,3 +1,5 @@ +Warning "deno bundle" is deprecated and will be removed in the future. +Use alternative bundlers like "deno_emit", "esbuild" or "rollup" instead. Bundle file:///[WILDCARD]/subdir/shebang_file.js #!/usr/bin/env -S deno run --allow-read // deno-fmt-ignore-file diff --git a/cli/tests/testdata/cert/cafile_info.ts.out b/cli/tests/testdata/cert/cafile_info.ts.out index ddece30198f8ca..279453f88a21ae 100644 --- a/cli/tests/testdata/cert/cafile_info.ts.out +++ b/cli/tests/testdata/cert/cafile_info.ts.out @@ -4,11 +4,11 @@ dependencies: 8 unique size: [WILDCARD] https://localhost:5545/cert/cafile_info.ts ([WILDCARD]) -├── https://localhost:5545/subdir/mt_application_ecmascript.j2.js ([WILDCARD]) -├── https://localhost:5545/subdir/mt_application_x_javascript.j4.js ([WILDCARD]) -├── https://localhost:5545/subdir/mt_application_x_typescript.t4.ts ([WILDCARD]) -├── https://localhost:5545/subdir/mt_text_ecmascript.j3.js ([WILDCARD]) -├── https://localhost:5545/subdir/mt_text_javascript.j1.js ([WILDCARD]) ├── https://localhost:5545/subdir/mt_text_typescript.t1.ts ([WILDCARD]) +├── https://localhost:5545/subdir/mt_video_vdn.t2.ts ([WILDCARD]) ├── https://localhost:5545/subdir/mt_video_mp2t.t3.ts ([WILDCARD]) -└── https://localhost:5545/subdir/mt_video_vdn.t2.ts ([WILDCARD]) +├── https://localhost:5545/subdir/mt_application_x_typescript.t4.ts ([WILDCARD]) +├── https://localhost:5545/subdir/mt_text_javascript.j1.js ([WILDCARD]) +├── https://localhost:5545/subdir/mt_application_ecmascript.j2.js ([WILDCARD]) +├── https://localhost:5545/subdir/mt_text_ecmascript.j3.js ([WILDCARD]) +└── https://localhost:5545/subdir/mt_application_x_javascript.j4.js ([WILDCARD]) diff --git a/cli/tests/testdata/info/049_info_flag_script_jsx.out b/cli/tests/testdata/info/049_info_flag_script_jsx.out index 244541696d74a3..f49fc23566f1f4 100644 --- a/cli/tests/testdata/info/049_info_flag_script_jsx.out +++ b/cli/tests/testdata/info/049_info_flag_script_jsx.out @@ -5,11 +5,11 @@ dependencies: 8 unique size: [WILDCARD] http://127.0.0.1:4545/run/048_media_types_jsx.ts ([WILDCARD]) -├── http://localhost:4545/subdir/mt_application_ecmascript_jsx.j2.jsx ([WILDCARD]) -├── http://localhost:4545/subdir/mt_application_x_javascript_jsx.j4.jsx ([WILDCARD]) -├── http://localhost:4545/subdir/mt_application_x_typescript_tsx.t4.tsx ([WILDCARD]) -├── http://localhost:4545/subdir/mt_text_ecmascript_jsx.j3.jsx ([WILDCARD]) -├── http://localhost:4545/subdir/mt_text_javascript_jsx.j1.jsx ([WILDCARD]) ├── http://localhost:4545/subdir/mt_text_typescript_tsx.t1.tsx ([WILDCARD]) +├── http://localhost:4545/subdir/mt_video_vdn_tsx.t2.tsx ([WILDCARD]) ├── http://localhost:4545/subdir/mt_video_mp2t_tsx.t3.tsx ([WILDCARD]) -└── http://localhost:4545/subdir/mt_video_vdn_tsx.t2.tsx ([WILDCARD]) +├── http://localhost:4545/subdir/mt_application_x_typescript_tsx.t4.tsx ([WILDCARD]) +├── http://localhost:4545/subdir/mt_text_javascript_jsx.j1.jsx ([WILDCARD]) +├── http://localhost:4545/subdir/mt_application_ecmascript_jsx.j2.jsx ([WILDCARD]) +├── http://localhost:4545/subdir/mt_text_ecmascript_jsx.j3.jsx ([WILDCARD]) +└── http://localhost:4545/subdir/mt_application_x_javascript_jsx.j4.jsx ([WILDCARD]) diff --git a/cli/tests/testdata/info/076_info_json_deps_order.out b/cli/tests/testdata/info/076_info_json_deps_order.out index 98b5d5d5071db8..a1b15e00c09203 100644 --- a/cli/tests/testdata/info/076_info_json_deps_order.out +++ b/cli/tests/testdata/info/076_info_json_deps_order.out @@ -4,6 +4,7 @@ ], "modules": [ { + "kind": "esm", "dependencies": [ { "specifier": "./recursive_imports/A.ts", @@ -22,13 +23,13 @@ } } ], - "kind": "esm", "local": "[WILDCARD]076_info_json_deps_order.ts", [WILDCARD] "mediaType": "TypeScript", "specifier": "file://[WILDCARD]/076_info_json_deps_order.ts" }, { + "kind": "esm", "dependencies": [ { "specifier": "./B.ts", @@ -63,13 +64,13 @@ } } ], - "kind": "esm", "local": "[WILDCARD]A.ts", [WILDCARD] "mediaType": "TypeScript", "specifier": "file://[WILDCARD]/recursive_imports/A.ts" }, { + "kind": "esm", "dependencies": [ { "specifier": "./C.ts", @@ -104,13 +105,13 @@ } } ], - "kind": "esm", "local": "[WILDCARD]B.ts", [WILDCARD] "mediaType": "TypeScript", "specifier": "file://[WILDCARD]/recursive_imports/B.ts" }, { + "kind": "esm", "dependencies": [ { "specifier": "./A.ts", @@ -145,7 +146,6 @@ } } ], - "kind": "esm", "local": "[WILDCARD]C.ts", [WILDCARD] "mediaType": "TypeScript", diff --git a/cli/tests/testdata/info/json_output/main.out b/cli/tests/testdata/info/json_output/main.out index aaef028c00068a..5a89d5cab037bd 100644 --- a/cli/tests/testdata/info/json_output/main.out +++ b/cli/tests/testdata/info/json_output/main.out @@ -4,6 +4,7 @@ ], "modules": [ { + "kind": "esm", "dependencies": [ { "specifier": "../../subdir/mod1.ts", @@ -22,13 +23,13 @@ } } ], - "kind": "esm", "local": "[WILDCARD]main.ts", [WILDCARD] "mediaType": "TypeScript", "specifier": "file://[WILDCARD]/json_output/main.ts" }, { + "kind": "esm", "dependencies": [ { "specifier": "./subdir2/mod2.ts", @@ -47,7 +48,6 @@ } } ], - "kind": "esm", "local": "[WILDCARD]mod1.ts", [WILDCARD] "mediaType": "TypeScript", @@ -61,6 +61,7 @@ "specifier": "file://[WILDCARD]/subdir/print_hello.ts" }, { + "kind": "esm", "dependencies": [ { "specifier": "../print_hello.ts", @@ -79,7 +80,6 @@ } } ], - "kind": "esm", "local": "[WILDCARD]mod2.ts", [WILDCARD] "mediaType": "TypeScript", diff --git a/cli/tests/testdata/info/multiple_imports.out b/cli/tests/testdata/info/multiple_imports.out index ea35e69c8dcba1..cb13318cae40e9 100644 --- a/cli/tests/testdata/info/multiple_imports.out +++ b/cli/tests/testdata/info/multiple_imports.out @@ -5,11 +5,11 @@ dependencies: 8 unique size: [WILDCARD] http://127.0.0.1:4545/run/019_media_types.ts ([WILDCARD]) -├── http://localhost:4545/subdir/mt_application_ecmascript.j2.js ([WILDCARD]) -├── http://localhost:4545/subdir/mt_application_x_javascript.j4.js ([WILDCARD]) -├── http://localhost:4545/subdir/mt_application_x_typescript.t4.ts ([WILDCARD]) -├── http://localhost:4545/subdir/mt_text_ecmascript.j3.js ([WILDCARD]) -├── http://localhost:4545/subdir/mt_text_javascript.j1.js ([WILDCARD]) ├── http://localhost:4545/subdir/mt_text_typescript.t1.ts ([WILDCARD]) +├── http://localhost:4545/subdir/mt_video_vdn.t2.ts ([WILDCARD]) ├── http://localhost:4545/subdir/mt_video_mp2t.t3.ts ([WILDCARD]) -└── http://localhost:4545/subdir/mt_video_vdn.t2.ts ([WILDCARD]) +├── http://localhost:4545/subdir/mt_application_x_typescript.t4.ts ([WILDCARD]) +├── http://localhost:4545/subdir/mt_text_javascript.j1.js ([WILDCARD]) +├── http://localhost:4545/subdir/mt_application_ecmascript.j2.js ([WILDCARD]) +├── http://localhost:4545/subdir/mt_text_ecmascript.j3.js ([WILDCARD]) +└── http://localhost:4545/subdir/mt_application_x_javascript.j4.js ([WILDCARD]) diff --git a/cli/tests/testdata/lsp/completion_resolve_response.json b/cli/tests/testdata/lsp/completion_resolve_response.json index 034a4781f110be..28ad756a332e01 100644 --- a/cli/tests/testdata/lsp/completion_resolve_response.json +++ b/cli/tests/testdata/lsp/completion_resolve_response.json @@ -1,7 +1,7 @@ { "label": "build", "kind": 6, - "detail": "const Deno.build: {\n target: string;\n arch: \"x86_64\" | \"aarch64\";\n os: \"darwin\" | \"linux\" | \"windows\";\n vendor: string;\n env?: string | undefined;\n}", + "detail": "const Deno.build: {\n target: string;\n arch: \"x86_64\" | \"aarch64\";\n os: \"darwin\" | \"linux\" | \"windows\" | \"freebsd\" | \"netbsd\" | \"aix\" | \"solaris\" | \"illumos\";\n vendor: string;\n env?: string | undefined;\n}", "documentation": { "kind": "markdown", "value": "Information related to the build of the current Deno runtime.\n\nUsers are discouraged from code branching based on this information, as\nassumptions about what is available in what build environment might change\nover time. Developers should specifically sniff out the features they\nintend to use.\n\nThe intended use for the information is for logging and debugging purposes.\n\n*@category* - Runtime Environment" diff --git a/cli/tests/testdata/npm/cached_only/main.out b/cli/tests/testdata/npm/cached_only/main.out index e902bff4974777..d03420fee2464d 100644 --- a/cli/tests/testdata/npm/cached_only/main.out +++ b/cli/tests/testdata/npm/cached_only/main.out @@ -1,4 +1,2 @@ -error: Error getting response at http://localhost:4545/npm/registry/chalk - -Caused by: - An npm specifier not found in cache: "chalk", --cached-only is specified. +error: Error getting response at http://localhost:4545/npm/registry/chalk for package "chalk": An npm specifier not found in cache: "chalk", --cached-only is specified. + at file:///[WILDCARD]/testdata/npm/cached_only/main.ts:1:19 diff --git a/cli/tests/testdata/npm/cjs_with_deps/main.out b/cli/tests/testdata/npm/cjs_with_deps/main.out index 23c217f7a53a50..3a16ff46707f4f 100644 --- a/cli/tests/testdata/npm/cjs_with_deps/main.out +++ b/cli/tests/testdata/npm/cjs_with_deps/main.out @@ -1,5 +1,7 @@ -Download http://localhost:4545/npm/registry/chai Download http://localhost:4545/npm/registry/chalk +Download http://localhost:4545/npm/registry/chai +Download http://localhost:4545/npm/registry/ansi-styles +Download http://localhost:4545/npm/registry/supports-color Download http://localhost:4545/npm/registry/assertion-error Download http://localhost:4545/npm/registry/check-error Download http://localhost:4545/npm/registry/deep-eql @@ -7,8 +9,6 @@ Download http://localhost:4545/npm/registry/get-func-name Download http://localhost:4545/npm/registry/loupe Download http://localhost:4545/npm/registry/pathval Download http://localhost:4545/npm/registry/type-detect -Download http://localhost:4545/npm/registry/ansi-styles -Download http://localhost:4545/npm/registry/supports-color Download http://localhost:4545/npm/registry/color-convert Download http://localhost:4545/npm/registry/has-flag Download http://localhost:4545/npm/registry/color-name diff --git a/cli/tests/testdata/npm/cjs_with_deps/main_info.out b/cli/tests/testdata/npm/cjs_with_deps/main_info.out index 345583a9021048..cf84197e166b78 100644 --- a/cli/tests/testdata/npm/cjs_with_deps/main_info.out +++ b/cli/tests/testdata/npm/cjs_with_deps/main_info.out @@ -4,19 +4,19 @@ dependencies: 14 unique size: [WILDCARD] file:///[WILDCARD]/npm/cjs_with_deps/main.js ([WILDCARD]) -├─┬ npm:chai@4.3 - 4.3.6 ([WILDCARD]) -│ ├── npm:assertion-error@1.1.0 ([WILDCARD]) -│ ├── npm:check-error@1.0.2 ([WILDCARD]) -│ ├─┬ npm:deep-eql@3.0.1 ([WILDCARD]) -│ │ └── npm:type-detect@4.0.8 ([WILDCARD]) -│ ├── npm:get-func-name@2.0.0 ([WILDCARD]) -│ ├─┬ npm:loupe@2.3.4 ([WILDCARD]) -│ │ └── npm:get-func-name@2.0.0 ([WILDCARD]) -│ ├── npm:pathval@1.1.1 ([WILDCARD]) -│ └── npm:type-detect@4.0.8 ([WILDCARD]) -└─┬ npm:chalk@4 - 4.1.2 ([WILDCARD]) - ├─┬ npm:ansi-styles@4.3.0 ([WILDCARD]) - │ └─┬ npm:color-convert@2.0.1 ([WILDCARD]) - │ └── npm:color-name@1.1.4 ([WILDCARD]) - └─┬ npm:supports-color@7.2.0 ([WILDCARD]) - └── npm:has-flag@4.0.0 ([WILDCARD]) +├─┬ npm:chalk@4.1.2 ([WILDCARD]) +│ ├─┬ npm:ansi-styles@4.3.0 ([WILDCARD]) +│ │ └─┬ npm:color-convert@2.0.1 ([WILDCARD]) +│ │ └── npm:color-name@1.1.4 ([WILDCARD]) +│ └─┬ npm:supports-color@7.2.0 ([WILDCARD]) +│ └── npm:has-flag@4.0.0 ([WILDCARD]) +└─┬ npm:chai@4.3.6 ([WILDCARD]) + ├── npm:assertion-error@1.1.0 ([WILDCARD]) + ├── npm:check-error@1.0.2 ([WILDCARD]) + ├─┬ npm:deep-eql@3.0.1 ([WILDCARD]) + │ └── npm:type-detect@4.0.8 ([WILDCARD]) + ├── npm:get-func-name@2.0.0 ([WILDCARD]) + ├─┬ npm:loupe@2.3.4 ([WILDCARD]) + │ └── npm:get-func-name@2.0.0 ([WILDCARD]) + ├── npm:pathval@1.1.1 ([WILDCARD]) + └── npm:type-detect@4.0.8 ([WILDCARD]) diff --git a/cli/tests/testdata/npm/cjs_with_deps/main_info_json.out b/cli/tests/testdata/npm/cjs_with_deps/main_info_json.out index bc7b9e162207b4..e2a659a42dca81 100644 --- a/cli/tests/testdata/npm/cjs_with_deps/main_info_json.out +++ b/cli/tests/testdata/npm/cjs_with_deps/main_info_json.out @@ -4,43 +4,43 @@ ], "modules": [ { + "kind": "esm", "dependencies": [ { - "specifier": "npm:chai@4.3", + "specifier": "npm:chalk@4", "code": { - "specifier": "npm:chai@4.3", + "specifier": "npm:chalk@4", "span": { "start": { - "line": 1, - "character": 23 + "line": 0, + "character": 18 }, "end": { - "line": 1, - "character": 37 + "line": 0, + "character": 31 } } }, - "npmPackage": "chai@4.3.6" + "npmPackage": "chalk@4.1.2" }, { - "specifier": "npm:chalk@4", + "specifier": "npm:chai@4.3", "code": { - "specifier": "npm:chalk@4", + "specifier": "npm:chai@4.3", "span": { "start": { - "line": 0, - "character": 18 + "line": 1, + "character": 23 }, "end": { - "line": 0, - "character": 31 + "line": 1, + "character": 37 } } }, - "npmPackage": "chalk@4.1.2" + "npmPackage": "chai@4.3.6" } ], - "kind": "esm", "local": "[WILDCARD]main.js", "emit": null, "map": null, @@ -49,7 +49,10 @@ "specifier": "[WILDCARD]/main.js" } ], - "redirects": {}, + "redirects": { + "npm:chai@4.3": "npm:chai@4.3.6", + "npm:chalk@4": "npm:chalk@4.1.2" + }, "npmPackages": { "ansi-styles@4.3.0": { "name": "ansi-styles", diff --git a/cli/tests/testdata/npm/compare_globals/main.out b/cli/tests/testdata/npm/compare_globals/main.out index 8b3b62bc1ba4ae..2868341683fa65 100644 --- a/cli/tests/testdata/npm/compare_globals/main.out +++ b/cli/tests/testdata/npm/compare_globals/main.out @@ -1,7 +1,10 @@ -Download http://localhost:4545/npm/registry/@denotest/globals Download http://localhost:4545/npm/registry/@types/node +Download http://localhost:4545/npm/registry/@denotest/globals Download http://localhost:4545/npm/registry/@denotest/globals/1.0.0.tgz Download http://localhost:4545/npm/registry/@types/node/node-18.8.2.tgz Check file:///[WILDCARD]/npm/compare_globals/main.ts true [] +5 +undefined +undefined diff --git a/cli/tests/testdata/npm/compare_globals/main.ts b/cli/tests/testdata/npm/compare_globals/main.ts index 5710d0bd59019f..0468404a882612 100644 --- a/cli/tests/testdata/npm/compare_globals/main.ts +++ b/cli/tests/testdata/npm/compare_globals/main.ts @@ -12,3 +12,16 @@ type _TestHasNodeJsGlobal = NodeJS.Architecture; const controller = new AbortController(); controller.abort("reason"); // in the NodeJS declaration it doesn't have a reason + +// Super edge case where some Node code deletes a global where the +// Node code has its own global and the Deno code has the same global, +// but it's different. Basically if some Node code deletes +// one of these globals then we don't want it to suddenly inherit +// the Deno global. +globals.withNodeGlobalThis((nodeGlobalThis: any) => { + (globalThis as any).setTimeout = 5; + console.log(setTimeout); + delete nodeGlobalThis["setTimeout"]; + console.log(nodeGlobalThis["setTimeout"]); // should be undefined + console.log(globalThis["setTimeout"]); // should be undefined +}); diff --git a/cli/tests/testdata/npm/import_map/main.out b/cli/tests/testdata/npm/import_map/main.out index b5b67651ef6e79..29f0f4283835c1 100644 --- a/cli/tests/testdata/npm/import_map/main.out +++ b/cli/tests/testdata/npm/import_map/main.out @@ -1,5 +1,5 @@ -Download http://localhost:4545/npm/registry/@denotest/dual-cjs-esm Download http://localhost:4545/npm/registry/chalk +Download http://localhost:4545/npm/registry/@denotest/dual-cjs-esm Download http://localhost:4545/npm/registry/@denotest/dual-cjs-esm/1.0.0.tgz Download http://localhost:4545/npm/registry/chalk/chalk-5.0.1.tgz chalk import map loads diff --git a/cli/tests/testdata/npm/info/chalk.out b/cli/tests/testdata/npm/info/chalk.out index 89ea05e713c17b..d7ac95120edc0a 100644 --- a/cli/tests/testdata/npm/info/chalk.out +++ b/cli/tests/testdata/npm/info/chalk.out @@ -1,8 +1,7 @@ -type: Unknown dependencies: 5 unique size: [WILDCARD] -npm:chalk@4 - 4.1.2 ([WILDCARD]) +npm:chalk@4.1.2 ([WILDCARD]) ├─┬ npm:ansi-styles@4.3.0 ([WILDCARD]) │ └─┬ npm:color-convert@2.0.1 ([WILDCARD]) │ └── npm:color-name@1.1.4 ([WILDCARD]) diff --git a/cli/tests/testdata/npm/info/chalk_json.out b/cli/tests/testdata/npm/info/chalk_json.out index f6673d0321b487..0f86bc9941e53c 100644 --- a/cli/tests/testdata/npm/info/chalk_json.out +++ b/cli/tests/testdata/npm/info/chalk_json.out @@ -5,11 +5,13 @@ "modules": [ { "kind": "npm", - "specifier": "npm:chalk@4", + "specifier": "npm:chalk@4.1.2", "npmPackage": "chalk@4.1.2" } ], - "redirects": {}, + "redirects": { + "npm:chalk@4": "npm:chalk@4.1.2" + }, "npmPackages": { "ansi-styles@4.3.0": { "name": "ansi-styles", diff --git a/cli/tests/testdata/npm/node_modules_import/main.out b/cli/tests/testdata/npm/node_modules_import/main.out new file mode 100644 index 00000000000000..083edaac24891a --- /dev/null +++ b/cli/tests/testdata/npm/node_modules_import/main.out @@ -0,0 +1,3 @@ +2 +2 +2 diff --git a/cli/tests/testdata/npm/node_modules_import/main.ts b/cli/tests/testdata/npm/node_modules_import/main.ts new file mode 100644 index 00000000000000..848ca0f81e0557 --- /dev/null +++ b/cli/tests/testdata/npm/node_modules_import/main.ts @@ -0,0 +1,16 @@ +import * as myImport1 from "@denotest/esm-basic"; +import * as myImport2 from "./node_modules/@denotest/esm-basic/main.mjs"; +import * as myImport3 from "@denotest/esm-basic/main.mjs"; + +myImport1.setValue(5); +myImport2.setValue(2); + +// these should all give type errors +const value1: string = myImport1.getValue(); +const value2: string = myImport2.getValue(); +const value3: string = myImport3.getValue(); + +// these should all be equal because it should be mutating the same module +console.log(value1); +console.log(value2); +console.log(value3); diff --git a/cli/tests/testdata/npm/node_modules_import/main_check.out b/cli/tests/testdata/npm/node_modules_import/main_check.out new file mode 100644 index 00000000000000..cf7cc110d4abcd --- /dev/null +++ b/cli/tests/testdata/npm/node_modules_import/main_check.out @@ -0,0 +1,16 @@ +error: TS2322 [ERROR]: Type 'number' is not assignable to type 'string'. +const value1: string = myImport1.getValue(); + ~~~~~~ + at file:///[WILDCARD]/npm/node_modules_import/main.ts:9:7 + +TS2322 [ERROR]: Type 'number' is not assignable to type 'string'. +const value2: string = myImport2.getValue(); + ~~~~~~ + at file:///[WILDCARD]/npm/node_modules_import/main.ts:10:7 + +TS2322 [ERROR]: Type 'number' is not assignable to type 'string'. +const value3: string = myImport3.getValue(); + ~~~~~~ + at file:///[WILDCARD]/npm/node_modules_import/main.ts:11:7 + +Found 3 errors. diff --git a/cli/tests/testdata/npm/node_modules_import/package.json b/cli/tests/testdata/npm/node_modules_import/package.json new file mode 100644 index 00000000000000..ed77298e0c790c --- /dev/null +++ b/cli/tests/testdata/npm/node_modules_import/package.json @@ -0,0 +1,5 @@ +{ + "dependencies": { + "@denotest/esm-basic": "^1" + } +} diff --git a/cli/tests/testdata/npm/peer_deps_with_copied_folders/main_info.out b/cli/tests/testdata/npm/peer_deps_with_copied_folders/main_info.out index c9c4a59c142d3d..d85b00094c1da1 100644 --- a/cli/tests/testdata/npm/peer_deps_with_copied_folders/main_info.out +++ b/cli/tests/testdata/npm/peer_deps_with_copied_folders/main_info.out @@ -4,11 +4,11 @@ dependencies: 6 unique size: [WILDCARD] file:///[WILDCARD]/testdata/npm/peer_deps_with_copied_folders/main.ts (171B) -├─┬ npm:@denotest/peer-dep-test-child@1 - 1.0.0 ([WILDCARD]) +├─┬ npm:@denotest/peer-dep-test-child@1.0.0 ([WILDCARD]) │ ├─┬ npm:@denotest/peer-dep-test-grandchild@1.0.0_@denotest+peer-dep-test-peer@1.0.0 ([WILDCARD]) │ │ └── npm:@denotest/peer-dep-test-peer@1.0.0 ([WILDCARD]) │ └── npm:@denotest/peer-dep-test-peer@1.0.0 ([WILDCARD]) -└─┬ npm:@denotest/peer-dep-test-child@2 - 2.0.0 ([WILDCARD]) +└─┬ npm:@denotest/peer-dep-test-child@2.0.0 ([WILDCARD]) ├─┬ npm:@denotest/peer-dep-test-grandchild@1.0.0_@denotest+peer-dep-test-peer@2.0.0 ([WILDCARD]) │ └── npm:@denotest/peer-dep-test-peer@2.0.0 ([WILDCARD]) └── npm:@denotest/peer-dep-test-peer@2.0.0 ([WILDCARD]) diff --git a/cli/tests/testdata/npm/peer_deps_with_copied_folders/main_info_json.out b/cli/tests/testdata/npm/peer_deps_with_copied_folders/main_info_json.out index 634ec62516e4a6..6a455b00179e10 100644 --- a/cli/tests/testdata/npm/peer_deps_with_copied_folders/main_info_json.out +++ b/cli/tests/testdata/npm/peer_deps_with_copied_folders/main_info_json.out @@ -4,6 +4,7 @@ ], "modules": [ { + "kind": "esm", "dependencies": [ { "specifier": "npm:@denotest/peer-dep-test-child@1", @@ -40,7 +41,6 @@ "npmPackage": "@denotest/peer-dep-test-child@2.0.0_@denotest+peer-dep-test-peer@2.0.0" } ], - "kind": "esm", "local": "[WILDCARD]main.ts", "emit": null, "map": null, @@ -49,7 +49,10 @@ "specifier": "file://[WILDCARD]/main.ts" } ], - "redirects": {}, + "redirects": { + "npm:@denotest/peer-dep-test-child@1": "npm:@denotest/peer-dep-test-child@1.0.0", + "npm:@denotest/peer-dep-test-child@2": "npm:@denotest/peer-dep-test-child@2.0.0" + }, "npmPackages": { "@denotest/peer-dep-test-child@1.0.0_@denotest+peer-dep-test-peer@1.0.0": { "name": "@denotest/peer-dep-test-child", diff --git a/cli/tests/testdata/npm/registry/@denotest/bin/0.5.0/cli.mjs b/cli/tests/testdata/npm/registry/@denotest/bin/0.5.0/cli.mjs new file mode 100644 index 00000000000000..0ae8e919033045 --- /dev/null +++ b/cli/tests/testdata/npm/registry/@denotest/bin/0.5.0/cli.mjs @@ -0,0 +1,5 @@ +import process from "node:process"; + +for (const arg of process.argv.slice(2)) { + console.log(arg); +} diff --git a/cli/tests/testdata/npm/registry/@denotest/bin/0.5.0/package.json b/cli/tests/testdata/npm/registry/@denotest/bin/0.5.0/package.json new file mode 100644 index 00000000000000..caa2ef53888d0d --- /dev/null +++ b/cli/tests/testdata/npm/registry/@denotest/bin/0.5.0/package.json @@ -0,0 +1,5 @@ +{ + "name": "@deno/bin", + "version": "0.5.0", + "bin": "./cli.mjs" +} diff --git a/cli/tests/testdata/npm/registry/@denotest/esm-basic/1.0.0/main.d.mts b/cli/tests/testdata/npm/registry/@denotest/esm-basic/1.0.0/main.d.mts new file mode 100644 index 00000000000000..fa7814911e70e0 --- /dev/null +++ b/cli/tests/testdata/npm/registry/@denotest/esm-basic/1.0.0/main.d.mts @@ -0,0 +1,2 @@ +export declare function setValue(val: number): void; +export declare function getValue(): number; diff --git a/cli/tests/testdata/npm/registry/@denotest/esm-basic/1.0.0/main.mjs b/cli/tests/testdata/npm/registry/@denotest/esm-basic/1.0.0/main.mjs new file mode 100644 index 00000000000000..23df4221cbeb23 --- /dev/null +++ b/cli/tests/testdata/npm/registry/@denotest/esm-basic/1.0.0/main.mjs @@ -0,0 +1,9 @@ +let value = 0; + +export function setValue(newValue) { + value = newValue; +} + +export function getValue() { + return value; +} diff --git a/cli/tests/testdata/npm/registry/@denotest/esm-basic/1.0.0/package.json b/cli/tests/testdata/npm/registry/@denotest/esm-basic/1.0.0/package.json new file mode 100644 index 00000000000000..757ac2db9029f2 --- /dev/null +++ b/cli/tests/testdata/npm/registry/@denotest/esm-basic/1.0.0/package.json @@ -0,0 +1,7 @@ +{ + "name": "@denotest/esm-basic", + "version": "1.0.0", + "type": "module", + "main": "main.mjs", + "types": "main.d.mts" +} diff --git a/cli/tests/testdata/npm/registry/@denotest/globals/1.0.0/index.d.ts b/cli/tests/testdata/npm/registry/@denotest/globals/1.0.0/index.d.ts index ee03712dd461bb..3f3eeb92af6483 100644 --- a/cli/tests/testdata/npm/registry/@denotest/globals/1.0.0/index.d.ts +++ b/cli/tests/testdata/npm/registry/@denotest/globals/1.0.0/index.d.ts @@ -11,3 +11,5 @@ type AssertTrue = never; type _TestHasProcessGlobal = AssertTrue< typeof globalThis extends { process: any } ? true : false >; + +export function withNodeGlobalThis(action: (global: typeof globalThis) => void): void; diff --git a/cli/tests/testdata/npm/registry/@denotest/globals/1.0.0/index.js b/cli/tests/testdata/npm/registry/@denotest/globals/1.0.0/index.js index 50d2d3d2a3286a..daac83c6644e07 100644 --- a/cli/tests/testdata/npm/registry/@denotest/globals/1.0.0/index.js +++ b/cli/tests/testdata/npm/registry/@denotest/globals/1.0.0/index.js @@ -1,3 +1,7 @@ exports.globalThis = globalThis; exports.global = global; exports.process = process; + +exports.withNodeGlobalThis = function (action) { + action(globalThis); +}; diff --git a/cli/tests/testdata/npm/remote_npm_specifier/main.out b/cli/tests/testdata/npm/remote_npm_specifier/main.out index 0cb08b7bc3675b..9daeafb9864cf4 100644 --- a/cli/tests/testdata/npm/remote_npm_specifier/main.out +++ b/cli/tests/testdata/npm/remote_npm_specifier/main.out @@ -1 +1 @@ -error: importing npm specifiers in remote modules requires the --unstable flag (referrer: http://localhost:4545/npm/remote_npm_specifier/remote.ts) +test diff --git a/cli/tests/testdata/npm/typescript_file_in_package/main.out b/cli/tests/testdata/npm/typescript_file_in_package/main.out index ba53f77255066f..0d6f53cd972a88 100644 --- a/cli/tests/testdata/npm/typescript_file_in_package/main.out +++ b/cli/tests/testdata/npm/typescript_file_in_package/main.out @@ -1,6 +1,6 @@ Download http://localhost:4545/npm/registry/@denotest/typescript-file Download http://localhost:4545/npm/registry/@denotest/typescript-file/1.0.0.tgz -error: Could not resolve 'npm:@denotest/typescript-file'. +error: Could not resolve 'npm:@denotest/typescript-file@1.0.0'. Caused by: TypeScript files are not supported in npm packages: file:///[WILDCARD]/@denotest/typescript-file/1.0.0/index.ts diff --git a/cli/tests/testdata/package_json/basic/fail_check.check.out b/cli/tests/testdata/package_json/basic/fail_check.check.out new file mode 100644 index 00000000000000..03997a05184a16 --- /dev/null +++ b/cli/tests/testdata/package_json/basic/fail_check.check.out @@ -0,0 +1,4 @@ +error: TS2322 [ERROR]: Type 'number' is not assignable to type 'string'. +const _test: string = getValue(); + ~~~~~ + at file:///[WILDCARD]/fail_check.ts:3:7 diff --git a/cli/tests/testdata/package_json/basic/fail_check.ts b/cli/tests/testdata/package_json/basic/fail_check.ts new file mode 100644 index 00000000000000..ce849d92fbb3ed --- /dev/null +++ b/cli/tests/testdata/package_json/basic/fail_check.ts @@ -0,0 +1,3 @@ +import { getValue } from "./main.ts"; + +const _test: string = getValue(); diff --git a/cli/tests/testdata/package_json/basic/main.bench.out b/cli/tests/testdata/package_json/basic/main.bench.out new file mode 100644 index 00000000000000..db0db211472e03 --- /dev/null +++ b/cli/tests/testdata/package_json/basic/main.bench.out @@ -0,0 +1,11 @@ +Download http://localhost:4545/npm/registry/@denotest/esm-basic +Download http://localhost:4545/npm/registry/@denotest/esm-basic/1.0.0.tgz +Check file:///[WILDCARD]/main.bench.ts +0 +cpu: [WILDCARD] +runtime: [WILDCARD] + +file:///[WILDCARD]/main.bench.ts +[WILDCARD] +-------------------------------------------------- ----------------------------- +should add [WILDCARD] diff --git a/cli/tests/testdata/package_json/basic/main.bench.ts b/cli/tests/testdata/package_json/basic/main.bench.ts new file mode 100644 index 00000000000000..51bfc7bba1b6a1 --- /dev/null +++ b/cli/tests/testdata/package_json/basic/main.bench.ts @@ -0,0 +1,7 @@ +import { add } from "./main.ts"; + +Deno.bench("should add", () => { + if (add(1, 2) !== 3) { + throw new Error("Fail"); + } +}); diff --git a/cli/tests/testdata/package_json/basic/main.cache.out b/cli/tests/testdata/package_json/basic/main.cache.out new file mode 100644 index 00000000000000..4be62e9eb70dfb --- /dev/null +++ b/cli/tests/testdata/package_json/basic/main.cache.out @@ -0,0 +1,2 @@ +Download http://localhost:4545/npm/registry/@denotest/esm-basic +Download http://localhost:4545/npm/registry/@denotest/esm-basic/1.0.0.tgz diff --git a/cli/tests/testdata/package_json/basic/main.check.out b/cli/tests/testdata/package_json/basic/main.check.out new file mode 100644 index 00000000000000..09c3773144ea3a --- /dev/null +++ b/cli/tests/testdata/package_json/basic/main.check.out @@ -0,0 +1,3 @@ +Download http://localhost:4545/npm/registry/@denotest/esm-basic +Download http://localhost:4545/npm/registry/@denotest/esm-basic/1.0.0.tgz +Check file://[WILDCARD]/main.ts diff --git a/cli/tests/testdata/package_json/basic/main.info.out b/cli/tests/testdata/package_json/basic/main.info.out new file mode 100644 index 00000000000000..48c10a0bae4591 --- /dev/null +++ b/cli/tests/testdata/package_json/basic/main.info.out @@ -0,0 +1,7 @@ +local: [WILDCARD]main.ts +type: TypeScript +dependencies: 1 unique +size: [WILDCARD] + +file://[WILDCARD]/package_json/basic/main.ts ([WILDCARD]) +└── npm:@denotest/esm-basic@1.0.0 ([WILDCARD]) diff --git a/cli/tests/testdata/package_json/basic/main.test.out b/cli/tests/testdata/package_json/basic/main.test.out new file mode 100644 index 00000000000000..b04420b3bc05d4 --- /dev/null +++ b/cli/tests/testdata/package_json/basic/main.test.out @@ -0,0 +1,9 @@ +Download http://localhost:4545/npm/registry/@denotest/esm-basic +Download http://localhost:4545/npm/registry/@denotest/esm-basic/1.0.0.tgz +Check file://[WILDCARD]/main.test.ts +0 +running 1 test from [WILDCARD]main.test.ts +should add ... ok ([WILDCARD]) + +ok | 1 passed | 0 failed ([WILDCARD]) + diff --git a/cli/tests/testdata/package_json/basic/main.test.ts b/cli/tests/testdata/package_json/basic/main.test.ts new file mode 100644 index 00000000000000..298ce1f5bee459 --- /dev/null +++ b/cli/tests/testdata/package_json/basic/main.test.ts @@ -0,0 +1,7 @@ +import { add } from "./main.ts"; + +Deno.test("should add", () => { + if (add(1, 2) !== 3) { + throw new Error("Fail"); + } +}); diff --git a/cli/tests/testdata/package_json/basic/main.ts b/cli/tests/testdata/package_json/basic/main.ts new file mode 100644 index 00000000000000..5911fe32d03f89 --- /dev/null +++ b/cli/tests/testdata/package_json/basic/main.ts @@ -0,0 +1,11 @@ +import * as test from "@denotest/esm-basic"; + +console.log(test.getValue()); + +export function add(a: number, b: number) { + return a + b; +} + +export function getValue() { + return test.getValue(); +} diff --git a/cli/tests/testdata/package_json/basic/package.json b/cli/tests/testdata/package_json/basic/package.json new file mode 100644 index 00000000000000..54ca824d645c5f --- /dev/null +++ b/cli/tests/testdata/package_json/basic/package.json @@ -0,0 +1,5 @@ +{ + "dependencies": { + "@denotest/esm-basic": "*" + } +} diff --git a/cli/tests/testdata/package_json/deno_json/deno.json b/cli/tests/testdata/package_json/deno_json/deno.json new file mode 100644 index 00000000000000..8a89da280984b0 --- /dev/null +++ b/cli/tests/testdata/package_json/deno_json/deno.json @@ -0,0 +1,5 @@ +{ + "imports": { + "other": "./other.ts" + } +} diff --git a/cli/tests/testdata/package_json/deno_json/main.check.out b/cli/tests/testdata/package_json/deno_json/main.check.out new file mode 100644 index 00000000000000..53b6869c0388ca --- /dev/null +++ b/cli/tests/testdata/package_json/deno_json/main.check.out @@ -0,0 +1,11 @@ +error: TS2322 [ERROR]: Type 'number' is not assignable to type 'string'. +const _strValue1: string = NUMBER_VALUE; + ~~~~~~~~~~ + at file:///[WILDCARD]/main.ts:8:7 + +TS2322 [ERROR]: Type 'number' is not assignable to type 'string'. +const _strValue2: string = test.getValue(); + ~~~~~~~~~~ + at file:///[WILDCARD]/main.ts:9:7 + +Found 2 errors. diff --git a/cli/tests/testdata/package_json/deno_json/main.out b/cli/tests/testdata/package_json/deno_json/main.out new file mode 100644 index 00000000000000..1191247b6d9a20 --- /dev/null +++ b/cli/tests/testdata/package_json/deno_json/main.out @@ -0,0 +1,2 @@ +1 +2 diff --git a/cli/tests/testdata/package_json/deno_json/main.ts b/cli/tests/testdata/package_json/deno_json/main.ts new file mode 100644 index 00000000000000..7768ff3fc4bcd8 --- /dev/null +++ b/cli/tests/testdata/package_json/deno_json/main.ts @@ -0,0 +1,9 @@ +import { NUMBER_VALUE } from "other"; +import * as test from "@denotest/esm-basic"; + +test.setValue(2); +console.log(test.getValue()); + +// these should cause type errors +const _strValue1: string = NUMBER_VALUE; +const _strValue2: string = test.getValue(); diff --git a/cli/tests/testdata/package_json/deno_json/other.ts b/cli/tests/testdata/package_json/deno_json/other.ts new file mode 100644 index 00000000000000..997d84adfb2fc2 --- /dev/null +++ b/cli/tests/testdata/package_json/deno_json/other.ts @@ -0,0 +1,3 @@ +console.log(1); + +export const NUMBER_VALUE = 1; diff --git a/cli/tests/testdata/package_json/deno_json/package.json b/cli/tests/testdata/package_json/deno_json/package.json new file mode 100644 index 00000000000000..54ca824d645c5f --- /dev/null +++ b/cli/tests/testdata/package_json/deno_json/package.json @@ -0,0 +1,5 @@ +{ + "dependencies": { + "@denotest/esm-basic": "*" + } +} diff --git a/cli/tests/testdata/run/dynamic_import_concurrent_non_statically_analyzable/main.out b/cli/tests/testdata/run/dynamic_import_concurrent_non_statically_analyzable/main.out new file mode 100644 index 00000000000000..c344d0aaee5302 --- /dev/null +++ b/cli/tests/testdata/run/dynamic_import_concurrent_non_statically_analyzable/main.out @@ -0,0 +1,100 @@ +0 +1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15 +16 +17 +18 +19 +20 +21 +22 +23 +24 +25 +26 +27 +28 +29 +30 +31 +32 +33 +34 +35 +36 +37 +38 +39 +40 +41 +42 +43 +44 +45 +46 +47 +48 +49 +50 +51 +52 +53 +54 +55 +56 +57 +58 +59 +60 +61 +62 +63 +64 +65 +66 +67 +68 +69 +70 +71 +72 +73 +74 +75 +76 +77 +78 +79 +80 +81 +82 +83 +84 +85 +86 +87 +88 +89 +90 +91 +92 +93 +94 +95 +96 +97 +98 +99 diff --git a/cli/tests/testdata/run/dynamic_import_concurrent_non_statically_analyzable/main.ts b/cli/tests/testdata/run/dynamic_import_concurrent_non_statically_analyzable/main.ts new file mode 100644 index 00000000000000..0832e40bedd6dc --- /dev/null +++ b/cli/tests/testdata/run/dynamic_import_concurrent_non_statically_analyzable/main.ts @@ -0,0 +1,16 @@ +import * as path from "http://localhost:4545/deno_std/path/mod.ts"; + +const currentDir = path.dirname(path.fromFileUrl(import.meta.url)); +const url = path.toFileUrl(path.join(currentDir, "./mod.ts")); +const urls = []; + +// this is hard to reproduce, but doing this will help +for (let i = 0; i < 100; i++) { + urls.push(url.toString() + "#" + i); +} + +const results = await Promise.all(urls.map((url) => import(url))); + +for (const result of results) { + result.outputValue(); +} diff --git a/cli/tests/testdata/run/dynamic_import_concurrent_non_statically_analyzable/mod.ts b/cli/tests/testdata/run/dynamic_import_concurrent_non_statically_analyzable/mod.ts new file mode 100644 index 00000000000000..56f2002ed564a7 --- /dev/null +++ b/cli/tests/testdata/run/dynamic_import_concurrent_non_statically_analyzable/mod.ts @@ -0,0 +1,7 @@ +// sleep a bit so many concurrent tasks end up +// attempting to build the graph at the same time +import "http://localhost:4545/sleep/10"; + +export function outputValue() { + console.log(parseInt(new URL(import.meta.url).hash.slice(1), 10)); +} diff --git a/cli/tests/testdata/run/error_009_extensions_error.js.out b/cli/tests/testdata/run/error_009_extensions_error.js.out index 558eedbe152093..36fc6af26f18f8 100644 --- a/cli/tests/testdata/run/error_009_extensions_error.js.out +++ b/cli/tests/testdata/run/error_009_extensions_error.js.out @@ -2,5 +2,5 @@ new Event(); ^ at [WILDCARD] - at new Event (internal:ext/web/[WILDCARD]) + at new Event (internal:deno_web/[WILDCARD]) at [WILDCARD] diff --git a/cli/tests/testdata/run/event_listener_error_immediate_exit.ts.out b/cli/tests/testdata/run/event_listener_error_immediate_exit.ts.out index 1fb3ce76a407e2..8f03f71b814764 100644 --- a/cli/tests/testdata/run/event_listener_error_immediate_exit.ts.out +++ b/cli/tests/testdata/run/event_listener_error_immediate_exit.ts.out @@ -1,5 +1,4 @@ 1 -queueMicrotask error: Uncaught Error: bar throw new Error("bar"); ^ diff --git a/cli/tests/testdata/run/ffi/unstable_ffi_10.js b/cli/tests/testdata/run/ffi/unstable_ffi_10.js index da1c5b3a2595ed..d291c6bbc89943 100644 --- a/cli/tests/testdata/run/ffi/unstable_ffi_10.js +++ b/cli/tests/testdata/run/ffi/unstable_ffi_10.js @@ -1 +1 @@ -Deno[Deno.internal].core.ops.op_ffi_read_i16(0n, 0); +Deno[Deno.internal].core.ops.op_ffi_read_i16(null, 0); diff --git a/cli/tests/testdata/run/ffi/unstable_ffi_11.js b/cli/tests/testdata/run/ffi/unstable_ffi_11.js index c2d9213b4f5d46..fc00fac38e1e03 100644 --- a/cli/tests/testdata/run/ffi/unstable_ffi_11.js +++ b/cli/tests/testdata/run/ffi/unstable_ffi_11.js @@ -1 +1 @@ -Deno[Deno.internal].core.ops.op_ffi_read_u32(0n, 0); +Deno[Deno.internal].core.ops.op_ffi_read_u32(null, 0); diff --git a/cli/tests/testdata/run/ffi/unstable_ffi_12.js b/cli/tests/testdata/run/ffi/unstable_ffi_12.js index d3aaa7a71e58ac..6f085115d5e3d5 100644 --- a/cli/tests/testdata/run/ffi/unstable_ffi_12.js +++ b/cli/tests/testdata/run/ffi/unstable_ffi_12.js @@ -1 +1 @@ -Deno[Deno.internal].core.ops.op_ffi_read_i32(0n, 0); +Deno[Deno.internal].core.ops.op_ffi_read_i32(null, 0); diff --git a/cli/tests/testdata/run/ffi/unstable_ffi_13.js b/cli/tests/testdata/run/ffi/unstable_ffi_13.js index 859fbad60daa0a..c3b5105db1c0b5 100644 --- a/cli/tests/testdata/run/ffi/unstable_ffi_13.js +++ b/cli/tests/testdata/run/ffi/unstable_ffi_13.js @@ -1 +1 @@ -Deno[Deno.internal].core.ops.op_ffi_read_u64(0n, 0, new Uint32Array(2)); +Deno[Deno.internal].core.ops.op_ffi_read_u64(null, 0, new Uint32Array(2)); diff --git a/cli/tests/testdata/run/ffi/unstable_ffi_14.js b/cli/tests/testdata/run/ffi/unstable_ffi_14.js index 19f8a48c86e8ae..2d095c5d667392 100644 --- a/cli/tests/testdata/run/ffi/unstable_ffi_14.js +++ b/cli/tests/testdata/run/ffi/unstable_ffi_14.js @@ -1 +1 @@ -Deno[Deno.internal].core.ops.op_ffi_read_f32(0n, 0); +Deno[Deno.internal].core.ops.op_ffi_read_f32(null, 0); diff --git a/cli/tests/testdata/run/ffi/unstable_ffi_15.js b/cli/tests/testdata/run/ffi/unstable_ffi_15.js index 3a4c0252b9e9fa..a3cf2b0c5b9e28 100644 --- a/cli/tests/testdata/run/ffi/unstable_ffi_15.js +++ b/cli/tests/testdata/run/ffi/unstable_ffi_15.js @@ -1 +1 @@ -Deno[Deno.internal].core.ops.op_ffi_read_f64(0n, 0); +Deno[Deno.internal].core.ops.op_ffi_read_f64(null, 0); diff --git a/cli/tests/testdata/run/ffi/unstable_ffi_16.js b/cli/tests/testdata/run/ffi/unstable_ffi_16.js new file mode 100644 index 00000000000000..2bf3759b36ef8e --- /dev/null +++ b/cli/tests/testdata/run/ffi/unstable_ffi_16.js @@ -0,0 +1 @@ +Deno[Deno.internal].core.ops.op_ffi_ptr_value(null, new Uint32Array(2)); diff --git a/cli/tests/testdata/run/ffi/unstable_ffi_16.js.out b/cli/tests/testdata/run/ffi/unstable_ffi_16.js.out new file mode 100644 index 00000000000000..d6887078410938 --- /dev/null +++ b/cli/tests/testdata/run/ffi/unstable_ffi_16.js.out @@ -0,0 +1 @@ +Unstable API 'Deno.UnsafePointer#value'. The --unstable flag must be provided. diff --git a/cli/tests/testdata/run/ffi/unstable_ffi_17.js b/cli/tests/testdata/run/ffi/unstable_ffi_17.js new file mode 100644 index 00000000000000..595727092a7cf4 --- /dev/null +++ b/cli/tests/testdata/run/ffi/unstable_ffi_17.js @@ -0,0 +1 @@ +Deno[Deno.internal].core.ops.op_ffi_get_buf(null, 0, 0); diff --git a/cli/tests/testdata/run/ffi/unstable_ffi_17.js.out b/cli/tests/testdata/run/ffi/unstable_ffi_17.js.out new file mode 100644 index 00000000000000..294931243677a6 --- /dev/null +++ b/cli/tests/testdata/run/ffi/unstable_ffi_17.js.out @@ -0,0 +1 @@ +Unstable API 'Deno.UnsafePointerView#getArrayBuffer'. The --unstable flag must be provided. diff --git a/cli/tests/testdata/run/ffi/unstable_ffi_18.js b/cli/tests/testdata/run/ffi/unstable_ffi_18.js new file mode 100644 index 00000000000000..d222a7b7d78aa0 --- /dev/null +++ b/cli/tests/testdata/run/ffi/unstable_ffi_18.js @@ -0,0 +1 @@ +Deno[Deno.internal].core.ops.op_ffi_ptr_create(null); diff --git a/cli/tests/testdata/run/ffi/unstable_ffi_18.js.out b/cli/tests/testdata/run/ffi/unstable_ffi_18.js.out new file mode 100644 index 00000000000000..6f7ea0d8f3899d --- /dev/null +++ b/cli/tests/testdata/run/ffi/unstable_ffi_18.js.out @@ -0,0 +1 @@ +Unstable API 'Deno.UnsafePointer#create'. The --unstable flag must be provided. diff --git a/cli/tests/testdata/run/ffi/unstable_ffi_19.js b/cli/tests/testdata/run/ffi/unstable_ffi_19.js new file mode 100644 index 00000000000000..97d65002297638 --- /dev/null +++ b/cli/tests/testdata/run/ffi/unstable_ffi_19.js @@ -0,0 +1 @@ +Deno[Deno.internal].core.ops.op_ffi_ptr_equals(null, null); diff --git a/cli/tests/testdata/run/ffi/unstable_ffi_19.js.out b/cli/tests/testdata/run/ffi/unstable_ffi_19.js.out new file mode 100644 index 00000000000000..15a99b9ab80a3f --- /dev/null +++ b/cli/tests/testdata/run/ffi/unstable_ffi_19.js.out @@ -0,0 +1 @@ +Unstable API 'Deno.UnsafePointer#equals'. The --unstable flag must be provided. diff --git a/cli/tests/testdata/run/ffi/unstable_ffi_2.js b/cli/tests/testdata/run/ffi/unstable_ffi_2.js index 4ed91a9c2b1dc3..c99b1e586de265 100644 --- a/cli/tests/testdata/run/ffi/unstable_ffi_2.js +++ b/cli/tests/testdata/run/ffi/unstable_ffi_2.js @@ -1,4 +1,4 @@ -Deno[Deno.internal].core.ops.op_ffi_call_ptr(0n, { +Deno[Deno.internal].core.ops.op_ffi_call_ptr(null, { name: null, parameters: [], result: "void", diff --git a/cli/tests/testdata/run/ffi/unstable_ffi_3.js b/cli/tests/testdata/run/ffi/unstable_ffi_3.js index 284ecbc913c54f..b59a264ead7ff3 100644 --- a/cli/tests/testdata/run/ffi/unstable_ffi_3.js +++ b/cli/tests/testdata/run/ffi/unstable_ffi_3.js @@ -1,4 +1,4 @@ -Deno[Deno.internal].core.opAsync("op_ffi_call_ptr_nonblocking", 0n, { +Deno[Deno.internal].core.opAsync("op_ffi_call_ptr_nonblocking", null, { name: null, parameters: [], result: "void", diff --git a/cli/tests/testdata/run/ffi/unstable_ffi_5.js b/cli/tests/testdata/run/ffi/unstable_ffi_5.js index 278c3c9d255873..416c781752475f 100644 --- a/cli/tests/testdata/run/ffi/unstable_ffi_5.js +++ b/cli/tests/testdata/run/ffi/unstable_ffi_5.js @@ -1 +1,6 @@ -Deno[Deno.internal].core.ops.op_ffi_buf_copy_into(0n, 0, new Uint8Array(0), 0); +Deno[Deno.internal].core.ops.op_ffi_buf_copy_into( + null, + 0, + new Uint8Array(0), + 0, +); diff --git a/cli/tests/testdata/run/ffi/unstable_ffi_6.js b/cli/tests/testdata/run/ffi/unstable_ffi_6.js index e6add70d62006e..7a079f5fb87801 100644 --- a/cli/tests/testdata/run/ffi/unstable_ffi_6.js +++ b/cli/tests/testdata/run/ffi/unstable_ffi_6.js @@ -1 +1 @@ -Deno[Deno.internal].core.ops.op_ffi_cstr_read(0n, 0); +Deno[Deno.internal].core.ops.op_ffi_cstr_read(null, 0); diff --git a/cli/tests/testdata/run/ffi/unstable_ffi_7.js b/cli/tests/testdata/run/ffi/unstable_ffi_7.js index 6ba28b3e33f004..1f9e5f0c0534b2 100644 --- a/cli/tests/testdata/run/ffi/unstable_ffi_7.js +++ b/cli/tests/testdata/run/ffi/unstable_ffi_7.js @@ -1 +1 @@ -Deno[Deno.internal].core.ops.op_ffi_read_u8(0n, 0); +Deno[Deno.internal].core.ops.op_ffi_read_u8(null, 0); diff --git a/cli/tests/testdata/run/ffi/unstable_ffi_8.js b/cli/tests/testdata/run/ffi/unstable_ffi_8.js index 2b0e0343bf5ea2..cbd0ec9eca66ea 100644 --- a/cli/tests/testdata/run/ffi/unstable_ffi_8.js +++ b/cli/tests/testdata/run/ffi/unstable_ffi_8.js @@ -1 +1 @@ -Deno[Deno.internal].core.ops.op_ffi_read_i8(0n, 0); +Deno[Deno.internal].core.ops.op_ffi_read_i8(null, 0); diff --git a/cli/tests/testdata/run/ffi/unstable_ffi_9.js b/cli/tests/testdata/run/ffi/unstable_ffi_9.js index 729a8584ed7a9a..9e8da12db1d12e 100644 --- a/cli/tests/testdata/run/ffi/unstable_ffi_9.js +++ b/cli/tests/testdata/run/ffi/unstable_ffi_9.js @@ -1 +1 @@ -Deno[Deno.internal].core.ops.op_ffi_read_u16(0n, 0); +Deno[Deno.internal].core.ops.op_ffi_read_u16(null, 0); diff --git a/cli/tests/testdata/run/internal_dynamic_import.ts b/cli/tests/testdata/run/internal_dynamic_import.ts new file mode 100644 index 00000000000000..3fb2791e37a2f4 --- /dev/null +++ b/cli/tests/testdata/run/internal_dynamic_import.ts @@ -0,0 +1 @@ +await import("internal:runtime/js/01_build.js"); diff --git a/cli/tests/testdata/run/internal_dynamic_import.ts.out b/cli/tests/testdata/run/internal_dynamic_import.ts.out new file mode 100644 index 00000000000000..fa98b8733cd3c7 --- /dev/null +++ b/cli/tests/testdata/run/internal_dynamic_import.ts.out @@ -0,0 +1,4 @@ +error: Uncaught TypeError: Cannot load internal module from external code +await import("internal:runtime/js/01_build.js"); +^ + at [WILDCARD]/internal_dynamic_import.ts:1:1 diff --git a/cli/tests/testdata/run/internal_import.ts b/cli/tests/testdata/run/internal_import.ts new file mode 100644 index 00000000000000..666b68769ac36d --- /dev/null +++ b/cli/tests/testdata/run/internal_import.ts @@ -0,0 +1 @@ +import "internal:runtime/js/01_build.js"; diff --git a/cli/tests/testdata/run/internal_import.ts.out b/cli/tests/testdata/run/internal_import.ts.out new file mode 100644 index 00000000000000..142308a3853a37 --- /dev/null +++ b/cli/tests/testdata/run/internal_import.ts.out @@ -0,0 +1,8 @@ +error: Unsupported scheme "internal" for module "internal:runtime/js/01_build.js". Supported schemes: [ + "data", + "blob", + "file", + "http", + "https", +] + at [WILDCARD] diff --git a/cli/tests/testdata/run/node_builtin_modules/mod.js b/cli/tests/testdata/run/node_builtin_modules/mod.js index 70e39be5689ae1..a01ac4422b2f32 100644 --- a/cli/tests/testdata/run/node_builtin_modules/mod.js +++ b/cli/tests/testdata/run/node_builtin_modules/mod.js @@ -1,2 +1,5 @@ +import { createRequire } from "node:module"; +console.log(createRequire); import process from "node:process"; console.log(process.version); +console.log(process.argv); diff --git a/cli/tests/testdata/run/node_builtin_modules/mod.js.out b/cli/tests/testdata/run/node_builtin_modules/mod.js.out index 9dc2247f4d4339..0d96b31ab63e27 100644 --- a/cli/tests/testdata/run/node_builtin_modules/mod.js.out +++ b/cli/tests/testdata/run/node_builtin_modules/mod.js.out @@ -1 +1,8 @@ +[Function: createRequire] v[WILDCARD].[WILDCARD].[WILDCARD] +[ + "[WILDCARD]", + "[WILDCARD]mod.js", + "hello", + "there" +] diff --git a/cli/tests/testdata/run/node_builtin_modules/mod.ts b/cli/tests/testdata/run/node_builtin_modules/mod.ts index 70e39be5689ae1..a01ac4422b2f32 100644 --- a/cli/tests/testdata/run/node_builtin_modules/mod.ts +++ b/cli/tests/testdata/run/node_builtin_modules/mod.ts @@ -1,2 +1,5 @@ +import { createRequire } from "node:module"; +console.log(createRequire); import process from "node:process"; console.log(process.version); +console.log(process.argv); diff --git a/cli/tests/testdata/run/node_builtin_modules/mod.ts.out b/cli/tests/testdata/run/node_builtin_modules/mod.ts.out index 9dc2247f4d4339..f19bd81e67974f 100644 --- a/cli/tests/testdata/run/node_builtin_modules/mod.ts.out +++ b/cli/tests/testdata/run/node_builtin_modules/mod.ts.out @@ -1 +1,8 @@ +[Function: createRequire] v[WILDCARD].[WILDCARD].[WILDCARD] +[ + "[WILDCARD]", + "[WILDCARD]mod.ts", + "hello", + "there" +] diff --git a/cli/tests/testdata/run/permissions_prompt_allow_all.ts b/cli/tests/testdata/run/permissions_prompt_allow_all.ts new file mode 100644 index 00000000000000..8aa7d040eca6d5 --- /dev/null +++ b/cli/tests/testdata/run/permissions_prompt_allow_all.ts @@ -0,0 +1,20 @@ +Deno.permissions.requestSync({ name: "run", command: "FOO" }); +Deno.permissions.requestSync({ name: "run", command: "BAR" }); + +Deno.permissions.requestSync({ name: "read", path: "FOO" }); +Deno.permissions.requestSync({ name: "read", path: "BAR" }); + +Deno.permissions.requestSync({ name: "write", path: "FOO" }); +Deno.permissions.requestSync({ name: "write", path: "BAR" }); + +Deno.permissions.requestSync({ name: "net", host: "FOO" }); +Deno.permissions.requestSync({ name: "net", host: "BAR" }); + +Deno.permissions.requestSync({ name: "env", variable: "FOO" }); +Deno.permissions.requestSync({ name: "env", variable: "BAR" }); + +Deno.permissions.requestSync({ name: "sys", kind: "loadavg" }); +Deno.permissions.requestSync({ name: "sys", kind: "hostname" }); + +Deno.permissions.requestSync({ name: "ffi", path: "FOO" }); +Deno.permissions.requestSync({ name: "ffi", path: "BAR" }); diff --git a/cli/tests/testdata/run/permissions_prompt_allow_all_2.ts b/cli/tests/testdata/run/permissions_prompt_allow_all_2.ts new file mode 100644 index 00000000000000..f42b3575362268 --- /dev/null +++ b/cli/tests/testdata/run/permissions_prompt_allow_all_2.ts @@ -0,0 +1,8 @@ +Deno.env.get("FOO"); +Deno.env.get("BAR"); + +Deno.loadavg(); +Deno.hostname(); + +Deno.cwd(); +Deno.lstatSync(new URL("../", import.meta.url)); diff --git a/cli/tests/testdata/run/reference_types_error.js.out b/cli/tests/testdata/run/reference_types_error.js.out index ebb9b3a26d6371..86055f3ac3f683 100644 --- a/cli/tests/testdata/run/reference_types_error.js.out +++ b/cli/tests/testdata/run/reference_types_error.js.out @@ -1,2 +1,2 @@ error: Module not found "file:///[WILDCARD]/nonexistent.d.ts". - at file:///[WILDCARD]/reference_types_error.js:1:23 + at file:///[WILDCARD]/reference_types_error.js:1:22 diff --git a/cli/tests/testdata/run/resolve_dns.ts b/cli/tests/testdata/run/resolve_dns.ts index ae6ed70a4210c1..a2d0fd04684912 100644 --- a/cli/tests/testdata/run/resolve_dns.ts +++ b/cli/tests/testdata/run/resolve_dns.ts @@ -69,3 +69,25 @@ try { } catch (e) { console.log(e.message); } + +try { + const ac = new AbortController(); + queueMicrotask(() => ac.abort()); + await Deno.resolveDns("www.example.com", "A", { + ...nameServer, + signal: ac.signal, + }); +} catch (e) { + console.log(e.name); +} + +try { + const ac = new AbortController(); + ac.abort(); + await Deno.resolveDns("www.example.com", "A", { + ...nameServer, + signal: ac.signal, + }); +} catch (e) { + console.log(e.name); +} diff --git a/cli/tests/testdata/run/resolve_dns.ts.out b/cli/tests/testdata/run/resolve_dns.ts.out index f41dc68bcbddd8..02502839592973 100644 --- a/cli/tests/testdata/run/resolve_dns.ts.out +++ b/cli/tests/testdata/run/resolve_dns.ts.out @@ -24,3 +24,5 @@ TXT [["I","am","a","txt","record"],["I","am","another","txt","record"],["I am a different","txt record"],["key=val"]] Error NotFound thrown for not-found-example.com Provided record type is not supported +AbortError +AbortError diff --git a/cli/tests/testdata/run/wasm_streaming_panic_test.js.out b/cli/tests/testdata/run/wasm_streaming_panic_test.js.out index 72237df6fa5373..eec1e3b9688758 100644 --- a/cli/tests/testdata/run/wasm_streaming_panic_test.js.out +++ b/cli/tests/testdata/run/wasm_streaming_panic_test.js.out @@ -1,2 +1,2 @@ error: Uncaught (in promise) TypeError: Invalid WebAssembly content type. - at handleWasmStreaming (internal:ext/fetch/26_fetch.js:[WILDCARD]) + at handleWasmStreaming (internal:deno_fetch/26_fetch.js:[WILDCARD]) diff --git a/cli/tests/testdata/run/with_package_json/.gitignore b/cli/tests/testdata/run/with_package_json/.gitignore new file mode 100644 index 00000000000000..40b878db5b1c97 --- /dev/null +++ b/cli/tests/testdata/run/with_package_json/.gitignore @@ -0,0 +1 @@ +node_modules/ \ No newline at end of file diff --git a/cli/tests/testdata/run/with_package_json/no_deno_json/main.out b/cli/tests/testdata/run/with_package_json/no_deno_json/main.out new file mode 100644 index 00000000000000..a41c8787aeb004 --- /dev/null +++ b/cli/tests/testdata/run/with_package_json/no_deno_json/main.out @@ -0,0 +1,9 @@ +[WILDCARD]package.json file found at '[WILDCARD]with_package_json[WILDCARD]package.json' +[WILDCARD] +ok +[Chalk] { + constructor: [Function], + Instance: [Class: ChalkClass], + supportsColor: false, + stderr: [Chalk] { constructor: [Function], Instance: [Class: ChalkClass], supportsColor: false } +} diff --git a/cli/tests/testdata/run/with_package_json/no_deno_json/main.ts b/cli/tests/testdata/run/with_package_json/no_deno_json/main.ts new file mode 100644 index 00000000000000..1e6e5004009456 --- /dev/null +++ b/cli/tests/testdata/run/with_package_json/no_deno_json/main.ts @@ -0,0 +1,4 @@ +import chalk from "chalk"; + +console.log("ok"); +console.log(chalk); diff --git a/cli/tests/testdata/run/with_package_json/no_deno_json/no_package_json_imports.out b/cli/tests/testdata/run/with_package_json/no_deno_json/no_package_json_imports.out new file mode 100644 index 00000000000000..7ed6ff82de6bcc --- /dev/null +++ b/cli/tests/testdata/run/with_package_json/no_deno_json/no_package_json_imports.out @@ -0,0 +1 @@ +5 diff --git a/cli/tests/testdata/run/with_package_json/no_deno_json/no_package_json_imports.ts b/cli/tests/testdata/run/with_package_json/no_deno_json/no_package_json_imports.ts new file mode 100644 index 00000000000000..0f3785f9101be9 --- /dev/null +++ b/cli/tests/testdata/run/with_package_json/no_deno_json/no_package_json_imports.ts @@ -0,0 +1 @@ +console.log(5); diff --git a/cli/tests/testdata/run/with_package_json/no_deno_json/noconfig.out b/cli/tests/testdata/run/with_package_json/no_deno_json/noconfig.out new file mode 100644 index 00000000000000..ee6e346de6d690 --- /dev/null +++ b/cli/tests/testdata/run/with_package_json/no_deno_json/noconfig.out @@ -0,0 +1,3 @@ +[WILDCARD]package.json auto-discovery is disabled +[WILDCARD] +success diff --git a/cli/tests/testdata/run/with_package_json/no_deno_json/noconfig.ts b/cli/tests/testdata/run/with_package_json/no_deno_json/noconfig.ts new file mode 100644 index 00000000000000..73b348fbc23504 --- /dev/null +++ b/cli/tests/testdata/run/with_package_json/no_deno_json/noconfig.ts @@ -0,0 +1,8 @@ +// ensure the cwd is this directory +const cwd = Deno.cwd(); +if (!cwd.endsWith("no_deno_json")) { + console.log(cwd); + throw "FAIL"; +} else { + console.log("success"); +} diff --git a/cli/tests/testdata/run/with_package_json/no_deno_json/package.json b/cli/tests/testdata/run/with_package_json/no_deno_json/package.json new file mode 100644 index 00000000000000..a85b890a84c3b8 --- /dev/null +++ b/cli/tests/testdata/run/with_package_json/no_deno_json/package.json @@ -0,0 +1,9 @@ +{ + "dependencies": { + "@denotest/check-error": "1.0.0", + "chalk": "4" + }, + "devDependencies": { + "@denotest/cjs-default-export": "1.0.0" + } +} diff --git a/cli/tests/testdata/run/with_package_json/no_deno_json/sub_dir/main.js b/cli/tests/testdata/run/with_package_json/no_deno_json/sub_dir/main.js new file mode 100644 index 00000000000000..492a8fa40f8114 --- /dev/null +++ b/cli/tests/testdata/run/with_package_json/no_deno_json/sub_dir/main.js @@ -0,0 +1,3 @@ +import "chalk"; +console.log(Deno.cwd()); +console.log(Deno.statSync("../node_modules")); diff --git a/cli/tests/testdata/run/with_package_json/no_deno_json/sub_dir/main.out b/cli/tests/testdata/run/with_package_json/no_deno_json/sub_dir/main.out new file mode 100644 index 00000000000000..0ec79196042a12 --- /dev/null +++ b/cli/tests/testdata/run/with_package_json/no_deno_json/sub_dir/main.out @@ -0,0 +1,7 @@ +Download http://[WILDCARD] +[WILDCARD]sub_dir +{ + [WILDCARD] + isDirectory: true, + [WILDCARD] +} diff --git a/cli/tests/testdata/run/with_package_json/npm_binary/main.out b/cli/tests/testdata/run/with_package_json/npm_binary/main.out new file mode 100644 index 00000000000000..56cdae6f94d11e --- /dev/null +++ b/cli/tests/testdata/run/with_package_json/npm_binary/main.out @@ -0,0 +1,6 @@ +[WILDCARD]package.json file found at '[WILDCARD]with_package_json[WILDCARD]npm_binary[WILDCARD]package.json' +[WILDCARD] +this +is +a +test diff --git a/cli/tests/testdata/run/with_package_json/npm_binary/package.json b/cli/tests/testdata/run/with_package_json/npm_binary/package.json new file mode 100644 index 00000000000000..9ee3f39a8636e0 --- /dev/null +++ b/cli/tests/testdata/run/with_package_json/npm_binary/package.json @@ -0,0 +1,8 @@ +{ + "dependencies": { + "@denotest/check-error": "1.0.0" + }, + "devDependencies": { + "@denotest/cjs-default-export": "1.0.0" + } +} diff --git a/cli/tests/testdata/run/with_package_json/with_stop/main.out b/cli/tests/testdata/run/with_package_json/with_stop/main.out new file mode 100644 index 00000000000000..b199faf8db5782 --- /dev/null +++ b/cli/tests/testdata/run/with_package_json/with_stop/main.out @@ -0,0 +1,5 @@ +[WILDCARD]Config file found at '[WILDCARD]with_package_json[WILDCARD]with_stop[WILDCARD]some[WILDCARD]nested[WILDCARD]deno.json' +[WILDCARD]No package.json file found +[WILDCARD] +error: Relative import path "chalk" not prefixed with / or ./ or ../ + at file:///[WILDCARD]with_package_json/with_stop/some/nested/dir/main.ts:3:19 diff --git a/cli/tests/testdata/run/with_package_json/with_stop/package.json b/cli/tests/testdata/run/with_package_json/with_stop/package.json new file mode 100644 index 00000000000000..9ee3f39a8636e0 --- /dev/null +++ b/cli/tests/testdata/run/with_package_json/with_stop/package.json @@ -0,0 +1,8 @@ +{ + "dependencies": { + "@denotest/check-error": "1.0.0" + }, + "devDependencies": { + "@denotest/cjs-default-export": "1.0.0" + } +} diff --git a/cli/tests/testdata/run/with_package_json/with_stop/some/nested/deno.json b/cli/tests/testdata/run/with_package_json/with_stop/some/nested/deno.json new file mode 100644 index 00000000000000..36e1765d1aface --- /dev/null +++ b/cli/tests/testdata/run/with_package_json/with_stop/some/nested/deno.json @@ -0,0 +1,5 @@ +{ + "tasks": { + "dev": "deno run main.ts" + } +} diff --git a/cli/tests/testdata/run/with_package_json/with_stop/some/nested/dir/main.ts b/cli/tests/testdata/run/with_package_json/with_stop/some/nested/dir/main.ts new file mode 100644 index 00000000000000..6016470a1067d4 --- /dev/null +++ b/cli/tests/testdata/run/with_package_json/with_stop/some/nested/dir/main.ts @@ -0,0 +1,6 @@ +// This import should fail, because `package.json` is not discovered, as we're +// stopping the discovery when encountering `deno.json`. +import chalk from "chalk"; + +console.log("ok"); +console.log(chalk); diff --git a/cli/tests/testdata/run/worker_close_in_wasm_reactions.js.out b/cli/tests/testdata/run/worker_close_in_wasm_reactions.js.out index 6485c620e5c3dc..66eb8201cdf3ea 100644 --- a/cli/tests/testdata/run/worker_close_in_wasm_reactions.js.out +++ b/cli/tests/testdata/run/worker_close_in_wasm_reactions.js.out @@ -1 +1,2 @@ Error: CompileError: WebAssembly.compile(): expected length: @+10 + at file:///[WILDCARD]/close_in_wasm_reactions.js:18:13 diff --git a/cli/tests/testdata/run/worker_drop_handle_race.js.out b/cli/tests/testdata/run/worker_drop_handle_race.js.out index afb522baac4bdc..c47db83ace67c0 100644 --- a/cli/tests/testdata/run/worker_drop_handle_race.js.out +++ b/cli/tests/testdata/run/worker_drop_handle_race.js.out @@ -2,7 +2,7 @@ error: Uncaught (in worker "") Error throw new Error(); ^ at [WILDCARD]/workers/drop_handle_race.js:2:9 - at Object.action (internal:ext/web/02_timers.js:[WILDCARD]) - at handleTimerMacrotask (internal:ext/web/02_timers.js:[WILDCARD]) + at Object.action (internal:deno_web/02_timers.js:[WILDCARD]) + at handleTimerMacrotask (internal:deno_web/02_timers.js:[WILDCARD]) error: Uncaught (in promise) Error: Unhandled error in child worker. at Worker.#pollControl (internal:runtime/js/11_workers.js:[WILDCARD]) diff --git a/cli/tests/testdata/task/both/deno.json b/cli/tests/testdata/task/both/deno.json new file mode 100644 index 00000000000000..1038609a4afbf5 --- /dev/null +++ b/cli/tests/testdata/task/both/deno.json @@ -0,0 +1,6 @@ +{ + "tasks": { + "output": "deno eval 'console.log(1)'", + "other": "deno eval 'console.log(2)'" + } +} diff --git a/cli/tests/testdata/task/both/deno_selected.out b/cli/tests/testdata/task/both/deno_selected.out new file mode 100644 index 00000000000000..f5bbab26dbce8e --- /dev/null +++ b/cli/tests/testdata/task/both/deno_selected.out @@ -0,0 +1,2 @@ +Task other deno eval 'console.log(2)' +2 diff --git a/cli/tests/testdata/task/both/echo.out b/cli/tests/testdata/task/both/echo.out new file mode 100644 index 00000000000000..e69de29bb2d1d6 diff --git a/cli/tests/testdata/task/both/no_args.out b/cli/tests/testdata/task/both/no_args.out new file mode 100644 index 00000000000000..fce690b705341e --- /dev/null +++ b/cli/tests/testdata/task/both/no_args.out @@ -0,0 +1,7 @@ +Available tasks: +- output + deno eval 'console.log(1)' +- other + deno eval 'console.log(2)' +- bin (package.json) + cli-esm testing this out diff --git a/cli/tests/testdata/task/both/package.json b/cli/tests/testdata/task/both/package.json new file mode 100644 index 00000000000000..708ccc6b192fb4 --- /dev/null +++ b/cli/tests/testdata/task/both/package.json @@ -0,0 +1,9 @@ +{ + "scripts": { + "bin": "cli-esm testing this out", + "output": "echo should never be called or shown" + }, + "dependencies": { + "other": "npm:@denotest/bin@1.0" + } +} diff --git a/cli/tests/testdata/task/both/package_json_selected.out b/cli/tests/testdata/task/both/package_json_selected.out new file mode 100644 index 00000000000000..435145debea991 --- /dev/null +++ b/cli/tests/testdata/task/both/package_json_selected.out @@ -0,0 +1,8 @@ +Download http://localhost:4545/npm/registry/@denotest/bin +Download http://localhost:4545/npm/registry/@denotest/bin/1.0.0.tgz +Warning Currently only basic package.json `scripts` are supported. Programs like `rimraf` or `cross-env` will not work correctly. This will be fixed in the upcoming release. +Task bin cli-esm testing this out "asdf" +testing +this +out +asdf diff --git a/cli/tests/testdata/task/both/prefers_deno.out b/cli/tests/testdata/task/both/prefers_deno.out new file mode 100644 index 00000000000000..391737272d68d8 --- /dev/null +++ b/cli/tests/testdata/task/both/prefers_deno.out @@ -0,0 +1,2 @@ +Task output deno eval 'console.log(1)' "some" "text" +1 diff --git a/cli/tests/testdata/task/deno.json b/cli/tests/testdata/task/deno_json/deno.json similarity index 100% rename from cli/tests/testdata/task/deno.json rename to cli/tests/testdata/task/deno_json/deno.json diff --git a/cli/tests/testdata/task/task_additional_args.out b/cli/tests/testdata/task/deno_json/task_additional_args.out similarity index 100% rename from cli/tests/testdata/task/task_additional_args.out rename to cli/tests/testdata/task/deno_json/task_additional_args.out diff --git a/cli/tests/testdata/task/task_additional_args_nested_strings.out b/cli/tests/testdata/task/deno_json/task_additional_args_nested_strings.out similarity index 100% rename from cli/tests/testdata/task/task_additional_args_nested_strings.out rename to cli/tests/testdata/task/deno_json/task_additional_args_nested_strings.out diff --git a/cli/tests/testdata/task/task_additional_args_no_logic.out b/cli/tests/testdata/task/deno_json/task_additional_args_no_logic.out similarity index 100% rename from cli/tests/testdata/task/task_additional_args_no_logic.out rename to cli/tests/testdata/task/deno_json/task_additional_args_no_logic.out diff --git a/cli/tests/testdata/task/task_additional_args_no_shell_expansion.out b/cli/tests/testdata/task/deno_json/task_additional_args_no_shell_expansion.out similarity index 100% rename from cli/tests/testdata/task/task_additional_args_no_shell_expansion.out rename to cli/tests/testdata/task/deno_json/task_additional_args_no_shell_expansion.out diff --git a/cli/tests/testdata/task/task_boolean_logic.out b/cli/tests/testdata/task/deno_json/task_boolean_logic.out similarity index 100% rename from cli/tests/testdata/task/task_boolean_logic.out rename to cli/tests/testdata/task/deno_json/task_boolean_logic.out diff --git a/cli/tests/testdata/task/task_cwd.out b/cli/tests/testdata/task/deno_json/task_cwd.out similarity index 100% rename from cli/tests/testdata/task/task_cwd.out rename to cli/tests/testdata/task/deno_json/task_cwd.out diff --git a/cli/tests/testdata/task/task_deno_exe_no_env.out b/cli/tests/testdata/task/deno_json/task_deno_exe_no_env.out similarity index 100% rename from cli/tests/testdata/task/task_deno_exe_no_env.out rename to cli/tests/testdata/task/deno_json/task_deno_exe_no_env.out diff --git a/cli/tests/testdata/task/task_exit_code_5.out b/cli/tests/testdata/task/deno_json/task_exit_code_5.out similarity index 100% rename from cli/tests/testdata/task/task_exit_code_5.out rename to cli/tests/testdata/task/deno_json/task_exit_code_5.out diff --git a/cli/tests/testdata/task/task_init_cwd.out b/cli/tests/testdata/task/deno_json/task_init_cwd.out similarity index 100% rename from cli/tests/testdata/task/task_init_cwd.out rename to cli/tests/testdata/task/deno_json/task_init_cwd.out diff --git a/cli/tests/testdata/task/task_init_cwd_already_set.out b/cli/tests/testdata/task/deno_json/task_init_cwd_already_set.out similarity index 100% rename from cli/tests/testdata/task/task_init_cwd_already_set.out rename to cli/tests/testdata/task/deno_json/task_init_cwd_already_set.out diff --git a/cli/tests/testdata/task/task_no_args.out b/cli/tests/testdata/task/deno_json/task_no_args.out similarity index 100% rename from cli/tests/testdata/task/task_no_args.out rename to cli/tests/testdata/task/deno_json/task_no_args.out index e41b3edd5b820f..18f86fce6cdf8a 100644 --- a/cli/tests/testdata/task/task_no_args.out +++ b/cli/tests/testdata/task/deno_json/task_no_args.out @@ -1,19 +1,19 @@ Available tasks: - boolean_logic sleep 0.1 && echo 3 && echo 4 & echo 1 && echo 2 || echo NOPE -- deno_echo - deno eval 'console.log(5)' - echo echo 1 +- deno_echo + deno eval 'console.log(5)' +- strings + deno run main.ts && deno eval "console.log(\"test\")" +- piped + echo 12345 | (deno eval 'const b = new Uint8Array(1);Deno.stdin.readSync(b);console.log(b)' && deno eval 'const b = new Uint8Array(1);Deno.stdin.readSync(b);console.log(b)') +- exit_code_5 + echo $(echo 10 ; exit 2) && exit 5 - echo_cwd echo $(pwd) -- echo_emoji - echo 🔥 - echo_init_cwd echo $INIT_CWD -- exit_code_5 - echo $(echo 10 ; exit 2) && exit 5 -- piped - echo 12345 | (deno eval 'const b = new Uint8Array(1);Deno.stdin.readSync(b);console.log(b)' && deno eval 'const b = new Uint8Array(1);Deno.stdin.readSync(b);console.log(b)') -- strings - deno run main.ts && deno eval "console.log(\"test\")" +- echo_emoji + echo 🔥 diff --git a/cli/tests/testdata/task/task_non_existent.out b/cli/tests/testdata/task/deno_json/task_non_existent.out similarity index 100% rename from cli/tests/testdata/task/task_non_existent.out rename to cli/tests/testdata/task/deno_json/task_non_existent.out index 0e70f24d9ba37c..efe3805f68b45d 100644 --- a/cli/tests/testdata/task/task_non_existent.out +++ b/cli/tests/testdata/task/deno_json/task_non_existent.out @@ -2,19 +2,19 @@ Task not found: non_existent Available tasks: - boolean_logic sleep 0.1 && echo 3 && echo 4 & echo 1 && echo 2 || echo NOPE -- deno_echo - deno eval 'console.log(5)' - echo echo 1 +- deno_echo + deno eval 'console.log(5)' +- strings + deno run main.ts && deno eval "console.log(\"test\")" +- piped + echo 12345 | (deno eval 'const b = new Uint8Array(1);Deno.stdin.readSync(b);console.log(b)' && deno eval 'const b = new Uint8Array(1);Deno.stdin.readSync(b);console.log(b)') +- exit_code_5 + echo $(echo 10 ; exit 2) && exit 5 - echo_cwd echo $(pwd) -- echo_emoji - echo 🔥 - echo_init_cwd echo $INIT_CWD -- exit_code_5 - echo $(echo 10 ; exit 2) && exit 5 -- piped - echo 12345 | (deno eval 'const b = new Uint8Array(1);Deno.stdin.readSync(b);console.log(b)' && deno eval 'const b = new Uint8Array(1);Deno.stdin.readSync(b);console.log(b)') -- strings - deno run main.ts && deno eval "console.log(\"test\")" +- echo_emoji + echo 🔥 diff --git a/cli/tests/testdata/task/task_piped_stdin.out b/cli/tests/testdata/task/deno_json/task_piped_stdin.out similarity index 100% rename from cli/tests/testdata/task/task_piped_stdin.out rename to cli/tests/testdata/task/deno_json/task_piped_stdin.out diff --git a/cli/tests/testdata/task/npx/non_existent.out b/cli/tests/testdata/task/npx/non_existent.out new file mode 100644 index 00000000000000..b08d29ece63c48 --- /dev/null +++ b/cli/tests/testdata/task/npx/non_existent.out @@ -0,0 +1,3 @@ +Warning Currently only basic package.json `scripts` are supported. Programs like `rimraf` or `cross-env` will not work correctly. This will be fixed in the upcoming release. +Task non-existent npx this-command-should-not-exist-for-you +npx: could not resolve command 'this-command-should-not-exist-for-you' diff --git a/cli/tests/testdata/task/npx/on_own.out b/cli/tests/testdata/task/npx/on_own.out new file mode 100644 index 00000000000000..80d8ed9db3bad6 --- /dev/null +++ b/cli/tests/testdata/task/npx/on_own.out @@ -0,0 +1,3 @@ +Warning Currently only basic package.json `scripts` are supported. Programs like `rimraf` or `cross-env` will not work correctly. This will be fixed in the upcoming release. +Task on-own npx +npx: missing command diff --git a/cli/tests/testdata/task/npx/package.json b/cli/tests/testdata/task/npx/package.json new file mode 100644 index 00000000000000..59602b96fe2b07 --- /dev/null +++ b/cli/tests/testdata/task/npx/package.json @@ -0,0 +1,6 @@ +{ + "scripts": { + "non-existent": "npx this-command-should-not-exist-for-you", + "on-own": "npx" + } +} diff --git a/cli/tests/testdata/task/package_json/bin.out b/cli/tests/testdata/task/package_json/bin.out new file mode 100644 index 00000000000000..d8594a243cbab9 --- /dev/null +++ b/cli/tests/testdata/task/package_json/bin.out @@ -0,0 +1,11 @@ +Download http://localhost:4545/npm/registry/@denotest/bin +Download http://localhost:4545/npm/registry/@denotest/bin/0.5.0.tgz +Download http://localhost:4545/npm/registry/@denotest/bin/1.0.0.tgz +Warning Currently only basic package.json `scripts` are supported. Programs like `rimraf` or `cross-env` will not work correctly. This will be fixed in the upcoming release. +Task bin @denotest/bin hi && cli-esm testing this out && npx cli-cjs test "extra" +hi +testing +this +out +test +extra diff --git a/cli/tests/testdata/task/package_json/echo.out b/cli/tests/testdata/task/package_json/echo.out new file mode 100644 index 00000000000000..d00491fd7e5bb6 --- /dev/null +++ b/cli/tests/testdata/task/package_json/echo.out @@ -0,0 +1 @@ +1 diff --git a/cli/tests/testdata/task/package_json/no_args.out b/cli/tests/testdata/task/package_json/no_args.out new file mode 100644 index 00000000000000..de149ccf9698e0 --- /dev/null +++ b/cli/tests/testdata/task/package_json/no_args.out @@ -0,0 +1,5 @@ +Available tasks: +- echo (package.json) + deno eval 'console.log(1)' +- bin (package.json) + @denotest/bin hi && cli-esm testing this out && npx cli-cjs test diff --git a/cli/tests/testdata/task/package_json/package.json b/cli/tests/testdata/task/package_json/package.json new file mode 100644 index 00000000000000..cedbe2d5bda35e --- /dev/null +++ b/cli/tests/testdata/task/package_json/package.json @@ -0,0 +1,10 @@ +{ + "scripts": { + "echo": "deno eval 'console.log(1)'", + "bin": "@denotest/bin hi && cli-esm testing this out && npx cli-cjs test" + }, + "dependencies": { + "@denotest/bin": "0.5", + "other": "npm:@denotest/bin@1.0" + } +} diff --git a/cli/tests/testdata/vendor/dynamic_non_existent.ts b/cli/tests/testdata/vendor/dynamic_non_existent.ts new file mode 100644 index 00000000000000..a48e2accb0669d --- /dev/null +++ b/cli/tests/testdata/vendor/dynamic_non_existent.ts @@ -0,0 +1,11 @@ +// this should still vendor +// deno-lint-ignore no-constant-condition +if (false) { + await import("./non-existent.js"); +} + +export class Logger { + log(text: string) { + console.log(text); + } +} diff --git a/cli/tests/testdata/vendor/dynamic_non_existent.ts.out b/cli/tests/testdata/vendor/dynamic_non_existent.ts.out new file mode 100644 index 00000000000000..a1b2ade81f128a --- /dev/null +++ b/cli/tests/testdata/vendor/dynamic_non_existent.ts.out @@ -0,0 +1,7 @@ +Download http://localhost:4545/vendor/dynamic_non_existent.ts +Download http://localhost:4545/vendor/non-existent.js +Ignoring: Dynamic import not found "http://localhost:4545/vendor/non-existent.js". + at http://localhost:4545/vendor/dynamic_non_existent.ts:4:16 +Vendored 1 module into vendor/ directory. + +To use vendored modules, specify the `--import-map vendor/import_map.json` flag when invoking Deno subcommands or add an `"importMap": ""` entry to a deno.json file. diff --git a/cli/tests/unit/console_test.ts b/cli/tests/unit/console_test.ts index ccc8638d25d355..239a8bf26c2b61 100644 --- a/cli/tests/unit/console_test.ts +++ b/cli/tests/unit/console_test.ts @@ -1395,7 +1395,8 @@ Deno.test(function consoleTable() { console.table({ a: "test", b: 1 }); assertEquals( stripColor(out.toString()), - `┌───────┬────────┐ + `\ +┌───────┬────────┐ │ (idx) │ Values │ ├───────┼────────┤ │ a │ "test" │ @@ -1408,12 +1409,28 @@ Deno.test(function consoleTable() { console.table({ a: { b: 10 }, b: { b: 20, c: 30 } }, ["c"]); assertEquals( stripColor(out.toString()), - `┌───────┬────┐ + `\ +┌───────┬────┐ │ (idx) │ c │ ├───────┼────┤ │ a │ │ │ b │ 30 │ └───────┴────┘ +`, + ); + }); + mockConsole((console, out) => { + console.table([[1, 1], [234, 2.34], [56789, 56.789]]); + assertEquals( + stripColor(out.toString()), + `\ +┌───────┬───────┬────────┐ +│ (idx) │ 0 │ 1 │ +├───────┼───────┼────────┤ +│ 0 │ 1 │ 1 │ +│ 1 │ 234 │ 2.34 │ +│ 2 │ 56789 │ 56.789 │ +└───────┴───────┴────────┘ `, ); }); @@ -1421,7 +1438,8 @@ Deno.test(function consoleTable() { console.table([1, 2, [3, [4]], [5, 6], [[7], [8]]]); assertEquals( stripColor(out.toString()), - `┌───────┬───────┬───────┬────────┐ + `\ +┌───────┬───────┬───────┬────────┐ │ (idx) │ 0 │ 1 │ Values │ ├───────┼───────┼───────┼────────┤ │ 0 │ │ │ 1 │ @@ -1437,7 +1455,8 @@ Deno.test(function consoleTable() { console.table(new Set([1, 2, 3, "test"])); assertEquals( stripColor(out.toString()), - `┌────────────┬────────┐ + `\ +┌────────────┬────────┐ │ (iter idx) │ Values │ ├────────────┼────────┤ │ 0 │ 1 │ @@ -1457,7 +1476,8 @@ Deno.test(function consoleTable() { ); assertEquals( stripColor(out.toString()), - `┌────────────┬─────┬────────┐ + `\ +┌────────────┬─────┬────────┐ │ (iter idx) │ Key │ Values │ ├────────────┼─────┼────────┤ │ 0 │ 1 │ "one" │ @@ -1476,7 +1496,8 @@ Deno.test(function consoleTable() { }); assertEquals( stripColor(out.toString()), - `┌───────┬───────────┬───────────────────┬────────┐ + `\ +┌───────┬───────────┬───────────────────┬────────┐ │ (idx) │ c │ e │ Values │ ├───────┼───────────┼───────────────────┼────────┤ │ a │ │ │ true │ @@ -1498,7 +1519,8 @@ Deno.test(function consoleTable() { ]); assertEquals( stripColor(out.toString()), - `┌───────┬────────┬──────────────────────┬────┬────────┐ + `\ +┌───────┬────────┬──────────────────────┬────┬────────┐ │ (idx) │ 0 │ 1 │ a │ Values │ ├───────┼────────┼──────────────────────┼────┼────────┤ │ 0 │ │ │ │ 1 │ @@ -1514,7 +1536,8 @@ Deno.test(function consoleTable() { console.table([]); assertEquals( stripColor(out.toString()), - `┌───────┐ + `\ +┌───────┐ │ (idx) │ ├───────┤ └───────┘ @@ -1525,7 +1548,8 @@ Deno.test(function consoleTable() { console.table({}); assertEquals( stripColor(out.toString()), - `┌───────┐ + `\ +┌───────┐ │ (idx) │ ├───────┤ └───────┘ @@ -1536,7 +1560,8 @@ Deno.test(function consoleTable() { console.table(new Set()); assertEquals( stripColor(out.toString()), - `┌────────────┐ + `\ +┌────────────┐ │ (iter idx) │ ├────────────┤ └────────────┘ @@ -1547,7 +1572,8 @@ Deno.test(function consoleTable() { console.table(new Map()); assertEquals( stripColor(out.toString()), - `┌────────────┐ + `\ +┌────────────┐ │ (iter idx) │ ├────────────┤ └────────────┘ @@ -1562,7 +1588,8 @@ Deno.test(function consoleTable() { console.table(["Hello", "你好", "Amapá"]); assertEquals( stripColor(out.toString()), - `┌───────┬─────────┐ + `\ +┌───────┬─────────┐ │ (idx) │ Values │ ├───────┼─────────┤ │ 0 │ "Hello" │ @@ -1579,7 +1606,8 @@ Deno.test(function consoleTable() { ]); assertEquals( stripColor(out.toString()), - `┌───────┬───┬───┐ + `\ +┌───────┬───┬───┐ │ (idx) │ 0 │ 1 │ ├───────┼───┼───┤ │ 0 │ 1 │ 2 │ @@ -1592,7 +1620,8 @@ Deno.test(function consoleTable() { console.table({ 1: { a: 4, b: 5 }, 2: null, 3: { b: 6, c: 7 } }, ["b"]); assertEquals( stripColor(out.toString()), - `┌───────┬───┐ + `\ +┌───────┬───┐ │ (idx) │ b │ ├───────┼───┤ │ 1 │ 5 │ @@ -1606,7 +1635,8 @@ Deno.test(function consoleTable() { console.table([{ a: 0 }, { a: 1, b: 1 }, { a: 2 }, { a: 3, b: 3 }]); assertEquals( stripColor(out.toString()), - `┌───────┬───┬───┐ + `\ +┌───────┬───┬───┐ │ (idx) │ a │ b │ ├───────┼───┼───┤ │ 0 │ 0 │ │ @@ -1624,7 +1654,8 @@ Deno.test(function consoleTable() { ); assertEquals( stripColor(out.toString()), - `┌───────┬───┬───┬───┐ + `\ +┌───────┬───┬───┬───┐ │ (idx) │ a │ b │ c │ ├───────┼───┼───┼───┤ │ 0 │ 0 │ │ │ diff --git a/cli/tests/unit/error_test.ts b/cli/tests/unit/error_test.ts index bb7a9baf85ff15..6fdf4f762968ee 100644 --- a/cli/tests/unit/error_test.ts +++ b/cli/tests/unit/error_test.ts @@ -15,6 +15,7 @@ Deno.test("Errors work", () => { assert(new Deno.errors.InvalidData("msg") instanceof Error); assert(new Deno.errors.TimedOut("msg") instanceof Error); assert(new Deno.errors.Interrupted("msg") instanceof Error); + assert(new Deno.errors.WouldBlock("msg") instanceof Error); assert(new Deno.errors.WriteZero("msg") instanceof Error); assert(new Deno.errors.UnexpectedEof("msg") instanceof Error); assert(new Deno.errors.BadResource("msg") instanceof Error); diff --git a/cli/tests/unit/ffi_test.ts b/cli/tests/unit/ffi_test.ts index 690b37afcb3b1e..65b257774d0b75 100644 --- a/cli/tests/unit/ffi_test.ts +++ b/cli/tests/unit/ffi_test.ts @@ -29,7 +29,8 @@ Deno.test({ permissions: { ffi: false } }, function ffiPermissionDenied() { Deno.dlopen("/usr/lib/libc.so.6", {}); }, Deno.errors.PermissionDenied); const fnptr = new Deno.UnsafeFnPointer( - 0n, + // @ts-expect-error: Not NonNullable but null check is after premissions check. + null, { parameters: ["u32", "pointer"], result: "void", @@ -41,7 +42,10 @@ Deno.test({ permissions: { ffi: false } }, function ffiPermissionDenied() { assertThrows(() => { Deno.UnsafePointer.of(new Uint8Array(0)); }, Deno.errors.PermissionDenied); - const ptrView = new Deno.UnsafePointerView(0n); + const ptrView = new Deno.UnsafePointerView( + // @ts-expect-error: Not NonNullable but null check is after premissions check. + null, + ); assertThrows(() => { ptrView.copyInto(new Uint8Array(0)); }, Deno.errors.PermissionDenied); diff --git a/cli/tests/unit/flash_test.ts b/cli/tests/unit/flash_test.ts index 98249385bbfeb7..ebee7a02417a02 100644 --- a/cli/tests/unit/flash_test.ts +++ b/cli/tests/unit/flash_test.ts @@ -89,12 +89,13 @@ Deno.test({ permissions: { net: true } }, async function httpServerBasic() { const listeningPromise = deferred(); const server = Deno.serve({ - handler: async (request) => { + handler: async (request, { remoteAddr }) => { // FIXME(bartlomieju): // make sure that request can be inspected console.log(request); assertEquals(new URL(request.url).href, "http://127.0.0.1:4501/"); assertEquals(await request.text(), ""); + assertEquals(remoteAddr.hostname, "127.0.0.1"); promise.resolve(); return new Response("Hello World", { headers: { "foo": "bar" } }); }, @@ -1701,7 +1702,7 @@ Deno.test( const server = Deno.serve({ handler: () => { promise.resolve(); - return new Response("foo bar baz"); + return new Response("NaN".repeat(100)); }, port: 4503, signal: ac.signal, @@ -1726,7 +1727,7 @@ Deno.test( assert(readResult); const msg = decoder.decode(buf.subarray(0, readResult)); - assert(msg.endsWith("Content-Length: 11\r\n\r\n")); + assert(msg.endsWith("Content-Length: 300\r\n\r\n")); conn.close(); diff --git a/cli/tests/unit/http_test.ts b/cli/tests/unit/http_test.ts index 331570a84e9f98..b8eb3be84257c5 100644 --- a/cli/tests/unit/http_test.ts +++ b/cli/tests/unit/http_test.ts @@ -14,6 +14,11 @@ import { } from "./test_util.ts"; import { join } from "../../../test_util/std/path/mod.ts"; +const { + buildCaseInsensitiveCommaValueFinder, + // @ts-expect-error TypeScript (as of 3.7) does not support indexing namespaces by symbol +} = Deno[Deno.internal]; + async function writeRequestAndReadResponse(conn: Deno.Conn): Promise { const encoder = new TextEncoder(); const decoder = new TextDecoder(); @@ -2610,6 +2615,30 @@ Deno.test({ }, }); +Deno.test("case insensitive comma value finder", async (t) => { + const cases = /** @type {[string, boolean][]} */ ([ + ["websocket", true], + ["wEbSOcKET", true], + [",wEbSOcKET", true], + [",wEbSOcKET,", true], + [", wEbSOcKET ,", true], + ["test, wEbSOcKET ,", true], + ["test ,\twEbSOcKET\t\t ,", true], + ["test , wEbSOcKET", true], + ["test, asdf,web,wEbSOcKET", true], + ["test, asdf,web,wEbSOcKETs", false], + ["test, asdf,awebsocket,wEbSOcKETs", false], + ]); + + const findValue = buildCaseInsensitiveCommaValueFinder("websocket"); + for (const [input, expected] of cases) { + await t.step(input.toString(), () => { + const actual = findValue(input); + assertEquals(actual, expected); + }); + } +}); + async function httpServerWithErrorBody( listener: Deno.Listener, compression: boolean, diff --git a/cli/tests/unit/read_file_test.ts b/cli/tests/unit/read_file_test.ts index 0d25ea6d859c13..24cf6dccf585ed 100644 --- a/cli/tests/unit/read_file_test.ts +++ b/cli/tests/unit/read_file_test.ts @@ -140,6 +140,17 @@ Deno.test( }, ); +// Test that AbortController's cancel handle is cleaned-up correctly, and do not leak resources. +Deno.test( + { permissions: { read: true } }, + async function readFileWithAbortSignalNotCalled() { + const ac = new AbortController(); + await Deno.readFile("cli/tests/testdata/assets/fixture.json", { + signal: ac.signal, + }); + }, +); + Deno.test( { permissions: { read: true }, ignore: Deno.build.os !== "linux" }, async function readFileProcFs() { diff --git a/cli/tests/unit/read_text_file_test.ts b/cli/tests/unit/read_text_file_test.ts index e78276dde88477..c40cb83e396caf 100644 --- a/cli/tests/unit/read_text_file_test.ts +++ b/cli/tests/unit/read_text_file_test.ts @@ -95,7 +95,7 @@ Deno.test( queueMicrotask(() => ac.abort()); const error = await assertRejects( async () => { - await Deno.readFile("cli/tests/testdata/assets/fixture.json", { + await Deno.readTextFile("cli/tests/testdata/assets/fixture.json", { signal: ac.signal, }); }, @@ -113,7 +113,7 @@ Deno.test( queueMicrotask(() => ac.abort(abortReason)); const error = await assertRejects( async () => { - await Deno.readFile("cli/tests/testdata/assets/fixture.json", { + await Deno.readTextFile("cli/tests/testdata/assets/fixture.json", { signal: ac.signal, }); }, @@ -128,7 +128,7 @@ Deno.test( const ac = new AbortController(); queueMicrotask(() => ac.abort("Some string")); try { - await Deno.readFile("cli/tests/testdata/assets/fixture.json", { + await Deno.readTextFile("cli/tests/testdata/assets/fixture.json", { signal: ac.signal, }); unreachable(); @@ -138,6 +138,17 @@ Deno.test( }, ); +// Test that AbortController's cancel handle is cleaned-up correctly, and do not leak resources. +Deno.test( + { permissions: { read: true } }, + async function readTextFileWithAbortSignalNotCalled() { + const ac = new AbortController(); + await Deno.readTextFile("cli/tests/testdata/assets/fixture.json", { + signal: ac.signal, + }); + }, +); + Deno.test( { permissions: { read: true }, ignore: Deno.build.os !== "linux" }, async function readTextFileProcFs() { diff --git a/cli/tests/unit/webgpu_test.ts b/cli/tests/unit/webgpu_test.ts index decceb0f91a3a1..2d98167cfdec77 100644 --- a/cli/tests/unit/webgpu_test.ts +++ b/cli/tests/unit/webgpu_test.ts @@ -217,6 +217,16 @@ Deno.test({ Deno.close(Number(resources[resources.length - 1])); }); +Deno.test({ + ignore: isWsl || isLinuxOrMacCI, +}, async function webgpuAdapterHasFeatures() { + const adapter = await navigator.gpu.requestAdapter(); + assert(adapter); + assert(adapter.features); + const resources = Object.keys(Deno.resources()); + Deno.close(Number(resources[resources.length - 1])); +}); + async function checkIsWsl() { return Deno.build.os === "linux" && await hasMicrosoftProcVersion(); diff --git a/cli/tests/unit/write_file_test.ts b/cli/tests/unit/write_file_test.ts index 78d0f5badbee79..5f1ffd7a636742 100644 --- a/cli/tests/unit/write_file_test.ts +++ b/cli/tests/unit/write_file_test.ts @@ -379,6 +379,22 @@ Deno.test( }, ); +// Test that AbortController's cancel handle is cleaned-up correctly, and do not leak resources. +Deno.test( + { permissions: { read: true, write: true } }, + async function writeFileWithAbortSignalNotCalled() { + const ac = new AbortController(); + const enc = new TextEncoder(); + const data = enc.encode("Hello"); + const filename = Deno.makeTempDirSync() + "/test.txt"; + await Deno.writeFile(filename, data, { signal: ac.signal }); + const dataRead = Deno.readFileSync(filename); + const dec = new TextDecoder("utf-8"); + const actual = dec.decode(dataRead); + assertEquals(actual, "Hello"); + }, +); + function assertNotExists(filename: string | URL) { if (pathExists(filename)) { throw new Error(`The file ${filename} exists.`); diff --git a/cli/tests/unit_node/child_process_test.ts b/cli/tests/unit_node/child_process_test.ts new file mode 100644 index 00000000000000..c6e2e3ef255419 --- /dev/null +++ b/cli/tests/unit_node/child_process_test.ts @@ -0,0 +1,577 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. + +import CP from "node:child_process"; +import { Buffer } from "node:buffer"; +import { + assert, + assertEquals, + assertExists, + assertNotStrictEquals, + assertStrictEquals, + assertStringIncludes, +} from "../../../test_util/std/testing/asserts.ts"; +import { Deferred, deferred } from "../../../test_util/std/async/deferred.ts"; +import * as path from "../../../test_util/std/path/mod.ts"; + +const { spawn, execFile, execFileSync, ChildProcess } = CP; + +function withTimeout(timeoutInMS: number): Deferred { + const promise = deferred(); + const timer = setTimeout(() => { + promise.reject("Timeout"); + }, timeoutInMS); + promise.then(() => { + clearTimeout(timer); + }); + return promise; +} + +// TODO(uki00a): Once Node.js's `parallel/test-child-process-spawn-error.js` works, this test case should be removed. +Deno.test("[node/child_process spawn] The 'error' event is emitted when no binary is found", async () => { + const promise = withTimeout(1000); + const childProcess = spawn("no-such-cmd"); + childProcess.on("error", (_err: Error) => { + // TODO(@bartlomieju) Assert an error message. + promise.resolve(); + }); + await promise; +}); + +Deno.test("[node/child_process spawn] The 'exit' event is emitted with an exit code after the child process ends", async () => { + const promise = withTimeout(3000); + const childProcess = spawn(Deno.execPath(), ["--help"], { + env: { NO_COLOR: "true" }, + }); + try { + let exitCode = null; + childProcess.on("exit", (code: number) => { + promise.resolve(); + exitCode = code; + }); + await promise; + assertStrictEquals(exitCode, 0); + assertStrictEquals(childProcess.exitCode, exitCode); + } finally { + childProcess.kill(); + childProcess.stdout?.destroy(); + childProcess.stderr?.destroy(); + } +}); + +Deno.test("[node/child_process disconnect] the method exists", async () => { + const promise = withTimeout(1000); + const childProcess = spawn(Deno.execPath(), ["--help"], { + env: { NO_COLOR: "true" }, + }); + try { + childProcess.disconnect(); + childProcess.on("exit", () => { + promise.resolve(); + }); + await promise; + } finally { + childProcess.kill(); + childProcess.stdout?.destroy(); + childProcess.stderr?.destroy(); + } +}); + +Deno.test({ + name: "[node/child_process spawn] Verify that stdin and stdout work", + fn: async () => { + const promise = withTimeout(3000); + const childProcess = spawn(Deno.execPath(), ["fmt", "-"], { + env: { NO_COLOR: "true" }, + stdio: ["pipe", "pipe"], + }); + try { + assert(childProcess.stdin, "stdin should be defined"); + assert(childProcess.stdout, "stdout should be defined"); + let data = ""; + childProcess.stdout.on("data", (chunk) => { + data += chunk; + }); + childProcess.stdin.write(" console.log('hello')", "utf-8"); + childProcess.stdin.end(); + childProcess.on("close", () => { + promise.resolve(); + }); + await promise; + assertStrictEquals(data, `console.log("hello");\n`); + } finally { + childProcess.kill(); + } + }, +}); + +Deno.test({ + name: "[node/child_process spawn] stdin and stdout with binary data", + fn: async () => { + const promise = withTimeout(10000); + const p = path.join( + path.dirname(path.fromFileUrl(import.meta.url)), + "./testdata/binary_stdio.js", + ); + const childProcess = spawn(Deno.execPath(), ["run", p], { + env: { NO_COLOR: "true" }, + stdio: ["pipe", "pipe"], + }); + try { + assert(childProcess.stdin, "stdin should be defined"); + assert(childProcess.stdout, "stdout should be defined"); + let data: Buffer; + childProcess.stdout.on("data", (chunk) => { + data = chunk; + }); + const buffer = new Uint8Array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9]); + childProcess.stdin.write(buffer); + childProcess.stdin.end(); + childProcess.on("close", () => { + promise.resolve(); + }); + await promise; + assertEquals(new Uint8Array(data!), buffer); + } finally { + childProcess.kill(); + } + }, +}); + +async function spawnAndGetEnvValue( + inputValue: string | number | boolean, +): Promise { + const promise = withTimeout(3000); + const env = spawn( + `"${Deno.execPath()}" eval -p "Deno.env.toObject().BAZ"`, + { + env: { BAZ: String(inputValue), NO_COLOR: "true" }, + shell: true, + }, + ); + try { + let envOutput = ""; + + assert(env.stdout); + env.on("error", (err: Error) => promise.reject(err)); + env.stdout.on("data", (data) => { + envOutput += data; + }); + env.on("close", () => { + promise.resolve(envOutput.trim()); + }); + return await promise; + } finally { + env.kill(); + } +} + +Deno.test({ + ignore: Deno.build.os === "windows", + name: + "[node/child_process spawn] Verify that environment values can be numbers", + async fn() { + const envOutputValue = await spawnAndGetEnvValue(42); + assertStrictEquals(envOutputValue, "42"); + }, +}); + +Deno.test({ + ignore: Deno.build.os === "windows", + name: + "[node/child_process spawn] Verify that environment values can be booleans", + async fn() { + const envOutputValue = await spawnAndGetEnvValue(false); + assertStrictEquals(envOutputValue, "false"); + }, +}); + +/* Start of ported part */ 3; +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Ported from Node 15.5.1 + +// TODO(uki00a): Remove this case once Node's `parallel/test-child-process-spawn-event.js` works. +Deno.test("[child_process spawn] 'spawn' event", async () => { + const timeout = withTimeout(3000); + const subprocess = spawn(Deno.execPath(), ["eval", "console.log('ok')"]); + + let didSpawn = false; + subprocess.on("spawn", function () { + didSpawn = true; + }); + + function mustNotBeCalled() { + timeout.reject(new Error("function should not have been called")); + } + + const promises = [] as Promise[]; + function mustBeCalledAfterSpawn() { + const promise = deferred(); + promises.push(promise); + return () => { + if (didSpawn) { + promise.resolve(); + } else { + promise.reject( + new Error("function should be called after the 'spawn' event"), + ); + } + }; + } + + subprocess.on("error", mustNotBeCalled); + subprocess.stdout!.on("data", mustBeCalledAfterSpawn()); + subprocess.stdout!.on("end", mustBeCalledAfterSpawn()); + subprocess.stdout!.on("close", mustBeCalledAfterSpawn()); + subprocess.stderr!.on("data", mustNotBeCalled); + subprocess.stderr!.on("end", mustBeCalledAfterSpawn()); + subprocess.stderr!.on("close", mustBeCalledAfterSpawn()); + subprocess.on("exit", mustBeCalledAfterSpawn()); + subprocess.on("close", mustBeCalledAfterSpawn()); + + try { + await Promise.race([Promise.all(promises), timeout]); + timeout.resolve(); + } finally { + subprocess.kill(); + } +}); + +// TODO(uki00a): Remove this case once Node's `parallel/test-child-process-spawn-shell.js` works. +Deno.test("[child_process spawn] Verify that a shell is executed", async () => { + const promise = withTimeout(3000); + const doesNotExist = spawn("does-not-exist", { shell: true }); + try { + assertNotStrictEquals(doesNotExist.spawnfile, "does-not-exist"); + doesNotExist.on("error", () => { + promise.reject("The 'error' event must not be emitted."); + }); + doesNotExist.on("exit", (code: number, signal: null) => { + assertStrictEquals(signal, null); + + if (Deno.build.os === "windows") { + assertStrictEquals(code, 1); // Exit code of cmd.exe + } else { + assertStrictEquals(code, 127); // Exit code of /bin/sh }); + } + + promise.resolve(); + }); + await promise; + } finally { + doesNotExist.kill(); + doesNotExist.stdout?.destroy(); + doesNotExist.stderr?.destroy(); + } +}); + +// TODO(uki00a): Remove this case once Node's `parallel/test-child-process-spawn-shell.js` works. +Deno.test({ + ignore: Deno.build.os === "windows", + name: "[node/child_process spawn] Verify that passing arguments works", + async fn() { + const promise = withTimeout(3000); + const echo = spawn("echo", ["foo"], { + shell: true, + }); + let echoOutput = ""; + + try { + assertStrictEquals( + echo.spawnargs[echo.spawnargs.length - 1].replace(/"/g, ""), + "echo foo", + ); + assert(echo.stdout); + echo.stdout.on("data", (data) => { + echoOutput += data; + }); + echo.on("close", () => { + assertStrictEquals(echoOutput.trim(), "foo"); + promise.resolve(); + }); + await promise; + } finally { + echo.kill(); + } + }, +}); + +// TODO(uki00a): Remove this case once Node's `parallel/test-child-process-spawn-shell.js` works. +Deno.test({ + ignore: Deno.build.os === "windows", + name: "[node/child_process spawn] Verity that shell features can be used", + async fn() { + const promise = withTimeout(3000); + const cmd = "echo bar | cat"; + const command = spawn(cmd, { + shell: true, + }); + try { + let commandOutput = ""; + + assert(command.stdout); + command.stdout.on("data", (data) => { + commandOutput += data; + }); + + command.on("close", () => { + assertStrictEquals(commandOutput.trim(), "bar"); + promise.resolve(); + }); + + await promise; + } finally { + command.kill(); + } + }, +}); + +// TODO(uki00a): Remove this case once Node's `parallel/test-child-process-spawn-shell.js` works. +Deno.test({ + ignore: Deno.build.os === "windows", + name: + "[node/child_process spawn] Verity that environment is properly inherited", + async fn() { + const promise = withTimeout(3000); + const env = spawn( + `"${Deno.execPath()}" eval -p "Deno.env.toObject().BAZ"`, + { + env: { BAZ: "buzz", NO_COLOR: "true" }, + shell: true, + }, + ); + try { + let envOutput = ""; + + assert(env.stdout); + env.on("error", (err: Error) => promise.reject(err)); + env.stdout.on("data", (data) => { + envOutput += data; + }); + env.on("close", () => { + assertStrictEquals(envOutput.trim(), "buzz"); + promise.resolve(); + }); + await promise; + } finally { + env.kill(); + } + }, +}); +/* End of ported part */ + +Deno.test({ + name: "[node/child_process execFile] Get stdout as a string", + async fn() { + let child: unknown; + const script = path.join( + path.dirname(path.fromFileUrl(import.meta.url)), + "./testdata/exec_file_text_output.js", + ); + const promise = new Promise((resolve, reject) => { + child = execFile(Deno.execPath(), ["run", script], (err, stdout) => { + if (err) reject(err); + else if (stdout) resolve(stdout as string); + else resolve(null); + }); + }); + try { + const stdout = await promise; + assertEquals(stdout, "Hello World!\n"); + } finally { + if (child instanceof ChildProcess) { + child.kill(); + } + } + }, +}); + +Deno.test({ + name: "[node/child_process execFile] Get stdout as a buffer", + async fn() { + let child: unknown; + const script = path.join( + path.dirname(path.fromFileUrl(import.meta.url)), + "./testdata/exec_file_text_output.js", + ); + const promise = new Promise((resolve, reject) => { + child = execFile( + Deno.execPath(), + ["run", script], + { encoding: "buffer" }, + (err, stdout) => { + if (err) reject(err); + else if (stdout) resolve(stdout as Buffer); + else resolve(null); + }, + ); + }); + try { + const stdout = await promise; + assert(Buffer.isBuffer(stdout)); + assertEquals(stdout.toString("utf8"), "Hello World!\n"); + } finally { + if (child instanceof ChildProcess) { + child.kill(); + } + } + }, +}); + +Deno.test({ + name: "[node/child_process execFile] Get stderr", + async fn() { + let child: unknown; + const script = path.join( + path.dirname(path.fromFileUrl(import.meta.url)), + "./testdata/exec_file_text_error.js", + ); + const promise = new Promise< + { err: Error | null; stderr?: string | Buffer } + >((resolve) => { + child = execFile(Deno.execPath(), ["run", script], (err, _, stderr) => { + resolve({ err, stderr }); + }); + }); + try { + const { err, stderr } = await promise; + if (child instanceof ChildProcess) { + assertEquals(child.exitCode, 1); + assertEquals(stderr, "yikes!\n"); + } else { + throw err; + } + } finally { + if (child instanceof ChildProcess) { + child.kill(); + } + } + }, +}); + +Deno.test({ + name: "[node/child_process execFile] Exceed given maxBuffer limit", + async fn() { + let child: unknown; + const script = path.join( + path.dirname(path.fromFileUrl(import.meta.url)), + "./testdata/exec_file_text_error.js", + ); + const promise = new Promise< + { err: Error | null; stderr?: string | Buffer } + >((resolve) => { + child = execFile(Deno.execPath(), ["run", script], { + encoding: "buffer", + maxBuffer: 3, + }, (err, _, stderr) => { + resolve({ err, stderr }); + }); + }); + try { + const { err, stderr } = await promise; + if (child instanceof ChildProcess) { + assert(err); + assertEquals( + // deno-lint-ignore no-explicit-any + (err as any).code, + "ERR_CHILD_PROCESS_STDIO_MAXBUFFER", + ); + assertEquals(err.message, "stderr maxBuffer length exceeded"); + assertEquals((stderr as Buffer).toString("utf8"), "yik"); + } else { + throw err; + } + } finally { + if (child instanceof ChildProcess) { + child.kill(); + } + } + }, +}); + +Deno.test({ + name: "[node/child_process] ChildProcess.kill()", + async fn() { + const script = path.join( + path.dirname(path.fromFileUrl(import.meta.url)), + "./testdata/infinite_loop.js", + ); + const childProcess = spawn(Deno.execPath(), ["run", script]); + const p = withTimeout(3000); + childProcess.on("exit", () => p.resolve()); + childProcess.kill("SIGKILL"); + await p; + assert(childProcess.killed); + assertEquals(childProcess.signalCode, "SIGKILL"); + assertExists(childProcess.exitCode); + }, +}); + +Deno.test({ + name: "[node/child_process] ChildProcess.unref()", + async fn() { + const script = path.join( + path.dirname(path.fromFileUrl(import.meta.url)), + "testdata", + "child_process_unref.js", + ); + const childProcess = spawn(Deno.execPath(), [ + "run", + "-A", + "--unstable", + script, + ]); + const p = deferred(); + childProcess.on("exit", () => p.resolve()); + await p; + }, +}); + +Deno.test({ + name: "[node/child_process] child_process.fork", + async fn() { + const testdataDir = path.join( + path.dirname(path.fromFileUrl(import.meta.url)), + "testdata", + ); + const script = path.join( + testdataDir, + "node_modules", + "foo", + "index.js", + ); + const p = deferred(); + const cp = CP.fork(script, [], { cwd: testdataDir, stdio: "pipe" }); + let output = ""; + cp.on("close", () => p.resolve()); + cp.stdout?.on("data", (data) => { + output += data; + }); + await p; + assertEquals(output, "foo\ntrue\ntrue\ntrue\n"); + }, +}); + +Deno.test("[node/child_process execFileSync] 'inherit' stdout and stderr", () => { + execFileSync(Deno.execPath(), ["--help"], { stdio: "inherit" }); +}); + +Deno.test( + "[node/child_process spawn] supports windowsVerbatimArguments option", + { ignore: Deno.build.os !== "windows" }, + async () => { + const cmdFinished = deferred(); + let output = ""; + const cp = spawn("cmd", ["/d", "/s", "/c", '"deno ^"--version^""'], { + stdio: "pipe", + windowsVerbatimArguments: true, + }); + cp.on("close", () => cmdFinished.resolve()); + cp.stdout?.on("data", (data) => { + output += data; + }); + await cmdFinished; + assertStringIncludes(output, "deno"); + assertStringIncludes(output, "v8"); + assertStringIncludes(output, "typescript"); + }, +); diff --git a/cli/tests/unit_node/crypto_cipher_test.ts b/cli/tests/unit_node/crypto_cipher_test.ts new file mode 100644 index 00000000000000..e3bd7ca26216cf --- /dev/null +++ b/cli/tests/unit_node/crypto_cipher_test.ts @@ -0,0 +1,50 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. +import crypto from "node:crypto"; +import { Buffer } from "node:buffer"; +import { + assertEquals, + assertThrows, +} from "../../../test_util/std/testing/asserts.ts"; + +const rsaPrivateKey = Deno.readTextFileSync( + new URL("./testdata/rsa_private.pem", import.meta.url), +); +const rsaPublicKey = Deno.readTextFileSync( + new URL("./testdata/rsa_public.pem", import.meta.url), +); + +const input = new TextEncoder().encode("hello world"); + +Deno.test({ + name: "rsa public encrypt and private decrypt", + fn() { + const encrypted = crypto.publicEncrypt(Buffer.from(rsaPublicKey), input); + const decrypted = crypto.privateDecrypt( + Buffer.from(rsaPrivateKey), + Buffer.from(encrypted), + ); + assertEquals(decrypted, input); + }, +}); + +Deno.test({ + name: "rsa private encrypt and private decrypt", + fn() { + const encrypted = crypto.privateEncrypt(rsaPrivateKey, input); + const decrypted = crypto.privateDecrypt( + rsaPrivateKey, + Buffer.from(encrypted), + ); + assertEquals(decrypted, input); + }, +}); + +Deno.test({ + name: "rsa public decrypt fail", + fn() { + const encrypted = crypto.publicEncrypt(rsaPublicKey, input); + assertThrows(() => + crypto.publicDecrypt(rsaPublicKey, Buffer.from(encrypted)) + ); + }, +}); diff --git a/cli/tests/unit_node/process_test.ts b/cli/tests/unit_node/process_test.ts new file mode 100644 index 00000000000000..7310e4ad7c003d --- /dev/null +++ b/cli/tests/unit_node/process_test.ts @@ -0,0 +1,713 @@ +// deno-lint-ignore-file no-undef +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. + +import process, { argv, env } from "node:process"; +import { + assert, + assertEquals, + assertFalse, + assertObjectMatch, + assertStrictEquals, + assertThrows, +} from "../../../test_util/std/testing/asserts.ts"; +import { stripColor } from "../../../test_util/std/fmt/colors.ts"; +import { deferred } from "../../../test_util/std/async/deferred.ts"; +import * as path from "../../../test_util/std/path/mod.ts"; +import { delay } from "../../../test_util/std/async/delay.ts"; + +const testDir = new URL(".", import.meta.url); + +Deno.test({ + name: "process.cwd and process.chdir success", + fn() { + assertEquals(process.cwd(), Deno.cwd()); + + const currentDir = Deno.cwd(); + + const tempDir = Deno.makeTempDirSync(); + process.chdir(tempDir); + assertEquals( + Deno.realPathSync(process.cwd()), + Deno.realPathSync(tempDir), + ); + + process.chdir(currentDir); + }, +}); + +Deno.test({ + name: "process.chdir failure", + fn() { + assertThrows( + () => { + process.chdir("non-existent-directory-name"); + }, + Deno.errors.NotFound, + "file", + // On every OS Deno returns: "No such file" except for Windows, where it's: + // "The system cannot find the file specified. (os error 2)" so "file" is + // the only common string here. + ); + }, +}); + +Deno.test({ + name: "process.version", + fn() { + assertEquals(typeof process, "object"); + assertEquals(typeof process.version, "string"); + assertEquals(typeof process.versions, "object"); + assertEquals(typeof process.versions.node, "string"); + assertEquals(typeof process.versions.v8, "string"); + assertEquals(typeof process.versions.uv, "string"); + assertEquals(typeof process.versions.zlib, "string"); + assertEquals(typeof process.versions.brotli, "string"); + assertEquals(typeof process.versions.ares, "string"); + assertEquals(typeof process.versions.modules, "string"); + assertEquals(typeof process.versions.nghttp2, "string"); + assertEquals(typeof process.versions.napi, "string"); + assertEquals(typeof process.versions.llhttp, "string"); + assertEquals(typeof process.versions.openssl, "string"); + assertEquals(typeof process.versions.cldr, "string"); + assertEquals(typeof process.versions.icu, "string"); + assertEquals(typeof process.versions.tz, "string"); + assertEquals(typeof process.versions.unicode, "string"); + // These two are not present in `process.versions` in Node, but we + // add them anyway + assertEquals(typeof process.versions.deno, "string"); + assertEquals(typeof process.versions.typescript, "string"); + }, +}); + +Deno.test({ + name: "process.platform", + fn() { + assertEquals(typeof process.platform, "string"); + }, +}); + +Deno.test({ + name: "process.mainModule", + fn() { + assertEquals(process.mainModule, undefined); + // Check that it is writable + // @ts-ignore these are deprecated now + process.mainModule = "foo"; + // @ts-ignore these are deprecated now + assertEquals(process.mainModule, "foo"); + }, +}); + +Deno.test({ + name: "process.arch", + fn() { + assertEquals(typeof process.arch, "string"); + if (Deno.build.arch == "x86_64") { + assertEquals(process.arch, "x64"); + } else if (Deno.build.arch == "aarch64") { + assertEquals(process.arch, "arm64"); + } else { + throw new Error("unreachable"); + } + }, +}); + +Deno.test({ + name: "process.pid", + fn() { + assertEquals(typeof process.pid, "number"); + assertEquals(process.pid, Deno.pid); + }, +}); + +Deno.test({ + name: "process.on", + async fn() { + assertEquals(typeof process.on, "function"); + + let triggered = false; + process.on("exit", () => { + triggered = true; + }); + // @ts-ignore fix the type here + process.emit("exit"); + assert(triggered); + + const cwd = path.dirname(path.fromFileUrl(import.meta.url)); + + const command = new Deno.Command(Deno.execPath(), { + args: [ + "run", + "--quiet", + "--unstable", + "./testdata/process_exit.ts", + ], + cwd, + }); + const { stdout } = await command.output(); + + const decoder = new TextDecoder(); + assertEquals(stripColor(decoder.decode(stdout).trim()), "1\n2"); + }, +}); + +Deno.test({ + name: "process.on signal", + ignore: Deno.build.os == "windows", + async fn() { + const promise = deferred(); + let c = 0; + const listener = () => { + c += 1; + }; + process.on("SIGINT", listener); + setTimeout(async () => { + // Sends SIGINT 3 times. + for (const _ of Array(3)) { + await delay(20); + Deno.kill(Deno.pid, "SIGINT"); + } + await delay(20); + Deno.removeSignalListener("SIGINT", listener); + promise.resolve(); + }); + await promise; + assertEquals(c, 3); + }, +}); + +Deno.test({ + name: "process.off signal", + ignore: Deno.build.os == "windows", + async fn() { + const promise = deferred(); + let c = 0; + const listener = () => { + c += 1; + process.off("SIGINT", listener); + }; + process.on("SIGINT", listener); + setTimeout(async () => { + // Sends SIGINT 3 times. + for (const _ of Array(3)) { + await delay(20); + Deno.kill(Deno.pid, "SIGINT"); + } + await delay(20); + promise.resolve(); + }); + await promise; + assertEquals(c, 1); + }, +}); + +Deno.test({ + name: "process.on SIGBREAK doesn't throw", + fn() { + const listener = () => {}; + process.on("SIGBREAK", listener); + process.off("SIGBREAK", listener); + }, +}); + +Deno.test({ + name: "process.on SIGTERM doesn't throw on windows", + ignore: Deno.build.os !== "windows", + fn() { + const listener = () => {}; + process.on("SIGTERM", listener); + process.off("SIGTERM", listener); + }, +}); + +Deno.test({ + name: "process.argv", + fn() { + assert(Array.isArray(argv)); + assert(Array.isArray(process.argv)); + assert( + process.argv[0].match(/[^/\\]*deno[^/\\]*$/), + "deno included in the file name of argv[0]", + ); + assertEquals( + process.argv[1], + path.fromFileUrl(Deno.mainModule), + ); + // argv supports array methods. + assert(Array.isArray(process.argv.slice(2))); + assertEquals(process.argv.indexOf(Deno.execPath()), 0); + assertEquals(process.argv.indexOf(path.fromFileUrl(Deno.mainModule)), 1); + }, +}); + +Deno.test({ + name: "process.execArgv", + fn() { + assert(Array.isArray(process.execArgv)); + assert(process.execArgv.length == 0); + // execArgv supports array methods. + assert(Array.isArray(process.argv.slice(0))); + assertEquals(process.argv.indexOf("foo"), -1); + }, +}); + +Deno.test({ + name: "process.env", + fn() { + Deno.env.set("HELLO", "WORLD"); + + assertObjectMatch(process.env, Deno.env.toObject()); + + assertEquals(typeof (process.env.HELLO), "string"); + assertEquals(process.env.HELLO, "WORLD"); + + assertEquals(typeof env.HELLO, "string"); + assertEquals(env.HELLO, "WORLD"); + + assert(Object.getOwnPropertyNames(process.env).includes("HELLO")); + assert(Object.keys(process.env).includes("HELLO")); + + assert(Object.prototype.hasOwnProperty.call(process.env, "HELLO")); + assert( + !Object.prototype.hasOwnProperty.call( + process.env, + "SURELY_NON_EXISTENT_VAR", + ), + ); + + // deno-lint-ignore no-prototype-builtins + assert(process.env.hasOwnProperty("HELLO")); + assert("HELLO" in process.env); + assert(Object.keys(process.env.valueOf()).includes("HELLO")); + + assertEquals(process.env.toString(), "[object Object]"); + assertEquals(process.env.toLocaleString(), "[object Object]"); + + // should not error when assigning false to an env var + process.env.HELLO = false as unknown as string; + assertEquals(process.env.HELLO, "false"); + process.env.HELLO = "WORLD"; + assertEquals(process.env.HELLO, "WORLD"); + }, +}); + +Deno.test({ + name: "process.env requires scoped env permission", + permissions: { env: ["FOO"] }, + fn() { + Deno.env.set("FOO", "1"); + assert("FOO" in process.env); + assertFalse("BAR" in process.env); + assert(Object.hasOwn(process.env, "FOO")); + assertFalse(Object.hasOwn(process.env, "BAR")); + }, +}); + +Deno.test({ + name: "process.env doesn't throw with invalid env var names", + fn() { + assertEquals(process.env[""], undefined); + assertEquals(process.env["\0"], undefined); + assertEquals(process.env["=c:"], undefined); + assertFalse(Object.hasOwn(process.env, "")); + assertFalse(Object.hasOwn(process.env, "\0")); + assertFalse(Object.hasOwn(process.env, "=c:")); + assertFalse("" in process.env); + assertFalse("\0" in process.env); + assertFalse("=c:" in process.env); + }, +}); + +Deno.test({ + name: "process.stdin", + fn() { + assertEquals(process.stdin.fd, Deno.stdin.rid); + assertEquals(process.stdin.isTTY, Deno.isatty(Deno.stdin.rid)); + }, +}); + +Deno.test({ + name: "process.stdin readable with a TTY", + // TODO(PolarETech): Run this test even in non tty environment + ignore: !Deno.isatty(Deno.stdin.rid), + async fn() { + const promise = deferred(); + const expected = ["foo", "bar", null, "end"]; + const data: (string | null)[] = []; + + process.stdin.setEncoding("utf8"); + process.stdin.on("readable", () => { + data.push(process.stdin.read()); + }); + process.stdin.on("end", () => { + data.push("end"); + }); + + process.stdin.push("foo"); + process.nextTick(() => { + process.stdin.push("bar"); + process.nextTick(() => { + process.stdin.push(null); + promise.resolve(); + }); + }); + + await promise; + assertEquals(process.stdin.readableHighWaterMark, 0); + assertEquals(data, expected); + }, +}); + +Deno.test({ + name: "process.stdin readable with piping a file", + async fn() { + const expected = ["65536", "foo", "bar", "null", "end"]; + const scriptPath = "./testdata/process_stdin.ts"; + const filePath = "./testdata/process_stdin_dummy.txt"; + + const shell = Deno.build.os === "windows" ? "cmd.exe" : "/bin/sh"; + const cmd = `"${Deno.execPath()}" run ${scriptPath} < ${filePath}`; + const args = Deno.build.os === "windows" ? ["/d", "/c", cmd] : ["-c", cmd]; + + const p = new Deno.Command(shell, { + args, + stdin: "null", + stdout: "piped", + stderr: "null", + windowsRawArguments: true, + cwd: testDir, + }); + + const { stdout } = await p.output(); + const data = new TextDecoder().decode(stdout).trim().split("\n"); + assertEquals(data, expected); + }, +}); + +Deno.test({ + name: "process.stdin readable with piping a stream", + async fn() { + const expected = ["16384", "foo", "bar", "null", "end"]; + const scriptPath = "./testdata/process_stdin.ts"; + + const command = new Deno.Command(Deno.execPath(), { + args: ["run", scriptPath], + stdin: "piped", + stdout: "piped", + stderr: "null", + cwd: testDir, + }); + const child = command.spawn(); + + const writer = await child.stdin.getWriter(); + writer.ready + .then(() => writer.write(new TextEncoder().encode("foo\nbar"))) + .then(() => writer.releaseLock()) + .then(() => child.stdin.close()); + + const { stdout } = await child.output(); + const data = new TextDecoder().decode(stdout).trim().split("\n"); + assertEquals(data, expected); + }, +}); + +Deno.test({ + name: "process.stdin readable with piping a socket", + ignore: Deno.build.os === "windows", + async fn() { + const expected = ["16384", "foo", "bar", "null", "end"]; + const scriptPath = "./testdata/process_stdin.ts"; + + const listener = Deno.listen({ hostname: "127.0.0.1", port: 9000 }); + listener.accept().then(async (conn) => { + await conn.write(new TextEncoder().encode("foo\nbar")); + conn.close(); + listener.close(); + }); + + const shell = "/bin/bash"; + const cmd = + `"${Deno.execPath()}" run ${scriptPath} < /dev/tcp/127.0.0.1/9000`; + const args = ["-c", cmd]; + + const p = new Deno.Command(shell, { + args, + stdin: "null", + stdout: "piped", + stderr: "null", + cwd: testDir, + }); + + const { stdout } = await p.output(); + const data = new TextDecoder().decode(stdout).trim().split("\n"); + assertEquals(data, expected); + }, +}); + +Deno.test({ + name: "process.stdin readable with null", + async fn() { + const expected = ["65536", "null", "end"]; + const scriptPath = "./testdata/process_stdin.ts"; + + const command = new Deno.Command(Deno.execPath(), { + args: ["run", scriptPath], + stdin: "null", + stdout: "piped", + stderr: "null", + cwd: testDir, + }); + + const { stdout } = await command.output(); + const data = new TextDecoder().decode(stdout).trim().split("\n"); + assertEquals(data, expected); + }, +}); + +// TODO(kt3k): Enable this test case. 'readable' event handler in +// `process_stdin.ts` doesn't work now +Deno.test({ + name: "process.stdin readable with unsuitable stdin", + ignore: true, + // // TODO(PolarETech): Prepare a similar test that can be run on Windows + // ignore: Deno.build.os === "windows", + async fn() { + const expected = ["16384", "null", "end"]; + const scriptPath = "./testdata/process_stdin.ts"; + const directoryPath = "./testdata/"; + + const shell = "/bin/bash"; + const cmd = `"${Deno.execPath()}" run ${scriptPath} < ${directoryPath}`; + const args = ["-c", cmd]; + + const p = new Deno.Command(shell, { + args, + stdin: "null", + stdout: "piped", + stderr: "null", + windowsRawArguments: true, + cwd: testDir, + }); + + const { stdout } = await p.output(); + const data = new TextDecoder().decode(stdout).trim().split("\n"); + assertEquals(data, expected); + }, +}); + +Deno.test({ + name: "process.stdout", + fn() { + assertEquals(process.stdout.fd, Deno.stdout.rid); + const isTTY = Deno.isatty(Deno.stdout.rid); + assertEquals(process.stdout.isTTY, isTTY); + const consoleSize = isTTY ? Deno.consoleSize() : undefined; + assertEquals(process.stdout.columns, consoleSize?.columns); + assertEquals(process.stdout.rows, consoleSize?.rows); + assertEquals( + `${process.stdout.getWindowSize()}`, + `${consoleSize && [consoleSize.columns, consoleSize.rows]}`, + ); + + if (isTTY) { + assertStrictEquals(process.stdout.cursorTo(1, 2, () => {}), true); + assertStrictEquals(process.stdout.moveCursor(3, 4, () => {}), true); + assertStrictEquals(process.stdout.clearLine(1, () => {}), true); + assertStrictEquals(process.stdout.clearScreenDown(() => {}), true); + } else { + assertStrictEquals(process.stdout.cursorTo, undefined); + assertStrictEquals(process.stdout.moveCursor, undefined); + assertStrictEquals(process.stdout.clearLine, undefined); + assertStrictEquals(process.stdout.clearScreenDown, undefined); + } + }, +}); + +Deno.test({ + name: "process.stderr", + fn() { + assertEquals(process.stderr.fd, Deno.stderr.rid); + const isTTY = Deno.isatty(Deno.stderr.rid); + assertEquals(process.stderr.isTTY, isTTY); + const consoleSize = isTTY ? Deno.consoleSize() : undefined; + assertEquals(process.stderr.columns, consoleSize?.columns); + assertEquals(process.stderr.rows, consoleSize?.rows); + assertEquals( + `${process.stderr.getWindowSize()}`, + `${consoleSize && [consoleSize.columns, consoleSize.rows]}`, + ); + + if (isTTY) { + assertStrictEquals(process.stderr.cursorTo(1, 2, () => {}), true); + assertStrictEquals(process.stderr.moveCursor(3, 4, () => {}), true); + assertStrictEquals(process.stderr.clearLine(1, () => {}), true); + assertStrictEquals(process.stderr.clearScreenDown(() => {}), true); + } else { + assertStrictEquals(process.stderr.cursorTo, undefined); + assertStrictEquals(process.stderr.moveCursor, undefined); + assertStrictEquals(process.stderr.clearLine, undefined); + assertStrictEquals(process.stderr.clearScreenDown, undefined); + } + }, +}); + +Deno.test({ + name: "process.nextTick", + async fn() { + let withoutArguments = false; + process.nextTick(() => { + withoutArguments = true; + }); + + const expected = 12; + let result; + process.nextTick((x: number) => { + result = x; + }, 12); + + await delay(10); + assert(withoutArguments); + assertEquals(result, expected); + }, +}); + +Deno.test({ + name: "process.hrtime", + // TODO(kt3k): Enable this test + ignore: true, + fn() { + const [sec0, nano0] = process.hrtime(); + // seconds and nano seconds are positive integers. + assert(sec0 > 0); + assert(Number.isInteger(sec0)); + assert(nano0 > 0); + assert(Number.isInteger(nano0)); + + const [sec1, nano1] = process.hrtime(); + // the later call returns bigger value + assert(sec1 >= sec0); + assert(nano1 > nano0); + + const [sec2, nano2] = process.hrtime([sec1, nano1]); + // the difference of the 2 calls is a small positive value. + assertEquals(sec2, 0); + assert(nano2 > 0); + }, +}); + +Deno.test({ + name: "process.hrtime.bigint", + fn() { + const time = process.hrtime.bigint(); + assertEquals(typeof time, "bigint"); + assert(time > 0n); + }, +}); + +Deno.test("process.on, process.off, process.removeListener doesn't throw on unimplemented events", () => { + const events = [ + "beforeExit", + "disconnect", + "message", + "multipleResolves", + "rejectionHandled", + "uncaughtException", + "uncaughtExceptionMonitor", + "unhandledRejection", + "worker", + ]; + const handler = () => {}; + events.forEach((ev) => { + process.on(ev, handler); + assertEquals(process.listenerCount(ev), 1); + process.off(ev, handler); + assertEquals(process.listenerCount(ev), 0); + process.on(ev, handler); + assertEquals(process.listenerCount(ev), 1); + process.removeListener(ev, handler); + assertEquals(process.listenerCount(ev), 0); + }); +}); + +Deno.test("process.memoryUsage()", () => { + const mem = process.memoryUsage(); + assert(typeof mem.rss === "number"); + assert(typeof mem.heapTotal === "number"); + assert(typeof mem.heapUsed === "number"); + assert(typeof mem.external === "number"); + assert(typeof mem.arrayBuffers === "number"); + assertEquals(mem.arrayBuffers, 0); +}); + +Deno.test("process.memoryUsage.rss()", () => { + const rss = process.memoryUsage.rss(); + assert(typeof rss === "number"); +}); + +Deno.test("process.exitCode", () => { + assert(process.exitCode === undefined); + process.exitCode = 127; + assert(process.exitCode === 127); +}); + +Deno.test("process.config", () => { + assert(process.config !== undefined); + assert(process.config.target_defaults !== undefined); + assert(process.config.variables !== undefined); +}); + +Deno.test("process._exiting", () => { + // @ts-ignore fix the type here + assert(process._exiting === false); +}); + +Deno.test("process.execPath", () => { + assertEquals(process.execPath, process.argv[0]); +}); + +Deno.test("process.execPath is writable", () => { + // pnpm writes to process.execPath + // https://github.com/pnpm/pnpm/blob/67d8b65d2e8da1df3725034b8c5b1fcf3af4ad81/packages/config/src/index.ts#L175 + const originalExecPath = process.execPath; + try { + process.execPath = "/path/to/node"; + assertEquals(process.execPath, "/path/to/node"); + } finally { + process.execPath = originalExecPath; + } +}); + +Deno.test("process.getgid", () => { + if (Deno.build.os === "windows") { + assertEquals(process.getgid, undefined); + } else { + assertEquals(process.getgid?.(), Deno.gid()); + } +}); + +Deno.test("process.getuid", () => { + if (Deno.build.os === "windows") { + assertEquals(process.getuid, undefined); + } else { + assertEquals(process.getuid?.(), Deno.uid()); + } +}); + +Deno.test({ + name: "process.exit", + async fn() { + const command = new Deno.Command(Deno.execPath(), { + args: [ + "run", + "--quiet", + "--unstable", + "./testdata/process_exit2.ts", + ], + cwd: testDir, + }); + const { stdout } = await command.output(); + + const decoder = new TextDecoder(); + assertEquals(stripColor(decoder.decode(stdout).trim()), "exit"); + }, +}); diff --git a/cli/tests/unit_node/testdata/binary_stdio.js b/cli/tests/unit_node/testdata/binary_stdio.js new file mode 100644 index 00000000000000..aa370a933cc263 --- /dev/null +++ b/cli/tests/unit_node/testdata/binary_stdio.js @@ -0,0 +1,11 @@ +const buffer = new Uint8Array(10); +const nread = await Deno.stdin.read(buffer); + +if (nread != 10) { + throw new Error("Too little data read"); +} + +const nwritten = await Deno.stdout.write(buffer); +if (nwritten != 10) { + throw new Error("Too little data written"); +} diff --git a/cli/tests/unit_node/testdata/child_process_unref.js b/cli/tests/unit_node/testdata/child_process_unref.js new file mode 100644 index 00000000000000..cc7815d97c7943 --- /dev/null +++ b/cli/tests/unit_node/testdata/child_process_unref.js @@ -0,0 +1,9 @@ +import cp from "node:child_process"; +import * as path from "node:path"; + +const script = path.join( + path.dirname(path.fromFileUrl(import.meta.url)), + "infinite_loop.js", +); +const childProcess = cp.spawn(Deno.execPath(), ["run", script]); +childProcess.unref(); diff --git a/cli/tests/unit_node/testdata/exec_file_text_error.js b/cli/tests/unit_node/testdata/exec_file_text_error.js new file mode 100644 index 00000000000000..9697e60442b8f5 --- /dev/null +++ b/cli/tests/unit_node/testdata/exec_file_text_error.js @@ -0,0 +1,2 @@ +console.error("yikes!"); +Deno.exit(1); diff --git a/cli/tests/unit_node/testdata/exec_file_text_output.js b/cli/tests/unit_node/testdata/exec_file_text_output.js new file mode 100644 index 00000000000000..019c0f4bc8e76a --- /dev/null +++ b/cli/tests/unit_node/testdata/exec_file_text_output.js @@ -0,0 +1 @@ +console.log("Hello World!"); diff --git a/cli/tests/unit_node/testdata/infinite_loop.js b/cli/tests/unit_node/testdata/infinite_loop.js new file mode 100644 index 00000000000000..0e6540a7b79b3f --- /dev/null +++ b/cli/tests/unit_node/testdata/infinite_loop.js @@ -0,0 +1,3 @@ +while (true) { + await new Promise((resolve) => setTimeout(resolve, 1000)); +} diff --git a/cli/tests/unit_node/testdata/node_modules/foo/index.js b/cli/tests/unit_node/testdata/node_modules/foo/index.js new file mode 100644 index 00000000000000..24faba789a562f --- /dev/null +++ b/cli/tests/unit_node/testdata/node_modules/foo/index.js @@ -0,0 +1,4 @@ +console.log("foo"); +console.log(typeof require === "function"); +console.log(typeof module === "object"); +console.log(typeof exports === "object"); diff --git a/cli/tests/unit_node/testdata/node_modules/foo/package.json b/cli/tests/unit_node/testdata/node_modules/foo/package.json new file mode 100644 index 00000000000000..bde99de9287a49 --- /dev/null +++ b/cli/tests/unit_node/testdata/node_modules/foo/package.json @@ -0,0 +1,3 @@ +{ + "name": "foo" +} diff --git a/cli/tests/unit_node/testdata/process_exit.ts b/cli/tests/unit_node/testdata/process_exit.ts new file mode 100644 index 00000000000000..57351c08703972 --- /dev/null +++ b/cli/tests/unit_node/testdata/process_exit.ts @@ -0,0 +1,19 @@ +import process from "node:process"; + +//deno-lint-ignore no-undef +process.on("exit", () => { + console.log(1); +}); + +function unexpected() { + console.log(null); +} +//deno-lint-ignore no-undef +process.on("exit", unexpected); +//deno-lint-ignore no-undef +process.removeListener("exit", unexpected); + +//deno-lint-ignore no-undef +process.on("exit", () => { + console.log(2); +}); diff --git a/cli/tests/unit_node/testdata/process_exit2.ts b/cli/tests/unit_node/testdata/process_exit2.ts new file mode 100644 index 00000000000000..3731f745aed585 --- /dev/null +++ b/cli/tests/unit_node/testdata/process_exit2.ts @@ -0,0 +1,4 @@ +import process from "node:process"; + +process.on("exit", () => console.log("exit")); +process.exit(); diff --git a/cli/tests/unit_node/testdata/process_stdin.ts b/cli/tests/unit_node/testdata/process_stdin.ts new file mode 100644 index 00000000000000..23562b090ad237 --- /dev/null +++ b/cli/tests/unit_node/testdata/process_stdin.ts @@ -0,0 +1,11 @@ +import process from "node:process"; + +console.log(process.stdin.readableHighWaterMark); + +process.stdin.setEncoding("utf8"); +process.stdin.on("readable", () => { + console.log(process.stdin.read()); +}); +process.stdin.on("end", () => { + console.log("end"); +}); diff --git a/cli/tests/unit_node/testdata/process_stdin_dummy.txt b/cli/tests/unit_node/testdata/process_stdin_dummy.txt new file mode 100644 index 00000000000000..a907ec3f431eeb --- /dev/null +++ b/cli/tests/unit_node/testdata/process_stdin_dummy.txt @@ -0,0 +1,2 @@ +foo +bar \ No newline at end of file diff --git a/cli/tests/unit_node/testdata/rsa_private.pem b/cli/tests/unit_node/testdata/rsa_private.pem new file mode 100644 index 00000000000000..cd274ae6d9071d --- /dev/null +++ b/cli/tests/unit_node/testdata/rsa_private.pem @@ -0,0 +1,28 @@ +-----BEGIN PRIVATE KEY----- +MIIEvwIBADANBgkqhkiG9w0BAQEFAASCBKkwggSlAgEAAoIBAQC33FiIiiexwLe/ +P8DZx5HsqFlmUO7/lvJ7necJVNwqdZ3ax5jpQB0p6uxfqeOvzcN3k5V7UFb/Am+n +kSNZMAZhsWzCU2Z4Pjh50QYz3f0Hour7/yIGStOLyYY3hgLK2K8TbhgjQPhdkw9+ +QtKlpvbL8fLgONAoGrVOFnRQGcr70iFffsm79mgZhKVMgYiHPJqJgGHvCtkGg9zM +gS7p63+Q3ZWedtFS2RhMX3uCBy/mH6EOlRCNBbRmA4xxNzyf5GQaki3T+Iz9tOMj +dPP+CwV2LqEdylmBuik8vrfTb3qIHLKKBAI8lXN26wWtA3kN4L7NP+cbKlCRlqct +vhmylLH1AgMBAAECggEBAJLZ6ti7yDKgY+LcT/NiBDqKyEUBlbMNZIW5vAPnBKbh +JIDO9WIv9Fs7qSpLbnFHnr0OYtGIfMPXtUiYkyw0QJSc+upHZMvbno4llpes0eHc +jWVTBWETON4oywvj/Kz53vRc9eiKhxVuVWyagNcQgYSprjzLA+9UTcWeB67Guyrf +8YJUE2LC23RiMA5nGYoSHfVRl0c75gj7A0X9nwpAI+xw3kcaVHRIhA6WowA3Pj1o +pK2t692+NLVRylpvMMSS4rziDexomFykCFukYWYB/kZOOSSETSsTWoMXXl1KqsoZ +8IW06NR4rXtIgQ3sTfbYKGZNF5nWFgZ+hJVx0We1Qg0CgYEA8UovlB4nrBm7xH+u +7XXBMbqxADQm5vaEZxw9eluc+tP7cIAI4sglMIvL/FMpbd2pEeP/BkR76NTDzzDu +PAZvUGRavgEjy0O9j2NAs/WPK4tZF+vFdunhnSh4EHAF4Ij9kbsUi90NOpbGfVqP +dOaHqzgHKoR23Cuusk9wFQ2XTV8CgYEAwxHdEYT9xrpfrHPqSBQPpO0dWGKJEkrW +Ob+76rSfuL8wGR4OBNmQdhLuU9zTIh22pog+XPnLPAecC+4yu/wtJ2SPCKiKDbJB +re0CKPyRfGqzvA3njXwMxXazU4kGs+2Fg+xu/iKbaIjxXrclBLhkxhBtySrwAFhx +xOk6fFcPLSsCgYEAqS/Mdr5CMRGGMH0bKhPUWEtAixUGZhJaunX5wY71Xoc/Gh4c +nO+b7BNJ/+5L8WZog0vr6PgiLhrqBaCYm2wjpyoG2o2wDHm+NAlzN/wp3G2EFhrS +xdOux+S1c0kpRcyoiAO2n29rNDa+jOzwBBcU8ACEPdLOCQl0IEFFJO33tl8CgYBY +DOIqnEsovsucvh3MNzHwkg8i7CdPGHSmUIN0J9/ItpPxYn2VdtccVOM6+3xZ8+uU +M/9iXGZ+TDkFsZk4/VUsaNmfYOQf1oyLA2ZsNcU90bQbeHNCi/H/19qOJFXgNaCE +sd5P3DMl9lptFGIjRVBHjvbfTQBUR5fi+BusMGfrTQKBgQCTtzMEJP2sef883AJr +XuGVPLzwLi9eTBvPzc5r5pfkvh7mDDmWFxHZm5kctvavqgy32uUPsQgMi1Kz67bU +s5dY9MCVrN2elhTLD8LOiAz8836o3AxFefm5cUWGaU/aZWDYR0QtNqFdyHyRaodo +JJfnfK+oK1Eq7+PvpXfVN9BkYw== +-----END PRIVATE KEY----- diff --git a/cli/tests/unit_node/testdata/rsa_public.pem b/cli/tests/unit_node/testdata/rsa_public.pem new file mode 100644 index 00000000000000..8c30cfa52d47af --- /dev/null +++ b/cli/tests/unit_node/testdata/rsa_public.pem @@ -0,0 +1,9 @@ +-----BEGIN PUBLIC KEY----- +MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAt9xYiIonscC3vz/A2ceR +7KhZZlDu/5bye53nCVTcKnWd2seY6UAdKersX6njr83Dd5OVe1BW/wJvp5EjWTAG +YbFswlNmeD44edEGM939B6Lq+/8iBkrTi8mGN4YCytivE24YI0D4XZMPfkLSpab2 +y/Hy4DjQKBq1ThZ0UBnK+9IhX37Ju/ZoGYSlTIGIhzyaiYBh7wrZBoPczIEu6et/ +kN2VnnbRUtkYTF97ggcv5h+hDpUQjQW0ZgOMcTc8n+RkGpIt0/iM/bTjI3Tz/gsF +di6hHcpZgbopPL630296iByyigQCPJVzdusFrQN5DeC+zT/nGypQkZanLb4ZspSx +9QIDAQAB +-----END PUBLIC KEY----- diff --git a/cli/tests/unit_node/tls_test.ts b/cli/tests/unit_node/tls_test.ts new file mode 100644 index 00000000000000..79c1e634ca0cb4 --- /dev/null +++ b/cli/tests/unit_node/tls_test.ts @@ -0,0 +1,122 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. + +import { + assertEquals, + assertInstanceOf, +} from "../../../test_util/std/testing/asserts.ts"; +import { delay } from "../../../test_util/std/async/delay.ts"; +import { deferred } from "../../../test_util/std/async/deferred.ts"; +import { fromFileUrl, join } from "../../../test_util/std/path/mod.ts"; +import { serveTls } from "../../../test_util/std/http/server.ts"; +import * as tls from "node:tls"; +import * as net from "node:net"; +import * as stream from "node:stream"; + +const tlsTestdataDir = fromFileUrl( + new URL("../testdata/tls", import.meta.url), +); +const keyFile = join(tlsTestdataDir, "localhost.key"); +const certFile = join(tlsTestdataDir, "localhost.crt"); +const key = await Deno.readTextFile(keyFile); +const cert = await Deno.readTextFile(certFile); +const rootCaCert = await Deno.readTextFile(join(tlsTestdataDir, "RootCA.pem")); + +Deno.test("tls.connect makes tls connection", async () => { + const ctl = new AbortController(); + const serve = serveTls(() => new Response("hello"), { + port: 8443, + key, + cert, + signal: ctl.signal, + }); + + await delay(200); + + const conn = tls.connect({ + host: "localhost", + port: 8443, + secureContext: { + ca: rootCaCert, + // deno-lint-ignore no-explicit-any + } as any, + }); + conn.write(`GET / HTTP/1.1 +Host: localhost +Connection: close + +`); + conn.on("data", (chunk) => { + const text = new TextDecoder().decode(chunk); + const bodyText = text.split("\r\n\r\n").at(-1)?.trim(); + assertEquals(bodyText, "hello"); + conn.destroy(); + ctl.abort(); + }); + + await serve; +}); + +Deno.test("tls.createServer creates a TLS server", async () => { + const p = deferred(); + const server = tls.createServer( + // deno-lint-ignore no-explicit-any + { host: "0.0.0.0", key, cert } as any, + (socket: net.Socket) => { + socket.write("welcome!\n"); + socket.setEncoding("utf8"); + socket.pipe(socket).on("data", (data) => { + if (data.toString().trim() === "goodbye") { + socket.destroy(); + } + }); + }, + ); + server.listen(0, async () => { + const conn = await Deno.connectTls({ + hostname: "127.0.0.1", + // deno-lint-ignore no-explicit-any + port: (server.address() as any).port, + caCerts: [rootCaCert], + }); + + const buf = new Uint8Array(100); + await Deno.read(conn.rid, buf); + let text: string; + text = new TextDecoder().decode(buf); + assertEquals(text.replaceAll("\0", ""), "welcome!\n"); + buf.fill(0); + + Deno.write(conn.rid, new TextEncoder().encode("hey\n")); + await Deno.read(conn.rid, buf); + text = new TextDecoder().decode(buf); + assertEquals(text.replaceAll("\0", ""), "hey\n"); + buf.fill(0); + + Deno.write(conn.rid, new TextEncoder().encode("goodbye\n")); + await Deno.read(conn.rid, buf); + text = new TextDecoder().decode(buf); + assertEquals(text.replaceAll("\0", ""), "goodbye\n"); + + conn.close(); + server.close(); + p.resolve(); + }); + await p; +}); + +Deno.test("TLSSocket can construct without options", () => { + // deno-lint-ignore no-explicit-any + new tls.TLSSocket(new stream.PassThrough() as any); +}); + +Deno.test("tlssocket._handle._parentWrap is set", () => { + // Note: This feature is used in popular 'http2-wrapper' module + // https://github.com/szmarczak/http2-wrapper/blob/51eeaf59ff9344fb192b092241bfda8506983620/source/utils/js-stream-socket.js#L6 + const parentWrap = + // deno-lint-ignore no-explicit-any + ((new tls.TLSSocket(new stream.PassThrough() as any, {}) as any) + // deno-lint-ignore no-explicit-any + ._handle as any)! + ._parentWrap; + assertInstanceOf(parentWrap, stream.PassThrough); +}); diff --git a/cli/tests/unit_node/tty_test.ts b/cli/tests/unit_node/tty_test.ts new file mode 100644 index 00000000000000..d2bf32efcbea80 --- /dev/null +++ b/cli/tests/unit_node/tty_test.ts @@ -0,0 +1,31 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. +// deno-lint-ignore-file no-explicit-any + +import { assert } from "../../../test_util/std/testing/asserts.ts"; +import { isatty } from "node:tty"; + +Deno.test("[node/tty isatty] returns true when fd is a tty, false otherwise", () => { + assert(Deno.isatty(Deno.stdin.rid) === isatty(Deno.stdin.rid)); + assert(Deno.isatty(Deno.stdout.rid) === isatty(Deno.stdout.rid)); + assert(Deno.isatty(Deno.stderr.rid) === isatty(Deno.stderr.rid)); + + const file = Deno.openSync("README.md"); + assert(!isatty(file.rid)); + Deno.close(file.rid); +}); + +Deno.test("[node/tty isatty] returns false for irrelevant values", () => { + // invalid numeric fd + assert(!isatty(1234567)); + + // TODO(kt3k): Enable this test when the below issue resolved + // https://github.com/denoland/deno/issues/14398 + // assert(!isatty(-1)); + + // invalid type fd + assert(!isatty("abc" as any)); + assert(!isatty({} as any)); + assert(!isatty([] as any)); + assert(!isatty(null as any)); + assert(!isatty(undefined as any)); +}); diff --git a/cli/tests/unit_node/util_test.ts b/cli/tests/unit_node/util_test.ts new file mode 100644 index 00000000000000..81794c856ff4c2 --- /dev/null +++ b/cli/tests/unit_node/util_test.ts @@ -0,0 +1,256 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. + +import { + assert, + assertEquals, + assertStrictEquals, + assertThrows, +} from "../../../test_util/std/testing/asserts.ts"; +import { stripColor } from "../../../test_util/std/fmt/colors.ts"; +import * as util from "node:util"; + +Deno.test({ + name: "[util] format", + fn() { + assertEquals(util.format("%o", [10, 11]), "[ 10, 11, [length]: 2 ]"); + }, +}); + +Deno.test({ + name: "[util] inspect.custom", + fn() { + assertEquals(util.inspect.custom, Symbol.for("nodejs.util.inspect.custom")); + }, +}); + +Deno.test({ + name: "[util] inspect", + fn() { + assertEquals(stripColor(util.inspect({ foo: 123 })), "{ foo: 123 }"); + assertEquals(stripColor(util.inspect("foo")), "'foo'"); + assertEquals( + stripColor(util.inspect("Deno's logo is so cute.")), + `"Deno's logo is so cute."`, + ); + assertEquals( + stripColor(util.inspect([1, 2, 3, 4, 5, 6, 7])), + `[ + 1, 2, 3, 4, + 5, 6, 7 +]`, + ); + }, +}); + +Deno.test({ + name: "[util] isBoolean", + fn() { + assert(util.isBoolean(true)); + assert(util.isBoolean(new Boolean())); + assert(util.isBoolean(new Boolean(true))); + assert(util.isBoolean(false)); + assert(!util.isBoolean("deno")); + assert(!util.isBoolean("true")); + }, +}); + +Deno.test({ + name: "[util] isNull", + fn() { + let n; + assert(util.isNull(null)); + assert(!util.isNull(n)); + assert(!util.isNull(0)); + assert(!util.isNull({})); + }, +}); + +Deno.test({ + name: "[util] isNullOrUndefined", + fn() { + let n; + assert(util.isNullOrUndefined(null)); + assert(util.isNullOrUndefined(n)); + assert(!util.isNullOrUndefined({})); + assert(!util.isNullOrUndefined("undefined")); + }, +}); + +Deno.test({ + name: "[util] isNumber", + fn() { + assert(util.isNumber(666)); + assert(util.isNumber(new Number(666))); + assert(!util.isNumber("999")); + assert(!util.isNumber(null)); + }, +}); + +Deno.test({ + name: "[util] isString", + fn() { + assert(util.isString("deno")); + assert(util.isString(new String("DIO"))); + assert(!util.isString(1337)); + }, +}); + +Deno.test({ + name: "[util] isSymbol", + fn() { + assert(util.isSymbol(Symbol())); + assert(!util.isSymbol(123)); + assert(!util.isSymbol("string")); + }, +}); + +Deno.test({ + name: "[util] isUndefined", + fn() { + let t; + assert(util.isUndefined(t)); + assert(!util.isUndefined("undefined")); + assert(!util.isUndefined({})); + }, +}); + +Deno.test({ + name: "[util] isObject", + fn() { + const dio = { stand: "Za Warudo" }; + assert(util.isObject(dio)); + assert(util.isObject(new RegExp(/Toki Wo Tomare/))); + assert(!util.isObject("Jotaro")); + }, +}); + +Deno.test({ + name: "[util] isError", + fn() { + const java = new Error(); + const nodejs = new TypeError(); + const deno = "Future"; + assert(util.isError(java)); + assert(util.isError(nodejs)); + assert(!util.isError(deno)); + }, +}); + +Deno.test({ + name: "[util] isFunction", + fn() { + const f = function () {}; + assert(util.isFunction(f)); + assert(!util.isFunction({})); + assert(!util.isFunction(new RegExp(/f/))); + }, +}); + +Deno.test({ + name: "[util] isRegExp", + fn() { + assert(util.isRegExp(new RegExp(/f/))); + assert(util.isRegExp(/fuManchu/)); + assert(!util.isRegExp({ evil: "eye" })); + assert(!util.isRegExp(null)); + }, +}); + +Deno.test({ + name: "[util] isArray", + fn() { + assert(util.isArray([])); + assert(!util.isArray({ yaNo: "array" })); + assert(!util.isArray(null)); + }, +}); + +Deno.test({ + name: "[util] isPrimitive", + fn() { + const stringType = "hasti"; + const booleanType = true; + const integerType = 2; + const symbolType = Symbol("anything"); + + const functionType = function doBest() {}; + const objectType = { name: "ali" }; + const arrayType = [1, 2, 3]; + + assert(util.isPrimitive(stringType)); + assert(util.isPrimitive(booleanType)); + assert(util.isPrimitive(integerType)); + assert(util.isPrimitive(symbolType)); + assert(util.isPrimitive(null)); + assert(util.isPrimitive(undefined)); + assert(!util.isPrimitive(functionType)); + assert(!util.isPrimitive(arrayType)); + assert(!util.isPrimitive(objectType)); + }, +}); + +Deno.test({ + name: "[util] TextDecoder", + fn() { + assert(util.TextDecoder === TextDecoder); + const td: util.TextDecoder = new util.TextDecoder(); + assert(td instanceof TextDecoder); + }, +}); + +Deno.test({ + name: "[util] TextEncoder", + fn() { + assert(util.TextEncoder === TextEncoder); + const te: util.TextEncoder = new util.TextEncoder(); + assert(te instanceof TextEncoder); + }, +}); + +Deno.test({ + name: "[util] isDate", + fn() { + // Test verifies the method is exposed. See _util/_util_types_test for details + assert(util.types.isDate(new Date())); + }, +}); + +Deno.test({ + name: "[util] getSystemErrorName()", + fn() { + type FnTestInvalidArg = (code?: unknown) => void; + + assertThrows( + () => (util.getSystemErrorName as FnTestInvalidArg)(), + TypeError, + ); + assertThrows( + () => (util.getSystemErrorName as FnTestInvalidArg)(1), + RangeError, + ); + + assertStrictEquals(util.getSystemErrorName(-424242), undefined); + + switch (Deno.build.os) { + case "windows": + assertStrictEquals(util.getSystemErrorName(-4091), "EADDRINUSE"); + break; + + case "darwin": + assertStrictEquals(util.getSystemErrorName(-48), "EADDRINUSE"); + break; + + case "linux": + assertStrictEquals(util.getSystemErrorName(-98), "EADDRINUSE"); + break; + } + }, +}); + +Deno.test({ + name: "[util] deprecate() works", + fn() { + const fn = util.deprecate(() => {}, "foo"); + fn(); + }, +}); diff --git a/cli/tests/unit_node/v8_test.ts b/cli/tests/unit_node/v8_test.ts new file mode 100644 index 00000000000000..ab19035962d47a --- /dev/null +++ b/cli/tests/unit_node/v8_test.ts @@ -0,0 +1,58 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. +import { + cachedDataVersionTag, + getHeapStatistics, + setFlagsFromString, +} from "node:v8"; +import { + assertEquals, + assertThrows, +} from "../../../test_util/std/testing/asserts.ts"; + +// https://github.com/nodejs/node/blob/a2bbe5ff216bc28f8dac1c36a8750025a93c3827/test/parallel/test-v8-version-tag.js#L6 +Deno.test({ + name: "cachedDataVersionTag success", + fn() { + const tag = cachedDataVersionTag(); + assertEquals(typeof tag, "number"); + assertEquals(cachedDataVersionTag(), tag); + }, +}); + +// https://github.com/nodejs/node/blob/a2bbe5ff216bc28f8dac1c36a8750025a93c3827/test/parallel/test-v8-stats.js#L6 +Deno.test({ + name: "getHeapStatistics success", + fn() { + const s = getHeapStatistics(); + const keys = [ + "does_zap_garbage", + "external_memory", + "heap_size_limit", + "malloced_memory", + "number_of_detached_contexts", + "number_of_native_contexts", + "peak_malloced_memory", + "total_available_size", + "total_global_handles_size", + "total_heap_size", + "total_heap_size_executable", + "total_physical_size", + "used_global_handles_size", + "used_heap_size", + ]; + assertEquals(Object.keys(s).sort(), keys); + for (const k of keys) { + assertEquals( + typeof (s as unknown as Record)[k], + "number", + ); + } + }, +}); + +Deno.test({ + name: "setFlagsFromString throws", + fn() { + assertThrows(() => setFlagsFromString("--allow_natives_syntax")); + }, +}); diff --git a/cli/tools/bench.rs b/cli/tools/bench.rs index 3896070d0d02ef..fdcbae5997fadd 100644 --- a/cli/tools/bench.rs +++ b/cli/tools/bench.rs @@ -5,7 +5,8 @@ use crate::args::BenchOptions; use crate::args::CliOptions; use crate::args::TypeCheckMode; use crate::colors; -use crate::graph_util::graph_valid; +use crate::display::write_json_to_stdout; +use crate::graph_util::graph_valid_with_cli_options; use crate::ops; use crate::proc_state::ProcState; use crate::tools::test::format_test_error; @@ -14,6 +15,7 @@ use crate::util::file_watcher; use crate::util::file_watcher::ResolutionResult; use crate::util::fs::collect_specifiers; use crate::util::path::is_supported_ext; +use crate::version::get_user_agent; use crate::worker::create_main_worker_for_test_or_bench; use deno_core::error::generic_error; @@ -42,6 +44,7 @@ use tokio::sync::mpsc::UnboundedSender; #[derive(Debug, Clone)] struct BenchSpecifierOptions { filter: TestFilter, + json: bool, } #[derive(Debug, Clone, Eq, PartialEq, Deserialize)] @@ -63,7 +66,7 @@ pub enum BenchEvent { Result(usize, BenchResult), } -#[derive(Debug, Clone, Deserialize)] +#[derive(Debug, Clone, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub enum BenchResult { Ok(BenchStats), @@ -110,7 +113,13 @@ impl BenchReport { } } -fn create_reporter(show_output: bool) -> Box { +fn create_reporter( + show_output: bool, + json: bool, +) -> Box { + if json { + return Box::new(JsonReporter::new()); + } Box::new(ConsoleReporter::new(show_output)) } @@ -124,6 +133,81 @@ pub trait BenchReporter { fn report_result(&mut self, desc: &BenchDescription, result: &BenchResult); } +#[derive(Debug, Serialize)] +struct JsonReporterOutput { + runtime: String, + cpu: String, + benches: Vec, +} + +impl Default for JsonReporterOutput { + fn default() -> Self { + Self { + runtime: format!("{} {}", get_user_agent(), env!("TARGET")), + cpu: mitata::cpu::name(), + benches: vec![], + } + } +} + +#[derive(Debug, Serialize)] +struct JsonReporterBench { + origin: String, + group: Option, + name: String, + baseline: bool, + results: Vec, +} + +#[derive(Debug, Serialize)] +struct JsonReporter(JsonReporterOutput); + +impl JsonReporter { + fn new() -> Self { + Self(Default::default()) + } +} + +impl BenchReporter for JsonReporter { + fn report_group_summary(&mut self) {} + #[cold] + fn report_plan(&mut self, _plan: &BenchPlan) {} + + fn report_end(&mut self, _report: &BenchReport) { + match write_json_to_stdout(self) { + Ok(_) => (), + Err(e) => println!("{e}"), + } + } + + fn report_register(&mut self, _desc: &BenchDescription) {} + + fn report_wait(&mut self, _desc: &BenchDescription) {} + + fn report_output(&mut self, _output: &str) {} + + fn report_result(&mut self, desc: &BenchDescription, result: &BenchResult) { + let maybe_bench = self.0.benches.iter_mut().find(|bench| { + bench.origin == desc.origin + && bench.group == desc.group + && bench.name == desc.name + && bench.baseline == desc.baseline + }); + + if let Some(bench) = maybe_bench { + bench.results.push(result.clone()); + } else { + self.0.benches.push(JsonReporterBench { + origin: desc.origin.clone(), + group: desc.group.clone(), + name: desc.name.clone(), + baseline: desc.baseline, + results: vec![result.clone()], + }); + } + } +} + struct ConsoleReporter { name: String, show_output: bool, @@ -377,12 +461,14 @@ async fn bench_specifiers( let (sender, mut receiver) = unbounded_channel::(); + let option_for_handles = options.clone(); + let join_handles = specifiers.into_iter().map(move |specifier| { let ps = ps.clone(); let permissions = permissions.clone(); let specifier = specifier; let sender = sender.clone(); - let options = options.clone(); + let options = option_for_handles.clone(); tokio::task::spawn_blocking(move || { let future = bench_specifier(ps, permissions, specifier, sender, options); @@ -399,7 +485,8 @@ async fn bench_specifiers( tokio::task::spawn(async move { let mut used_only = false; let mut report = BenchReport::new(); - let mut reporter = create_reporter(log_level != Some(Level::Error)); + let mut reporter = + create_reporter(log_level != Some(Level::Error), options.json); let mut benches = IndexMap::new(); while let Some(event) = receiver.recv().await { @@ -510,6 +597,7 @@ pub async fn run_benchmarks( specifiers, BenchSpecifierOptions { filter: TestFilter::from_flag(&bench_options.filter), + json: bench_options.json, }, ) .await?; @@ -550,7 +638,7 @@ pub async fn run_benchmarks_with_watch( bench_modules.clone() }; let graph = ps.create_graph(bench_modules.clone()).await?; - graph_valid(&graph, !no_check, ps.options.check_js())?; + graph_valid_with_cli_options(&graph, &bench_modules, &ps.options)?; // TODO(@kitsonk) - This should be totally derivable from the graph. for specifier in bench_modules { @@ -562,7 +650,7 @@ pub async fn run_benchmarks_with_watch( output: &mut HashSet<&'a ModuleSpecifier>, no_check: bool, ) { - if let Some(module) = maybe_module { + if let Some(module) = maybe_module.and_then(|m| m.esm()) { for dep in module.dependencies.values() { if let Some(specifier) = &dep.get_code() { if !output.contains(specifier) { @@ -591,6 +679,7 @@ pub async fn run_benchmarks_with_watch( } } } + // This bench module and all it's dependencies let mut modules = HashSet::new(); modules.insert(&specifier); @@ -659,6 +748,7 @@ pub async fn run_benchmarks_with_watch( specifiers, BenchSpecifierOptions { filter: TestFilter::from_flag(&bench_options.filter), + json: bench_options.json, }, ) .await?; diff --git a/cli/tools/bundle.rs b/cli/tools/bundle.rs index 437bad1d490cfa..d75da5ec76e886 100644 --- a/cli/tools/bundle.rs +++ b/cli/tools/bundle.rs @@ -6,6 +6,7 @@ use std::sync::Arc; use deno_core::error::AnyError; use deno_core::futures::FutureExt; use deno_core::resolve_url_or_path; +use deno_graph::Module; use deno_runtime::colors; use crate::args::BundleFlags; @@ -25,6 +26,15 @@ pub async fn bundle( bundle_flags: BundleFlags, ) -> Result<(), AnyError> { let cli_options = Arc::new(CliOptions::from_flags(flags)?); + + log::info!( + "{} \"deno bundle\" is deprecated and will be removed in the future.", + colors::yellow("Warning"), + ); + log::info!( + "Use alternative bundlers like \"deno_emit\", \"esbuild\" or \"rollup\" instead." + ); + let resolver = |_| { let cli_options = cli_options.clone(); let source_file1 = &bundle_flags.source_file; @@ -38,7 +48,14 @@ pub async fn bundle( let mut paths_to_watch: Vec = graph .specifiers() - .filter_map(|(_, r)| r.ok().and_then(|(s, _, _)| s.to_file_path().ok())) + .filter_map(|(_, r)| { + r.ok().and_then(|module| match module { + Module::Esm(m) => m.specifier.to_file_path().ok(), + Module::Json(m) => m.specifier.to_file_path().ok(), + // nothing to watch + Module::Node(_) | Module::Npm(_) | Module::External(_) => None, + }) + }) .collect(); if let Ok(Some(import_map_path)) = ps diff --git a/cli/tools/check.rs b/cli/tools/check.rs index 44593237335942..8b2731ef6d058e 100644 --- a/cli/tools/check.rs +++ b/cli/tools/check.rs @@ -6,7 +6,8 @@ use std::sync::Arc; use deno_ast::MediaType; use deno_ast::ModuleSpecifier; use deno_core::error::AnyError; -use deno_core::parking_lot::RwLock; +use deno_graph::Module; +use deno_graph::ModuleGraph; use deno_runtime::colors; use once_cell::sync::Lazy; use regex::Regex; @@ -15,8 +16,6 @@ use crate::args::TsConfig; use crate::args::TypeCheckMode; use crate::cache::FastInsecureHasher; use crate::cache::TypeCheckCache; -use crate::graph_util::GraphData; -use crate::graph_util::ModuleEntry; use crate::npm::NpmPackageResolver; use crate::tsc; use crate::tsc::Diagnostics; @@ -55,18 +54,13 @@ pub struct CheckResult { /// It is expected that it is determined if a check and/or emit is validated /// before the function is called. pub fn check( - roots: &[ModuleSpecifier], - graph_data: Arc>, + graph: Arc, cache: &TypeCheckCache, npm_resolver: &NpmPackageResolver, options: CheckOptions, ) -> Result { let check_js = options.ts_config.get_check_js(); - let segment_graph_data = { - let graph_data = graph_data.read(); - graph_data.graph_segment(roots).unwrap() - }; - let check_hash = match get_check_hash(&segment_graph_data, &options) { + let check_hash = match get_check_hash(&graph, &options) { CheckHashResult::NoFiles => return Ok(Default::default()), CheckHashResult::Hash(hash) => hash, }; @@ -76,23 +70,21 @@ pub fn check( return Ok(Default::default()); } - let root_names = get_tsc_roots(&segment_graph_data, check_js); if options.log_checks { - for root in roots { + for root in &graph.roots { let root_str = root.as_str(); - // `$deno` specifiers are internal, don't print them. - if !root_str.contains("$deno") { - log::info!("{} {}", colors::green("Check"), root_str); - } + log::info!("{} {}", colors::green("Check"), root_str); } } + + let root_names = get_tsc_roots(&graph, check_js); // while there might be multiple roots, we can't "merge" the build info, so we // try to retrieve the build info for first root, which is the most common use // case. let maybe_tsbuildinfo = if options.reload { None } else { - cache.get_tsbuildinfo(&roots[0]) + cache.get_tsbuildinfo(&graph.roots[0]) }; // to make tsc build info work, we need to consistently hash modules, so that // tsc can better determine if an emit is still valid or not, so we provide @@ -105,7 +97,7 @@ pub fn check( let response = tsc::exec(tsc::Request { config: options.ts_config, debug: options.debug, - graph_data, + graph: graph.clone(), hash_data, maybe_config_specifier: options.maybe_config_specifier, maybe_npm_resolver: Some(npm_resolver.clone()), @@ -137,7 +129,7 @@ pub fn check( }; if let Some(tsbuildinfo) = response.maybe_tsbuildinfo { - cache.set_tsbuildinfo(&roots[0], &tsbuildinfo); + cache.set_tsbuildinfo(&graph.roots[0], &tsbuildinfo); } if diagnostics.is_empty() { @@ -158,7 +150,7 @@ enum CheckHashResult { /// Gets a hash of the inputs for type checking. This can then /// be used to tell fn get_check_hash( - graph_data: &GraphData, + graph: &ModuleGraph, options: &CheckOptions, ) -> CheckHashResult { let mut hasher = FastInsecureHasher::new(); @@ -170,48 +162,54 @@ fn get_check_hash( hasher.write(&options.ts_config.as_bytes()); let check_js = options.ts_config.get_check_js(); - let mut sorted_entries = graph_data.entries().collect::>(); - sorted_entries.sort_by_key(|(s, _)| s.as_str()); // make it deterministic + let mut sorted_modules = graph.modules().collect::>(); + sorted_modules.sort_by_key(|m| m.specifier().as_str()); // make it deterministic let mut has_file = false; let mut has_file_to_type_check = false; - for (specifier, module_entry) in sorted_entries { - if let ModuleEntry::Module { - code, media_type, .. - } = module_entry - { - let ts_check = has_ts_check(*media_type, code); - if ts_check { - has_file_to_type_check = true; - } - - match media_type { - MediaType::TypeScript - | MediaType::Dts - | MediaType::Dmts - | MediaType::Dcts - | MediaType::Mts - | MediaType::Cts - | MediaType::Tsx => { - has_file = true; + for module in sorted_modules { + match module { + Module::Esm(module) => { + let ts_check = has_ts_check(module.media_type, &module.source); + if ts_check { has_file_to_type_check = true; } - MediaType::JavaScript - | MediaType::Mjs - | MediaType::Cjs - | MediaType::Jsx => { - has_file = true; - if !check_js && !ts_check { - continue; + + match module.media_type { + MediaType::TypeScript + | MediaType::Dts + | MediaType::Dmts + | MediaType::Dcts + | MediaType::Mts + | MediaType::Cts + | MediaType::Tsx => { + has_file = true; + has_file_to_type_check = true; } + MediaType::JavaScript + | MediaType::Mjs + | MediaType::Cjs + | MediaType::Jsx => { + has_file = true; + if !check_js && !ts_check { + continue; + } + } + MediaType::Json + | MediaType::TsBuildInfo + | MediaType::SourceMap + | MediaType::Wasm + | MediaType::Unknown => continue, } - MediaType::Json - | MediaType::TsBuildInfo - | MediaType::SourceMap - | MediaType::Wasm - | MediaType::Unknown => continue, + + hasher.write_str(module.specifier.as_str()); + hasher.write_str(&module.source); + } + Module::Json(_) + | Module::External(_) + | Module::Node(_) + | Module::Npm(_) => { + // ignore } - hasher.write_str(specifier.as_str()); - hasher.write_str(code); } } @@ -230,37 +228,45 @@ fn get_check_hash( /// the roots, so they get type checked and optionally emitted, /// otherwise they would be ignored if only imported into JavaScript. fn get_tsc_roots( - graph_data: &GraphData, + graph: &ModuleGraph, check_js: bool, ) -> Vec<(ModuleSpecifier, MediaType)> { let mut result = Vec::new(); - if graph_data.has_node_builtin_specifier() { + if graph.has_node_specifier { // inject a specifier that will resolve node types result.push(( ModuleSpecifier::parse("asset:///node_types.d.ts").unwrap(), MediaType::Dts, )); } - result.extend(graph_data.entries().into_iter().filter_map( - |(specifier, module_entry)| match module_entry { - ModuleEntry::Module { - media_type, code, .. - } => match media_type { - MediaType::TypeScript - | MediaType::Tsx - | MediaType::Mts - | MediaType::Cts - | MediaType::Jsx => Some((specifier.clone(), *media_type)), - MediaType::JavaScript | MediaType::Mjs | MediaType::Cjs - if check_js || has_ts_check(*media_type, code) => - { - Some((specifier.clone(), *media_type)) + result.extend(graph.modules().filter_map(|module| match module { + Module::Esm(module) => match module.media_type { + MediaType::TypeScript + | MediaType::Tsx + | MediaType::Mts + | MediaType::Cts + | MediaType::Jsx => Some((module.specifier.clone(), module.media_type)), + MediaType::JavaScript | MediaType::Mjs | MediaType::Cjs => { + if check_js || has_ts_check(module.media_type, &module.source) { + Some((module.specifier.clone(), module.media_type)) + } else { + None } - _ => None, - }, - _ => None, + } + MediaType::Json + | MediaType::Dts + | MediaType::Dmts + | MediaType::Dcts + | MediaType::Wasm + | MediaType::TsBuildInfo + | MediaType::SourceMap + | MediaType::Unknown => None, }, - )); + Module::External(_) + | Module::Node(_) + | Module::Npm(_) + | Module::Json(_) => None, + })); result } @@ -276,7 +282,18 @@ fn has_ts_check(media_type: MediaType, file_text: &str) -> bool { | MediaType::Jsx => get_leading_comments(file_text) .iter() .any(|text| TS_CHECK_RE.is_match(text)), - _ => false, + MediaType::TypeScript + | MediaType::Mts + | MediaType::Cts + | MediaType::Dts + | MediaType::Dcts + | MediaType::Dmts + | MediaType::Tsx + | MediaType::Json + | MediaType::Wasm + | MediaType::TsBuildInfo + | MediaType::SourceMap + | MediaType::Unknown => false, } } diff --git a/cli/tools/doc.rs b/cli/tools/doc.rs index 3e19d6df204fcf..e0413ab798475d 100644 --- a/cli/tools/doc.rs +++ b/cli/tools/doc.rs @@ -40,18 +40,17 @@ pub async fn print_docs( Vec::new(), ); let analyzer = deno_graph::CapturingModuleAnalyzer::default(); - let graph = deno_graph::create_graph( - vec![source_file_specifier.clone()], - &mut loader, - deno_graph::GraphOptions { - is_dynamic: false, - imports: None, - resolver: None, - module_analyzer: Some(&analyzer), - reporter: None, - }, - ) - .await; + let mut graph = deno_graph::ModuleGraph::default(); + graph + .build( + vec![source_file_specifier.clone()], + &mut loader, + deno_graph::BuildOptions { + module_analyzer: Some(&analyzer), + ..Default::default() + }, + ) + .await; let doc_parser = doc::DocParser::new( graph, doc_flags.private, diff --git a/cli/tools/info.rs b/cli/tools/info.rs index f3de922c6b89c5..8a7f4b6b9853bd 100644 --- a/cli/tools/info.rs +++ b/cli/tools/info.rs @@ -10,19 +10,20 @@ use deno_core::error::AnyError; use deno_core::resolve_url_or_path; use deno_core::serde_json; use deno_core::serde_json::json; +use deno_graph::npm::NpmPackageNv; +use deno_graph::npm::NpmPackageNvReference; +use deno_graph::npm::NpmPackageReqReference; use deno_graph::Dependency; use deno_graph::Module; use deno_graph::ModuleGraph; use deno_graph::ModuleGraphError; -use deno_graph::Resolved; +use deno_graph::Resolution; use deno_runtime::colors; use crate::args::Flags; use crate::args::InfoFlags; use crate::display; use crate::npm::NpmPackageId; -use crate::npm::NpmPackageReference; -use crate::npm::NpmPackageReq; use crate::npm::NpmPackageResolver; use crate::npm::NpmResolutionPackage; use crate::npm::NpmResolutionSnapshot; @@ -33,7 +34,11 @@ pub async fn info(flags: Flags, info_flags: InfoFlags) -> Result<(), AnyError> { let ps = ProcState::build(flags).await?; if let Some(specifier) = info_flags.file { let specifier = resolve_url_or_path(&specifier)?; - let graph = ps.create_graph(vec![specifier]).await?; + let mut loader = ps.create_graph_loader(); + loader.enable_loading_cache_info(); // for displaying the cache information + let graph = ps + .create_graph_with_loader(vec![specifier], &mut loader) + .await?; if info_flags.json { let mut json_graph = json!(graph); @@ -137,7 +142,7 @@ fn add_npm_packages_to_json( let modules = json.get_mut("modules").and_then(|m| m.as_array_mut()); if let Some(modules) = modules { if modules.len() == 1 - && modules[0].get("kind").and_then(|k| k.as_str()) == Some("external") + && modules[0].get("kind").and_then(|k| k.as_str()) == Some("npm") { // If there is only one module and it's "external", then that means // someone provided an npm specifier as a cli argument. In this case, @@ -146,18 +151,18 @@ fn add_npm_packages_to_json( let maybe_package = module .get("specifier") .and_then(|k| k.as_str()) - .and_then(|specifier| NpmPackageReference::from_str(specifier).ok()) + .and_then(|specifier| NpmPackageNvReference::from_str(specifier).ok()) .and_then(|package_ref| { snapshot - .resolve_package_from_deno_module(&package_ref.req) + .resolve_package_from_deno_module(&package_ref.nv) .ok() }); if let Some(pkg) = maybe_package { if let Some(module) = module.as_object_mut() { - module - .insert("npmPackage".to_string(), pkg.id.as_serialized().into()); - // change the "kind" to be "npm" - module.insert("kind".to_string(), "npm".into()); + module.insert( + "npmPackage".to_string(), + pkg.pkg_id.as_serialized().into(), + ); } } } else { @@ -167,7 +172,10 @@ fn add_npm_packages_to_json( // references. So there could be listed multiple npm specifiers // that would resolve to a single npm package. for i in (0..modules.len()).rev() { - if modules[i].get("kind").and_then(|k| k.as_str()) == Some("external") { + if matches!( + modules[i].get("kind").and_then(|k| k.as_str()), + Some("npm") | Some("external") + ) { modules.remove(i); } } @@ -182,13 +190,12 @@ fn add_npm_packages_to_json( if let serde_json::Value::Object(dep) = dep { let specifier = dep.get("specifier").and_then(|s| s.as_str()); if let Some(specifier) = specifier { - if let Ok(npm_ref) = NpmPackageReference::from_str(specifier) { - if let Ok(pkg) = - snapshot.resolve_package_from_deno_module(&npm_ref.req) + if let Ok(npm_ref) = NpmPackageReqReference::from_str(specifier) { + if let Ok(pkg) = snapshot.resolve_pkg_from_pkg_req(&npm_ref.req) { dep.insert( "npmPackage".to_string(), - pkg.id.as_serialized().into(), + pkg.pkg_id.as_serialized().into(), ); } } @@ -200,12 +207,15 @@ fn add_npm_packages_to_json( } let mut sorted_packages = snapshot.all_packages(); - sorted_packages.sort_by(|a, b| a.id.cmp(&b.id)); + sorted_packages.sort_by(|a, b| a.pkg_id.cmp(&b.pkg_id)); let mut json_packages = serde_json::Map::with_capacity(sorted_packages.len()); for pkg in sorted_packages { let mut kv = serde_json::Map::new(); - kv.insert("name".to_string(), pkg.id.name.to_string().into()); - kv.insert("version".to_string(), pkg.id.version.to_string().into()); + kv.insert("name".to_string(), pkg.pkg_id.nv.name.to_string().into()); + kv.insert( + "version".to_string(), + pkg.pkg_id.nv.version.to_string().into(), + ); let mut deps = pkg.dependencies.values().collect::>(); deps.sort(); let deps = deps @@ -214,7 +224,7 @@ fn add_npm_packages_to_json( .collect::>(); kv.insert("dependencies".to_string(), deps.into()); - json_packages.insert(pkg.id.as_serialized(), kv.into()); + json_packages.insert(pkg.pkg_id.as_serialized(), kv.into()); } json.insert("npmPackages".to_string(), json_packages.into()); @@ -294,9 +304,8 @@ fn print_tree_node( #[derive(Default)] struct NpmInfo { package_sizes: HashMap, - resolved_reqs: HashMap, + resolved_ids: HashMap, packages: HashMap, - specifiers: HashMap, } impl NpmInfo { @@ -306,20 +315,16 @@ impl NpmInfo { npm_snapshot: &'a NpmResolutionSnapshot, ) -> Self { let mut info = NpmInfo::default(); - if !npm_resolver.has_packages() { - return info; // skip going over the specifiers if there's no npm packages + if graph.npm_packages.is_empty() { + return info; // skip going over the modules if there's no npm packages } - for (specifier, _) in graph.specifiers() { - if let Ok(reference) = NpmPackageReference::from_specifier(specifier) { - info - .specifiers - .insert(specifier.clone(), reference.req.clone()); - if let Ok(package) = - npm_snapshot.resolve_package_from_deno_module(&reference.req) - { - info.resolved_reqs.insert(reference.req, package.id.clone()); - if !info.packages.contains_key(&package.id) { + for module in graph.modules() { + if let Module::Npm(module) = module { + let nv = &module.nv_reference.nv; + if let Ok(package) = npm_snapshot.resolve_package_from_deno_module(nv) { + info.resolved_ids.insert(nv.clone(), package.pkg_id.clone()); + if !info.packages.contains_key(&package.pkg_id) { info.fill_package_info(package, npm_resolver, npm_snapshot); } } @@ -335,9 +340,11 @@ impl NpmInfo { npm_resolver: &'a NpmPackageResolver, npm_snapshot: &'a NpmResolutionSnapshot, ) { - self.packages.insert(package.id.clone(), package.clone()); - if let Ok(size) = npm_resolver.package_size(&package.id) { - self.package_sizes.insert(package.id.clone(), size); + self + .packages + .insert(package.pkg_id.clone(), package.clone()); + if let Ok(size) = npm_resolver.package_size(&package.pkg_id) { + self.package_sizes.insert(package.pkg_id.clone(), size); } for id in package.dependencies.values() { if !self.packages.contains_key(id) { @@ -348,15 +355,12 @@ impl NpmInfo { } } - pub fn package_from_specifier( + pub fn resolve_package( &self, - specifier: &ModuleSpecifier, + nv: &NpmPackageNv, ) -> Option<&NpmResolutionPackage> { - self - .specifiers - .get(specifier) - .and_then(|package_req| self.resolved_reqs.get(package_req)) - .and_then(|id| self.packages.get(id)) + let id = self.resolved_ids.get(nv)?; + self.packages.get(id) } } @@ -394,7 +398,12 @@ impl<'a> GraphDisplayContext<'a> { let root_specifier = self.graph.resolve(&self.graph.roots[0]); match self.graph.try_get(&root_specifier) { Ok(Some(root)) => { - if let Some(cache_info) = root.maybe_cache_info.as_ref() { + let maybe_cache_info = match root { + Module::Esm(module) => module.maybe_cache_info.as_ref(), + Module::Json(module) => module.maybe_cache_info.as_ref(), + Module::Node(_) | Module::Npm(_) | Module::External(_) => None, + }; + if let Some(cache_info) = maybe_cache_info { if let Some(local) = &cache_info.local { writeln!( writer, @@ -420,9 +429,21 @@ impl<'a> GraphDisplayContext<'a> { )?; } } - writeln!(writer, "{} {}", colors::bold("type:"), root.media_type)?; - let total_modules_size = - self.graph.modules().map(|m| m.size() as f64).sum::(); + if let Some(module) = root.esm() { + writeln!(writer, "{} {}", colors::bold("type:"), module.media_type)?; + } + let total_modules_size = self + .graph + .modules() + .map(|m| { + let size = match m { + Module::Esm(module) => module.size(), + Module::Json(module) => module.size(), + Module::Node(_) | Module::Npm(_) | Module::External(_) => 0, + }; + size as f64 + }) + .sum::(); let total_npm_package_size = self .npm_info .package_sizes @@ -430,9 +451,9 @@ impl<'a> GraphDisplayContext<'a> { .map(|s| *s as f64) .sum::(); let total_size = total_modules_size + total_npm_package_size; - let dep_count = self.graph.modules().count() - 1 + let dep_count = self.graph.modules().count() - 1 // -1 for the root module + self.npm_info.packages.len() - - self.npm_info.resolved_reqs.len(); + - self.npm_info.resolved_ids.len(); writeln!( writer, "{} {} unique", @@ -450,15 +471,16 @@ impl<'a> GraphDisplayContext<'a> { print_tree_node(&root_node, writer)?; Ok(()) } - Err(ModuleGraphError::Missing(_)) => { - writeln!( - writer, - "{} module could not be found", - colors::red("error:") - ) - } Err(err) => { - writeln!(writer, "{} {}", colors::red("error:"), err) + if let ModuleGraphError::Missing(_, _) = *err { + writeln!( + writer, + "{} module could not be found", + colors::red("error:") + ) + } else { + writeln!(writer, "{} {}", colors::red("error:"), err) + } } Ok(None) => { writeln!( @@ -493,42 +515,39 @@ impl<'a> GraphDisplayContext<'a> { use PackageOrSpecifier::*; - let package_or_specifier = - match self.npm_info.package_from_specifier(&module.specifier) { + let package_or_specifier = match module.npm() { + Some(npm) => match self.npm_info.resolve_package(&npm.nv_reference.nv) { Some(package) => Package(package.clone()), - None => Specifier(module.specifier.clone()), - }; + None => Specifier(module.specifier().clone()), // should never happen + }, + None => Specifier(module.specifier().clone()), + }; let was_seen = !self.seen.insert(match &package_or_specifier { - Package(package) => package.id.as_serialized(), + Package(package) => package.pkg_id.as_serialized(), Specifier(specifier) => specifier.to_string(), }); let header_text = if was_seen { let specifier_str = if type_dep { - colors::italic_gray(&module.specifier).to_string() + colors::italic_gray(module.specifier()).to_string() } else { - colors::gray(&module.specifier).to_string() + colors::gray(module.specifier()).to_string() }; format!("{} {}", specifier_str, colors::gray("*")) } else { - let specifier_str = if type_dep { - colors::italic(&module.specifier).to_string() + let header_text = if type_dep { + colors::italic(module.specifier()).to_string() } else { - module.specifier.to_string() - }; - let header_text = match &package_or_specifier { - Package(package) => { - format!("{} - {}", specifier_str, package.id.version) - } - Specifier(_) => specifier_str, + module.specifier().to_string() }; let maybe_size = match &package_or_specifier { Package(package) => { - self.npm_info.package_sizes.get(&package.id).copied() + self.npm_info.package_sizes.get(&package.pkg_id).copied() } - Specifier(_) => module - .maybe_source - .as_ref() - .map(|s| s.as_bytes().len() as u64), + Specifier(_) => match module { + Module::Esm(module) => Some(module.size() as u64), + Module::Json(module) => Some(module.size() as u64), + Module::Node(_) | Module::Npm(_) | Module::External(_) => None, + }, }; format!("{} {}", header_text, maybe_size_to_text(maybe_size)) }; @@ -536,18 +555,22 @@ impl<'a> GraphDisplayContext<'a> { let mut tree_node = TreeNode::from_text(header_text); if !was_seen { - if let Some((_, type_dep)) = &module.maybe_types_dependency { - if let Some(child) = self.build_resolved_info(type_dep, true) { - tree_node.children.push(child); - } - } match &package_or_specifier { Package(package) => { tree_node.children.extend(self.build_npm_deps(package)); } Specifier(_) => { - for dep in module.dependencies.values() { - tree_node.children.extend(self.build_dep_info(dep)); + if let Some(module) = module.esm() { + if let Some(types_dep) = &module.maybe_types_dependency { + if let Some(child) = + self.build_resolved_info(&types_dep.dependency, true) + { + tree_node.children.push(child); + } + } + for dep in module.dependencies.values() { + tree_node.children.extend(self.build_dep_info(dep)); + } } } } @@ -572,7 +595,7 @@ impl<'a> GraphDisplayContext<'a> { )); if let Some(package) = self.npm_info.packages.get(dep_id) { if !package.dependencies.is_empty() { - let was_seen = !self.seen.insert(package.id.as_serialized()); + let was_seen = !self.seen.insert(package.pkg_id.as_serialized()); if was_seen { child.text = format!("{} {}", child.text, colors::gray("*")); } else { @@ -596,7 +619,7 @@ impl<'a> GraphDisplayContext<'a> { ModuleGraphError::InvalidTypeAssertion { .. } => { self.build_error_msg(specifier, "(invalid import assertion)") } - ModuleGraphError::LoadingErr(_, _) => { + ModuleGraphError::LoadingErr(_, _, _) => { self.build_error_msg(specifier, "(loading error)") } ModuleGraphError::ParseErr(_, _) => { @@ -605,13 +628,14 @@ impl<'a> GraphDisplayContext<'a> { ModuleGraphError::ResolutionError(_) => { self.build_error_msg(specifier, "(resolution error)") } - ModuleGraphError::UnsupportedImportAssertionType(_, _) => { + ModuleGraphError::UnsupportedImportAssertionType { .. } => { self.build_error_msg(specifier, "(unsupported import assertion)") } - ModuleGraphError::UnsupportedMediaType(_, _) => { + ModuleGraphError::UnsupportedMediaType { .. } => { self.build_error_msg(specifier, "(unsupported)") } - ModuleGraphError::Missing(_) => { + ModuleGraphError::Missing(_, _) + | ModuleGraphError::MissingDynamic(_, _) => { self.build_error_msg(specifier, "(missing)") } } @@ -631,15 +655,16 @@ impl<'a> GraphDisplayContext<'a> { fn build_resolved_info( &mut self, - resolved: &Resolved, + resolution: &Resolution, type_dep: bool, ) -> Option { - match resolved { - Resolved::Ok { specifier, .. } => { + match resolution { + Resolution::Ok(resolved) => { + let specifier = &resolved.specifier; let resolved_specifier = self.graph.resolve(specifier); Some(match self.graph.try_get(&resolved_specifier) { Ok(Some(module)) => self.build_module_info(module, type_dep), - Err(err) => self.build_error_info(&err, &resolved_specifier), + Err(err) => self.build_error_info(err, &resolved_specifier), Ok(None) => TreeNode::from_text(format!( "{} {}", colors::red(specifier), @@ -647,7 +672,7 @@ impl<'a> GraphDisplayContext<'a> { )), }) } - Resolved::Err(err) => Some(TreeNode::from_text(format!( + Resolution::Err(err) => Some(TreeNode::from_text(format!( "{} {}", colors::italic(err.to_string()), colors::red_bold("(resolve error)") diff --git a/cli/tools/installer.rs b/cli/tools/installer.rs index 77ef813a168586..a43ec84d54cd0f 100644 --- a/cli/tools/installer.rs +++ b/cli/tools/installer.rs @@ -6,14 +6,16 @@ use crate::args::ConfigFlag; use crate::args::Flags; use crate::args::InstallFlags; use crate::args::TypeCheckMode; -use crate::npm::NpmPackageReference; +use crate::http_util::HttpClient; use crate::proc_state::ProcState; use crate::util::fs::canonicalize_path_maybe_not_exists; + use deno_core::anyhow::Context; use deno_core::error::generic_error; use deno_core::error::AnyError; use deno_core::resolve_url_or_path; use deno_core::url::Url; +use deno_graph::npm::NpmPackageReqReference; use log::Level; use once_cell::sync::Lazy; use regex::Regex; @@ -125,8 +127,19 @@ fn get_installer_root() -> Result { Ok(home_path) } -pub fn infer_name_from_url(url: &Url) -> Option { - if let Ok(npm_ref) = NpmPackageReference::from_specifier(url) { +pub async fn infer_name_from_url(url: &Url) -> Option { + // If there's an absolute url with no path, eg. https://my-cli.com + // perform a request, and see if it redirects another file instead. + let mut url = url.clone(); + + if url.path() == "/" { + let client = HttpClient::new(None, None).unwrap(); + if let Ok(res) = client.get_redirected_response(url.clone()).await { + url = res.url().clone(); + } + } + + if let Ok(npm_ref) = NpmPackageReqReference::from_specifier(&url) { if let Some(sub_path) = npm_ref.sub_path { if !sub_path.contains('/') { return Some(sub_path); @@ -226,14 +239,14 @@ pub async fn install_command( .await?; // create the install shim - create_install_shim(flags, install_flags) + create_install_shim(flags, install_flags).await } -fn create_install_shim( +async fn create_install_shim( flags: Flags, install_flags: InstallFlags, ) -> Result<(), AnyError> { - let shim_data = resolve_shim_data(&flags, &install_flags)?; + let shim_data = resolve_shim_data(&flags, &install_flags).await?; // ensure directory exists if let Ok(metadata) = fs::metadata(&shim_data.installation_dir) { @@ -283,7 +296,7 @@ struct ShimData { extra_files: Vec<(PathBuf, String)>, } -fn resolve_shim_data( +async fn resolve_shim_data( flags: &Flags, install_flags: &InstallFlags, ) -> Result { @@ -297,10 +310,11 @@ fn resolve_shim_data( // Check if module_url is remote let module_url = resolve_url_or_path(&install_flags.module_url)?; - let name = install_flags - .name - .clone() - .or_else(|| infer_name_from_url(&module_url)); + let name = if install_flags.name.is_some() { + install_flags.name.clone() + } else { + infer_name_from_url(&module_url).await + }; let name = match name { Some(name) => name, @@ -416,7 +430,7 @@ fn resolve_shim_data( executable_args.push("--no-lock".to_string()); } else if flags.lock.is_some() // always use a lockfile for an npm entrypoint unless --no-lock - || NpmPackageReference::from_specifier(&module_url).is_ok() + || NpmPackageReqReference::from_specifier(&module_url).is_ok() { let copy_path = get_hidden_file_with_ext(&file_path, "lock.json"); executable_args.push("--lock".to_string()); @@ -479,115 +493,131 @@ mod tests { use test_util::testdata_path; use test_util::TempDir; - #[test] - fn install_infer_name_from_url() { + #[tokio::test] + async fn install_infer_name_from_url() { assert_eq!( infer_name_from_url( &Url::parse("https://example.com/abc/server.ts").unwrap() - ), + ) + .await, Some("server".to_string()) ); assert_eq!( infer_name_from_url( &Url::parse("https://example.com/abc/main.ts").unwrap() - ), + ) + .await, Some("abc".to_string()) ); assert_eq!( infer_name_from_url( &Url::parse("https://example.com/abc/mod.ts").unwrap() - ), + ) + .await, Some("abc".to_string()) ); assert_eq!( infer_name_from_url( &Url::parse("https://example.com/abc/index.ts").unwrap() - ), + ) + .await, Some("abc".to_string()) ); assert_eq!( infer_name_from_url( &Url::parse("https://example.com/abc/cli.ts").unwrap() - ), + ) + .await, Some("abc".to_string()) ); assert_eq!( - infer_name_from_url(&Url::parse("https://example.com/main.ts").unwrap()), + infer_name_from_url(&Url::parse("https://example.com/main.ts").unwrap()) + .await, Some("main".to_string()) ); assert_eq!( - infer_name_from_url(&Url::parse("https://example.com").unwrap()), + infer_name_from_url(&Url::parse("https://example.com").unwrap()).await, None ); assert_eq!( - infer_name_from_url(&Url::parse("file:///abc/server.ts").unwrap()), + infer_name_from_url(&Url::parse("file:///abc/server.ts").unwrap()).await, Some("server".to_string()) ); assert_eq!( - infer_name_from_url(&Url::parse("file:///abc/main.ts").unwrap()), + infer_name_from_url(&Url::parse("file:///abc/main.ts").unwrap()).await, Some("abc".to_string()) ); assert_eq!( - infer_name_from_url(&Url::parse("file:///main.ts").unwrap()), + infer_name_from_url(&Url::parse("file:///main.ts").unwrap()).await, Some("main".to_string()) ); - assert_eq!(infer_name_from_url(&Url::parse("file:///").unwrap()), None); + assert_eq!( + infer_name_from_url(&Url::parse("file:///").unwrap()).await, + None + ); assert_eq!( infer_name_from_url( &Url::parse("https://example.com/abc@0.1.0").unwrap() - ), + ) + .await, Some("abc".to_string()) ); assert_eq!( infer_name_from_url( &Url::parse("https://example.com/abc@0.1.0/main.ts").unwrap() - ), + ) + .await, Some("abc".to_string()) ); assert_eq!( infer_name_from_url( &Url::parse("https://example.com/abc@def@ghi").unwrap() - ), + ) + .await, Some("abc".to_string()) ); assert_eq!( - infer_name_from_url(&Url::parse("https://example.com/@abc.ts").unwrap()), + infer_name_from_url(&Url::parse("https://example.com/@abc.ts").unwrap()) + .await, Some("@abc".to_string()) ); assert_eq!( infer_name_from_url( &Url::parse("https://example.com/@abc/mod.ts").unwrap() - ), + ) + .await, Some("@abc".to_string()) ); assert_eq!( - infer_name_from_url(&Url::parse("file:///@abc.ts").unwrap()), + infer_name_from_url(&Url::parse("file:///@abc.ts").unwrap()).await, Some("@abc".to_string()) ); assert_eq!( - infer_name_from_url(&Url::parse("file:///@abc/cli.ts").unwrap()), + infer_name_from_url(&Url::parse("file:///@abc/cli.ts").unwrap()).await, Some("@abc".to_string()) ); assert_eq!( - infer_name_from_url(&Url::parse("npm:cowsay@1.2/cowthink").unwrap()), + infer_name_from_url(&Url::parse("npm:cowsay@1.2/cowthink").unwrap()) + .await, Some("cowthink".to_string()) ); assert_eq!( - infer_name_from_url(&Url::parse("npm:cowsay@1.2/cowthink/test").unwrap()), + infer_name_from_url(&Url::parse("npm:cowsay@1.2/cowthink/test").unwrap()) + .await, Some("cowsay".to_string()) ); assert_eq!( - infer_name_from_url(&Url::parse("npm:cowsay@1.2").unwrap()), + infer_name_from_url(&Url::parse("npm:cowsay@1.2").unwrap()).await, Some("cowsay".to_string()) ); assert_eq!( - infer_name_from_url(&Url::parse("npm:@types/node@1.2").unwrap()), + infer_name_from_url(&Url::parse("npm:@types/node@1.2").unwrap()).await, None ); } - #[test] - fn install_unstable() { + #[tokio::test] + async fn install_unstable() { let temp_dir = TempDir::new(); let bin_dir = temp_dir.path().join("bin"); std::fs::create_dir(&bin_dir).unwrap(); @@ -605,6 +635,7 @@ mod tests { force: false, }, ) + .await .unwrap(); let mut file_path = bin_dir.join("echo_test"); @@ -626,8 +657,8 @@ mod tests { } } - #[test] - fn install_inferred_name() { + #[tokio::test] + async fn install_inferred_name() { let shim_data = resolve_shim_data( &Flags::default(), &InstallFlags { @@ -638,6 +669,7 @@ mod tests { force: false, }, ) + .await .unwrap(); assert_eq!(shim_data.name, "echo_server"); @@ -647,8 +679,8 @@ mod tests { ); } - #[test] - fn install_inferred_name_from_parent() { + #[tokio::test] + async fn install_inferred_name_from_parent() { let shim_data = resolve_shim_data( &Flags::default(), &InstallFlags { @@ -659,6 +691,7 @@ mod tests { force: false, }, ) + .await .unwrap(); assert_eq!(shim_data.name, "subdir"); @@ -668,8 +701,36 @@ mod tests { ); } - #[test] - fn install_custom_dir_option() { + #[tokio::test] + async fn install_inferred_name_after_redirect_for_no_path_url() { + let _http_server_guard = test_util::http_server(); + let shim_data = resolve_shim_data( + &Flags::default(), + &InstallFlags { + module_url: "http://localhost:4550/?redirect_to=/subdir/redirects/a.ts" + .to_string(), + args: vec![], + name: None, + root: Some(env::temp_dir()), + force: false, + }, + ) + .await + .unwrap(); + + assert_eq!(shim_data.name, "a"); + assert_eq!( + shim_data.args, + vec![ + "run", + "--no-config", + "http://localhost:4550/?redirect_to=/subdir/redirects/a.ts", + ] + ); + } + + #[tokio::test] + async fn install_custom_dir_option() { let shim_data = resolve_shim_data( &Flags::default(), &InstallFlags { @@ -680,6 +741,7 @@ mod tests { force: false, }, ) + .await .unwrap(); assert_eq!(shim_data.name, "echo_test"); @@ -689,8 +751,8 @@ mod tests { ); } - #[test] - fn install_with_flags() { + #[tokio::test] + async fn install_with_flags() { let shim_data = resolve_shim_data( &Flags { allow_net: Some(vec![]), @@ -707,6 +769,7 @@ mod tests { force: false, }, ) + .await .unwrap(); assert_eq!(shim_data.name, "echo_test"); @@ -724,8 +787,8 @@ mod tests { ); } - #[test] - fn install_prompt() { + #[tokio::test] + async fn install_prompt() { let shim_data = resolve_shim_data( &Flags { no_prompt: true, @@ -739,6 +802,7 @@ mod tests { force: false, }, ) + .await .unwrap(); assert_eq!( @@ -752,8 +816,8 @@ mod tests { ); } - #[test] - fn install_allow_all() { + #[tokio::test] + async fn install_allow_all() { let shim_data = resolve_shim_data( &Flags { allow_all: true, @@ -767,6 +831,7 @@ mod tests { force: false, }, ) + .await .unwrap(); assert_eq!( @@ -780,8 +845,8 @@ mod tests { ); } - #[test] - fn install_npm_lockfile_default() { + #[tokio::test] + async fn install_npm_lockfile_default() { let temp_dir = canonicalize_path(&env::temp_dir()).unwrap(); let shim_data = resolve_shim_data( &Flags { @@ -796,6 +861,7 @@ mod tests { force: false, }, ) + .await .unwrap(); let lock_path = temp_dir.join("bin").join(".cowsay.lock.json"); @@ -813,8 +879,8 @@ mod tests { assert_eq!(shim_data.extra_files, vec![(lock_path, "{}".to_string())]); } - #[test] - fn install_npm_no_lock() { + #[tokio::test] + async fn install_npm_no_lock() { let shim_data = resolve_shim_data( &Flags { allow_all: true, @@ -829,6 +895,7 @@ mod tests { force: false, }, ) + .await .unwrap(); assert_eq!( @@ -844,8 +911,8 @@ mod tests { assert_eq!(shim_data.extra_files, vec![]); } - #[test] - fn install_local_module() { + #[tokio::test] + async fn install_local_module() { let temp_dir = TempDir::new(); let bin_dir = temp_dir.path().join("bin"); std::fs::create_dir(&bin_dir).unwrap(); @@ -863,6 +930,7 @@ mod tests { force: false, }, ) + .await .unwrap(); let mut file_path = bin_dir.join("echo_test"); @@ -875,8 +943,8 @@ mod tests { assert!(content.contains(&local_module_url.to_string())); } - #[test] - fn install_force() { + #[tokio::test] + async fn install_force() { let temp_dir = TempDir::new(); let bin_dir = temp_dir.path().join("bin"); std::fs::create_dir(&bin_dir).unwrap(); @@ -891,6 +959,7 @@ mod tests { force: false, }, ) + .await .unwrap(); let mut file_path = bin_dir.join("echo_test"); @@ -909,7 +978,8 @@ mod tests { root: Some(temp_dir.path().to_path_buf()), force: false, }, - ); + ) + .await; assert!(no_force_result.is_err()); assert!(no_force_result .unwrap_err() @@ -929,15 +999,16 @@ mod tests { root: Some(temp_dir.path().to_path_buf()), force: true, }, - ); + ) + .await; assert!(force_result.is_ok()); // Assert modified let file_content_2 = fs::read_to_string(&file_path).unwrap(); assert!(file_content_2.contains("cat.ts")); } - #[test] - fn install_with_config() { + #[tokio::test] + async fn install_with_config() { let temp_dir = TempDir::new(); let bin_dir = temp_dir.path().join("bin"); let config_file_path = temp_dir.path().join("test_tsconfig.json"); @@ -960,7 +1031,8 @@ mod tests { root: Some(temp_dir.path().to_path_buf()), force: true, }, - ); + ) + .await; assert!(result.is_ok()); let config_file_name = ".echo_test.deno.json"; @@ -973,8 +1045,8 @@ mod tests { // TODO: enable on Windows after fixing batch escaping #[cfg(not(windows))] - #[test] - fn install_shell_escaping() { + #[tokio::test] + async fn install_shell_escaping() { let temp_dir = TempDir::new(); let bin_dir = temp_dir.path().join("bin"); std::fs::create_dir(&bin_dir).unwrap(); @@ -989,6 +1061,7 @@ mod tests { force: false, }, ) + .await .unwrap(); let mut file_path = bin_dir.join("echo_test"); @@ -1007,8 +1080,8 @@ mod tests { } } - #[test] - fn install_unicode() { + #[tokio::test] + async fn install_unicode() { let temp_dir = TempDir::new(); let bin_dir = temp_dir.path().join("bin"); std::fs::create_dir(&bin_dir).unwrap(); @@ -1028,6 +1101,7 @@ mod tests { force: false, }, ) + .await .unwrap(); let mut file_path = bin_dir.join("echo_test"); @@ -1047,8 +1121,8 @@ mod tests { assert!(status.success()); } - #[test] - fn install_with_import_map() { + #[tokio::test] + async fn install_with_import_map() { let temp_dir = TempDir::new(); let bin_dir = temp_dir.path().join("bin"); let import_map_path = temp_dir.path().join("import_map.json"); @@ -1070,7 +1144,8 @@ mod tests { root: Some(temp_dir.path().to_path_buf()), force: true, }, - ); + ) + .await; assert!(result.is_ok()); let mut file_path = bin_dir.join("echo_test"); @@ -1093,8 +1168,8 @@ mod tests { } // Regression test for https://github.com/denoland/deno/issues/10556. - #[test] - fn install_file_url() { + #[tokio::test] + async fn install_file_url() { let temp_dir = TempDir::new(); let bin_dir = temp_dir.path().join("bin"); let module_path = fs::canonicalize(testdata_path().join("cat.ts")).unwrap(); @@ -1111,7 +1186,8 @@ mod tests { root: Some(temp_dir.path().to_path_buf()), force: true, }, - ); + ) + .await; assert!(result.is_ok()); let mut file_path = bin_dir.join("echo_test"); diff --git a/cli/tools/repl/session.rs b/cli/tools/repl/session.rs index 0e40a1e32144a2..e9ddd09b1a3a44 100644 --- a/cli/tools/repl/session.rs +++ b/cli/tools/repl/session.rs @@ -2,8 +2,8 @@ use crate::colors; use crate::lsp::ReplLanguageServer; -use crate::npm::NpmPackageReference; use crate::ProcState; + use deno_ast::swc::ast as swc_ast; use deno_ast::swc::visit::noop_visit_type; use deno_ast::swc::visit::Visit; @@ -18,7 +18,9 @@ use deno_core::futures::StreamExt; use deno_core::serde_json; use deno_core::serde_json::Value; use deno_core::LocalInspectorSession; +use deno_graph::npm::NpmPackageReqReference; use deno_graph::source::Resolver; +use deno_runtime::deno_node; use deno_runtime::worker::MainWorker; use super::cdp; @@ -443,26 +445,25 @@ impl ReplSession { .flat_map(|i| { self .proc_state - .maybe_resolver - .as_ref() - .and_then(|resolver| resolver.resolve(i, &self.referrer).ok()) + .resolver + .resolve(i, &self.referrer) + .ok() .or_else(|| ModuleSpecifier::parse(i).ok()) }) .collect::>(); let npm_imports = resolved_imports .iter() - .flat_map(|url| NpmPackageReference::from_specifier(url).ok()) + .flat_map(|url| NpmPackageReqReference::from_specifier(url).ok()) .map(|r| r.req) .collect::>(); let has_node_specifier = resolved_imports.iter().any(|url| url.scheme() == "node"); if !npm_imports.is_empty() || has_node_specifier { if !self.has_initialized_node_runtime { - self.proc_state.prepare_node_std_graph().await?; - crate::node::initialize_runtime( + deno_node::initialize_runtime( &mut self.worker.js_runtime, - self.proc_state.options.node_modules_dir(), + self.proc_state.options.has_node_modules_dir(), ) .await?; self.has_initialized_node_runtime = true; diff --git a/cli/tools/run.rs b/cli/tools/run.rs index ad1d15ecebd54b..c2f21b75a48a4c 100644 --- a/cli/tools/run.rs +++ b/cli/tools/run.rs @@ -9,6 +9,7 @@ use deno_ast::MediaType; use deno_ast::ModuleSpecifier; use deno_core::error::AnyError; use deno_core::resolve_url_or_path; +use deno_graph::npm::NpmPackageReqReference; use deno_runtime::deno_wsi::event_loop::WsiEventLoopProxy; use deno_runtime::permissions::Permissions; use deno_runtime::permissions::PermissionsContainer; @@ -17,7 +18,6 @@ use crate::args::EvalFlags; use crate::args::Flags; use crate::args::RunFlags; use crate::file_fetcher::File; -use crate::npm::NpmPackageReference; use crate::proc_state::ProcState; use crate::util; use crate::worker::create_main_worker; @@ -54,12 +54,12 @@ To grant permissions, set them before the script argument. For example: ps.dir.upgrade_check_file_path(), ); - let main_module = if NpmPackageReference::from_str(&run_flags.script).is_ok() - { - ModuleSpecifier::parse(&run_flags.script)? - } else { - resolve_url_or_path(&run_flags.script)? - }; + let main_module = + if NpmPackageReqReference::from_str(&run_flags.script).is_ok() { + ModuleSpecifier::parse(&run_flags.script)? + } else { + resolve_url_or_path(&run_flags.script)? + }; let permissions = PermissionsContainer::new(Permissions::from_options( &ps.options.permissions_options(), )?); diff --git a/cli/tools/standalone.rs b/cli/tools/standalone.rs index 6085625fb717f1..92b2a524dd41da 100644 --- a/cli/tools/standalone.rs +++ b/cli/tools/standalone.rs @@ -43,7 +43,8 @@ pub async fn compile( let module_specifier = resolve_url_or_path(&compile_flags.source_file)?; let deno_dir = &ps.dir; - let output_path = resolve_compile_executable_output_path(&compile_flags)?; + let output_path = + resolve_compile_executable_output_path(&compile_flags).await?; let graph = Arc::try_unwrap( create_graph_and_maybe_check(module_specifier.clone(), &ps).await?, @@ -53,8 +54,6 @@ pub async fn compile( // at the moment, we don't support npm specifiers in deno_compile, so show an error error_for_any_npm_specifier(&graph)?; - graph.valid()?; - let parser = ps.parsed_source_cache.as_capturing_parser(); let eszip = eszip::EszipV2::from_graph(graph, &parser, Default::default())?; @@ -283,20 +282,33 @@ async fn write_standalone_binary( Ok(()) } -fn resolve_compile_executable_output_path( +async fn resolve_compile_executable_output_path( compile_flags: &CompileFlags, ) -> Result { let module_specifier = resolve_url_or_path(&compile_flags.source_file)?; - compile_flags.output.as_ref().and_then(|output| { - if path_has_trailing_slash(output) { - let infer_file_name = infer_name_from_url(&module_specifier).map(PathBuf::from)?; - Some(output.join(infer_file_name)) + + let mut output = compile_flags.output.clone(); + + if let Some(out) = output.as_ref() { + if path_has_trailing_slash(out) { + if let Some(infer_file_name) = infer_name_from_url(&module_specifier) + .await + .map(PathBuf::from) + { + output = Some(out.join(infer_file_name)); + } } else { - Some(output.to_path_buf()) + output = Some(out.to_path_buf()); } - }).or_else(|| { - infer_name_from_url(&module_specifier).map(PathBuf::from) - }).ok_or_else(|| generic_error( + } + + if output.is_none() { + output = infer_name_from_url(&module_specifier) + .await + .map(PathBuf::from) + } + + output.ok_or_else(|| generic_error( "An executable name was not provided. One could not be inferred from the URL. Aborting.", )).map(|output| { get_os_specific_filepath(output, &compile_flags.target) @@ -327,14 +339,15 @@ fn get_os_specific_filepath( mod test { pub use super::*; - #[test] - fn resolve_compile_executable_output_path_target_linux() { + #[tokio::test] + async fn resolve_compile_executable_output_path_target_linux() { let path = resolve_compile_executable_output_path(&CompileFlags { source_file: "mod.ts".to_string(), output: Some(PathBuf::from("./file")), args: Vec::new(), target: Some("x86_64-unknown-linux-gnu".to_string()), }) + .await .unwrap(); // no extension, no matter what the operating system is @@ -343,14 +356,15 @@ mod test { assert_eq!(path.file_name().unwrap(), "file"); } - #[test] - fn resolve_compile_executable_output_path_target_windows() { + #[tokio::test] + async fn resolve_compile_executable_output_path_target_windows() { let path = resolve_compile_executable_output_path(&CompileFlags { source_file: "mod.ts".to_string(), output: Some(PathBuf::from("./file")), args: Vec::new(), target: Some("x86_64-pc-windows-msvc".to_string()), }) + .await .unwrap(); assert_eq!(path.file_name().unwrap(), "file.exe"); } diff --git a/cli/tools/task.rs b/cli/tools/task.rs index 0b611b9d32ee19..9b76f256cb13ab 100644 --- a/cli/tools/task.rs +++ b/cli/tools/task.rs @@ -8,18 +8,16 @@ use crate::util::fs::canonicalize_path; use deno_core::anyhow::bail; use deno_core::anyhow::Context; use deno_core::error::AnyError; -use std::collections::BTreeMap; +use deno_core::futures; +use deno_core::futures::future::LocalBoxFuture; +use deno_graph::npm::NpmPackageNv; +use deno_task_shell::ExecuteResult; +use deno_task_shell::ShellCommand; +use deno_task_shell::ShellCommandContext; +use indexmap::IndexMap; use std::collections::HashMap; use std::path::PathBuf; - -fn print_available_tasks(tasks_config: BTreeMap) { - eprintln!("{}", colors::green("Available tasks:")); - - for name in tasks_config.keys() { - eprintln!("- {}", colors::cyan(name)); - eprintln!(" {}", tasks_config[name]) - } -} +use std::rc::Rc; pub async fn execute_script( flags: Flags, @@ -27,62 +25,218 @@ pub async fn execute_script( ) -> Result { let ps = ProcState::build(flags).await?; let tasks_config = ps.options.resolve_tasks_config()?; - let config_file_url = ps.options.maybe_config_file_specifier().unwrap(); - let config_file_path = if config_file_url.scheme() == "file" { - config_file_url.to_file_path().unwrap() - } else { - bail!("Only local configuration files are supported") + let maybe_package_json = ps.options.maybe_package_json(); + let package_json_scripts = maybe_package_json + .as_ref() + .and_then(|p| p.scripts.clone()) + .unwrap_or_default(); + + let task_name = match &task_flags.task { + Some(task) => task, + None => { + print_available_tasks(&tasks_config, &package_json_scripts); + return Ok(1); + } }; - if task_flags.task.is_empty() { - print_available_tasks(tasks_config); - return Ok(1); + if let Some(script) = tasks_config.get(task_name) { + let config_file_url = ps.options.maybe_config_file_specifier().unwrap(); + let config_file_path = if config_file_url.scheme() == "file" { + config_file_url.to_file_path().unwrap() + } else { + bail!("Only local configuration files are supported") + }; + let cwd = match task_flags.cwd { + Some(path) => canonicalize_path(&PathBuf::from(path))?, + None => config_file_path.parent().unwrap().to_owned(), + }; + let script = get_script_with_args(script, &ps); + output_task(task_name, &script); + let seq_list = deno_task_shell::parser::parse(&script) + .with_context(|| format!("Error parsing script '{task_name}'."))?; + let env_vars = collect_env_vars(); + let exit_code = + deno_task_shell::execute(seq_list, env_vars, &cwd, Default::default()) + .await; + Ok(exit_code) + } else if let Some(script) = package_json_scripts.get(task_name) { + ps.package_json_deps_installer + .ensure_top_level_install() + .await?; + ps.npm_resolver.resolve_pending().await?; + + let cwd = match task_flags.cwd { + Some(path) => canonicalize_path(&PathBuf::from(path))?, + None => maybe_package_json + .as_ref() + .unwrap() + .path + .parent() + .unwrap() + .to_owned(), + }; + let script = get_script_with_args(script, &ps); + log::info!( + "{} Currently only basic package.json `scripts` are supported. Programs like `rimraf` or `cross-env` will not work correctly. This will be fixed in the upcoming release.", + colors::yellow("Warning"), + ); + output_task(task_name, &script); + let seq_list = deno_task_shell::parser::parse(&script) + .with_context(|| format!("Error parsing script '{task_name}'."))?; + let npx_commands = resolve_npm_commands(&ps)?; + let env_vars = collect_env_vars(); + let exit_code = + deno_task_shell::execute(seq_list, env_vars, &cwd, npx_commands).await; + Ok(exit_code) + } else { + eprintln!("Task not found: {task_name}"); + print_available_tasks(&tasks_config, &package_json_scripts); + Ok(1) } +} - let cwd = match task_flags.cwd { - Some(path) => canonicalize_path(&PathBuf::from(path))?, - None => config_file_path.parent().unwrap().to_owned(), - }; - let task_name = task_flags.task; - let maybe_script = tasks_config.get(&task_name); +fn get_script_with_args(script: &str, ps: &ProcState) -> String { + let additional_args = ps + .options + .argv() + .iter() + // surround all the additional arguments in double quotes + // and santize any command substition + .map(|a| format!("\"{}\"", a.replace('"', "\\\"").replace('$', "\\$"))) + .collect::>() + .join(" "); + let script = format!("{script} {additional_args}"); + script.trim().to_owned() +} + +fn output_task(task_name: &str, script: &str) { + log::info!( + "{} {} {}", + colors::green("Task"), + colors::cyan(&task_name), + script, + ); +} - if let Some(script) = maybe_script { - let additional_args = ps - .options - .argv() +fn collect_env_vars() -> HashMap { + // get the starting env vars (the PWD env var will be set by deno_task_shell) + let mut env_vars = std::env::vars().collect::>(); + const INIT_CWD_NAME: &str = "INIT_CWD"; + if !env_vars.contains_key(INIT_CWD_NAME) { + if let Ok(cwd) = std::env::current_dir() { + // if not set, set an INIT_CWD env var that has the cwd + env_vars + .insert(INIT_CWD_NAME.to_string(), cwd.to_string_lossy().to_string()); + } + } + env_vars +} + +fn print_available_tasks( + // order can be important, so these use an index map + tasks_config: &IndexMap, + package_json_scripts: &IndexMap, +) { + eprintln!("{}", colors::green("Available tasks:")); + + let mut had_task = false; + for (is_deno, (key, value)) in tasks_config.iter().map(|e| (true, e)).chain( + package_json_scripts .iter() - // surround all the additional arguments in double quotes - // and santize any command substition - .map(|a| format!("\"{}\"", a.replace('"', "\\\"").replace('$', "\\$"))) - .collect::>() - .join(" "); - let script = format!("{script} {additional_args}"); - let script = script.trim(); - log::info!( - "{} {} {}", - colors::green("Task"), - colors::cyan(&task_name), - script, + .filter(|(key, _)| !tasks_config.contains_key(*key)) + .map(|e| (false, e)), + ) { + eprintln!( + "- {}{}", + colors::cyan(key), + if is_deno { + "".to_string() + } else { + format!(" {}", colors::italic_gray("(package.json)")) + } ); - let seq_list = deno_task_shell::parser::parse(script) - .with_context(|| format!("Error parsing script '{task_name}'."))?; + eprintln!(" {value}"); + had_task = true; + } + if !had_task { + eprintln!(" {}", colors::red("No tasks found in configuration file")); + } +} + +struct NpxCommand; - // get the starting env vars (the PWD env var will be set by deno_task_shell) - let mut env_vars = std::env::vars().collect::>(); - const INIT_CWD_NAME: &str = "INIT_CWD"; - if !env_vars.contains_key(INIT_CWD_NAME) { - if let Ok(cwd) = std::env::current_dir() { - // if not set, set an INIT_CWD env var that has the cwd - env_vars - .insert(INIT_CWD_NAME.to_string(), cwd.to_string_lossy().to_string()); +impl ShellCommand for NpxCommand { + fn execute( + &self, + mut context: ShellCommandContext, + ) -> LocalBoxFuture<'static, ExecuteResult> { + if let Some(first_arg) = context.args.get(0).cloned() { + if let Some(command) = context.state.resolve_command(&first_arg) { + let context = ShellCommandContext { + args: context.args.iter().skip(1).cloned().collect::>(), + ..context + }; + command.execute(context) + } else { + let _ = context + .stderr + .write_line(&format!("npx: could not resolve command '{first_arg}'")); + Box::pin(futures::future::ready(ExecuteResult::from_exit_code(1))) } + } else { + let _ = context.stderr.write_line("npx: missing command"); + Box::pin(futures::future::ready(ExecuteResult::from_exit_code(1))) } + } +} - let exit_code = deno_task_shell::execute(seq_list, env_vars, &cwd).await; - Ok(exit_code) - } else { - eprintln!("Task not found: {task_name}"); - print_available_tasks(tasks_config); - Ok(1) +#[derive(Clone)] +struct NpmPackageBinCommand { + name: String, + npm_package: NpmPackageNv, +} + +impl ShellCommand for NpmPackageBinCommand { + fn execute( + &self, + context: ShellCommandContext, + ) -> LocalBoxFuture<'static, ExecuteResult> { + let mut args = vec![ + "run".to_string(), + "-A".to_string(), + if self.npm_package.name == self.name { + format!("npm:{}", self.npm_package) + } else { + format!("npm:{}/{}", self.npm_package, self.name) + }, + ]; + args.extend(context.args); + let executable_command = + deno_task_shell::ExecutableCommand::new("deno".to_string()); + executable_command.execute(ShellCommandContext { args, ..context }) + } +} + +fn resolve_npm_commands( + ps: &ProcState, +) -> Result>, AnyError> { + let mut result = HashMap::new(); + let snapshot = ps.npm_resolver.snapshot(); + for id in snapshot.top_level_packages() { + let bin_commands = + crate::node::node_resolve_binary_commands(&id.nv, &ps.npm_resolver)?; + for bin_command in bin_commands { + result.insert( + bin_command.to_string(), + Rc::new(NpmPackageBinCommand { + name: bin_command, + npm_package: id.nv.clone(), + }) as Rc, + ); + } + } + if !result.contains_key("npx") { + result.insert("npx".to_string(), Rc::new(NpxCommand)); } + Ok(result) } diff --git a/cli/tools/test.rs b/cli/tools/test.rs index cd2ac6ba53f47e..d0f3013b38c6fa 100644 --- a/cli/tools/test.rs +++ b/cli/tools/test.rs @@ -7,7 +7,7 @@ use crate::args::TypeCheckMode; use crate::colors; use crate::display; use crate::file_fetcher::File; -use crate::graph_util::graph_valid; +use crate::graph_util::graph_valid_with_cli_options; use crate::ops; use crate::proc_state::ProcState; use crate::util::checksum; @@ -1377,7 +1377,7 @@ pub async fn run_tests_with_watch( test_modules.clone() }; let graph = ps.create_graph(test_modules.clone()).await?; - graph_valid(&graph, !no_check, ps.options.check_js())?; + graph_valid_with_cli_options(&graph, &test_modules, &ps.options)?; // TODO(@kitsonk) - This should be totally derivable from the graph. for specifier in test_modules { @@ -1389,7 +1389,7 @@ pub async fn run_tests_with_watch( output: &mut HashSet<&'a ModuleSpecifier>, no_check: bool, ) { - if let Some(module) = maybe_module { + if let Some(module) = maybe_module.and_then(|m| m.esm()) { for dep in module.dependencies.values() { if let Some(specifier) = &dep.get_code() { if !output.contains(specifier) { diff --git a/cli/tools/upgrade.rs b/cli/tools/upgrade.rs index b1561470ef7b7f..6f3b6b4ae9793d 100644 --- a/cli/tools/upgrade.rs +++ b/cli/tools/upgrade.rs @@ -17,6 +17,7 @@ use deno_core::anyhow::Context; use deno_core::error::AnyError; use deno_core::futures::future::BoxFuture; use deno_core::futures::FutureExt; +use deno_graph::semver::Version; use once_cell::sync::Lazy; use std::borrow::Cow; use std::env; @@ -124,7 +125,7 @@ impl UpdateChecker { let file = self.maybe_file.as_ref()?; // If the current version saved is not the actualy current version of the binary // It means - // - We already check for a new vesion today + // - We already check for a new version today // - The user have probably upgraded today // So we should not prompt and wait for tomorrow for the latest version to be updated again if file.current_version != self.env.current_version() { @@ -134,8 +135,8 @@ impl UpdateChecker { return None; } - if let Ok(current) = semver::Version::parse(&self.env.current_version()) { - if let Ok(latest) = semver::Version::parse(&file.latest_version) { + if let Ok(current) = Version::parse_standard(&self.env.current_version()) { + if let Ok(latest) = Version::parse_standard(&file.latest_version) { if current >= latest { return None; } @@ -288,9 +289,9 @@ pub async fn upgrade( { bail!("Invalid commit hash passed"); } else if !upgrade_flags.canary - && semver::Version::parse(&passed_version).is_err() + && Version::parse_standard(&passed_version).is_err() { - bail!("Invalid semver passed"); + bail!("Invalid version passed"); } let current_is_passed = if upgrade_flags.canary { @@ -328,8 +329,8 @@ pub async fn upgrade( crate::version::GIT_COMMIT_HASH == latest_hash } else if !crate::version::is_canary() { let current = - semver::Version::parse(&crate::version::denog_short()).unwrap(); - let latest = semver::Version::parse(&latest_version).unwrap(); + Version::parse_standard(&crate::version::denog_short()).unwrap(); + let latest = Version::parse_standard(&latest_version).unwrap(); current >= latest } else { false @@ -510,7 +511,17 @@ pub fn unpack_into_dir( .arg(format!("'{}'", &archive_path.to_str().unwrap())) .arg("-DestinationPath") .arg(format!("'{}'", &temp_dir_path.to_str().unwrap())) - .spawn()? + .spawn() + .map_err(|err| { + if err.kind() == std::io::ErrorKind::NotFound { + std::io::Error::new( + std::io::ErrorKind::NotFound, + "`powershell.exe` was not found in your PATH", + ) + } else { + err + } + })? .wait()? } "zip" => { @@ -523,7 +534,7 @@ pub fn unpack_into_dir( if err.kind() == std::io::ErrorKind::NotFound { std::io::Error::new( std::io::ErrorKind::NotFound, - "`unzip` was not found on your PATH, please install `unzip`", + "`unzip` was not found in your PATH, please install `unzip`", ) } else { err diff --git a/cli/tools/vendor/build.rs b/cli/tools/vendor/build.rs index f418670b3a77c6..3bee843fd63d43 100644 --- a/cli/tools/vendor/build.rs +++ b/cli/tools/vendor/build.rs @@ -10,14 +10,15 @@ use deno_core::anyhow::bail; use deno_core::anyhow::Context; use deno_core::error::AnyError; use deno_core::parking_lot::Mutex; +use deno_graph::EsmModule; use deno_graph::Module; use deno_graph::ModuleGraph; -use deno_graph::ModuleKind; use import_map::ImportMap; use import_map::SpecifierMap; use crate::args::Lockfile; use crate::cache::ParsedSourceCache; +use crate::graph_util; use crate::graph_util::graph_lock_or_exit; use super::analyze::has_default_export; @@ -72,24 +73,27 @@ pub fn build( validate_original_import_map(original_im, &output_dir_specifier)?; } - // build the graph + // check the lockfile if let Some(lockfile) = maybe_lockfile { graph_lock_or_exit(&graph, &mut lockfile.lock()); } - let mut graph_errors = graph.errors().peekable(); - if graph_errors.peek().is_some() { - for err in graph_errors { - log::error!("{}", err); - } - bail!("failed vendoring"); - } + // surface any errors + graph_util::graph_valid( + &graph, + &graph.roots, + graph_util::GraphValidOptions { + is_vendoring: true, + check_js: true, + follow_type_only: true, + }, + )?; // figure out how to map remote modules to local let all_modules = graph.modules().collect::>(); let remote_modules = all_modules .iter() - .filter(|m| is_remote_specifier(&m.specifier)) + .filter(|m| is_remote_specifier(m.specifier())) .copied() .collect::>(); let mappings = @@ -97,21 +101,16 @@ pub fn build( // write out all the files for module in &remote_modules { - let source = match &module.maybe_source { - Some(source) => source, - None => continue, + let source = match module { + Module::Esm(module) => &module.source, + Module::Json(module) => &module.source, + Module::Node(_) | Module::Npm(_) | Module::External(_) => continue, }; + let specifier = module.specifier(); let local_path = mappings - .proxied_path(&module.specifier) - .unwrap_or_else(|| mappings.local_path(&module.specifier)); - if !matches!(module.kind, ModuleKind::Esm | ModuleKind::Asserted) { - log::warn!( - "Unsupported module kind {:?} for {}", - module.kind, - module.specifier - ); - continue; - } + .proxied_path(specifier) + .unwrap_or_else(|| mappings.local_path(specifier)); + environment.create_dir_all(local_path.parent().unwrap())?; environment.write_file(&local_path, source)?; } @@ -119,7 +118,7 @@ pub fn build( // write out the proxies for (specifier, proxied_module) in mappings.proxied_modules() { let proxy_path = mappings.local_path(specifier); - let module = graph.get(specifier).unwrap(); + let module = graph.get(specifier).unwrap().esm().unwrap(); let text = build_proxy_module_source(module, proxied_module, parsed_source_cache)?; @@ -181,7 +180,7 @@ fn validate_original_import_map( } fn build_proxy_module_source( - module: &Module, + module: &EsmModule, proxied_module: &ProxiedModule, parsed_source_cache: &ParsedSourceCache, ) -> Result { @@ -207,13 +206,11 @@ fn build_proxy_module_source( writeln!(text, "export * from \"{relative_specifier}\";").unwrap(); // add a default export if one exists in the module - if let Some(parsed_source) = - parsed_source_cache.get_parsed_source_from_module(module)? - { - if has_default_export(&parsed_source) { - writeln!(text, "export {{ default }} from \"{relative_specifier}\";") - .unwrap(); - } + let parsed_source = + parsed_source_cache.get_parsed_source_from_esm_module(module)?; + if has_default_export(&parsed_source) { + writeln!(text, "export {{ default }} from \"{relative_specifier}\";") + .unwrap(); } Ok(text) @@ -1119,7 +1116,13 @@ mod test { .err() .unwrap(); - assert_eq!(err.to_string(), "failed vendoring"); + assert_eq!( + err.to_string(), + concat!( + "500 Internal Server Error\n", + " at https://localhost/mod.ts:1:14" + ) + ); } fn to_file_vec(items: &[(&str, &str)]) -> Vec<(String, String)> { diff --git a/cli/tools/vendor/import_map.rs b/cli/tools/vendor/import_map.rs index 0897cbcf68975c..916eb55c58e57c 100644 --- a/cli/tools/vendor/import_map.rs +++ b/cli/tools/vendor/import_map.rs @@ -4,12 +4,11 @@ use deno_ast::LineAndColumnIndex; use deno_ast::ModuleSpecifier; use deno_ast::SourceTextInfo; use deno_core::error::AnyError; -use deno_graph::MediaType; use deno_graph::Module; use deno_graph::ModuleGraph; use deno_graph::Position; use deno_graph::Range; -use deno_graph::Resolved; +use deno_graph::Resolution; use import_map::ImportMap; use import_map::SpecifierMap; use indexmap::IndexMap; @@ -205,23 +204,22 @@ fn visit_modules( parsed_source_cache: &ParsedSourceCache, ) -> Result<(), AnyError> { for module in modules { - if module.media_type == MediaType::Json { + let module = match module { + Module::Esm(module) => module, // skip visiting Json modules as they are leaves - continue; - } - - let text_info = - match parsed_source_cache.get_parsed_source_from_module(module)? { - Some(source) => source.text_info().clone(), - None => continue, - }; - let source_text = match &module.maybe_source { - Some(source) => source, - None => continue, + Module::Json(_) + | Module::Npm(_) + | Module::Node(_) + | Module::External(_) => continue, }; + let parsed_source = + parsed_source_cache.get_parsed_source_from_esm_module(module)?; + let text_info = parsed_source.text_info().clone(); + let source_text = &module.source; + for dep in module.dependencies.values() { - visit_maybe_resolved( + visit_resolution( &dep.maybe_code, graph, import_map, @@ -230,7 +228,7 @@ fn visit_modules( &text_info, source_text, ); - visit_maybe_resolved( + visit_resolution( &dep.maybe_type, graph, import_map, @@ -241,9 +239,9 @@ fn visit_modules( ); } - if let Some((_, maybe_resolved)) = &module.maybe_types_dependency { - visit_maybe_resolved( - maybe_resolved, + if let Some(types_dep) = &module.maybe_types_dependency { + visit_resolution( + &types_dep.dependency, graph, import_map, &module.specifier, @@ -257,8 +255,8 @@ fn visit_modules( Ok(()) } -fn visit_maybe_resolved( - maybe_resolved: &Resolved, +fn visit_resolution( + resolution: &Resolution, graph: &ModuleGraph, import_map: &mut ImportMapBuilder, referrer: &ModuleSpecifier, @@ -266,15 +264,17 @@ fn visit_maybe_resolved( text_info: &SourceTextInfo, source_text: &str, ) { - if let Resolved::Ok { - specifier, range, .. - } = maybe_resolved - { - let text = text_from_range(text_info, source_text, range); + if let Some(resolved) = resolution.ok() { + let text = text_from_range(text_info, source_text, &resolved.range); // if the text is empty then it's probably an x-TypeScript-types if !text.is_empty() { handle_dep_specifier( - text, specifier, graph, import_map, referrer, mappings, + text, + &resolved.specifier, + graph, + import_map, + referrer, + mappings, ); } } @@ -288,7 +288,12 @@ fn handle_dep_specifier( referrer: &ModuleSpecifier, mappings: &Mappings, ) { - let specifier = graph.resolve(unresolved_specifier); + let specifier = match graph.get(unresolved_specifier) { + Some(module) => module.specifier().clone(), + // Ignore when None. The graph was previous validated so this is a + // dynamic import that was missing and is ignored for vendoring + None => return, + }; // check if it's referencing a remote module if is_remote_specifier(&specifier) { handle_remote_dep_specifier( diff --git a/cli/tools/vendor/mappings.rs b/cli/tools/vendor/mappings.rs index 8cf6388d2dd95f..1ecc14edfb4bed 100644 --- a/cli/tools/vendor/mappings.rs +++ b/cli/tools/vendor/mappings.rs @@ -11,7 +11,6 @@ use deno_core::error::AnyError; use deno_graph::Module; use deno_graph::ModuleGraph; use deno_graph::Position; -use deno_graph::Resolved; use crate::util::path::path_with_stem_suffix; use crate::util::path::relative_specifier; @@ -40,8 +39,9 @@ impl Mappings { remote_modules: &[&Module], output_dir: &Path, ) -> Result { - let partitioned_specifiers = - partition_by_root_specifiers(remote_modules.iter().map(|m| &m.specifier)); + let partitioned_specifiers = partition_by_root_specifiers( + remote_modules.iter().map(|m| m.specifier()), + ); let mut mapped_paths = HashSet::new(); let mut mappings = HashMap::new(); let mut proxies = HashMap::new(); @@ -53,7 +53,12 @@ impl Mappings { &mut mapped_paths, ); for specifier in specifiers { - let media_type = graph.get(&specifier).unwrap().media_type; + let module = graph.get(&specifier).unwrap(); + let media_type = match module { + Module::Esm(module) => module.media_type, + Module::Json(_) => MediaType::Json, + Module::Node(_) | Module::Npm(_) | Module::External(_) => continue, + }; let sub_path = sanitize_filepath(&make_url_relative(&root, &{ let mut specifier = specifier.clone(); specifier.set_query(None); @@ -76,29 +81,30 @@ impl Mappings { // resolve all the "proxy" paths to use for when an x-typescript-types header is specified for module in remote_modules { - if let Some(( - _, - Resolved::Ok { - specifier, range, .. - }, - )) = &module.maybe_types_dependency - { - // hack to tell if it's an x-typescript-types header - let is_ts_types_header = - range.start == Position::zeroed() && range.end == Position::zeroed(); - if is_ts_types_header { - let module_path = mappings.get(&module.specifier).unwrap(); - let proxied_path = get_unique_path( - path_with_stem_suffix(module_path, ".proxied"), - &mut mapped_paths, - ); - proxies.insert( - module.specifier.clone(), - ProxiedModule { - output_path: proxied_path, - declaration_specifier: specifier.clone(), - }, - ); + if let Some(module) = module.esm() { + if let Some(resolved) = &module + .maybe_types_dependency + .as_ref() + .and_then(|d| d.dependency.ok()) + { + let range = &resolved.range; + // hack to tell if it's an x-typescript-types header + let is_ts_types_header = range.start == Position::zeroed() + && range.end == Position::zeroed(); + if is_ts_types_header { + let module_path = mappings.get(&module.specifier).unwrap(); + let proxied_path = get_unique_path( + path_with_stem_suffix(module_path, ".proxied"), + &mut mapped_paths, + ); + proxies.insert( + module.specifier.clone(), + ProxiedModule { + output_path: proxied_path, + declaration_specifier: resolved.specifier.clone(), + }, + ); + } } } } diff --git a/cli/tools/vendor/mod.rs b/cli/tools/vendor/mod.rs index 209eb30b774912..e3536a00d76891 100644 --- a/cli/tools/vendor/mod.rs +++ b/cli/tools/vendor/mod.rs @@ -156,7 +156,7 @@ fn maybe_update_config_file(output_dir: &Path, ps: &ProcState) -> bool { let fmt_config = ps .options - .get_maybe_config_file() + .maybe_config_file() .as_ref() .and_then(|config| config.to_fmt_config().ok()) .unwrap_or_default() diff --git a/cli/tools/vendor/test.rs b/cli/tools/vendor/test.rs index a24fa1e4f33239..bf34fc185788db 100644 --- a/cli/tools/vendor/test.rs +++ b/cli/tools/vendor/test.rs @@ -20,7 +20,10 @@ use deno_graph::ModuleGraph; use import_map::ImportMap; use crate::cache::ParsedSourceCache; -use crate::resolver::CliResolver; +use crate::npm::NpmRegistryApi; +use crate::npm::NpmResolution; +use crate::npm::PackageJsonDepsInstaller; +use crate::resolver::CliGraphResolver; use super::build::VendorEnvironment; @@ -260,20 +263,37 @@ async fn build_test_graph( mut loader: TestLoader, analyzer: &dyn deno_graph::ModuleAnalyzer, ) -> ModuleGraph { - let resolver = - original_import_map.map(|m| CliResolver::with_import_map(Arc::new(m))); - deno_graph::create_graph( - roots, - &mut loader, - deno_graph::GraphOptions { - is_dynamic: false, - imports: None, - resolver: resolver.as_ref().map(|r| r.as_graph_resolver()), - module_analyzer: Some(analyzer), - reporter: None, - }, - ) - .await + let resolver = original_import_map.map(|m| { + let npm_registry_api = NpmRegistryApi::new_uninitialized(); + let npm_resolution = + NpmResolution::new(npm_registry_api.clone(), None, None); + let deps_installer = PackageJsonDepsInstaller::new( + npm_registry_api.clone(), + npm_resolution.clone(), + None, + ); + CliGraphResolver::new( + None, + Some(Arc::new(m)), + false, + npm_registry_api, + npm_resolution, + deps_installer, + ) + }); + let mut graph = ModuleGraph::default(); + graph + .build( + roots, + &mut loader, + deno_graph::BuildOptions { + resolver: resolver.as_ref().map(|r| r.as_graph_resolver()), + module_analyzer: Some(analyzer), + ..Default::default() + }, + ) + .await; + graph } fn make_path(text: &str) -> PathBuf { diff --git a/cli/tsc/99_main_compiler.js b/cli/tsc/99_main_compiler.js index 138b24ba0d8982..bd966f03bd9178 100644 --- a/cli/tsc/99_main_compiler.js +++ b/cli/tsc/99_main_compiler.js @@ -93,43 +93,6 @@ delete Object.prototype.__proto__; } } - // deno-fmt-ignore - const base64abc = [ - "A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O", - "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z", "a", "b", "c", "d", - "e", "f", "g", "h", "i", "j", "k", "l", "m", "n", "o", "p", "q", "r", "s", - "t", "u", "v", "w", "x", "y", "z", "0", "1", "2", "3", "4", "5", "6", "7", - "8", "9", "+", "/", - ]; - - /** Taken from https://deno.land/std/encoding/base64.ts */ - function convertToBase64(data) { - const uint8 = core.encode(data); - let result = "", - i; - const l = uint8.length; - for (i = 2; i < l; i += 3) { - result += base64abc[uint8[i - 2] >> 2]; - result += base64abc[((uint8[i - 2] & 0x03) << 4) | (uint8[i - 1] >> 4)]; - result += base64abc[((uint8[i - 1] & 0x0f) << 2) | (uint8[i] >> 6)]; - result += base64abc[uint8[i] & 0x3f]; - } - if (i === l + 1) { - // 1 octet yet to write - result += base64abc[uint8[i - 2] >> 2]; - result += base64abc[(uint8[i - 2] & 0x03) << 4]; - result += "=="; - } - if (i === l) { - // 2 octets yet to write - result += base64abc[uint8[i - 2] >> 2]; - result += base64abc[((uint8[i - 2] & 0x03) << 4) | (uint8[i - 1] >> 4)]; - result += base64abc[(uint8[i - 1] & 0x0f) << 2]; - result += "="; - } - return result; - } - class SpecifierIsCjsCache { /** @type {Set} */ #cache = new Set(); @@ -818,14 +781,6 @@ delete Object.prototype.__proto__; * @param {Request} request */ function exec({ config, debug: debugFlag, rootNames }) { - // https://github.com/microsoft/TypeScript/issues/49150 - ts.base64encode = function (host, input) { - if (host && host.base64encode) { - return host.base64encode(input); - } - return convertToBase64(input); - }; - setLogDebug(debugFlag, "TS"); performanceStart(); if (logDebug) { diff --git a/cli/tsc/diagnostics.rs b/cli/tsc/diagnostics.rs index 461cda77511d6e..a865daa9d938c3 100644 --- a/cli/tsc/diagnostics.rs +++ b/cli/tsc/diagnostics.rs @@ -29,15 +29,6 @@ const UNSTABLE_DENO_PROPS: &[&str] = &[ "removeSignalListener", "shutdown", "umask", - "Child", - "ChildProcess", - "ChildStatus", - "SpawnOutput", - "command", - "Command", - "CommandOptions", - "CommandStatus", - "CommandOutput", "serve", "ServeInit", "ServeTlsInit", diff --git a/cli/tsc/dts/lib.deno.ns.d.ts b/cli/tsc/dts/lib.deno.ns.d.ts index 8d65341de92394..154b5f15ffeefd 100644 --- a/cli/tsc/dts/lib.deno.ns.d.ts +++ b/cli/tsc/dts/lib.deno.ns.d.ts @@ -219,6 +219,12 @@ declare namespace Deno { * * @category Errors */ export class Interrupted extends Error {} + /** + * Raised when the underlying operating system would need to block to + * complete but an asynchronous (non-blocking) API is used. + * + * @category Errors */ + export class WouldBlock extends Error {} /** * Raised when expecting to write to a IO buffer resulted in zero bytes * being written. @@ -440,6 +446,20 @@ declare namespace Deno { */ export function osRelease(): string; + /** + * Returns the Operating System uptime in number of seconds. + * + * ```ts + * console.log(Deno.osUptime()); + * ``` + * + * Requires `allow-sys` permission. + * + * @tags allow-sys + * @category Runtime Environment + */ + export function osUptime(): number; + /** * Options which define the permissions within a test or worker context. * @@ -3952,6 +3972,218 @@ declare namespace Deno { */ export function run(opt: T): Process; + /** Create a child process. + * + * If any stdio options are not set to `"piped"`, accessing the corresponding + * field on the `Command` or its `CommandOutput` will throw a `TypeError`. + * + * If `stdin` is set to `"piped"`, the `stdin` {@linkcode WritableStream} + * needs to be closed manually. + * + * @example Spawn a subprocess and pipe the output to a file + * + * ```ts + * const command = new Deno.Command(Deno.execPath(), { + * args: [ + * "eval", + * "console.log('Hello World')", + * ], + * stdin: "piped", + * }); + * const child = command.spawn(); + * + * // open a file and pipe the subprocess output to it. + * child.stdout.pipeTo(Deno.openSync("output").writable); + * + * // manually close stdin + * child.stdin.close(); + * const status = await child.status; + * ``` + * + * @example Spawn a subprocess and collect its output + * + * ```ts + * const command = new Deno.Command(Deno.execPath(), { + * args: [ + * "eval", + * "console.log('hello'); console.error('world')", + * ], + * }); + * const { code, stdout, stderr } = await command.output(); + * console.assert(code === 0); + * console.assert("hello\n" === new TextDecoder().decode(stdout)); + * console.assert("world\n" === new TextDecoder().decode(stderr)); + * ``` + * + * @example Spawn a subprocess and collect its output synchronously + * + * ```ts + * const command = new Deno.Command(Deno.execPath(), { + * args: [ + * "eval", + * "console.log('hello'); console.error('world')", + * ], + * }); + * const { code, stdout, stderr } = command.outputSync(); + * console.assert(code === 0); + * console.assert("hello\n" === new TextDecoder().decode(stdout)); + * console.assert("world\n" === new TextDecoder().decode(stderr)); + * ``` + * + * @category Sub Process + */ + export class Command { + constructor(command: string | URL, options?: CommandOptions); + /** + * Executes the {@linkcode Deno.Command}, waiting for it to finish and + * collecting all of its output. + * If `spawn()` was called, calling this function will collect the remaining + * output. + * + * Will throw an error if `stdin: "piped"` is set. + * + * If options `stdout` or `stderr` are not set to `"piped"`, accessing the + * corresponding field on {@linkcode Deno.CommandOutput} will throw a `TypeError`. + */ + output(): Promise; + /** + * Synchronously executes the {@linkcode Deno.Command}, waiting for it to + * finish and collecting all of its output. + * + * Will throw an error if `stdin: "piped"` is set. + * + * If options `stdout` or `stderr` are not set to `"piped"`, accessing the + * corresponding field on {@linkcode Deno.CommandOutput} will throw a `TypeError`. + */ + outputSync(): CommandOutput; + /** + * Spawns a streamable subprocess, allowing to use the other methods. + */ + spawn(): ChildProcess; + } + + /** + * The interface for handling a child process returned from + * {@linkcode Deno.Command.spawn}. + * + * @category Sub Process + */ + export class ChildProcess { + get stdin(): WritableStream; + get stdout(): ReadableStream; + get stderr(): ReadableStream; + readonly pid: number; + /** Get the status of the child. */ + readonly status: Promise; + + /** Waits for the child to exit completely, returning all its output and + * status. */ + output(): Promise; + /** Kills the process with given {@linkcode Deno.Signal}. + * + * @param [signo="SIGTERM"] + */ + kill(signo?: Signal): void; + + /** Ensure that the status of the child process prevents the Deno process + * from exiting. */ + ref(): void; + /** Ensure that the status of the child process does not block the Deno + * process from exiting. */ + unref(): void; + } + + /** + * Options which can be set when calling {@linkcode Deno.Command}. + * + * @category Sub Process + */ + export interface CommandOptions { + /** Arguments to pass to the process. */ + args?: string[]; + /** + * The working directory of the process. + * + * If not specified, the `cwd` of the parent process is used. + */ + cwd?: string | URL; + /** + * Clear environmental variables from parent process. + * + * Doesn't guarantee that only `env` variables are present, as the OS may + * set environmental variables for processes. + * + * @default {false} + */ + clearEnv?: boolean; + /** Environmental variables to pass to the subprocess. */ + env?: Record; + /** + * Sets the child process’s user ID. This translates to a setuid call in the + * child process. Failure in the set uid call will cause the spawn to fail. + */ + uid?: number; + /** Similar to `uid`, but sets the group ID of the child process. */ + gid?: number; + /** + * An {@linkcode AbortSignal} that allows closing the process using the + * corresponding {@linkcode AbortController} by sending the process a + * SIGTERM signal. + * + * Not supported in {@linkcode Deno.Command.outputSync}. + */ + signal?: AbortSignal; + + /** How `stdin` of the spawned process should be handled. + * + * Defaults to `"inherit"` for `output` & `outputSync`, + * and `"inherit"` for `spawn`. */ + stdin?: "piped" | "inherit" | "null"; + /** How `stdout` of the spawned process should be handled. + * + * Defaults to `"piped"` for `output` & `outputSync`, + * and `"inherit"` for `spawn`. */ + stdout?: "piped" | "inherit" | "null"; + /** How `stderr` of the spawned process should be handled. + * + * Defaults to `"piped"` for `output` & `outputSync`, + * and `"inherit"` for `spawn`. */ + stderr?: "piped" | "inherit" | "null"; + + /** Skips quoting and escaping of the arguments on windows. This option + * is ignored on non-windows platforms. + * + * @default {false} */ + windowsRawArguments?: boolean; + } + + /** + * @category Sub Process + */ + export interface CommandStatus { + /** If the child process exits with a 0 status code, `success` will be set + * to `true`, otherwise `false`. */ + success: boolean; + /** The exit code of the child process. */ + code: number; + /** The signal associated with the child process. */ + signal: Signal | null; + } + + /** + * The interface returned from calling {@linkcode Command.output} or + * {@linkcode Command.outputSync} which represents the result of spawning the + * child process. + * + * @category Sub Process + */ + export interface CommandOutput extends CommandStatus { + /** The buffered output from the child process' `stdout`. */ + readonly stdout: Uint8Array; + /** The buffered output from the child process' `stderr`. */ + readonly stderr: Uint8Array; + } + /** Option which can be specified when performing {@linkcode Deno.inspect}. * * @category Console and Debugging */ @@ -4377,7 +4609,7 @@ declare namespace Deno { * const status = await Deno.permissions.query({ name: "read", path: "/etc" }); * console.log(status.state); * ``` - * + * * ```ts * const status = Deno.permissions.querySync({ name: "read", path: "/etc" }); * console.log(status.state); @@ -4391,7 +4623,7 @@ declare namespace Deno { * const status = await Deno.permissions.revoke({ name: "run" }); * assert(status.state !== "granted") * ``` - * + * * ```ts * import { assert } from "https://deno.land/std/testing/asserts.ts"; * @@ -4409,7 +4641,7 @@ declare namespace Deno { * console.log("'env' permission is denied."); * } * ``` - * + * * ```ts * const status = Deno.permissions.requestSync({ name: "env" }); * if (status.state === "granted") { @@ -4443,7 +4675,7 @@ declare namespace Deno { arch: "x86_64" | "aarch64"; /** The operating system that the Deno CLI was built for. `"darwin"` is * also known as OSX or MacOS. */ - os: "darwin" | "linux" | "windows"; + os: "darwin" | "linux" | "windows" | "freebsd" | "netbsd" | "aix" | "solaris" | "illumos"; /** The computer vendor that the Deno CLI was built for. */ vendor: string; /** Optional environment flags that were set for this build of Deno CLI. */ @@ -4989,6 +5221,12 @@ declare namespace Deno { * @default {53} */ port?: number; }; + /** + * An abort signal to allow cancellation of the DNS resolution operation. + * If the signal becomes aborted the resolveDns operation will be stopped + * and the promise returned will be rejected with an AbortError. + */ + signal?: AbortSignal; } /** If {@linkcode Deno.resolveDns} is called with `"CAA"` record type diff --git a/cli/tsc/dts/lib.deno.unstable.d.ts b/cli/tsc/dts/lib.deno.unstable.d.ts index b5b8b03e856b25..1633adb0ebe6fd 100644 --- a/cli/tsc/dts/lib.deno.unstable.d.ts +++ b/cli/tsc/dts/lib.deno.unstable.d.ts @@ -130,10 +130,10 @@ declare namespace Deno { */ type ToNativeTypeMap = & Record - & Record + & Record & Record - & Record - & Record + & Record + & Record & Record; /** **UNSTABLE**: New API, yet to be vetted. @@ -190,7 +190,7 @@ declare namespace Deno { */ type FromNativeTypeMap = & Record - & Record + & Record & Record & Record & Record @@ -339,6 +339,9 @@ declare namespace Deno { [K in keyof T]: StaticForeignSymbol; }; + const brand: unique symbol; + type PointerObject = { [brand]: unknown }; + /** **UNSTABLE**: New API, yet to be vetted. * * Pointer type depends on the architecture and actual pointer value. @@ -349,7 +352,7 @@ declare namespace Deno { * * @category FFI */ - export type PointerValue = number | bigint; + export type PointerValue = null | PointerObject; /** **UNSTABLE**: New API, yet to be vetted. * @@ -359,8 +362,16 @@ declare namespace Deno { * @category FFI */ export class UnsafePointer { + /** Create a pointer from a numeric value. This is one is really dangerous! */ + static create(value: number | bigint): PointerValue; + /** Returns `true` if the two pointers point to the same address. */ + static equals(a: PointerValue, b: PointerValue): boolean; /** Return the direct memory pointer to the typed array in memory. */ static of(value: Deno.UnsafeCallback | BufferSource): PointerValue; + /** Return a new pointer offset from the original by `offset` bytes. */ + static offset(value: NonNullable, offset: number): PointerValue + /** Get the numeric value of a pointer */ + static value(value: PointerValue): number | bigint; } /** **UNSTABLE**: New API, yet to be vetted. @@ -373,9 +384,9 @@ declare namespace Deno { * @category FFI */ export class UnsafePointerView { - constructor(pointer: PointerValue); + constructor(pointer: NonNullable); - pointer: PointerValue; + pointer: NonNullable; /** Gets a boolean at the specified byte offset from the pointer. */ getBool(offset?: number): boolean; @@ -399,29 +410,31 @@ declare namespace Deno { getInt32(offset?: number): number; /** Gets an unsigned 64-bit integer at the specified byte offset from the * pointer. */ - getBigUint64(offset?: number): PointerValue; + getBigUint64(offset?: number): number | bigint; /** Gets a signed 64-bit integer at the specified byte offset from the * pointer. */ - getBigInt64(offset?: number): PointerValue; + getBigInt64(offset?: number): number | bigint; /** Gets a signed 32-bit float at the specified byte offset from the * pointer. */ getFloat32(offset?: number): number; /** Gets a signed 64-bit float at the specified byte offset from the * pointer. */ getFloat64(offset?: number): number; + /** Gets a pointer at the specified byte offset from the pointer */ + getPointer(offset?: number): PointerValue; /** Gets a C string (`null` terminated string) at the specified byte offset * from the pointer. */ getCString(offset?: number): string; /** Gets a C string (`null` terminated string) at the specified byte offset * from the specified pointer. */ - static getCString(pointer: PointerValue, offset?: number): string; + static getCString(pointer: NonNullable, offset?: number): string; /** Gets an `ArrayBuffer` of length `byteLength` at the specified byte * offset from the pointer. */ getArrayBuffer(byteLength: number, offset?: number): ArrayBuffer; /** Gets an `ArrayBuffer` of length `byteLength` at the specified byte * offset from the specified pointer. */ static getArrayBuffer( - pointer: PointerValue, + pointer: NonNullable, byteLength: number, offset?: number, ): ArrayBuffer; @@ -437,7 +450,7 @@ declare namespace Deno { * * Also takes optional byte offset from the pointer. */ static copyInto( - pointer: PointerValue, + pointer: NonNullable, destination: BufferSource, offset?: number, ): void; @@ -452,11 +465,11 @@ declare namespace Deno { */ export class UnsafeFnPointer { /** The pointer to the function. */ - pointer: PointerValue; + pointer: NonNullable; /** The definition of the function. */ definition: Fn; - constructor(pointer: PointerValue, definition: Const); + constructor(pointer: NonNullable, definition: Const); /** Call the foreign function. */ call: FromForeignFunction; @@ -498,46 +511,80 @@ declare namespace Deno { * * The function pointer remains valid until the `close()` method is called. * - * The callback can be explicitly referenced via `ref()` and dereferenced via - * `deref()` to stop Deno's process from exiting. + * All `UnsafeCallback` are always thread safe in that they can be called from + * foreign threads without crashing. However, they do not wake up the Deno event + * loop by default. + * + * If a callback is to be called from foreign threads, use the `threadSafe()` + * static constructor or explicitly call `ref()` to have the callback wake up + * the Deno event loop when called from foreign threads. This also stops + * Deno's process from exiting while the callback still exists and is not + * unref'ed. + * + * Use `deref()` to then allow Deno's process to exit. Calling `deref()` on + * a ref'ed callback does not stop it from waking up the Deno event loop when + * called from foreign threads. * * @category FFI */ export class UnsafeCallback< - Definition extends UnsafeCallbackDefinition = UnsafeCallbackDefinition, + Definition extends UnsafeCallbackDefinition = UnsafeCallbackDefinition > { constructor( definition: Const, callback: UnsafeCallbackFunction< Definition["parameters"], Definition["result"] - >, + > ); /** The pointer to the unsafe callback. */ - pointer: PointerValue; + readonly pointer: NonNullable; /** The definition of the unsafe callback. */ - definition: Definition; + readonly definition: Definition; /** The callback function. */ - callback: UnsafeCallbackFunction< + readonly callback: UnsafeCallbackFunction< Definition["parameters"], Definition["result"] >; /** - * Adds one to this callback's reference counting and returns the new + * Creates an {@linkcode UnsafeCallback} and calls `ref()` once to allow it to + * wake up the Deno event loop when called from foreign threads. + * + * This also stops Deno's process from exiting while the callback still + * exists and is not unref'ed. + */ + static threadSafe< + Definition extends UnsafeCallbackDefinition = UnsafeCallbackDefinition + >( + definition: Const, + callback: UnsafeCallbackFunction< + Definition["parameters"], + Definition["result"] + > + ): UnsafeCallback; + + /** + * Increments the callback's reference counting and returns the new * reference count. * - * If the callback's reference count is non-zero, it will keep Deno's + * After `ref()` has been called, the callback always wakes up the + * Deno event loop when called from foreign threads. + * + * If the callback's reference count is non-zero, it keeps Deno's * process from exiting. */ ref(): number; /** - * Removes one from this callback's reference counting and returns the new + * Decrements the callback's reference counting and returns the new * reference count. * - * If the callback's reference counter is zero, it will no longer keep + * Calling `unref()` does not stop a callback from waking up the Deno + * event loop when called from foreign threads. + * + * If the callback's reference counter is zero, it no longer keeps * Deno's process from exiting. */ unref(): number; @@ -545,11 +592,12 @@ declare namespace Deno { /** * Removes the C function pointer associated with this instance. * - * Continuing to use the instance after calling this object will lead to - * errors and crashes. + * Continuing to use the instance or the C function pointer after closing + * the `UnsafeCallback` will lead to errors and crashes. * - * Calling this method will also immediately set the callback's reference - * counting to zero and it will no longer keep Deno's process from exiting. + * Calling this method sets the callback's reference counting to zero, + * stops the callback from waking up the Deno event loop when called from + * foreign threads and no longer keeps Deno's process from exiting. */ close(): void; } @@ -1115,6 +1163,18 @@ declare namespace Deno { */ export function funlockSync(rid: number): void; + /** **UNSTABLE**: New API, yet to be vetted. + * + * Information for a HTTP request. + * + * @category HTTP Server + */ + export interface ServeHandlerInfo { + /** The remote address of the connection. */ + remoteAddr: Deno.NetAddr; + } + + /** **UNSTABLE**: New API, yet to be vetted. * * A handler for HTTP requests. Consumes a request and returns a response. @@ -1125,7 +1185,7 @@ declare namespace Deno { * * @category HTTP Server */ - export type ServeHandler = (request: Request) => Response | Promise; + export type ServeHandler = (request: Request, info: ServeHandlerInfo) => Response | Promise; /** **UNSTABLE**: New API, yet to be vetted. * @@ -1405,239 +1465,6 @@ declare namespace Deno { * @category HTTP Server */ export function upgradeHttpRaw(request: Request): [Deno.Conn, Uint8Array]; - - /** **UNSTABLE**: New API, yet to be vetted. - * - * Create a child process. - * - * If any stdio options are not set to `"piped"`, accessing the corresponding - * field on the `Command` or its `CommandOutput` will throw a `TypeError`. - * - * If `stdin` is set to `"piped"`, the `stdin` {@linkcode WritableStream} - * needs to be closed manually. - * - * @example Spawn a subprocess and pipe the output to a file - * - * ```ts - * const command = new Deno.Command(Deno.execPath(), { - * args: [ - * "eval", - * "console.log('Hello World')", - * ], - * stdin: "piped", - * }); - * const child = command.spawn(); - * - * // open a file and pipe the subprocess output to it. - * child.stdout.pipeTo(Deno.openSync("output").writable); - * - * // manually close stdin - * child.stdin.close(); - * const status = await child.status; - * ``` - * - * @example Spawn a subprocess and collect its output - * - * ```ts - * const command = new Deno.Command(Deno.execPath(), { - * args: [ - * "eval", - * "console.log('hello'); console.error('world')", - * ], - * }); - * const { code, stdout, stderr } = await command.output(); - * console.assert(code === 0); - * console.assert("hello\n" === new TextDecoder().decode(stdout)); - * console.assert("world\n" === new TextDecoder().decode(stderr)); - * ``` - * - * @example Spawn a subprocess and collect its output synchronously - * - * ```ts - * const command = new Deno.Command(Deno.execPath(), { - * args: [ - * "eval", - * "console.log('hello'); console.error('world')", - * ], - * }); - * const { code, stdout, stderr } = command.outputSync(); - * console.assert(code === 0); - * console.assert("hello\n" === new TextDecoder().decode(stdout)); - * console.assert("world\n" === new TextDecoder().decode(stderr)); - * ``` - * - * @category Sub Process - */ - export class Command { - constructor(command: string | URL, options?: CommandOptions); - /** - * Executes the {@linkcode Deno.Command}, waiting for it to finish and - * collecting all of its output. - * If `spawn()` was called, calling this function will collect the remaining - * output. - * - * Will throw an error if `stdin: "piped"` is set. - * - * If options `stdout` or `stderr` are not set to `"piped"`, accessing the - * corresponding field on {@linkcode Deno.CommandOutput} will throw a `TypeError`. - */ - output(): Promise; - /** - * Synchronously executes the {@linkcode Deno.Command}, waiting for it to - * finish and collecting all of its output. - * - * Will throw an error if `stdin: "piped"` is set. - * - * If options `stdout` or `stderr` are not set to `"piped"`, accessing the - * corresponding field on {@linkcode Deno.CommandOutput} will throw a `TypeError`. - */ - outputSync(): CommandOutput; - /** - * Spawns a streamable subprocess, allowing to use the other methods. - */ - spawn(): ChildProcess; - } - - /** **UNSTABLE**: New API, yet to be vetted. - * - * The interface for handling a child process returned from - * {@linkcode Deno.Command.spawn}. - * - * @category Sub Process - */ - export class ChildProcess { - get stdin(): WritableStream; - get stdout(): ReadableStream; - get stderr(): ReadableStream; - readonly pid: number; - /** Get the status of the child. */ - readonly status: Promise; - - /** Waits for the child to exit completely, returning all its output and - * status. */ - output(): Promise; - /** Kills the process with given {@linkcode Deno.Signal}. - * - * @param [signo="SIGTERM"] - */ - kill(signo?: Signal): void; - - /** Ensure that the status of the child process prevents the Deno process - * from exiting. */ - ref(): void; - /** Ensure that the status of the child process does not block the Deno - * process from exiting. */ - unref(): void; - } - - /** **UNSTABLE**: New API, yet to be vetted. - * - * Options which can be set when calling {@linkcode Deno.command}. - * - * @category Sub Process - */ - export interface CommandOptions { - /** Arguments to pass to the process. */ - args?: string[]; - /** - * The working directory of the process. - * - * If not specified, the `cwd` of the parent process is used. - */ - cwd?: string | URL; - /** - * Clear environmental variables from parent process. - * - * Doesn't guarantee that only `env` variables are present, as the OS may - * set environmental variables for processes. - * - * @default {false} - */ - clearEnv?: boolean; - /** Environmental variables to pass to the subprocess. */ - env?: Record; - /** - * Sets the child process’s user ID. This translates to a setuid call in the - * child process. Failure in the set uid call will cause the spawn to fail. - */ - uid?: number; - /** Similar to `uid`, but sets the group ID of the child process. */ - gid?: number; - /** - * An {@linkcode AbortSignal} that allows closing the process using the - * corresponding {@linkcode AbortController} by sending the process a - * SIGTERM signal. - * - * Not supported in {@linkcode Deno.Command.outputSync}. - */ - signal?: AbortSignal; - - /** How `stdin` of the spawned process should be handled. - * - * Defaults to `"inherit"` for `output` & `outputSync`, - * and `"inherit"` for `spawn`. */ - stdin?: "piped" | "inherit" | "null"; - /** How `stdout` of the spawned process should be handled. - * - * Defaults to `"piped"` for `output` & `outputSync`, - * and `"inherit"` for `spawn`. */ - stdout?: "piped" | "inherit" | "null"; - /** How `stderr` of the spawned process should be handled. - * - * Defaults to `"piped"` for `output` & `outputSync`, - * and `"inherit"` for `spawn`. */ - stderr?: "piped" | "inherit" | "null"; - - /** Skips quoting and escaping of the arguments on windows. This option - * is ignored on non-windows platforms. - * - * @default {false} */ - windowsRawArguments?: boolean; - } - - /** **UNSTABLE**: New API, yet to be vetted. - * - * @category Sub Process - */ - export interface CommandStatus { - /** If the child process exits with a 0 status code, `success` will be set - * to `true`, otherwise `false`. */ - success: boolean; - /** The exit code of the child process. */ - code: number; - /** The signal associated with the child process. */ - signal: Signal | null; - } - - /** **UNSTABLE**: New API, yet to be vetted. - * - * The interface returned from calling {@linkcode Command.output} or - * {@linkcode Command.outputSync} which represents the result of spawning the - * child process. - * - * @category Sub Process - */ - export interface CommandOutput extends CommandStatus { - /** The buffered output from the child process' `stdout`. */ - readonly stdout: Uint8Array; - /** The buffered output from the child process' `stderr`. */ - readonly stderr: Uint8Array; - } - - /** **UNSTABLE**: New API, yet to be vetted. - * - * Returns the Operating System uptime in number of seconds. - * - * ```ts - * console.log(Deno.osUptime()); - * ``` - * - * Requires `allow-sys` permission. - * - * @tags allow-sys - * @category Runtime Environment - */ - export function osUptime(): number; } /** **UNSTABLE**: New API, yet to be vetted. diff --git a/cli/tsc/mod.rs b/cli/tsc/mod.rs index 2643c02c045d41..f31a6cc77f3ed7 100644 --- a/cli/tsc/mod.rs +++ b/cli/tsc/mod.rs @@ -1,12 +1,9 @@ // Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. use crate::args::TsConfig; -use crate::graph_util::GraphData; -use crate::graph_util::ModuleEntry; use crate::node; use crate::node::node_resolve_npm_reference; use crate::node::NodeResolution; -use crate::npm::NpmPackageReference; use crate::npm::NpmPackageResolver; use crate::util::checksum; @@ -16,7 +13,6 @@ use deno_core::anyhow::Context; use deno_core::error::AnyError; use deno_core::located_script_name; use deno_core::op; -use deno_core::parking_lot::RwLock; use deno_core::resolve_url_or_path; use deno_core::serde::Deserialize; use deno_core::serde::Deserializer; @@ -32,7 +28,11 @@ use deno_core::ModuleSpecifier; use deno_core::OpState; use deno_core::RuntimeOptions; use deno_core::Snapshot; -use deno_graph::Resolved; +use deno_graph::npm::NpmPackageNvReference; +use deno_graph::npm::NpmPackageReqReference; +use deno_graph::Module; +use deno_graph::ModuleGraph; +use deno_graph::ResolutionResolved; use deno_runtime::deno_node::NodeResolutionMode; use deno_runtime::permissions::PermissionsContainer; use once_cell::sync::Lazy; @@ -57,6 +57,14 @@ pub static COMPILER_SNAPSHOT: Lazy> = Lazy::new( static COMPRESSED_COMPILER_SNAPSHOT: &[u8] = include_bytes!(concat!(env!("OUT_DIR"), "/COMPILER_SNAPSHOT.bin")); + // NOTE(bartlomieju): Compressing the TSC snapshot in debug build took + // ~45s on M1 MacBook Pro; without compression it took ~1s. + // Thus we're not not using compressed snapshot, trading off + // a lot of build time for some startup time in debug build. + #[cfg(debug_assertions)] + return COMPRESSED_COMPILER_SNAPSHOT.to_vec().into_boxed_slice(); + + #[cfg(not(debug_assertions))] zstd::bulk::decompress( &COMPRESSED_COMPILER_SNAPSHOT[4..], u32::from_le_bytes(COMPRESSED_COMPILER_SNAPSHOT[0..4].try_into().unwrap()) @@ -343,7 +351,7 @@ pub struct Request { pub config: TsConfig, /// Indicates to the tsc runtime if debug logging should occur. pub debug: bool, - pub graph_data: Arc>, + pub graph: Arc, pub hash_data: Vec>, pub maybe_config_specifier: Option, pub maybe_npm_resolver: Option, @@ -366,7 +374,7 @@ pub struct Response { #[derive(Debug, Default)] struct State { hash_data: Vec>, - graph_data: Arc>, + graph: Arc, maybe_config_specifier: Option, maybe_tsbuildinfo: Option, maybe_response: Option, @@ -377,7 +385,7 @@ struct State { impl State { pub fn new( - graph_data: Arc>, + graph: Arc, hash_data: Vec>, maybe_config_specifier: Option, maybe_npm_resolver: Option, @@ -387,7 +395,7 @@ impl State { ) -> Self { State { hash_data, - graph_data, + graph, maybe_config_specifier, maybe_npm_resolver, maybe_tsbuildinfo, @@ -467,15 +475,12 @@ struct ExistsArgs { #[op] fn op_exists(state: &mut OpState, args: ExistsArgs) -> bool { let state = state.borrow_mut::(); - let graph_data = state.graph_data.read(); + let graph = &state.graph; if let Ok(specifier) = normalize_specifier(&args.specifier) { if specifier.scheme() == "asset" || specifier.scheme() == "data" { true } else { - matches!( - graph_data.get(&graph_data.follow_redirect(&specifier)), - Some(ModuleEntry::Module { .. }) - ) + graph.get(&specifier).is_some() } } else { false @@ -518,7 +523,7 @@ fn op_load(state: &mut OpState, args: Value) -> Result { .context("Error converting a string module specifier for \"op_load\".")?; let mut hash: Option = None; let mut media_type = MediaType::Unknown; - let graph_data = state.graph_data.read(); + let graph = &state.graph; let data = if &v.specifier == "internal:///.tsbuildinfo" { state.maybe_tsbuildinfo.as_deref().map(Cow::Borrowed) // in certain situations we return a "blank" module to tsc and we need to @@ -542,15 +547,30 @@ fn op_load(state: &mut OpState, args: Value) -> Result { } else { &specifier }; - let maybe_source = if let Some(ModuleEntry::Module { - code, - media_type: mt, - .. - }) = - graph_data.get(&graph_data.follow_redirect(specifier)) - { - media_type = *mt; - Some(Cow::Borrowed(code as &str)) + let maybe_source = if let Some(module) = graph.get(specifier) { + match module { + Module::Esm(module) => { + media_type = module.media_type; + Some(Cow::Borrowed(&*module.source)) + } + Module::Json(module) => { + media_type = MediaType::Json; + Some(Cow::Borrowed(&*module.source)) + } + Module::Npm(_) | Module::Node(_) => None, + Module::External(module) => { + // means it's Deno code importing an npm module + let specifier = + node::resolve_specifier_into_node_modules(&module.specifier); + media_type = MediaType::from(&specifier); + let file_path = specifier.to_file_path().unwrap(); + let code = + std::fs::read_to_string(&file_path).with_context(|| { + format!("Unable to load {}", file_path.display()) + })?; + Some(Cow::Owned(code)) + } + } } else if state .maybe_npm_resolver .as_ref() @@ -624,82 +644,18 @@ fn op_resolve( continue; } - let graph_data = state.graph_data.read(); - let resolved_dep = match graph_data.get_dependencies(&referrer) { - Some(dependencies) => dependencies.get(&specifier).map(|d| { - if matches!(d.maybe_type, Resolved::Ok { .. }) { - &d.maybe_type - } else { - &d.maybe_code - } - }), - None => None, - }; + let graph = &state.graph; + let resolved_dep = graph + .get(&referrer) + .and_then(|m| m.esm()) + .and_then(|m| m.dependencies.get(&specifier)) + .and_then(|d| d.maybe_type.ok().or_else(|| d.maybe_code.ok())); + let maybe_result = match resolved_dep { - Some(Resolved::Ok { specifier, .. }) => { - let specifier = graph_data.follow_redirect(specifier); - match graph_data.get(&specifier) { - Some(ModuleEntry::Module { - media_type, - maybe_types, - .. - }) => match maybe_types { - Some(Resolved::Ok { specifier, .. }) => { - let types = graph_data.follow_redirect(specifier); - match graph_data.get(&types) { - Some(ModuleEntry::Module { media_type, .. }) => { - Some((types, *media_type)) - } - _ => None, - } - } - _ => Some((specifier, *media_type)), - }, - _ => { - // handle npm: urls - if let Ok(npm_ref) = NpmPackageReference::from_specifier(&specifier) - { - if let Some(npm_resolver) = &state.maybe_npm_resolver { - Some(resolve_npm_package_reference_types( - &npm_ref, - npm_resolver, - )?) - } else { - None - } - } else { - None - } - } - } - } - _ => { - if let Some(npm_resolver) = state.maybe_npm_resolver.as_ref() { - if npm_resolver.in_npm_package(&referrer) { - // we're in an npm package, so use node resolution - Some(NodeResolution::into_specifier_and_media_type( - node::node_resolve( - &specifier, - &referrer, - NodeResolutionMode::Types, - npm_resolver, - &mut PermissionsContainer::allow_all(), - ) - .ok() - .flatten(), - )) - } else if let Ok(npm_ref) = NpmPackageReference::from_str(&specifier) - { - // this could occur when resolving npm:@types/node when it is - // injected and not part of the graph - Some(resolve_npm_package_reference_types(&npm_ref, npm_resolver)?) - } else { - None - } - } else { - None - } + Some(ResolutionResolved { specifier, .. }) => { + resolve_graph_specifier_types(specifier, state)? } + _ => resolve_non_graph_specifier_types(&specifier, &referrer, state)?, }; let result = match maybe_result { Some((specifier, media_type)) => { @@ -738,8 +694,99 @@ fn op_resolve( Ok(resolved) } +fn resolve_graph_specifier_types( + specifier: &ModuleSpecifier, + state: &State, +) -> Result, AnyError> { + let graph = &state.graph; + let maybe_module = graph.get(specifier); + // follow the types reference directive, which may be pointing at an npm package + let maybe_module = match maybe_module { + Some(Module::Esm(module)) => { + let maybe_types_dep = module + .maybe_types_dependency + .as_ref() + .map(|d| &d.dependency); + match maybe_types_dep.and_then(|d| d.maybe_specifier()) { + Some(specifier) => graph.get(specifier), + _ => maybe_module, + } + } + maybe_module => maybe_module, + }; + + // now get the types from the resolved module + match maybe_module { + Some(Module::Esm(module)) => { + Ok(Some((module.specifier.clone(), module.media_type))) + } + Some(Module::Json(module)) => { + Ok(Some((module.specifier.clone(), module.media_type))) + } + Some(Module::Npm(module)) => { + if let Some(npm_resolver) = &state.maybe_npm_resolver { + resolve_npm_package_reference_types(&module.nv_reference, npm_resolver) + .map(Some) + } else { + Ok(None) + } + } + Some(Module::External(module)) => { + // we currently only use "External" for when the module is in an npm package + Ok(state.maybe_npm_resolver.as_ref().map(|npm_resolver| { + let specifier = + node::resolve_specifier_into_node_modules(&module.specifier); + NodeResolution::into_specifier_and_media_type( + node::url_to_node_resolution(specifier, npm_resolver).ok(), + ) + })) + } + Some(Module::Node(_)) | None => Ok(None), + } +} + +fn resolve_non_graph_specifier_types( + specifier: &str, + referrer: &ModuleSpecifier, + state: &State, +) -> Result, AnyError> { + let npm_resolver = match state.maybe_npm_resolver.as_ref() { + Some(npm_resolver) => npm_resolver, + None => return Ok(None), // we only support non-graph types for npm packages + }; + if npm_resolver.in_npm_package(referrer) { + // we're in an npm package, so use node resolution + Ok(Some(NodeResolution::into_specifier_and_media_type( + node::node_resolve( + specifier, + referrer, + NodeResolutionMode::Types, + npm_resolver, + &mut PermissionsContainer::allow_all(), + ) + .ok() + .flatten(), + ))) + } else if let Ok(npm_ref) = NpmPackageReqReference::from_str(specifier) { + // todo(dsherret): add support for injecting this in the graph so + // we don't need this special code here. + // This could occur when resolving npm:@types/node when it is + // injected and not part of the graph + let node_id = npm_resolver + .resolution() + .resolve_pkg_id_from_pkg_req(&npm_ref.req)?; + let npm_id_ref = NpmPackageNvReference { + nv: node_id.nv, + sub_path: npm_ref.sub_path, + }; + resolve_npm_package_reference_types(&npm_id_ref, npm_resolver).map(Some) + } else { + Ok(None) + } +} + pub fn resolve_npm_package_reference_types( - npm_ref: &NpmPackageReference, + npm_ref: &NpmPackageNvReference, npm_resolver: &NpmPackageResolver, ) -> Result<(ModuleSpecifier, MediaType), AnyError> { let maybe_resolution = node_resolve_npm_reference( @@ -818,7 +865,7 @@ pub fn exec(request: Request) -> Result { .ops(get_tsc_ops()) .state(move |state| { state.put(State::new( - request.graph_data.clone(), + request.graph.clone(), request.hash_data.clone(), request.maybe_config_specifier.clone(), request.maybe_npm_resolver.clone(), @@ -886,6 +933,7 @@ mod tests { use crate::args::TsConfig; use deno_core::futures::future; use deno_core::OpState; + use deno_graph::ModuleGraph; use std::fs; #[derive(Debug, Default)] @@ -928,20 +976,12 @@ mod tests { let hash_data = maybe_hash_data.unwrap_or_else(|| vec![b"".to_vec()]); let fixtures = test_util::testdata_path().join("tsc2"); let mut loader = MockLoader { fixtures }; - let graph = deno_graph::create_graph( - vec![specifier], - &mut loader, - deno_graph::GraphOptions { - is_dynamic: false, - imports: None, - resolver: None, - module_analyzer: None, - reporter: None, - }, - ) - .await; + let mut graph = ModuleGraph::default(); + graph + .build(vec![specifier], &mut loader, Default::default()) + .await; let state = State::new( - Arc::new(RwLock::new((&graph).into())), + Arc::new(graph), hash_data, None, None, @@ -960,18 +1000,10 @@ mod tests { let hash_data = vec![b"something".to_vec()]; let fixtures = test_util::testdata_path().join("tsc2"); let mut loader = MockLoader { fixtures }; - let graph = deno_graph::create_graph( - vec![specifier.clone()], - &mut loader, - deno_graph::GraphOptions { - is_dynamic: false, - imports: None, - resolver: None, - module_analyzer: None, - reporter: None, - }, - ) - .await; + let mut graph = ModuleGraph::default(); + graph + .build(vec![specifier.clone()], &mut loader, Default::default()) + .await; let config = TsConfig::new(json!({ "allowJs": true, "checkJs": false, @@ -992,7 +1024,7 @@ mod tests { let request = Request { config, debug: false, - graph_data: Arc::new(RwLock::new((&graph).into())), + graph: Arc::new(graph), hash_data, maybe_config_specifier: None, maybe_npm_resolver: None, diff --git a/cli/worker.rs b/cli/worker.rs index fb934b24eb7687..49acdd7d3ec651 100644 --- a/cli/worker.rs +++ b/cli/worker.rs @@ -15,7 +15,9 @@ use deno_core::serde_v8; use deno_core::v8; use deno_core::Extension; use deno_core::ModuleId; +use deno_graph::npm::NpmPackageReqReference; use deno_runtime::colors; +use deno_runtime::deno_node; use deno_runtime::deno_wsi::event_loop::WsiEventLoopProxy; use deno_runtime::fmt_errors::format_js_error; use deno_runtime::ops::worker_host::CreateWebWorkerCb; @@ -31,7 +33,6 @@ use crate::args::DenoSubcommand; use crate::errors; use crate::module_loader::CliModuleLoader; use crate::node; -use crate::npm::NpmPackageReference; use crate::ops; use crate::proc_state::ProcState; use crate::tools; @@ -68,9 +69,8 @@ impl CliMainWorker { log::debug!("main_module {}", self.main_module); if self.is_main_cjs { - self.ps.prepare_node_std_graph().await?; self.initialize_main_module_for_node().await?; - node::load_cjs_module_from_ext_node( + deno_node::load_cjs_module( &mut self.worker.js_runtime, &self.main_module.to_file_path().unwrap().to_string_lossy(), true, @@ -280,9 +280,6 @@ impl CliMainWorker { async fn execute_main_module_possibly_with_npm( &mut self, ) -> Result<(), AnyError> { - if self.ps.npm_resolver.has_packages() { - self.ps.prepare_node_std_graph().await?; - } let id = self.worker.preload_main_module(&self.main_module).await?; self.evaluate_module_possibly_with_npm(id).await } @@ -298,28 +295,28 @@ impl CliMainWorker { &mut self, id: ModuleId, ) -> Result<(), AnyError> { - if self.ps.npm_resolver.has_packages() { + if self.ps.npm_resolver.has_packages() || self.ps.graph().has_node_specifier + { self.initialize_main_module_for_node().await?; } self.worker.evaluate_module(id).await } async fn initialize_main_module_for_node(&mut self) -> Result<(), AnyError> { - self.ps.prepare_node_std_graph().await?; - node::initialize_runtime( + deno_node::initialize_runtime( &mut self.worker.js_runtime, - self.ps.options.node_modules_dir(), + self.ps.options.has_node_modules_dir(), ) .await?; if let DenoSubcommand::Run(flags) = self.ps.options.sub_command() { - if let Ok(pkg_ref) = NpmPackageReference::from_str(&flags.script) { + if let Ok(pkg_ref) = NpmPackageReqReference::from_str(&flags.script) { // if the user ran a binary command, we'll need to set process.argv[0] // to be the name of the binary command instead of deno let binary_name = pkg_ref .sub_path .as_deref() .unwrap_or(pkg_ref.req.name.as_str()); - node::initialize_binary_command( + deno_node::initialize_binary_command( &mut self.worker.js_runtime, binary_name, ) @@ -451,21 +448,25 @@ async fn create_main_worker_internal( bench_or_test: bool, ) -> Result { let (main_module, is_main_cjs) = if let Ok(package_ref) = - NpmPackageReference::from_specifier(&main_module) + NpmPackageReqReference::from_specifier(&main_module) { ps.npm_resolver .add_package_reqs(vec![package_ref.req.clone()]) .await?; + let pkg_nv = ps + .npm_resolver + .resolution() + .resolve_pkg_id_from_pkg_req(&package_ref.req)? + .nv; let node_resolution = node::node_resolve_binary_export( - &package_ref.req, + &pkg_nv, package_ref.sub_path.as_deref(), &ps.npm_resolver, - &mut PermissionsContainer::allow_all(), )?; let is_main_cjs = matches!(node_resolution, node::NodeResolution::CommonJs(_)); (node_resolution.into_url(), is_main_cjs) - } else if ps.npm_resolver.is_npm_main() { + } else if ps.options.is_npm_main() { let node_resolution = node::url_to_node_resolution(main_module, &ps.npm_resolver)?; let is_main_cjs = @@ -633,9 +634,9 @@ fn create_web_worker_pre_execute_module_callback( let fut = async move { // this will be up to date after pre-load if ps.npm_resolver.has_packages() { - node::initialize_runtime( + deno_node::initialize_runtime( &mut worker.js_runtime, - ps.options.node_modules_dir(), + ps.options.has_node_modules_dir(), ) .await?; } diff --git a/core/01_core.js b/core/01_core.js index d3cd1d7bea79cb..5a622b0ea8f907 100644 --- a/core/01_core.js +++ b/core/01_core.js @@ -10,24 +10,19 @@ TypeError, URIError, Array, - ArrayFrom, ArrayPrototypeFill, - ArrayPrototypeJoin, ArrayPrototypePush, ArrayPrototypeMap, ErrorCaptureStackTrace, - Function, Promise, ObjectAssign, ObjectFromEntries, - ObjectPrototypeHasOwnProperty, Map, MapPrototypeGet, MapPrototypeHas, MapPrototypeDelete, MapPrototypeSet, PromisePrototypeThen, - ReflectApply, SafePromisePrototypeFinally, StringPrototypeSlice, SymbolFor, @@ -175,61 +170,20 @@ return nextPromiseId++; } - // Generate async op wrappers. See core/bindings.rs - function initializeAsyncOps() { - function genAsyncOp(op, name, args) { - return new Function( - "setPromise", - "getPromise", - "promiseIdSymbol", - "rollPromiseId", - "handleOpCallTracing", - "op", - "unwrapOpResult", - "PromisePrototypeThen", - ` - return function ${name}(${args}) { - const id = rollPromiseId(); - let promise = PromisePrototypeThen(setPromise(id), unwrapOpResult); - try { - op(id, ${args}); - } catch (err) { - // Cleanup the just-created promise - getPromise(id); - // Rethrow the error - throw err; - } - promise = handleOpCallTracing("${name}", id, promise); - promise[promiseIdSymbol] = id; - return promise; - } - `, - )( - setPromise, - getPromise, - promiseIdSymbol, - rollPromiseId, - handleOpCallTracing, - op, - unwrapOpResult, - PromisePrototypeThen, - ); - } - - // { : , ... } - const info = ops.asyncOpsInfo(); - for (const name in info) { - if (!ObjectPrototypeHasOwnProperty(info, name)) { - continue; - } - const argc = info[name]; - const op = ops[name]; - const args = ArrayPrototypeJoin( - ArrayFrom({ length: argc }, (_, i) => `arg${i}`), - ", ", - ); - ops[name] = genAsyncOp(op, name, args); + function opAsync(name, ...args) { + const id = rollPromiseId(); + let promise = PromisePrototypeThen(setPromise(id), unwrapOpResult); + try { + ops[name](id, ...args); + } catch (err) { + // Cleanup the just-created promise + getPromise(id); + // Rethrow the error + throw err; } + promise = handleOpCallTracing(name, id, promise); + promise[promiseIdSymbol] = id; + return promise; } function handleOpCallTracing(opName, promiseId, p) { @@ -245,10 +199,6 @@ } } - function opAsync(opName, ...args) { - return ReflectApply(ops[opName], ops, args); - } - function refOp(promiseId) { if (!hasPromise(promiseId)) { return; @@ -326,47 +276,81 @@ } const InterruptedPrototype = Interrupted.prototype; - const promiseHooks = { - init: [], - before: [], - after: [], - resolve: [], - hasBeenSet: false, - }; + const promiseHooks = [ + [], // init + [], // before + [], // after + [], // resolve + ]; function setPromiseHooks(init, before, after, resolve) { - if (init) ArrayPrototypePush(promiseHooks.init, init); - if (before) ArrayPrototypePush(promiseHooks.before, before); - if (after) ArrayPrototypePush(promiseHooks.after, after); - if (resolve) ArrayPrototypePush(promiseHooks.resolve, resolve); + const hooks = [init, before, after, resolve]; + for (let i = 0; i < hooks.length; i++) { + const hook = hooks[i]; + // Skip if no callback was provided for this hook type. + if (hook == null) { + continue; + } + // Verify that the type of `hook` is a function. + if (typeof hook !== "function") { + throw new TypeError(`Expected function at position ${i}`); + } + // Add the hook to the list. + ArrayPrototypePush(promiseHooks[i], hook); + } - if (!promiseHooks.hasBeenSet) { - promiseHooks.hasBeenSet = true; + const wrappedHooks = ArrayPrototypeMap(promiseHooks, (hooks) => { + switch (hooks.length) { + case 0: + return undefined; + case 1: + return hooks[0]; + case 2: + return create2xHookWrapper(hooks[0], hooks[1]); + case 3: + return create3xHookWrapper(hooks[0], hooks[1], hooks[2]); + default: + return createHookListWrapper(hooks); + } - ops.op_set_promise_hooks((promise, parentPromise) => { - for (let i = 0; i < promiseHooks.init.length; ++i) { - promiseHooks.init[i](promise, parentPromise); - } - }, (promise) => { - for (let i = 0; i < promiseHooks.before.length; ++i) { - promiseHooks.before[i](promise); - } - }, (promise) => { - for (let i = 0; i < promiseHooks.after.length; ++i) { - promiseHooks.after[i](promise); - } - }, (promise) => { - for (let i = 0; i < promiseHooks.resolve.length; ++i) { - promiseHooks.resolve[i](promise); - } - }); - } + // The following functions are used to create wrapper functions that call + // all the hooks in a list of a certain length. The reason to use a + // function that creates a wrapper is to minimize the number of objects + // captured in the closure. + function create2xHookWrapper(hook1, hook2) { + return function (promise, parent) { + hook1(promise, parent); + hook2(promise, parent); + }; + } + function create3xHookWrapper(hook1, hook2, hook3) { + return function (promise, parent) { + hook1(promise, parent); + hook2(promise, parent); + hook3(promise, parent); + }; + } + function createHookListWrapper(hooks) { + return function (promise, parent) { + for (let i = 0; i < hooks.length; i++) { + const hook = hooks[i]; + hook(promise, parent); + } + }; + } + }); + + ops.op_set_promise_hooks( + wrappedHooks[0], + wrappedHooks[1], + wrappedHooks[2], + wrappedHooks[3], + ); } // Extra Deno.core.* exports const core = ObjectAssign(globalThis.Deno.core, { opAsync, - initializeAsyncOps, resources, metrics, registerErrorBuilder, @@ -386,11 +370,11 @@ setPromiseHooks, close: (rid) => ops.op_close(rid), tryClose: (rid) => ops.op_try_close(rid), - read: (rid, buffer) => ops.op_read(rid, buffer), - readAll: (rid) => ops.op_read_all(rid), - write: (rid, buffer) => ops.op_write(rid, buffer), - writeAll: (rid, buffer) => ops.op_write_all(rid, buffer), - shutdown: (rid) => ops.op_shutdown(rid), + read: opAsync.bind(null, "op_read"), + readAll: opAsync.bind(null, "op_read_all"), + write: opAsync.bind(null, "op_write"), + writeAll: opAsync.bind(null, "op_write_all"), + shutdown: opAsync.bind(null, "op_shutdown"), print: (msg, isErr) => ops.op_print(msg, isErr), setMacrotaskCallback: (fn) => ops.op_set_macrotask_callback(fn), setNextTickCallback: (fn) => ops.op_set_next_tick_callback(fn), @@ -427,6 +411,8 @@ }); ObjectAssign(globalThis.__bootstrap, { core }); + const internals = {}; + ObjectAssign(globalThis.__bootstrap, { internals }); ObjectAssign(globalThis.Deno, { core }); // Direct bindings on `globalThis` diff --git a/core/Cargo.toml b/core/Cargo.toml index 2b4d4020780e2c..55602d614dfb16 100644 --- a/core/Cargo.toml +++ b/core/Cargo.toml @@ -2,7 +2,7 @@ [package] name = "deno_core" -version = "0.171.0" +version = "0.173.0" authors.workspace = true edition.workspace = true license.workspace = true @@ -16,6 +16,7 @@ path = "lib.rs" [features] default = ["v8_use_custom_libcxx"] v8_use_custom_libcxx = ["v8/use_custom_libcxx"] +include_js_files_for_snapshotting = [] [dependencies] anyhow.workspace = true diff --git a/core/async_cell.rs b/core/async_cell.rs index a5b8d5467d63e7..0f173ed17cd4cb 100644 --- a/core/async_cell.rs +++ b/core/async_cell.rs @@ -257,7 +257,7 @@ mod internal { use std::pin::Pin; impl AsyncRefCell { - /// Borrow the cell's contents synchronouslym without creating an + /// Borrow the cell's contents synchronously without creating an /// intermediate future. If the cell has already been borrowed and either /// the existing or the requested borrow is exclusive, this function returns /// `None`. diff --git a/core/bindings.rs b/core/bindings.rs index d5f38d3c2fb0ac..aa42c2b7742e77 100644 --- a/core/bindings.rs +++ b/core/bindings.rs @@ -10,6 +10,7 @@ use v8::MapFnTo; use crate::error::is_instance_of_error; use crate::modules::get_asserted_module_type_from_assertions; use crate::modules::parse_import_assertions; +use crate::modules::resolve_helper; use crate::modules::validate_import_assertions; use crate::modules::ImportAssertionsKind; use crate::modules::ModuleMap; @@ -110,26 +111,38 @@ pub fn initialize_context<'s>( let scope = &mut v8::ContextScope::new(scope, context); + let deno_str = v8::String::new(scope, "Deno").unwrap(); + let core_str = v8::String::new(scope, "core").unwrap(); + let ops_str = v8::String::new(scope, "ops").unwrap(); + // Snapshot already registered `Deno.core.ops` but // extensions may provide ops that aren't part of the snapshot. if snapshot_options.loaded() { // Grab the Deno.core.ops object & init it - let ops_obj = JsRuntime::eval::(scope, "Deno.core.ops") - .expect("Deno.core.ops to exist"); + let deno_obj: v8::Local = global + .get(scope, deno_str.into()) + .unwrap() + .try_into() + .unwrap(); + let core_obj: v8::Local = deno_obj + .get(scope, core_str.into()) + .unwrap() + .try_into() + .unwrap(); + let ops_obj: v8::Local = core_obj + .get(scope, ops_str.into()) + .expect("Deno.core.ops to exist") + .try_into() + .unwrap(); initialize_ops(scope, ops_obj, op_ctxs, snapshot_options); - if snapshot_options != SnapshotOptions::CreateFromExisting { - initialize_async_ops_info(scope, ops_obj, op_ctxs); - } return scope.escape(context); } // global.Deno = { core: { } }; let deno_obj = v8::Object::new(scope); - let deno_str = v8::String::new(scope, "Deno").unwrap(); global.set(scope, deno_str.into(), deno_obj.into()); let core_obj = v8::Object::new(scope); - let core_str = v8::String::new(scope, "core").unwrap(); deno_obj.set(scope, core_str.into(), core_obj.into()); // Bind functions to Deno.core.* @@ -143,12 +156,8 @@ pub fn initialize_context<'s>( // Bind functions to Deno.core.ops.* let ops_obj = v8::Object::new(scope); - let ops_str = v8::String::new(scope, "ops").unwrap(); core_obj.set(scope, ops_str.into(), ops_obj.into()); - if !snapshot_options.will_snapshot() { - initialize_async_ops_info(scope, ops_obj, op_ctxs); - } initialize_ops(scope, ops_obj, op_ctxs, snapshot_options); scope.escape(context) } @@ -267,45 +276,47 @@ pub fn host_import_module_dynamically_callback<'s>( .unwrap() .to_rust_string_lossy(scope); + let is_internal_module = specifier_str.starts_with("internal:"); let resolver = v8::PromiseResolver::new(scope).unwrap(); let promise = resolver.get_promise(scope); - let assertions = parse_import_assertions( - scope, - import_assertions, - ImportAssertionsKind::DynamicImport, - ); + if !is_internal_module { + let assertions = parse_import_assertions( + scope, + import_assertions, + ImportAssertionsKind::DynamicImport, + ); - { - let tc_scope = &mut v8::TryCatch::new(scope); - validate_import_assertions(tc_scope, &assertions); - if tc_scope.has_caught() { - let e = tc_scope.exception().unwrap(); - resolver.reject(tc_scope, e); + { + let tc_scope = &mut v8::TryCatch::new(scope); + validate_import_assertions(tc_scope, &assertions); + if tc_scope.has_caught() { + let e = tc_scope.exception().unwrap(); + resolver.reject(tc_scope, e); + } } - } - let asserted_module_type = - get_asserted_module_type_from_assertions(&assertions); + let asserted_module_type = + get_asserted_module_type_from_assertions(&assertions); - let resolver_handle = v8::Global::new(scope, resolver); - { - let state_rc = JsRuntime::state(scope); - let module_map_rc = JsRuntime::module_map(scope); + let resolver_handle = v8::Global::new(scope, resolver); + { + let state_rc = JsRuntime::state(scope); + let module_map_rc = JsRuntime::module_map(scope); - debug!( - "dyn_import specifier {} referrer {} ", - specifier_str, referrer_name_str - ); - ModuleMap::load_dynamic_import( - module_map_rc, - &specifier_str, - &referrer_name_str, - asserted_module_type, - resolver_handle, - ); - state_rc.borrow_mut().notify_new_dynamic_import(); + debug!( + "dyn_import specifier {} referrer {} ", + specifier_str, referrer_name_str + ); + ModuleMap::load_dynamic_import( + module_map_rc, + &specifier_str, + &referrer_name_str, + asserted_module_type, + resolver_handle, + ); + state_rc.borrow_mut().notify_new_dynamic_import(); + } } - // Map errors from module resolution (not JS errors from module execution) to // ones rethrown from this scope, so they include the call stack of the // dynamic import site. Error objects without any stack frames are assumed to @@ -317,6 +328,14 @@ pub fn host_import_module_dynamically_callback<'s>( let promise = promise.catch(scope, map_err).unwrap(); + if is_internal_module { + let message = + v8::String::new(scope, "Cannot load internal module from external code") + .unwrap(); + let exception = v8::Exception::type_error(scope, message); + resolver.reject(scope, exception); + } + Some(promise) } @@ -369,9 +388,12 @@ fn import_meta_resolve( url_prop.to_rust_string_lossy(scope) }; let module_map_rc = JsRuntime::module_map(scope); - let loader = { + let (loader, snapshot_loaded_and_not_snapshotting) = { let module_map = module_map_rc.borrow(); - module_map.loader.clone() + ( + module_map.loader.clone(), + module_map.snapshot_loaded_and_not_snapshotting, + ) }; let specifier_str = specifier.to_rust_string_lossy(scope); @@ -380,8 +402,13 @@ fn import_meta_resolve( return; } - match loader.resolve(&specifier_str, &referrer, ResolutionKind::DynamicImport) - { + match resolve_helper( + snapshot_loaded_and_not_snapshotting, + loader, + &specifier_str, + &referrer, + ResolutionKind::DynamicImport, + ) { Ok(resolved) => { let resolved_val = serde_v8::to_v8(scope, resolved.as_str()).unwrap(); rv.set(resolved_val); @@ -624,84 +651,3 @@ pub fn throw_type_error(scope: &mut v8::HandleScope, message: impl AsRef) { let exception = v8::Exception::type_error(scope, message); scope.throw_exception(exception); } - -struct AsyncOpsInfo { - ptr: *const OpCtx, - len: usize, -} - -impl<'s> IntoIterator for &'s AsyncOpsInfo { - type Item = &'s OpCtx; - type IntoIter = AsyncOpsInfoIterator<'s>; - - fn into_iter(self) -> Self::IntoIter { - AsyncOpsInfoIterator { - // SAFETY: OpCtx slice is valid for the lifetime of the Isolate - info: unsafe { std::slice::from_raw_parts(self.ptr, self.len) }, - index: 0, - } - } -} - -struct AsyncOpsInfoIterator<'s> { - info: &'s [OpCtx], - index: usize, -} - -impl<'s> Iterator for AsyncOpsInfoIterator<'s> { - type Item = &'s OpCtx; - - fn next(&mut self) -> Option { - loop { - match self.info.get(self.index) { - Some(ctx) if ctx.decl.is_async => { - self.index += 1; - return Some(ctx); - } - Some(_) => { - self.index += 1; - } - None => return None, - } - } - } -} - -fn async_ops_info( - scope: &mut v8::HandleScope, - args: v8::FunctionCallbackArguments, - mut rv: v8::ReturnValue, -) { - let async_op_names = v8::Object::new(scope); - let external: v8::Local = args.data().try_into().unwrap(); - let info: &AsyncOpsInfo = - // SAFETY: external is guaranteed to be a valid pointer to AsyncOpsInfo - unsafe { &*(external.value() as *const AsyncOpsInfo) }; - for ctx in info { - let name = v8::String::new(scope, ctx.decl.name).unwrap(); - let argc = v8::Integer::new(scope, ctx.decl.argc as i32); - async_op_names.set(scope, name.into(), argc.into()); - } - rv.set(async_op_names.into()); -} - -fn initialize_async_ops_info( - scope: &mut v8::HandleScope, - ops_obj: v8::Local, - op_ctxs: &[OpCtx], -) { - let key = v8::String::new(scope, "asyncOpsInfo").unwrap(); - let external = v8::External::new( - scope, - Box::into_raw(Box::new(AsyncOpsInfo { - ptr: op_ctxs as *const [OpCtx] as _, - len: op_ctxs.len(), - })) as *mut c_void, - ); - let val = v8::Function::builder(async_ops_info) - .data(external.into()) - .build(scope) - .unwrap(); - val.set_name(key); - ops_obj.set(scope, key.into(), val.into()); -} diff --git a/core/error_codes.rs b/core/error_codes.rs index 874aa4ec645fb8..ebe0366099a13e 100644 --- a/core/error_codes.rs +++ b/core/error_codes.rs @@ -56,6 +56,7 @@ fn get_io_error_code(err: &std::io::Error) -> &'static str { // ErrorKind::ExecutableFileBusy => "ETXTBSY", // ErrorKind::CrossesDevices => "EXDEV", ErrorKind::PermissionDenied => "EACCES", // NOTE: Collides with EPERM ... + ErrorKind::WouldBlock => "EWOULDBLOCK", // NOTE: Collides with EAGAIN ... _ => "", } } diff --git a/core/examples/http_bench_json_ops/http_bench_json_ops.js b/core/examples/http_bench_json_ops/http_bench_json_ops.js index 9650804c705ffa..5a205188b07dd5 100644 --- a/core/examples/http_bench_json_ops/http_bench_json_ops.js +++ b/core/examples/http_bench_json_ops/http_bench_json_ops.js @@ -2,9 +2,8 @@ // This is not a real HTTP server. We read blindly one time into 'requestBuf', // then write this fixed 'responseBuf'. The point of this benchmark is to // exercise the event loop in a simple yet semi-realistic way. -Deno.core.initializeAsyncOps(); -const { ops } = Deno.core; +const { ops, opAsync } = Deno.core; const requestBuf = new Uint8Array(64 * 1024); const responseBuf = new Uint8Array( @@ -20,11 +19,11 @@ function listen() { /** Accepts a connection, returns rid. */ function accept(serverRid) { - return ops.op_accept(serverRid); + return opAsync("op_accept", serverRid); } function read(serverRid, buf) { - return ops.op_read_socket(serverRid, buf); + return opAsync("op_read_socket", serverRid, buf); } async function serve(rid) { diff --git a/core/extensions.rs b/core/extensions.rs index 129e7b62a86313..f84ab0a91a5831 100644 --- a/core/extensions.rs +++ b/core/extensions.rs @@ -1,12 +1,45 @@ // Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. use crate::OpState; +use anyhow::Context as _; use anyhow::Error; use std::cell::RefCell; +use std::path::PathBuf; use std::rc::Rc; use std::task::Context; use v8::fast_api::FastFunction; -pub type SourcePair = (&'static str, &'static str); +#[derive(Clone, Debug)] +pub enum ExtensionFileSourceCode { + /// Source code is included in the binary produced. Either by being defined + /// inline, or included using `include_str!()`. If you are snapshotting, this + /// will result in two copies of the source code being included - one in the + /// snapshot, the other the static string in the `Extension`. + IncludedInBinary(&'static str), + + // Source code is loaded from a file on disk. It's meant to be used if the + // embedder is creating snapshots. Files will be loaded from the filesystem + // during the build time and they will only be present in the V8 snapshot. + LoadedFromFsDuringSnapshot(PathBuf), +} + +impl ExtensionFileSourceCode { + pub fn load(&self) -> Result { + match self { + ExtensionFileSourceCode::IncludedInBinary(code) => Ok(code.to_string()), + ExtensionFileSourceCode::LoadedFromFsDuringSnapshot(path) => { + let msg = format!("Failed to read \"{}\"", path.display()); + let code = std::fs::read_to_string(path).context(msg)?; + Ok(code) + } + } + } +} + +#[derive(Clone, Debug)] +pub struct ExtensionFileSource { + pub specifier: String, + pub code: ExtensionFileSourceCode, +} pub type OpFnRef = v8::FunctionCallback; pub type OpMiddlewareFn = dyn Fn(OpDecl) -> OpDecl; pub type OpStateFn = dyn Fn(&mut OpState) -> Result<(), Error>; @@ -37,7 +70,9 @@ impl OpDecl { #[derive(Default)] pub struct Extension { - js_files: Option>, + js_files: Option>, + esm_files: Option>, + esm_entry_point: Option<&'static str>, ops: Option>, opstate_fn: Option>, middleware_fn: Option>, @@ -81,13 +116,24 @@ impl Extension { /// returns JS source code to be loaded into the isolate (either at snapshotting, /// or at startup). as a vector of a tuple of the file name, and the source code. - pub fn init_js(&self) -> &[SourcePair] { + pub fn get_js_sources(&self) -> &[ExtensionFileSource] { match &self.js_files { Some(files) => files, None => &[], } } + pub fn get_esm_sources(&self) -> &[ExtensionFileSource] { + match &self.esm_files { + Some(files) => files, + None => &[], + } + } + + pub fn get_esm_entry_point(&self) -> Option<&'static str> { + self.esm_entry_point + } + /// Called at JsRuntime startup to initialize ops in the isolate. pub fn init_ops(&mut self) -> Option> { // TODO(@AaronO): maybe make op registration idempotent @@ -144,7 +190,9 @@ impl Extension { // Provides a convenient builder pattern to declare Extensions #[derive(Default)] pub struct ExtensionBuilder { - js: Vec, + js: Vec, + esm: Vec, + esm_entry_point: Option<&'static str>, ops: Vec, state: Option>, middleware: Option>, @@ -159,11 +207,38 @@ impl ExtensionBuilder { self } - pub fn js(&mut self, js_files: Vec) -> &mut Self { + pub fn js(&mut self, js_files: Vec) -> &mut Self { + let js_files = + // TODO(bartlomieju): if we're automatically remapping here, then we should + // use a different result struct that `ExtensionFileSource` as it's confusing + // when (and why) the remapping happens. + js_files.into_iter().map(|file_source| ExtensionFileSource { + specifier: format!("internal:{}/{}", self.name, file_source.specifier), + code: file_source.code, + }); self.js.extend(js_files); self } + pub fn esm(&mut self, esm_files: Vec) -> &mut Self { + let esm_files = esm_files + .into_iter() + // TODO(bartlomieju): if we're automatically remapping here, then we should + // use a different result struct that `ExtensionFileSource` as it's confusing + // when (and why) the remapping happens. + .map(|file_source| ExtensionFileSource { + specifier: format!("internal:{}/{}", self.name, file_source.specifier), + code: file_source.code, + }); + self.esm.extend(esm_files); + self + } + + pub fn esm_entry_point(&mut self, entry_point: &'static str) -> &mut Self { + self.esm_entry_point = Some(entry_point); + self + } + pub fn ops(&mut self, ops: Vec) -> &mut Self { self.ops.extend(ops); self @@ -195,10 +270,13 @@ impl ExtensionBuilder { pub fn build(&mut self) -> Extension { let js_files = Some(std::mem::take(&mut self.js)); + let esm_files = Some(std::mem::take(&mut self.esm)); let ops = Some(std::mem::take(&mut self.ops)); let deps = Some(std::mem::take(&mut self.deps)); Extension { js_files, + esm_files, + esm_entry_point: self.esm_entry_point.take(), ops, opstate_fn: self.state.take(), middleware_fn: self.middleware.take(), @@ -210,25 +288,83 @@ impl ExtensionBuilder { } } } -/// Helps embed JS files in an extension. Returns Vec<(&'static str, &'static str)> -/// representing the filename and source code. + +/// Helps embed JS files in an extension. Returns a vector of +/// `ExtensionFileSource`, that represent the filename and source code. All +/// specified files are rewritten into "internal:/". /// -/// Example: +/// An optional "dir" option can be specified to prefix all files with a +/// directory name. +/// +/// Example (for "my_extension"): /// ```ignore /// include_js_files!( -/// prefix "internal:extensions/hello", /// "01_hello.js", /// "02_goodbye.js", /// ) +/// // Produces following specifiers: +/// - "internal:my_extension/01_hello.js" +/// - "internal:my_extension/02_goodbye.js" +/// +/// /// Example with "dir" option (for "my_extension"): +/// ```ignore +/// include_js_files!( +/// dir "js", +/// "01_hello.js", +/// "02_goodbye.js", +/// ) +/// // Produces following specifiers: +/// - "internal:my_extension/js/01_hello.js" +/// - "internal:my_extension/js/02_goodbye.js" /// ``` +#[cfg(not(feature = "include_js_files_for_snapshotting"))] +#[macro_export] +macro_rules! include_js_files { + (dir $dir:literal, $($file:literal,)+) => { + vec![ + $($crate::ExtensionFileSource { + specifier: concat!($dir, "/", $file).to_string(), + code: $crate::ExtensionFileSourceCode::IncludedInBinary( + include_str!(concat!($dir, "/", $file) + )), + },)+ + ] + }; + + ($($file:literal,)+) => { + vec![ + $($crate::ExtensionFileSource { + specifier: $file.to_string(), + code: $crate::ExtensionFileSourceCode::IncludedInBinary( + include_str!($file) + ), + },)+ + ] + }; +} + +#[cfg(feature = "include_js_files_for_snapshotting")] #[macro_export] macro_rules! include_js_files { - (prefix $prefix:literal, $($file:literal,)+) => { + (dir $dir:literal, $($file:literal,)+) => { + vec![ + $($crate::ExtensionFileSource { + specifier: concat!($dir, "/", $file).to_string(), + code: $crate::ExtensionFileSourceCode::LoadedFromFsDuringSnapshot( + std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR")).join($dir).join($file) + ), + },)+ + ] + }; + + ($($file:literal,)+) => { vec![ - $(( - concat!($prefix, "/", $file), - include_str!($file), - ),)+ + $($crate::ExtensionFileSource { + specifier: $file.to_string(), + code: $crate::ExtensionFileSourceCode::LoadedFromFsDuringSnapshot( + std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR")).join($file) + ), + },)+ ] }; } diff --git a/core/icudtl.dat b/core/icudtl.dat index dfa97affb6033c..d1f10917ab52e3 100644 Binary files a/core/icudtl.dat and b/core/icudtl.dat differ diff --git a/core/inspector.rs b/core/inspector.rs index 2b03d663de94bf..c83784fe387b58 100644 --- a/core/inspector.rs +++ b/core/inspector.rs @@ -111,6 +111,16 @@ impl v8::inspector::V8InspectorClientImpl for JsRuntimeInspector { &self.v8_inspector_client } + unsafe fn base_ptr( + this: *const Self, + ) -> *const v8::inspector::V8InspectorClientBase + where + Self: Sized, + { + // SAFETY: this pointer is valid for the whole lifetime of inspector + unsafe { std::ptr::addr_of!((*this).v8_inspector_client) } + } + fn base_mut(&mut self) -> &mut v8::inspector::V8InspectorClientBase { &mut self.v8_inspector_client } @@ -647,6 +657,14 @@ impl v8::inspector::ChannelImpl for InspectorSession { &self.v8_channel } + unsafe fn base_ptr(this: *const Self) -> *const v8::inspector::ChannelBase + where + Self: Sized, + { + // SAFETY: this pointer is valid for the whole lifetime of inspector + unsafe { std::ptr::addr_of!((*this).v8_channel) } + } + fn base_mut(&mut self) -> &mut v8::inspector::ChannelBase { &mut self.v8_channel } diff --git a/core/lib.deno_core.d.ts b/core/lib.deno_core.d.ts index ad6739b3c20e80..457fa07dbcb177 100644 --- a/core/lib.deno_core.d.ts +++ b/core/lib.deno_core.d.ts @@ -19,7 +19,7 @@ declare namespace Deno { /** Mark following promise as "unref", ie. event loop will exit * if there are only "unref" promises left. */ - function unrefOps(promiseId: number): void; + function unrefOp(promiseId: number): void; /** * List of all registered ops, in the form of a map that maps op diff --git a/core/lib.rs b/core/lib.rs index 461b4fd205f908..51a03493d606de 100644 --- a/core/lib.rs +++ b/core/lib.rs @@ -54,6 +54,8 @@ pub use crate::async_cell::RcLike; pub use crate::async_cell::RcRef; pub use crate::extensions::Extension; pub use crate::extensions::ExtensionBuilder; +pub use crate::extensions::ExtensionFileSource; +pub use crate::extensions::ExtensionFileSourceCode; pub use crate::extensions::OpDecl; pub use crate::extensions::OpMiddlewareFn; pub use crate::flags::v8_set_flags; @@ -73,6 +75,8 @@ pub use crate::module_specifier::ModuleResolutionError; pub use crate::module_specifier::ModuleSpecifier; pub use crate::module_specifier::DUMMY_SPECIFIER; pub use crate::modules::FsModuleLoader; +pub use crate::modules::InternalModuleLoader; +pub use crate::modules::InternalModuleLoaderCb; pub use crate::modules::ModuleId; pub use crate::modules::ModuleLoader; pub use crate::modules::ModuleSource; diff --git a/core/modules.rs b/core/modules.rs index 1b7169ea4ac745..b6220bb3bc6ac2 100644 --- a/core/modules.rs +++ b/core/modules.rs @@ -2,6 +2,7 @@ use crate::bindings; use crate::error::generic_error; +use crate::extensions::ExtensionFileSource; use crate::module_specifier::ModuleSpecifier; use crate::resolve_import; use crate::resolve_url; @@ -160,6 +161,7 @@ fn json_module_evaluation_steps<'a>( /// the module against an import assertion (if one is present /// in the import statement). #[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)] +#[repr(u32)] pub enum ModuleType { JavaScript, Json, @@ -274,21 +276,177 @@ pub struct NoopModuleLoader; impl ModuleLoader for NoopModuleLoader { fn resolve( &self, - _specifier: &str, - _referrer: &str, + specifier: &str, + referrer: &str, _kind: ResolutionKind, ) -> Result { - Err(generic_error("Module loading is not supported")) + Err(generic_error( + format!("Module loading is not supported; attempted to resolve: \"{specifier}\" from \"{referrer}\"") + )) } fn load( &self, - _module_specifier: &ModuleSpecifier, - _maybe_referrer: Option, + module_specifier: &ModuleSpecifier, + maybe_referrer: Option, _is_dyn_import: bool, ) -> Pin> { - async { Err(generic_error("Module loading is not supported")) } - .boxed_local() + let err = generic_error( + format!( + "Module loading is not supported; attempted to load: \"{module_specifier}\" from \"{maybe_referrer:?}\"", + ) + ); + async move { Err(err) }.boxed_local() + } +} + +/// Helper function, that calls into `loader.resolve()`, but denies resolution +/// of `internal` scheme if we are running with a snapshot loaded and not +/// creating a snapshot +pub(crate) fn resolve_helper( + snapshot_loaded_and_not_snapshotting: bool, + loader: Rc, + specifier: &str, + referrer: &str, + kind: ResolutionKind, +) -> Result { + if snapshot_loaded_and_not_snapshotting && specifier.starts_with("internal:") + { + return Err(generic_error( + "Cannot load internal module from external code", + )); + } + + loader.resolve(specifier, referrer, kind) +} + +/// Function that can be passed to the `InternalModuleLoader` that allows to +/// transpile sources before passing to V8. +pub type InternalModuleLoaderCb = + Box Result>; + +pub struct InternalModuleLoader { + module_loader: Rc, + esm_sources: Vec, + maybe_load_callback: Option, +} + +impl Default for InternalModuleLoader { + fn default() -> Self { + Self { + module_loader: Rc::new(NoopModuleLoader), + esm_sources: vec![], + maybe_load_callback: None, + } + } +} + +impl InternalModuleLoader { + pub fn new( + module_loader: Option>, + esm_sources: Vec, + maybe_load_callback: Option, + ) -> Self { + InternalModuleLoader { + module_loader: module_loader.unwrap_or_else(|| Rc::new(NoopModuleLoader)), + esm_sources, + maybe_load_callback, + } + } +} + +impl ModuleLoader for InternalModuleLoader { + fn resolve( + &self, + specifier: &str, + referrer: &str, + kind: ResolutionKind, + ) -> Result { + if let Ok(url_specifier) = ModuleSpecifier::parse(specifier) { + if url_specifier.scheme() == "internal" { + let referrer_specifier = ModuleSpecifier::parse(referrer).ok(); + if referrer == "." || referrer_specifier.unwrap().scheme() == "internal" + { + return Ok(url_specifier); + } else { + return Err(generic_error( + "Cannot load internal module from external code", + )); + }; + } + } + + self.module_loader.resolve(specifier, referrer, kind) + } + + fn load( + &self, + module_specifier: &ModuleSpecifier, + maybe_referrer: Option, + is_dyn_import: bool, + ) -> Pin> { + if module_specifier.scheme() != "internal" { + return self.module_loader.load( + module_specifier, + maybe_referrer, + is_dyn_import, + ); + } + + let specifier = module_specifier.to_string(); + let maybe_file_source = self + .esm_sources + .iter() + .find(|file_source| file_source.specifier == module_specifier.as_str()); + + if let Some(file_source) = maybe_file_source { + let result = if let Some(load_callback) = &self.maybe_load_callback { + load_callback(file_source) + } else { + match file_source.code.load() { + Ok(code) => Ok(code), + Err(err) => return futures::future::err(err).boxed_local(), + } + }; + + return async move { + let code = result?; + let source = ModuleSource { + code: code.into_bytes().into_boxed_slice(), + module_type: ModuleType::JavaScript, + module_url_specified: specifier.clone(), + module_url_found: specifier.clone(), + }; + Ok(source) + } + .boxed_local(); + } + + async move { + Err(generic_error(format!( + "Cannot find internal module source for specifier {specifier}" + ))) + } + .boxed_local() + } + + fn prepare_load( + &self, + op_state: Rc>, + module_specifier: &ModuleSpecifier, + maybe_referrer: Option, + is_dyn_import: bool, + ) -> Pin>>> { + if module_specifier.scheme() == "internal" { + return async { Ok(()) }.boxed_local(); + } + + self.module_loader.prepare_load( + op_state, + module_specifier, + maybe_referrer, + is_dyn_import, + ) } } @@ -377,10 +535,11 @@ pub(crate) struct RecursiveModuleLoad { module_map_rc: Rc>, pending: FuturesUnordered>>, visited: HashSet, - // These two fields are copied from `module_map_rc`, but they are cloned ahead - // of time to avoid already-borrowed errors. + // These three fields are copied from `module_map_rc`, but they are cloned + // ahead of time to avoid already-borrowed errors. op_state: Rc>, loader: Rc, + snapshot_loaded_and_not_snapshotting: bool, } impl RecursiveModuleLoad { @@ -436,6 +595,9 @@ impl RecursiveModuleLoad { init, state: LoadState::Init, module_map_rc: module_map_rc.clone(), + snapshot_loaded_and_not_snapshotting: module_map_rc + .borrow() + .snapshot_loaded_and_not_snapshotting, op_state, loader, pending: FuturesUnordered::new(), @@ -464,39 +626,60 @@ impl RecursiveModuleLoad { fn resolve_root(&self) -> Result { match self.init { - LoadInit::Main(ref specifier) => { - self - .loader - .resolve(specifier, ".", ResolutionKind::MainModule) - } - LoadInit::Side(ref specifier) => { - self.loader.resolve(specifier, ".", ResolutionKind::Import) + LoadInit::Main(ref specifier) => resolve_helper( + self.snapshot_loaded_and_not_snapshotting, + self.loader.clone(), + specifier, + ".", + ResolutionKind::MainModule, + ), + LoadInit::Side(ref specifier) => resolve_helper( + self.snapshot_loaded_and_not_snapshotting, + self.loader.clone(), + specifier, + ".", + ResolutionKind::Import, + ), + LoadInit::DynamicImport(ref specifier, ref referrer, _) => { + resolve_helper( + self.snapshot_loaded_and_not_snapshotting, + self.loader.clone(), + specifier, + referrer, + ResolutionKind::DynamicImport, + ) } - LoadInit::DynamicImport(ref specifier, ref referrer, _) => self - .loader - .resolve(specifier, referrer, ResolutionKind::DynamicImport), } } async fn prepare(&self) -> Result<(), Error> { let op_state = self.op_state.clone(); + let (module_specifier, maybe_referrer) = match self.init { LoadInit::Main(ref specifier) => { - let spec = - self - .loader - .resolve(specifier, ".", ResolutionKind::MainModule)?; + let spec = resolve_helper( + self.snapshot_loaded_and_not_snapshotting, + self.loader.clone(), + specifier, + ".", + ResolutionKind::MainModule, + )?; (spec, None) } LoadInit::Side(ref specifier) => { - let spec = - self - .loader - .resolve(specifier, ".", ResolutionKind::Import)?; + let spec = resolve_helper( + self.snapshot_loaded_and_not_snapshotting, + self.loader.clone(), + specifier, + ".", + ResolutionKind::Import, + )?; (spec, None) } LoadInit::DynamicImport(ref specifier, ref referrer, _) => { - let spec = self.loader.resolve( + let spec = resolve_helper( + self.snapshot_loaded_and_not_snapshotting, + self.loader.clone(), specifier, referrer, ResolutionKind::DynamicImport, @@ -592,7 +775,7 @@ impl RecursiveModuleLoad { self.visited.insert(module_request.clone()); while let Some((module_id, module_request)) = already_registered.pop_front() { - let referrer = module_request.specifier.clone(); + let referrer = ModuleSpecifier::parse(&module_request.specifier).unwrap(); let imports = self .module_map_rc .borrow() @@ -607,17 +790,15 @@ impl RecursiveModuleLoad { ) { already_registered.push_back((module_id, module_request.clone())); } else { - let referrer = referrer.clone(); let request = module_request.clone(); + let specifier = + ModuleSpecifier::parse(&module_request.specifier).unwrap(); + let referrer = referrer.clone(); let loader = self.loader.clone(); let is_dynamic_import = self.is_dynamic_import(); let fut = async move { let load_result = loader - .load( - &request.specifier, - Some(referrer.clone()), - is_dynamic_import, - ) + .load(&specifier, Some(referrer.clone()), is_dynamic_import) .await; load_result.map(|s| (request, s)) }; @@ -669,7 +850,7 @@ impl Stream for RecursiveModuleLoad { let asserted_module_type = inner.root_asserted_module_type.unwrap(); let module_type = inner.root_module_type.unwrap(); let module_request = ModuleRequest { - specifier: module_specifier.clone(), + specifier: module_specifier.to_string(), asserted_module_type, }; let module_source = ModuleSource { @@ -693,7 +874,7 @@ impl Stream for RecursiveModuleLoad { _ => AssertedModuleType::JavaScriptOrWasm, }; let module_request = ModuleRequest { - specifier: module_specifier.clone(), + specifier: module_specifier.to_string(), asserted_module_type, }; let loader = inner.loader.clone(); @@ -723,6 +904,7 @@ impl Stream for RecursiveModuleLoad { } #[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)] +#[repr(u32)] pub(crate) enum AssertedModuleType { JavaScriptOrWasm, Json, @@ -752,7 +934,7 @@ impl std::fmt::Display for AssertedModuleType { /// which case this will have a `AssertedModuleType::Json`. #[derive(Clone, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)] pub(crate) struct ModuleRequest { - pub specifier: ModuleSpecifier, + pub specifier: String, pub asserted_module_type: AssertedModuleType, } @@ -805,52 +987,102 @@ pub(crate) struct ModuleMap { // This store is used temporarly, to forward parsed JSON // value from `new_json_module` to `json_module_evaluation_steps` json_value_store: HashMap, v8::Global>, + + pub(crate) snapshot_loaded_and_not_snapshotting: bool, } impl ModuleMap { pub fn serialize_for_snapshotting( &self, scope: &mut v8::HandleScope, - ) -> (v8::Global, Vec>) { - let obj = v8::Object::new(scope); + ) -> (v8::Global, Vec>) { + let array = v8::Array::new(scope, 3); - let next_load_id_str = v8::String::new(scope, "next_load_id").unwrap(); let next_load_id = v8::Integer::new(scope, self.next_load_id); - obj.set(scope, next_load_id_str.into(), next_load_id.into()); + array.set_index(scope, 0, next_load_id.into()); - let info_val = serde_v8::to_v8(scope, self.info.clone()).unwrap(); - let info_str = v8::String::new(scope, "info").unwrap(); - obj.set(scope, info_str.into(), info_val); + let info_arr = v8::Array::new(scope, self.info.len() as i32); + for (i, info) in self.info.iter().enumerate() { + let module_info_arr = v8::Array::new(scope, 5); - let by_name_triples: Vec<(String, AssertedModuleType, SymbolicModule)> = - self - .by_name - .clone() - .into_iter() - .map(|el| (el.0 .0, el.0 .1, el.1)) - .collect(); - let by_name_array = serde_v8::to_v8(scope, by_name_triples).unwrap(); - let by_name_str = v8::String::new(scope, "by_name").unwrap(); - obj.set(scope, by_name_str.into(), by_name_array); + let id = v8::Integer::new(scope, info.id as i32); + module_info_arr.set_index(scope, 0, id.into()); + + let main = v8::Boolean::new(scope, info.main); + module_info_arr.set_index(scope, 1, main.into()); + + let name = v8::String::new(scope, &info.name).unwrap(); + module_info_arr.set_index(scope, 2, name.into()); - let obj_global = v8::Global::new(scope, obj); + let array_len = 2 * info.requests.len() as i32; + let requests_arr = v8::Array::new(scope, array_len); + for (i, request) in info.requests.iter().enumerate() { + let specifier = v8::String::new(scope, &request.specifier).unwrap(); + requests_arr.set_index(scope, 2 * i as u32, specifier.into()); + + let asserted_module_type = + v8::Integer::new(scope, request.asserted_module_type as i32); + requests_arr.set_index( + scope, + (2 * i) as u32 + 1, + asserted_module_type.into(), + ); + } + module_info_arr.set_index(scope, 3, requests_arr.into()); + + let module_type = v8::Integer::new(scope, info.module_type as i32); + module_info_arr.set_index(scope, 4, module_type.into()); + + info_arr.set_index(scope, i as u32, module_info_arr.into()); + } + array.set_index(scope, 1, info_arr.into()); + + let by_name_array = v8::Array::new(scope, self.by_name.len() as i32); + { + for (i, elem) in self.by_name.iter().enumerate() { + let arr = v8::Array::new(scope, 3); + + let (specifier, asserted_module_type) = elem.0; + let specifier = v8::String::new(scope, specifier).unwrap(); + arr.set_index(scope, 0, specifier.into()); + + let asserted_module_type = + v8::Integer::new(scope, *asserted_module_type as i32); + arr.set_index(scope, 1, asserted_module_type.into()); + + let symbolic_module: v8::Local = match &elem.1 { + SymbolicModule::Alias(alias) => { + let alias = v8::String::new(scope, alias).unwrap(); + alias.into() + } + SymbolicModule::Mod(id) => { + let id = v8::Integer::new(scope, *id as i32); + id.into() + } + }; + arr.set_index(scope, 2, symbolic_module); + + by_name_array.set_index(scope, i as u32, arr.into()); + } + } + array.set_index(scope, 2, by_name_array.into()); + + let array_global = v8::Global::new(scope, array); let handles = self.handles.clone(); - (obj_global, handles) + (array_global, handles) } pub fn update_with_snapshot_data( &mut self, scope: &mut v8::HandleScope, - data: v8::Global, + data: v8::Global, module_handles: Vec>, ) { - let local_data: v8::Local = v8::Local::new(scope, data); + let local_data: v8::Local = v8::Local::new(scope, data); { - let next_load_id_str = v8::String::new(scope, "next_load_id").unwrap(); - let next_load_id = - local_data.get(scope, next_load_id_str.into()).unwrap(); + let next_load_id = local_data.get_index(scope, 0).unwrap(); assert!(next_load_id.is_int32()); let integer = next_load_id.to_integer(scope).unwrap(); let val = integer.int32_value(scope).unwrap(); @@ -858,22 +1090,136 @@ impl ModuleMap { } { - let info_str = v8::String::new(scope, "info").unwrap(); - let info_val = local_data.get(scope, info_str.into()).unwrap(); - self.info = serde_v8::from_v8(scope, info_val).unwrap(); + let info_val = local_data.get_index(scope, 1).unwrap(); + + let info_arr: v8::Local = info_val.try_into().unwrap(); + let len = info_arr.length() as usize; + let mut info = Vec::with_capacity(len); + + for i in 0..len { + let module_info_arr: v8::Local = info_arr + .get_index(scope, i as u32) + .unwrap() + .try_into() + .unwrap(); + let id = module_info_arr + .get_index(scope, 0) + .unwrap() + .to_integer(scope) + .unwrap() + .value() as ModuleId; + + let main = module_info_arr + .get_index(scope, 1) + .unwrap() + .to_boolean(scope) + .is_true(); + + let name = module_info_arr + .get_index(scope, 2) + .unwrap() + .to_rust_string_lossy(scope); + + let requests_arr: v8::Local = module_info_arr + .get_index(scope, 3) + .unwrap() + .try_into() + .unwrap(); + let len = (requests_arr.length() as usize) / 2; + let mut requests = Vec::with_capacity(len); + for i in 0..len { + let specifier = requests_arr + .get_index(scope, (2 * i) as u32) + .unwrap() + .to_rust_string_lossy(scope); + let asserted_module_type_no = requests_arr + .get_index(scope, (2 * i + 1) as u32) + .unwrap() + .to_integer(scope) + .unwrap() + .value(); + let asserted_module_type = match asserted_module_type_no { + 0 => AssertedModuleType::JavaScriptOrWasm, + 1 => AssertedModuleType::Json, + _ => unreachable!(), + }; + requests.push(ModuleRequest { + specifier, + asserted_module_type, + }); + } + + let module_type_no = module_info_arr + .get_index(scope, 4) + .unwrap() + .to_integer(scope) + .unwrap() + .value(); + let module_type = match module_type_no { + 0 => ModuleType::JavaScript, + 1 => ModuleType::Json, + _ => unreachable!(), + }; + + let module_info = ModuleInfo { + id, + main, + name, + requests, + module_type, + }; + info.push(module_info); + } + + self.info = info; } { - let by_name_str = v8::String::new(scope, "by_name").unwrap(); - let by_name_data = local_data.get(scope, by_name_str.into()).unwrap(); - let by_name_deser: Vec<(String, AssertedModuleType, SymbolicModule)> = - serde_v8::from_v8(scope, by_name_data).unwrap(); - self.by_name = by_name_deser - .into_iter() - .map(|(name, module_type, symbolic_module)| { - ((name, module_type), symbolic_module) - }) - .collect(); + let by_name_arr: v8::Local = + local_data.get_index(scope, 2).unwrap().try_into().unwrap(); + let len = by_name_arr.length() as usize; + let mut by_name = HashMap::with_capacity(len); + + for i in 0..len { + let arr: v8::Local = by_name_arr + .get_index(scope, i as u32) + .unwrap() + .try_into() + .unwrap(); + + let specifier = + arr.get_index(scope, 0).unwrap().to_rust_string_lossy(scope); + let asserted_module_type = match arr + .get_index(scope, 1) + .unwrap() + .to_integer(scope) + .unwrap() + .value() + { + 0 => AssertedModuleType::JavaScriptOrWasm, + 1 => AssertedModuleType::Json, + _ => unreachable!(), + }; + let key = (specifier, asserted_module_type); + + let symbolic_module_val = arr.get_index(scope, 2).unwrap(); + let val = if symbolic_module_val.is_number() { + SymbolicModule::Mod( + symbolic_module_val + .to_integer(scope) + .unwrap() + .value() + .try_into() + .unwrap(), + ) + } else { + SymbolicModule::Alias(symbolic_module_val.to_rust_string_lossy(scope)) + }; + + by_name.insert(key, val); + } + + self.by_name = by_name; } self.handles = module_handles; @@ -882,6 +1228,7 @@ impl ModuleMap { pub(crate) fn new( loader: Rc, op_state: Rc>, + snapshot_loaded_and_not_snapshotting: bool, ) -> ModuleMap { Self { handles: vec![], @@ -894,6 +1241,7 @@ impl ModuleMap { preparing_dynamic_imports: FuturesUnordered::new(), pending_dynamic_imports: FuturesUnordered::new(), json_value_store: HashMap::new(), + snapshot_loaded_and_not_snapshotting, } } @@ -1020,7 +1368,9 @@ impl ModuleMap { return Err(ModuleError::Exception(exception)); } - let module_specifier = match self.loader.resolve( + let module_specifier = match resolve_helper( + self.snapshot_loaded_and_not_snapshotting, + self.loader.clone(), &import_specifier, name, if is_dynamic_import { @@ -1035,7 +1385,7 @@ impl ModuleMap { let asserted_module_type = get_asserted_module_type_from_assertions(&assertions); let request = ModuleRequest { - specifier: module_specifier, + specifier: module_specifier.to_string(), asserted_module_type, }; requests.push(request); @@ -1186,7 +1536,17 @@ impl ModuleMap { .borrow_mut() .dynamic_import_map .insert(load.id, resolver_handle); - let resolve_result = module_map_rc.borrow().loader.resolve( + + let (loader, snapshot_loaded_and_not_snapshotting) = { + let module_map = module_map_rc.borrow(); + ( + module_map.loader.clone(), + module_map.snapshot_loaded_and_not_snapshotting, + ) + }; + let resolve_result = resolve_helper( + snapshot_loaded_and_not_snapshotting, + loader, specifier, referrer, ResolutionKind::DynamicImport, @@ -1224,10 +1584,14 @@ impl ModuleMap { referrer: &str, import_assertions: HashMap, ) -> Option> { - let resolved_specifier = self - .loader - .resolve(specifier, referrer, ResolutionKind::Import) - .expect("Module should have been already resolved"); + let resolved_specifier = resolve_helper( + self.snapshot_loaded_and_not_snapshotting, + self.loader.clone(), + specifier, + referrer, + ResolutionKind::Import, + ) + .expect("Module should have been already resolved"); let module_type = get_asserted_module_type_from_assertions(&import_assertions); @@ -1517,11 +1881,11 @@ import "/a.js"; modules.get_requested_modules(a_id), Some(&vec![ ModuleRequest { - specifier: resolve_url("file:///b.js").unwrap(), + specifier: "file:///b.js".to_string(), asserted_module_type: AssertedModuleType::JavaScriptOrWasm, }, ModuleRequest { - specifier: resolve_url("file:///c.js").unwrap(), + specifier: "file:///c.js".to_string(), asserted_module_type: AssertedModuleType::JavaScriptOrWasm, }, ]) @@ -1529,14 +1893,14 @@ import "/a.js"; assert_eq!( modules.get_requested_modules(b_id), Some(&vec![ModuleRequest { - specifier: resolve_url("file:///c.js").unwrap(), + specifier: "file:///c.js".to_string(), asserted_module_type: AssertedModuleType::JavaScriptOrWasm, },]) ); assert_eq!( modules.get_requested_modules(c_id), Some(&vec![ModuleRequest { - specifier: resolve_url("file:///d.js").unwrap(), + specifier: "file:///d.js".to_string(), asserted_module_type: AssertedModuleType::JavaScriptOrWasm, },]) ); @@ -1637,7 +2001,7 @@ import "/a.js"; assert_eq!( imports, Some(&vec![ModuleRequest { - specifier: resolve_url("file:///b.js").unwrap(), + specifier: "file:///b.js".to_string(), asserted_module_type: AssertedModuleType::JavaScriptOrWasm, },]) ); @@ -1745,7 +2109,7 @@ import "/a.js"; assert_eq!( imports, Some(&vec![ModuleRequest { - specifier: resolve_url("file:///b.json").unwrap(), + specifier: "file:///b.json".to_string(), asserted_module_type: AssertedModuleType::Json, },]) ); @@ -2073,7 +2437,7 @@ import "/a.js"; assert_eq!( modules.get_requested_modules(circular1_id), Some(&vec![ModuleRequest { - specifier: resolve_url("file:///circular2.js").unwrap(), + specifier: "file:///circular2.js".to_string(), asserted_module_type: AssertedModuleType::JavaScriptOrWasm, }]) ); @@ -2081,7 +2445,7 @@ import "/a.js"; assert_eq!( modules.get_requested_modules(circular2_id), Some(&vec![ModuleRequest { - specifier: resolve_url("file:///circular3.js").unwrap(), + specifier: "file:///circular3.js".to_string(), asserted_module_type: AssertedModuleType::JavaScriptOrWasm, }]) ); @@ -2096,11 +2460,11 @@ import "/a.js"; modules.get_requested_modules(circular3_id), Some(&vec![ ModuleRequest { - specifier: resolve_url("file:///circular1.js").unwrap(), + specifier: "file:///circular1.js".to_string(), asserted_module_type: AssertedModuleType::JavaScriptOrWasm, }, ModuleRequest { - specifier: resolve_url("file:///circular2.js").unwrap(), + specifier: "file:///circular2.js".to_string(), asserted_module_type: AssertedModuleType::JavaScriptOrWasm, } ]) @@ -2320,11 +2684,11 @@ if (import.meta.url != 'file:///main_with_code.js') throw Error(); modules.get_requested_modules(main_id), Some(&vec![ ModuleRequest { - specifier: resolve_url("file:///b.js").unwrap(), + specifier: "file:///b.js".to_string(), asserted_module_type: AssertedModuleType::JavaScriptOrWasm, }, ModuleRequest { - specifier: resolve_url("file:///c.js").unwrap(), + specifier: "file:///c.js".to_string(), asserted_module_type: AssertedModuleType::JavaScriptOrWasm, } ]) @@ -2332,14 +2696,14 @@ if (import.meta.url != 'file:///main_with_code.js') throw Error(); assert_eq!( modules.get_requested_modules(b_id), Some(&vec![ModuleRequest { - specifier: resolve_url("file:///c.js").unwrap(), + specifier: "file:///c.js".to_string(), asserted_module_type: AssertedModuleType::JavaScriptOrWasm, }]) ); assert_eq!( modules.get_requested_modules(c_id), Some(&vec![ModuleRequest { - specifier: resolve_url("file:///d.js").unwrap(), + specifier: "file:///d.js".to_string(), asserted_module_type: AssertedModuleType::JavaScriptOrWasm, }]) ); @@ -2508,4 +2872,51 @@ if (import.meta.url != 'file:///main_with_code.js') throw Error(); ) .unwrap(); } + + #[test] + fn internal_module_loader() { + let loader = InternalModuleLoader::default(); + assert!(loader + .resolve("internal:foo", "internal:bar", ResolutionKind::Import) + .is_ok()); + assert_eq!( + loader + .resolve("internal:foo", "file://bar", ResolutionKind::Import) + .err() + .map(|e| e.to_string()), + Some("Cannot load internal module from external code".to_string()) + ); + assert_eq!( + loader + .resolve("file://foo", "file://bar", ResolutionKind::Import) + .err() + .map(|e| e.to_string()), + Some( + "Module loading is not supported; attempted to resolve: \"file://foo\" from \"file://bar\"" + .to_string() + ) + ); + assert_eq!( + loader + .resolve("file://foo", "internal:bar", ResolutionKind::Import) + .err() + .map(|e| e.to_string()), + Some( + "Module loading is not supported; attempted to resolve: \"file://foo\" from \"internal:bar\"" + .to_string() + ) + ); + assert_eq!( + resolve_helper( + true, + Rc::new(loader), + "internal:core.js", + "file://bar", + ResolutionKind::Import, + ) + .err() + .map(|e| e.to_string()), + Some("Cannot load internal module from external code".to_string()) + ); + } } diff --git a/core/ops_builtin.rs b/core/ops_builtin.rs index 87504f41d25f55..1cc1e8d34579e8 100644 --- a/core/ops_builtin.rs +++ b/core/ops_builtin.rs @@ -19,9 +19,8 @@ use std::io::Write; use std::rc::Rc; pub(crate) fn init_builtins() -> Extension { - Extension::builder("deno_builtins") + Extension::builder("core") .js(include_js_files!( - prefix "internal:core", "00_primordials.js", "01_core.js", "02_error.js", diff --git a/core/ops_builtin_v8.rs b/core/ops_builtin_v8.rs index a94f8a50b7321a..c3c4ec092f017c 100644 --- a/core/ops_builtin_v8.rs +++ b/core/ops_builtin_v8.rs @@ -595,25 +595,27 @@ fn op_get_promise_details<'a>( #[op(v8)] fn op_set_promise_hooks( scope: &mut v8::HandleScope, - init_cb: serde_v8::Value, - before_cb: serde_v8::Value, - after_cb: serde_v8::Value, - resolve_cb: serde_v8::Value, + init_hook: serde_v8::Value, + before_hook: serde_v8::Value, + after_hook: serde_v8::Value, + resolve_hook: serde_v8::Value, ) -> Result<(), Error> { - let init_hook_global = to_v8_fn(scope, init_cb)?; - let before_hook_global = to_v8_fn(scope, before_cb)?; - let after_hook_global = to_v8_fn(scope, after_cb)?; - let resolve_hook_global = to_v8_fn(scope, resolve_cb)?; - let init_hook = v8::Local::new(scope, init_hook_global); - let before_hook = v8::Local::new(scope, before_hook_global); - let after_hook = v8::Local::new(scope, after_hook_global); - let resolve_hook = v8::Local::new(scope, resolve_hook_global); - - scope.get_current_context().set_promise_hooks( - init_hook, - before_hook, - after_hook, - resolve_hook, + let v8_fns = [init_hook, before_hook, after_hook, resolve_hook] + .into_iter() + .enumerate() + .filter(|(_, hook)| !hook.v8_value.is_undefined()) + .try_fold([None; 4], |mut v8_fns, (i, hook)| { + let v8_fn = v8::Local::::try_from(hook.v8_value) + .map_err(|err| type_error(err.to_string()))?; + v8_fns[i] = Some(v8_fn); + Ok::<_, Error>(v8_fns) + })?; + + scope.set_promise_hooks( + v8_fns[0], // init + v8_fns[1], // before + v8_fns[2], // after + v8_fns[3], // resolve ); Ok(()) diff --git a/core/runtime.rs b/core/runtime.rs index 1bda16f3e773ac..81314f0bf7a2a1 100644 --- a/core/runtime.rs +++ b/core/runtime.rs @@ -8,22 +8,25 @@ use crate::extensions::OpDecl; use crate::extensions::OpEventLoopFn; use crate::inspector::JsRuntimeInspector; use crate::module_specifier::ModuleSpecifier; +use crate::modules::InternalModuleLoaderCb; use crate::modules::ModuleError; use crate::modules::ModuleId; use crate::modules::ModuleLoadId; use crate::modules::ModuleLoader; use crate::modules::ModuleMap; -use crate::modules::NoopModuleLoader; use crate::op_void_async; use crate::op_void_sync; use crate::ops::*; use crate::source_map::SourceMapCache; use crate::source_map::SourceMapGetter; use crate::Extension; +use crate::ExtensionFileSource; +use crate::NoopModuleLoader; use crate::OpMiddlewareFn; use crate::OpResult; use crate::OpState; use crate::PromiseId; +use anyhow::Context as AnyhowContext; use anyhow::Error; use futures::channel::oneshot; use futures::future::poll_fn; @@ -199,9 +202,9 @@ fn v8_init( ) { // Include 10MB ICU data file. #[repr(C, align(16))] - struct IcuData([u8; 10454784]); + struct IcuData([u8; 10541264]); static ICU_DATA: IcuData = IcuData(*include_bytes!("icudtl.dat")); - v8::icu::set_common_data_71(&ICU_DATA.0).unwrap(); + v8::icu::set_common_data_72(&ICU_DATA.0).unwrap(); let flags = concat!( " --wasm-test-streaming", @@ -269,6 +272,11 @@ pub struct RuntimeOptions { /// The snapshot is deterministic and uses predictable random numbers. pub will_snapshot: bool, + /// An optional callback that will be called for each module that is loaded + /// during snapshotting. This callback can be used to transpile source on the + /// fly, during snapshotting, eg. to transpile TypeScript to JavaScript. + pub snapshot_module_load_cb: Option, + /// Isolate creation parameters. pub create_params: Option, @@ -314,12 +322,6 @@ impl SnapshotOptions { SnapshotOptions::Load | SnapshotOptions::CreateFromExisting ) } - pub fn will_snapshot(&self) -> bool { - matches!( - self, - SnapshotOptions::Create | SnapshotOptions::CreateFromExisting - ) - } fn from_bools(snapshot_loaded: bool, will_snapshot: bool) -> Self { match (snapshot_loaded, will_snapshot) { @@ -430,7 +432,7 @@ impl JsRuntime { fn get_context_data( scope: &mut v8::HandleScope<()>, context: v8::Local, - ) -> (Vec>, v8::Global) { + ) -> (Vec>, v8::Global) { fn data_error_to_panic(err: v8::DataError) -> ! { match err { v8::DataError::BadType { actual, expected } => { @@ -449,15 +451,11 @@ impl JsRuntime { // The 0th element is the module map itself, followed by X number of module // handles. We need to deserialize the "next_module_id" field from the // map to see how many module handles we expect. - match scope.get_context_data_from_snapshot_once::(0) { + match scope.get_context_data_from_snapshot_once::(0) { Ok(val) => { let next_module_id = { - let info_str = v8::String::new(&mut scope, "info").unwrap(); - let info_data: v8::Local = val - .get(&mut scope, info_str.into()) - .unwrap() - .try_into() - .unwrap(); + let info_data: v8::Local = + val.get_index(&mut scope, 1).unwrap().try_into().unwrap(); info_data.length() }; @@ -605,9 +603,34 @@ impl JsRuntime { None }; - let loader = options - .module_loader - .unwrap_or_else(|| Rc::new(NoopModuleLoader)); + let loader = if snapshot_options != SnapshotOptions::Load { + let esm_sources = options + .extensions_with_js + .iter() + .flat_map(|ext| ext.get_esm_sources().to_owned()) + .collect::>(); + + #[cfg(feature = "include_js_files_for_snapshotting")] + for source in &esm_sources { + use crate::ExtensionFileSourceCode; + if let ExtensionFileSourceCode::LoadedFromFsDuringSnapshot(path) = + &source.code + { + println!("cargo:rerun-if-changed={}", path.display()) + } + } + + Rc::new(crate::modules::InternalModuleLoader::new( + options.module_loader, + esm_sources, + options.snapshot_module_load_cb, + )) + } else { + options + .module_loader + .unwrap_or_else(|| Rc::new(NoopModuleLoader)) + }; + { let mut state = state_rc.borrow_mut(); state.global_realm = Some(JsRealm(global_context.clone())); @@ -621,7 +644,11 @@ impl JsRuntime { Rc::into_raw(state_rc.clone()) as *mut c_void, ); - let module_map_rc = Rc::new(RefCell::new(ModuleMap::new(loader, op_state))); + let module_map_rc = Rc::new(RefCell::new(ModuleMap::new( + loader, + op_state, + snapshot_options == SnapshotOptions::Load, + ))); if let Some(module_map_data) = module_map_data { let scope = &mut v8::HandleScope::with_context(&mut isolate, global_context); @@ -802,13 +829,52 @@ impl JsRuntime { /// Initializes JS of provided Extensions in the given realm fn init_extension_js(&mut self, realm: &JsRealm) -> Result<(), Error> { + fn load_and_evaluate_module( + runtime: &mut JsRuntime, + file_source: &ExtensionFileSource, + ) -> Result<(), Error> { + futures::executor::block_on(async { + let id = runtime + .load_side_module( + &ModuleSpecifier::parse(&file_source.specifier)?, + None, + ) + .await?; + let receiver = runtime.mod_evaluate(id); + runtime.run_event_loop(false).await?; + receiver.await? + }) + .with_context(|| format!("Couldn't execute '{}'", file_source.specifier)) + } + // Take extensions to avoid double-borrow let extensions = std::mem::take(&mut self.extensions_with_js); for ext in &extensions { - let js_files = ext.init_js(); - for (filename, source) in js_files { - // TODO(@AaronO): use JsRuntime::execute_static() here to move src off heap - realm.execute_script(self.v8_isolate(), filename, source)?; + { + let esm_files = ext.get_esm_sources(); + if let Some(entry_point) = ext.get_esm_entry_point() { + let file_source = esm_files + .iter() + .find(|file| file.specifier == entry_point) + .unwrap(); + load_and_evaluate_module(self, file_source)?; + } else { + for file_source in esm_files { + load_and_evaluate_module(self, file_source)?; + } + } + } + + { + let js_files = ext.get_js_sources(); + for file_source in js_files { + // TODO(@AaronO): use JsRuntime::execute_static() here to move src off heap + realm.execute_script( + self.v8_isolate(), + &file_source.specifier, + &file_source.code.load()?, + )?; + } } } // Restore extensions @@ -923,11 +989,36 @@ impl JsRuntime { fn init_cbs(&mut self, realm: &JsRealm) { let (recv_cb, build_custom_error_cb) = { let scope = &mut realm.handle_scope(self.v8_isolate()); - let recv_cb = - Self::eval::(scope, "Deno.core.opresolve").unwrap(); - let build_custom_error_cb = - Self::eval::(scope, "Deno.core.buildCustomError") - .expect("Deno.core.buildCustomError is undefined in the realm"); + let context = realm.context(); + let context_local = v8::Local::new(scope, context); + let global = context_local.global(scope); + let deno_str = v8::String::new(scope, "Deno").unwrap(); + let core_str = v8::String::new(scope, "core").unwrap(); + let opresolve_str = v8::String::new(scope, "opresolve").unwrap(); + let build_custom_error_str = + v8::String::new(scope, "buildCustomError").unwrap(); + + let deno_obj: v8::Local = global + .get(scope, deno_str.into()) + .unwrap() + .try_into() + .unwrap(); + let core_obj: v8::Local = deno_obj + .get(scope, core_str.into()) + .unwrap() + .try_into() + .unwrap(); + + let recv_cb: v8::Local = core_obj + .get(scope, opresolve_str.into()) + .unwrap() + .try_into() + .unwrap(); + let build_custom_error_cb: v8::Local = core_obj + .get(scope, build_custom_error_str.into()) + .unwrap() + .try_into() + .unwrap(); ( v8::Global::new(scope, recv_cb), v8::Global::new(scope, build_custom_error_cb), @@ -1717,7 +1808,11 @@ impl JsRuntime { .map(|handle| v8::Local::new(tc_scope, handle)) .expect("ModuleInfo not found"); let mut status = module.get_status(); - assert_eq!(status, v8::ModuleStatus::Instantiated); + assert_eq!( + status, + v8::ModuleStatus::Instantiated, + "Module not instantiated {id}" + ); let (sender, receiver) = oneshot::channel(); @@ -2462,7 +2557,6 @@ impl JsRuntime { let tc_scope = &mut v8::TryCatch::new(scope); let this = v8::undefined(tc_scope).into(); js_nexttick_cb.call(tc_scope, this, &[]); - if let Some(exception) = tc_scope.exception() { return exception_to_err_result(tc_scope, exception, false); } @@ -2823,10 +2917,10 @@ pub mod tests { .execute_script( "filename.js", r#" - Deno.core.initializeAsyncOps(); + var promiseIdSymbol = Symbol.for("Deno.core.internalPromiseId"); - var p1 = Deno.core.ops.op_test(42); - var p2 = Deno.core.ops.op_test(42); + var p1 = Deno.core.opAsync("op_test", 42); + var p2 = Deno.core.opAsync("op_test", 42); "#, ) .unwrap(); @@ -2879,7 +2973,7 @@ pub mod tests { "filename.js", r#" let control = 42; - Deno.core.initializeAsyncOps(); + Deno.core.opAsync("op_test", control); async function main() { Deno.core.opAsync("op_test", control); @@ -2898,7 +2992,7 @@ pub mod tests { .execute_script( "filename.js", r#" - Deno.core.initializeAsyncOps(); + const p = Deno.core.opAsync("op_test", 42); if (p[Symbol.for("Deno.core.internalPromiseId")] == undefined) { throw new Error("missing id on returned promise"); @@ -2915,7 +3009,7 @@ pub mod tests { .execute_script( "filename.js", r#" - Deno.core.initializeAsyncOps(); + Deno.core.opAsync("op_test"); "#, ) @@ -2930,7 +3024,7 @@ pub mod tests { .execute_script( "filename.js", r#" - Deno.core.initializeAsyncOps(); + let zero_copy_a = new Uint8Array([0]); Deno.core.opAsync("op_test", null, zero_copy_a); "#, @@ -3579,7 +3673,7 @@ pub mod tests { main, name: specifier.to_string(), requests: vec![crate::modules::ModuleRequest { - specifier: crate::resolve_url(&format!("file:///{prev}.js")).unwrap(), + specifier: format!("file:///{prev}.js"), asserted_module_type: AssertedModuleType::JavaScriptOrWasm, }], module_type: ModuleType::JavaScript, @@ -3804,7 +3898,7 @@ if (errMessage !== "higher-level sync error: original sync error") { .execute_script( "test_error_context_async.js", r#" -Deno.core.initializeAsyncOps(); + (async () => { let errMessage; try { @@ -3959,7 +4053,7 @@ assertEquals(1, notify_return_value); runtime .execute_script( "op_async_borrow.js", - "Deno.core.initializeAsyncOps(); Deno.core.ops.op_async_borrow()", + "Deno.core.opAsync(\"op_async_borrow\")", ) .unwrap(); runtime.run_event_loop(false).await.unwrap(); @@ -4033,8 +4127,8 @@ Deno.core.ops.op_sync_serialize_object_with_numbers_as_keys({ .execute_script( "op_async_serialize_object_with_numbers_as_keys.js", r#" -Deno.core.initializeAsyncOps(); -Deno.core.ops.op_async_serialize_object_with_numbers_as_keys({ + +Deno.core.opAsync("op_async_serialize_object_with_numbers_as_keys", { lines: { 100: { unit: "m" @@ -4072,7 +4166,7 @@ Deno.core.ops.op_async_serialize_object_with_numbers_as_keys({ .execute_script( "macrotasks_and_nextticks.js", r#" - Deno.core.initializeAsyncOps(); + (async function () { const results = []; Deno.core.ops.op_set_macrotask_callback(() => { @@ -4340,12 +4434,12 @@ Deno.core.ops.op_async_serialize_object_with_numbers_as_keys({ "", &format!( r#" - Deno.core.initializeAsyncOps(); + globalThis.rejectValue = undefined; Deno.core.setPromiseRejectCallback((_type, _promise, reason) => {{ globalThis.rejectValue = `{realm_name}/${{reason}}`; }}); - Deno.core.ops.op_void_async().then(() => Promise.reject({number})); + Deno.core.opAsync("op_void_async").then(() => Promise.reject({number})); "# ), ) @@ -4776,12 +4870,12 @@ Deno.core.ops.op_async_serialize_object_with_numbers_as_keys({ runtime.v8_isolate(), "", r#" - Deno.core.initializeAsyncOps(); + (async function () { - const buf = await Deno.core.ops.op_test(false); + const buf = await Deno.core.opAsync("op_test", false); let err; try { - await Deno.core.ops.op_test(true); + await Deno.core.opAsync("op_test", true); } catch(e) { err = e; } @@ -4830,8 +4924,8 @@ Deno.core.ops.op_async_serialize_object_with_numbers_as_keys({ runtime.v8_isolate(), "", r#" - Deno.core.initializeAsyncOps(); - var promise = Deno.core.ops.op_pending(); + + var promise = Deno.core.opAsync("op_pending"); "#, ) .unwrap(); @@ -4840,8 +4934,8 @@ Deno.core.ops.op_async_serialize_object_with_numbers_as_keys({ runtime.v8_isolate(), "", r#" - Deno.core.initializeAsyncOps(); - var promise = Deno.core.ops.op_pending(); + + var promise = Deno.core.opAsync("op_pending"); "#, ) .unwrap(); @@ -4894,4 +4988,72 @@ Deno.core.ops.op_async_serialize_object_with_numbers_as_keys({ ) .is_ok()); } + + #[tokio::test] + async fn cant_load_internal_module_when_snapshot_is_loaded_and_not_snapshotting( + ) { + #[derive(Default)] + struct ModsLoader; + + impl ModuleLoader for ModsLoader { + fn resolve( + &self, + specifier: &str, + referrer: &str, + _kind: ResolutionKind, + ) -> Result { + assert_eq!(specifier, "file:///main.js"); + assert_eq!(referrer, "."); + let s = crate::resolve_import(specifier, referrer).unwrap(); + Ok(s) + } + + fn load( + &self, + _module_specifier: &ModuleSpecifier, + _maybe_referrer: Option, + _is_dyn_import: bool, + ) -> Pin> { + let source = r#" + // This module doesn't really exist, just verifying that we'll get + // an error when specifier starts with "internal:". + import { core } from "internal:core.js"; + "#; + + async move { + Ok(ModuleSource { + code: source.as_bytes().to_vec().into_boxed_slice(), + module_url_specified: "file:///main.js".to_string(), + module_url_found: "file:///main.js".to_string(), + module_type: ModuleType::JavaScript, + }) + } + .boxed_local() + } + } + + let snapshot = { + let runtime = JsRuntime::new(RuntimeOptions { + will_snapshot: true, + ..Default::default() + }); + let snap: &[u8] = &runtime.snapshot(); + Vec::from(snap).into_boxed_slice() + }; + + let mut runtime2 = JsRuntime::new(RuntimeOptions { + module_loader: Some(Rc::new(ModsLoader)), + startup_snapshot: Some(Snapshot::Boxed(snapshot)), + ..Default::default() + }); + + let err = runtime2 + .load_main_module(&crate::resolve_url("file:///main.js").unwrap(), None) + .await + .unwrap_err(); + assert_eq!( + err.to_string(), + "Cannot load internal module from external code" + ); + } } diff --git a/core/snapshot_util.rs b/core/snapshot_util.rs index 8e397e26211cde..5b0ba92a0161b5 100644 --- a/core/snapshot_util.rs +++ b/core/snapshot_util.rs @@ -2,8 +2,10 @@ use std::path::Path; use std::path::PathBuf; +use std::time::Instant; use crate::Extension; +use crate::InternalModuleLoaderCb; use crate::JsRuntime; use crate::RuntimeOptions; use crate::Snapshot; @@ -16,38 +18,37 @@ pub struct CreateSnapshotOptions { pub startup_snapshot: Option, pub extensions: Vec, pub extensions_with_js: Vec, - pub additional_files: Vec, pub compression_cb: Option>, + pub snapshot_module_load_cb: Option, } pub fn create_snapshot(create_snapshot_options: CreateSnapshotOptions) { - let mut js_runtime = JsRuntime::new(RuntimeOptions { + let mut mark = Instant::now(); + + let js_runtime = JsRuntime::new(RuntimeOptions { will_snapshot: true, startup_snapshot: create_snapshot_options.startup_snapshot, extensions: create_snapshot_options.extensions, extensions_with_js: create_snapshot_options.extensions_with_js, + snapshot_module_load_cb: create_snapshot_options.snapshot_module_load_cb, ..Default::default() }); - - // TODO(nayeemrmn): https://github.com/rust-lang/cargo/issues/3946 to get the - // workspace root. - let display_root = Path::new(create_snapshot_options.cargo_manifest_dir) - .parent() - .unwrap(); - for file in create_snapshot_options.additional_files { - let display_path = file.strip_prefix(display_root).unwrap_or(&file); - let display_path_str = display_path.display().to_string(); - js_runtime - .execute_script( - &("internal:".to_string() + &display_path_str.replace('\\', "/")), - &std::fs::read_to_string(&file).unwrap(), - ) - .unwrap(); - } + println!( + "JsRuntime for snapshot prepared, took {:#?} ({})", + Instant::now().saturating_duration_since(mark), + create_snapshot_options.snapshot_path.display() + ); + mark = Instant::now(); let snapshot = js_runtime.snapshot(); let snapshot_slice: &[u8] = &snapshot; - println!("Snapshot size: {}", snapshot_slice.len()); + println!( + "Snapshot size: {}, took {:#?} ({})", + snapshot_slice.len(), + Instant::now().saturating_duration_since(mark), + create_snapshot_options.snapshot_path.display() + ); + mark = Instant::now(); let maybe_compressed_snapshot: Box> = if let Some(compression_cb) = create_snapshot_options.compression_cb { @@ -61,7 +62,13 @@ pub fn create_snapshot(create_snapshot_options: CreateSnapshotOptions) { (compression_cb)(&mut vec, snapshot_slice); - println!("Snapshot compressed size: {}", vec.len()); + println!( + "Snapshot compressed size: {}, took {:#?} ({})", + vec.len(), + Instant::now().saturating_duration_since(mark), + create_snapshot_options.snapshot_path.display() + ); + mark = std::time::Instant::now(); Box::new(vec) } else { @@ -74,14 +81,18 @@ pub fn create_snapshot(create_snapshot_options: CreateSnapshotOptions) { ) .unwrap(); println!( - "Snapshot written to: {} ", - create_snapshot_options.snapshot_path.display() + "Snapshot written, took: {:#?} ({})", + Instant::now().saturating_duration_since(mark), + create_snapshot_options.snapshot_path.display(), ); } +pub type FilterFn = Box bool>; + pub fn get_js_files( cargo_manifest_dir: &'static str, directory: &str, + filter: Option, ) -> Vec { let manifest_dir = Path::new(cargo_manifest_dir); let mut js_files = std::fs::read_dir(directory) @@ -92,7 +103,7 @@ pub fn get_js_files( }) .filter(|path| { path.extension().unwrap_or_default() == "js" - && !path.ends_with("99_main.js") + && filter.as_ref().map(|filter| filter(path)).unwrap_or(true) }) .collect::>(); js_files.sort(); diff --git a/examples/hello-triangle/README.md b/examples/hello-triangle/README.md index 3b533ba23ffad9..1a6a22c830f0e8 100644 --- a/examples/hello-triangle/README.md +++ b/examples/hello-triangle/README.md @@ -7,7 +7,7 @@ This example renders a triangle to a window. ## To Run ``` -denog run --unstable --wsi https://raw.githubusercontent.com/denogdev/denog/v0.6.0/examples/hello-triangle/main.ts +denog run --unstable --wsi https://raw.githubusercontent.com/denogdev/denog/v0.7.0/examples/hello-triangle/main.ts ``` ## Screenshots diff --git a/ext/broadcast_channel/01_broadcast_channel.js b/ext/broadcast_channel/01_broadcast_channel.js index 82bede8b06336e..2ff30161027a62 100644 --- a/ext/broadcast_channel/01_broadcast_channel.js +++ b/ext/broadcast_channel/01_broadcast_channel.js @@ -2,150 +2,149 @@ /// -"use strict"; - -((window) => { - const core = window.Deno.core; - const ops = core.ops; - const webidl = window.__bootstrap.webidl; - const { MessageEvent, defineEventHandler, setTarget } = - window.__bootstrap.event; - const { EventTarget } = window.__bootstrap.eventTarget; - const { DOMException } = window.__bootstrap.domException; - const { - ArrayPrototypeIndexOf, - ArrayPrototypeSplice, - ArrayPrototypePush, - Symbol, - Uint8Array, - } = window.__bootstrap.primordials; - - const _name = Symbol("[[name]]"); - const _closed = Symbol("[[closed]]"); - - const channels = []; - let rid = null; - - async function recv() { - while (channels.length > 0) { - const message = await core.opAsync("op_broadcast_recv", rid); - - if (message === null) { - break; - } - - const { 0: name, 1: data } = message; - dispatch(null, name, new Uint8Array(data)); +const core = globalThis.Deno.core; +const ops = core.ops; +import * as webidl from "internal:deno_webidl/00_webidl.js"; +import { + defineEventHandler, + EventTarget, + setTarget, +} from "internal:deno_web/02_event.js"; +import DOMException from "internal:deno_web/01_dom_exception.js"; +const primordials = globalThis.__bootstrap.primordials; +const { + ArrayPrototypeIndexOf, + ArrayPrototypeSplice, + ArrayPrototypePush, + Symbol, + Uint8Array, +} = primordials; + +const _name = Symbol("[[name]]"); +const _closed = Symbol("[[closed]]"); + +const channels = []; +let rid = null; + +async function recv() { + while (channels.length > 0) { + const message = await core.opAsync("op_broadcast_recv", rid); + + if (message === null) { + break; } - core.close(rid); - rid = null; + const { 0: name, 1: data } = message; + dispatch(null, name, new Uint8Array(data)); } - function dispatch(source, name, data) { - for (let i = 0; i < channels.length; ++i) { - const channel = channels[i]; - - if (channel === source) continue; // Don't self-send. - if (channel[_name] !== name) continue; - if (channel[_closed]) continue; - - const go = () => { - if (channel[_closed]) return; - const event = new MessageEvent("message", { - data: core.deserialize(data), // TODO(bnoordhuis) Cache immutables. - origin: "http://127.0.0.1", - }); - setTarget(event, channel); - channel.dispatchEvent(event); - }; - - defer(go); - } - } + core.close(rid); + rid = null; +} - // Defer to avoid starving the event loop. Not using queueMicrotask() - // for that reason: it lets promises make forward progress but can - // still starve other parts of the event loop. - function defer(go) { - setTimeout(go, 1); - } +function dispatch(source, name, data) { + for (let i = 0; i < channels.length; ++i) { + const channel = channels[i]; - class BroadcastChannel extends EventTarget { - [_name]; - [_closed] = false; + if (channel === source) continue; // Don't self-send. + if (channel[_name] !== name) continue; + if (channel[_closed]) continue; - get name() { - return this[_name]; - } + const go = () => { + if (channel[_closed]) return; + const event = new MessageEvent("message", { + data: core.deserialize(data), // TODO(bnoordhuis) Cache immutables. + origin: "http://127.0.0.1", + }); + setTarget(event, channel); + channel.dispatchEvent(event); + }; - constructor(name) { - super(); + defer(go); + } +} - const prefix = "Failed to construct 'BroadcastChannel'"; - webidl.requiredArguments(arguments.length, 1, { prefix }); +// Defer to avoid starving the event loop. Not using queueMicrotask() +// for that reason: it lets promises make forward progress but can +// still starve other parts of the event loop. +function defer(go) { + setTimeout(go, 1); +} - this[_name] = webidl.converters["DOMString"](name, { - prefix, - context: "Argument 1", - }); +class BroadcastChannel extends EventTarget { + [_name]; + [_closed] = false; - this[webidl.brand] = webidl.brand; + get name() { + return this[_name]; + } - ArrayPrototypePush(channels, this); + constructor(name) { + super(); - if (rid === null) { - // Create the rid immediately, otherwise there is a time window (and a - // race condition) where messages can get lost, because recv() is async. - rid = ops.op_broadcast_subscribe(); - recv(); - } - } + const prefix = "Failed to construct 'BroadcastChannel'"; + webidl.requiredArguments(arguments.length, 1, { prefix }); - postMessage(message) { - webidl.assertBranded(this, BroadcastChannelPrototype); + this[_name] = webidl.converters["DOMString"](name, { + prefix, + context: "Argument 1", + }); - const prefix = "Failed to execute 'postMessage' on 'BroadcastChannel'"; - webidl.requiredArguments(arguments.length, 1, { prefix }); + this[webidl.brand] = webidl.brand; - if (this[_closed]) { - throw new DOMException("Already closed", "InvalidStateError"); - } + ArrayPrototypePush(channels, this); - if (typeof message === "function" || typeof message === "symbol") { - throw new DOMException("Uncloneable value", "DataCloneError"); - } + if (rid === null) { + // Create the rid immediately, otherwise there is a time window (and a + // race condition) where messages can get lost, because recv() is async. + rid = ops.op_broadcast_subscribe(); + recv(); + } + } - const data = core.serialize(message); + postMessage(message) { + webidl.assertBranded(this, BroadcastChannelPrototype); - // Send to other listeners in this VM. - dispatch(this, this[_name], new Uint8Array(data)); + const prefix = "Failed to execute 'postMessage' on 'BroadcastChannel'"; + webidl.requiredArguments(arguments.length, 1, { prefix }); - // Send to listeners in other VMs. - defer(() => { - if (!this[_closed]) { - core.opAsync("op_broadcast_send", rid, this[_name], data); - } - }); + if (this[_closed]) { + throw new DOMException("Already closed", "InvalidStateError"); } - close() { - webidl.assertBranded(this, BroadcastChannelPrototype); - this[_closed] = true; + if (typeof message === "function" || typeof message === "symbol") { + throw new DOMException("Uncloneable value", "DataCloneError"); + } - const index = ArrayPrototypeIndexOf(channels, this); - if (index === -1) return; + const data = core.serialize(message); - ArrayPrototypeSplice(channels, index, 1); - if (channels.length === 0) { - ops.op_broadcast_unsubscribe(rid); + // Send to other listeners in this VM. + dispatch(this, this[_name], new Uint8Array(data)); + + // Send to listeners in other VMs. + defer(() => { + if (!this[_closed]) { + core.opAsync("op_broadcast_send", rid, this[_name], data); } + }); + } + + close() { + webidl.assertBranded(this, BroadcastChannelPrototype); + this[_closed] = true; + + const index = ArrayPrototypeIndexOf(channels, this); + if (index === -1) return; + + ArrayPrototypeSplice(channels, index, 1); + if (channels.length === 0) { + ops.op_broadcast_unsubscribe(rid); } } +} - defineEventHandler(BroadcastChannel.prototype, "message"); - defineEventHandler(BroadcastChannel.prototype, "messageerror"); - const BroadcastChannelPrototype = BroadcastChannel.prototype; +defineEventHandler(BroadcastChannel.prototype, "message"); +defineEventHandler(BroadcastChannel.prototype, "messageerror"); +const BroadcastChannelPrototype = BroadcastChannel.prototype; - window.__bootstrap.broadcastChannel = { BroadcastChannel }; -})(this); +export { BroadcastChannel }; diff --git a/ext/broadcast_channel/Cargo.toml b/ext/broadcast_channel/Cargo.toml index c182a7a8f2a36f..4e75367530408a 100644 --- a/ext/broadcast_channel/Cargo.toml +++ b/ext/broadcast_channel/Cargo.toml @@ -2,7 +2,7 @@ [package] name = "deno_broadcast_channel" -version = "0.83.0" +version = "0.85.0" authors.workspace = true edition.workspace = true license.workspace = true diff --git a/ext/broadcast_channel/lib.rs b/ext/broadcast_channel/lib.rs index 674d2414dd80aa..85994bf979138e 100644 --- a/ext/broadcast_channel/lib.rs +++ b/ext/broadcast_channel/lib.rs @@ -112,10 +112,7 @@ pub fn init( ) -> Extension { Extension::builder(env!("CARGO_PKG_NAME")) .dependencies(vec!["deno_webidl", "deno_web"]) - .js(include_js_files!( - prefix "internal:ext/broadcast_channel", - "01_broadcast_channel.js", - )) + .esm(include_js_files!("01_broadcast_channel.js",)) .ops(vec![ op_broadcast_subscribe::decl::(), op_broadcast_unsubscribe::decl::(), diff --git a/ext/cache/01_cache.js b/ext/cache/01_cache.js index bf0243e3c52f9d..b1e05d0dbf7bcd 100644 --- a/ext/cache/01_cache.js +++ b/ext/cache/01_cache.js @@ -1,296 +1,292 @@ // Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. -"use strict"; -((window) => { - const core = window.__bootstrap.core; - const webidl = window.__bootstrap.webidl; - const { - Symbol, - TypeError, - ObjectPrototypeIsPrototypeOf, - } = window.__bootstrap.primordials; - const { - Request, - toInnerResponse, - toInnerRequest, - } = window.__bootstrap.fetch; - const { URLPrototype } = window.__bootstrap.url; - const RequestPrototype = Request.prototype; - const { getHeader } = window.__bootstrap.headers; - const { readableStreamForRid } = window.__bootstrap.streams; +const core = globalThis.Deno.core; +import * as webidl from "internal:deno_webidl/00_webidl.js"; +const primordials = globalThis.__bootstrap.primordials; +const { + Symbol, + TypeError, + ObjectPrototypeIsPrototypeOf, +} = primordials; +import { + Request, + RequestPrototype, + toInnerRequest, +} from "internal:deno_fetch/23_request.js"; +import { toInnerResponse } from "internal:deno_fetch/23_response.js"; +import { URLPrototype } from "internal:deno_url/00_url.js"; +import { getHeader } from "internal:deno_fetch/20_headers.js"; +import { readableStreamForRid } from "internal:deno_web/06_streams.js"; - class CacheStorage { - constructor() { - webidl.illegalConstructor(); - } +class CacheStorage { + constructor() { + webidl.illegalConstructor(); + } - async open(cacheName) { - webidl.assertBranded(this, CacheStoragePrototype); - const prefix = "Failed to execute 'open' on 'CacheStorage'"; - webidl.requiredArguments(arguments.length, 1, { prefix }); - cacheName = webidl.converters["DOMString"](cacheName, { - prefix, - context: "Argument 1", - }); - const cacheId = await core.opAsync("op_cache_storage_open", cacheName); - const cache = webidl.createBranded(Cache); - cache[_id] = cacheId; - return cache; - } + async open(cacheName) { + webidl.assertBranded(this, CacheStoragePrototype); + const prefix = "Failed to execute 'open' on 'CacheStorage'"; + webidl.requiredArguments(arguments.length, 1, { prefix }); + cacheName = webidl.converters["DOMString"](cacheName, { + prefix, + context: "Argument 1", + }); + const cacheId = await core.opAsync("op_cache_storage_open", cacheName); + const cache = webidl.createBranded(Cache); + cache[_id] = cacheId; + return cache; + } - async has(cacheName) { - webidl.assertBranded(this, CacheStoragePrototype); - const prefix = "Failed to execute 'has' on 'CacheStorage'"; - webidl.requiredArguments(arguments.length, 1, { prefix }); - cacheName = webidl.converters["DOMString"](cacheName, { - prefix, - context: "Argument 1", - }); - return await core.opAsync("op_cache_storage_has", cacheName); - } + async has(cacheName) { + webidl.assertBranded(this, CacheStoragePrototype); + const prefix = "Failed to execute 'has' on 'CacheStorage'"; + webidl.requiredArguments(arguments.length, 1, { prefix }); + cacheName = webidl.converters["DOMString"](cacheName, { + prefix, + context: "Argument 1", + }); + return await core.opAsync("op_cache_storage_has", cacheName); + } - async delete(cacheName) { - webidl.assertBranded(this, CacheStoragePrototype); - const prefix = "Failed to execute 'delete' on 'CacheStorage'"; - webidl.requiredArguments(arguments.length, 1, { prefix }); - cacheName = webidl.converters["DOMString"](cacheName, { - prefix, - context: "Argument 1", - }); - return await core.opAsync("op_cache_storage_delete", cacheName); - } + async delete(cacheName) { + webidl.assertBranded(this, CacheStoragePrototype); + const prefix = "Failed to execute 'delete' on 'CacheStorage'"; + webidl.requiredArguments(arguments.length, 1, { prefix }); + cacheName = webidl.converters["DOMString"](cacheName, { + prefix, + context: "Argument 1", + }); + return await core.opAsync("op_cache_storage_delete", cacheName); } +} - const _matchAll = Symbol("[[matchAll]]"); - const _id = Symbol("id"); +const _matchAll = Symbol("[[matchAll]]"); +const _id = Symbol("id"); - class Cache { - /** @type {number} */ - [_id]; +class Cache { + /** @type {number} */ + [_id]; - constructor() { - webidl.illegalConstructor(); - } + constructor() { + webidl.illegalConstructor(); + } - /** See https://w3c.github.io/ServiceWorker/#dom-cache-put */ - async put(request, response) { - webidl.assertBranded(this, CachePrototype); - const prefix = "Failed to execute 'put' on 'Cache'"; - webidl.requiredArguments(arguments.length, 2, { prefix }); - request = webidl.converters["RequestInfo_DOMString"](request, { - prefix, - context: "Argument 1", - }); - response = webidl.converters["Response"](response, { - prefix, - context: "Argument 2", - }); - // Step 1. - let innerRequest = null; - // Step 2. - if (ObjectPrototypeIsPrototypeOf(RequestPrototype, request)) { - innerRequest = toInnerRequest(request); - } else { - // Step 3. - innerRequest = toInnerRequest(new Request(request)); - } - // Step 4. - const reqUrl = new URL(innerRequest.url()); - if (reqUrl.protocol !== "http:" && reqUrl.protocol !== "https:") { - throw new TypeError( - "Request url protocol must be 'http:' or 'https:'", - ); - } - if (innerRequest.method !== "GET") { - throw new TypeError("Request method must be GET"); - } - // Step 5. - const innerResponse = toInnerResponse(response); - // Step 6. - if (innerResponse.status === 206) { - throw new TypeError("Response status must not be 206"); - } - // Step 7. - const varyHeader = getHeader(innerResponse.headerList, "vary"); - if (varyHeader) { - const fieldValues = varyHeader.split(","); - for (let i = 0; i < fieldValues.length; ++i) { - const field = fieldValues[i]; - if (field.trim() === "*") { - throw new TypeError("Vary header must not contain '*'"); - } + /** See https://w3c.github.io/ServiceWorker/#dom-cache-put */ + async put(request, response) { + webidl.assertBranded(this, CachePrototype); + const prefix = "Failed to execute 'put' on 'Cache'"; + webidl.requiredArguments(arguments.length, 2, { prefix }); + request = webidl.converters["RequestInfo_DOMString"](request, { + prefix, + context: "Argument 1", + }); + response = webidl.converters["Response"](response, { + prefix, + context: "Argument 2", + }); + // Step 1. + let innerRequest = null; + // Step 2. + if (ObjectPrototypeIsPrototypeOf(RequestPrototype, request)) { + innerRequest = toInnerRequest(request); + } else { + // Step 3. + innerRequest = toInnerRequest(new Request(request)); + } + // Step 4. + const reqUrl = new URL(innerRequest.url()); + if (reqUrl.protocol !== "http:" && reqUrl.protocol !== "https:") { + throw new TypeError( + "Request url protocol must be 'http:' or 'https:'", + ); + } + if (innerRequest.method !== "GET") { + throw new TypeError("Request method must be GET"); + } + // Step 5. + const innerResponse = toInnerResponse(response); + // Step 6. + if (innerResponse.status === 206) { + throw new TypeError("Response status must not be 206"); + } + // Step 7. + const varyHeader = getHeader(innerResponse.headerList, "vary"); + if (varyHeader) { + const fieldValues = varyHeader.split(","); + for (let i = 0; i < fieldValues.length; ++i) { + const field = fieldValues[i]; + if (field.trim() === "*") { + throw new TypeError("Vary header must not contain '*'"); } } + } - // Step 8. - if (innerResponse.body !== null && innerResponse.body.unusable()) { - throw new TypeError("Response body is already used"); - } - // acquire lock before async op - const reader = innerResponse.body?.stream.getReader(); + // Step 8. + if (innerResponse.body !== null && innerResponse.body.unusable()) { + throw new TypeError("Response body is already used"); + } + // acquire lock before async op + const reader = innerResponse.body?.stream.getReader(); - // Remove fragment from request URL before put. - reqUrl.hash = ""; + // Remove fragment from request URL before put. + reqUrl.hash = ""; - // Step 9-11. - const rid = await core.opAsync( - "op_cache_put", - { - cacheId: this[_id], - requestUrl: reqUrl.toString(), - responseHeaders: innerResponse.headerList, - requestHeaders: innerRequest.headerList, - responseHasBody: innerResponse.body !== null, - responseStatus: innerResponse.status, - responseStatusText: innerResponse.statusMessage, - }, - ); - if (reader) { - try { - while (true) { - const { value, done } = await reader.read(); - if (done) { - await core.shutdown(rid); - break; - } - await core.writeAll(rid, value); + // Step 9-11. + const rid = await core.opAsync( + "op_cache_put", + { + cacheId: this[_id], + requestUrl: reqUrl.toString(), + responseHeaders: innerResponse.headerList, + requestHeaders: innerRequest.headerList, + responseHasBody: innerResponse.body !== null, + responseStatus: innerResponse.status, + responseStatusText: innerResponse.statusMessage, + }, + ); + if (reader) { + try { + while (true) { + const { value, done } = await reader.read(); + if (done) { + await core.shutdown(rid); + break; } - } finally { - core.close(rid); + await core.writeAll(rid, value); } + } finally { + core.close(rid); } - // Step 12-19: TODO(@satyarohith): do the insertion in background. } + // Step 12-19: TODO(@satyarohith): do the insertion in background. + } - /** See https://w3c.github.io/ServiceWorker/#cache-match */ - async match(request, options) { - webidl.assertBranded(this, CachePrototype); - const prefix = "Failed to execute 'match' on 'Cache'"; - webidl.requiredArguments(arguments.length, 1, { prefix }); - request = webidl.converters["RequestInfo_DOMString"](request, { - prefix, - context: "Argument 1", - }); - const p = await this[_matchAll](request, options); - if (p.length > 0) { - return p[0]; - } else { - return undefined; - } + /** See https://w3c.github.io/ServiceWorker/#cache-match */ + async match(request, options) { + webidl.assertBranded(this, CachePrototype); + const prefix = "Failed to execute 'match' on 'Cache'"; + webidl.requiredArguments(arguments.length, 1, { prefix }); + request = webidl.converters["RequestInfo_DOMString"](request, { + prefix, + context: "Argument 1", + }); + const p = await this[_matchAll](request, options); + if (p.length > 0) { + return p[0]; + } else { + return undefined; } + } - /** See https://w3c.github.io/ServiceWorker/#cache-delete */ - async delete(request, _options) { - webidl.assertBranded(this, CachePrototype); - const prefix = "Failed to execute 'delete' on 'Cache'"; - webidl.requiredArguments(arguments.length, 1, { prefix }); - request = webidl.converters["RequestInfo_DOMString"](request, { - prefix, - context: "Argument 1", - }); - // Step 1. - let r = null; - // Step 2. - if (ObjectPrototypeIsPrototypeOf(RequestPrototype, request)) { - r = request; - if (request.method !== "GET") { - return false; - } - } else if ( - typeof request === "string" || - ObjectPrototypeIsPrototypeOf(URLPrototype, request) - ) { - r = new Request(request); + /** See https://w3c.github.io/ServiceWorker/#cache-delete */ + async delete(request, _options) { + webidl.assertBranded(this, CachePrototype); + const prefix = "Failed to execute 'delete' on 'Cache'"; + webidl.requiredArguments(arguments.length, 1, { prefix }); + request = webidl.converters["RequestInfo_DOMString"](request, { + prefix, + context: "Argument 1", + }); + // Step 1. + let r = null; + // Step 2. + if (ObjectPrototypeIsPrototypeOf(RequestPrototype, request)) { + r = request; + if (request.method !== "GET") { + return false; } - return await core.opAsync("op_cache_delete", { - cacheId: this[_id], - requestUrl: r.url, - }); + } else if ( + typeof request === "string" || + ObjectPrototypeIsPrototypeOf(URLPrototype, request) + ) { + r = new Request(request); } + return await core.opAsync("op_cache_delete", { + cacheId: this[_id], + requestUrl: r.url, + }); + } - /** See https://w3c.github.io/ServiceWorker/#cache-matchall - * - * Note: the function is private as we don't want to expose - * this API to the public yet. - * - * The function will return an array of responses. - */ - async [_matchAll](request, _options) { - // Step 1. - let r = null; - // Step 2. - if (ObjectPrototypeIsPrototypeOf(RequestPrototype, request)) { - r = request; - if (request.method !== "GET") { - return []; - } - } else if ( - typeof request === "string" || - ObjectPrototypeIsPrototypeOf(URLPrototype, request) - ) { - r = new Request(request); + /** See https://w3c.github.io/ServiceWorker/#cache-matchall + * + * Note: the function is private as we don't want to expose + * this API to the public yet. + * + * The function will return an array of responses. + */ + async [_matchAll](request, _options) { + // Step 1. + let r = null; + // Step 2. + if (ObjectPrototypeIsPrototypeOf(RequestPrototype, request)) { + r = request; + if (request.method !== "GET") { + return []; } + } else if ( + typeof request === "string" || + ObjectPrototypeIsPrototypeOf(URLPrototype, request) + ) { + r = new Request(request); + } - // Step 5. - const responses = []; - // Step 5.2 - if (r === null) { - // Step 5.3 - // Note: we have to return all responses in the cache when - // the request is null. - // We deviate from the spec here and return an empty array - // as we don't expose matchAll() API. - return responses; - } else { - // Remove the fragment from the request URL. - const url = new URL(r.url); - url.hash = ""; - const innerRequest = toInnerRequest(r); - const matchResult = await core.opAsync( - "op_cache_match", + // Step 5. + const responses = []; + // Step 5.2 + if (r === null) { + // Step 5.3 + // Note: we have to return all responses in the cache when + // the request is null. + // We deviate from the spec here and return an empty array + // as we don't expose matchAll() API. + return responses; + } else { + // Remove the fragment from the request URL. + const url = new URL(r.url); + url.hash = ""; + const innerRequest = toInnerRequest(r); + const matchResult = await core.opAsync( + "op_cache_match", + { + cacheId: this[_id], + requestUrl: url.toString(), + requestHeaders: innerRequest.headerList, + }, + ); + if (matchResult) { + const { 0: meta, 1: responseBodyRid } = matchResult; + let body = null; + if (responseBodyRid !== null) { + body = readableStreamForRid(responseBodyRid); + } + const response = new Response( + body, { - cacheId: this[_id], - requestUrl: url.toString(), - requestHeaders: innerRequest.headerList, + headers: meta.responseHeaders, + status: meta.responseStatus, + statusText: meta.responseStatusText, }, ); - if (matchResult) { - const { 0: meta, 1: responseBodyRid } = matchResult; - let body = null; - if (responseBodyRid !== null) { - body = readableStreamForRid(responseBodyRid); - } - const response = new Response( - body, - { - headers: meta.responseHeaders, - status: meta.responseStatus, - statusText: meta.responseStatusText, - }, - ); - responses.push(response); - } + responses.push(response); } - // Step 5.4-5.5: don't apply in this context. - - return responses; } + // Step 5.4-5.5: don't apply in this context. + + return responses; } +} - webidl.configurePrototype(CacheStorage); - webidl.configurePrototype(Cache); - const CacheStoragePrototype = CacheStorage.prototype; - const CachePrototype = Cache.prototype; +webidl.configurePrototype(CacheStorage); +webidl.configurePrototype(Cache); +const CacheStoragePrototype = CacheStorage.prototype; +const CachePrototype = Cache.prototype; - let cacheStorage; - window.__bootstrap.caches = { - CacheStorage, - Cache, - cacheStorage() { - if (!cacheStorage) { - cacheStorage = webidl.createBranded(CacheStorage); - } - return cacheStorage; - }, - }; -})(this); +let cacheStorageStorage; +function cacheStorage() { + if (!cacheStorageStorage) { + cacheStorageStorage = webidl.createBranded(CacheStorage); + } + return cacheStorageStorage; +} + +export { Cache, CacheStorage, cacheStorage }; diff --git a/ext/cache/Cargo.toml b/ext/cache/Cargo.toml index 4eb4a9da05a493..4c5b300ff48f4a 100644 --- a/ext/cache/Cargo.toml +++ b/ext/cache/Cargo.toml @@ -2,7 +2,7 @@ [package] name = "deno_cache" -version = "0.21.0" +version = "0.23.0" authors.workspace = true edition.workspace = true license.workspace = true diff --git a/ext/cache/lib.rs b/ext/cache/lib.rs index 8884071534fa91..b0efbca55254fe 100644 --- a/ext/cache/lib.rs +++ b/ext/cache/lib.rs @@ -27,10 +27,7 @@ pub fn init( ) -> Extension { Extension::builder(env!("CARGO_PKG_NAME")) .dependencies(vec!["deno_webidl", "deno_web", "deno_url", "deno_fetch"]) - .js(include_js_files!( - prefix "internal:ext/cache", - "01_cache.js", - )) + .esm(include_js_files!("01_cache.js",)) .ops(vec![ op_cache_storage_open::decl::(), op_cache_storage_has::decl::(), diff --git a/ext/console/01_colors.js b/ext/console/01_colors.js index 00425f08b759bb..d01edd2471944e 100644 --- a/ext/console/01_colors.js +++ b/ext/console/01_colors.js @@ -2,110 +2,107 @@ /// -"use strict"; - -((window) => { - const { - RegExp, - StringPrototypeReplace, - ArrayPrototypeJoin, - } = window.__bootstrap.primordials; - - let noColor = false; - - function setNoColor(value) { - noColor = value; - } - - function getNoColor() { - return noColor; - } - - function code(open, close) { - return { - open: `\x1b[${open}m`, - close: `\x1b[${close}m`, - regexp: new RegExp(`\\x1b\\[${close}m`, "g"), - }; - } - - function run(str, code) { - return `${code.open}${ - StringPrototypeReplace(str, code.regexp, code.open) - }${code.close}`; - } - - function bold(str) { - return run(str, code(1, 22)); - } - - function italic(str) { - return run(str, code(3, 23)); - } - - function yellow(str) { - return run(str, code(33, 39)); - } - - function cyan(str) { - return run(str, code(36, 39)); - } - - function red(str) { - return run(str, code(31, 39)); - } - - function green(str) { - return run(str, code(32, 39)); - } - - function bgRed(str) { - return run(str, code(41, 49)); - } - - function white(str) { - return run(str, code(37, 39)); - } - - function gray(str) { - return run(str, code(90, 39)); - } - - function magenta(str) { - return run(str, code(35, 39)); - } - - // https://github.com/chalk/ansi-regex/blob/02fa893d619d3da85411acc8fd4e2eea0e95a9d9/index.js - const ANSI_PATTERN = new RegExp( - ArrayPrototypeJoin([ - "[\\u001B\\u009B][[\\]()#;?]*(?:(?:(?:(?:;[-a-zA-Z\\d\\/#&.:=?%@~_]+)*|[a-zA-Z\\d]+(?:;[-a-zA-Z\\d\\/#&.:=?%@~_]*)*)?\\u0007)", - "(?:(?:\\d{1,4}(?:;\\d{0,4})*)?[\\dA-PR-TZcf-nq-uy=><~]))", - ], "|"), - "g", - ); - - function stripColor(string) { - return StringPrototypeReplace(string, ANSI_PATTERN, ""); - } - - function maybeColor(fn) { - return !noColor ? fn : (s) => s; - } - - window.__bootstrap.colors = { - bold, - italic, - yellow, - cyan, - red, - green, - bgRed, - white, - gray, - magenta, - stripColor, - maybeColor, - setNoColor, - getNoColor, +const primordials = globalThis.__bootstrap.primordials; +const { + RegExp, + StringPrototypeReplace, + ArrayPrototypeJoin, +} = primordials; + +let noColor = false; + +function setNoColor(value) { + noColor = value; +} + +function getNoColor() { + return noColor; +} + +function code(open, close) { + return { + open: `\x1b[${open}m`, + close: `\x1b[${close}m`, + regexp: new RegExp(`\\x1b\\[${close}m`, "g"), }; -})(this); +} + +function run(str, code) { + return `${code.open}${ + StringPrototypeReplace(str, code.regexp, code.open) + }${code.close}`; +} + +function bold(str) { + return run(str, code(1, 22)); +} + +function italic(str) { + return run(str, code(3, 23)); +} + +function yellow(str) { + return run(str, code(33, 39)); +} + +function cyan(str) { + return run(str, code(36, 39)); +} + +function red(str) { + return run(str, code(31, 39)); +} + +function green(str) { + return run(str, code(32, 39)); +} + +function bgRed(str) { + return run(str, code(41, 49)); +} + +function white(str) { + return run(str, code(37, 39)); +} + +function gray(str) { + return run(str, code(90, 39)); +} + +function magenta(str) { + return run(str, code(35, 39)); +} + +// https://github.com/chalk/ansi-regex/blob/02fa893d619d3da85411acc8fd4e2eea0e95a9d9/index.js +const ANSI_PATTERN = new RegExp( + ArrayPrototypeJoin([ + "[\\u001B\\u009B][[\\]()#;?]*(?:(?:(?:(?:;[-a-zA-Z\\d\\/#&.:=?%@~_]+)*|[a-zA-Z\\d]+(?:;[-a-zA-Z\\d\\/#&.:=?%@~_]*)*)?\\u0007)", + "(?:(?:\\d{1,4}(?:;\\d{0,4})*)?[\\dA-PR-TZcf-nq-uy=><~]))", + ], "|"), + "g", +); + +function stripColor(string) { + return StringPrototypeReplace(string, ANSI_PATTERN, ""); +} + +function maybeColor(fn) { + return !noColor ? fn : (s) => s; +} + +export { + bgRed, + bold, + cyan, + getNoColor, + gray, + green, + italic, + magenta, + maybeColor, + red, + setNoColor, + stripColor, + white, + yellow, +}; diff --git a/ext/console/02_console.js b/ext/console/02_console.js index a9ec524887ef6d..a8b335b948ce95 100644 --- a/ext/console/02_console.js +++ b/ext/console/02_console.js @@ -2,2385 +2,2375 @@ /// -"use strict"; - -((window) => { - const core = window.Deno.core; - const colors = window.__bootstrap.colors; - const { - AggregateErrorPrototype, - ArrayPrototypeUnshift, - isNaN, - DatePrototype, - DateNow, - DatePrototypeGetTime, - DatePrototypeToISOString, - Boolean, - BooleanPrototype, - BooleanPrototypeToString, - ObjectKeys, - ObjectCreate, - ObjectAssign, - ObjectIs, - ObjectValues, - ObjectFromEntries, - ObjectGetPrototypeOf, - ObjectGetOwnPropertyDescriptor, - ObjectGetOwnPropertySymbols, - ObjectPrototypeHasOwnProperty, - ObjectPrototypeIsPrototypeOf, - ObjectPrototypePropertyIsEnumerable, - PromisePrototype, - String, - StringPrototype, - StringPrototypeRepeat, - StringPrototypeReplace, - StringPrototypeReplaceAll, - StringPrototypeSplit, - StringPrototypeSlice, - StringPrototypeCodePointAt, - StringPrototypeCharCodeAt, - StringPrototypeNormalize, - StringPrototypeMatch, - StringPrototypePadStart, - StringPrototypeLocaleCompare, - StringPrototypeToString, - StringPrototypeTrim, - StringPrototypeIncludes, - StringPrototypeStartsWith, - TypeError, - NumberParseInt, - RegExp, - RegExpPrototype, - RegExpPrototypeTest, - RegExpPrototypeToString, - SafeArrayIterator, - SafeStringIterator, - SafeSet, - SetPrototype, - SetPrototypeEntries, - SetPrototypeGetSize, - Symbol, - SymbolPrototype, - SymbolPrototypeToString, - SymbolPrototypeValueOf, - SymbolToStringTag, - SymbolHasInstance, - SymbolFor, - Array, - ArrayIsArray, - ArrayPrototypeJoin, - ArrayPrototypeMap, - ArrayPrototypeReduce, - ArrayPrototypeEntries, - ArrayPrototypePush, - ArrayPrototypePop, - ArrayPrototypeSort, - ArrayPrototypeSlice, - ArrayPrototypeShift, - ArrayPrototypeIncludes, - ArrayPrototypeFill, - ArrayPrototypeFilter, - ArrayPrototypeFind, - FunctionPrototypeBind, - FunctionPrototypeToString, - Map, - MapPrototype, - MapPrototypeHas, - MapPrototypeGet, - MapPrototypeSet, - MapPrototypeDelete, - MapPrototypeEntries, - MapPrototypeForEach, - MapPrototypeGetSize, - Error, - ErrorPrototype, - ErrorCaptureStackTrace, - MathAbs, - MathMax, - MathMin, - MathSqrt, - MathRound, - MathFloor, - Number, - NumberPrototype, - NumberPrototypeToString, - NumberPrototypeValueOf, - BigIntPrototype, - BigIntPrototypeToString, - Proxy, - ReflectGet, - ReflectGetOwnPropertyDescriptor, - ReflectGetPrototypeOf, - ReflectHas, - TypedArrayPrototypeGetLength, - TypedArrayPrototypeGetSymbolToStringTag, - WeakMapPrototype, - WeakSetPrototype, - } = window.__bootstrap.primordials; - - function isInvalidDate(x) { - return isNaN(DatePrototypeGetTime(x)); - } - - function hasOwnProperty(obj, v) { - if (obj == null) { - return false; - } - return ObjectPrototypeHasOwnProperty(obj, v); - } - - function propertyIsEnumerable(obj, prop) { - if ( - obj == null || - typeof obj.propertyIsEnumerable !== "function" - ) { - return false; - } - - return ObjectPrototypePropertyIsEnumerable(obj, prop); +const core = globalThis.Deno.core; +const internals = globalThis.__bootstrap.internals; +const primordials = globalThis.__bootstrap.primordials; +const { + AggregateErrorPrototype, + ArrayPrototypeUnshift, + isNaN, + DatePrototype, + DateNow, + DatePrototypeGetTime, + DatePrototypeToISOString, + Boolean, + BooleanPrototype, + BooleanPrototypeToString, + ObjectKeys, + ObjectCreate, + ObjectAssign, + ObjectIs, + ObjectValues, + ObjectFromEntries, + ObjectGetPrototypeOf, + ObjectGetOwnPropertyDescriptor, + ObjectGetOwnPropertySymbols, + ObjectPrototypeHasOwnProperty, + ObjectPrototypeIsPrototypeOf, + ObjectPrototypePropertyIsEnumerable, + PromisePrototype, + String, + StringPrototype, + StringPrototypeRepeat, + StringPrototypeReplace, + StringPrototypeReplaceAll, + StringPrototypeSplit, + StringPrototypeSlice, + StringPrototypeCodePointAt, + StringPrototypeCharCodeAt, + StringPrototypeNormalize, + StringPrototypeMatch, + StringPrototypePadStart, + StringPrototypeLocaleCompare, + StringPrototypeToString, + StringPrototypeTrim, + StringPrototypeIncludes, + StringPrototypeStartsWith, + TypeError, + NumberIsInteger, + NumberParseInt, + RegExp, + RegExpPrototype, + RegExpPrototypeTest, + RegExpPrototypeToString, + SafeArrayIterator, + SafeStringIterator, + SafeSet, + SetPrototype, + SetPrototypeEntries, + SetPrototypeGetSize, + Symbol, + SymbolPrototype, + SymbolPrototypeToString, + SymbolPrototypeValueOf, + SymbolToStringTag, + SymbolHasInstance, + SymbolFor, + Array, + ArrayIsArray, + ArrayPrototypeJoin, + ArrayPrototypeMap, + ArrayPrototypeReduce, + ArrayPrototypeEntries, + ArrayPrototypePush, + ArrayPrototypePop, + ArrayPrototypeSort, + ArrayPrototypeSlice, + ArrayPrototypeShift, + ArrayPrototypeIncludes, + ArrayPrototypeFill, + ArrayPrototypeFilter, + ArrayPrototypeFind, + FunctionPrototypeBind, + FunctionPrototypeToString, + Map, + MapPrototype, + MapPrototypeHas, + MapPrototypeGet, + MapPrototypeSet, + MapPrototypeDelete, + MapPrototypeEntries, + MapPrototypeForEach, + MapPrototypeGetSize, + Error, + ErrorPrototype, + ErrorCaptureStackTrace, + MathAbs, + MathMax, + MathMin, + MathSqrt, + MathRound, + MathFloor, + Number, + NumberPrototype, + NumberPrototypeToString, + NumberPrototypeValueOf, + BigIntPrototype, + BigIntPrototypeToString, + Proxy, + ReflectGet, + ReflectGetOwnPropertyDescriptor, + ReflectGetPrototypeOf, + ReflectHas, + TypedArrayPrototypeGetLength, + TypedArrayPrototypeGetSymbolToStringTag, + WeakMapPrototype, + WeakSetPrototype, +} = primordials; +import * as colors from "internal:deno_console/01_colors.js"; + +function isInvalidDate(x) { + return isNaN(DatePrototypeGetTime(x)); +} + +function hasOwnProperty(obj, v) { + if (obj == null) { + return false; } + return ObjectPrototypeHasOwnProperty(obj, v); +} - // Copyright Joyent, Inc. and other Node contributors. MIT license. - // Forked from Node's lib/internal/cli_table.js - - function isTypedArray(x) { - return TypedArrayPrototypeGetSymbolToStringTag(x) !== undefined; +function propertyIsEnumerable(obj, prop) { + if ( + obj == null || + typeof obj.propertyIsEnumerable !== "function" + ) { + return false; } - const tableChars = { - middleMiddle: "─", - rowMiddle: "┼", - topRight: "┐", - topLeft: "┌", - leftMiddle: "├", - topMiddle: "┬", - bottomRight: "┘", - bottomLeft: "└", - bottomMiddle: "┴", - rightMiddle: "┤", - left: "│ ", - right: " │", - middle: " │ ", - }; - - function isFullWidthCodePoint(code) { - // Code points are partially derived from: - // http://www.unicode.org/Public/UNIDATA/EastAsianWidth.txt - return ( - code >= 0x1100 && - (code <= 0x115f || // Hangul Jamo - code === 0x2329 || // LEFT-POINTING ANGLE BRACKET - code === 0x232a || // RIGHT-POINTING ANGLE BRACKET - // CJK Radicals Supplement .. Enclosed CJK Letters and Months - (code >= 0x2e80 && code <= 0x3247 && code !== 0x303f) || - // Enclosed CJK Letters and Months .. CJK Unified Ideographs Extension A - (code >= 0x3250 && code <= 0x4dbf) || - // CJK Unified Ideographs .. Yi Radicals - (code >= 0x4e00 && code <= 0xa4c6) || - // Hangul Jamo Extended-A - (code >= 0xa960 && code <= 0xa97c) || - // Hangul Syllables - (code >= 0xac00 && code <= 0xd7a3) || - // CJK Compatibility Ideographs - (code >= 0xf900 && code <= 0xfaff) || - // Vertical Forms - (code >= 0xfe10 && code <= 0xfe19) || - // CJK Compatibility Forms .. Small Form Variants - (code >= 0xfe30 && code <= 0xfe6b) || - // Halfwidth and Fullwidth Forms - (code >= 0xff01 && code <= 0xff60) || - (code >= 0xffe0 && code <= 0xffe6) || - // Kana Supplement - (code >= 0x1b000 && code <= 0x1b001) || - // Enclosed Ideographic Supplement - (code >= 0x1f200 && code <= 0x1f251) || - // Miscellaneous Symbols and Pictographs 0x1f300 - 0x1f5ff - // Emoticons 0x1f600 - 0x1f64f - (code >= 0x1f300 && code <= 0x1f64f) || - // CJK Unified Ideographs Extension B .. Tertiary Ideographic Plane - (code >= 0x20000 && code <= 0x3fffd)) - ); + return ObjectPrototypePropertyIsEnumerable(obj, prop); +} + +// Copyright Joyent, Inc. and other Node contributors. MIT license. +// Forked from Node's lib/internal/cli_table.js + +function isTypedArray(x) { + return TypedArrayPrototypeGetSymbolToStringTag(x) !== undefined; +} + +const tableChars = { + middleMiddle: "─", + rowMiddle: "┼", + topRight: "┐", + topLeft: "┌", + leftMiddle: "├", + topMiddle: "┬", + bottomRight: "┘", + bottomLeft: "└", + bottomMiddle: "┴", + rightMiddle: "┤", + left: "│ ", + right: " │", + middle: " │ ", +}; + +function isFullWidthCodePoint(code) { + // Code points are partially derived from: + // http://www.unicode.org/Public/UNIDATA/EastAsianWidth.txt + return ( + code >= 0x1100 && + (code <= 0x115f || // Hangul Jamo + code === 0x2329 || // LEFT-POINTING ANGLE BRACKET + code === 0x232a || // RIGHT-POINTING ANGLE BRACKET + // CJK Radicals Supplement .. Enclosed CJK Letters and Months + (code >= 0x2e80 && code <= 0x3247 && code !== 0x303f) || + // Enclosed CJK Letters and Months .. CJK Unified Ideographs Extension A + (code >= 0x3250 && code <= 0x4dbf) || + // CJK Unified Ideographs .. Yi Radicals + (code >= 0x4e00 && code <= 0xa4c6) || + // Hangul Jamo Extended-A + (code >= 0xa960 && code <= 0xa97c) || + // Hangul Syllables + (code >= 0xac00 && code <= 0xd7a3) || + // CJK Compatibility Ideographs + (code >= 0xf900 && code <= 0xfaff) || + // Vertical Forms + (code >= 0xfe10 && code <= 0xfe19) || + // CJK Compatibility Forms .. Small Form Variants + (code >= 0xfe30 && code <= 0xfe6b) || + // Halfwidth and Fullwidth Forms + (code >= 0xff01 && code <= 0xff60) || + (code >= 0xffe0 && code <= 0xffe6) || + // Kana Supplement + (code >= 0x1b000 && code <= 0x1b001) || + // Enclosed Ideographic Supplement + (code >= 0x1f200 && code <= 0x1f251) || + // Miscellaneous Symbols and Pictographs 0x1f300 - 0x1f5ff + // Emoticons 0x1f600 - 0x1f64f + (code >= 0x1f300 && code <= 0x1f64f) || + // CJK Unified Ideographs Extension B .. Tertiary Ideographic Plane + (code >= 0x20000 && code <= 0x3fffd)) + ); +} + +function getStringWidth(str) { + str = StringPrototypeNormalize(colors.stripColor(str), "NFC"); + let width = 0; + + for (const ch of new SafeStringIterator(str)) { + width += isFullWidthCodePoint(StringPrototypeCodePointAt(ch, 0)) ? 2 : 1; } - function getStringWidth(str) { - str = StringPrototypeNormalize(colors.stripColor(str), "NFC"); - let width = 0; - - for (const ch of new SafeStringIterator(str)) { - width += isFullWidthCodePoint(StringPrototypeCodePointAt(ch, 0)) ? 2 : 1; + return width; +} + +function renderRow(row, columnWidths, columnRightAlign) { + let out = tableChars.left; + for (let i = 0; i < row.length; i++) { + const cell = row[i]; + const len = getStringWidth(cell); + const padding = StringPrototypeRepeat(" ", columnWidths[i] - len); + if (columnRightAlign?.[i]) { + out += `${padding}${cell}`; + } else { + out += `${cell}${padding}`; + } + if (i !== row.length - 1) { + out += tableChars.middle; } - - return width; } - - function renderRow(row, columnWidths, columnRightAlign) { - let out = tableChars.left; - for (let i = 0; i < row.length; i++) { - const cell = row[i]; - const len = getStringWidth(cell); - const padding = StringPrototypeRepeat(" ", columnWidths[i] - len); - if (columnRightAlign?.[i]) { - out += `${padding}${cell}`; - } else { - out += `${cell}${padding}`; - } - if (i !== row.length - 1) { - out += tableChars.middle; + out += tableChars.right; + return out; +} + +function cliTable(head, columns) { + const rows = []; + const columnWidths = ArrayPrototypeMap(head, (h) => getStringWidth(h)); + const longestColumn = ArrayPrototypeReduce( + columns, + (n, a) => MathMax(n, a.length), + 0, + ); + const columnRightAlign = new Array(columnWidths.length).fill(true); + + for (let i = 0; i < head.length; i++) { + const column = columns[i]; + for (let j = 0; j < longestColumn; j++) { + if (rows[j] === undefined) { + rows[j] = []; } + const value = (rows[j][i] = hasOwnProperty(column, j) ? column[j] : ""); + const width = columnWidths[i] || 0; + const counted = getStringWidth(value); + columnWidths[i] = MathMax(width, counted); + columnRightAlign[i] &= NumberIsInteger(+value); } - out += tableChars.right; - return out; } - function canRightAlign(value) { - const isNumber = !isNaN(value); - return isNumber; + const divider = ArrayPrototypeMap( + columnWidths, + (i) => StringPrototypeRepeat(tableChars.middleMiddle, i + 2), + ); + + let result = + `${tableChars.topLeft}${ + ArrayPrototypeJoin(divider, tableChars.topMiddle) + }` + + `${tableChars.topRight}\n${renderRow(head, columnWidths)}\n` + + `${tableChars.leftMiddle}${ + ArrayPrototypeJoin(divider, tableChars.rowMiddle) + }` + + `${tableChars.rightMiddle}\n`; + + for (let i = 0; i < rows.length; ++i) { + const row = rows[i]; + result += `${renderRow(row, columnWidths, columnRightAlign)}\n`; } - function cliTable(head, columns) { - const rows = []; - const columnWidths = ArrayPrototypeMap(head, (h) => getStringWidth(h)); - const longestColumn = ArrayPrototypeReduce( - columns, - (n, a) => MathMax(n, a.length), - 0, - ); - const columnRightAlign = new Array(columnWidths.length).fill(true); - - for (let i = 0; i < head.length; i++) { - const column = columns[i]; - for (let j = 0; j < longestColumn; j++) { - if (rows[j] === undefined) { - rows[j] = []; - } - const value = (rows[j][i] = hasOwnProperty(column, j) ? column[j] : ""); - const width = columnWidths[i] || 0; - const counted = getStringWidth(value); - columnWidths[i] = MathMax(width, counted); - columnRightAlign[i] &= canRightAlign(value); - } - } + result += + `${tableChars.bottomLeft}${ + ArrayPrototypeJoin(divider, tableChars.bottomMiddle) + }` + + tableChars.bottomRight; + + return result; +} +/* End of forked part */ + +const DEFAULT_INSPECT_OPTIONS = { + depth: 4, + indentLevel: 0, + sorted: false, + trailingComma: false, + compact: true, + iterableLimit: 100, + showProxy: false, + colors: false, + getters: false, + showHidden: false, + strAbbreviateSize: 100, +}; + +const DEFAULT_INDENT = " "; // Default indent string + +const LINE_BREAKING_LENGTH = 80; +const MIN_GROUP_LENGTH = 6; +const STR_ABBREVIATE_SIZE = 100; + +const PROMISE_STRING_BASE_LENGTH = 12; + +class CSI { + static kClear = "\x1b[1;1H"; + static kClearScreenDown = "\x1b[0J"; +} + +function getClassInstanceName(instance) { + if (typeof instance != "object") { + return ""; + } + const constructor = instance?.constructor; + if (typeof constructor == "function") { + return constructor.name ?? ""; + } + return ""; +} + +function maybeColor(fn, inspectOptions) { + return inspectOptions.colors ? fn : (s) => s; +} + +function inspectFunction(value, inspectOptions) { + const cyan = maybeColor(colors.cyan, inspectOptions); + if ( + ReflectHas(value, customInspect) && + typeof value[customInspect] === "function" + ) { + return String(value[customInspect](inspect, inspectOptions)); + } + // Might be Function/AsyncFunction/GeneratorFunction/AsyncGeneratorFunction + let cstrName = ObjectGetPrototypeOf(value)?.constructor?.name; + if (!cstrName) { + // If prototype is removed or broken, + // use generic 'Function' instead. + cstrName = "Function"; + } + const stringValue = FunctionPrototypeToString(value); + // Might be Class + if (StringPrototypeStartsWith(stringValue, "class")) { + cstrName = "Class"; + } - const divider = ArrayPrototypeMap( - columnWidths, - (i) => StringPrototypeRepeat(tableChars.middleMiddle, i + 2), + // Our function may have properties, so we want to format those + // as if our function was an object + // If we didn't find any properties, we will just append an + // empty suffix. + let suffix = ``; + let refStr = ""; + if ( + ObjectKeys(value).length > 0 || + ObjectGetOwnPropertySymbols(value).length > 0 + ) { + const { 0: propString, 1: refIndex } = inspectRawObject( + value, + inspectOptions, ); - - let result = - `${tableChars.topLeft}${ - ArrayPrototypeJoin(divider, tableChars.topMiddle) - }` + - `${tableChars.topRight}\n${renderRow(head, columnWidths)}\n` + - `${tableChars.leftMiddle}${ - ArrayPrototypeJoin(divider, tableChars.rowMiddle) - }` + - `${tableChars.rightMiddle}\n`; - - for (let i = 0; i < rows.length; ++i) { - const row = rows[i]; - result += `${renderRow(row, columnWidths, columnRightAlign)}\n`; + refStr = refIndex; + // Filter out the empty string for the case we only have + // non-enumerable symbols. + if ( + propString.length > 0 && + propString !== "{}" + ) { + suffix = ` ${propString}`; } - - result += - `${tableChars.bottomLeft}${ - ArrayPrototypeJoin(divider, tableChars.bottomMiddle) - }` + - tableChars.bottomRight; - - return result; - } - /* End of forked part */ - - const DEFAULT_INSPECT_OPTIONS = { - depth: 4, - indentLevel: 0, - sorted: false, - trailingComma: false, - compact: true, - iterableLimit: 100, - showProxy: false, - colors: false, - getters: false, - showHidden: false, - strAbbreviateSize: 100, - }; - - const DEFAULT_INDENT = " "; // Default indent string - - const LINE_BREAKING_LENGTH = 80; - const MIN_GROUP_LENGTH = 6; - const STR_ABBREVIATE_SIZE = 100; - - const PROMISE_STRING_BASE_LENGTH = 12; - - class CSI { - static kClear = "\x1b[1;1H"; - static kClearScreenDown = "\x1b[0J"; } - function getClassInstanceName(instance) { - if (typeof instance != "object") { - return ""; - } - const constructor = instance?.constructor; - if (typeof constructor == "function") { - return constructor.name ?? ""; - } - return ""; + if (value.name && value.name !== "anonymous") { + // from MDN spec + return cyan(`${refStr}[${cstrName}: ${value.name}]`) + suffix; } - - function maybeColor(fn, inspectOptions) { - return inspectOptions.colors ? fn : (s) => s; + return cyan(`${refStr}[${cstrName}]`) + suffix; +} + +function inspectIterable( + value, + options, + inspectOptions, +) { + const cyan = maybeColor(colors.cyan, inspectOptions); + if (inspectOptions.indentLevel >= inspectOptions.depth) { + return cyan(`[${options.typeName}]`); } - function inspectFunction(value, inspectOptions) { - const cyan = maybeColor(colors.cyan, inspectOptions); - if ( - ReflectHas(value, customInspect) && - typeof value[customInspect] === "function" - ) { - return String(value[customInspect](inspect, inspectOptions)); - } - // Might be Function/AsyncFunction/GeneratorFunction/AsyncGeneratorFunction - let cstrName = ObjectGetPrototypeOf(value)?.constructor?.name; - if (!cstrName) { - // If prototype is removed or broken, - // use generic 'Function' instead. - cstrName = "Function"; - } - const stringValue = FunctionPrototypeToString(value); - // Might be Class - if (StringPrototypeStartsWith(stringValue, "class")) { - cstrName = "Class"; - } + const entries = []; + let iter; + let valueIsTypedArray = false; + let entriesLength; + + switch (options.typeName) { + case "Map": + iter = MapPrototypeEntries(value); + entriesLength = MapPrototypeGetSize(value); + break; + case "Set": + iter = SetPrototypeEntries(value); + entriesLength = SetPrototypeGetSize(value); + break; + case "Array": + entriesLength = value.length; + break; + default: + if (isTypedArray(value)) { + entriesLength = TypedArrayPrototypeGetLength(value); + iter = ArrayPrototypeEntries(value); + valueIsTypedArray = true; + } else { + throw new TypeError("unreachable"); + } + } - // Our function may have properties, so we want to format those - // as if our function was an object - // If we didn't find any properties, we will just append an - // empty suffix. - let suffix = ``; - let refStr = ""; - if ( - ObjectKeys(value).length > 0 || - ObjectGetOwnPropertySymbols(value).length > 0 + let entriesLengthWithoutEmptyItems = entriesLength; + if (options.typeName === "Array") { + for ( + let i = 0, j = 0; + i < entriesLength && j < inspectOptions.iterableLimit; + i++, j++ ) { - const { 0: propString, 1: refIndex } = inspectRawObject( - value, + inspectOptions.indentLevel++; + const { entry, skipTo } = options.entryHandler( + [i, value[i]], inspectOptions, ); - refStr = refIndex; - // Filter out the empty string for the case we only have - // non-enumerable symbols. - if ( - propString.length > 0 && - propString !== "{}" - ) { - suffix = ` ${propString}`; - } - } - - if (value.name && value.name !== "anonymous") { - // from MDN spec - return cyan(`${refStr}[${cstrName}: ${value.name}]`) + suffix; - } - return cyan(`${refStr}[${cstrName}]`) + suffix; - } + ArrayPrototypePush(entries, entry); + inspectOptions.indentLevel--; - function inspectIterable( - value, - options, - inspectOptions, - ) { - const cyan = maybeColor(colors.cyan, inspectOptions); - if (inspectOptions.indentLevel >= inspectOptions.depth) { - return cyan(`[${options.typeName}]`); + if (skipTo) { + // subtract skipped (empty) items + entriesLengthWithoutEmptyItems -= skipTo - i; + i = skipTo; + } } - - const entries = []; - let iter; - let valueIsTypedArray = false; - let entriesLength; - - switch (options.typeName) { - case "Map": - iter = MapPrototypeEntries(value); - entriesLength = MapPrototypeGetSize(value); - break; - case "Set": - iter = SetPrototypeEntries(value); - entriesLength = SetPrototypeGetSize(value); - break; - case "Array": - entriesLength = value.length; - break; - default: - if (isTypedArray(value)) { - entriesLength = TypedArrayPrototypeGetLength(value); - iter = ArrayPrototypeEntries(value); - valueIsTypedArray = true; - } else { - throw new TypeError("unreachable"); + } else { + let i = 0; + while (true) { + let el; + try { + const res = iter.next(); + if (res.done) { + break; } - } - - let entriesLengthWithoutEmptyItems = entriesLength; - if (options.typeName === "Array") { - for ( - let i = 0, j = 0; - i < entriesLength && j < inspectOptions.iterableLimit; - i++, j++ - ) { + el = res.value; + } catch (err) { + if (valueIsTypedArray) { + // TypedArray.prototype.entries doesn't throw, unless the ArrayBuffer + // is detached. We don't want to show the exception in that case, so + // we catch it here and pretend the ArrayBuffer has no entries (like + // Chrome DevTools does). + break; + } + throw err; + } + if (i < inspectOptions.iterableLimit) { inspectOptions.indentLevel++; - const { entry, skipTo } = options.entryHandler( - [i, value[i]], - inspectOptions, + ArrayPrototypePush( + entries, + options.entryHandler( + el, + inspectOptions, + ), ); - ArrayPrototypePush(entries, entry); inspectOptions.indentLevel--; - - if (skipTo) { - // subtract skipped (empty) items - entriesLengthWithoutEmptyItems -= skipTo - i; - i = skipTo; - } - } - } else { - let i = 0; - while (true) { - let el; - try { - const res = iter.next(); - if (res.done) { - break; - } - el = res.value; - } catch (err) { - if (valueIsTypedArray) { - // TypedArray.prototype.entries doesn't throw, unless the ArrayBuffer - // is detached. We don't want to show the exception in that case, so - // we catch it here and pretend the ArrayBuffer has no entries (like - // Chrome DevTools does). - break; - } - throw err; - } - if (i < inspectOptions.iterableLimit) { - inspectOptions.indentLevel++; - ArrayPrototypePush( - entries, - options.entryHandler( - el, - inspectOptions, - ), - ); - inspectOptions.indentLevel--; - } else { - break; - } - i++; + } else { + break; } + i++; } + } - if (options.sort) { - ArrayPrototypeSort(entries); - } - - if (entriesLengthWithoutEmptyItems > inspectOptions.iterableLimit) { - const nmore = entriesLengthWithoutEmptyItems - - inspectOptions.iterableLimit; - ArrayPrototypePush(entries, `... ${nmore} more items`); - } + if (options.sort) { + ArrayPrototypeSort(entries); + } - const iPrefix = `${options.displayName ? options.displayName + " " : ""}`; + if (entriesLengthWithoutEmptyItems > inspectOptions.iterableLimit) { + const nmore = entriesLengthWithoutEmptyItems - + inspectOptions.iterableLimit; + ArrayPrototypePush(entries, `... ${nmore} more items`); + } - const level = inspectOptions.indentLevel; - const initIndentation = `\n${ - StringPrototypeRepeat(DEFAULT_INDENT, level + 1) - }`; - const entryIndentation = `,\n${ - StringPrototypeRepeat(DEFAULT_INDENT, level + 1) - }`; - const closingDelimIndentation = StringPrototypeRepeat( - DEFAULT_INDENT, - level, - ); - const closingIndentation = `${ - inspectOptions.trailingComma ? "," : "" - }\n${closingDelimIndentation}`; - - let iContent; - if (entries.length === 0 && !inspectOptions.compact) { - iContent = `\n${closingDelimIndentation}`; - } else if (options.group && entries.length > MIN_GROUP_LENGTH) { - const groups = groupEntries(entries, level, value); + const iPrefix = `${options.displayName ? options.displayName + " " : ""}`; + + const level = inspectOptions.indentLevel; + const initIndentation = `\n${ + StringPrototypeRepeat(DEFAULT_INDENT, level + 1) + }`; + const entryIndentation = `,\n${ + StringPrototypeRepeat(DEFAULT_INDENT, level + 1) + }`; + const closingDelimIndentation = StringPrototypeRepeat( + DEFAULT_INDENT, + level, + ); + const closingIndentation = `${ + inspectOptions.trailingComma ? "," : "" + }\n${closingDelimIndentation}`; + + let iContent; + if (entries.length === 0 && !inspectOptions.compact) { + iContent = `\n${closingDelimIndentation}`; + } else if (options.group && entries.length > MIN_GROUP_LENGTH) { + const groups = groupEntries(entries, level, value); + iContent = `${initIndentation}${ + ArrayPrototypeJoin(groups, entryIndentation) + }${closingIndentation}`; + } else { + iContent = entries.length === 0 + ? "" + : ` ${ArrayPrototypeJoin(entries, ", ")} `; + if ( + colors.stripColor(iContent).length > LINE_BREAKING_LENGTH || + !inspectOptions.compact + ) { iContent = `${initIndentation}${ - ArrayPrototypeJoin(groups, entryIndentation) + ArrayPrototypeJoin(entries, entryIndentation) }${closingIndentation}`; - } else { - iContent = entries.length === 0 - ? "" - : ` ${ArrayPrototypeJoin(entries, ", ")} `; - if ( - colors.stripColor(iContent).length > LINE_BREAKING_LENGTH || - !inspectOptions.compact - ) { - iContent = `${initIndentation}${ - ArrayPrototypeJoin(entries, entryIndentation) - }${closingIndentation}`; - } } - - return `${iPrefix}${options.delims[0]}${iContent}${options.delims[1]}`; } - // Ported from Node.js - // Copyright Node.js contributors. All rights reserved. - function groupEntries( - entries, - level, - value, - iterableLimit = 100, + return `${iPrefix}${options.delims[0]}${iContent}${options.delims[1]}`; +} + +// Ported from Node.js +// Copyright Node.js contributors. All rights reserved. +function groupEntries( + entries, + level, + value, + iterableLimit = 100, +) { + let totalLength = 0; + let maxLength = 0; + let entriesLength = entries.length; + if (iterableLimit < entriesLength) { + // This makes sure the "... n more items" part is not taken into account. + entriesLength--; + } + const separatorSpace = 2; // Add 1 for the space and 1 for the separator. + const dataLen = new Array(entriesLength); + // Calculate the total length of all output entries and the individual max + // entries length of all output entries. + // IN PROGRESS: Colors are being taken into account. + for (let i = 0; i < entriesLength; i++) { + // Taking colors into account: removing the ANSI color + // codes from the string before measuring its length + const len = colors.stripColor(entries[i]).length; + dataLen[i] = len; + totalLength += len + separatorSpace; + if (maxLength < len) maxLength = len; + } + // Add two to `maxLength` as we add a single whitespace character plus a comma + // in-between two entries. + const actualMax = maxLength + separatorSpace; + // Check if at least three entries fit next to each other and prevent grouping + // of arrays that contains entries of very different length (i.e., if a single + // entry is longer than 1/5 of all other entries combined). Otherwise the + // space in-between small entries would be enormous. + if ( + actualMax * 3 + (level + 1) < LINE_BREAKING_LENGTH && + (totalLength / actualMax > 5 || maxLength <= 6) ) { - let totalLength = 0; - let maxLength = 0; - let entriesLength = entries.length; - if (iterableLimit < entriesLength) { - // This makes sure the "... n more items" part is not taken into account. - entriesLength--; - } - const separatorSpace = 2; // Add 1 for the space and 1 for the separator. - const dataLen = new Array(entriesLength); - // Calculate the total length of all output entries and the individual max - // entries length of all output entries. - // IN PROGRESS: Colors are being taken into account. - for (let i = 0; i < entriesLength; i++) { - // Taking colors into account: removing the ANSI color - // codes from the string before measuring its length - const len = colors.stripColor(entries[i]).length; - dataLen[i] = len; - totalLength += len + separatorSpace; - if (maxLength < len) maxLength = len; - } - // Add two to `maxLength` as we add a single whitespace character plus a comma - // in-between two entries. - const actualMax = maxLength + separatorSpace; - // Check if at least three entries fit next to each other and prevent grouping - // of arrays that contains entries of very different length (i.e., if a single - // entry is longer than 1/5 of all other entries combined). Otherwise the - // space in-between small entries would be enormous. - if ( - actualMax * 3 + (level + 1) < LINE_BREAKING_LENGTH && - (totalLength / actualMax > 5 || maxLength <= 6) - ) { - const approxCharHeights = 2.5; - const averageBias = MathSqrt(actualMax - totalLength / entries.length); - const biasedMax = MathMax(actualMax - 3 - averageBias, 1); - // Dynamically check how many columns seem possible. - const columns = MathMin( - // Ideally a square should be drawn. We expect a character to be about 2.5 - // times as high as wide. This is the area formula to calculate a square - // which contains n rectangles of size `actualMax * approxCharHeights`. - // Divide that by `actualMax` to receive the correct number of columns. - // The added bias increases the columns for short entries. - MathRound( - MathSqrt(approxCharHeights * biasedMax * entriesLength) / biasedMax, - ), - // Do not exceed the breakLength. - MathFloor((LINE_BREAKING_LENGTH - (level + 1)) / actualMax), - // Limit the columns to a maximum of fifteen. - 15, - ); - // Return with the original output if no grouping should happen. - if (columns <= 1) { - return entries; - } - const tmp = []; - const maxLineLength = []; - for (let i = 0; i < columns; i++) { - let lineMaxLength = 0; - for (let j = i; j < entries.length; j += columns) { - if (dataLen[j] > lineMaxLength) lineMaxLength = dataLen[j]; - } - lineMaxLength += separatorSpace; - maxLineLength[i] = lineMaxLength; + const approxCharHeights = 2.5; + const averageBias = MathSqrt(actualMax - totalLength / entries.length); + const biasedMax = MathMax(actualMax - 3 - averageBias, 1); + // Dynamically check how many columns seem possible. + const columns = MathMin( + // Ideally a square should be drawn. We expect a character to be about 2.5 + // times as high as wide. This is the area formula to calculate a square + // which contains n rectangles of size `actualMax * approxCharHeights`. + // Divide that by `actualMax` to receive the correct number of columns. + // The added bias increases the columns for short entries. + MathRound( + MathSqrt(approxCharHeights * biasedMax * entriesLength) / biasedMax, + ), + // Do not exceed the breakLength. + MathFloor((LINE_BREAKING_LENGTH - (level + 1)) / actualMax), + // Limit the columns to a maximum of fifteen. + 15, + ); + // Return with the original output if no grouping should happen. + if (columns <= 1) { + return entries; + } + const tmp = []; + const maxLineLength = []; + for (let i = 0; i < columns; i++) { + let lineMaxLength = 0; + for (let j = i; j < entries.length; j += columns) { + if (dataLen[j] > lineMaxLength) lineMaxLength = dataLen[j]; } - let order = "padStart"; - if (value !== undefined) { - for (let i = 0; i < entries.length; i++) { - if ( - typeof value[i] !== "number" && - typeof value[i] !== "bigint" - ) { - order = "padEnd"; - break; - } + lineMaxLength += separatorSpace; + maxLineLength[i] = lineMaxLength; + } + let order = "padStart"; + if (value !== undefined) { + for (let i = 0; i < entries.length; i++) { + if ( + typeof value[i] !== "number" && + typeof value[i] !== "bigint" + ) { + order = "padEnd"; + break; } } - // Each iteration creates a single line of grouped entries. - for (let i = 0; i < entriesLength; i += columns) { - // The last lines may contain less entries than columns. - const max = MathMin(i + columns, entriesLength); - let str = ""; - let j = i; - for (; j < max - 1; j++) { - const lengthOfColorCodes = entries[j].length - dataLen[j]; - const padding = maxLineLength[j - i] + lengthOfColorCodes; - str += `${entries[j]}, `[order](padding, " "); - } - if (order === "padStart") { - const lengthOfColorCodes = entries[j].length - dataLen[j]; - const padding = maxLineLength[j - i] + - lengthOfColorCodes - - separatorSpace; - str += StringPrototypePadStart(entries[j], padding, " "); - } else { - str += entries[j]; - } - ArrayPrototypePush(tmp, str); + } + // Each iteration creates a single line of grouped entries. + for (let i = 0; i < entriesLength; i += columns) { + // The last lines may contain less entries than columns. + const max = MathMin(i + columns, entriesLength); + let str = ""; + let j = i; + for (; j < max - 1; j++) { + const lengthOfColorCodes = entries[j].length - dataLen[j]; + const padding = maxLineLength[j - i] + lengthOfColorCodes; + str += `${entries[j]}, `[order](padding, " "); } - if (iterableLimit < entries.length) { - ArrayPrototypePush(tmp, entries[entriesLength]); + if (order === "padStart") { + const lengthOfColorCodes = entries[j].length - dataLen[j]; + const padding = maxLineLength[j - i] + + lengthOfColorCodes - + separatorSpace; + str += StringPrototypePadStart(entries[j], padding, " "); + } else { + str += entries[j]; } - entries = tmp; + ArrayPrototypePush(tmp, str); + } + if (iterableLimit < entries.length) { + ArrayPrototypePush(tmp, entries[entriesLength]); } - return entries; + entries = tmp; } - - let circular; - function handleCircular(value, cyan) { - let index = 1; - if (circular === undefined) { - circular = new Map(); + return entries; +} + +let circular; +function handleCircular(value, cyan) { + let index = 1; + if (circular === undefined) { + circular = new Map(); + MapPrototypeSet(circular, value, index); + } else { + index = MapPrototypeGet(circular, value); + if (index === undefined) { + index = circular.size + 1; MapPrototypeSet(circular, value, index); - } else { - index = MapPrototypeGet(circular, value); - if (index === undefined) { - index = circular.size + 1; - MapPrototypeSet(circular, value, index); - } } - // Circular string is cyan - return cyan(`[Circular *${index}]`); + } + // Circular string is cyan + return cyan(`[Circular *${index}]`); +} + +function _inspectValue( + value, + inspectOptions, +) { + const proxyDetails = core.getProxyDetails(value); + if (proxyDetails != null && inspectOptions.showProxy) { + return inspectProxy(proxyDetails, inspectOptions); } - function _inspectValue( - value, - inspectOptions, - ) { - const proxyDetails = core.getProxyDetails(value); - if (proxyDetails != null && inspectOptions.showProxy) { - return inspectProxy(proxyDetails, inspectOptions); - } - - const green = maybeColor(colors.green, inspectOptions); - const yellow = maybeColor(colors.yellow, inspectOptions); - const gray = maybeColor(colors.gray, inspectOptions); - const cyan = maybeColor(colors.cyan, inspectOptions); - const bold = maybeColor(colors.bold, inspectOptions); - const red = maybeColor(colors.red, inspectOptions); - - switch (typeof value) { - case "string": - return green(quoteString(value)); - case "number": // Numbers are yellow - // Special handling of -0 - return yellow(ObjectIs(value, -0) ? "-0" : `${value}`); - case "boolean": // booleans are yellow - return yellow(String(value)); - case "undefined": // undefined is gray - return gray(String(value)); - case "symbol": // Symbols are green - return green(maybeQuoteSymbol(value)); - case "bigint": // Bigints are yellow - return yellow(`${value}n`); - case "function": // Function string is cyan - if (ctxHas(value)) { - // Circular string is cyan - return handleCircular(value, cyan); - } + const green = maybeColor(colors.green, inspectOptions); + const yellow = maybeColor(colors.yellow, inspectOptions); + const gray = maybeColor(colors.gray, inspectOptions); + const cyan = maybeColor(colors.cyan, inspectOptions); + const bold = maybeColor(colors.bold, inspectOptions); + const red = maybeColor(colors.red, inspectOptions); + + switch (typeof value) { + case "string": + return green(quoteString(value)); + case "number": // Numbers are yellow + // Special handling of -0 + return yellow(ObjectIs(value, -0) ? "-0" : `${value}`); + case "boolean": // booleans are yellow + return yellow(String(value)); + case "undefined": // undefined is gray + return gray(String(value)); + case "symbol": // Symbols are green + return green(maybeQuoteSymbol(value)); + case "bigint": // Bigints are yellow + return yellow(`${value}n`); + case "function": // Function string is cyan + if (ctxHas(value)) { + // Circular string is cyan + return handleCircular(value, cyan); + } - return inspectFunction(value, inspectOptions); - case "object": // null is bold - if (value === null) { - return bold("null"); - } + return inspectFunction(value, inspectOptions); + case "object": // null is bold + if (value === null) { + return bold("null"); + } - if (ctxHas(value)) { - return handleCircular(value, cyan); - } + if (ctxHas(value)) { + return handleCircular(value, cyan); + } - return inspectObject( - value, - inspectOptions, - proxyDetails, - ); - default: - // Not implemented is red - return red("[Not Implemented]"); - } + return inspectObject( + value, + inspectOptions, + proxyDetails, + ); + default: + // Not implemented is red + return red("[Not Implemented]"); } - - function inspectValue( - value, - inspectOptions, - ) { - ArrayPrototypePush(CTX_STACK, value); - let x; - try { - x = _inspectValue(value, inspectOptions); - } finally { - ArrayPrototypePop(CTX_STACK); - } - return x; - } - - // We can match Node's quoting behavior exactly by swapping the double quote and - // single quote in this array. That would give preference to single quotes. - // However, we prefer double quotes as the default. - const QUOTES = ['"', "'", "`"]; - - /** Surround the string in quotes. - * - * The quote symbol is chosen by taking the first of the `QUOTES` array which - * does not occur in the string. If they all occur, settle with `QUOTES[0]`. - * - * Insert a backslash before any occurrence of the chosen quote symbol and - * before any backslash. - */ - function quoteString(string) { - const quote = - ArrayPrototypeFind(QUOTES, (c) => !StringPrototypeIncludes(string, c)) ?? - QUOTES[0]; - const escapePattern = new RegExp(`(?=[${quote}\\\\])`, "g"); - string = StringPrototypeReplace(string, escapePattern, "\\"); - string = replaceEscapeSequences(string); - return `${quote}${string}${quote}`; - } - - // Replace escape sequences that can modify output. - function replaceEscapeSequences(string) { - const escapeMap = { - "\b": "\\b", - "\f": "\\f", - "\n": "\\n", - "\r": "\\r", - "\t": "\\t", - "\v": "\\v", - }; - - return StringPrototypeReplace( - StringPrototypeReplace( - string, - /([\b\f\n\r\t\v])/g, - (c) => escapeMap[c], - ), - // deno-lint-ignore no-control-regex - /[\x00-\x1f\x7f-\x9f]/g, - (c) => - "\\x" + - StringPrototypePadStart( - NumberPrototypeToString(StringPrototypeCharCodeAt(c, 0), 16), - 2, - "0", - ), - ); +} + +function inspectValue( + value, + inspectOptions, +) { + ArrayPrototypePush(CTX_STACK, value); + let x; + try { + x = _inspectValue(value, inspectOptions); + } finally { + ArrayPrototypePop(CTX_STACK); } + return x; +} + +// We can match Node's quoting behavior exactly by swapping the double quote and +// single quote in this array. That would give preference to single quotes. +// However, we prefer double quotes as the default. +const QUOTES = ['"', "'", "`"]; + +/** Surround the string in quotes. + * + * The quote symbol is chosen by taking the first of the `QUOTES` array which + * does not occur in the string. If they all occur, settle with `QUOTES[0]`. + * + * Insert a backslash before any occurrence of the chosen quote symbol and + * before any backslash. + */ +function quoteString(string) { + const quote = + ArrayPrototypeFind(QUOTES, (c) => !StringPrototypeIncludes(string, c)) ?? + QUOTES[0]; + const escapePattern = new RegExp(`(?=[${quote}\\\\])`, "g"); + string = StringPrototypeReplace(string, escapePattern, "\\"); + string = replaceEscapeSequences(string); + return `${quote}${string}${quote}`; +} + +// Replace escape sequences that can modify output. +function replaceEscapeSequences(string) { + const escapeMap = { + "\b": "\\b", + "\f": "\\f", + "\n": "\\n", + "\r": "\\r", + "\t": "\\t", + "\v": "\\v", + }; - // Surround a string with quotes when it is required (e.g the string not a valid identifier). - function maybeQuoteString(string) { - if (RegExpPrototypeTest(/^[a-zA-Z_][a-zA-Z_0-9]*$/, string)) { - return replaceEscapeSequences(string); - } + return StringPrototypeReplace( + StringPrototypeReplace( + string, + /([\b\f\n\r\t\v])/g, + (c) => escapeMap[c], + ), + // deno-lint-ignore no-control-regex + /[\x00-\x1f\x7f-\x9f]/g, + (c) => + "\\x" + + StringPrototypePadStart( + NumberPrototypeToString(StringPrototypeCharCodeAt(c, 0), 16), + 2, + "0", + ), + ); +} - return quoteString(string); +// Surround a string with quotes when it is required (e.g the string not a valid identifier). +function maybeQuoteString(string) { + if (RegExpPrototypeTest(/^[a-zA-Z_][a-zA-Z_0-9]*$/, string)) { + return replaceEscapeSequences(string); } - // Surround a symbol's description in quotes when it is required (e.g the description has non printable characters). - function maybeQuoteSymbol(symbol) { - if (symbol.description === undefined) { - return SymbolPrototypeToString(symbol); - } + return quoteString(string); +} - if (RegExpPrototypeTest(/^[a-zA-Z_][a-zA-Z_.0-9]*$/, symbol.description)) { - return SymbolPrototypeToString(symbol); - } - - return `Symbol(${quoteString(symbol.description)})`; +// Surround a symbol's description in quotes when it is required (e.g the description has non printable characters). +function maybeQuoteSymbol(symbol) { + if (symbol.description === undefined) { + return SymbolPrototypeToString(symbol); } - const CTX_STACK = []; - function ctxHas(x) { - // Only check parent contexts - return ArrayPrototypeIncludes( - ArrayPrototypeSlice(CTX_STACK, 0, CTX_STACK.length - 1), - x, - ); + if (RegExpPrototypeTest(/^[a-zA-Z_][a-zA-Z_.0-9]*$/, symbol.description)) { + return SymbolPrototypeToString(symbol); } - // Print strings when they are inside of arrays or objects with quotes - function inspectValueWithQuotes( - value, - inspectOptions, - ) { - const abbreviateSize = - typeof inspectOptions.strAbbreviateSize === "undefined" - ? STR_ABBREVIATE_SIZE - : inspectOptions.strAbbreviateSize; - const green = maybeColor(colors.green, inspectOptions); - switch (typeof value) { - case "string": { - const trunc = value.length > abbreviateSize - ? StringPrototypeSlice(value, 0, abbreviateSize) + "..." - : value; - return green(quoteString(trunc)); // Quoted strings are green - } - default: - return inspectValue(value, inspectOptions); - } + return `Symbol(${quoteString(symbol.description)})`; +} + +const CTX_STACK = []; +function ctxHas(x) { + // Only check parent contexts + return ArrayPrototypeIncludes( + ArrayPrototypeSlice(CTX_STACK, 0, CTX_STACK.length - 1), + x, + ); +} + +// Print strings when they are inside of arrays or objects with quotes +function inspectValueWithQuotes( + value, + inspectOptions, +) { + const abbreviateSize = typeof inspectOptions.strAbbreviateSize === "undefined" + ? STR_ABBREVIATE_SIZE + : inspectOptions.strAbbreviateSize; + const green = maybeColor(colors.green, inspectOptions); + switch (typeof value) { + case "string": { + const trunc = value.length > abbreviateSize + ? StringPrototypeSlice(value, 0, abbreviateSize) + "..." + : value; + return green(quoteString(trunc)); // Quoted strings are green + } + default: + return inspectValue(value, inspectOptions); } - - function inspectArray( - value, - inspectOptions, - ) { - const gray = maybeColor(colors.gray, inspectOptions); - let lastValidIndex = 0; - let keys; - const options = { - typeName: "Array", - displayName: "", - delims: ["[", "]"], - entryHandler: (entry, inspectOptions) => { - const { 0: index, 1: val } = entry; - let i = index; - lastValidIndex = index; - if (!ObjectPrototypeHasOwnProperty(value, i)) { - let skipTo; - keys = keys || ObjectKeys(value); - i = value.length; - if (keys.length === 0) { - // fast path, all items are empty - skipTo = i; - } else { - // Not all indexes are empty or there's a non-index property - // Find first non-empty array index - while (keys.length) { - const key = ArrayPrototypeShift(keys); - // check if it's a valid array index - if (key > lastValidIndex && key < 2 ** 32 - 1) { - i = Number(key); - break; - } +} + +function inspectArray( + value, + inspectOptions, +) { + const gray = maybeColor(colors.gray, inspectOptions); + let lastValidIndex = 0; + let keys; + const options = { + typeName: "Array", + displayName: "", + delims: ["[", "]"], + entryHandler: (entry, inspectOptions) => { + const { 0: index, 1: val } = entry; + let i = index; + lastValidIndex = index; + if (!ObjectPrototypeHasOwnProperty(value, i)) { + let skipTo; + keys = keys || ObjectKeys(value); + i = value.length; + if (keys.length === 0) { + // fast path, all items are empty + skipTo = i; + } else { + // Not all indexes are empty or there's a non-index property + // Find first non-empty array index + while (keys.length) { + const key = ArrayPrototypeShift(keys); + // check if it's a valid array index + if (key > lastValidIndex && key < 2 ** 32 - 1) { + i = Number(key); + break; } - - skipTo = i - 1; } - const emptyItems = i - index; - const ending = emptyItems > 1 ? "s" : ""; - return { - entry: gray(`<${emptyItems} empty item${ending}>`), - skipTo, - }; - } else { - return { entry: inspectValueWithQuotes(val, inspectOptions) }; - } - }, - group: inspectOptions.compact, - sort: false, - }; - return inspectIterable(value, options, inspectOptions); - } - - function inspectTypedArray( - typedArrayName, - value, - inspectOptions, - ) { - const valueLength = value.length; - const options = { - typeName: typedArrayName, - displayName: `${typedArrayName}(${valueLength})`, - delims: ["[", "]"], - entryHandler: (entry, inspectOptions) => { - const val = entry[1]; - inspectOptions.indentLevel++; - const inspectedValue = inspectValueWithQuotes(val, inspectOptions); - inspectOptions.indentLevel--; - return inspectedValue; - }, - group: inspectOptions.compact, - sort: false, - }; - return inspectIterable(value, options, inspectOptions); - } - function inspectSet( - value, - inspectOptions, - ) { - const options = { - typeName: "Set", - displayName: "Set", - delims: ["{", "}"], - entryHandler: (entry, inspectOptions) => { - const val = entry[1]; - inspectOptions.indentLevel++; - const inspectedValue = inspectValueWithQuotes(val, inspectOptions); - inspectOptions.indentLevel--; - return inspectedValue; - }, - group: false, - sort: inspectOptions.sorted, - }; - return inspectIterable(value, options, inspectOptions); - } - - function inspectMap( + skipTo = i - 1; + } + const emptyItems = i - index; + const ending = emptyItems > 1 ? "s" : ""; + return { + entry: gray(`<${emptyItems} empty item${ending}>`), + skipTo, + }; + } else { + return { entry: inspectValueWithQuotes(val, inspectOptions) }; + } + }, + group: inspectOptions.compact, + sort: false, + }; + return inspectIterable(value, options, inspectOptions); +} + +function inspectTypedArray( + typedArrayName, + value, + inspectOptions, +) { + const valueLength = value.length; + const options = { + typeName: typedArrayName, + displayName: `${typedArrayName}(${valueLength})`, + delims: ["[", "]"], + entryHandler: (entry, inspectOptions) => { + const val = entry[1]; + inspectOptions.indentLevel++; + const inspectedValue = inspectValueWithQuotes(val, inspectOptions); + inspectOptions.indentLevel--; + return inspectedValue; + }, + group: inspectOptions.compact, + sort: false, + }; + return inspectIterable(value, options, inspectOptions); +} + +function inspectSet( + value, + inspectOptions, +) { + const options = { + typeName: "Set", + displayName: "Set", + delims: ["{", "}"], + entryHandler: (entry, inspectOptions) => { + const val = entry[1]; + inspectOptions.indentLevel++; + const inspectedValue = inspectValueWithQuotes(val, inspectOptions); + inspectOptions.indentLevel--; + return inspectedValue; + }, + group: false, + sort: inspectOptions.sorted, + }; + return inspectIterable(value, options, inspectOptions); +} + +function inspectMap( + value, + inspectOptions, +) { + const options = { + typeName: "Map", + displayName: "Map", + delims: ["{", "}"], + entryHandler: (entry, inspectOptions) => { + const { 0: key, 1: val } = entry; + inspectOptions.indentLevel++; + const inspectedValue = `${ + inspectValueWithQuotes(key, inspectOptions) + } => ${inspectValueWithQuotes(val, inspectOptions)}`; + inspectOptions.indentLevel--; + return inspectedValue; + }, + group: false, + sort: inspectOptions.sorted, + }; + return inspectIterable( value, + options, inspectOptions, - ) { - const options = { - typeName: "Map", - displayName: "Map", - delims: ["{", "}"], - entryHandler: (entry, inspectOptions) => { - const { 0: key, 1: val } = entry; - inspectOptions.indentLevel++; - const inspectedValue = `${ - inspectValueWithQuotes(key, inspectOptions) - } => ${inspectValueWithQuotes(val, inspectOptions)}`; - inspectOptions.indentLevel--; - return inspectedValue; - }, - group: false, - sort: inspectOptions.sorted, - }; - return inspectIterable( - value, - options, - inspectOptions, - ); - } - - function inspectWeakSet(inspectOptions) { - const cyan = maybeColor(colors.cyan, inspectOptions); - return `WeakSet { ${cyan("[items unknown]")} }`; // as seen in Node, with cyan color - } - - function inspectWeakMap(inspectOptions) { - const cyan = maybeColor(colors.cyan, inspectOptions); - return `WeakMap { ${cyan("[items unknown]")} }`; // as seen in Node, with cyan color - } - - function inspectDate(value, inspectOptions) { - // without quotes, ISO format, in magenta like before - const magenta = maybeColor(colors.magenta, inspectOptions); - return magenta( - isInvalidDate(value) ? "Invalid Date" : DatePrototypeToISOString(value), - ); + ); +} + +function inspectWeakSet(inspectOptions) { + const cyan = maybeColor(colors.cyan, inspectOptions); + return `WeakSet { ${cyan("[items unknown]")} }`; // as seen in Node, with cyan color +} + +function inspectWeakMap(inspectOptions) { + const cyan = maybeColor(colors.cyan, inspectOptions); + return `WeakMap { ${cyan("[items unknown]")} }`; // as seen in Node, with cyan color +} + +function inspectDate(value, inspectOptions) { + // without quotes, ISO format, in magenta like before + const magenta = maybeColor(colors.magenta, inspectOptions); + return magenta( + isInvalidDate(value) ? "Invalid Date" : DatePrototypeToISOString(value), + ); +} + +function inspectRegExp(value, inspectOptions) { + const red = maybeColor(colors.red, inspectOptions); + return red(RegExpPrototypeToString(value)); // RegExps are red +} + +function inspectError(value, cyan) { + const causes = [value]; + + let err = value; + while (err.cause) { + if (ArrayPrototypeIncludes(causes, err.cause)) { + ArrayPrototypePush(causes, handleCircular(err.cause, cyan)); + break; + } else { + ArrayPrototypePush(causes, err.cause); + err = err.cause; + } } - function inspectRegExp(value, inspectOptions) { - const red = maybeColor(colors.red, inspectOptions); - return red(RegExpPrototypeToString(value)); // RegExps are red + const refMap = new Map(); + for (let i = 0; i < causes.length; ++i) { + const cause = causes[i]; + if (circular !== undefined) { + const index = MapPrototypeGet(circular, cause); + if (index !== undefined) { + MapPrototypeSet(refMap, cause, cyan(` `)); + } + } } + ArrayPrototypeShift(causes); - function inspectError(value, cyan) { - const causes = [value]; + let finalMessage = MapPrototypeGet(refMap, value) ?? ""; - let err = value; - while (err.cause) { - if (ArrayPrototypeIncludes(causes, err.cause)) { - ArrayPrototypePush(causes, handleCircular(err.cause, cyan)); + if (ObjectPrototypeIsPrototypeOf(AggregateErrorPrototype, value)) { + const stackLines = StringPrototypeSplit(value.stack, "\n"); + while (true) { + const line = ArrayPrototypeShift(stackLines); + if (RegExpPrototypeTest(/\s+at/, line)) { + ArrayPrototypeUnshift(stackLines, line); + break; + } else if (typeof line === "undefined") { break; - } else { - ArrayPrototypePush(causes, err.cause); - err = err.cause; - } - } - - const refMap = new Map(); - for (let i = 0; i < causes.length; ++i) { - const cause = causes[i]; - if (circular !== undefined) { - const index = MapPrototypeGet(circular, cause); - if (index !== undefined) { - MapPrototypeSet(refMap, cause, cyan(` `)); - } } - } - ArrayPrototypeShift(causes); - - let finalMessage = MapPrototypeGet(refMap, value) ?? ""; - if (ObjectPrototypeIsPrototypeOf(AggregateErrorPrototype, value)) { - const stackLines = StringPrototypeSplit(value.stack, "\n"); - while (true) { - const line = ArrayPrototypeShift(stackLines); - if (RegExpPrototypeTest(/\s+at/, line)) { - ArrayPrototypeUnshift(stackLines, line); - break; - } else if (typeof line === "undefined") { - break; - } - - finalMessage += line; - finalMessage += "\n"; - } - const aggregateMessage = ArrayPrototypeJoin( - ArrayPrototypeMap( - value.errors, - (error) => - StringPrototypeReplace( - inspectArgs([error]), - /^(?!\s*$)/gm, - StringPrototypeRepeat(" ", 4), - ), - ), - "\n", - ); - finalMessage += aggregateMessage; + finalMessage += line; finalMessage += "\n"; - finalMessage += ArrayPrototypeJoin(stackLines, "\n"); - } else { - finalMessage += value.stack; } - - finalMessage += ArrayPrototypeJoin( + const aggregateMessage = ArrayPrototypeJoin( ArrayPrototypeMap( - causes, - (cause) => - "\nCaused by " + (MapPrototypeGet(refMap, cause) ?? "") + - (cause?.stack ?? cause), + value.errors, + (error) => + StringPrototypeReplace( + inspectArgs([error]), + /^(?!\s*$)/gm, + StringPrototypeRepeat(" ", 4), + ), ), - "", + "\n", ); - - return finalMessage; + finalMessage += aggregateMessage; + finalMessage += "\n"; + finalMessage += ArrayPrototypeJoin(stackLines, "\n"); + } else { + finalMessage += value.stack; } - function inspectStringObject(value, inspectOptions) { - const cyan = maybeColor(colors.cyan, inspectOptions); - return cyan(`[String: "${StringPrototypeToString(value)}"]`); // wrappers are in cyan + finalMessage += ArrayPrototypeJoin( + ArrayPrototypeMap( + causes, + (cause) => + "\nCaused by " + (MapPrototypeGet(refMap, cause) ?? "") + + (cause?.stack ?? cause), + ), + "", + ); + + return finalMessage; +} + +function inspectStringObject(value, inspectOptions) { + const cyan = maybeColor(colors.cyan, inspectOptions); + return cyan(`[String: "${StringPrototypeToString(value)}"]`); // wrappers are in cyan +} + +function inspectBooleanObject(value, inspectOptions) { + const cyan = maybeColor(colors.cyan, inspectOptions); + return cyan(`[Boolean: ${BooleanPrototypeToString(value)}]`); // wrappers are in cyan +} + +function inspectNumberObject(value, inspectOptions) { + const cyan = maybeColor(colors.cyan, inspectOptions); + // Special handling of -0 + return cyan( + `[Number: ${ + ObjectIs(NumberPrototypeValueOf(value), -0) + ? "-0" + : NumberPrototypeToString(value) + }]`, + ); // wrappers are in cyan +} + +function inspectBigIntObject(value, inspectOptions) { + const cyan = maybeColor(colors.cyan, inspectOptions); + return cyan(`[BigInt: ${BigIntPrototypeToString(value)}n]`); // wrappers are in cyan +} + +function inspectSymbolObject(value, inspectOptions) { + const cyan = maybeColor(colors.cyan, inspectOptions); + return cyan(`[Symbol: ${maybeQuoteSymbol(SymbolPrototypeValueOf(value))}]`); // wrappers are in cyan +} + +const PromiseState = { + Pending: 0, + Fulfilled: 1, + Rejected: 2, +}; + +function inspectPromise( + value, + inspectOptions, +) { + const cyan = maybeColor(colors.cyan, inspectOptions); + const red = maybeColor(colors.red, inspectOptions); + + const { 0: state, 1: result } = core.getPromiseDetails(value); + + if (state === PromiseState.Pending) { + return `Promise { ${cyan("")} }`; } - function inspectBooleanObject(value, inspectOptions) { - const cyan = maybeColor(colors.cyan, inspectOptions); - return cyan(`[Boolean: ${BooleanPrototypeToString(value)}]`); // wrappers are in cyan - } + const prefix = state === PromiseState.Fulfilled + ? "" + : `${red("")} `; - function inspectNumberObject(value, inspectOptions) { - const cyan = maybeColor(colors.cyan, inspectOptions); - // Special handling of -0 - return cyan( - `[Number: ${ - ObjectIs(NumberPrototypeValueOf(value), -0) - ? "-0" - : NumberPrototypeToString(value) - }]`, - ); // wrappers are in cyan - } + inspectOptions.indentLevel++; + const str = `${prefix}${inspectValueWithQuotes(result, inspectOptions)}`; + inspectOptions.indentLevel--; - function inspectBigIntObject(value, inspectOptions) { - const cyan = maybeColor(colors.cyan, inspectOptions); - return cyan(`[BigInt: ${BigIntPrototypeToString(value)}n]`); // wrappers are in cyan + if (str.length + PROMISE_STRING_BASE_LENGTH > LINE_BREAKING_LENGTH) { + return `Promise {\n${ + StringPrototypeRepeat(DEFAULT_INDENT, inspectOptions.indentLevel + 1) + }${str}\n}`; } - function inspectSymbolObject(value, inspectOptions) { - const cyan = maybeColor(colors.cyan, inspectOptions); - return cyan(`[Symbol: ${maybeQuoteSymbol(SymbolPrototypeValueOf(value))}]`); // wrappers are in cyan - } + return `Promise { ${str} }`; +} - const PromiseState = { - Pending: 0, - Fulfilled: 1, - Rejected: 2, - }; +function inspectProxy( + targetAndHandler, + inspectOptions, +) { + return `Proxy ${inspectArray(targetAndHandler, inspectOptions)}`; +} - function inspectPromise( - value, - inspectOptions, - ) { - const cyan = maybeColor(colors.cyan, inspectOptions); - const red = maybeColor(colors.red, inspectOptions); - - const { 0: state, 1: result } = core.getPromiseDetails(value); +function inspectRawObject( + value, + inspectOptions, +) { + const cyan = maybeColor(colors.cyan, inspectOptions); - if (state === PromiseState.Pending) { - return `Promise { ${cyan("")} }`; - } - - const prefix = state === PromiseState.Fulfilled - ? "" - : `${red("")} `; - - inspectOptions.indentLevel++; - const str = `${prefix}${inspectValueWithQuotes(result, inspectOptions)}`; - inspectOptions.indentLevel--; + if (inspectOptions.indentLevel >= inspectOptions.depth) { + return [cyan("[Object]"), ""]; // wrappers are in cyan + } - if (str.length + PROMISE_STRING_BASE_LENGTH > LINE_BREAKING_LENGTH) { - return `Promise {\n${ - StringPrototypeRepeat(DEFAULT_INDENT, inspectOptions.indentLevel + 1) - }${str}\n}`; - } + let baseString; - return `Promise { ${str} }`; + let shouldShowDisplayName = false; + let displayName = value[ + SymbolToStringTag + ]; + if (!displayName) { + displayName = getClassInstanceName(value); } - - function inspectProxy( - targetAndHandler, - inspectOptions, + if ( + displayName && displayName !== "Object" && displayName !== "anonymous" ) { - return `Proxy ${inspectArray(targetAndHandler, inspectOptions)}`; + shouldShowDisplayName = true; } - function inspectRawObject( - value, - inspectOptions, - ) { - const cyan = maybeColor(colors.cyan, inspectOptions); + const entries = []; + const stringKeys = ObjectKeys(value); + const symbolKeys = ObjectGetOwnPropertySymbols(value); + if (inspectOptions.sorted) { + ArrayPrototypeSort(stringKeys); + ArrayPrototypeSort( + symbolKeys, + (s1, s2) => + StringPrototypeLocaleCompare( + s1.description ?? "", + s2.description ?? "", + ), + ); + } - if (inspectOptions.indentLevel >= inspectOptions.depth) { - return [cyan("[Object]"), ""]; // wrappers are in cyan - } + const red = maybeColor(colors.red, inspectOptions); - let baseString; + inspectOptions.indentLevel++; - let shouldShowDisplayName = false; - let displayName = value[ - SymbolToStringTag - ]; - if (!displayName) { - displayName = getClassInstanceName(value); - } - if ( - displayName && displayName !== "Object" && displayName !== "anonymous" - ) { - shouldShowDisplayName = true; - } - - const entries = []; - const stringKeys = ObjectKeys(value); - const symbolKeys = ObjectGetOwnPropertySymbols(value); - if (inspectOptions.sorted) { - ArrayPrototypeSort(stringKeys); - ArrayPrototypeSort( - symbolKeys, - (s1, s2) => - StringPrototypeLocaleCompare( - s1.description ?? "", - s2.description ?? "", - ), + for (let i = 0; i < stringKeys.length; ++i) { + const key = stringKeys[i]; + if (inspectOptions.getters) { + let propertyValue; + let error = null; + try { + propertyValue = value[key]; + } catch (error_) { + error = error_; + } + const inspectedValue = error == null + ? inspectValueWithQuotes(propertyValue, inspectOptions) + : red(`[Thrown ${error.name}: ${error.message}]`); + ArrayPrototypePush( + entries, + `${maybeQuoteString(key)}: ${inspectedValue}`, ); - } - - const red = maybeColor(colors.red, inspectOptions); - - inspectOptions.indentLevel++; - - for (let i = 0; i < stringKeys.length; ++i) { - const key = stringKeys[i]; - if (inspectOptions.getters) { - let propertyValue; - let error = null; - try { - propertyValue = value[key]; - } catch (error_) { - error = error_; - } - const inspectedValue = error == null - ? inspectValueWithQuotes(propertyValue, inspectOptions) - : red(`[Thrown ${error.name}: ${error.message}]`); + } else { + const descriptor = ObjectGetOwnPropertyDescriptor(value, key); + if (descriptor.get !== undefined && descriptor.set !== undefined) { ArrayPrototypePush( entries, - `${maybeQuoteString(key)}: ${inspectedValue}`, + `${maybeQuoteString(key)}: [Getter/Setter]`, ); + } else if (descriptor.get !== undefined) { + ArrayPrototypePush(entries, `${maybeQuoteString(key)}: [Getter]`); } else { - const descriptor = ObjectGetOwnPropertyDescriptor(value, key); - if (descriptor.get !== undefined && descriptor.set !== undefined) { - ArrayPrototypePush( - entries, - `${maybeQuoteString(key)}: [Getter/Setter]`, - ); - } else if (descriptor.get !== undefined) { - ArrayPrototypePush(entries, `${maybeQuoteString(key)}: [Getter]`); - } else { - ArrayPrototypePush( - entries, - `${maybeQuoteString(key)}: ${ - inspectValueWithQuotes(value[key], inspectOptions) - }`, - ); - } + ArrayPrototypePush( + entries, + `${maybeQuoteString(key)}: ${ + inspectValueWithQuotes(value[key], inspectOptions) + }`, + ); } } + } - for (let i = 0; i < symbolKeys.length; ++i) { - const key = symbolKeys[i]; - if ( - !inspectOptions.showHidden && - !propertyIsEnumerable(value, key) - ) { - continue; - } + for (let i = 0; i < symbolKeys.length; ++i) { + const key = symbolKeys[i]; + if ( + !inspectOptions.showHidden && + !propertyIsEnumerable(value, key) + ) { + continue; + } - if (inspectOptions.getters) { - let propertyValue; - let error; - try { - propertyValue = value[key]; - } catch (error_) { - error = error_; - } - const inspectedValue = error == null - ? inspectValueWithQuotes(propertyValue, inspectOptions) - : red(`Thrown ${error.name}: ${error.message}`); + if (inspectOptions.getters) { + let propertyValue; + let error; + try { + propertyValue = value[key]; + } catch (error_) { + error = error_; + } + const inspectedValue = error == null + ? inspectValueWithQuotes(propertyValue, inspectOptions) + : red(`Thrown ${error.name}: ${error.message}`); + ArrayPrototypePush( + entries, + `[${maybeQuoteSymbol(key)}]: ${inspectedValue}`, + ); + } else { + const descriptor = ObjectGetOwnPropertyDescriptor(value, key); + if (descriptor.get !== undefined && descriptor.set !== undefined) { ArrayPrototypePush( entries, - `[${maybeQuoteSymbol(key)}]: ${inspectedValue}`, + `[${maybeQuoteSymbol(key)}]: [Getter/Setter]`, ); + } else if (descriptor.get !== undefined) { + ArrayPrototypePush(entries, `[${maybeQuoteSymbol(key)}]: [Getter]`); } else { - const descriptor = ObjectGetOwnPropertyDescriptor(value, key); - if (descriptor.get !== undefined && descriptor.set !== undefined) { - ArrayPrototypePush( - entries, - `[${maybeQuoteSymbol(key)}]: [Getter/Setter]`, - ); - } else if (descriptor.get !== undefined) { - ArrayPrototypePush(entries, `[${maybeQuoteSymbol(key)}]: [Getter]`); - } else { - ArrayPrototypePush( - entries, - `[${maybeQuoteSymbol(key)}]: ${ - inspectValueWithQuotes(value[key], inspectOptions) - }`, - ); - } + ArrayPrototypePush( + entries, + `[${maybeQuoteSymbol(key)}]: ${ + inspectValueWithQuotes(value[key], inspectOptions) + }`, + ); } } + } - inspectOptions.indentLevel--; + inspectOptions.indentLevel--; - // Making sure color codes are ignored when calculating the total length - const totalLength = entries.length + inspectOptions.indentLevel + - colors.stripColor(ArrayPrototypeJoin(entries, "")).length; + // Making sure color codes are ignored when calculating the total length + const totalLength = entries.length + inspectOptions.indentLevel + + colors.stripColor(ArrayPrototypeJoin(entries, "")).length; - if (entries.length === 0) { - baseString = "{}"; - } else if (totalLength > LINE_BREAKING_LENGTH || !inspectOptions.compact) { - const entryIndent = StringPrototypeRepeat( - DEFAULT_INDENT, - inspectOptions.indentLevel + 1, - ); - const closingIndent = StringPrototypeRepeat( - DEFAULT_INDENT, - inspectOptions.indentLevel, - ); - baseString = `{\n${entryIndent}${ - ArrayPrototypeJoin(entries, `,\n${entryIndent}`) - }${inspectOptions.trailingComma ? "," : ""}\n${closingIndent}}`; - } else { - baseString = `{ ${ArrayPrototypeJoin(entries, ", ")} }`; - } + if (entries.length === 0) { + baseString = "{}"; + } else if (totalLength > LINE_BREAKING_LENGTH || !inspectOptions.compact) { + const entryIndent = StringPrototypeRepeat( + DEFAULT_INDENT, + inspectOptions.indentLevel + 1, + ); + const closingIndent = StringPrototypeRepeat( + DEFAULT_INDENT, + inspectOptions.indentLevel, + ); + baseString = `{\n${entryIndent}${ + ArrayPrototypeJoin(entries, `,\n${entryIndent}`) + }${inspectOptions.trailingComma ? "," : ""}\n${closingIndent}}`; + } else { + baseString = `{ ${ArrayPrototypeJoin(entries, ", ")} }`; + } - if (shouldShowDisplayName) { - baseString = `${displayName} ${baseString}`; - } + if (shouldShowDisplayName) { + baseString = `${displayName} ${baseString}`; + } - let refIndex = ""; - if (circular !== undefined) { - const index = MapPrototypeGet(circular, value); - if (index !== undefined) { - refIndex = cyan(` `); - } + let refIndex = ""; + if (circular !== undefined) { + const index = MapPrototypeGet(circular, value); + if (index !== undefined) { + refIndex = cyan(` `); } - - return [baseString, refIndex]; } - function inspectObject(value, inspectOptions, proxyDetails) { - if ( - ReflectHas(value, customInspect) && - typeof value[customInspect] === "function" - ) { - return String(value[customInspect](inspect, inspectOptions)); - } - // This non-unique symbol is used to support op_crates, ie. - // in extensions/web we don't want to depend on public - // Symbol.for("Deno.customInspect") symbol defined in the public API. - // Internal only, shouldn't be used by users. - const privateCustomInspect = SymbolFor("Deno.privateCustomInspect"); - if ( - ReflectHas(value, privateCustomInspect) && - typeof value[privateCustomInspect] === "function" - ) { - // TODO(nayeemrmn): `inspect` is passed as an argument because custom - // inspect implementations in `extensions` need it, but may not have access - // to the `Deno` namespace in web workers. Remove when the `Deno` - // namespace is always enabled. - return String( - value[privateCustomInspect](inspect, inspectOptions), - ); - } - if (ObjectPrototypeIsPrototypeOf(ErrorPrototype, value)) { - return inspectError(value, maybeColor(colors.cyan, inspectOptions)); - } else if (ArrayIsArray(value)) { - return inspectArray(value, inspectOptions); - } else if (ObjectPrototypeIsPrototypeOf(NumberPrototype, value)) { - return inspectNumberObject(value, inspectOptions); - } else if (ObjectPrototypeIsPrototypeOf(BigIntPrototype, value)) { - return inspectBigIntObject(value, inspectOptions); - } else if (ObjectPrototypeIsPrototypeOf(BooleanPrototype, value)) { - return inspectBooleanObject(value, inspectOptions); - } else if (ObjectPrototypeIsPrototypeOf(StringPrototype, value)) { - return inspectStringObject(value, inspectOptions); - } else if (ObjectPrototypeIsPrototypeOf(SymbolPrototype, value)) { - return inspectSymbolObject(value, inspectOptions); - } else if (ObjectPrototypeIsPrototypeOf(PromisePrototype, value)) { - return inspectPromise(value, inspectOptions); - } else if (ObjectPrototypeIsPrototypeOf(RegExpPrototype, value)) { - return inspectRegExp(value, inspectOptions); - } else if (ObjectPrototypeIsPrototypeOf(DatePrototype, value)) { - return inspectDate( - proxyDetails ? proxyDetails[0] : value, - inspectOptions, - ); - } else if (ObjectPrototypeIsPrototypeOf(SetPrototype, value)) { - return inspectSet( - proxyDetails ? proxyDetails[0] : value, - inspectOptions, - ); - } else if (ObjectPrototypeIsPrototypeOf(MapPrototype, value)) { - return inspectMap( - proxyDetails ? proxyDetails[0] : value, - inspectOptions, - ); - } else if (ObjectPrototypeIsPrototypeOf(WeakSetPrototype, value)) { - return inspectWeakSet(inspectOptions); - } else if (ObjectPrototypeIsPrototypeOf(WeakMapPrototype, value)) { - return inspectWeakMap(inspectOptions); - } else if (isTypedArray(value)) { - return inspectTypedArray( - ObjectGetPrototypeOf(value).constructor.name, - value, - inspectOptions, - ); + return [baseString, refIndex]; +} + +function inspectObject(value, inspectOptions, proxyDetails) { + if ( + ReflectHas(value, customInspect) && + typeof value[customInspect] === "function" + ) { + return String(value[customInspect](inspect, inspectOptions)); + } + // This non-unique symbol is used to support op_crates, ie. + // in extensions/web we don't want to depend on public + // Symbol.for("Deno.customInspect") symbol defined in the public API. + // Internal only, shouldn't be used by users. + const privateCustomInspect = SymbolFor("Deno.privateCustomInspect"); + if ( + ReflectHas(value, privateCustomInspect) && + typeof value[privateCustomInspect] === "function" + ) { + // TODO(nayeemrmn): `inspect` is passed as an argument because custom + // inspect implementations in `extensions` need it, but may not have access + // to the `Deno` namespace in web workers. Remove when the `Deno` + // namespace is always enabled. + return String( + value[privateCustomInspect](inspect, inspectOptions), + ); + } + if (ObjectPrototypeIsPrototypeOf(ErrorPrototype, value)) { + return inspectError(value, maybeColor(colors.cyan, inspectOptions)); + } else if (ArrayIsArray(value)) { + return inspectArray(value, inspectOptions); + } else if (ObjectPrototypeIsPrototypeOf(NumberPrototype, value)) { + return inspectNumberObject(value, inspectOptions); + } else if (ObjectPrototypeIsPrototypeOf(BigIntPrototype, value)) { + return inspectBigIntObject(value, inspectOptions); + } else if (ObjectPrototypeIsPrototypeOf(BooleanPrototype, value)) { + return inspectBooleanObject(value, inspectOptions); + } else if (ObjectPrototypeIsPrototypeOf(StringPrototype, value)) { + return inspectStringObject(value, inspectOptions); + } else if (ObjectPrototypeIsPrototypeOf(SymbolPrototype, value)) { + return inspectSymbolObject(value, inspectOptions); + } else if (ObjectPrototypeIsPrototypeOf(PromisePrototype, value)) { + return inspectPromise(value, inspectOptions); + } else if (ObjectPrototypeIsPrototypeOf(RegExpPrototype, value)) { + return inspectRegExp(value, inspectOptions); + } else if (ObjectPrototypeIsPrototypeOf(DatePrototype, value)) { + return inspectDate( + proxyDetails ? proxyDetails[0] : value, + inspectOptions, + ); + } else if (ObjectPrototypeIsPrototypeOf(SetPrototype, value)) { + return inspectSet( + proxyDetails ? proxyDetails[0] : value, + inspectOptions, + ); + } else if (ObjectPrototypeIsPrototypeOf(MapPrototype, value)) { + return inspectMap( + proxyDetails ? proxyDetails[0] : value, + inspectOptions, + ); + } else if (ObjectPrototypeIsPrototypeOf(WeakSetPrototype, value)) { + return inspectWeakSet(inspectOptions); + } else if (ObjectPrototypeIsPrototypeOf(WeakMapPrototype, value)) { + return inspectWeakMap(inspectOptions); + } else if (isTypedArray(value)) { + return inspectTypedArray( + ObjectGetPrototypeOf(value).constructor.name, + value, + inspectOptions, + ); + } else { + // Otherwise, default object formatting + let { 0: insp, 1: refIndex } = inspectRawObject(value, inspectOptions); + insp = refIndex + insp; + return insp; + } +} + +const colorKeywords = new Map([ + ["black", "#000000"], + ["silver", "#c0c0c0"], + ["gray", "#808080"], + ["white", "#ffffff"], + ["maroon", "#800000"], + ["red", "#ff0000"], + ["purple", "#800080"], + ["fuchsia", "#ff00ff"], + ["green", "#008000"], + ["lime", "#00ff00"], + ["olive", "#808000"], + ["yellow", "#ffff00"], + ["navy", "#000080"], + ["blue", "#0000ff"], + ["teal", "#008080"], + ["aqua", "#00ffff"], + ["orange", "#ffa500"], + ["aliceblue", "#f0f8ff"], + ["antiquewhite", "#faebd7"], + ["aquamarine", "#7fffd4"], + ["azure", "#f0ffff"], + ["beige", "#f5f5dc"], + ["bisque", "#ffe4c4"], + ["blanchedalmond", "#ffebcd"], + ["blueviolet", "#8a2be2"], + ["brown", "#a52a2a"], + ["burlywood", "#deb887"], + ["cadetblue", "#5f9ea0"], + ["chartreuse", "#7fff00"], + ["chocolate", "#d2691e"], + ["coral", "#ff7f50"], + ["cornflowerblue", "#6495ed"], + ["cornsilk", "#fff8dc"], + ["crimson", "#dc143c"], + ["cyan", "#00ffff"], + ["darkblue", "#00008b"], + ["darkcyan", "#008b8b"], + ["darkgoldenrod", "#b8860b"], + ["darkgray", "#a9a9a9"], + ["darkgreen", "#006400"], + ["darkgrey", "#a9a9a9"], + ["darkkhaki", "#bdb76b"], + ["darkmagenta", "#8b008b"], + ["darkolivegreen", "#556b2f"], + ["darkorange", "#ff8c00"], + ["darkorchid", "#9932cc"], + ["darkred", "#8b0000"], + ["darksalmon", "#e9967a"], + ["darkseagreen", "#8fbc8f"], + ["darkslateblue", "#483d8b"], + ["darkslategray", "#2f4f4f"], + ["darkslategrey", "#2f4f4f"], + ["darkturquoise", "#00ced1"], + ["darkviolet", "#9400d3"], + ["deeppink", "#ff1493"], + ["deepskyblue", "#00bfff"], + ["dimgray", "#696969"], + ["dimgrey", "#696969"], + ["dodgerblue", "#1e90ff"], + ["firebrick", "#b22222"], + ["floralwhite", "#fffaf0"], + ["forestgreen", "#228b22"], + ["gainsboro", "#dcdcdc"], + ["ghostwhite", "#f8f8ff"], + ["gold", "#ffd700"], + ["goldenrod", "#daa520"], + ["greenyellow", "#adff2f"], + ["grey", "#808080"], + ["honeydew", "#f0fff0"], + ["hotpink", "#ff69b4"], + ["indianred", "#cd5c5c"], + ["indigo", "#4b0082"], + ["ivory", "#fffff0"], + ["khaki", "#f0e68c"], + ["lavender", "#e6e6fa"], + ["lavenderblush", "#fff0f5"], + ["lawngreen", "#7cfc00"], + ["lemonchiffon", "#fffacd"], + ["lightblue", "#add8e6"], + ["lightcoral", "#f08080"], + ["lightcyan", "#e0ffff"], + ["lightgoldenrodyellow", "#fafad2"], + ["lightgray", "#d3d3d3"], + ["lightgreen", "#90ee90"], + ["lightgrey", "#d3d3d3"], + ["lightpink", "#ffb6c1"], + ["lightsalmon", "#ffa07a"], + ["lightseagreen", "#20b2aa"], + ["lightskyblue", "#87cefa"], + ["lightslategray", "#778899"], + ["lightslategrey", "#778899"], + ["lightsteelblue", "#b0c4de"], + ["lightyellow", "#ffffe0"], + ["limegreen", "#32cd32"], + ["linen", "#faf0e6"], + ["magenta", "#ff00ff"], + ["mediumaquamarine", "#66cdaa"], + ["mediumblue", "#0000cd"], + ["mediumorchid", "#ba55d3"], + ["mediumpurple", "#9370db"], + ["mediumseagreen", "#3cb371"], + ["mediumslateblue", "#7b68ee"], + ["mediumspringgreen", "#00fa9a"], + ["mediumturquoise", "#48d1cc"], + ["mediumvioletred", "#c71585"], + ["midnightblue", "#191970"], + ["mintcream", "#f5fffa"], + ["mistyrose", "#ffe4e1"], + ["moccasin", "#ffe4b5"], + ["navajowhite", "#ffdead"], + ["oldlace", "#fdf5e6"], + ["olivedrab", "#6b8e23"], + ["orangered", "#ff4500"], + ["orchid", "#da70d6"], + ["palegoldenrod", "#eee8aa"], + ["palegreen", "#98fb98"], + ["paleturquoise", "#afeeee"], + ["palevioletred", "#db7093"], + ["papayawhip", "#ffefd5"], + ["peachpuff", "#ffdab9"], + ["peru", "#cd853f"], + ["pink", "#ffc0cb"], + ["plum", "#dda0dd"], + ["powderblue", "#b0e0e6"], + ["rosybrown", "#bc8f8f"], + ["royalblue", "#4169e1"], + ["saddlebrown", "#8b4513"], + ["salmon", "#fa8072"], + ["sandybrown", "#f4a460"], + ["seagreen", "#2e8b57"], + ["seashell", "#fff5ee"], + ["sienna", "#a0522d"], + ["skyblue", "#87ceeb"], + ["slateblue", "#6a5acd"], + ["slategray", "#708090"], + ["slategrey", "#708090"], + ["snow", "#fffafa"], + ["springgreen", "#00ff7f"], + ["steelblue", "#4682b4"], + ["tan", "#d2b48c"], + ["thistle", "#d8bfd8"], + ["tomato", "#ff6347"], + ["turquoise", "#40e0d0"], + ["violet", "#ee82ee"], + ["wheat", "#f5deb3"], + ["whitesmoke", "#f5f5f5"], + ["yellowgreen", "#9acd32"], + ["rebeccapurple", "#663399"], +]); + +function parseCssColor(colorString) { + if (MapPrototypeHas(colorKeywords, colorString)) { + colorString = MapPrototypeGet(colorKeywords, colorString); + } + // deno-fmt-ignore + const hashMatch = StringPrototypeMatch(colorString, /^#([\dA-Fa-f]{2})([\dA-Fa-f]{2})([\dA-Fa-f]{2})([\dA-Fa-f]{2})?$/); + if (hashMatch != null) { + return [ + Number(`0x${hashMatch[1]}`), + Number(`0x${hashMatch[2]}`), + Number(`0x${hashMatch[3]}`), + ]; + } + // deno-fmt-ignore + const smallHashMatch = StringPrototypeMatch(colorString, /^#([\dA-Fa-f])([\dA-Fa-f])([\dA-Fa-f])([\dA-Fa-f])?$/); + if (smallHashMatch != null) { + return [ + Number(`0x${smallHashMatch[1]}0`), + Number(`0x${smallHashMatch[2]}0`), + Number(`0x${smallHashMatch[3]}0`), + ]; + } + // deno-fmt-ignore + const rgbMatch = StringPrototypeMatch(colorString, /^rgba?\(\s*([+\-]?\d*\.?\d+)\s*,\s*([+\-]?\d*\.?\d+)\s*,\s*([+\-]?\d*\.?\d+)\s*(,\s*([+\-]?\d*\.?\d+)\s*)?\)$/); + if (rgbMatch != null) { + return [ + MathRound(MathMax(0, MathMin(255, Number(rgbMatch[1])))), + MathRound(MathMax(0, MathMin(255, Number(rgbMatch[2])))), + MathRound(MathMax(0, MathMin(255, Number(rgbMatch[3])))), + ]; + } + // deno-fmt-ignore + const hslMatch = StringPrototypeMatch(colorString, /^hsla?\(\s*([+\-]?\d*\.?\d+)\s*,\s*([+\-]?\d*\.?\d+)%\s*,\s*([+\-]?\d*\.?\d+)%\s*(,\s*([+\-]?\d*\.?\d+)\s*)?\)$/); + if (hslMatch != null) { + // https://www.rapidtables.com/convert/color/hsl-to-rgb.html + let h = Number(hslMatch[1]) % 360; + if (h < 0) { + h += 360; + } + const s = MathMax(0, MathMin(100, Number(hslMatch[2]))) / 100; + const l = MathMax(0, MathMin(100, Number(hslMatch[3]))) / 100; + const c = (1 - MathAbs(2 * l - 1)) * s; + const x = c * (1 - MathAbs((h / 60) % 2 - 1)); + const m = l - c / 2; + let r_; + let g_; + let b_; + if (h < 60) { + ({ 0: r_, 1: g_, 2: b_ } = [c, x, 0]); + } else if (h < 120) { + ({ 0: r_, 1: g_, 2: b_ } = [x, c, 0]); + } else if (h < 180) { + ({ 0: r_, 1: g_, 2: b_ } = [0, c, x]); + } else if (h < 240) { + ({ 0: r_, 1: g_, 2: b_ } = [0, x, c]); + } else if (h < 300) { + ({ 0: r_, 1: g_, 2: b_ } = [x, 0, c]); } else { - // Otherwise, default object formatting - let { 0: insp, 1: refIndex } = inspectRawObject(value, inspectOptions); - insp = refIndex + insp; - return insp; + ({ 0: r_, 1: g_, 2: b_ } = [c, 0, x]); } + return [ + MathRound((r_ + m) * 255), + MathRound((g_ + m) * 255), + MathRound((b_ + m) * 255), + ]; } - - const colorKeywords = new Map([ - ["black", "#000000"], - ["silver", "#c0c0c0"], - ["gray", "#808080"], - ["white", "#ffffff"], - ["maroon", "#800000"], - ["red", "#ff0000"], - ["purple", "#800080"], - ["fuchsia", "#ff00ff"], - ["green", "#008000"], - ["lime", "#00ff00"], - ["olive", "#808000"], - ["yellow", "#ffff00"], - ["navy", "#000080"], - ["blue", "#0000ff"], - ["teal", "#008080"], - ["aqua", "#00ffff"], - ["orange", "#ffa500"], - ["aliceblue", "#f0f8ff"], - ["antiquewhite", "#faebd7"], - ["aquamarine", "#7fffd4"], - ["azure", "#f0ffff"], - ["beige", "#f5f5dc"], - ["bisque", "#ffe4c4"], - ["blanchedalmond", "#ffebcd"], - ["blueviolet", "#8a2be2"], - ["brown", "#a52a2a"], - ["burlywood", "#deb887"], - ["cadetblue", "#5f9ea0"], - ["chartreuse", "#7fff00"], - ["chocolate", "#d2691e"], - ["coral", "#ff7f50"], - ["cornflowerblue", "#6495ed"], - ["cornsilk", "#fff8dc"], - ["crimson", "#dc143c"], - ["cyan", "#00ffff"], - ["darkblue", "#00008b"], - ["darkcyan", "#008b8b"], - ["darkgoldenrod", "#b8860b"], - ["darkgray", "#a9a9a9"], - ["darkgreen", "#006400"], - ["darkgrey", "#a9a9a9"], - ["darkkhaki", "#bdb76b"], - ["darkmagenta", "#8b008b"], - ["darkolivegreen", "#556b2f"], - ["darkorange", "#ff8c00"], - ["darkorchid", "#9932cc"], - ["darkred", "#8b0000"], - ["darksalmon", "#e9967a"], - ["darkseagreen", "#8fbc8f"], - ["darkslateblue", "#483d8b"], - ["darkslategray", "#2f4f4f"], - ["darkslategrey", "#2f4f4f"], - ["darkturquoise", "#00ced1"], - ["darkviolet", "#9400d3"], - ["deeppink", "#ff1493"], - ["deepskyblue", "#00bfff"], - ["dimgray", "#696969"], - ["dimgrey", "#696969"], - ["dodgerblue", "#1e90ff"], - ["firebrick", "#b22222"], - ["floralwhite", "#fffaf0"], - ["forestgreen", "#228b22"], - ["gainsboro", "#dcdcdc"], - ["ghostwhite", "#f8f8ff"], - ["gold", "#ffd700"], - ["goldenrod", "#daa520"], - ["greenyellow", "#adff2f"], - ["grey", "#808080"], - ["honeydew", "#f0fff0"], - ["hotpink", "#ff69b4"], - ["indianred", "#cd5c5c"], - ["indigo", "#4b0082"], - ["ivory", "#fffff0"], - ["khaki", "#f0e68c"], - ["lavender", "#e6e6fa"], - ["lavenderblush", "#fff0f5"], - ["lawngreen", "#7cfc00"], - ["lemonchiffon", "#fffacd"], - ["lightblue", "#add8e6"], - ["lightcoral", "#f08080"], - ["lightcyan", "#e0ffff"], - ["lightgoldenrodyellow", "#fafad2"], - ["lightgray", "#d3d3d3"], - ["lightgreen", "#90ee90"], - ["lightgrey", "#d3d3d3"], - ["lightpink", "#ffb6c1"], - ["lightsalmon", "#ffa07a"], - ["lightseagreen", "#20b2aa"], - ["lightskyblue", "#87cefa"], - ["lightslategray", "#778899"], - ["lightslategrey", "#778899"], - ["lightsteelblue", "#b0c4de"], - ["lightyellow", "#ffffe0"], - ["limegreen", "#32cd32"], - ["linen", "#faf0e6"], - ["magenta", "#ff00ff"], - ["mediumaquamarine", "#66cdaa"], - ["mediumblue", "#0000cd"], - ["mediumorchid", "#ba55d3"], - ["mediumpurple", "#9370db"], - ["mediumseagreen", "#3cb371"], - ["mediumslateblue", "#7b68ee"], - ["mediumspringgreen", "#00fa9a"], - ["mediumturquoise", "#48d1cc"], - ["mediumvioletred", "#c71585"], - ["midnightblue", "#191970"], - ["mintcream", "#f5fffa"], - ["mistyrose", "#ffe4e1"], - ["moccasin", "#ffe4b5"], - ["navajowhite", "#ffdead"], - ["oldlace", "#fdf5e6"], - ["olivedrab", "#6b8e23"], - ["orangered", "#ff4500"], - ["orchid", "#da70d6"], - ["palegoldenrod", "#eee8aa"], - ["palegreen", "#98fb98"], - ["paleturquoise", "#afeeee"], - ["palevioletred", "#db7093"], - ["papayawhip", "#ffefd5"], - ["peachpuff", "#ffdab9"], - ["peru", "#cd853f"], - ["pink", "#ffc0cb"], - ["plum", "#dda0dd"], - ["powderblue", "#b0e0e6"], - ["rosybrown", "#bc8f8f"], - ["royalblue", "#4169e1"], - ["saddlebrown", "#8b4513"], - ["salmon", "#fa8072"], - ["sandybrown", "#f4a460"], - ["seagreen", "#2e8b57"], - ["seashell", "#fff5ee"], - ["sienna", "#a0522d"], - ["skyblue", "#87ceeb"], - ["slateblue", "#6a5acd"], - ["slategray", "#708090"], - ["slategrey", "#708090"], - ["snow", "#fffafa"], - ["springgreen", "#00ff7f"], - ["steelblue", "#4682b4"], - ["tan", "#d2b48c"], - ["thistle", "#d8bfd8"], - ["tomato", "#ff6347"], - ["turquoise", "#40e0d0"], - ["violet", "#ee82ee"], - ["wheat", "#f5deb3"], - ["whitesmoke", "#f5f5f5"], - ["yellowgreen", "#9acd32"], - ["rebeccapurple", "#663399"], - ]); - - function parseCssColor(colorString) { - if (MapPrototypeHas(colorKeywords, colorString)) { - colorString = MapPrototypeGet(colorKeywords, colorString); - } - // deno-fmt-ignore - const hashMatch = StringPrototypeMatch(colorString, /^#([\dA-Fa-f]{2})([\dA-Fa-f]{2})([\dA-Fa-f]{2})([\dA-Fa-f]{2})?$/); - if (hashMatch != null) { - return [ - Number(`0x${hashMatch[1]}`), - Number(`0x${hashMatch[2]}`), - Number(`0x${hashMatch[3]}`), - ]; - } - // deno-fmt-ignore - const smallHashMatch = StringPrototypeMatch(colorString, /^#([\dA-Fa-f])([\dA-Fa-f])([\dA-Fa-f])([\dA-Fa-f])?$/); - if (smallHashMatch != null) { - return [ - Number(`0x${smallHashMatch[1]}0`), - Number(`0x${smallHashMatch[2]}0`), - Number(`0x${smallHashMatch[3]}0`), - ]; - } - // deno-fmt-ignore - const rgbMatch = StringPrototypeMatch(colorString, /^rgba?\(\s*([+\-]?\d*\.?\d+)\s*,\s*([+\-]?\d*\.?\d+)\s*,\s*([+\-]?\d*\.?\d+)\s*(,\s*([+\-]?\d*\.?\d+)\s*)?\)$/); - if (rgbMatch != null) { - return [ - MathRound(MathMax(0, MathMin(255, Number(rgbMatch[1])))), - MathRound(MathMax(0, MathMin(255, Number(rgbMatch[2])))), - MathRound(MathMax(0, MathMin(255, Number(rgbMatch[3])))), - ]; - } - // deno-fmt-ignore - const hslMatch = StringPrototypeMatch(colorString, /^hsla?\(\s*([+\-]?\d*\.?\d+)\s*,\s*([+\-]?\d*\.?\d+)%\s*,\s*([+\-]?\d*\.?\d+)%\s*(,\s*([+\-]?\d*\.?\d+)\s*)?\)$/); - if (hslMatch != null) { - // https://www.rapidtables.com/convert/color/hsl-to-rgb.html - let h = Number(hslMatch[1]) % 360; - if (h < 0) { - h += 360; - } - const s = MathMax(0, MathMin(100, Number(hslMatch[2]))) / 100; - const l = MathMax(0, MathMin(100, Number(hslMatch[3]))) / 100; - const c = (1 - MathAbs(2 * l - 1)) * s; - const x = c * (1 - MathAbs((h / 60) % 2 - 1)); - const m = l - c / 2; - let r_; - let g_; - let b_; - if (h < 60) { - ({ 0: r_, 1: g_, 2: b_ } = [c, x, 0]); - } else if (h < 120) { - ({ 0: r_, 1: g_, 2: b_ } = [x, c, 0]); - } else if (h < 180) { - ({ 0: r_, 1: g_, 2: b_ } = [0, c, x]); - } else if (h < 240) { - ({ 0: r_, 1: g_, 2: b_ } = [0, x, c]); - } else if (h < 300) { - ({ 0: r_, 1: g_, 2: b_ } = [x, 0, c]); - } else { - ({ 0: r_, 1: g_, 2: b_ } = [c, 0, x]); + return null; +} + +function getDefaultCss() { + return { + backgroundColor: null, + color: null, + fontWeight: null, + fontStyle: null, + textDecorationColor: null, + textDecorationLine: [], + }; +} + +function parseCss(cssString) { + const css = getDefaultCss(); + + const rawEntries = []; + let inValue = false; + let currentKey = null; + let parenthesesDepth = 0; + let currentPart = ""; + for (let i = 0; i < cssString.length; i++) { + const c = cssString[i]; + if (c == "(") { + parenthesesDepth++; + } else if (parenthesesDepth > 0) { + if (c == ")") { + parenthesesDepth--; } - return [ - MathRound((r_ + m) * 255), - MathRound((g_ + m) * 255), - MathRound((b_ + m) * 255), - ]; - } - return null; - } - - function getDefaultCss() { - return { - backgroundColor: null, - color: null, - fontWeight: null, - fontStyle: null, - textDecorationColor: null, - textDecorationLine: [], - }; - } - - function parseCss(cssString) { - const css = getDefaultCss(); - - const rawEntries = []; - let inValue = false; - let currentKey = null; - let parenthesesDepth = 0; - let currentPart = ""; - for (let i = 0; i < cssString.length; i++) { - const c = cssString[i]; - if (c == "(") { - parenthesesDepth++; - } else if (parenthesesDepth > 0) { - if (c == ")") { - parenthesesDepth--; - } - } else if (inValue) { - if (c == ";") { - const value = StringPrototypeTrim(currentPart); - if (value != "") { - ArrayPrototypePush(rawEntries, [currentKey, value]); - } - currentKey = null; - currentPart = ""; - inValue = false; - continue; + } else if (inValue) { + if (c == ";") { + const value = StringPrototypeTrim(currentPart); + if (value != "") { + ArrayPrototypePush(rawEntries, [currentKey, value]); } - } else if (c == ":") { - currentKey = StringPrototypeTrim(currentPart); + currentKey = null; currentPart = ""; - inValue = true; + inValue = false; continue; } - currentPart += c; - } - if (inValue && parenthesesDepth == 0) { - const value = StringPrototypeTrim(currentPart); - if (value != "") { - ArrayPrototypePush(rawEntries, [currentKey, value]); - } - currentKey = null; + } else if (c == ":") { + currentKey = StringPrototypeTrim(currentPart); currentPart = ""; + inValue = true; + continue; + } + currentPart += c; + } + if (inValue && parenthesesDepth == 0) { + const value = StringPrototypeTrim(currentPart); + if (value != "") { + ArrayPrototypePush(rawEntries, [currentKey, value]); } + currentKey = null; + currentPart = ""; + } - for (let i = 0; i < rawEntries.length; ++i) { - const { 0: key, 1: value } = rawEntries[i]; - if (key == "background-color") { - if (value != null) { - css.backgroundColor = value; - } - } else if (key == "color") { - if (value != null) { - css.color = value; - } - } else if (key == "font-weight") { - if (value == "bold") { - css.fontWeight = value; - } - } else if (key == "font-style") { + for (let i = 0; i < rawEntries.length; ++i) { + const { 0: key, 1: value } = rawEntries[i]; + if (key == "background-color") { + if (value != null) { + css.backgroundColor = value; + } + } else if (key == "color") { + if (value != null) { + css.color = value; + } + } else if (key == "font-weight") { + if (value == "bold") { + css.fontWeight = value; + } + } else if (key == "font-style") { + if ( + ArrayPrototypeIncludes(["italic", "oblique", "oblique 14deg"], value) + ) { + css.fontStyle = "italic"; + } + } else if (key == "text-decoration-line") { + css.textDecorationLine = []; + const lineTypes = StringPrototypeSplit(value, /\s+/g); + for (let i = 0; i < lineTypes.length; ++i) { + const lineType = lineTypes[i]; if ( - ArrayPrototypeIncludes(["italic", "oblique", "oblique 14deg"], value) + ArrayPrototypeIncludes( + ["line-through", "overline", "underline"], + lineType, + ) ) { - css.fontStyle = "italic"; - } - } else if (key == "text-decoration-line") { - css.textDecorationLine = []; - const lineTypes = StringPrototypeSplit(value, /\s+/g); - for (let i = 0; i < lineTypes.length; ++i) { - const lineType = lineTypes[i]; - if ( - ArrayPrototypeIncludes( - ["line-through", "overline", "underline"], - lineType, - ) - ) { - ArrayPrototypePush(css.textDecorationLine, lineType); - } + ArrayPrototypePush(css.textDecorationLine, lineType); } - } else if (key == "text-decoration-color") { - const color = parseCssColor(value); - if (color != null) { - css.textDecorationColor = color; - } - } else if (key == "text-decoration") { - css.textDecorationColor = null; - css.textDecorationLine = []; - const args = StringPrototypeSplit(value, /\s+/g); - for (let i = 0; i < args.length; ++i) { - const arg = args[i]; - const maybeColor = parseCssColor(arg); - if (maybeColor != null) { - css.textDecorationColor = maybeColor; - } else if ( - ArrayPrototypeIncludes( - ["line-through", "overline", "underline"], - arg, - ) - ) { - ArrayPrototypePush(css.textDecorationLine, arg); - } + } + } else if (key == "text-decoration-color") { + const color = parseCssColor(value); + if (color != null) { + css.textDecorationColor = color; + } + } else if (key == "text-decoration") { + css.textDecorationColor = null; + css.textDecorationLine = []; + const args = StringPrototypeSplit(value, /\s+/g); + for (let i = 0; i < args.length; ++i) { + const arg = args[i]; + const maybeColor = parseCssColor(arg); + if (maybeColor != null) { + css.textDecorationColor = maybeColor; + } else if ( + ArrayPrototypeIncludes( + ["line-through", "overline", "underline"], + arg, + ) + ) { + ArrayPrototypePush(css.textDecorationLine, arg); } } } + } - return css; - } - - function colorEquals(color1, color2) { - return color1?.[0] == color2?.[0] && color1?.[1] == color2?.[1] && - color1?.[2] == color2?.[2]; - } - - function cssToAnsi(css, prevCss = null) { - prevCss = prevCss ?? getDefaultCss(); - let ansi = ""; - if (!colorEquals(css.backgroundColor, prevCss.backgroundColor)) { - if (css.backgroundColor == null) { - ansi += "\x1b[49m"; - } else if (css.backgroundColor == "black") { - ansi += `\x1b[40m`; - } else if (css.backgroundColor == "red") { - ansi += `\x1b[41m`; - } else if (css.backgroundColor == "green") { - ansi += `\x1b[42m`; - } else if (css.backgroundColor == "yellow") { - ansi += `\x1b[43m`; - } else if (css.backgroundColor == "blue") { - ansi += `\x1b[44m`; - } else if (css.backgroundColor == "magenta") { - ansi += `\x1b[45m`; - } else if (css.backgroundColor == "cyan") { - ansi += `\x1b[46m`; - } else if (css.backgroundColor == "white") { - ansi += `\x1b[47m`; + return css; +} + +function colorEquals(color1, color2) { + return color1?.[0] == color2?.[0] && color1?.[1] == color2?.[1] && + color1?.[2] == color2?.[2]; +} + +function cssToAnsi(css, prevCss = null) { + prevCss = prevCss ?? getDefaultCss(); + let ansi = ""; + if (!colorEquals(css.backgroundColor, prevCss.backgroundColor)) { + if (css.backgroundColor == null) { + ansi += "\x1b[49m"; + } else if (css.backgroundColor == "black") { + ansi += `\x1b[40m`; + } else if (css.backgroundColor == "red") { + ansi += `\x1b[41m`; + } else if (css.backgroundColor == "green") { + ansi += `\x1b[42m`; + } else if (css.backgroundColor == "yellow") { + ansi += `\x1b[43m`; + } else if (css.backgroundColor == "blue") { + ansi += `\x1b[44m`; + } else if (css.backgroundColor == "magenta") { + ansi += `\x1b[45m`; + } else if (css.backgroundColor == "cyan") { + ansi += `\x1b[46m`; + } else if (css.backgroundColor == "white") { + ansi += `\x1b[47m`; + } else { + if (ArrayIsArray(css.backgroundColor)) { + const { 0: r, 1: g, 2: b } = css.backgroundColor; + ansi += `\x1b[48;2;${r};${g};${b}m`; } else { - if (ArrayIsArray(css.backgroundColor)) { - const { 0: r, 1: g, 2: b } = css.backgroundColor; + const parsed = parseCssColor(css.backgroundColor); + if (parsed !== null) { + const { 0: r, 1: g, 2: b } = parsed; ansi += `\x1b[48;2;${r};${g};${b}m`; } else { - const parsed = parseCssColor(css.backgroundColor); - if (parsed !== null) { - const { 0: r, 1: g, 2: b } = parsed; - ansi += `\x1b[48;2;${r};${g};${b}m`; - } else { - ansi += "\x1b[49m"; - } + ansi += "\x1b[49m"; } } } - if (!colorEquals(css.color, prevCss.color)) { - if (css.color == null) { - ansi += "\x1b[39m"; - } else if (css.color == "black") { - ansi += `\x1b[30m`; - } else if (css.color == "red") { - ansi += `\x1b[31m`; - } else if (css.color == "green") { - ansi += `\x1b[32m`; - } else if (css.color == "yellow") { - ansi += `\x1b[33m`; - } else if (css.color == "blue") { - ansi += `\x1b[34m`; - } else if (css.color == "magenta") { - ansi += `\x1b[35m`; - } else if (css.color == "cyan") { - ansi += `\x1b[36m`; - } else if (css.color == "white") { - ansi += `\x1b[37m`; + } + if (!colorEquals(css.color, prevCss.color)) { + if (css.color == null) { + ansi += "\x1b[39m"; + } else if (css.color == "black") { + ansi += `\x1b[30m`; + } else if (css.color == "red") { + ansi += `\x1b[31m`; + } else if (css.color == "green") { + ansi += `\x1b[32m`; + } else if (css.color == "yellow") { + ansi += `\x1b[33m`; + } else if (css.color == "blue") { + ansi += `\x1b[34m`; + } else if (css.color == "magenta") { + ansi += `\x1b[35m`; + } else if (css.color == "cyan") { + ansi += `\x1b[36m`; + } else if (css.color == "white") { + ansi += `\x1b[37m`; + } else { + if (ArrayIsArray(css.color)) { + const { 0: r, 1: g, 2: b } = css.color; + ansi += `\x1b[38;2;${r};${g};${b}m`; } else { - if (ArrayIsArray(css.color)) { - const { 0: r, 1: g, 2: b } = css.color; + const parsed = parseCssColor(css.color); + if (parsed !== null) { + const { 0: r, 1: g, 2: b } = parsed; ansi += `\x1b[38;2;${r};${g};${b}m`; } else { - const parsed = parseCssColor(css.color); - if (parsed !== null) { - const { 0: r, 1: g, 2: b } = parsed; - ansi += `\x1b[38;2;${r};${g};${b}m`; - } else { - ansi += "\x1b[39m"; - } + ansi += "\x1b[39m"; } } } - if (css.fontWeight != prevCss.fontWeight) { - if (css.fontWeight == "bold") { - ansi += `\x1b[1m`; - } else { - ansi += "\x1b[22m"; - } + } + if (css.fontWeight != prevCss.fontWeight) { + if (css.fontWeight == "bold") { + ansi += `\x1b[1m`; + } else { + ansi += "\x1b[22m"; } - if (css.fontStyle != prevCss.fontStyle) { - if (css.fontStyle == "italic") { - ansi += `\x1b[3m`; - } else { - ansi += "\x1b[23m"; - } + } + if (css.fontStyle != prevCss.fontStyle) { + if (css.fontStyle == "italic") { + ansi += `\x1b[3m`; + } else { + ansi += "\x1b[23m"; } - if (!colorEquals(css.textDecorationColor, prevCss.textDecorationColor)) { - if (css.textDecorationColor != null) { - const { 0: r, 1: g, 2: b } = css.textDecorationColor; - ansi += `\x1b[58;2;${r};${g};${b}m`; - } else { - ansi += "\x1b[59m"; - } + } + if (!colorEquals(css.textDecorationColor, prevCss.textDecorationColor)) { + if (css.textDecorationColor != null) { + const { 0: r, 1: g, 2: b } = css.textDecorationColor; + ansi += `\x1b[58;2;${r};${g};${b}m`; + } else { + ansi += "\x1b[59m"; } - if ( - ArrayPrototypeIncludes(css.textDecorationLine, "line-through") != - ArrayPrototypeIncludes(prevCss.textDecorationLine, "line-through") - ) { - if (ArrayPrototypeIncludes(css.textDecorationLine, "line-through")) { - ansi += "\x1b[9m"; - } else { - ansi += "\x1b[29m"; - } + } + if ( + ArrayPrototypeIncludes(css.textDecorationLine, "line-through") != + ArrayPrototypeIncludes(prevCss.textDecorationLine, "line-through") + ) { + if (ArrayPrototypeIncludes(css.textDecorationLine, "line-through")) { + ansi += "\x1b[9m"; + } else { + ansi += "\x1b[29m"; } - if ( - ArrayPrototypeIncludes(css.textDecorationLine, "overline") != - ArrayPrototypeIncludes(prevCss.textDecorationLine, "overline") - ) { - if (ArrayPrototypeIncludes(css.textDecorationLine, "overline")) { - ansi += "\x1b[53m"; - } else { - ansi += "\x1b[55m"; - } + } + if ( + ArrayPrototypeIncludes(css.textDecorationLine, "overline") != + ArrayPrototypeIncludes(prevCss.textDecorationLine, "overline") + ) { + if (ArrayPrototypeIncludes(css.textDecorationLine, "overline")) { + ansi += "\x1b[53m"; + } else { + ansi += "\x1b[55m"; } - if ( - ArrayPrototypeIncludes(css.textDecorationLine, "underline") != - ArrayPrototypeIncludes(prevCss.textDecorationLine, "underline") - ) { - if (ArrayPrototypeIncludes(css.textDecorationLine, "underline")) { - ansi += "\x1b[4m"; - } else { - ansi += "\x1b[24m"; - } + } + if ( + ArrayPrototypeIncludes(css.textDecorationLine, "underline") != + ArrayPrototypeIncludes(prevCss.textDecorationLine, "underline") + ) { + if (ArrayPrototypeIncludes(css.textDecorationLine, "underline")) { + ansi += "\x1b[4m"; + } else { + ansi += "\x1b[24m"; } - return ansi; - } - - function inspectArgs(args, inspectOptions = {}) { - circular = undefined; - - const noColor = colors.getNoColor(); - const rInspectOptions = { ...DEFAULT_INSPECT_OPTIONS, ...inspectOptions }; - const first = args[0]; - let a = 0; - let string = ""; - - if (typeof first == "string" && args.length > 1) { - a++; - // Index of the first not-yet-appended character. Use this so we only - // have to append to `string` when a substitution occurs / at the end. - let appendedChars = 0; - let usedStyle = false; - let prevCss = null; - for (let i = 0; i < first.length - 1; i++) { - if (first[i] == "%") { - const char = first[++i]; - if (a < args.length) { - let formattedArg = null; - if (char == "s") { - // Format as a string. - formattedArg = String(args[a++]); - } else if (ArrayPrototypeIncludes(["d", "i"], char)) { - // Format as an integer. - const value = args[a++]; - if (typeof value == "bigint") { - formattedArg = `${value}n`; - } else if (typeof value == "number") { - formattedArg = `${NumberParseInt(String(value))}`; - } else { - formattedArg = "NaN"; - } - } else if (char == "f") { - // Format as a floating point value. - const value = args[a++]; - if (typeof value == "number") { - formattedArg = `${value}`; - } else { - formattedArg = "NaN"; - } - } else if (ArrayPrototypeIncludes(["O", "o"], char)) { - // Format as an object. - formattedArg = inspectValue(args[a++], rInspectOptions); - } else if (char == "c") { - const value = args[a++]; - if (!noColor) { - const css = parseCss(value); - formattedArg = cssToAnsi(css, prevCss); - if (formattedArg != "") { - usedStyle = true; - prevCss = css; - } - } else { - formattedArg = ""; - } + } + return ansi; +} + +function inspectArgs(args, inspectOptions = {}) { + circular = undefined; + + const noColor = colors.getNoColor(); + const rInspectOptions = { ...DEFAULT_INSPECT_OPTIONS, ...inspectOptions }; + const first = args[0]; + let a = 0; + let string = ""; + + if (typeof first == "string" && args.length > 1) { + a++; + // Index of the first not-yet-appended character. Use this so we only + // have to append to `string` when a substitution occurs / at the end. + let appendedChars = 0; + let usedStyle = false; + let prevCss = null; + for (let i = 0; i < first.length - 1; i++) { + if (first[i] == "%") { + const char = first[++i]; + if (a < args.length) { + let formattedArg = null; + if (char == "s") { + // Format as a string. + formattedArg = String(args[a++]); + } else if (ArrayPrototypeIncludes(["d", "i"], char)) { + // Format as an integer. + const value = args[a++]; + if (typeof value == "bigint") { + formattedArg = `${value}n`; + } else if (typeof value == "number") { + formattedArg = `${NumberParseInt(String(value))}`; + } else { + formattedArg = "NaN"; } - - if (formattedArg != null) { - string += StringPrototypeSlice(first, appendedChars, i - 1) + - formattedArg; - appendedChars = i + 1; + } else if (char == "f") { + // Format as a floating point value. + const value = args[a++]; + if (typeof value == "number") { + formattedArg = `${value}`; + } else { + formattedArg = "NaN"; + } + } else if (ArrayPrototypeIncludes(["O", "o"], char)) { + // Format as an object. + formattedArg = inspectValue(args[a++], rInspectOptions); + } else if (char == "c") { + const value = args[a++]; + if (!noColor) { + const css = parseCss(value); + formattedArg = cssToAnsi(css, prevCss); + if (formattedArg != "") { + usedStyle = true; + prevCss = css; + } + } else { + formattedArg = ""; } } - if (char == "%") { - string += StringPrototypeSlice(first, appendedChars, i - 1) + "%"; + + if (formattedArg != null) { + string += StringPrototypeSlice(first, appendedChars, i - 1) + + formattedArg; appendedChars = i + 1; } } - } - string += StringPrototypeSlice(first, appendedChars); - if (usedStyle) { - string += "\x1b[0m"; + if (char == "%") { + string += StringPrototypeSlice(first, appendedChars, i - 1) + "%"; + appendedChars = i + 1; + } } } - - for (; a < args.length; a++) { - if (a > 0) { - string += " "; - } - if (typeof args[a] == "string") { - string += args[a]; - } else { - // Use default maximum depth for null or undefined arguments. - string += inspectValue(args[a], rInspectOptions); - } + string += StringPrototypeSlice(first, appendedChars); + if (usedStyle) { + string += "\x1b[0m"; } + } - if (rInspectOptions.indentLevel > 0) { - const groupIndent = StringPrototypeRepeat( - DEFAULT_INDENT, - rInspectOptions.indentLevel, - ); - string = groupIndent + - StringPrototypeReplaceAll(string, "\n", `\n${groupIndent}`); + for (; a < args.length; a++) { + if (a > 0) { + string += " "; } + if (typeof args[a] == "string") { + string += args[a]; + } else { + // Use default maximum depth for null or undefined arguments. + string += inspectValue(args[a], rInspectOptions); + } + } - return string; + if (rInspectOptions.indentLevel > 0) { + const groupIndent = StringPrototypeRepeat( + DEFAULT_INDENT, + rInspectOptions.indentLevel, + ); + string = groupIndent + + StringPrototypeReplaceAll(string, "\n", `\n${groupIndent}`); } - const countMap = new Map(); - const timerMap = new Map(); - const isConsoleInstance = Symbol("isConsoleInstance"); + return string; +} - function getConsoleInspectOptions() { - return { - ...DEFAULT_INSPECT_OPTIONS, - colors: !colors.getNoColor(), - }; - } +const countMap = new Map(); +const timerMap = new Map(); +const isConsoleInstance = Symbol("isConsoleInstance"); - class Console { - #printFunc = null; - [isConsoleInstance] = false; - - constructor(printFunc) { - this.#printFunc = printFunc; - this.indentLevel = 0; - this[isConsoleInstance] = true; - - // ref https://console.spec.whatwg.org/#console-namespace - // For historical web-compatibility reasons, the namespace object for - // console must have as its [[Prototype]] an empty object, created as if - // by ObjectCreate(%ObjectPrototype%), instead of %ObjectPrototype%. - const console = ObjectCreate({}, { - [SymbolToStringTag]: { - enumerable: false, - writable: false, - configurable: true, - value: "console", - }, - }); - ObjectAssign(console, this); - return console; - } +function getConsoleInspectOptions() { + return { + ...DEFAULT_INSPECT_OPTIONS, + colors: !colors.getNoColor(), + }; +} + +class Console { + #printFunc = null; + [isConsoleInstance] = false; + + constructor(printFunc) { + this.#printFunc = printFunc; + this.indentLevel = 0; + this[isConsoleInstance] = true; + + // ref https://console.spec.whatwg.org/#console-namespace + // For historical web-compatibility reasons, the namespace object for + // console must have as its [[Prototype]] an empty object, created as if + // by ObjectCreate(%ObjectPrototype%), instead of %ObjectPrototype%. + const console = ObjectCreate({}, { + [SymbolToStringTag]: { + enumerable: false, + writable: false, + configurable: true, + value: "console", + }, + }); + ObjectAssign(console, this); + return console; + } - log = (...args) => { - this.#printFunc( - inspectArgs(args, { - ...getConsoleInspectOptions(), - indentLevel: this.indentLevel, - }) + "\n", - 1, - ); - }; + log = (...args) => { + this.#printFunc( + inspectArgs(args, { + ...getConsoleInspectOptions(), + indentLevel: this.indentLevel, + }) + "\n", + 1, + ); + }; - debug = (...args) => { - this.#printFunc( - inspectArgs(args, { - ...getConsoleInspectOptions(), - indentLevel: this.indentLevel, - }) + "\n", - 0, - ); - }; + debug = (...args) => { + this.#printFunc( + inspectArgs(args, { + ...getConsoleInspectOptions(), + indentLevel: this.indentLevel, + }) + "\n", + 0, + ); + }; - info = (...args) => { - this.#printFunc( - inspectArgs(args, { - ...getConsoleInspectOptions(), - indentLevel: this.indentLevel, - }) + "\n", - 1, - ); - }; + info = (...args) => { + this.#printFunc( + inspectArgs(args, { + ...getConsoleInspectOptions(), + indentLevel: this.indentLevel, + }) + "\n", + 1, + ); + }; - dir = (obj = undefined, options = {}) => { - this.#printFunc( - inspectArgs([obj], { ...getConsoleInspectOptions(), ...options }) + - "\n", - 1, - ); - }; + dir = (obj = undefined, options = {}) => { + this.#printFunc( + inspectArgs([obj], { ...getConsoleInspectOptions(), ...options }) + + "\n", + 1, + ); + }; - dirxml = this.dir; + dirxml = this.dir; - warn = (...args) => { - this.#printFunc( - inspectArgs(args, { - ...getConsoleInspectOptions(), - indentLevel: this.indentLevel, - }) + "\n", - 2, - ); - }; + warn = (...args) => { + this.#printFunc( + inspectArgs(args, { + ...getConsoleInspectOptions(), + indentLevel: this.indentLevel, + }) + "\n", + 2, + ); + }; - error = (...args) => { - this.#printFunc( - inspectArgs(args, { - ...getConsoleInspectOptions(), - indentLevel: this.indentLevel, - }) + "\n", - 3, - ); - }; + error = (...args) => { + this.#printFunc( + inspectArgs(args, { + ...getConsoleInspectOptions(), + indentLevel: this.indentLevel, + }) + "\n", + 3, + ); + }; - assert = (condition = false, ...args) => { - if (condition) { - return; - } + assert = (condition = false, ...args) => { + if (condition) { + return; + } - if (args.length === 0) { - this.error("Assertion failed"); - return; - } + if (args.length === 0) { + this.error("Assertion failed"); + return; + } - const [first, ...rest] = new SafeArrayIterator(args); + const [first, ...rest] = new SafeArrayIterator(args); - if (typeof first === "string") { - this.error( - `Assertion failed: ${first}`, - ...new SafeArrayIterator(rest), - ); - return; - } + if (typeof first === "string") { + this.error( + `Assertion failed: ${first}`, + ...new SafeArrayIterator(rest), + ); + return; + } - this.error(`Assertion failed:`, ...new SafeArrayIterator(args)); - }; + this.error(`Assertion failed:`, ...new SafeArrayIterator(args)); + }; - count = (label = "default") => { - label = String(label); + count = (label = "default") => { + label = String(label); - if (MapPrototypeHas(countMap, label)) { - const current = MapPrototypeGet(countMap, label) || 0; - MapPrototypeSet(countMap, label, current + 1); - } else { - MapPrototypeSet(countMap, label, 1); - } + if (MapPrototypeHas(countMap, label)) { + const current = MapPrototypeGet(countMap, label) || 0; + MapPrototypeSet(countMap, label, current + 1); + } else { + MapPrototypeSet(countMap, label, 1); + } - this.info(`${label}: ${MapPrototypeGet(countMap, label)}`); - }; + this.info(`${label}: ${MapPrototypeGet(countMap, label)}`); + }; - countReset = (label = "default") => { - label = String(label); + countReset = (label = "default") => { + label = String(label); - if (MapPrototypeHas(countMap, label)) { - MapPrototypeSet(countMap, label, 0); - } else { - this.warn(`Count for '${label}' does not exist`); - } - }; + if (MapPrototypeHas(countMap, label)) { + MapPrototypeSet(countMap, label, 0); + } else { + this.warn(`Count for '${label}' does not exist`); + } + }; - table = (data = undefined, properties = undefined) => { - if (properties !== undefined && !ArrayIsArray(properties)) { - throw new Error( - "The 'properties' argument must be of type Array. " + - "Received type string", - ); - } + table = (data = undefined, properties = undefined) => { + if (properties !== undefined && !ArrayIsArray(properties)) { + throw new Error( + "The 'properties' argument must be of type Array. " + + "Received type string", + ); + } - if (data === null || typeof data !== "object") { - return this.log(data); - } + if (data === null || typeof data !== "object") { + return this.log(data); + } - const stringifyValue = (value) => - inspectValueWithQuotes(value, { - ...DEFAULT_INSPECT_OPTIONS, - depth: 1, - }); - const toTable = (header, body) => this.log(cliTable(header, body)); - - let resultData; - const isSet = ObjectPrototypeIsPrototypeOf(SetPrototype, data); - const isMap = ObjectPrototypeIsPrototypeOf(MapPrototype, data); - const valuesKey = "Values"; - const indexKey = isSet || isMap ? "(iter idx)" : "(idx)"; - - if (isSet) { - resultData = [...new SafeSet(data)]; - } else if (isMap) { - let idx = 0; - resultData = {}; - - MapPrototypeForEach(data, (v, k) => { - resultData[idx] = { Key: k, Values: v }; - idx++; - }); - } else { - resultData = data; - } + const stringifyValue = (value) => + inspectValueWithQuotes(value, { + ...DEFAULT_INSPECT_OPTIONS, + depth: 1, + }); + const toTable = (header, body) => this.log(cliTable(header, body)); + + let resultData; + const isSet = ObjectPrototypeIsPrototypeOf(SetPrototype, data); + const isMap = ObjectPrototypeIsPrototypeOf(MapPrototype, data); + const valuesKey = "Values"; + const indexKey = isSet || isMap ? "(iter idx)" : "(idx)"; + + if (isSet) { + resultData = [...new SafeSet(data)]; + } else if (isMap) { + let idx = 0; + resultData = {}; + + MapPrototypeForEach(data, (v, k) => { + resultData[idx] = { Key: k, Values: v }; + idx++; + }); + } else { + resultData = data; + } - const keys = ObjectKeys(resultData); - const numRows = keys.length; + const keys = ObjectKeys(resultData); + const numRows = keys.length; - const objectValues = properties - ? ObjectFromEntries( - ArrayPrototypeMap( - properties, - (name) => [name, ArrayPrototypeFill(new Array(numRows), "")], - ), - ) - : {}; - const indexKeys = []; - const values = []; - - let hasPrimitives = false; - keys.forEach((k, idx) => { - const value = resultData[k]; - const primitive = value === null || - (typeof value !== "function" && typeof value !== "object"); - if (properties === undefined && primitive) { - hasPrimitives = true; - ArrayPrototypePush(values, stringifyValue(value)); - } else { - const valueObj = value || {}; - const keys = properties || ObjectKeys(valueObj); - for (let i = 0; i < keys.length; ++i) { - const k = keys[i]; - if (!primitive && ReflectHas(valueObj, k)) { - if (!(ReflectHas(objectValues, k))) { - objectValues[k] = ArrayPrototypeFill(new Array(numRows), ""); - } - objectValues[k][idx] = stringifyValue(valueObj[k]); + const objectValues = properties + ? ObjectFromEntries( + ArrayPrototypeMap( + properties, + (name) => [name, ArrayPrototypeFill(new Array(numRows), "")], + ), + ) + : {}; + const indexKeys = []; + const values = []; + + let hasPrimitives = false; + keys.forEach((k, idx) => { + const value = resultData[k]; + const primitive = value === null || + (typeof value !== "function" && typeof value !== "object"); + if (properties === undefined && primitive) { + hasPrimitives = true; + ArrayPrototypePush(values, stringifyValue(value)); + } else { + const valueObj = value || {}; + const keys = properties || ObjectKeys(valueObj); + for (let i = 0; i < keys.length; ++i) { + const k = keys[i]; + if (!primitive && ReflectHas(valueObj, k)) { + if (!(ReflectHas(objectValues, k))) { + objectValues[k] = ArrayPrototypeFill(new Array(numRows), ""); } + objectValues[k][idx] = stringifyValue(valueObj[k]); } - ArrayPrototypePush(values, ""); } - - ArrayPrototypePush(indexKeys, k); - }); - - const headerKeys = ObjectKeys(objectValues); - const bodyValues = ObjectValues(objectValues); - const headerProps = properties || - [ - ...new SafeArrayIterator(headerKeys), - !isMap && hasPrimitives && valuesKey, - ]; - const header = ArrayPrototypeFilter([ - indexKey, - ...new SafeArrayIterator(headerProps), - ], Boolean); - const body = [indexKeys, ...new SafeArrayIterator(bodyValues), values]; - - toTable(header, body); - }; - - time = (label = "default") => { - label = String(label); - - if (MapPrototypeHas(timerMap, label)) { - this.warn(`Timer '${label}' already exists`); - return; + ArrayPrototypePush(values, ""); } - MapPrototypeSet(timerMap, label, DateNow()); - }; - - timeLog = (label = "default", ...args) => { - label = String(label); + ArrayPrototypePush(indexKeys, k); + }); - if (!MapPrototypeHas(timerMap, label)) { - this.warn(`Timer '${label}' does not exists`); - return; - } + const headerKeys = ObjectKeys(objectValues); + const bodyValues = ObjectValues(objectValues); + const headerProps = properties || + [ + ...new SafeArrayIterator(headerKeys), + !isMap && hasPrimitives && valuesKey, + ]; + const header = ArrayPrototypeFilter([ + indexKey, + ...new SafeArrayIterator(headerProps), + ], Boolean); + const body = [indexKeys, ...new SafeArrayIterator(bodyValues), values]; - const startTime = MapPrototypeGet(timerMap, label); - const duration = DateNow() - startTime; + toTable(header, body); + }; - this.info(`${label}: ${duration}ms`, ...new SafeArrayIterator(args)); - }; + time = (label = "default") => { + label = String(label); - timeEnd = (label = "default") => { - label = String(label); + if (MapPrototypeHas(timerMap, label)) { + this.warn(`Timer '${label}' already exists`); + return; + } - if (!MapPrototypeHas(timerMap, label)) { - this.warn(`Timer '${label}' does not exist`); - return; - } + MapPrototypeSet(timerMap, label, DateNow()); + }; - const startTime = MapPrototypeGet(timerMap, label); - MapPrototypeDelete(timerMap, label); - const duration = DateNow() - startTime; + timeLog = (label = "default", ...args) => { + label = String(label); - this.info(`${label}: ${duration}ms`); - }; + if (!MapPrototypeHas(timerMap, label)) { + this.warn(`Timer '${label}' does not exists`); + return; + } - group = (...label) => { - if (label.length > 0) { - this.log(...new SafeArrayIterator(label)); - } - this.indentLevel += 2; - }; + const startTime = MapPrototypeGet(timerMap, label); + const duration = DateNow() - startTime; - groupCollapsed = this.group; + this.info(`${label}: ${duration}ms`, ...new SafeArrayIterator(args)); + }; - groupEnd = () => { - if (this.indentLevel > 0) { - this.indentLevel -= 2; - } - }; + timeEnd = (label = "default") => { + label = String(label); - clear = () => { - this.indentLevel = 0; - this.#printFunc(CSI.kClear, 1); - this.#printFunc(CSI.kClearScreenDown, 1); - }; + if (!MapPrototypeHas(timerMap, label)) { + this.warn(`Timer '${label}' does not exist`); + return; + } - trace = (...args) => { - const message = inspectArgs( - args, - { ...getConsoleInspectOptions(), indentLevel: 0 }, - ); - const err = { - name: "Trace", - message, - }; - ErrorCaptureStackTrace(err, this.trace); - this.error(err.stack); - }; + const startTime = MapPrototypeGet(timerMap, label); + MapPrototypeDelete(timerMap, label); + const duration = DateNow() - startTime; - // These methods are noops, but when the inspector is connected, they - // call into V8. - profile = (_label) => {}; - profileEnd = (_label) => {}; - timeStamp = (_label) => {}; + this.info(`${label}: ${duration}ms`); + }; - static [SymbolHasInstance](instance) { - return instance[isConsoleInstance]; + group = (...label) => { + if (label.length > 0) { + this.log(...new SafeArrayIterator(label)); } - } + this.indentLevel += 2; + }; - const customInspect = SymbolFor("Deno.customInspect"); + groupCollapsed = this.group; - function inspect( - value, - inspectOptions = {}, - ) { - circular = undefined; - return inspectValue(value, { - ...DEFAULT_INSPECT_OPTIONS, - ...inspectOptions, - }); - } + groupEnd = () => { + if (this.indentLevel > 0) { + this.indentLevel -= 2; + } + }; - /** Creates a proxy that represents a subset of the properties - * of the original object optionally without evaluating the properties - * in order to get the values. */ - function createFilteredInspectProxy({ object, keys, evaluate }) { - return new Proxy({}, { - get(_target, key) { - if (key === SymbolToStringTag) { - return object.constructor?.name; - } else if (ArrayPrototypeIncludes(keys, key)) { - return ReflectGet(object, key); - } else { - return undefined; - } - }, - getOwnPropertyDescriptor(_target, key) { - if (!ArrayPrototypeIncludes(keys, key)) { - return undefined; - } else if (evaluate) { - return getEvaluatedDescriptor(object, key); - } else { - return getDescendantPropertyDescriptor(object, key) ?? - getEvaluatedDescriptor(object, key); - } - }, - has(_target, key) { - return ArrayPrototypeIncludes(keys, key); - }, - ownKeys() { - return keys; - }, - }); + clear = () => { + this.indentLevel = 0; + this.#printFunc(CSI.kClear, 1); + this.#printFunc(CSI.kClearScreenDown, 1); + }; - function getDescendantPropertyDescriptor(object, key) { - let propertyDescriptor = ReflectGetOwnPropertyDescriptor(object, key); - if (!propertyDescriptor) { - const prototype = ReflectGetPrototypeOf(object); - if (prototype) { - propertyDescriptor = getDescendantPropertyDescriptor(prototype, key); - } - } - return propertyDescriptor; - } + trace = (...args) => { + const message = inspectArgs( + args, + { ...getConsoleInspectOptions(), indentLevel: 0 }, + ); + const err = { + name: "Trace", + message, + }; + ErrorCaptureStackTrace(err, this.trace); + this.error(err.stack); + }; - function getEvaluatedDescriptor(object, key) { - return { - configurable: true, - enumerable: true, - value: object[key], - }; - } - } + // These methods are noops, but when the inspector is connected, they + // call into V8. + profile = (_label) => {}; + profileEnd = (_label) => {}; + timeStamp = (_label) => {}; - // A helper function that will bind our own console implementation - // with default implementation of Console from V8. This will cause - // console messages to be piped to inspector console. - // - // We are using `Deno.core.callConsole` binding to preserve proper stack - // frames in inspector console. This has to be done because V8 considers - // the last JS stack frame as gospel for the inspector. In our case we - // specifically want the latest user stack frame to be the one that matters - // though. - // - // Inspired by: - // https://github.com/nodejs/node/blob/1317252dfe8824fd9cfee125d2aaa94004db2f3b/lib/internal/util/inspector.js#L39-L61 - function wrapConsole(consoleFromDeno, consoleFromV8) { - const callConsole = core.callConsole; - - const keys = ObjectKeys(consoleFromV8); - for (let i = 0; i < keys.length; ++i) { - const key = keys[i]; - if (ObjectPrototypeHasOwnProperty(consoleFromDeno, key)) { - consoleFromDeno[key] = FunctionPrototypeBind( - callConsole, - consoleFromDeno, - consoleFromV8[key], - consoleFromDeno[key], - ); + static [SymbolHasInstance](instance) { + return instance[isConsoleInstance]; + } +} + +const customInspect = SymbolFor("Deno.customInspect"); + +function inspect( + value, + inspectOptions = {}, +) { + circular = undefined; + return inspectValue(value, { + ...DEFAULT_INSPECT_OPTIONS, + ...inspectOptions, + }); +} + +/** Creates a proxy that represents a subset of the properties + * of the original object optionally without evaluating the properties + * in order to get the values. */ +function createFilteredInspectProxy({ object, keys, evaluate }) { + return new Proxy({}, { + get(_target, key) { + if (key === SymbolToStringTag) { + return object.constructor?.name; + } else if (ArrayPrototypeIncludes(keys, key)) { + return ReflectGet(object, key); + } else { + return undefined; + } + }, + getOwnPropertyDescriptor(_target, key) { + if (!ArrayPrototypeIncludes(keys, key)) { + return undefined; + } else if (evaluate) { + return getEvaluatedDescriptor(object, key); } else { - // Add additional console APIs from the inspector - consoleFromDeno[key] = consoleFromV8[key]; + return getDescendantPropertyDescriptor(object, key) ?? + getEvaluatedDescriptor(object, key); + } + }, + has(_target, key) { + return ArrayPrototypeIncludes(keys, key); + }, + ownKeys() { + return keys; + }, + }); + + function getDescendantPropertyDescriptor(object, key) { + let propertyDescriptor = ReflectGetOwnPropertyDescriptor(object, key); + if (!propertyDescriptor) { + const prototype = ReflectGetPrototypeOf(object); + if (prototype) { + propertyDescriptor = getDescendantPropertyDescriptor(prototype, key); } } + return propertyDescriptor; } - // Expose these fields to internalObject for tests. - window.__bootstrap.internals = { - ...window.__bootstrap.internals ?? {}, - Console, - cssToAnsi, - inspectArgs, - parseCss, - parseCssColor, - }; - - window.__bootstrap.console = { - CSI, - inspectArgs, - Console, - customInspect, - inspect, - wrapConsole, - createFilteredInspectProxy, - quoteString, - }; -})(this); + function getEvaluatedDescriptor(object, key) { + return { + configurable: true, + enumerable: true, + value: object[key], + }; + } +} + +// A helper function that will bind our own console implementation +// with default implementation of Console from V8. This will cause +// console messages to be piped to inspector console. +// +// We are using `Deno.core.callConsole` binding to preserve proper stack +// frames in inspector console. This has to be done because V8 considers +// the last JS stack frame as gospel for the inspector. In our case we +// specifically want the latest user stack frame to be the one that matters +// though. +// +// Inspired by: +// https://github.com/nodejs/node/blob/1317252dfe8824fd9cfee125d2aaa94004db2f3b/lib/internal/util/inspector.js#L39-L61 +function wrapConsole(consoleFromDeno, consoleFromV8) { + const callConsole = core.callConsole; + + const keys = ObjectKeys(consoleFromV8); + for (let i = 0; i < keys.length; ++i) { + const key = keys[i]; + if (ObjectPrototypeHasOwnProperty(consoleFromDeno, key)) { + consoleFromDeno[key] = FunctionPrototypeBind( + callConsole, + consoleFromDeno, + consoleFromV8[key], + consoleFromDeno[key], + ); + } else { + // Add additional console APIs from the inspector + consoleFromDeno[key] = consoleFromV8[key]; + } + } +} + +// Expose these fields to internalObject for tests. +internals.Console = Console; +internals.cssToAnsi = cssToAnsi; +internals.inspectArgs = inspectArgs; +internals.parseCss = parseCss; +internals.parseCssColor = parseCssColor; + +export { + Console, + createFilteredInspectProxy, + CSI, + customInspect, + inspect, + inspectArgs, + quoteString, + wrapConsole, +}; diff --git a/ext/console/Cargo.toml b/ext/console/Cargo.toml index 1e3019702ef440..229b7494da8aa7 100644 --- a/ext/console/Cargo.toml +++ b/ext/console/Cargo.toml @@ -2,7 +2,7 @@ [package] name = "deno_console" -version = "0.89.0" +version = "0.91.0" authors.workspace = true edition.workspace = true license.workspace = true diff --git a/ext/console/internal.d.ts b/ext/console/internal.d.ts index 57c5d120ba2b24..fba711d5a8b6e4 100644 --- a/ext/console/internal.d.ts +++ b/ext/console/internal.d.ts @@ -3,14 +3,10 @@ /// /// -declare namespace globalThis { - declare namespace __bootstrap { - declare namespace console { - declare function createFilteredInspectProxy(params: { - object: TObject; - keys: (keyof TObject)[]; - evaluate: boolean; - }): Record; - } - } +declare module "internal:deno_console/02_console.js" { + function createFilteredInspectProxy(params: { + object: TObject; + keys: (keyof TObject)[]; + evaluate: boolean; + }): Record; } diff --git a/ext/console/lib.rs b/ext/console/lib.rs index 4b3b450299a6db..158a1a05eecd86 100644 --- a/ext/console/lib.rs +++ b/ext/console/lib.rs @@ -6,11 +6,7 @@ use std::path::PathBuf; pub fn init() -> Extension { Extension::builder(env!("CARGO_PKG_NAME")) - .js(include_js_files!( - prefix "internal:ext/console", - "01_colors.js", - "02_console.js", - )) + .esm(include_js_files!("01_colors.js", "02_console.js",)) .build() } diff --git a/ext/crypto/00_crypto.js b/ext/crypto/00_crypto.js index 7d30dcccf641f5..7bc62714fe0ac8 100644 --- a/ext/crypto/00_crypto.js +++ b/ext/crypto/00_crypto.js @@ -6,2207 +6,2311 @@ /// /// -"use strict"; - -((window) => { - const core = window.Deno.core; - const ops = core.ops; - const webidl = window.__bootstrap.webidl; - const { DOMException } = window.__bootstrap.domException; - - const { - ArrayBufferPrototype, - ArrayBufferIsView, - ArrayPrototypeEvery, - ArrayPrototypeFind, - ArrayPrototypeIncludes, - BigInt64ArrayPrototype, - BigUint64ArrayPrototype, - Int16ArrayPrototype, - Int32ArrayPrototype, - Int8ArrayPrototype, - JSONParse, - JSONStringify, - MathCeil, - ObjectAssign, - ObjectPrototypeHasOwnProperty, - ObjectPrototypeIsPrototypeOf, - StringPrototypeToLowerCase, - StringPrototypeToUpperCase, - StringPrototypeCharCodeAt, - StringFromCharCode, - SafeArrayIterator, - Symbol, - SymbolFor, - SyntaxError, - TypedArrayPrototypeSlice, - TypeError, - Uint16ArrayPrototype, - Uint32ArrayPrototype, - Uint8Array, - Uint8ArrayPrototype, - Uint8ClampedArrayPrototype, - WeakMap, - WeakMapPrototypeGet, - WeakMapPrototypeSet, - } = window.__bootstrap.primordials; - - // P-521 is not yet supported. - const supportedNamedCurves = ["P-256", "P-384"]; - const recognisedUsages = [ - "encrypt", - "decrypt", - "sign", - "verify", - "deriveKey", - "deriveBits", - "wrapKey", - "unwrapKey", - ]; - - const simpleAlgorithmDictionaries = { - AesGcmParams: { iv: "BufferSource", additionalData: "BufferSource" }, - RsaHashedKeyGenParams: { hash: "HashAlgorithmIdentifier" }, - EcKeyGenParams: {}, - HmacKeyGenParams: { hash: "HashAlgorithmIdentifier" }, - RsaPssParams: {}, - EcdsaParams: { hash: "HashAlgorithmIdentifier" }, - HmacImportParams: { hash: "HashAlgorithmIdentifier" }, - HkdfParams: { - hash: "HashAlgorithmIdentifier", - salt: "BufferSource", - info: "BufferSource", - }, - Pbkdf2Params: { hash: "HashAlgorithmIdentifier", salt: "BufferSource" }, - RsaOaepParams: { label: "BufferSource" }, - RsaHashedImportParams: { hash: "HashAlgorithmIdentifier" }, - EcKeyImportParams: {}, - }; - - const supportedAlgorithms = { - "digest": { - "SHA-1": null, - "SHA-256": null, - "SHA-384": null, - "SHA-512": null, - }, - "generateKey": { - "RSASSA-PKCS1-v1_5": "RsaHashedKeyGenParams", - "RSA-PSS": "RsaHashedKeyGenParams", - "RSA-OAEP": "RsaHashedKeyGenParams", - "ECDSA": "EcKeyGenParams", - "ECDH": "EcKeyGenParams", - "AES-CTR": "AesKeyGenParams", - "AES-CBC": "AesKeyGenParams", - "AES-GCM": "AesKeyGenParams", - "AES-KW": "AesKeyGenParams", - "HMAC": "HmacKeyGenParams", - "X25519": null, - "Ed25519": null, - }, - "sign": { - "RSASSA-PKCS1-v1_5": null, - "RSA-PSS": "RsaPssParams", - "ECDSA": "EcdsaParams", - "HMAC": null, - "Ed25519": null, - }, - "verify": { - "RSASSA-PKCS1-v1_5": null, - "RSA-PSS": "RsaPssParams", - "ECDSA": "EcdsaParams", - "HMAC": null, - "Ed25519": null, - }, - "importKey": { - "RSASSA-PKCS1-v1_5": "RsaHashedImportParams", - "RSA-PSS": "RsaHashedImportParams", - "RSA-OAEP": "RsaHashedImportParams", - "ECDSA": "EcKeyImportParams", - "ECDH": "EcKeyImportParams", - "HMAC": "HmacImportParams", - "HKDF": null, - "PBKDF2": null, - "AES-CTR": null, - "AES-CBC": null, - "AES-GCM": null, - "AES-KW": null, - "Ed25519": null, - "X25519": null, - }, - "deriveBits": { - "HKDF": "HkdfParams", - "PBKDF2": "Pbkdf2Params", - "ECDH": "EcdhKeyDeriveParams", - "X25519": "EcdhKeyDeriveParams", - }, - "encrypt": { - "RSA-OAEP": "RsaOaepParams", - "AES-CBC": "AesCbcParams", - "AES-GCM": "AesGcmParams", - "AES-CTR": "AesCtrParams", - }, - "decrypt": { - "RSA-OAEP": "RsaOaepParams", - "AES-CBC": "AesCbcParams", - "AES-GCM": "AesGcmParams", - "AES-CTR": "AesCtrParams", - }, - "get key length": { - "AES-CBC": "AesDerivedKeyParams", - "AES-CTR": "AesDerivedKeyParams", - "AES-GCM": "AesDerivedKeyParams", - "AES-KW": "AesDerivedKeyParams", - "HMAC": "HmacImportParams", - "HKDF": null, - "PBKDF2": null, - }, - "wrapKey": { - "AES-KW": null, - }, - "unwrapKey": { - "AES-KW": null, - }, - }; - - const aesJwkAlg = { - "AES-CTR": { - 128: "A128CTR", - 192: "A192CTR", - 256: "A256CTR", - }, - "AES-CBC": { - 128: "A128CBC", - 192: "A192CBC", - 256: "A256CBC", - }, - "AES-GCM": { - 128: "A128GCM", - 192: "A192GCM", - 256: "A256GCM", - }, - "AES-KW": { - 128: "A128KW", - 192: "A192KW", - 256: "A256KW", - }, - }; +const core = globalThis.Deno.core; +const ops = core.ops; +const primordials = globalThis.__bootstrap.primordials; +import * as webidl from "internal:deno_webidl/00_webidl.js"; +import DOMException from "internal:deno_web/01_dom_exception.js"; +const { + ArrayBufferPrototype, + ArrayBufferIsView, + ArrayPrototypeEvery, + ArrayPrototypeFind, + ArrayPrototypeIncludes, + BigInt64ArrayPrototype, + BigUint64ArrayPrototype, + Int16ArrayPrototype, + Int32ArrayPrototype, + Int8ArrayPrototype, + JSONParse, + JSONStringify, + MathCeil, + ObjectAssign, + ObjectPrototypeHasOwnProperty, + ObjectPrototypeIsPrototypeOf, + StringPrototypeToLowerCase, + StringPrototypeToUpperCase, + StringPrototypeCharCodeAt, + StringFromCharCode, + SafeArrayIterator, + Symbol, + SymbolFor, + SyntaxError, + TypedArrayPrototypeSlice, + TypeError, + Uint16ArrayPrototype, + Uint32ArrayPrototype, + Uint8Array, + Uint8ArrayPrototype, + Uint8ClampedArrayPrototype, + WeakMap, + WeakMapPrototypeGet, + WeakMapPrototypeSet, +} = primordials; + +// P-521 is not yet supported. +const supportedNamedCurves = ["P-256", "P-384"]; +const recognisedUsages = [ + "encrypt", + "decrypt", + "sign", + "verify", + "deriveKey", + "deriveBits", + "wrapKey", + "unwrapKey", +]; + +const simpleAlgorithmDictionaries = { + AesGcmParams: { iv: "BufferSource", additionalData: "BufferSource" }, + RsaHashedKeyGenParams: { hash: "HashAlgorithmIdentifier" }, + EcKeyGenParams: {}, + HmacKeyGenParams: { hash: "HashAlgorithmIdentifier" }, + RsaPssParams: {}, + EcdsaParams: { hash: "HashAlgorithmIdentifier" }, + HmacImportParams: { hash: "HashAlgorithmIdentifier" }, + HkdfParams: { + hash: "HashAlgorithmIdentifier", + salt: "BufferSource", + info: "BufferSource", + }, + Pbkdf2Params: { hash: "HashAlgorithmIdentifier", salt: "BufferSource" }, + RsaOaepParams: { label: "BufferSource" }, + RsaHashedImportParams: { hash: "HashAlgorithmIdentifier" }, + EcKeyImportParams: {}, +}; + +const supportedAlgorithms = { + "digest": { + "SHA-1": null, + "SHA-256": null, + "SHA-384": null, + "SHA-512": null, + }, + "generateKey": { + "RSASSA-PKCS1-v1_5": "RsaHashedKeyGenParams", + "RSA-PSS": "RsaHashedKeyGenParams", + "RSA-OAEP": "RsaHashedKeyGenParams", + "ECDSA": "EcKeyGenParams", + "ECDH": "EcKeyGenParams", + "AES-CTR": "AesKeyGenParams", + "AES-CBC": "AesKeyGenParams", + "AES-GCM": "AesKeyGenParams", + "AES-KW": "AesKeyGenParams", + "HMAC": "HmacKeyGenParams", + "X25519": null, + "Ed25519": null, + }, + "sign": { + "RSASSA-PKCS1-v1_5": null, + "RSA-PSS": "RsaPssParams", + "ECDSA": "EcdsaParams", + "HMAC": null, + "Ed25519": null, + }, + "verify": { + "RSASSA-PKCS1-v1_5": null, + "RSA-PSS": "RsaPssParams", + "ECDSA": "EcdsaParams", + "HMAC": null, + "Ed25519": null, + }, + "importKey": { + "RSASSA-PKCS1-v1_5": "RsaHashedImportParams", + "RSA-PSS": "RsaHashedImportParams", + "RSA-OAEP": "RsaHashedImportParams", + "ECDSA": "EcKeyImportParams", + "ECDH": "EcKeyImportParams", + "HMAC": "HmacImportParams", + "HKDF": null, + "PBKDF2": null, + "AES-CTR": null, + "AES-CBC": null, + "AES-GCM": null, + "AES-KW": null, + "Ed25519": null, + "X25519": null, + }, + "deriveBits": { + "HKDF": "HkdfParams", + "PBKDF2": "Pbkdf2Params", + "ECDH": "EcdhKeyDeriveParams", + "X25519": "EcdhKeyDeriveParams", + }, + "encrypt": { + "RSA-OAEP": "RsaOaepParams", + "AES-CBC": "AesCbcParams", + "AES-GCM": "AesGcmParams", + "AES-CTR": "AesCtrParams", + }, + "decrypt": { + "RSA-OAEP": "RsaOaepParams", + "AES-CBC": "AesCbcParams", + "AES-GCM": "AesGcmParams", + "AES-CTR": "AesCtrParams", + }, + "get key length": { + "AES-CBC": "AesDerivedKeyParams", + "AES-CTR": "AesDerivedKeyParams", + "AES-GCM": "AesDerivedKeyParams", + "AES-KW": "AesDerivedKeyParams", + "HMAC": "HmacImportParams", + "HKDF": null, + "PBKDF2": null, + }, + "wrapKey": { + "AES-KW": null, + }, + "unwrapKey": { + "AES-KW": null, + }, +}; + +const aesJwkAlg = { + "AES-CTR": { + 128: "A128CTR", + 192: "A192CTR", + 256: "A256CTR", + }, + "AES-CBC": { + 128: "A128CBC", + 192: "A192CBC", + 256: "A256CBC", + }, + "AES-GCM": { + 128: "A128GCM", + 192: "A192GCM", + 256: "A256GCM", + }, + "AES-KW": { + 128: "A128KW", + 192: "A192KW", + 256: "A256KW", + }, +}; + +// See https://www.w3.org/TR/WebCryptoAPI/#dfn-normalize-an-algorithm +// 18.4.4 +function normalizeAlgorithm(algorithm, op) { + if (typeof algorithm == "string") { + return normalizeAlgorithm({ name: algorithm }, op); + } - // See https://www.w3.org/TR/WebCryptoAPI/#dfn-normalize-an-algorithm - // 18.4.4 - function normalizeAlgorithm(algorithm, op) { - if (typeof algorithm == "string") { - return normalizeAlgorithm({ name: algorithm }, op); + // 1. + const registeredAlgorithms = supportedAlgorithms[op]; + // 2. 3. + const initialAlg = webidl.converters.Algorithm(algorithm, { + prefix: "Failed to normalize algorithm", + context: "passed algorithm", + }); + // 4. + let algName = initialAlg.name; + + // 5. + let desiredType = undefined; + for (const key in registeredAlgorithms) { + if (!ObjectPrototypeHasOwnProperty(registeredAlgorithms, key)) { + continue; + } + if ( + StringPrototypeToUpperCase(key) === StringPrototypeToUpperCase(algName) + ) { + algName = key; + desiredType = registeredAlgorithms[key]; } + } + if (desiredType === undefined) { + throw new DOMException( + "Unrecognized algorithm name", + "NotSupportedError", + ); + } - // 1. - const registeredAlgorithms = supportedAlgorithms[op]; - // 2. 3. - const initialAlg = webidl.converters.Algorithm(algorithm, { - prefix: "Failed to normalize algorithm", - context: "passed algorithm", - }); - // 4. - let algName = initialAlg.name; + // Fast path everything below if the registered dictionary is "None". + if (desiredType === null) { + return { name: algName }; + } - // 5. - let desiredType = undefined; - for (const key in registeredAlgorithms) { - if (!ObjectPrototypeHasOwnProperty(registeredAlgorithms, key)) { - continue; - } - if ( - StringPrototypeToUpperCase(key) === StringPrototypeToUpperCase(algName) - ) { - algName = key; - desiredType = registeredAlgorithms[key]; - } + // 6. + const normalizedAlgorithm = webidl.converters[desiredType](algorithm, { + prefix: "Failed to normalize algorithm", + context: "passed algorithm", + }); + // 7. + normalizedAlgorithm.name = algName; + + // 9. + const dict = simpleAlgorithmDictionaries[desiredType]; + // 10. + for (const member in dict) { + if (!ObjectPrototypeHasOwnProperty(dict, member)) { + continue; } - if (desiredType === undefined) { - throw new DOMException( - "Unrecognized algorithm name", - "NotSupportedError", + const idlType = dict[member]; + const idlValue = normalizedAlgorithm[member]; + // 3. + if (idlType === "BufferSource" && idlValue) { + normalizedAlgorithm[member] = TypedArrayPrototypeSlice( + new Uint8Array( + ArrayBufferIsView(idlValue) ? idlValue.buffer : idlValue, + idlValue.byteOffset ?? 0, + idlValue.byteLength, + ), ); + } else if (idlType === "HashAlgorithmIdentifier") { + normalizedAlgorithm[member] = normalizeAlgorithm(idlValue, "digest"); + } else if (idlType === "AlgorithmIdentifier") { + // TODO(lucacasonato): implement + throw new TypeError("unimplemented"); } + } - // Fast path everything below if the registered dictionary is "None". - if (desiredType === null) { - return { name: algName }; - } - - // 6. - const normalizedAlgorithm = webidl.converters[desiredType](algorithm, { - prefix: "Failed to normalize algorithm", - context: "passed algorithm", - }); - // 7. - normalizedAlgorithm.name = algName; + return normalizedAlgorithm; +} + +/** + * @param {ArrayBufferView | ArrayBuffer} input + * @returns {Uint8Array} + */ +function copyBuffer(input) { + return TypedArrayPrototypeSlice( + ArrayBufferIsView(input) + ? new Uint8Array(input.buffer, input.byteOffset, input.byteLength) + : new Uint8Array(input), + ); +} + +const _handle = Symbol("[[handle]]"); +const _algorithm = Symbol("[[algorithm]]"); +const _extractable = Symbol("[[extractable]]"); +const _usages = Symbol("[[usages]]"); +const _type = Symbol("[[type]]"); + +class CryptoKey { + /** @type {string} */ + [_type]; + /** @type {boolean} */ + [_extractable]; + /** @type {object} */ + [_algorithm]; + /** @type {string[]} */ + [_usages]; + /** @type {object} */ + [_handle]; + + constructor() { + webidl.illegalConstructor(); + } - // 9. - const dict = simpleAlgorithmDictionaries[desiredType]; - // 10. - for (const member in dict) { - if (!ObjectPrototypeHasOwnProperty(dict, member)) { - continue; - } - const idlType = dict[member]; - const idlValue = normalizedAlgorithm[member]; - // 3. - if (idlType === "BufferSource" && idlValue) { - normalizedAlgorithm[member] = TypedArrayPrototypeSlice( - new Uint8Array( - ArrayBufferIsView(idlValue) ? idlValue.buffer : idlValue, - idlValue.byteOffset ?? 0, - idlValue.byteLength, - ), - ); - } else if (idlType === "HashAlgorithmIdentifier") { - normalizedAlgorithm[member] = normalizeAlgorithm(idlValue, "digest"); - } else if (idlType === "AlgorithmIdentifier") { - // TODO(lucacasonato): implement - throw new TypeError("unimplemented"); - } - } + /** @returns {string} */ + get type() { + webidl.assertBranded(this, CryptoKeyPrototype); + return this[_type]; + } - return normalizedAlgorithm; + /** @returns {boolean} */ + get extractable() { + webidl.assertBranded(this, CryptoKeyPrototype); + return this[_extractable]; } - /** - * @param {ArrayBufferView | ArrayBuffer} input - * @returns {Uint8Array} - */ - function copyBuffer(input) { - return TypedArrayPrototypeSlice( - ArrayBufferIsView(input) - ? new Uint8Array(input.buffer, input.byteOffset, input.byteLength) - : new Uint8Array(input), - ); + /** @returns {string[]} */ + get usages() { + webidl.assertBranded(this, CryptoKeyPrototype); + // TODO(lucacasonato): return a SameObject copy + return this[_usages]; } - const _handle = Symbol("[[handle]]"); - const _algorithm = Symbol("[[algorithm]]"); - const _extractable = Symbol("[[extractable]]"); - const _usages = Symbol("[[usages]]"); - const _type = Symbol("[[type]]"); - - class CryptoKey { - /** @type {string} */ - [_type]; - /** @type {boolean} */ - [_extractable]; - /** @type {object} */ - [_algorithm]; - /** @type {string[]} */ - [_usages]; - /** @type {object} */ - [_handle]; - - constructor() { - webidl.illegalConstructor(); - } + /** @returns {object} */ + get algorithm() { + webidl.assertBranded(this, CryptoKeyPrototype); + // TODO(lucacasonato): return a SameObject copy + return this[_algorithm]; + } - /** @returns {string} */ - get type() { - webidl.assertBranded(this, CryptoKeyPrototype); - return this[_type]; - } + [SymbolFor("Deno.customInspect")](inspect) { + return `${this.constructor.name} ${ + inspect({ + type: this.type, + extractable: this.extractable, + algorithm: this.algorithm, + usages: this.usages, + }) + }`; + } +} + +webidl.configurePrototype(CryptoKey); +const CryptoKeyPrototype = CryptoKey.prototype; + +/** + * @param {string} type + * @param {boolean} extractable + * @param {string[]} usages + * @param {object} algorithm + * @param {object} handle + * @returns + */ +function constructKey(type, extractable, usages, algorithm, handle) { + const key = webidl.createBranded(CryptoKey); + key[_type] = type; + key[_extractable] = extractable; + key[_usages] = usages; + key[_algorithm] = algorithm; + key[_handle] = handle; + return key; +} + +// https://w3c.github.io/webcrypto/#concept-usage-intersection +/** + * @param {string[]} a + * @param {string[]} b + * @returns + */ +function usageIntersection(a, b) { + return a.filter((i) => b.includes(i)); +} + +// TODO(lucacasonato): this should be moved to rust +/** @type {WeakMap} */ +const KEY_STORE = new WeakMap(); + +function getKeyLength(algorithm) { + switch (algorithm.name) { + case "AES-CBC": + case "AES-CTR": + case "AES-GCM": + case "AES-KW": { + // 1. + if (!ArrayPrototypeIncludes([128, 192, 256], algorithm.length)) { + throw new DOMException( + "length must be 128, 192, or 256", + "OperationError", + ); + } - /** @returns {boolean} */ - get extractable() { - webidl.assertBranded(this, CryptoKeyPrototype); - return this[_extractable]; + // 2. + return algorithm.length; } + case "HMAC": { + // 1. + let length; + if (algorithm.length === undefined) { + switch (algorithm.hash.name) { + case "SHA-1": + length = 512; + break; + case "SHA-256": + length = 512; + break; + case "SHA-384": + length = 1024; + break; + case "SHA-512": + length = 1024; + break; + default: + throw new DOMException( + "Unrecognized hash algorithm", + "NotSupportedError", + ); + } + } else if (algorithm.length !== 0) { + length = algorithm.length; + } else { + throw new TypeError("Invalid length."); + } - /** @returns {string[]} */ - get usages() { - webidl.assertBranded(this, CryptoKeyPrototype); - // TODO(lucacasonato): return a SameObject copy - return this[_usages]; + // 2. + return length; } - - /** @returns {object} */ - get algorithm() { - webidl.assertBranded(this, CryptoKeyPrototype); - // TODO(lucacasonato): return a SameObject copy - return this[_algorithm]; + case "HKDF": { + // 1. + return null; } - - [SymbolFor("Deno.customInspect")](inspect) { - return `${this.constructor.name} ${ - inspect({ - type: this.type, - extractable: this.extractable, - algorithm: this.algorithm, - usages: this.usages, - }) - }`; + case "PBKDF2": { + // 1. + return null; } + default: + throw new TypeError("unreachable"); } +} - webidl.configurePrototype(CryptoKey); - const CryptoKeyPrototype = CryptoKey.prototype; - - /** - * @param {string} type - * @param {boolean} extractable - * @param {string[]} usages - * @param {object} algorithm - * @param {object} handle - * @returns - */ - function constructKey(type, extractable, usages, algorithm, handle) { - const key = webidl.createBranded(CryptoKey); - key[_type] = type; - key[_extractable] = extractable; - key[_usages] = usages; - key[_algorithm] = algorithm; - key[_handle] = handle; - return key; +class SubtleCrypto { + constructor() { + webidl.illegalConstructor(); } - // https://w3c.github.io/webcrypto/#concept-usage-intersection /** - * @param {string[]} a - * @param {string[]} b - * @returns + * @param {string} algorithm + * @param {BufferSource} data + * @returns {Promise} */ - function usageIntersection(a, b) { - return a.filter((i) => b.includes(i)); - } + async digest(algorithm, data) { + webidl.assertBranded(this, SubtleCryptoPrototype); + const prefix = "Failed to execute 'digest' on 'SubtleCrypto'"; + webidl.requiredArguments(arguments.length, 2, { prefix }); + algorithm = webidl.converters.AlgorithmIdentifier(algorithm, { + prefix, + context: "Argument 1", + }); + data = webidl.converters.BufferSource(data, { + prefix, + context: "Argument 2", + }); - // TODO(lucacasonato): this should be moved to rust - /** @type {WeakMap} */ - const KEY_STORE = new WeakMap(); + data = copyBuffer(data); - function getKeyLength(algorithm) { - switch (algorithm.name) { - case "AES-CBC": - case "AES-CTR": - case "AES-GCM": - case "AES-KW": { - // 1. - if (!ArrayPrototypeIncludes([128, 192, 256], algorithm.length)) { - throw new DOMException( - "length must be 128, 192, or 256", - "OperationError", - ); - } + algorithm = normalizeAlgorithm(algorithm, "digest"); - // 2. - return algorithm.length; - } - case "HMAC": { - // 1. - let length; - if (algorithm.length === undefined) { - switch (algorithm.hash.name) { - case "SHA-1": - length = 512; - break; - case "SHA-256": - length = 512; - break; - case "SHA-384": - length = 1024; - break; - case "SHA-512": - length = 1024; - break; - default: - throw new DOMException( - "Unrecognized hash algorithm", - "NotSupportedError", - ); - } - } else if (algorithm.length !== 0) { - length = algorithm.length; - } else { - throw new TypeError("Invalid length."); - } + const result = await core.opAsync( + "op_crypto_subtle_digest", + algorithm.name, + data, + ); - // 2. - return length; - } - case "HKDF": { - // 1. - return null; - } - case "PBKDF2": { - // 1. - return null; - } - default: - throw new TypeError("unreachable"); - } + return result.buffer; } - class SubtleCrypto { - constructor() { - webidl.illegalConstructor(); - } - - /** - * @param {string} algorithm - * @param {BufferSource} data - * @returns {Promise} - */ - async digest(algorithm, data) { - webidl.assertBranded(this, SubtleCryptoPrototype); - const prefix = "Failed to execute 'digest' on 'SubtleCrypto'"; - webidl.requiredArguments(arguments.length, 2, { prefix }); - algorithm = webidl.converters.AlgorithmIdentifier(algorithm, { - prefix, - context: "Argument 1", - }); - data = webidl.converters.BufferSource(data, { - prefix, - context: "Argument 2", - }); + /** + * @param {string} algorithm + * @param {CryptoKey} key + * @param {BufferSource} data + * @returns {Promise} + */ + async encrypt(algorithm, key, data) { + webidl.assertBranded(this, SubtleCryptoPrototype); + const prefix = "Failed to execute 'encrypt' on 'SubtleCrypto'"; + webidl.requiredArguments(arguments.length, 3, { prefix }); + algorithm = webidl.converters.AlgorithmIdentifier(algorithm, { + prefix, + context: "Argument 1", + }); + key = webidl.converters.CryptoKey(key, { + prefix, + context: "Argument 2", + }); + data = webidl.converters.BufferSource(data, { + prefix, + context: "Argument 3", + }); - data = copyBuffer(data); + // 2. + data = copyBuffer(data); - algorithm = normalizeAlgorithm(algorithm, "digest"); + // 3. + const normalizedAlgorithm = normalizeAlgorithm(algorithm, "encrypt"); - const result = await core.opAsync( - "op_crypto_subtle_digest", - algorithm.name, - data, + // 8. + if (normalizedAlgorithm.name !== key[_algorithm].name) { + throw new DOMException( + "Encryption algorithm doesn't match key algorithm.", + "InvalidAccessError", ); + } - return result.buffer; + // 9. + if (!ArrayPrototypeIncludes(key[_usages], "encrypt")) { + throw new DOMException( + "Key does not support the 'encrypt' operation.", + "InvalidAccessError", + ); } - /** - * @param {string} algorithm - * @param {CryptoKey} key - * @param {BufferSource} data - * @returns {Promise} - */ - async encrypt(algorithm, key, data) { - webidl.assertBranded(this, SubtleCryptoPrototype); - const prefix = "Failed to execute 'encrypt' on 'SubtleCrypto'"; - webidl.requiredArguments(arguments.length, 3, { prefix }); - algorithm = webidl.converters.AlgorithmIdentifier(algorithm, { - prefix, - context: "Argument 1", - }); - key = webidl.converters.CryptoKey(key, { - prefix, - context: "Argument 2", - }); - data = webidl.converters.BufferSource(data, { - prefix, - context: "Argument 3", - }); + return await encrypt(normalizedAlgorithm, key, data); + } - // 2. - data = copyBuffer(data); + /** + * @param {string} algorithm + * @param {CryptoKey} key + * @param {BufferSource} data + * @returns {Promise} + */ + async decrypt(algorithm, key, data) { + webidl.assertBranded(this, SubtleCryptoPrototype); + const prefix = "Failed to execute 'decrypt' on 'SubtleCrypto'"; + webidl.requiredArguments(arguments.length, 3, { prefix }); + algorithm = webidl.converters.AlgorithmIdentifier(algorithm, { + prefix, + context: "Argument 1", + }); + key = webidl.converters.CryptoKey(key, { + prefix, + context: "Argument 2", + }); + data = webidl.converters.BufferSource(data, { + prefix, + context: "Argument 3", + }); - // 3. - const normalizedAlgorithm = normalizeAlgorithm(algorithm, "encrypt"); + // 2. + data = copyBuffer(data); - // 8. - if (normalizedAlgorithm.name !== key[_algorithm].name) { - throw new DOMException( - "Encryption algorithm doesn't match key algorithm.", - "InvalidAccessError", - ); - } + // 3. + const normalizedAlgorithm = normalizeAlgorithm(algorithm, "decrypt"); - // 9. - if (!ArrayPrototypeIncludes(key[_usages], "encrypt")) { - throw new DOMException( - "Key does not support the 'encrypt' operation.", - "InvalidAccessError", - ); - } + // 8. + if (normalizedAlgorithm.name !== key[_algorithm].name) { + throw new DOMException( + "Decryption algorithm doesn't match key algorithm.", + "OperationError", + ); + } - return await encrypt(normalizedAlgorithm, key, data); + // 9. + if (!ArrayPrototypeIncludes(key[_usages], "decrypt")) { + throw new DOMException( + "Key does not support the 'decrypt' operation.", + "InvalidAccessError", + ); } - /** - * @param {string} algorithm - * @param {CryptoKey} key - * @param {BufferSource} data - * @returns {Promise} - */ - async decrypt(algorithm, key, data) { - webidl.assertBranded(this, SubtleCryptoPrototype); - const prefix = "Failed to execute 'decrypt' on 'SubtleCrypto'"; - webidl.requiredArguments(arguments.length, 3, { prefix }); - algorithm = webidl.converters.AlgorithmIdentifier(algorithm, { - prefix, - context: "Argument 1", - }); - key = webidl.converters.CryptoKey(key, { - prefix, - context: "Argument 2", - }); - data = webidl.converters.BufferSource(data, { - prefix, - context: "Argument 3", - }); + const handle = key[_handle]; + const keyData = WeakMapPrototypeGet(KEY_STORE, handle); - // 2. - data = copyBuffer(data); + switch (normalizedAlgorithm.name) { + case "RSA-OAEP": { + // 1. + if (key[_type] !== "private") { + throw new DOMException( + "Key type not supported", + "InvalidAccessError", + ); + } - // 3. - const normalizedAlgorithm = normalizeAlgorithm(algorithm, "decrypt"); + // 2. + if (normalizedAlgorithm.label) { + normalizedAlgorithm.label = copyBuffer(normalizedAlgorithm.label); + } else { + normalizedAlgorithm.label = new Uint8Array(); + } - // 8. - if (normalizedAlgorithm.name !== key[_algorithm].name) { - throw new DOMException( - "Decryption algorithm doesn't match key algorithm.", - "OperationError", - ); - } + // 3-5. + const hashAlgorithm = key[_algorithm].hash.name; + const plainText = await core.opAsync("op_crypto_decrypt", { + key: keyData, + algorithm: "RSA-OAEP", + hash: hashAlgorithm, + label: normalizedAlgorithm.label, + }, data); - // 9. - if (!ArrayPrototypeIncludes(key[_usages], "decrypt")) { - throw new DOMException( - "Key does not support the 'decrypt' operation.", - "InvalidAccessError", - ); + // 6. + return plainText.buffer; } + case "AES-CBC": { + normalizedAlgorithm.iv = copyBuffer(normalizedAlgorithm.iv); - const handle = key[_handle]; - const keyData = WeakMapPrototypeGet(KEY_STORE, handle); + // 1. + if (normalizedAlgorithm.iv.byteLength !== 16) { + throw new DOMException( + "Counter must be 16 bytes", + "OperationError", + ); + } - switch (normalizedAlgorithm.name) { - case "RSA-OAEP": { - // 1. - if (key[_type] !== "private") { - throw new DOMException( - "Key type not supported", - "InvalidAccessError", - ); - } + const plainText = await core.opAsync("op_crypto_decrypt", { + key: keyData, + algorithm: "AES-CBC", + iv: normalizedAlgorithm.iv, + length: key[_algorithm].length, + }, data); - // 2. - if (normalizedAlgorithm.label) { - normalizedAlgorithm.label = copyBuffer(normalizedAlgorithm.label); - } else { - normalizedAlgorithm.label = new Uint8Array(); - } + // 6. + return plainText.buffer; + } + case "AES-CTR": { + normalizedAlgorithm.counter = copyBuffer(normalizedAlgorithm.counter); - // 3-5. - const hashAlgorithm = key[_algorithm].hash.name; - const plainText = await core.opAsync("op_crypto_decrypt", { - key: keyData, - algorithm: "RSA-OAEP", - hash: hashAlgorithm, - label: normalizedAlgorithm.label, - }, data); + // 1. + if (normalizedAlgorithm.counter.byteLength !== 16) { + throw new DOMException( + "Counter vector must be 16 bytes", + "OperationError", + ); + } - // 6. - return plainText.buffer; + // 2. + if ( + normalizedAlgorithm.length === 0 || normalizedAlgorithm.length > 128 + ) { + throw new DOMException( + "Counter length must not be 0 or greater than 128", + "OperationError", + ); } - case "AES-CBC": { - normalizedAlgorithm.iv = copyBuffer(normalizedAlgorithm.iv); - // 1. - if (normalizedAlgorithm.iv.byteLength !== 16) { - throw new DOMException( - "Counter must be 16 bytes", - "OperationError", - ); - } + // 3. + const cipherText = await core.opAsync("op_crypto_decrypt", { + key: keyData, + algorithm: "AES-CTR", + keyLength: key[_algorithm].length, + counter: normalizedAlgorithm.counter, + ctrLength: normalizedAlgorithm.length, + }, data); - const plainText = await core.opAsync("op_crypto_decrypt", { - key: keyData, - algorithm: "AES-CBC", - iv: normalizedAlgorithm.iv, - length: key[_algorithm].length, - }, data); + // 4. + return cipherText.buffer; + } + case "AES-GCM": { + normalizedAlgorithm.iv = copyBuffer(normalizedAlgorithm.iv); - // 6. - return plainText.buffer; + // 1. + if (normalizedAlgorithm.tagLength === undefined) { + normalizedAlgorithm.tagLength = 128; + } else if ( + !ArrayPrototypeIncludes( + [32, 64, 96, 104, 112, 120, 128], + normalizedAlgorithm.tagLength, + ) + ) { + throw new DOMException( + "Invalid tag length", + "OperationError", + ); } - case "AES-CTR": { - normalizedAlgorithm.counter = copyBuffer(normalizedAlgorithm.counter); - - // 1. - if (normalizedAlgorithm.counter.byteLength !== 16) { - throw new DOMException( - "Counter vector must be 16 bytes", - "OperationError", - ); - } - - // 2. - if ( - normalizedAlgorithm.length === 0 || normalizedAlgorithm.length > 128 - ) { - throw new DOMException( - "Counter length must not be 0 or greater than 128", - "OperationError", - ); - } - // 3. - const cipherText = await core.opAsync("op_crypto_decrypt", { - key: keyData, - algorithm: "AES-CTR", - keyLength: key[_algorithm].length, - counter: normalizedAlgorithm.counter, - ctrLength: normalizedAlgorithm.length, - }, data); - - // 4. - return cipherText.buffer; + // 2. + if (data.byteLength < normalizedAlgorithm.tagLength / 8) { + throw new DOMException( + "Tag length overflows ciphertext", + "OperationError", + ); } - case "AES-GCM": { - normalizedAlgorithm.iv = copyBuffer(normalizedAlgorithm.iv); - // 1. - if (normalizedAlgorithm.tagLength === undefined) { - normalizedAlgorithm.tagLength = 128; - } else if ( - !ArrayPrototypeIncludes( - [32, 64, 96, 104, 112, 120, 128], - normalizedAlgorithm.tagLength, - ) - ) { - throw new DOMException( - "Invalid tag length", - "OperationError", - ); - } + // 3. We only support 96-bit and 128-bit nonce. + if ( + ArrayPrototypeIncludes( + [12, 16], + normalizedAlgorithm.iv.byteLength, + ) === undefined + ) { + throw new DOMException( + "Initialization vector length not supported", + "NotSupportedError", + ); + } - // 2. - if (data.byteLength < normalizedAlgorithm.tagLength / 8) { + // 4. + if (normalizedAlgorithm.additionalData !== undefined) { + if (normalizedAlgorithm.additionalData.byteLength > (2 ** 64) - 1) { throw new DOMException( - "Tag length overflows ciphertext", + "Additional data too large", "OperationError", ); } + normalizedAlgorithm.additionalData = copyBuffer( + normalizedAlgorithm.additionalData, + ); + } - // 3. We only support 96-bit and 128-bit nonce. - if ( - ArrayPrototypeIncludes( - [12, 16], - normalizedAlgorithm.iv.byteLength, - ) === undefined - ) { - throw new DOMException( - "Initialization vector length not supported", - "NotSupportedError", - ); - } - - // 4. - if (normalizedAlgorithm.additionalData !== undefined) { - if (normalizedAlgorithm.additionalData.byteLength > (2 ** 64) - 1) { - throw new DOMException( - "Additional data too large", - "OperationError", - ); - } - normalizedAlgorithm.additionalData = copyBuffer( - normalizedAlgorithm.additionalData, - ); - } + // 5-8. + const plaintext = await core.opAsync("op_crypto_decrypt", { + key: keyData, + algorithm: "AES-GCM", + length: key[_algorithm].length, + iv: normalizedAlgorithm.iv, + additionalData: normalizedAlgorithm.additionalData || + null, + tagLength: normalizedAlgorithm.tagLength, + }, data); - // 5-8. - const plaintext = await core.opAsync("op_crypto_decrypt", { - key: keyData, - algorithm: "AES-GCM", - length: key[_algorithm].length, - iv: normalizedAlgorithm.iv, - additionalData: normalizedAlgorithm.additionalData || - null, - tagLength: normalizedAlgorithm.tagLength, - }, data); - - // 9. - return plaintext.buffer; - } - default: - throw new DOMException("Not implemented", "NotSupportedError"); + // 9. + return plaintext.buffer; } + default: + throw new DOMException("Not implemented", "NotSupportedError"); } + } - /** - * @param {string} algorithm - * @param {CryptoKey} key - * @param {BufferSource} data - * @returns {Promise} - */ - async sign(algorithm, key, data) { - webidl.assertBranded(this, SubtleCryptoPrototype); - const prefix = "Failed to execute 'sign' on 'SubtleCrypto'"; - webidl.requiredArguments(arguments.length, 3, { prefix }); - algorithm = webidl.converters.AlgorithmIdentifier(algorithm, { - prefix, - context: "Argument 1", - }); - key = webidl.converters.CryptoKey(key, { - prefix, - context: "Argument 2", - }); - data = webidl.converters.BufferSource(data, { - prefix, - context: "Argument 3", - }); + /** + * @param {string} algorithm + * @param {CryptoKey} key + * @param {BufferSource} data + * @returns {Promise} + */ + async sign(algorithm, key, data) { + webidl.assertBranded(this, SubtleCryptoPrototype); + const prefix = "Failed to execute 'sign' on 'SubtleCrypto'"; + webidl.requiredArguments(arguments.length, 3, { prefix }); + algorithm = webidl.converters.AlgorithmIdentifier(algorithm, { + prefix, + context: "Argument 1", + }); + key = webidl.converters.CryptoKey(key, { + prefix, + context: "Argument 2", + }); + data = webidl.converters.BufferSource(data, { + prefix, + context: "Argument 3", + }); - // 1. - data = copyBuffer(data); + // 1. + data = copyBuffer(data); - // 2. - const normalizedAlgorithm = normalizeAlgorithm(algorithm, "sign"); + // 2. + const normalizedAlgorithm = normalizeAlgorithm(algorithm, "sign"); - const handle = key[_handle]; - const keyData = WeakMapPrototypeGet(KEY_STORE, handle); + const handle = key[_handle]; + const keyData = WeakMapPrototypeGet(KEY_STORE, handle); - // 8. - if (normalizedAlgorithm.name !== key[_algorithm].name) { - throw new DOMException( - "Signing algorithm doesn't match key algorithm.", - "InvalidAccessError", - ); - } + // 8. + if (normalizedAlgorithm.name !== key[_algorithm].name) { + throw new DOMException( + "Signing algorithm doesn't match key algorithm.", + "InvalidAccessError", + ); + } - // 9. - if (!ArrayPrototypeIncludes(key[_usages], "sign")) { - throw new DOMException( - "Key does not support the 'sign' operation.", - "InvalidAccessError", - ); - } + // 9. + if (!ArrayPrototypeIncludes(key[_usages], "sign")) { + throw new DOMException( + "Key does not support the 'sign' operation.", + "InvalidAccessError", + ); + } - switch (normalizedAlgorithm.name) { - case "RSASSA-PKCS1-v1_5": { - // 1. - if (key[_type] !== "private") { - throw new DOMException( - "Key type not supported", - "InvalidAccessError", - ); - } + switch (normalizedAlgorithm.name) { + case "RSASSA-PKCS1-v1_5": { + // 1. + if (key[_type] !== "private") { + throw new DOMException( + "Key type not supported", + "InvalidAccessError", + ); + } - // 2. - const hashAlgorithm = key[_algorithm].hash.name; - const signature = await core.opAsync("op_crypto_sign_key", { - key: keyData, - algorithm: "RSASSA-PKCS1-v1_5", - hash: hashAlgorithm, - }, data); + // 2. + const hashAlgorithm = key[_algorithm].hash.name; + const signature = await core.opAsync("op_crypto_sign_key", { + key: keyData, + algorithm: "RSASSA-PKCS1-v1_5", + hash: hashAlgorithm, + }, data); - return signature.buffer; + return signature.buffer; + } + case "RSA-PSS": { + // 1. + if (key[_type] !== "private") { + throw new DOMException( + "Key type not supported", + "InvalidAccessError", + ); } - case "RSA-PSS": { - // 1. - if (key[_type] !== "private") { - throw new DOMException( - "Key type not supported", - "InvalidAccessError", - ); - } - // 2. - const hashAlgorithm = key[_algorithm].hash.name; - const signature = await core.opAsync("op_crypto_sign_key", { - key: keyData, - algorithm: "RSA-PSS", - hash: hashAlgorithm, - saltLength: normalizedAlgorithm.saltLength, - }, data); + // 2. + const hashAlgorithm = key[_algorithm].hash.name; + const signature = await core.opAsync("op_crypto_sign_key", { + key: keyData, + algorithm: "RSA-PSS", + hash: hashAlgorithm, + saltLength: normalizedAlgorithm.saltLength, + }, data); - return signature.buffer; + return signature.buffer; + } + case "ECDSA": { + // 1. + if (key[_type] !== "private") { + throw new DOMException( + "Key type not supported", + "InvalidAccessError", + ); } - case "ECDSA": { - // 1. - if (key[_type] !== "private") { - throw new DOMException( - "Key type not supported", - "InvalidAccessError", - ); - } - // 2. - const hashAlgorithm = normalizedAlgorithm.hash.name; - const namedCurve = key[_algorithm].namedCurve; - if (!ArrayPrototypeIncludes(supportedNamedCurves, namedCurve)) { - throw new DOMException("Curve not supported", "NotSupportedError"); - } + // 2. + const hashAlgorithm = normalizedAlgorithm.hash.name; + const namedCurve = key[_algorithm].namedCurve; + if (!ArrayPrototypeIncludes(supportedNamedCurves, namedCurve)) { + throw new DOMException("Curve not supported", "NotSupportedError"); + } - const signature = await core.opAsync("op_crypto_sign_key", { - key: keyData, - algorithm: "ECDSA", - hash: hashAlgorithm, - namedCurve, - }, data); + const signature = await core.opAsync("op_crypto_sign_key", { + key: keyData, + algorithm: "ECDSA", + hash: hashAlgorithm, + namedCurve, + }, data); - return signature.buffer; - } - case "HMAC": { - const hashAlgorithm = key[_algorithm].hash.name; + return signature.buffer; + } + case "HMAC": { + const hashAlgorithm = key[_algorithm].hash.name; - const signature = await core.opAsync("op_crypto_sign_key", { - key: keyData, - algorithm: "HMAC", - hash: hashAlgorithm, - }, data); + const signature = await core.opAsync("op_crypto_sign_key", { + key: keyData, + algorithm: "HMAC", + hash: hashAlgorithm, + }, data); - return signature.buffer; + return signature.buffer; + } + case "Ed25519": { + // 1. + if (key[_type] !== "private") { + throw new DOMException( + "Key type not supported", + "InvalidAccessError", + ); } - case "Ed25519": { - // 1. - if (key[_type] !== "private") { - throw new DOMException( - "Key type not supported", - "InvalidAccessError", - ); - } - // https://briansmith.org/rustdoc/src/ring/ec/curve25519/ed25519/signing.rs.html#260 - const SIGNATURE_LEN = 32 * 2; // ELEM_LEN + SCALAR_LEN - const signature = new Uint8Array(SIGNATURE_LEN); - if (!ops.op_sign_ed25519(keyData, data, signature)) { - throw new DOMException( - "Failed to sign", - "OperationError", - ); - } - return signature.buffer; + // https://briansmith.org/rustdoc/src/ring/ec/curve25519/ed25519/signing.rs.html#260 + const SIGNATURE_LEN = 32 * 2; // ELEM_LEN + SCALAR_LEN + const signature = new Uint8Array(SIGNATURE_LEN); + if (!ops.op_sign_ed25519(keyData, data, signature)) { + throw new DOMException( + "Failed to sign", + "OperationError", + ); } + return signature.buffer; } - - throw new TypeError("unreachable"); } - /** - * @param {string} format - * @param {BufferSource} keyData - * @param {string} algorithm - * @param {boolean} extractable - * @param {KeyUsages[]} keyUsages - * @returns {Promise} - */ - // deno-lint-ignore require-await - async importKey(format, keyData, algorithm, extractable, keyUsages) { - webidl.assertBranded(this, SubtleCryptoPrototype); - const prefix = "Failed to execute 'importKey' on 'SubtleCrypto'"; - webidl.requiredArguments(arguments.length, 4, { prefix }); - format = webidl.converters.KeyFormat(format, { - prefix, - context: "Argument 1", - }); - keyData = webidl.converters["BufferSource or JsonWebKey"](keyData, { - prefix, - context: "Argument 2", - }); - algorithm = webidl.converters.AlgorithmIdentifier(algorithm, { - prefix, - context: "Argument 3", - }); - extractable = webidl.converters.boolean(extractable, { - prefix, - context: "Argument 4", - }); - keyUsages = webidl.converters["sequence"](keyUsages, { - prefix, - context: "Argument 5", - }); + throw new TypeError("unreachable"); + } - // 2. - if (format !== "jwk") { - if ( - ArrayBufferIsView(keyData) || - ObjectPrototypeIsPrototypeOf(ArrayBufferPrototype, keyData) - ) { - keyData = copyBuffer(keyData); - } else { - throw new TypeError("keyData is a JsonWebKey"); - } + /** + * @param {string} format + * @param {BufferSource} keyData + * @param {string} algorithm + * @param {boolean} extractable + * @param {KeyUsages[]} keyUsages + * @returns {Promise} + */ + // deno-lint-ignore require-await + async importKey(format, keyData, algorithm, extractable, keyUsages) { + webidl.assertBranded(this, SubtleCryptoPrototype); + const prefix = "Failed to execute 'importKey' on 'SubtleCrypto'"; + webidl.requiredArguments(arguments.length, 4, { prefix }); + format = webidl.converters.KeyFormat(format, { + prefix, + context: "Argument 1", + }); + keyData = webidl.converters["BufferSource or JsonWebKey"](keyData, { + prefix, + context: "Argument 2", + }); + algorithm = webidl.converters.AlgorithmIdentifier(algorithm, { + prefix, + context: "Argument 3", + }); + extractable = webidl.converters.boolean(extractable, { + prefix, + context: "Argument 4", + }); + keyUsages = webidl.converters["sequence"](keyUsages, { + prefix, + context: "Argument 5", + }); + + // 2. + if (format !== "jwk") { + if ( + ArrayBufferIsView(keyData) || + ObjectPrototypeIsPrototypeOf(ArrayBufferPrototype, keyData) + ) { + keyData = copyBuffer(keyData); } else { - if ( - ArrayBufferIsView(keyData) || - ObjectPrototypeIsPrototypeOf(ArrayBufferPrototype, keyData) - ) { - throw new TypeError("keyData is not a JsonWebKey"); - } + throw new TypeError("keyData is a JsonWebKey"); + } + } else { + if ( + ArrayBufferIsView(keyData) || + ObjectPrototypeIsPrototypeOf(ArrayBufferPrototype, keyData) + ) { + throw new TypeError("keyData is not a JsonWebKey"); } + } - const normalizedAlgorithm = normalizeAlgorithm(algorithm, "importKey"); + const normalizedAlgorithm = normalizeAlgorithm(algorithm, "importKey"); - const algorithmName = normalizedAlgorithm.name; + const algorithmName = normalizedAlgorithm.name; - switch (algorithmName) { - case "HMAC": { - return importKeyHMAC( - format, - normalizedAlgorithm, - keyData, - extractable, - keyUsages, - ); - } - case "ECDH": - case "ECDSA": { - return importKeyEC( - format, - normalizedAlgorithm, - keyData, - extractable, - keyUsages, - ); - } - case "RSASSA-PKCS1-v1_5": - case "RSA-PSS": - case "RSA-OAEP": { - return importKeyRSA( - format, - normalizedAlgorithm, - keyData, - extractable, - keyUsages, - ); - } - case "HKDF": { - return importKeyHKDF(format, keyData, extractable, keyUsages); - } - case "PBKDF2": { - return importKeyPBKDF2(format, keyData, extractable, keyUsages); - } - case "AES-CTR": - case "AES-CBC": - case "AES-GCM": { - return importKeyAES( - format, - normalizedAlgorithm, - keyData, - extractable, - keyUsages, - ["encrypt", "decrypt", "wrapKey", "unwrapKey"], - ); - } - case "AES-KW": { - return importKeyAES( - format, - normalizedAlgorithm, - keyData, - extractable, - keyUsages, - ["wrapKey", "unwrapKey"], - ); - } - case "X25519": { - return importKeyX25519( - format, - keyData, - extractable, - keyUsages, - ); - } - case "Ed25519": { - return importKeyEd25519( - format, - keyData, - extractable, - keyUsages, - ); - } - default: - throw new DOMException("Not implemented", "NotSupportedError"); + switch (algorithmName) { + case "HMAC": { + return importKeyHMAC( + format, + normalizedAlgorithm, + keyData, + extractable, + keyUsages, + ); + } + case "ECDH": + case "ECDSA": { + return importKeyEC( + format, + normalizedAlgorithm, + keyData, + extractable, + keyUsages, + ); + } + case "RSASSA-PKCS1-v1_5": + case "RSA-PSS": + case "RSA-OAEP": { + return importKeyRSA( + format, + normalizedAlgorithm, + keyData, + extractable, + keyUsages, + ); + } + case "HKDF": { + return importKeyHKDF(format, keyData, extractable, keyUsages); + } + case "PBKDF2": { + return importKeyPBKDF2(format, keyData, extractable, keyUsages); + } + case "AES-CTR": + case "AES-CBC": + case "AES-GCM": { + return importKeyAES( + format, + normalizedAlgorithm, + keyData, + extractable, + keyUsages, + ["encrypt", "decrypt", "wrapKey", "unwrapKey"], + ); + } + case "AES-KW": { + return importKeyAES( + format, + normalizedAlgorithm, + keyData, + extractable, + keyUsages, + ["wrapKey", "unwrapKey"], + ); + } + case "X25519": { + return importKeyX25519( + format, + keyData, + extractable, + keyUsages, + ); + } + case "Ed25519": { + return importKeyEd25519( + format, + keyData, + extractable, + keyUsages, + ); } + default: + throw new DOMException("Not implemented", "NotSupportedError"); } + } - /** - * @param {string} format - * @param {CryptoKey} key - * @returns {Promise} - */ - // deno-lint-ignore require-await - async exportKey(format, key) { - webidl.assertBranded(this, SubtleCryptoPrototype); - const prefix = "Failed to execute 'exportKey' on 'SubtleCrypto'"; - webidl.requiredArguments(arguments.length, 2, { prefix }); - format = webidl.converters.KeyFormat(format, { - prefix, - context: "Argument 1", - }); - key = webidl.converters.CryptoKey(key, { - prefix, - context: "Argument 2", - }); + /** + * @param {string} format + * @param {CryptoKey} key + * @returns {Promise} + */ + // deno-lint-ignore require-await + async exportKey(format, key) { + webidl.assertBranded(this, SubtleCryptoPrototype); + const prefix = "Failed to execute 'exportKey' on 'SubtleCrypto'"; + webidl.requiredArguments(arguments.length, 2, { prefix }); + format = webidl.converters.KeyFormat(format, { + prefix, + context: "Argument 1", + }); + key = webidl.converters.CryptoKey(key, { + prefix, + context: "Argument 2", + }); - const handle = key[_handle]; - // 2. - const innerKey = WeakMapPrototypeGet(KEY_STORE, handle); + const handle = key[_handle]; + // 2. + const innerKey = WeakMapPrototypeGet(KEY_STORE, handle); - const algorithmName = key[_algorithm].name; + const algorithmName = key[_algorithm].name; - let result; + let result; - switch (algorithmName) { - case "HMAC": { - result = exportKeyHMAC(format, key, innerKey); - break; - } - case "RSASSA-PKCS1-v1_5": - case "RSA-PSS": - case "RSA-OAEP": { - result = exportKeyRSA(format, key, innerKey); - break; - } - case "ECDH": - case "ECDSA": { - result = exportKeyEC(format, key, innerKey); - break; - } - case "Ed25519": { - result = exportKeyEd25519(format, key, innerKey); - break; - } - case "X25519": { - result = exportKeyX25519(format, key, innerKey); - break; - } - case "AES-CTR": - case "AES-CBC": - case "AES-GCM": - case "AES-KW": { - result = exportKeyAES(format, key, innerKey); - break; - } - default: - throw new DOMException("Not implemented", "NotSupportedError"); + switch (algorithmName) { + case "HMAC": { + result = exportKeyHMAC(format, key, innerKey); + break; } - - if (key.extractable === false) { - throw new DOMException( - "Key is not extractable", - "InvalidAccessError", - ); + case "RSASSA-PKCS1-v1_5": + case "RSA-PSS": + case "RSA-OAEP": { + result = exportKeyRSA(format, key, innerKey); + break; + } + case "ECDH": + case "ECDSA": { + result = exportKeyEC(format, key, innerKey); + break; + } + case "Ed25519": { + result = exportKeyEd25519(format, key, innerKey); + break; } + case "X25519": { + result = exportKeyX25519(format, key, innerKey); + break; + } + case "AES-CTR": + case "AES-CBC": + case "AES-GCM": + case "AES-KW": { + result = exportKeyAES(format, key, innerKey); + break; + } + default: + throw new DOMException("Not implemented", "NotSupportedError"); + } - return result; + if (key.extractable === false) { + throw new DOMException( + "Key is not extractable", + "InvalidAccessError", + ); } - /** - * @param {AlgorithmIdentifier} algorithm - * @param {CryptoKey} baseKey - * @param {number | null} length - * @returns {Promise} - */ - async deriveBits(algorithm, baseKey, length) { - webidl.assertBranded(this, SubtleCryptoPrototype); - const prefix = "Failed to execute 'deriveBits' on 'SubtleCrypto'"; - webidl.requiredArguments(arguments.length, 3, { prefix }); - algorithm = webidl.converters.AlgorithmIdentifier(algorithm, { - prefix, - context: "Argument 1", - }); - baseKey = webidl.converters.CryptoKey(baseKey, { + return result; + } + + /** + * @param {AlgorithmIdentifier} algorithm + * @param {CryptoKey} baseKey + * @param {number | null} length + * @returns {Promise} + */ + async deriveBits(algorithm, baseKey, length) { + webidl.assertBranded(this, SubtleCryptoPrototype); + const prefix = "Failed to execute 'deriveBits' on 'SubtleCrypto'"; + webidl.requiredArguments(arguments.length, 3, { prefix }); + algorithm = webidl.converters.AlgorithmIdentifier(algorithm, { + prefix, + context: "Argument 1", + }); + baseKey = webidl.converters.CryptoKey(baseKey, { + prefix, + context: "Argument 2", + }); + if (length !== null) { + length = webidl.converters["unsigned long"](length, { prefix, - context: "Argument 2", + context: "Argument 3", }); - if (length !== null) { - length = webidl.converters["unsigned long"](length, { - prefix, - context: "Argument 3", - }); - } + } - // 2. - const normalizedAlgorithm = normalizeAlgorithm(algorithm, "deriveBits"); - // 4-6. - const result = await deriveBits(normalizedAlgorithm, baseKey, length); - // 7. - if (normalizedAlgorithm.name !== baseKey[_algorithm].name) { - throw new DOMException("Invalid algorithm name", "InvalidAccessError"); - } - // 8. - if (!ArrayPrototypeIncludes(baseKey[_usages], "deriveBits")) { - throw new DOMException( - "baseKey usages does not contain `deriveBits`", - "InvalidAccessError", - ); - } - // 9-10. - return result; + // 2. + const normalizedAlgorithm = normalizeAlgorithm(algorithm, "deriveBits"); + // 4-6. + const result = await deriveBits(normalizedAlgorithm, baseKey, length); + // 7. + if (normalizedAlgorithm.name !== baseKey[_algorithm].name) { + throw new DOMException("Invalid algorithm name", "InvalidAccessError"); + } + // 8. + if (!ArrayPrototypeIncludes(baseKey[_usages], "deriveBits")) { + throw new DOMException( + "baseKey usages does not contain `deriveBits`", + "InvalidAccessError", + ); } + // 9-10. + return result; + } - /** - * @param {AlgorithmIdentifier} algorithm - * @param {CryptoKey} baseKey - * @param {number} length - * @returns {Promise} - */ - async deriveKey( - algorithm, - baseKey, + /** + * @param {AlgorithmIdentifier} algorithm + * @param {CryptoKey} baseKey + * @param {number} length + * @returns {Promise} + */ + async deriveKey( + algorithm, + baseKey, + derivedKeyType, + extractable, + keyUsages, + ) { + webidl.assertBranded(this, SubtleCryptoPrototype); + const prefix = "Failed to execute 'deriveKey' on 'SubtleCrypto'"; + webidl.requiredArguments(arguments.length, 5, { prefix }); + algorithm = webidl.converters.AlgorithmIdentifier(algorithm, { + prefix, + context: "Argument 1", + }); + baseKey = webidl.converters.CryptoKey(baseKey, { + prefix, + context: "Argument 2", + }); + derivedKeyType = webidl.converters.AlgorithmIdentifier(derivedKeyType, { + prefix, + context: "Argument 3", + }); + extractable = webidl.converters["boolean"](extractable, { + prefix, + context: "Argument 4", + }); + keyUsages = webidl.converters["sequence"](keyUsages, { + prefix, + context: "Argument 5", + }); + + // 2-3. + const normalizedAlgorithm = normalizeAlgorithm(algorithm, "deriveBits"); + + // 4-5. + const normalizedDerivedKeyAlgorithmImport = normalizeAlgorithm( + derivedKeyType, + "importKey", + ); + + // 6-7. + const normalizedDerivedKeyAlgorithmLength = normalizeAlgorithm( derivedKeyType, + "get key length", + ); + + // 8-10. + + // 11. + if (normalizedAlgorithm.name !== baseKey[_algorithm].name) { + throw new DOMException( + "Invalid algorithm name", + "InvalidAccessError", + ); + } + + // 12. + if (!ArrayPrototypeIncludes(baseKey[_usages], "deriveKey")) { + throw new DOMException( + "baseKey usages does not contain `deriveKey`", + "InvalidAccessError", + ); + } + + // 13. + const length = getKeyLength(normalizedDerivedKeyAlgorithmLength); + + // 14. + const secret = await this.deriveBits( + normalizedAlgorithm, + baseKey, + length, + ); + + // 15. + const result = await this.importKey( + "raw", + secret, + normalizedDerivedKeyAlgorithmImport, extractable, keyUsages, + ); + + // 16. + if ( + ArrayPrototypeIncludes(["private", "secret"], result[_type]) && + keyUsages.length == 0 ) { - webidl.assertBranded(this, SubtleCryptoPrototype); - const prefix = "Failed to execute 'deriveKey' on 'SubtleCrypto'"; - webidl.requiredArguments(arguments.length, 5, { prefix }); - algorithm = webidl.converters.AlgorithmIdentifier(algorithm, { - prefix, - context: "Argument 1", - }); - baseKey = webidl.converters.CryptoKey(baseKey, { - prefix, - context: "Argument 2", - }); - derivedKeyType = webidl.converters.AlgorithmIdentifier(derivedKeyType, { - prefix, - context: "Argument 3", - }); - extractable = webidl.converters["boolean"](extractable, { - prefix, - context: "Argument 4", - }); - keyUsages = webidl.converters["sequence"](keyUsages, { - prefix, - context: "Argument 5", - }); + throw new SyntaxError("Invalid key usages"); + } + // 17. + return result; + } - // 2-3. - const normalizedAlgorithm = normalizeAlgorithm(algorithm, "deriveBits"); + /** + * @param {string} algorithm + * @param {CryptoKey} key + * @param {BufferSource} signature + * @param {BufferSource} data + * @returns {Promise} + */ + async verify(algorithm, key, signature, data) { + webidl.assertBranded(this, SubtleCryptoPrototype); + const prefix = "Failed to execute 'verify' on 'SubtleCrypto'"; + webidl.requiredArguments(arguments.length, 4, { prefix }); + algorithm = webidl.converters.AlgorithmIdentifier(algorithm, { + prefix, + context: "Argument 1", + }); + key = webidl.converters.CryptoKey(key, { + prefix, + context: "Argument 2", + }); + signature = webidl.converters.BufferSource(signature, { + prefix, + context: "Argument 3", + }); + data = webidl.converters.BufferSource(data, { + prefix, + context: "Argument 4", + }); - // 4-5. - const normalizedDerivedKeyAlgorithmImport = normalizeAlgorithm( - derivedKeyType, - "importKey", + // 2. + signature = copyBuffer(signature); + + // 3. + data = copyBuffer(data); + + const normalizedAlgorithm = normalizeAlgorithm(algorithm, "verify"); + + const handle = key[_handle]; + const keyData = WeakMapPrototypeGet(KEY_STORE, handle); + + if (normalizedAlgorithm.name !== key[_algorithm].name) { + throw new DOMException( + "Verifying algorithm doesn't match key algorithm.", + "InvalidAccessError", ); + } - // 6-7. - const normalizedDerivedKeyAlgorithmLength = normalizeAlgorithm( - derivedKeyType, - "get key length", + if (!ArrayPrototypeIncludes(key[_usages], "verify")) { + throw new DOMException( + "Key does not support the 'verify' operation.", + "InvalidAccessError", ); + } - // 8-10. + switch (normalizedAlgorithm.name) { + case "RSASSA-PKCS1-v1_5": { + if (key[_type] !== "public") { + throw new DOMException( + "Key type not supported", + "InvalidAccessError", + ); + } - // 11. - if (normalizedAlgorithm.name !== baseKey[_algorithm].name) { - throw new DOMException( - "Invalid algorithm name", - "InvalidAccessError", - ); + const hashAlgorithm = key[_algorithm].hash.name; + return await core.opAsync("op_crypto_verify_key", { + key: keyData, + algorithm: "RSASSA-PKCS1-v1_5", + hash: hashAlgorithm, + signature, + }, data); } + case "RSA-PSS": { + if (key[_type] !== "public") { + throw new DOMException( + "Key type not supported", + "InvalidAccessError", + ); + } - // 12. - if (!ArrayPrototypeIncludes(baseKey[_usages], "deriveKey")) { - throw new DOMException( - "baseKey usages does not contain `deriveKey`", - "InvalidAccessError", - ); + const hashAlgorithm = key[_algorithm].hash.name; + return await core.opAsync("op_crypto_verify_key", { + key: keyData, + algorithm: "RSA-PSS", + hash: hashAlgorithm, + signature, + }, data); } + case "HMAC": { + const hash = key[_algorithm].hash.name; + return await core.opAsync("op_crypto_verify_key", { + key: keyData, + algorithm: "HMAC", + hash, + signature, + }, data); + } + case "ECDSA": { + // 1. + if (key[_type] !== "public") { + throw new DOMException( + "Key type not supported", + "InvalidAccessError", + ); + } + // 2. + const hash = normalizedAlgorithm.hash.name; - // 13. - const length = getKeyLength(normalizedDerivedKeyAlgorithmLength); - - // 14. - const secret = await this.deriveBits( - normalizedAlgorithm, - baseKey, - length, - ); - - // 15. - const result = await this.importKey( - "raw", - secret, - normalizedDerivedKeyAlgorithmImport, - extractable, - keyUsages, - ); + // 3-8. + return await core.opAsync("op_crypto_verify_key", { + key: keyData, + algorithm: "ECDSA", + hash, + signature, + namedCurve: key[_algorithm].namedCurve, + }, data); + } + case "Ed25519": { + // 1. + if (key[_type] !== "public") { + throw new DOMException( + "Key type not supported", + "InvalidAccessError", + ); + } - // 16. - if ( - ArrayPrototypeIncludes(["private", "secret"], result[_type]) && - keyUsages.length == 0 - ) { - throw new SyntaxError("Invalid key usages"); + return ops.op_verify_ed25519(keyData, data, signature); } - // 17. - return result; } - /** - * @param {string} algorithm - * @param {CryptoKey} key - * @param {BufferSource} signature - * @param {BufferSource} data - * @returns {Promise} - */ - async verify(algorithm, key, signature, data) { - webidl.assertBranded(this, SubtleCryptoPrototype); - const prefix = "Failed to execute 'verify' on 'SubtleCrypto'"; - webidl.requiredArguments(arguments.length, 4, { prefix }); - algorithm = webidl.converters.AlgorithmIdentifier(algorithm, { - prefix, - context: "Argument 1", - }); - key = webidl.converters.CryptoKey(key, { - prefix, - context: "Argument 2", - }); - signature = webidl.converters.BufferSource(signature, { - prefix, - context: "Argument 3", - }); - data = webidl.converters.BufferSource(data, { - prefix, - context: "Argument 4", - }); + throw new TypeError("unreachable"); + } - // 2. - signature = copyBuffer(signature); + /** + * @param {string} algorithm + * @param {boolean} extractable + * @param {KeyUsage[]} keyUsages + * @returns {Promise} + */ + async wrapKey(format, key, wrappingKey, wrapAlgorithm) { + webidl.assertBranded(this, SubtleCryptoPrototype); + const prefix = "Failed to execute 'wrapKey' on 'SubtleCrypto'"; + webidl.requiredArguments(arguments.length, 4, { prefix }); + format = webidl.converters.KeyFormat(format, { + prefix, + context: "Argument 1", + }); + key = webidl.converters.CryptoKey(key, { + prefix, + context: "Argument 2", + }); + wrappingKey = webidl.converters.CryptoKey(wrappingKey, { + prefix, + context: "Argument 3", + }); + wrapAlgorithm = webidl.converters.AlgorithmIdentifier(wrapAlgorithm, { + prefix, + context: "Argument 4", + }); + let normalizedAlgorithm; + + try { + // 2. + normalizedAlgorithm = normalizeAlgorithm(wrapAlgorithm, "wrapKey"); + } catch (_) { // 3. - data = copyBuffer(data); + normalizedAlgorithm = normalizeAlgorithm(wrapAlgorithm, "encrypt"); + } - const normalizedAlgorithm = normalizeAlgorithm(algorithm, "verify"); + // 8. + if (normalizedAlgorithm.name !== wrappingKey[_algorithm].name) { + throw new DOMException( + "Wrapping algorithm doesn't match key algorithm.", + "InvalidAccessError", + ); + } - const handle = key[_handle]; - const keyData = WeakMapPrototypeGet(KEY_STORE, handle); + // 9. + if (!ArrayPrototypeIncludes(wrappingKey[_usages], "wrapKey")) { + throw new DOMException( + "Key does not support the 'wrapKey' operation.", + "InvalidAccessError", + ); + } - if (normalizedAlgorithm.name !== key[_algorithm].name) { - throw new DOMException( - "Verifying algorithm doesn't match key algorithm.", - "InvalidAccessError", - ); - } + // 10. NotSupportedError will be thrown in step 12. + // 11. + if (key[_extractable] === false) { + throw new DOMException( + "Key is not extractable", + "InvalidAccessError", + ); + } - if (!ArrayPrototypeIncludes(key[_usages], "verify")) { - throw new DOMException( - "Key does not support the 'verify' operation.", - "InvalidAccessError", - ); + // 12. + const exportedKey = await this.exportKey(format, key); + + let bytes; + // 13. + if (format !== "jwk") { + bytes = new Uint8Array(exportedKey); + } else { + const jwk = JSONStringify(exportedKey); + const ret = new Uint8Array(jwk.length); + for (let i = 0; i < jwk.length; i++) { + ret[i] = StringPrototypeCharCodeAt(jwk, i); } + bytes = ret; + } - switch (normalizedAlgorithm.name) { - case "RSASSA-PKCS1-v1_5": { - if (key[_type] !== "public") { - throw new DOMException( - "Key type not supported", - "InvalidAccessError", - ); - } - - const hashAlgorithm = key[_algorithm].hash.name; - return await core.opAsync("op_crypto_verify_key", { - key: keyData, - algorithm: "RSASSA-PKCS1-v1_5", - hash: hashAlgorithm, - signature, - }, data); - } - case "RSA-PSS": { - if (key[_type] !== "public") { - throw new DOMException( - "Key type not supported", - "InvalidAccessError", - ); - } + // 14-15. + if ( + supportedAlgorithms["wrapKey"][normalizedAlgorithm.name] !== undefined + ) { + const handle = wrappingKey[_handle]; + const keyData = WeakMapPrototypeGet(KEY_STORE, handle); - const hashAlgorithm = key[_algorithm].hash.name; - return await core.opAsync("op_crypto_verify_key", { - key: keyData, - algorithm: "RSA-PSS", - hash: hashAlgorithm, - signature, - }, data); - } - case "HMAC": { - const hash = key[_algorithm].hash.name; - return await core.opAsync("op_crypto_verify_key", { + switch (normalizedAlgorithm.name) { + case "AES-KW": { + const cipherText = await ops.op_crypto_wrap_key({ key: keyData, - algorithm: "HMAC", - hash, - signature, - }, data); - } - case "ECDSA": { - // 1. - if (key[_type] !== "public") { - throw new DOMException( - "Key type not supported", - "InvalidAccessError", - ); - } - // 2. - const hash = normalizedAlgorithm.hash.name; + algorithm: normalizedAlgorithm.name, + }, bytes); - // 3-8. - return await core.opAsync("op_crypto_verify_key", { - key: keyData, - algorithm: "ECDSA", - hash, - signature, - namedCurve: key[_algorithm].namedCurve, - }, data); + // 4. + return cipherText.buffer; } - case "Ed25519": { - // 1. - if (key[_type] !== "public") { - throw new DOMException( - "Key type not supported", - "InvalidAccessError", - ); - } - - return ops.op_verify_ed25519(keyData, data, signature); + default: { + throw new DOMException( + "Not implemented", + "NotSupportedError", + ); } } + } else if ( + supportedAlgorithms["encrypt"][normalizedAlgorithm.name] !== undefined + ) { + // must construct a new key, since keyUsages is ["wrapKey"] and not ["encrypt"] + return await encrypt( + normalizedAlgorithm, + constructKey( + wrappingKey[_type], + wrappingKey[_extractable], + ["encrypt"], + wrappingKey[_algorithm], + wrappingKey[_handle], + ), + bytes, + ); + } else { + throw new DOMException( + "Algorithm not supported", + "NotSupportedError", + ); + } + } + /** + * @param {string} format + * @param {BufferSource} wrappedKey + * @param {CryptoKey} unwrappingKey + * @param {AlgorithmIdentifier} unwrapAlgorithm + * @param {AlgorithmIdentifier} unwrappedKeyAlgorithm + * @param {boolean} extractable + * @param {KeyUsage[]} keyUsages + * @returns {Promise} + */ + async unwrapKey( + format, + wrappedKey, + unwrappingKey, + unwrapAlgorithm, + unwrappedKeyAlgorithm, + extractable, + keyUsages, + ) { + webidl.assertBranded(this, SubtleCryptoPrototype); + const prefix = "Failed to execute 'unwrapKey' on 'SubtleCrypto'"; + webidl.requiredArguments(arguments.length, 7, { prefix }); + format = webidl.converters.KeyFormat(format, { + prefix, + context: "Argument 1", + }); + wrappedKey = webidl.converters.BufferSource(wrappedKey, { + prefix, + context: "Argument 2", + }); + unwrappingKey = webidl.converters.CryptoKey(unwrappingKey, { + prefix, + context: "Argument 3", + }); + unwrapAlgorithm = webidl.converters.AlgorithmIdentifier(unwrapAlgorithm, { + prefix, + context: "Argument 4", + }); + unwrappedKeyAlgorithm = webidl.converters.AlgorithmIdentifier( + unwrappedKeyAlgorithm, + { + prefix, + context: "Argument 5", + }, + ); + extractable = webidl.converters.boolean(extractable, { + prefix, + context: "Argument 6", + }); + keyUsages = webidl.converters["sequence"](keyUsages, { + prefix, + context: "Argument 7", + }); - throw new TypeError("unreachable"); - } + // 2. + wrappedKey = copyBuffer(wrappedKey); - /** - * @param {string} algorithm - * @param {boolean} extractable - * @param {KeyUsage[]} keyUsages - * @returns {Promise} - */ - async wrapKey(format, key, wrappingKey, wrapAlgorithm) { - webidl.assertBranded(this, SubtleCryptoPrototype); - const prefix = "Failed to execute 'wrapKey' on 'SubtleCrypto'"; - webidl.requiredArguments(arguments.length, 4, { prefix }); - format = webidl.converters.KeyFormat(format, { - prefix, - context: "Argument 1", - }); - key = webidl.converters.CryptoKey(key, { - prefix, - context: "Argument 2", - }); - wrappingKey = webidl.converters.CryptoKey(wrappingKey, { - prefix, - context: "Argument 3", - }); - wrapAlgorithm = webidl.converters.AlgorithmIdentifier(wrapAlgorithm, { - prefix, - context: "Argument 4", - }); + let normalizedAlgorithm; - let normalizedAlgorithm; + try { + // 3. + normalizedAlgorithm = normalizeAlgorithm(unwrapAlgorithm, "unwrapKey"); + } catch (_) { + // 4. + normalizedAlgorithm = normalizeAlgorithm(unwrapAlgorithm, "decrypt"); + } - try { - // 2. - normalizedAlgorithm = normalizeAlgorithm(wrapAlgorithm, "wrapKey"); - } catch (_) { - // 3. - normalizedAlgorithm = normalizeAlgorithm(wrapAlgorithm, "encrypt"); - } + // 6. + const normalizedKeyAlgorithm = normalizeAlgorithm( + unwrappedKeyAlgorithm, + "importKey", + ); - // 8. - if (normalizedAlgorithm.name !== wrappingKey[_algorithm].name) { - throw new DOMException( - "Wrapping algorithm doesn't match key algorithm.", - "InvalidAccessError", - ); - } + // 11. + if (normalizedAlgorithm.name !== unwrappingKey[_algorithm].name) { + throw new DOMException( + "Unwrapping algorithm doesn't match key algorithm.", + "InvalidAccessError", + ); + } - // 9. - if (!ArrayPrototypeIncludes(wrappingKey[_usages], "wrapKey")) { - throw new DOMException( - "Key does not support the 'wrapKey' operation.", - "InvalidAccessError", - ); - } + // 12. + if (!ArrayPrototypeIncludes(unwrappingKey[_usages], "unwrapKey")) { + throw new DOMException( + "Key does not support the 'unwrapKey' operation.", + "InvalidAccessError", + ); + } - // 10. NotSupportedError will be thrown in step 12. - // 11. - if (key[_extractable] === false) { - throw new DOMException( - "Key is not extractable", - "InvalidAccessError", - ); - } + // 13. + let key; + if ( + supportedAlgorithms["unwrapKey"][normalizedAlgorithm.name] !== undefined + ) { + const handle = unwrappingKey[_handle]; + const keyData = WeakMapPrototypeGet(KEY_STORE, handle); - // 12. - const exportedKey = await this.exportKey(format, key); + switch (normalizedAlgorithm.name) { + case "AES-KW": { + const plainText = await ops.op_crypto_unwrap_key({ + key: keyData, + algorithm: normalizedAlgorithm.name, + }, wrappedKey); - let bytes; - // 13. - if (format !== "jwk") { - bytes = new Uint8Array(exportedKey); - } else { - const jwk = JSONStringify(exportedKey); - const ret = new Uint8Array(jwk.length); - for (let i = 0; i < jwk.length; i++) { - ret[i] = StringPrototypeCharCodeAt(jwk, i); + // 4. + key = plainText.buffer; + break; + } + default: { + throw new DOMException( + "Not implemented", + "NotSupportedError", + ); } - bytes = ret; } + } else if ( + supportedAlgorithms["decrypt"][normalizedAlgorithm.name] !== undefined + ) { + // must construct a new key, since keyUsages is ["unwrapKey"] and not ["decrypt"] + key = await this.decrypt( + normalizedAlgorithm, + constructKey( + unwrappingKey[_type], + unwrappingKey[_extractable], + ["decrypt"], + unwrappingKey[_algorithm], + unwrappingKey[_handle], + ), + wrappedKey, + ); + } else { + throw new DOMException( + "Algorithm not supported", + "NotSupportedError", + ); + } - // 14-15. - if ( - supportedAlgorithms["wrapKey"][normalizedAlgorithm.name] !== undefined - ) { - const handle = wrappingKey[_handle]; - const keyData = WeakMapPrototypeGet(KEY_STORE, handle); - - switch (normalizedAlgorithm.name) { - case "AES-KW": { - const cipherText = await ops.op_crypto_wrap_key({ - key: keyData, - algorithm: normalizedAlgorithm.name, - }, bytes); - - // 4. - return cipherText.buffer; - } - default: { - throw new DOMException( - "Not implemented", - "NotSupportedError", - ); - } - } - } else if ( - supportedAlgorithms["encrypt"][normalizedAlgorithm.name] !== undefined - ) { - // must construct a new key, since keyUsages is ["wrapKey"] and not ["encrypt"] - return await encrypt( - normalizedAlgorithm, - constructKey( - wrappingKey[_type], - wrappingKey[_extractable], - ["encrypt"], - wrappingKey[_algorithm], - wrappingKey[_handle], - ), - bytes, - ); - } else { - throw new DOMException( - "Algorithm not supported", - "NotSupportedError", - ); + let bytes; + // 14. + if (format !== "jwk") { + bytes = key; + } else { + const k = new Uint8Array(key); + let str = ""; + for (let i = 0; i < k.length; i++) { + str += StringFromCharCode(k[i]); } + bytes = JSONParse(str); } - /** - * @param {string} format - * @param {BufferSource} wrappedKey - * @param {CryptoKey} unwrappingKey - * @param {AlgorithmIdentifier} unwrapAlgorithm - * @param {AlgorithmIdentifier} unwrappedKeyAlgorithm - * @param {boolean} extractable - * @param {KeyUsage[]} keyUsages - * @returns {Promise} - */ - async unwrapKey( + + // 15. + const result = await this.importKey( format, - wrappedKey, - unwrappingKey, - unwrapAlgorithm, - unwrappedKeyAlgorithm, + bytes, + normalizedKeyAlgorithm, extractable, keyUsages, + ); + // 16. + if ( + (result[_type] == "secret" || result[_type] == "private") && + keyUsages.length == 0 ) { - webidl.assertBranded(this, SubtleCryptoPrototype); - const prefix = "Failed to execute 'unwrapKey' on 'SubtleCrypto'"; - webidl.requiredArguments(arguments.length, 7, { prefix }); - format = webidl.converters.KeyFormat(format, { - prefix, - context: "Argument 1", - }); - wrappedKey = webidl.converters.BufferSource(wrappedKey, { - prefix, - context: "Argument 2", - }); - unwrappingKey = webidl.converters.CryptoKey(unwrappingKey, { - prefix, - context: "Argument 3", - }); - unwrapAlgorithm = webidl.converters.AlgorithmIdentifier(unwrapAlgorithm, { - prefix, - context: "Argument 4", - }); - unwrappedKeyAlgorithm = webidl.converters.AlgorithmIdentifier( - unwrappedKeyAlgorithm, - { - prefix, - context: "Argument 5", - }, - ); - extractable = webidl.converters.boolean(extractable, { - prefix, - context: "Argument 6", - }); - keyUsages = webidl.converters["sequence"](keyUsages, { - prefix, - context: "Argument 7", - }); - - // 2. - wrappedKey = copyBuffer(wrappedKey); + throw new SyntaxError("Invalid key type."); + } + // 17. + result[_extractable] = extractable; + // 18. + result[_usages] = usageIntersection(keyUsages, recognisedUsages); + // 19. + return result; + } - let normalizedAlgorithm; + /** + * @param {string} algorithm + * @param {boolean} extractable + * @param {KeyUsage[]} keyUsages + * @returns {Promise} + */ + async generateKey(algorithm, extractable, keyUsages) { + webidl.assertBranded(this, SubtleCryptoPrototype); + const prefix = "Failed to execute 'generateKey' on 'SubtleCrypto'"; + webidl.requiredArguments(arguments.length, 3, { prefix }); + algorithm = webidl.converters.AlgorithmIdentifier(algorithm, { + prefix, + context: "Argument 1", + }); + extractable = webidl.converters["boolean"](extractable, { + prefix, + context: "Argument 2", + }); + keyUsages = webidl.converters["sequence"](keyUsages, { + prefix, + context: "Argument 3", + }); - try { - // 3. - normalizedAlgorithm = normalizeAlgorithm(unwrapAlgorithm, "unwrapKey"); - } catch (_) { - // 4. - normalizedAlgorithm = normalizeAlgorithm(unwrapAlgorithm, "decrypt"); - } + const usages = keyUsages; - // 6. - const normalizedKeyAlgorithm = normalizeAlgorithm( - unwrappedKeyAlgorithm, - "importKey", - ); + const normalizedAlgorithm = normalizeAlgorithm(algorithm, "generateKey"); + const result = await generateKey( + normalizedAlgorithm, + extractable, + usages, + ); - // 11. - if (normalizedAlgorithm.name !== unwrappingKey[_algorithm].name) { - throw new DOMException( - "Unwrapping algorithm doesn't match key algorithm.", - "InvalidAccessError", - ); + if (ObjectPrototypeIsPrototypeOf(CryptoKeyPrototype, result)) { + const type = result[_type]; + if ((type === "secret" || type === "private") && usages.length === 0) { + throw new DOMException("Invalid key usages", "SyntaxError"); } - - // 12. - if (!ArrayPrototypeIncludes(unwrappingKey[_usages], "unwrapKey")) { - throw new DOMException( - "Key does not support the 'unwrapKey' operation.", - "InvalidAccessError", - ); + } else if ( + ObjectPrototypeIsPrototypeOf(CryptoKeyPrototype, result.privateKey) + ) { + if (result.privateKey[_usages].length === 0) { + throw new DOMException("Invalid key usages", "SyntaxError"); } + } - // 13. - let key; - if ( - supportedAlgorithms["unwrapKey"][normalizedAlgorithm.name] !== undefined - ) { - const handle = unwrappingKey[_handle]; - const keyData = WeakMapPrototypeGet(KEY_STORE, handle); + return result; + } +} +const SubtleCryptoPrototype = SubtleCrypto.prototype; - switch (normalizedAlgorithm.name) { - case "AES-KW": { - const plainText = await ops.op_crypto_unwrap_key({ - key: keyData, - algorithm: normalizedAlgorithm.name, - }, wrappedKey); +async function generateKey(normalizedAlgorithm, extractable, usages) { + const algorithmName = normalizedAlgorithm.name; - // 4. - key = plainText.buffer; - break; - } - default: { - throw new DOMException( - "Not implemented", - "NotSupportedError", - ); - } - } - } else if ( - supportedAlgorithms["decrypt"][normalizedAlgorithm.name] !== undefined + switch (algorithmName) { + case "RSASSA-PKCS1-v1_5": + case "RSA-PSS": { + // 1. + if ( + ArrayPrototypeFind( + usages, + (u) => !ArrayPrototypeIncludes(["sign", "verify"], u), + ) !== undefined ) { - // must construct a new key, since keyUsages is ["unwrapKey"] and not ["decrypt"] - key = await this.decrypt( - normalizedAlgorithm, - constructKey( - unwrappingKey[_type], - unwrappingKey[_extractable], - ["decrypt"], - unwrappingKey[_algorithm], - unwrappingKey[_handle], - ), - wrappedKey, - ); - } else { - throw new DOMException( - "Algorithm not supported", - "NotSupportedError", - ); - } - - let bytes; - // 14. - if (format !== "jwk") { - bytes = key; - } else { - const k = new Uint8Array(key); - let str = ""; - for (let i = 0; i < k.length; i++) { - str += StringFromCharCode(k[i]); - } - bytes = JSONParse(str); + throw new DOMException("Invalid key usages", "SyntaxError"); } - // 15. - const result = await this.importKey( - format, - bytes, - normalizedKeyAlgorithm, - extractable, - keyUsages, + // 2. + const keyData = await core.opAsync( + "op_crypto_generate_key", + { + algorithm: "RSA", + modulusLength: normalizedAlgorithm.modulusLength, + publicExponent: normalizedAlgorithm.publicExponent, + }, ); - // 16. - if ( - (result[_type] == "secret" || result[_type] == "private") && - keyUsages.length == 0 - ) { - throw new SyntaxError("Invalid key type."); - } - // 17. - result[_extractable] = extractable; - // 18. - result[_usages] = usageIntersection(keyUsages, recognisedUsages); - // 19. - return result; - } - - /** - * @param {string} algorithm - * @param {boolean} extractable - * @param {KeyUsage[]} keyUsages - * @returns {Promise} - */ - async generateKey(algorithm, extractable, keyUsages) { - webidl.assertBranded(this, SubtleCryptoPrototype); - const prefix = "Failed to execute 'generateKey' on 'SubtleCrypto'"; - webidl.requiredArguments(arguments.length, 3, { prefix }); - algorithm = webidl.converters.AlgorithmIdentifier(algorithm, { - prefix, - context: "Argument 1", - }); - extractable = webidl.converters["boolean"](extractable, { - prefix, - context: "Argument 2", - }); - keyUsages = webidl.converters["sequence"](keyUsages, { - prefix, - context: "Argument 3", + const handle = {}; + WeakMapPrototypeSet(KEY_STORE, handle, { + type: "private", + data: keyData, }); - const usages = keyUsages; + // 4-8. + const algorithm = { + name: algorithmName, + modulusLength: normalizedAlgorithm.modulusLength, + publicExponent: normalizedAlgorithm.publicExponent, + hash: normalizedAlgorithm.hash, + }; + + // 9-13. + const publicKey = constructKey( + "public", + true, + usageIntersection(usages, ["verify"]), + algorithm, + handle, + ); - const normalizedAlgorithm = normalizeAlgorithm(algorithm, "generateKey"); - const result = await generateKey( - normalizedAlgorithm, + // 14-18. + const privateKey = constructKey( + "private", extractable, - usages, + usageIntersection(usages, ["sign"]), + algorithm, + handle, ); - if (ObjectPrototypeIsPrototypeOf(CryptoKeyPrototype, result)) { - const type = result[_type]; - if ((type === "secret" || type === "private") && usages.length === 0) { - throw new DOMException("Invalid key usages", "SyntaxError"); - } - } else if ( - ObjectPrototypeIsPrototypeOf(CryptoKeyPrototype, result.privateKey) + // 19-22. + return { publicKey, privateKey }; + } + case "RSA-OAEP": { + if ( + ArrayPrototypeFind( + usages, + (u) => + !ArrayPrototypeIncludes([ + "encrypt", + "decrypt", + "wrapKey", + "unwrapKey", + ], u), + ) !== undefined ) { - if (result.privateKey[_usages].length === 0) { - throw new DOMException("Invalid key usages", "SyntaxError"); - } + throw new DOMException("Invalid key usages", "SyntaxError"); } - return result; - } - } - const SubtleCryptoPrototype = SubtleCrypto.prototype; - - async function generateKey(normalizedAlgorithm, extractable, usages) { - const algorithmName = normalizedAlgorithm.name; - - switch (algorithmName) { - case "RSASSA-PKCS1-v1_5": - case "RSA-PSS": { - // 1. - if ( - ArrayPrototypeFind( - usages, - (u) => !ArrayPrototypeIncludes(["sign", "verify"], u), - ) !== undefined - ) { - throw new DOMException("Invalid key usages", "SyntaxError"); - } - - // 2. - const keyData = await core.opAsync( - "op_crypto_generate_key", - { - algorithm: "RSA", - modulusLength: normalizedAlgorithm.modulusLength, - publicExponent: normalizedAlgorithm.publicExponent, - }, - ); - const handle = {}; - WeakMapPrototypeSet(KEY_STORE, handle, { - type: "private", - data: keyData, - }); - - // 4-8. - const algorithm = { - name: algorithmName, + // 2. + const keyData = await core.opAsync( + "op_crypto_generate_key", + { + algorithm: "RSA", modulusLength: normalizedAlgorithm.modulusLength, publicExponent: normalizedAlgorithm.publicExponent, - hash: normalizedAlgorithm.hash, - }; + }, + ); + const handle = {}; + WeakMapPrototypeSet(KEY_STORE, handle, { + type: "private", + data: keyData, + }); - // 9-13. - const publicKey = constructKey( - "public", - true, - usageIntersection(usages, ["verify"]), - algorithm, - handle, - ); + // 4-8. + const algorithm = { + name: algorithmName, + modulusLength: normalizedAlgorithm.modulusLength, + publicExponent: normalizedAlgorithm.publicExponent, + hash: normalizedAlgorithm.hash, + }; + + // 9-13. + const publicKey = constructKey( + "public", + true, + usageIntersection(usages, ["encrypt", "wrapKey"]), + algorithm, + handle, + ); - // 14-18. - const privateKey = constructKey( - "private", - extractable, - usageIntersection(usages, ["sign"]), - algorithm, - handle, - ); + // 14-18. + const privateKey = constructKey( + "private", + extractable, + usageIntersection(usages, ["decrypt", "unwrapKey"]), + algorithm, + handle, + ); + + // 19-22. + return { publicKey, privateKey }; + } + case "ECDSA": { + const namedCurve = normalizedAlgorithm.namedCurve; - // 19-22. - return { publicKey, privateKey }; + // 1. + if ( + ArrayPrototypeFind( + usages, + (u) => !ArrayPrototypeIncludes(["sign", "verify"], u), + ) !== undefined + ) { + throw new DOMException("Invalid key usages", "SyntaxError"); } - case "RSA-OAEP": { - if ( - ArrayPrototypeFind( - usages, - (u) => - !ArrayPrototypeIncludes([ - "encrypt", - "decrypt", - "wrapKey", - "unwrapKey", - ], u), - ) !== undefined - ) { - throw new DOMException("Invalid key usages", "SyntaxError"); - } - // 2. - const keyData = await core.opAsync( - "op_crypto_generate_key", - { - algorithm: "RSA", - modulusLength: normalizedAlgorithm.modulusLength, - publicExponent: normalizedAlgorithm.publicExponent, - }, - ); - const handle = {}; + // 2-3. + const handle = {}; + if ( + ArrayPrototypeIncludes( + supportedNamedCurves, + namedCurve, + ) + ) { + const keyData = await core.opAsync("op_crypto_generate_key", { + algorithm: "EC", + namedCurve, + }); WeakMapPrototypeSet(KEY_STORE, handle, { type: "private", data: keyData, }); + } else { + throw new DOMException("Curve not supported", "NotSupportedError"); + } - // 4-8. - const algorithm = { - name: algorithmName, - modulusLength: normalizedAlgorithm.modulusLength, - publicExponent: normalizedAlgorithm.publicExponent, - hash: normalizedAlgorithm.hash, - }; + // 4-6. + const algorithm = { + name: algorithmName, + namedCurve, + }; + + // 7-11. + const publicKey = constructKey( + "public", + true, + usageIntersection(usages, ["verify"]), + algorithm, + handle, + ); - // 9-13. - const publicKey = constructKey( - "public", - true, - usageIntersection(usages, ["encrypt", "wrapKey"]), - algorithm, - handle, - ); + // 12-16. + const privateKey = constructKey( + "private", + extractable, + usageIntersection(usages, ["sign"]), + algorithm, + handle, + ); - // 14-18. - const privateKey = constructKey( - "private", - extractable, - usageIntersection(usages, ["decrypt", "unwrapKey"]), - algorithm, - handle, - ); + // 17-20. + return { publicKey, privateKey }; + } + case "ECDH": { + const namedCurve = normalizedAlgorithm.namedCurve; - // 19-22. - return { publicKey, privateKey }; + // 1. + if ( + ArrayPrototypeFind( + usages, + (u) => !ArrayPrototypeIncludes(["deriveKey", "deriveBits"], u), + ) !== undefined + ) { + throw new DOMException("Invalid key usages", "SyntaxError"); } - case "ECDSA": { - const namedCurve = normalizedAlgorithm.namedCurve; - - // 1. - if ( - ArrayPrototypeFind( - usages, - (u) => !ArrayPrototypeIncludes(["sign", "verify"], u), - ) !== undefined - ) { - throw new DOMException("Invalid key usages", "SyntaxError"); - } - - // 2-3. - const handle = {}; - if ( - ArrayPrototypeIncludes( - supportedNamedCurves, - namedCurve, - ) - ) { - const keyData = await core.opAsync("op_crypto_generate_key", { - algorithm: "EC", - namedCurve, - }); - WeakMapPrototypeSet(KEY_STORE, handle, { - type: "private", - data: keyData, - }); - } else { - throw new DOMException("Curve not supported", "NotSupportedError"); - } - // 4-6. - const algorithm = { - name: algorithmName, + // 2-3. + const handle = {}; + if ( + ArrayPrototypeIncludes( + supportedNamedCurves, namedCurve, - }; + ) + ) { + const keyData = await core.opAsync("op_crypto_generate_key", { + algorithm: "EC", + namedCurve, + }); + WeakMapPrototypeSet(KEY_STORE, handle, { + type: "private", + data: keyData, + }); + } else { + throw new DOMException("Curve not supported", "NotSupportedError"); + } - // 7-11. - const publicKey = constructKey( - "public", - true, - usageIntersection(usages, ["verify"]), - algorithm, - handle, - ); + // 4-6. + const algorithm = { + name: algorithmName, + namedCurve, + }; + + // 7-11. + const publicKey = constructKey( + "public", + true, + usageIntersection(usages, []), + algorithm, + handle, + ); - // 12-16. - const privateKey = constructKey( - "private", - extractable, - usageIntersection(usages, ["sign"]), - algorithm, - handle, - ); + // 12-16. + const privateKey = constructKey( + "private", + extractable, + usageIntersection(usages, ["deriveKey", "deriveBits"]), + algorithm, + handle, + ); - // 17-20. - return { publicKey, privateKey }; + // 17-20. + return { publicKey, privateKey }; + } + case "AES-CTR": + case "AES-CBC": + case "AES-GCM": { + // 1. + if ( + ArrayPrototypeFind( + usages, + (u) => + !ArrayPrototypeIncludes([ + "encrypt", + "decrypt", + "wrapKey", + "unwrapKey", + ], u), + ) !== undefined + ) { + throw new DOMException("Invalid key usages", "SyntaxError"); } - case "ECDH": { - const namedCurve = normalizedAlgorithm.namedCurve; - - // 1. - if ( - ArrayPrototypeFind( - usages, - (u) => !ArrayPrototypeIncludes(["deriveKey", "deriveBits"], u), - ) !== undefined - ) { - throw new DOMException("Invalid key usages", "SyntaxError"); - } - - // 2-3. - const handle = {}; - if ( - ArrayPrototypeIncludes( - supportedNamedCurves, - namedCurve, - ) - ) { - const keyData = await core.opAsync("op_crypto_generate_key", { - algorithm: "EC", - namedCurve, - }); - WeakMapPrototypeSet(KEY_STORE, handle, { - type: "private", - data: keyData, - }); - } else { - throw new DOMException("Curve not supported", "NotSupportedError"); - } - // 4-6. - const algorithm = { - name: algorithmName, - namedCurve, - }; + return generateKeyAES(normalizedAlgorithm, extractable, usages); + } + case "AES-KW": { + // 1. + if ( + ArrayPrototypeFind( + usages, + (u) => !ArrayPrototypeIncludes(["wrapKey", "unwrapKey"], u), + ) !== undefined + ) { + throw new DOMException("Invalid key usages", "SyntaxError"); + } - // 7-11. - const publicKey = constructKey( - "public", - true, - usageIntersection(usages, []), - algorithm, - handle, - ); + return generateKeyAES(normalizedAlgorithm, extractable, usages); + } + case "X25519": { + if ( + ArrayPrototypeFind( + usages, + (u) => !ArrayPrototypeIncludes(["deriveKey", "deriveBits"], u), + ) !== undefined + ) { + throw new DOMException("Invalid key usages", "SyntaxError"); + } + const privateKeyData = new Uint8Array(32); + const publicKeyData = new Uint8Array(32); + ops.op_generate_x25519_keypair(privateKeyData, publicKeyData); + + const handle = {}; + WeakMapPrototypeSet(KEY_STORE, handle, privateKeyData); + + const publicHandle = {}; + WeakMapPrototypeSet(KEY_STORE, publicHandle, publicKeyData); + + const algorithm = { + name: algorithmName, + }; + + const publicKey = constructKey( + "public", + true, + usageIntersection(usages, []), + algorithm, + publicHandle, + ); - // 12-16. - const privateKey = constructKey( - "private", - extractable, - usageIntersection(usages, ["deriveKey", "deriveBits"]), - algorithm, - handle, - ); + const privateKey = constructKey( + "private", + extractable, + usageIntersection(usages, ["deriveKey", "deriveBits"]), + algorithm, + handle, + ); - // 17-20. - return { publicKey, privateKey }; + return { publicKey, privateKey }; + } + case "Ed25519": { + if ( + ArrayPrototypeFind( + usages, + (u) => !ArrayPrototypeIncludes(["sign", "verify"], u), + ) !== undefined + ) { + throw new DOMException("Invalid key usages", "SyntaxError"); } - case "AES-CTR": - case "AES-CBC": - case "AES-GCM": { - // 1. - if ( - ArrayPrototypeFind( - usages, - (u) => - !ArrayPrototypeIncludes([ - "encrypt", - "decrypt", - "wrapKey", - "unwrapKey", - ], u), - ) !== undefined - ) { - throw new DOMException("Invalid key usages", "SyntaxError"); - } - return generateKeyAES(normalizedAlgorithm, extractable, usages); + const ED25519_SEED_LEN = 32; + const ED25519_PUBLIC_KEY_LEN = 32; + const privateKeyData = new Uint8Array(ED25519_SEED_LEN); + const publicKeyData = new Uint8Array(ED25519_PUBLIC_KEY_LEN); + if ( + !ops.op_generate_ed25519_keypair(privateKeyData, publicKeyData) + ) { + throw new DOMException("Failed to generate key", "OperationError"); } - case "AES-KW": { - // 1. - if ( - ArrayPrototypeFind( - usages, - (u) => !ArrayPrototypeIncludes(["wrapKey", "unwrapKey"], u), - ) !== undefined - ) { - throw new DOMException("Invalid key usages", "SyntaxError"); - } - return generateKeyAES(normalizedAlgorithm, extractable, usages); - } - case "X25519": { - if ( - ArrayPrototypeFind( - usages, - (u) => !ArrayPrototypeIncludes(["deriveKey", "deriveBits"], u), - ) !== undefined - ) { - throw new DOMException("Invalid key usages", "SyntaxError"); - } - const privateKeyData = new Uint8Array(32); - const publicKeyData = new Uint8Array(32); - ops.op_generate_x25519_keypair(privateKeyData, publicKeyData); + const handle = {}; + WeakMapPrototypeSet(KEY_STORE, handle, privateKeyData); - const handle = {}; - WeakMapPrototypeSet(KEY_STORE, handle, privateKeyData); + const publicHandle = {}; + WeakMapPrototypeSet(KEY_STORE, publicHandle, publicKeyData); - const publicHandle = {}; - WeakMapPrototypeSet(KEY_STORE, publicHandle, publicKeyData); + const algorithm = { + name: algorithmName, + }; - const algorithm = { - name: algorithmName, - }; + const publicKey = constructKey( + "public", + true, + usageIntersection(usages, ["verify"]), + algorithm, + publicHandle, + ); - const publicKey = constructKey( - "public", - true, - usageIntersection(usages, []), - algorithm, - publicHandle, - ); + const privateKey = constructKey( + "private", + extractable, + usageIntersection(usages, ["sign"]), + algorithm, + handle, + ); - const privateKey = constructKey( - "private", - extractable, - usageIntersection(usages, ["deriveKey", "deriveBits"]), - algorithm, - handle, - ); + return { publicKey, privateKey }; + } + case "HMAC": { + // 1. + if ( + ArrayPrototypeFind( + usages, + (u) => !ArrayPrototypeIncludes(["sign", "verify"], u), + ) !== undefined + ) { + throw new DOMException("Invalid key usages", "SyntaxError"); + } - return { publicKey, privateKey }; + // 2. + let length; + if (normalizedAlgorithm.length === undefined) { + length = null; + } else if (normalizedAlgorithm.length !== 0) { + length = normalizedAlgorithm.length; + } else { + throw new DOMException("Invalid length", "OperationError"); } - case "Ed25519": { - if ( - ArrayPrototypeFind( - usages, - (u) => !ArrayPrototypeIncludes(["sign", "verify"], u), - ) !== undefined - ) { - throw new DOMException("Invalid key usages", "SyntaxError"); - } - const ED25519_SEED_LEN = 32; - const ED25519_PUBLIC_KEY_LEN = 32; - const privateKeyData = new Uint8Array(ED25519_SEED_LEN); - const publicKeyData = new Uint8Array(ED25519_PUBLIC_KEY_LEN); - if ( - !ops.op_generate_ed25519_keypair(privateKeyData, publicKeyData) - ) { - throw new DOMException("Failed to generate key", "OperationError"); - } + // 3-4. + const keyData = await core.opAsync("op_crypto_generate_key", { + algorithm: "HMAC", + hash: normalizedAlgorithm.hash.name, + length, + }); + const handle = {}; + WeakMapPrototypeSet(KEY_STORE, handle, { + type: "secret", + data: keyData, + }); - const handle = {}; - WeakMapPrototypeSet(KEY_STORE, handle, privateKeyData); + // 6-10. + const algorithm = { + name: algorithmName, + hash: { + name: normalizedAlgorithm.hash.name, + }, + length: keyData.byteLength * 8, + }; - const publicHandle = {}; - WeakMapPrototypeSet(KEY_STORE, publicHandle, publicKeyData); + // 5, 11-13. + const key = constructKey( + "secret", + extractable, + usages, + algorithm, + handle, + ); - const algorithm = { - name: algorithmName, - }; + // 14. + return key; + } + } +} + +function importKeyEd25519( + format, + keyData, + extractable, + keyUsages, +) { + switch (format) { + case "raw": { + // 1. + if ( + ArrayPrototypeFind( + keyUsages, + (u) => !ArrayPrototypeIncludes(["verify"], u), + ) !== undefined + ) { + throw new DOMException("Invalid key usages", "SyntaxError"); + } - const publicKey = constructKey( - "public", - true, - usageIntersection(usages, ["verify"]), - algorithm, - publicHandle, - ); + const handle = {}; + WeakMapPrototypeSet(KEY_STORE, handle, keyData); - const privateKey = constructKey( - "private", - extractable, - usageIntersection(usages, ["sign"]), - algorithm, - handle, - ); + // 2-3. + const algorithm = { + name: "Ed25519", + }; - return { publicKey, privateKey }; + // 4-6. + return constructKey( + "public", + extractable, + usageIntersection(keyUsages, recognisedUsages), + algorithm, + handle, + ); + } + case "spki": { + // 1. + if ( + ArrayPrototypeFind( + keyUsages, + (u) => !ArrayPrototypeIncludes(["verify"], u), + ) !== undefined + ) { + throw new DOMException("Invalid key usages", "SyntaxError"); } - case "HMAC": { - // 1. - if ( - ArrayPrototypeFind( - usages, - (u) => !ArrayPrototypeIncludes(["sign", "verify"], u), - ) !== undefined - ) { - throw new DOMException("Invalid key usages", "SyntaxError"); - } - // 2. - let length; - if (normalizedAlgorithm.length === undefined) { - length = null; - } else if (normalizedAlgorithm.length !== 0) { - length = normalizedAlgorithm.length; - } else { - throw new DOMException("Invalid length", "OperationError"); - } + const publicKeyData = new Uint8Array(32); + if (!ops.op_import_spki_ed25519(keyData, publicKeyData)) { + throw new DOMException("Invalid key data", "DataError"); + } - // 3-4. - const keyData = await core.opAsync("op_crypto_generate_key", { - algorithm: "HMAC", - hash: normalizedAlgorithm.hash.name, - length, - }); - const handle = {}; - WeakMapPrototypeSet(KEY_STORE, handle, { - type: "secret", - data: keyData, - }); + const handle = {}; + WeakMapPrototypeSet(KEY_STORE, handle, publicKeyData); - // 6-10. - const algorithm = { - name: algorithmName, - hash: { - name: normalizedAlgorithm.hash.name, - }, - length: keyData.byteLength * 8, - }; + const algorithm = { + name: "Ed25519", + }; - // 5, 11-13. - const key = constructKey( - "secret", - extractable, - usages, - algorithm, - handle, - ); + return constructKey( + "public", + extractable, + usageIntersection(keyUsages, recognisedUsages), + algorithm, + handle, + ); + } + case "pkcs8": { + // 1. + if ( + ArrayPrototypeFind( + keyUsages, + (u) => !ArrayPrototypeIncludes(["sign"], u), + ) !== undefined + ) { + throw new DOMException("Invalid key usages", "SyntaxError"); + } - // 14. - return key; + const privateKeyData = new Uint8Array(32); + if (!ops.op_import_pkcs8_ed25519(keyData, privateKeyData)) { + throw new DOMException("Invalid key data", "DataError"); } + + const handle = {}; + WeakMapPrototypeSet(KEY_STORE, handle, privateKeyData); + + const algorithm = { + name: "Ed25519", + }; + + return constructKey( + "private", + extractable, + usageIntersection(keyUsages, recognisedUsages), + algorithm, + handle, + ); } - } + case "jwk": { + // 1. + const jwk = keyData; - function importKeyEd25519( - format, - keyData, - extractable, - keyUsages, - ) { - switch (format) { - case "raw": { - // 1. + // 2. + if (jwk.d !== undefined) { if ( ArrayPrototypeFind( keyUsages, - (u) => !ArrayPrototypeIncludes(["verify"], u), + (u) => + !ArrayPrototypeIncludes( + ["sign"], + u, + ), ) !== undefined ) { throw new DOMException("Invalid key usages", "SyntaxError"); } - - const handle = {}; - WeakMapPrototypeSet(KEY_STORE, handle, keyData); - - // 2-3. - const algorithm = { - name: "Ed25519", - }; - - // 4-6. - return constructKey( - "public", - extractable, - usageIntersection(keyUsages, recognisedUsages), - algorithm, - handle, - ); - } - case "spki": { - // 1. + } else { if ( ArrayPrototypeFind( keyUsages, - (u) => !ArrayPrototypeIncludes(["verify"], u), + (u) => + !ArrayPrototypeIncludes( + ["verify"], + u, + ), ) !== undefined ) { throw new DOMException("Invalid key usages", "SyntaxError"); } + } - const publicKeyData = new Uint8Array(32); - if (!ops.op_import_spki_ed25519(keyData, publicKeyData)) { - throw new DOMException("Invalid key data", "DataError"); - } + // 3. + if (jwk.kty !== "OKP") { + throw new DOMException("Invalid key type", "DataError"); + } - const handle = {}; - WeakMapPrototypeSet(KEY_STORE, handle, publicKeyData); + // 4. + if (jwk.crv !== "Ed25519") { + throw new DOMException("Invalid curve", "DataError"); + } - const algorithm = { - name: "Ed25519", - }; + // 5. + if (jwk.alg !== undefined && jwk.alg !== "EdDSA") { + throw new DOMException("Invalid algorithm", "DataError"); + } - return constructKey( - "public", - extractable, - usageIntersection(keyUsages, recognisedUsages), - algorithm, - handle, - ); + // 6. + if ( + keyUsages.length > 0 && jwk.use !== undefined && jwk.use !== "sig" + ) { + throw new DOMException("Invalid key usage", "DataError"); } - case "pkcs8": { - // 1. + + // 7. + if (jwk.key_ops !== undefined) { if ( ArrayPrototypeFind( - keyUsages, - (u) => !ArrayPrototypeIncludes(["sign"], u), + jwk.key_ops, + (u) => !ArrayPrototypeIncludes(recognisedUsages, u), ) !== undefined ) { - throw new DOMException("Invalid key usages", "SyntaxError"); + throw new DOMException( + "'key_ops' property of JsonWebKey is invalid", + "DataError", + ); } - const privateKeyData = new Uint8Array(32); - if (!ops.op_import_pkcs8_ed25519(keyData, privateKeyData)) { - throw new DOMException("Invalid key data", "DataError"); + if ( + !ArrayPrototypeEvery( + jwk.key_ops, + (u) => ArrayPrototypeIncludes(keyUsages, u), + ) + ) { + throw new DOMException( + "'key_ops' property of JsonWebKey is invalid", + "DataError", + ); } + } + + // 8. + if (jwk.ext !== undefined && jwk.ext === false && extractable) { + throw new DOMException("Invalid key extractability", "DataError"); + } + + // 9. + if (jwk.d !== undefined) { + // https://www.rfc-editor.org/rfc/rfc8037#section-2 + const privateKeyData = ops.op_crypto_base64url_decode(jwk.d); const handle = {}; WeakMapPrototypeSet(KEY_STORE, handle, privateKeyData); @@ -2222,1152 +2326,934 @@ algorithm, handle, ); - } - case "jwk": { - // 1. - const jwk = keyData; - - // 2. - if (jwk.d !== undefined) { - if ( - ArrayPrototypeFind( - keyUsages, - (u) => - !ArrayPrototypeIncludes( - ["sign"], - u, - ), - ) !== undefined - ) { - throw new DOMException("Invalid key usages", "SyntaxError"); - } - } else { - if ( - ArrayPrototypeFind( - keyUsages, - (u) => - !ArrayPrototypeIncludes( - ["verify"], - u, - ), - ) !== undefined - ) { - throw new DOMException("Invalid key usages", "SyntaxError"); - } - } - - // 3. - if (jwk.kty !== "OKP") { - throw new DOMException("Invalid key type", "DataError"); - } - - // 4. - if (jwk.crv !== "Ed25519") { - throw new DOMException("Invalid curve", "DataError"); - } - - // 5. - if (jwk.alg !== undefined && jwk.alg !== "EdDSA") { - throw new DOMException("Invalid algorithm", "DataError"); - } - - // 6. - if ( - keyUsages.length > 0 && jwk.use !== undefined && jwk.use !== "sig" - ) { - throw new DOMException("Invalid key usage", "DataError"); - } - - // 7. - if (jwk.key_ops !== undefined) { - if ( - ArrayPrototypeFind( - jwk.key_ops, - (u) => !ArrayPrototypeIncludes(recognisedUsages, u), - ) !== undefined - ) { - throw new DOMException( - "'key_ops' property of JsonWebKey is invalid", - "DataError", - ); - } - - if ( - !ArrayPrototypeEvery( - jwk.key_ops, - (u) => ArrayPrototypeIncludes(keyUsages, u), - ) - ) { - throw new DOMException( - "'key_ops' property of JsonWebKey is invalid", - "DataError", - ); - } - } - - // 8. - if (jwk.ext !== undefined && jwk.ext === false && extractable) { - throw new DOMException("Invalid key extractability", "DataError"); - } - - // 9. - if (jwk.d !== undefined) { - // https://www.rfc-editor.org/rfc/rfc8037#section-2 - const privateKeyData = ops.op_crypto_base64url_decode(jwk.d); - - const handle = {}; - WeakMapPrototypeSet(KEY_STORE, handle, privateKeyData); - - const algorithm = { - name: "Ed25519", - }; - - return constructKey( - "private", - extractable, - usageIntersection(keyUsages, recognisedUsages), - algorithm, - handle, - ); - } else { - // https://www.rfc-editor.org/rfc/rfc8037#section-2 - const publicKeyData = ops.op_crypto_base64url_decode(jwk.x); - - const handle = {}; - WeakMapPrototypeSet(KEY_STORE, handle, publicKeyData); - - const algorithm = { - name: "Ed25519", - }; - - return constructKey( - "public", - extractable, - usageIntersection(keyUsages, recognisedUsages), - algorithm, - handle, - ); - } - } - default: - throw new DOMException("Not implemented", "NotSupportedError"); - } - } - - function importKeyX25519( - format, - keyData, - extractable, - keyUsages, - ) { - switch (format) { - case "raw": { - // 1. - if (keyUsages.length > 0) { - throw new DOMException("Invalid key usages", "SyntaxError"); - } + } else { + // https://www.rfc-editor.org/rfc/rfc8037#section-2 + const publicKeyData = ops.op_crypto_base64url_decode(jwk.x); const handle = {}; - WeakMapPrototypeSet(KEY_STORE, handle, keyData); + WeakMapPrototypeSet(KEY_STORE, handle, publicKeyData); - // 2-3. const algorithm = { - name: "X25519", + name: "Ed25519", }; - // 4-6. return constructKey( "public", extractable, - [], + usageIntersection(keyUsages, recognisedUsages), algorithm, handle, ); } - case "spki": { - // 1. - if (keyUsages.length > 0) { - throw new DOMException("Invalid key usages", "SyntaxError"); - } + } + default: + throw new DOMException("Not implemented", "NotSupportedError"); + } +} + +function importKeyX25519( + format, + keyData, + extractable, + keyUsages, +) { + switch (format) { + case "raw": { + // 1. + if (keyUsages.length > 0) { + throw new DOMException("Invalid key usages", "SyntaxError"); + } - const publicKeyData = new Uint8Array(32); - if (!ops.op_import_spki_x25519(keyData, publicKeyData)) { - throw new DOMException("Invalid key data", "DataError"); - } + const handle = {}; + WeakMapPrototypeSet(KEY_STORE, handle, keyData); - const handle = {}; - WeakMapPrototypeSet(KEY_STORE, handle, publicKeyData); + // 2-3. + const algorithm = { + name: "X25519", + }; - const algorithm = { - name: "X25519", - }; + // 4-6. + return constructKey( + "public", + extractable, + [], + algorithm, + handle, + ); + } + case "spki": { + // 1. + if (keyUsages.length > 0) { + throw new DOMException("Invalid key usages", "SyntaxError"); + } - return constructKey( - "public", - extractable, - [], - algorithm, - handle, - ); + const publicKeyData = new Uint8Array(32); + if (!ops.op_import_spki_x25519(keyData, publicKeyData)) { + throw new DOMException("Invalid key data", "DataError"); } - case "pkcs8": { - // 1. - if ( - ArrayPrototypeFind( - keyUsages, - (u) => !ArrayPrototypeIncludes(["deriveKey", "deriveBits"], u), - ) !== undefined - ) { - throw new DOMException("Invalid key usages", "SyntaxError"); - } - const privateKeyData = new Uint8Array(32); - if (!ops.op_import_pkcs8_x25519(keyData, privateKeyData)) { - throw new DOMException("Invalid key data", "DataError"); - } + const handle = {}; + WeakMapPrototypeSet(KEY_STORE, handle, publicKeyData); - const handle = {}; - WeakMapPrototypeSet(KEY_STORE, handle, privateKeyData); + const algorithm = { + name: "X25519", + }; - const algorithm = { - name: "X25519", - }; + return constructKey( + "public", + extractable, + [], + algorithm, + handle, + ); + } + case "pkcs8": { + // 1. + if ( + ArrayPrototypeFind( + keyUsages, + (u) => !ArrayPrototypeIncludes(["deriveKey", "deriveBits"], u), + ) !== undefined + ) { + throw new DOMException("Invalid key usages", "SyntaxError"); + } - return constructKey( - "private", - extractable, - usageIntersection(keyUsages, recognisedUsages), - algorithm, - handle, - ); + const privateKeyData = new Uint8Array(32); + if (!ops.op_import_pkcs8_x25519(keyData, privateKeyData)) { + throw new DOMException("Invalid key data", "DataError"); } - case "jwk": { - // 1. - const jwk = keyData; - // 2. - if (jwk.d !== undefined) { - if ( - ArrayPrototypeFind( - keyUsages, - (u) => - !ArrayPrototypeIncludes( - ["deriveKey", "deriveBits"], - u, - ), - ) !== undefined - ) { - throw new DOMException("Invalid key usages", "SyntaxError"); - } - } + const handle = {}; + WeakMapPrototypeSet(KEY_STORE, handle, privateKeyData); - // 3. - if (jwk.d === undefined && keyUsages.length > 0) { - throw new DOMException("Invalid key usages", "SyntaxError"); - } + const algorithm = { + name: "X25519", + }; - // 4. - if (jwk.kty !== "OKP") { - throw new DOMException("Invalid key type", "DataError"); - } + return constructKey( + "private", + extractable, + usageIntersection(keyUsages, recognisedUsages), + algorithm, + handle, + ); + } + case "jwk": { + // 1. + const jwk = keyData; - // 5. - if (jwk.crv !== "X25519") { - throw new DOMException("Invalid curve", "DataError"); + // 2. + if (jwk.d !== undefined) { + if ( + ArrayPrototypeFind( + keyUsages, + (u) => + !ArrayPrototypeIncludes( + ["deriveKey", "deriveBits"], + u, + ), + ) !== undefined + ) { + throw new DOMException("Invalid key usages", "SyntaxError"); } + } - // 6. - if (keyUsages.length > 0 && jwk.use !== undefined) { - if (jwk.use !== "enc") { - throw new DOMException("Invalid key use", "DataError"); - } - } + // 3. + if (jwk.d === undefined && keyUsages.length > 0) { + throw new DOMException("Invalid key usages", "SyntaxError"); + } - // 7. - if (jwk.key_ops !== undefined) { - if ( - ArrayPrototypeFind( - jwk.key_ops, - (u) => !ArrayPrototypeIncludes(recognisedUsages, u), - ) !== undefined - ) { - throw new DOMException( - "'key_ops' property of JsonWebKey is invalid", - "DataError", - ); - } + // 4. + if (jwk.kty !== "OKP") { + throw new DOMException("Invalid key type", "DataError"); + } - if ( - !ArrayPrototypeEvery( - jwk.key_ops, - (u) => ArrayPrototypeIncludes(keyUsages, u), - ) - ) { - throw new DOMException( - "'key_ops' property of JsonWebKey is invalid", - "DataError", - ); - } - } + // 5. + if (jwk.crv !== "X25519") { + throw new DOMException("Invalid curve", "DataError"); + } - // 8. - if (jwk.ext !== undefined && jwk.ext === false && extractable) { - throw new DOMException("Invalid key extractability", "DataError"); + // 6. + if (keyUsages.length > 0 && jwk.use !== undefined) { + if (jwk.use !== "enc") { + throw new DOMException("Invalid key use", "DataError"); } + } - // 9. - if (jwk.d !== undefined) { - // https://www.rfc-editor.org/rfc/rfc8037#section-2 - const privateKeyData = ops.op_crypto_base64url_decode(jwk.d); - - const handle = {}; - WeakMapPrototypeSet(KEY_STORE, handle, privateKeyData); - - const algorithm = { - name: "X25519", - }; - - return constructKey( - "private", - extractable, - usageIntersection(keyUsages, ["deriveKey", "deriveBits"]), - algorithm, - handle, + // 7. + if (jwk.key_ops !== undefined) { + if ( + ArrayPrototypeFind( + jwk.key_ops, + (u) => !ArrayPrototypeIncludes(recognisedUsages, u), + ) !== undefined + ) { + throw new DOMException( + "'key_ops' property of JsonWebKey is invalid", + "DataError", ); - } else { - // https://www.rfc-editor.org/rfc/rfc8037#section-2 - const publicKeyData = ops.op_crypto_base64url_decode(jwk.x); - - const handle = {}; - WeakMapPrototypeSet(KEY_STORE, handle, publicKeyData); - - const algorithm = { - name: "X25519", - }; - - return constructKey( - "public", - extractable, - [], - algorithm, - handle, + } + + if ( + !ArrayPrototypeEvery( + jwk.key_ops, + (u) => ArrayPrototypeIncludes(keyUsages, u), + ) + ) { + throw new DOMException( + "'key_ops' property of JsonWebKey is invalid", + "DataError", ); } } - default: - throw new DOMException("Not implemented", "NotSupportedError"); - } - } - function exportKeyAES( - format, - key, - innerKey, - ) { - switch (format) { - // 2. - case "raw": { - // 1. - const data = innerKey.data; - // 2. - return data.buffer; + // 8. + if (jwk.ext !== undefined && jwk.ext === false && extractable) { + throw new DOMException("Invalid key extractability", "DataError"); } - case "jwk": { - // 1-2. - const jwk = { - kty: "oct", - }; - // 3. - const data = ops.op_crypto_export_key({ - format: "jwksecret", - algorithm: "AES", - }, innerKey); - ObjectAssign(jwk, data); + // 9. + if (jwk.d !== undefined) { + // https://www.rfc-editor.org/rfc/rfc8037#section-2 + const privateKeyData = ops.op_crypto_base64url_decode(jwk.d); - // 4. - const algorithm = key[_algorithm]; - switch (algorithm.length) { - case 128: - jwk.alg = aesJwkAlg[algorithm.name][128]; - break; - case 192: - jwk.alg = aesJwkAlg[algorithm.name][192]; - break; - case 256: - jwk.alg = aesJwkAlg[algorithm.name][256]; - break; - default: - throw new DOMException( - "Invalid key length", - "NotSupportedError", - ); - } + const handle = {}; + WeakMapPrototypeSet(KEY_STORE, handle, privateKeyData); - // 5. - jwk.key_ops = key.usages; + const algorithm = { + name: "X25519", + }; - // 6. - jwk.ext = key[_extractable]; + return constructKey( + "private", + extractable, + usageIntersection(keyUsages, ["deriveKey", "deriveBits"]), + algorithm, + handle, + ); + } else { + // https://www.rfc-editor.org/rfc/rfc8037#section-2 + const publicKeyData = ops.op_crypto_base64url_decode(jwk.x); - // 7. - return jwk; + const handle = {}; + WeakMapPrototypeSet(KEY_STORE, handle, publicKeyData); + + const algorithm = { + name: "X25519", + }; + + return constructKey( + "public", + extractable, + [], + algorithm, + handle, + ); } - default: - throw new DOMException("Not implemented", "NotSupportedError"); } + default: + throw new DOMException("Not implemented", "NotSupportedError"); } - - function importKeyAES( - format, - normalizedAlgorithm, - keyData, - extractable, - keyUsages, - supportedKeyUsages, - ) { - // 1. - if ( - ArrayPrototypeFind( - keyUsages, - (u) => !ArrayPrototypeIncludes(supportedKeyUsages, u), - ) !== undefined - ) { - throw new DOMException("Invalid key usages", "SyntaxError"); +} + +function exportKeyAES( + format, + key, + innerKey, +) { + switch (format) { + // 2. + case "raw": { + // 1. + const data = innerKey.data; + // 2. + return data.buffer; } + case "jwk": { + // 1-2. + const jwk = { + kty: "oct", + }; - const algorithmName = normalizedAlgorithm.name; + // 3. + const data = ops.op_crypto_export_key({ + format: "jwksecret", + algorithm: "AES", + }, innerKey); + ObjectAssign(jwk, data); + + // 4. + const algorithm = key[_algorithm]; + switch (algorithm.length) { + case 128: + jwk.alg = aesJwkAlg[algorithm.name][128]; + break; + case 192: + jwk.alg = aesJwkAlg[algorithm.name][192]; + break; + case 256: + jwk.alg = aesJwkAlg[algorithm.name][256]; + break; + default: + throw new DOMException( + "Invalid key length", + "NotSupportedError", + ); + } - // 2. - let data = keyData; + // 5. + jwk.key_ops = key.usages; - switch (format) { - case "raw": { - // 2. - if ( - !ArrayPrototypeIncludes([128, 192, 256], keyData.byteLength * 8) - ) { - throw new DOMException("Invalid key length", "Datarror"); - } + // 6. + jwk.ext = key[_extractable]; - break; - } - case "jwk": { - // 1. - const jwk = keyData; + // 7. + return jwk; + } + default: + throw new DOMException("Not implemented", "NotSupportedError"); + } +} + +function importKeyAES( + format, + normalizedAlgorithm, + keyData, + extractable, + keyUsages, + supportedKeyUsages, +) { + // 1. + if ( + ArrayPrototypeFind( + keyUsages, + (u) => !ArrayPrototypeIncludes(supportedKeyUsages, u), + ) !== undefined + ) { + throw new DOMException("Invalid key usages", "SyntaxError"); + } - // 2. - if (jwk.kty !== "oct") { - throw new DOMException( - "'kty' property of JsonWebKey must be 'oct'", - "DataError", - ); - } + const algorithmName = normalizedAlgorithm.name; - // Section 6.4.1 of RFC7518 - if (jwk.k === undefined) { - throw new DOMException( - "'k' property of JsonWebKey must be present", - "DataError", - ); - } + // 2. + let data = keyData; - // 4. - const { rawData } = ops.op_crypto_import_key( - { algorithm: "AES" }, - { jwkSecret: jwk }, + switch (format) { + case "raw": { + // 2. + if ( + !ArrayPrototypeIncludes([128, 192, 256], keyData.byteLength * 8) + ) { + throw new DOMException("Invalid key length", "Datarror"); + } + + break; + } + case "jwk": { + // 1. + const jwk = keyData; + + // 2. + if (jwk.kty !== "oct") { + throw new DOMException( + "'kty' property of JsonWebKey must be 'oct'", + "DataError", ); - data = rawData.data; + } - // 5. - switch (data.byteLength * 8) { - case 128: - if ( - jwk.alg !== undefined && - jwk.alg !== aesJwkAlg[algorithmName][128] - ) { - throw new DOMException("Invalid algorithm", "DataError"); - } - break; - case 192: - if ( - jwk.alg !== undefined && - jwk.alg !== aesJwkAlg[algorithmName][192] - ) { - throw new DOMException("Invalid algorithm", "DataError"); - } - break; - case 256: - if ( - jwk.alg !== undefined && - jwk.alg !== aesJwkAlg[algorithmName][256] - ) { - throw new DOMException("Invalid algorithm", "DataError"); - } - break; - default: - throw new DOMException( - "Invalid key length", - "DataError", - ); - } + // Section 6.4.1 of RFC7518 + if (jwk.k === undefined) { + throw new DOMException( + "'k' property of JsonWebKey must be present", + "DataError", + ); + } - // 6. - if ( - keyUsages.length > 0 && jwk.use !== undefined && jwk.use !== "enc" - ) { - throw new DOMException("Invalid key usages", "DataError"); - } + // 4. + const { rawData } = ops.op_crypto_import_key( + { algorithm: "AES" }, + { jwkSecret: jwk }, + ); + data = rawData.data; - // 7. - // Section 4.3 of RFC7517 - if (jwk.key_ops !== undefined) { + // 5. + switch (data.byteLength * 8) { + case 128: if ( - ArrayPrototypeFind( - jwk.key_ops, - (u) => !ArrayPrototypeIncludes(recognisedUsages, u), - ) !== undefined + jwk.alg !== undefined && + jwk.alg !== aesJwkAlg[algorithmName][128] ) { - throw new DOMException( - "'key_ops' property of JsonWebKey is invalid", - "DataError", - ); + throw new DOMException("Invalid algorithm", "DataError"); } - + break; + case 192: if ( - !ArrayPrototypeEvery( - jwk.key_ops, - (u) => ArrayPrototypeIncludes(keyUsages, u), - ) + jwk.alg !== undefined && + jwk.alg !== aesJwkAlg[algorithmName][192] ) { - throw new DOMException( - "'key_ops' property of JsonWebKey is invalid", - "DataError", - ); + throw new DOMException("Invalid algorithm", "DataError"); } - } + break; + case 256: + if ( + jwk.alg !== undefined && + jwk.alg !== aesJwkAlg[algorithmName][256] + ) { + throw new DOMException("Invalid algorithm", "DataError"); + } + break; + default: + throw new DOMException( + "Invalid key length", + "DataError", + ); + } - // 8. - if (jwk.ext === false && extractable === true) { + // 6. + if ( + keyUsages.length > 0 && jwk.use !== undefined && jwk.use !== "enc" + ) { + throw new DOMException("Invalid key usages", "DataError"); + } + + // 7. + // Section 4.3 of RFC7517 + if (jwk.key_ops !== undefined) { + if ( + ArrayPrototypeFind( + jwk.key_ops, + (u) => !ArrayPrototypeIncludes(recognisedUsages, u), + ) !== undefined + ) { throw new DOMException( - "'ext' property of JsonWebKey must not be false if extractable is true", + "'key_ops' property of JsonWebKey is invalid", "DataError", ); } - break; + if ( + !ArrayPrototypeEvery( + jwk.key_ops, + (u) => ArrayPrototypeIncludes(keyUsages, u), + ) + ) { + throw new DOMException( + "'key_ops' property of JsonWebKey is invalid", + "DataError", + ); + } } - default: - throw new DOMException("Not implemented", "NotSupportedError"); - } - const handle = {}; - WeakMapPrototypeSet(KEY_STORE, handle, { - type: "secret", - data, - }); + // 8. + if (jwk.ext === false && extractable === true) { + throw new DOMException( + "'ext' property of JsonWebKey must not be false if extractable is true", + "DataError", + ); + } - // 4-7. - const algorithm = { - name: algorithmName, - length: data.byteLength * 8, - }; + break; + } + default: + throw new DOMException("Not implemented", "NotSupportedError"); + } - const key = constructKey( - "secret", - extractable, - usageIntersection(keyUsages, recognisedUsages), - algorithm, - handle, - ); + const handle = {}; + WeakMapPrototypeSet(KEY_STORE, handle, { + type: "secret", + data, + }); - // 8. - return key; - } + // 4-7. + const algorithm = { + name: algorithmName, + length: data.byteLength * 8, + }; - function importKeyHMAC( - format, - normalizedAlgorithm, - keyData, + const key = constructKey( + "secret", extractable, - keyUsages, + usageIntersection(keyUsages, recognisedUsages), + algorithm, + handle, + ); + + // 8. + return key; +} + +function importKeyHMAC( + format, + normalizedAlgorithm, + keyData, + extractable, + keyUsages, +) { + // 2. + if ( + ArrayPrototypeFind( + keyUsages, + (u) => !ArrayPrototypeIncludes(["sign", "verify"], u), + ) !== undefined ) { - // 2. - if ( - ArrayPrototypeFind( - keyUsages, - (u) => !ArrayPrototypeIncludes(["sign", "verify"], u), - ) !== undefined - ) { - throw new DOMException("Invalid key usages", "SyntaxError"); - } + throw new DOMException("Invalid key usages", "SyntaxError"); + } - // 3. - let hash; - let data; - - // 4. https://w3c.github.io/webcrypto/#hmac-operations - switch (format) { - case "raw": { - data = keyData; - hash = normalizedAlgorithm.hash; - break; - } - case "jwk": { - const jwk = keyData; + // 3. + let hash; + let data; - // 2. - if (jwk.kty !== "oct") { - throw new DOMException( - "'kty' property of JsonWebKey must be 'oct'", - "DataError", - ); - } + // 4. https://w3c.github.io/webcrypto/#hmac-operations + switch (format) { + case "raw": { + data = keyData; + hash = normalizedAlgorithm.hash; + break; + } + case "jwk": { + const jwk = keyData; - // Section 6.4.1 of RFC7518 - if (jwk.k === undefined) { - throw new DOMException( - "'k' property of JsonWebKey must be present", - "DataError", - ); - } + // 2. + if (jwk.kty !== "oct") { + throw new DOMException( + "'kty' property of JsonWebKey must be 'oct'", + "DataError", + ); + } - // 4. - const { rawData } = ops.op_crypto_import_key( - { algorithm: "HMAC" }, - { jwkSecret: jwk }, + // Section 6.4.1 of RFC7518 + if (jwk.k === undefined) { + throw new DOMException( + "'k' property of JsonWebKey must be present", + "DataError", ); - data = rawData.data; + } - // 5. - hash = normalizedAlgorithm.hash; + // 4. + const { rawData } = ops.op_crypto_import_key( + { algorithm: "HMAC" }, + { jwkSecret: jwk }, + ); + data = rawData.data; - // 6. - switch (hash.name) { - case "SHA-1": { - if (jwk.alg !== undefined && jwk.alg !== "HS1") { - throw new DOMException( - "'alg' property of JsonWebKey must be 'HS1'", - "DataError", - ); - } - break; - } - case "SHA-256": { - if (jwk.alg !== undefined && jwk.alg !== "HS256") { - throw new DOMException( - "'alg' property of JsonWebKey must be 'HS256'", - "DataError", - ); - } - break; - } - case "SHA-384": { - if (jwk.alg !== undefined && jwk.alg !== "HS384") { - throw new DOMException( - "'alg' property of JsonWebKey must be 'HS384'", - "DataError", - ); - } - break; - } - case "SHA-512": { - if (jwk.alg !== undefined && jwk.alg !== "HS512") { - throw new DOMException( - "'alg' property of JsonWebKey must be 'HS512'", - "DataError", - ); - } - break; - } - default: - throw new TypeError("unreachable"); - } + // 5. + hash = normalizedAlgorithm.hash; - // 7. - if ( - keyUsages.length > 0 && jwk.use !== undefined && jwk.use !== "sig" - ) { - throw new DOMException( - "'use' property of JsonWebKey must be 'sig'", - "DataError", - ); + // 6. + switch (hash.name) { + case "SHA-1": { + if (jwk.alg !== undefined && jwk.alg !== "HS1") { + throw new DOMException( + "'alg' property of JsonWebKey must be 'HS1'", + "DataError", + ); + } + break; } - - // 8. - // Section 4.3 of RFC7517 - if (jwk.key_ops !== undefined) { - if ( - ArrayPrototypeFind( - jwk.key_ops, - (u) => !ArrayPrototypeIncludes(recognisedUsages, u), - ) !== undefined - ) { + case "SHA-256": { + if (jwk.alg !== undefined && jwk.alg !== "HS256") { throw new DOMException( - "'key_ops' property of JsonWebKey is invalid", + "'alg' property of JsonWebKey must be 'HS256'", "DataError", ); } - - if ( - !ArrayPrototypeEvery( - jwk.key_ops, - (u) => ArrayPrototypeIncludes(keyUsages, u), - ) - ) { + break; + } + case "SHA-384": { + if (jwk.alg !== undefined && jwk.alg !== "HS384") { throw new DOMException( - "'key_ops' property of JsonWebKey is invalid", + "'alg' property of JsonWebKey must be 'HS384'", "DataError", ); } + break; } - - // 9. - if (jwk.ext === false && extractable === true) { - throw new DOMException( - "'ext' property of JsonWebKey must not be false if extractable is true", - "DataError", - ); + case "SHA-512": { + if (jwk.alg !== undefined && jwk.alg !== "HS512") { + throw new DOMException( + "'alg' property of JsonWebKey must be 'HS512'", + "DataError", + ); + } + break; } - - break; + default: + throw new TypeError("unreachable"); } - default: - throw new DOMException("Not implemented", "NotSupportedError"); - } - // 5. - let length = data.byteLength * 8; - // 6. - if (length === 0) { - throw new DOMException("Key length is zero", "DataError"); - } - // 7. - if (normalizedAlgorithm.length !== undefined) { + // 7. if ( - normalizedAlgorithm.length > length || - normalizedAlgorithm.length <= (length - 8) + keyUsages.length > 0 && jwk.use !== undefined && jwk.use !== "sig" ) { throw new DOMException( - "Key length is invalid", + "'use' property of JsonWebKey must be 'sig'", "DataError", ); } - length = normalizedAlgorithm.length; - } - - const handle = {}; - WeakMapPrototypeSet(KEY_STORE, handle, { - type: "secret", - data, - }); - - const algorithm = { - name: "HMAC", - length, - hash, - }; - - const key = constructKey( - "secret", - extractable, - usageIntersection(keyUsages, recognisedUsages), - algorithm, - handle, - ); - - return key; - } - - function importKeyEC( - format, - normalizedAlgorithm, - keyData, - extractable, - keyUsages, - ) { - const supportedUsages = SUPPORTED_KEY_USAGES[normalizedAlgorithm.name]; - switch (format) { - case "raw": { - // 1. + // 8. + // Section 4.3 of RFC7517 + if (jwk.key_ops !== undefined) { if ( - !ArrayPrototypeIncludes( - supportedNamedCurves, - normalizedAlgorithm.namedCurve, - ) + ArrayPrototypeFind( + jwk.key_ops, + (u) => !ArrayPrototypeIncludes(recognisedUsages, u), + ) !== undefined ) { throw new DOMException( - "Invalid namedCurve", + "'key_ops' property of JsonWebKey is invalid", "DataError", ); } - // 2. if ( - ArrayPrototypeFind( - keyUsages, - (u) => - !ArrayPrototypeIncludes( - SUPPORTED_KEY_USAGES[normalizedAlgorithm.name].public, - u, - ), - ) !== undefined + !ArrayPrototypeEvery( + jwk.key_ops, + (u) => ArrayPrototypeIncludes(keyUsages, u), + ) ) { - throw new DOMException("Invalid key usages", "SyntaxError"); + throw new DOMException( + "'key_ops' property of JsonWebKey is invalid", + "DataError", + ); } + } - // 3. - const { rawData } = ops.op_crypto_import_key({ - algorithm: normalizedAlgorithm.name, - namedCurve: normalizedAlgorithm.namedCurve, - }, { raw: keyData }); + // 9. + if (jwk.ext === false && extractable === true) { + throw new DOMException( + "'ext' property of JsonWebKey must not be false if extractable is true", + "DataError", + ); + } - const handle = {}; - WeakMapPrototypeSet(KEY_STORE, handle, rawData); + break; + } + default: + throw new DOMException("Not implemented", "NotSupportedError"); + } - // 4-5. - const algorithm = { - name: normalizedAlgorithm.name, - namedCurve: normalizedAlgorithm.namedCurve, - }; + // 5. + let length = data.byteLength * 8; + // 6. + if (length === 0) { + throw new DOMException("Key length is zero", "DataError"); + } + // 7. + if (normalizedAlgorithm.length !== undefined) { + if ( + normalizedAlgorithm.length > length || + normalizedAlgorithm.length <= (length - 8) + ) { + throw new DOMException( + "Key length is invalid", + "DataError", + ); + } + length = normalizedAlgorithm.length; + } - // 6-8. - const key = constructKey( - "public", - extractable, - usageIntersection(keyUsages, recognisedUsages), - algorithm, - handle, + const handle = {}; + WeakMapPrototypeSet(KEY_STORE, handle, { + type: "secret", + data, + }); + + const algorithm = { + name: "HMAC", + length, + hash, + }; + + const key = constructKey( + "secret", + extractable, + usageIntersection(keyUsages, recognisedUsages), + algorithm, + handle, + ); + + return key; +} + +function importKeyEC( + format, + normalizedAlgorithm, + keyData, + extractable, + keyUsages, +) { + const supportedUsages = SUPPORTED_KEY_USAGES[normalizedAlgorithm.name]; + + switch (format) { + case "raw": { + // 1. + if ( + !ArrayPrototypeIncludes( + supportedNamedCurves, + normalizedAlgorithm.namedCurve, + ) + ) { + throw new DOMException( + "Invalid namedCurve", + "DataError", ); + } - return key; + // 2. + if ( + ArrayPrototypeFind( + keyUsages, + (u) => + !ArrayPrototypeIncludes( + SUPPORTED_KEY_USAGES[normalizedAlgorithm.name].public, + u, + ), + ) !== undefined + ) { + throw new DOMException("Invalid key usages", "SyntaxError"); } - case "pkcs8": { - // 1. + + // 3. + const { rawData } = ops.op_crypto_import_key({ + algorithm: normalizedAlgorithm.name, + namedCurve: normalizedAlgorithm.namedCurve, + }, { raw: keyData }); + + const handle = {}; + WeakMapPrototypeSet(KEY_STORE, handle, rawData); + + // 4-5. + const algorithm = { + name: normalizedAlgorithm.name, + namedCurve: normalizedAlgorithm.namedCurve, + }; + + // 6-8. + const key = constructKey( + "public", + extractable, + usageIntersection(keyUsages, recognisedUsages), + algorithm, + handle, + ); + + return key; + } + case "pkcs8": { + // 1. + if ( + ArrayPrototypeFind( + keyUsages, + (u) => + !ArrayPrototypeIncludes( + SUPPORTED_KEY_USAGES[normalizedAlgorithm.name].private, + u, + ), + ) !== undefined + ) { + throw new DOMException("Invalid key usages", "SyntaxError"); + } + + // 2-9. + const { rawData } = ops.op_crypto_import_key({ + algorithm: normalizedAlgorithm.name, + namedCurve: normalizedAlgorithm.namedCurve, + }, { pkcs8: keyData }); + + const handle = {}; + WeakMapPrototypeSet(KEY_STORE, handle, rawData); + + const algorithm = { + name: normalizedAlgorithm.name, + namedCurve: normalizedAlgorithm.namedCurve, + }; + + const key = constructKey( + "private", + extractable, + usageIntersection(keyUsages, recognisedUsages), + algorithm, + handle, + ); + + return key; + } + case "spki": { + // 1. + if (normalizedAlgorithm.name == "ECDSA") { if ( ArrayPrototypeFind( keyUsages, (u) => !ArrayPrototypeIncludes( - SUPPORTED_KEY_USAGES[normalizedAlgorithm.name].private, + SUPPORTED_KEY_USAGES[normalizedAlgorithm.name].public, u, ), ) !== undefined ) { throw new DOMException("Invalid key usages", "SyntaxError"); } + } else if (keyUsages.length != 0) { + throw new DOMException("Key usage must be empty", "SyntaxError"); + } - // 2-9. - const { rawData } = ops.op_crypto_import_key({ - algorithm: normalizedAlgorithm.name, - namedCurve: normalizedAlgorithm.namedCurve, - }, { pkcs8: keyData }); - - const handle = {}; - WeakMapPrototypeSet(KEY_STORE, handle, rawData); + // 2-12 + const { rawData } = ops.op_crypto_import_key({ + algorithm: normalizedAlgorithm.name, + namedCurve: normalizedAlgorithm.namedCurve, + }, { spki: keyData }); - const algorithm = { - name: normalizedAlgorithm.name, - namedCurve: normalizedAlgorithm.namedCurve, - }; + const handle = {}; + WeakMapPrototypeSet(KEY_STORE, handle, rawData); - const key = constructKey( - "private", - extractable, - usageIntersection(keyUsages, recognisedUsages), - algorithm, - handle, - ); + const algorithm = { + name: normalizedAlgorithm.name, + namedCurve: normalizedAlgorithm.namedCurve, + }; - return key; - } - case "spki": { - // 1. - if (normalizedAlgorithm.name == "ECDSA") { - if ( - ArrayPrototypeFind( - keyUsages, - (u) => - !ArrayPrototypeIncludes( - SUPPORTED_KEY_USAGES[normalizedAlgorithm.name].public, - u, - ), - ) !== undefined - ) { - throw new DOMException("Invalid key usages", "SyntaxError"); - } - } else if (keyUsages.length != 0) { - throw new DOMException("Key usage must be empty", "SyntaxError"); - } + // 6-8. + const key = constructKey( + "public", + extractable, + usageIntersection(keyUsages, recognisedUsages), + algorithm, + handle, + ); - // 2-12 - const { rawData } = ops.op_crypto_import_key({ - algorithm: normalizedAlgorithm.name, - namedCurve: normalizedAlgorithm.namedCurve, - }, { spki: keyData }); + return key; + } + case "jwk": { + const jwk = keyData; - const handle = {}; - WeakMapPrototypeSet(KEY_STORE, handle, rawData); + const keyType = (jwk.d !== undefined) ? "private" : "public"; - const algorithm = { - name: normalizedAlgorithm.name, - namedCurve: normalizedAlgorithm.namedCurve, - }; + // 2. + if ( + ArrayPrototypeFind( + keyUsages, + (u) => !ArrayPrototypeIncludes(supportedUsages[keyType], u), + ) !== undefined + ) { + throw new DOMException("Invalid key usages", "SyntaxError"); + } - // 6-8. - const key = constructKey( - "public", - extractable, - usageIntersection(keyUsages, recognisedUsages), - algorithm, - handle, + // 3. + if (jwk.kty !== "EC") { + throw new DOMException( + "'kty' property of JsonWebKey must be 'EC'", + "DataError", ); - - return key; } - case "jwk": { - const jwk = keyData; - const keyType = (jwk.d !== undefined) ? "private" : "public"; + // 4. + if ( + keyUsages.length > 0 && jwk.use !== undefined && + jwk.use !== supportedUsages.jwkUse + ) { + throw new DOMException( + `'use' property of JsonWebKey must be '${supportedUsages.jwkUse}'`, + "DataError", + ); + } - // 2. + // 5. + // Section 4.3 of RFC7517 + if (jwk.key_ops !== undefined) { if ( ArrayPrototypeFind( - keyUsages, - (u) => !ArrayPrototypeIncludes(supportedUsages[keyType], u), + jwk.key_ops, + (u) => !ArrayPrototypeIncludes(recognisedUsages, u), ) !== undefined ) { - throw new DOMException("Invalid key usages", "SyntaxError"); - } - - // 3. - if (jwk.kty !== "EC") { throw new DOMException( - "'kty' property of JsonWebKey must be 'EC'", + "'key_ops' member of JsonWebKey is invalid", "DataError", ); } - // 4. if ( - keyUsages.length > 0 && jwk.use !== undefined && - jwk.use !== supportedUsages.jwkUse + !ArrayPrototypeEvery( + jwk.key_ops, + (u) => ArrayPrototypeIncludes(keyUsages, u), + ) ) { throw new DOMException( - `'use' property of JsonWebKey must be '${supportedUsages.jwkUse}'`, - "DataError", - ); - } - - // 5. - // Section 4.3 of RFC7517 - if (jwk.key_ops !== undefined) { - if ( - ArrayPrototypeFind( - jwk.key_ops, - (u) => !ArrayPrototypeIncludes(recognisedUsages, u), - ) !== undefined - ) { - throw new DOMException( - "'key_ops' member of JsonWebKey is invalid", - "DataError", - ); - } - - if ( - !ArrayPrototypeEvery( - jwk.key_ops, - (u) => ArrayPrototypeIncludes(keyUsages, u), - ) - ) { - throw new DOMException( - "'key_ops' member of JsonWebKey is invalid", - "DataError", - ); - } - } - - // 6. - if (jwk.ext === false && extractable === true) { - throw new DOMException( - "'ext' property of JsonWebKey must not be false if extractable is true", + "'key_ops' member of JsonWebKey is invalid", "DataError", ); } + } - // 9. - if (jwk.alg !== undefined && normalizedAlgorithm.name == "ECDSA") { - let algNamedCurve; - - switch (jwk.alg) { - case "ES256": { - algNamedCurve = "P-256"; - break; - } - case "ES384": { - algNamedCurve = "P-384"; - break; - } - case "ES512": { - algNamedCurve = "P-521"; - break; - } - default: - throw new DOMException( - "Curve algorithm not supported", - "DataError", - ); - } - - if (algNamedCurve) { - if (algNamedCurve !== normalizedAlgorithm.namedCurve) { - throw new DOMException( - "Mismatched curve algorithm", - "DataError", - ); - } - } - } - - // Validate that this is a valid public key. - if (jwk.x === undefined) { - throw new DOMException( - "'x' property of JsonWebKey is required for EC keys", - "DataError", - ); - } - if (jwk.y === undefined) { - throw new DOMException( - "'y' property of JsonWebKey is required for EC keys", - "DataError", - ); - } + // 6. + if (jwk.ext === false && extractable === true) { + throw new DOMException( + "'ext' property of JsonWebKey must not be false if extractable is true", + "DataError", + ); + } - if (jwk.d !== undefined) { - // it's also a Private key - const { rawData } = ops.op_crypto_import_key({ - algorithm: normalizedAlgorithm.name, - namedCurve: normalizedAlgorithm.namedCurve, - }, { jwkPrivateEc: jwk }); - - const handle = {}; - WeakMapPrototypeSet(KEY_STORE, handle, rawData); - - const algorithm = { - name: normalizedAlgorithm.name, - namedCurve: normalizedAlgorithm.namedCurve, - }; - - const key = constructKey( - "private", - extractable, - usageIntersection(keyUsages, recognisedUsages), - algorithm, - handle, - ); + // 9. + if (jwk.alg !== undefined && normalizedAlgorithm.name == "ECDSA") { + let algNamedCurve; - return key; - } else { - const { rawData } = ops.op_crypto_import_key({ - algorithm: normalizedAlgorithm.name, - namedCurve: normalizedAlgorithm.namedCurve, - }, { jwkPublicEc: jwk }); - - const handle = {}; - WeakMapPrototypeSet(KEY_STORE, handle, rawData); - - const algorithm = { - name: normalizedAlgorithm.name, - namedCurve: normalizedAlgorithm.namedCurve, - }; - - const key = constructKey( - "public", - extractable, - usageIntersection(keyUsages, recognisedUsages), - algorithm, - handle, - ); + switch (jwk.alg) { + case "ES256": { + algNamedCurve = "P-256"; + break; + } + case "ES384": { + algNamedCurve = "P-384"; + break; + } + case "ES512": { + algNamedCurve = "P-521"; + break; + } + default: + throw new DOMException( + "Curve algorithm not supported", + "DataError", + ); + } - return key; + if (algNamedCurve) { + if (algNamedCurve !== normalizedAlgorithm.namedCurve) { + throw new DOMException( + "Mismatched curve algorithm", + "DataError", + ); + } } } - default: - throw new DOMException("Not implemented", "NotSupportedError"); - } - } - - const SUPPORTED_KEY_USAGES = { - "RSASSA-PKCS1-v1_5": { - public: ["verify"], - private: ["sign"], - jwkUse: "sig", - }, - "RSA-PSS": { - public: ["verify"], - private: ["sign"], - jwkUse: "sig", - }, - "RSA-OAEP": { - public: ["encrypt", "wrapKey"], - private: ["decrypt", "unwrapKey"], - jwkUse: "enc", - }, - "ECDSA": { - public: ["verify"], - private: ["sign"], - jwkUse: "sig", - }, - "ECDH": { - public: [], - private: ["deriveKey", "deriveBits"], - jwkUse: "enc", - }, - }; - function importKeyRSA( - format, - normalizedAlgorithm, - keyData, - extractable, - keyUsages, - ) { - switch (format) { - case "pkcs8": { - // 1. - if ( - ArrayPrototypeFind( - keyUsages, - (u) => - !ArrayPrototypeIncludes( - SUPPORTED_KEY_USAGES[normalizedAlgorithm.name].private, - u, - ), - ) !== undefined - ) { - throw new DOMException("Invalid key usages", "SyntaxError"); - } + // Validate that this is a valid public key. + if (jwk.x === undefined) { + throw new DOMException( + "'x' property of JsonWebKey is required for EC keys", + "DataError", + ); + } + if (jwk.y === undefined) { + throw new DOMException( + "'y' property of JsonWebKey is required for EC keys", + "DataError", + ); + } - // 2-9. - const { modulusLength, publicExponent, rawData } = ops - .op_crypto_import_key( - { - algorithm: normalizedAlgorithm.name, - // Needed to perform step 7 without normalization. - hash: normalizedAlgorithm.hash.name, - }, - { pkcs8: keyData }, - ); + if (jwk.d !== undefined) { + // it's also a Private key + const { rawData } = ops.op_crypto_import_key({ + algorithm: normalizedAlgorithm.name, + namedCurve: normalizedAlgorithm.namedCurve, + }, { jwkPrivateEc: jwk }); const handle = {}; WeakMapPrototypeSet(KEY_STORE, handle, rawData); const algorithm = { name: normalizedAlgorithm.name, - modulusLength, - publicExponent, - hash: normalizedAlgorithm.hash, + namedCurve: normalizedAlgorithm.namedCurve, }; const key = constructKey( @@ -3379,41 +3265,18 @@ ); return key; - } - case "spki": { - // 1. - if ( - ArrayPrototypeFind( - keyUsages, - (u) => - !ArrayPrototypeIncludes( - SUPPORTED_KEY_USAGES[normalizedAlgorithm.name].public, - u, - ), - ) !== undefined - ) { - throw new DOMException("Invalid key usages", "SyntaxError"); - } - - // 2-9. - const { modulusLength, publicExponent, rawData } = ops - .op_crypto_import_key( - { - algorithm: normalizedAlgorithm.name, - // Needed to perform step 7 without normalization. - hash: normalizedAlgorithm.hash.name, - }, - { spki: keyData }, - ); + } else { + const { rawData } = ops.op_crypto_import_key({ + algorithm: normalizedAlgorithm.name, + namedCurve: normalizedAlgorithm.namedCurve, + }, { jwkPublicEc: jwk }); const handle = {}; WeakMapPrototypeSet(KEY_STORE, handle, rawData); const algorithm = { name: normalizedAlgorithm.name, - modulusLength, - publicExponent, - hash: normalizedAlgorithm.hash, + namedCurve: normalizedAlgorithm.namedCurve, }; const key = constructKey( @@ -3426,453 +3289,667 @@ return key; } - case "jwk": { - // 1. - const jwk = keyData; + } + default: + throw new DOMException("Not implemented", "NotSupportedError"); + } +} + +const SUPPORTED_KEY_USAGES = { + "RSASSA-PKCS1-v1_5": { + public: ["verify"], + private: ["sign"], + jwkUse: "sig", + }, + "RSA-PSS": { + public: ["verify"], + private: ["sign"], + jwkUse: "sig", + }, + "RSA-OAEP": { + public: ["encrypt", "wrapKey"], + private: ["decrypt", "unwrapKey"], + jwkUse: "enc", + }, + "ECDSA": { + public: ["verify"], + private: ["sign"], + jwkUse: "sig", + }, + "ECDH": { + public: [], + private: ["deriveKey", "deriveBits"], + jwkUse: "enc", + }, +}; + +function importKeyRSA( + format, + normalizedAlgorithm, + keyData, + extractable, + keyUsages, +) { + switch (format) { + case "pkcs8": { + // 1. + if ( + ArrayPrototypeFind( + keyUsages, + (u) => + !ArrayPrototypeIncludes( + SUPPORTED_KEY_USAGES[normalizedAlgorithm.name].private, + u, + ), + ) !== undefined + ) { + throw new DOMException("Invalid key usages", "SyntaxError"); + } - // 2. - if (jwk.d !== undefined) { - if ( - ArrayPrototypeFind( - keyUsages, - (u) => - !ArrayPrototypeIncludes( - SUPPORTED_KEY_USAGES[normalizedAlgorithm.name].private, - u, - ), - ) !== undefined - ) { - throw new DOMException("Invalid key usages", "SyntaxError"); - } - } else if ( + // 2-9. + const { modulusLength, publicExponent, rawData } = ops + .op_crypto_import_key( + { + algorithm: normalizedAlgorithm.name, + // Needed to perform step 7 without normalization. + hash: normalizedAlgorithm.hash.name, + }, + { pkcs8: keyData }, + ); + + const handle = {}; + WeakMapPrototypeSet(KEY_STORE, handle, rawData); + + const algorithm = { + name: normalizedAlgorithm.name, + modulusLength, + publicExponent, + hash: normalizedAlgorithm.hash, + }; + + const key = constructKey( + "private", + extractable, + usageIntersection(keyUsages, recognisedUsages), + algorithm, + handle, + ); + + return key; + } + case "spki": { + // 1. + if ( + ArrayPrototypeFind( + keyUsages, + (u) => + !ArrayPrototypeIncludes( + SUPPORTED_KEY_USAGES[normalizedAlgorithm.name].public, + u, + ), + ) !== undefined + ) { + throw new DOMException("Invalid key usages", "SyntaxError"); + } + + // 2-9. + const { modulusLength, publicExponent, rawData } = ops + .op_crypto_import_key( + { + algorithm: normalizedAlgorithm.name, + // Needed to perform step 7 without normalization. + hash: normalizedAlgorithm.hash.name, + }, + { spki: keyData }, + ); + + const handle = {}; + WeakMapPrototypeSet(KEY_STORE, handle, rawData); + + const algorithm = { + name: normalizedAlgorithm.name, + modulusLength, + publicExponent, + hash: normalizedAlgorithm.hash, + }; + + const key = constructKey( + "public", + extractable, + usageIntersection(keyUsages, recognisedUsages), + algorithm, + handle, + ); + + return key; + } + case "jwk": { + // 1. + const jwk = keyData; + + // 2. + if (jwk.d !== undefined) { + if ( ArrayPrototypeFind( keyUsages, (u) => !ArrayPrototypeIncludes( - SUPPORTED_KEY_USAGES[normalizedAlgorithm.name].public, + SUPPORTED_KEY_USAGES[normalizedAlgorithm.name].private, u, ), ) !== undefined ) { throw new DOMException("Invalid key usages", "SyntaxError"); } + } else if ( + ArrayPrototypeFind( + keyUsages, + (u) => + !ArrayPrototypeIncludes( + SUPPORTED_KEY_USAGES[normalizedAlgorithm.name].public, + u, + ), + ) !== undefined + ) { + throw new DOMException("Invalid key usages", "SyntaxError"); + } - // 3. - if (StringPrototypeToUpperCase(jwk.kty) !== "RSA") { + // 3. + if (StringPrototypeToUpperCase(jwk.kty) !== "RSA") { + throw new DOMException( + "'kty' property of JsonWebKey must be 'RSA'", + "DataError", + ); + } + + // 4. + if ( + keyUsages.length > 0 && jwk.use !== undefined && + StringPrototypeToLowerCase(jwk.use) !== + SUPPORTED_KEY_USAGES[normalizedAlgorithm.name].jwkUse + ) { + throw new DOMException( + `'use' property of JsonWebKey must be '${ + SUPPORTED_KEY_USAGES[normalizedAlgorithm.name].jwkUse + }'`, + "DataError", + ); + } + + // 5. + if (jwk.key_ops !== undefined) { + if ( + ArrayPrototypeFind( + jwk.key_ops, + (u) => !ArrayPrototypeIncludes(recognisedUsages, u), + ) !== undefined + ) { throw new DOMException( - "'kty' property of JsonWebKey must be 'RSA'", + "'key_ops' property of JsonWebKey is invalid", "DataError", ); } - // 4. if ( - keyUsages.length > 0 && jwk.use !== undefined && - StringPrototypeToLowerCase(jwk.use) !== - SUPPORTED_KEY_USAGES[normalizedAlgorithm.name].jwkUse + !ArrayPrototypeEvery( + jwk.key_ops, + (u) => ArrayPrototypeIncludes(keyUsages, u), + ) ) { throw new DOMException( - `'use' property of JsonWebKey must be '${ - SUPPORTED_KEY_USAGES[normalizedAlgorithm.name].jwkUse - }'`, + "'key_ops' property of JsonWebKey is invalid", "DataError", ); } + } - // 5. - if (jwk.key_ops !== undefined) { - if ( - ArrayPrototypeFind( - jwk.key_ops, - (u) => !ArrayPrototypeIncludes(recognisedUsages, u), - ) !== undefined - ) { + if (jwk.ext === false && extractable === true) { + throw new DOMException( + "'ext' property of JsonWebKey must not be false if extractable is true", + "DataError", + ); + } + + // 7. + let hash; + + // 8. + if (normalizedAlgorithm.name === "RSASSA-PKCS1-v1_5") { + switch (jwk.alg) { + case undefined: + hash = undefined; + break; + case "RS1": + hash = "SHA-1"; + break; + case "RS256": + hash = "SHA-256"; + break; + case "RS384": + hash = "SHA-384"; + break; + case "RS512": + hash = "SHA-512"; + break; + default: throw new DOMException( - "'key_ops' property of JsonWebKey is invalid", + `'alg' property of JsonWebKey must be one of 'RS1', 'RS256', 'RS384', 'RS512'`, "DataError", ); - } + } + } else if (normalizedAlgorithm.name === "RSA-PSS") { + switch (jwk.alg) { + case undefined: + hash = undefined; + break; + case "PS1": + hash = "SHA-1"; + break; + case "PS256": + hash = "SHA-256"; + break; + case "PS384": + hash = "SHA-384"; + break; + case "PS512": + hash = "SHA-512"; + break; + default: + throw new DOMException( + `'alg' property of JsonWebKey must be one of 'PS1', 'PS256', 'PS384', 'PS512'`, + "DataError", + ); + } + } else { + switch (jwk.alg) { + case undefined: + hash = undefined; + break; + case "RSA-OAEP": + hash = "SHA-1"; + break; + case "RSA-OAEP-256": + hash = "SHA-256"; + break; + case "RSA-OAEP-384": + hash = "SHA-384"; + break; + case "RSA-OAEP-512": + hash = "SHA-512"; + break; + default: + throw new DOMException( + `'alg' property of JsonWebKey must be one of 'RSA-OAEP', 'RSA-OAEP-256', 'RSA-OAEP-384', or 'RSA-OAEP-512'`, + "DataError", + ); + } + } - if ( - !ArrayPrototypeEvery( - jwk.key_ops, - (u) => ArrayPrototypeIncludes(keyUsages, u), - ) - ) { + // 9. + if (hash !== undefined) { + // 9.1. + const normalizedHash = normalizeAlgorithm(hash, "digest"); + + // 9.2. + if (normalizedHash.name !== normalizedAlgorithm.hash.name) { + throw new DOMException( + `'alg' property of JsonWebKey must be '${normalizedAlgorithm.name}'`, + "DataError", + ); + } + } + + // 10. + if (jwk.d !== undefined) { + // Private key + const optimizationsPresent = jwk.p !== undefined || + jwk.q !== undefined || jwk.dp !== undefined || + jwk.dq !== undefined || jwk.qi !== undefined; + if (optimizationsPresent) { + if (jwk.q === undefined) { + throw new DOMException( + "'q' property of JsonWebKey is required for private keys", + "DataError", + ); + } + if (jwk.dp === undefined) { + throw new DOMException( + "'dp' property of JsonWebKey is required for private keys", + "DataError", + ); + } + if (jwk.dq === undefined) { + throw new DOMException( + "'dq' property of JsonWebKey is required for private keys", + "DataError", + ); + } + if (jwk.qi === undefined) { throw new DOMException( - "'key_ops' property of JsonWebKey is invalid", + "'qi' property of JsonWebKey is required for private keys", "DataError", ); } + if (jwk.oth !== undefined) { + throw new DOMException( + "'oth' property of JsonWebKey is not supported", + "NotSupportedError", + ); + } + } else { + throw new DOMException( + "only optimized private keys are supported", + "NotSupportedError", + ); } - if (jwk.ext === false && extractable === true) { + const { modulusLength, publicExponent, rawData } = ops + .op_crypto_import_key( + { + algorithm: normalizedAlgorithm.name, + hash: normalizedAlgorithm.hash.name, + }, + { jwkPrivateRsa: jwk }, + ); + + const handle = {}; + WeakMapPrototypeSet(KEY_STORE, handle, rawData); + + const algorithm = { + name: normalizedAlgorithm.name, + modulusLength, + publicExponent, + hash: normalizedAlgorithm.hash, + }; + + const key = constructKey( + "private", + extractable, + usageIntersection(keyUsages, recognisedUsages), + algorithm, + handle, + ); + + return key; + } else { + // Validate that this is a valid public key. + if (jwk.n === undefined) { throw new DOMException( - "'ext' property of JsonWebKey must not be false if extractable is true", + "'n' property of JsonWebKey is required for public keys", "DataError", ); } - - // 7. - let hash; - - // 8. - if (normalizedAlgorithm.name === "RSASSA-PKCS1-v1_5") { - switch (jwk.alg) { - case undefined: - hash = undefined; - break; - case "RS1": - hash = "SHA-1"; - break; - case "RS256": - hash = "SHA-256"; - break; - case "RS384": - hash = "SHA-384"; - break; - case "RS512": - hash = "SHA-512"; - break; - default: - throw new DOMException( - `'alg' property of JsonWebKey must be one of 'RS1', 'RS256', 'RS384', 'RS512'`, - "DataError", - ); - } - } else if (normalizedAlgorithm.name === "RSA-PSS") { - switch (jwk.alg) { - case undefined: - hash = undefined; - break; - case "PS1": - hash = "SHA-1"; - break; - case "PS256": - hash = "SHA-256"; - break; - case "PS384": - hash = "SHA-384"; - break; - case "PS512": - hash = "SHA-512"; - break; - default: - throw new DOMException( - `'alg' property of JsonWebKey must be one of 'PS1', 'PS256', 'PS384', 'PS512'`, - "DataError", - ); - } - } else { - switch (jwk.alg) { - case undefined: - hash = undefined; - break; - case "RSA-OAEP": - hash = "SHA-1"; - break; - case "RSA-OAEP-256": - hash = "SHA-256"; - break; - case "RSA-OAEP-384": - hash = "SHA-384"; - break; - case "RSA-OAEP-512": - hash = "SHA-512"; - break; - default: - throw new DOMException( - `'alg' property of JsonWebKey must be one of 'RSA-OAEP', 'RSA-OAEP-256', 'RSA-OAEP-384', or 'RSA-OAEP-512'`, - "DataError", - ); - } - } - - // 9. - if (hash !== undefined) { - // 9.1. - const normalizedHash = normalizeAlgorithm(hash, "digest"); - - // 9.2. - if (normalizedHash.name !== normalizedAlgorithm.hash.name) { - throw new DOMException( - `'alg' property of JsonWebKey must be '${normalizedAlgorithm.name}'`, - "DataError", - ); - } + if (jwk.e === undefined) { + throw new DOMException( + "'e' property of JsonWebKey is required for public keys", + "DataError", + ); } - // 10. - if (jwk.d !== undefined) { - // Private key - const optimizationsPresent = jwk.p !== undefined || - jwk.q !== undefined || jwk.dp !== undefined || - jwk.dq !== undefined || jwk.qi !== undefined; - if (optimizationsPresent) { - if (jwk.q === undefined) { - throw new DOMException( - "'q' property of JsonWebKey is required for private keys", - "DataError", - ); - } - if (jwk.dp === undefined) { - throw new DOMException( - "'dp' property of JsonWebKey is required for private keys", - "DataError", - ); - } - if (jwk.dq === undefined) { - throw new DOMException( - "'dq' property of JsonWebKey is required for private keys", - "DataError", - ); - } - if (jwk.qi === undefined) { - throw new DOMException( - "'qi' property of JsonWebKey is required for private keys", - "DataError", - ); - } - if (jwk.oth !== undefined) { - throw new DOMException( - "'oth' property of JsonWebKey is not supported", - "NotSupportedError", - ); - } - } else { - throw new DOMException( - "only optimized private keys are supported", - "NotSupportedError", - ); - } - - const { modulusLength, publicExponent, rawData } = ops - .op_crypto_import_key( - { - algorithm: normalizedAlgorithm.name, - hash: normalizedAlgorithm.hash.name, - }, - { jwkPrivateRsa: jwk }, - ); - - const handle = {}; - WeakMapPrototypeSet(KEY_STORE, handle, rawData); - - const algorithm = { - name: normalizedAlgorithm.name, - modulusLength, - publicExponent, - hash: normalizedAlgorithm.hash, - }; - - const key = constructKey( - "private", - extractable, - usageIntersection(keyUsages, recognisedUsages), - algorithm, - handle, + const { modulusLength, publicExponent, rawData } = ops + .op_crypto_import_key( + { + algorithm: normalizedAlgorithm.name, + hash: normalizedAlgorithm.hash.name, + }, + { jwkPublicRsa: jwk }, ); - return key; - } else { - // Validate that this is a valid public key. - if (jwk.n === undefined) { - throw new DOMException( - "'n' property of JsonWebKey is required for public keys", - "DataError", - ); - } - if (jwk.e === undefined) { - throw new DOMException( - "'e' property of JsonWebKey is required for public keys", - "DataError", - ); - } + const handle = {}; + WeakMapPrototypeSet(KEY_STORE, handle, rawData); - const { modulusLength, publicExponent, rawData } = ops - .op_crypto_import_key( - { - algorithm: normalizedAlgorithm.name, - hash: normalizedAlgorithm.hash.name, - }, - { jwkPublicRsa: jwk }, - ); + const algorithm = { + name: normalizedAlgorithm.name, + modulusLength, + publicExponent, + hash: normalizedAlgorithm.hash, + }; - const handle = {}; - WeakMapPrototypeSet(KEY_STORE, handle, rawData); - - const algorithm = { - name: normalizedAlgorithm.name, - modulusLength, - publicExponent, - hash: normalizedAlgorithm.hash, - }; - - const key = constructKey( - "public", - extractable, - usageIntersection(keyUsages, recognisedUsages), - algorithm, - handle, - ); + const key = constructKey( + "public", + extractable, + usageIntersection(keyUsages, recognisedUsages), + algorithm, + handle, + ); - return key; - } + return key; } - default: - throw new DOMException("Not implemented", "NotSupportedError"); } + default: + throw new DOMException("Not implemented", "NotSupportedError"); + } +} + +function importKeyHKDF( + format, + keyData, + extractable, + keyUsages, +) { + if (format !== "raw") { + throw new DOMException("Format not supported", "NotSupportedError"); } - function importKeyHKDF( - format, - keyData, - extractable, - keyUsages, + // 1. + if ( + ArrayPrototypeFind( + keyUsages, + (u) => !ArrayPrototypeIncludes(["deriveKey", "deriveBits"], u), + ) !== undefined ) { - if (format !== "raw") { - throw new DOMException("Format not supported", "NotSupportedError"); - } + throw new DOMException("Invalid key usages", "SyntaxError"); + } - // 1. - if ( - ArrayPrototypeFind( - keyUsages, - (u) => !ArrayPrototypeIncludes(["deriveKey", "deriveBits"], u), - ) !== undefined - ) { - throw new DOMException("Invalid key usages", "SyntaxError"); - } + // 2. + if (extractable !== false) { + throw new DOMException( + "Key must not be extractable", + "SyntaxError", + ); + } - // 2. - if (extractable !== false) { - throw new DOMException( - "Key must not be extractable", - "SyntaxError", - ); - } + // 3. + const handle = {}; + WeakMapPrototypeSet(KEY_STORE, handle, { + type: "secret", + data: keyData, + }); - // 3. - const handle = {}; - WeakMapPrototypeSet(KEY_STORE, handle, { - type: "secret", - data: keyData, - }); + // 4-8. + const algorithm = { + name: "HKDF", + }; + const key = constructKey( + "secret", + false, + usageIntersection(keyUsages, recognisedUsages), + algorithm, + handle, + ); + + // 9. + return key; +} + +function importKeyPBKDF2( + format, + keyData, + extractable, + keyUsages, +) { + // 1. + if (format !== "raw") { + throw new DOMException("Format not supported", "NotSupportedError"); + } - // 4-8. - const algorithm = { - name: "HKDF", - }; - const key = constructKey( - "secret", - false, - usageIntersection(keyUsages, recognisedUsages), - algorithm, - handle, - ); + // 2. + if ( + ArrayPrototypeFind( + keyUsages, + (u) => !ArrayPrototypeIncludes(["deriveKey", "deriveBits"], u), + ) !== undefined + ) { + throw new DOMException("Invalid key usages", "SyntaxError"); + } - // 9. - return key; + // 3. + if (extractable !== false) { + throw new DOMException( + "Key must not be extractable", + "SyntaxError", + ); } - function importKeyPBKDF2( - format, - keyData, - extractable, - keyUsages, - ) { - // 1. - if (format !== "raw") { - throw new DOMException("Format not supported", "NotSupportedError"); - } + // 4. + const handle = {}; + WeakMapPrototypeSet(KEY_STORE, handle, { + type: "secret", + data: keyData, + }); - // 2. - if ( - ArrayPrototypeFind( - keyUsages, - (u) => !ArrayPrototypeIncludes(["deriveKey", "deriveBits"], u), - ) !== undefined - ) { - throw new DOMException("Invalid key usages", "SyntaxError"); - } + // 5-9. + const algorithm = { + name: "PBKDF2", + }; + const key = constructKey( + "secret", + false, + usageIntersection(keyUsages, recognisedUsages), + algorithm, + handle, + ); + + // 10. + return key; +} + +function exportKeyHMAC(format, key, innerKey) { + // 1. + if (innerKey == null) { + throw new DOMException("Key is not available", "OperationError"); + } + switch (format) { // 3. - if (extractable !== false) { - throw new DOMException( - "Key must not be extractable", - "SyntaxError", - ); + case "raw": { + const bits = innerKey.data; + for (let _i = 7 & (8 - bits.length % 8); _i > 0; _i--) { + bits.push(0); + } + // 4-5. + return bits.buffer; } + case "jwk": { + // 1-2. + const jwk = { + kty: "oct", + }; - // 4. - const handle = {}; - WeakMapPrototypeSet(KEY_STORE, handle, { - type: "secret", - data: keyData, - }); + // 3. + const data = ops.op_crypto_export_key({ + format: "jwksecret", + algorithm: key[_algorithm].name, + }, innerKey); + jwk.k = data.k; + + // 4. + const algorithm = key[_algorithm]; + // 5. + const hash = algorithm.hash; + // 6. + switch (hash.name) { + case "SHA-1": + jwk.alg = "HS1"; + break; + case "SHA-256": + jwk.alg = "HS256"; + break; + case "SHA-384": + jwk.alg = "HS384"; + break; + case "SHA-512": + jwk.alg = "HS512"; + break; + default: + throw new DOMException( + "Hash algorithm not supported", + "NotSupportedError", + ); + } + // 7. + jwk.key_ops = key.usages; + // 8. + jwk.ext = key[_extractable]; + // 9. + return jwk; + } + default: + throw new DOMException("Not implemented", "NotSupportedError"); + } +} - // 5-9. - const algorithm = { - name: "PBKDF2", - }; - const key = constructKey( - "secret", - false, - usageIntersection(keyUsages, recognisedUsages), - algorithm, - handle, - ); +function exportKeyRSA(format, key, innerKey) { + switch (format) { + case "pkcs8": { + // 1. + if (key[_type] !== "private") { + throw new DOMException( + "Key is not a private key", + "InvalidAccessError", + ); + } - // 10. - return key; - } + // 2. + const data = ops.op_crypto_export_key({ + algorithm: key[_algorithm].name, + format: "pkcs8", + }, innerKey); - function exportKeyHMAC(format, key, innerKey) { - // 1. - if (innerKey == null) { - throw new DOMException("Key is not available", "OperationError"); + // 3. + return data.buffer; } + case "spki": { + // 1. + if (key[_type] !== "public") { + throw new DOMException( + "Key is not a public key", + "InvalidAccessError", + ); + } + + // 2. + const data = ops.op_crypto_export_key({ + algorithm: key[_algorithm].name, + format: "spki", + }, innerKey); - switch (format) { // 3. - case "raw": { - const bits = innerKey.data; - for (let _i = 7 & (8 - bits.length % 8); _i > 0; _i--) { - bits.push(0); - } - // 4-5. - return bits.buffer; - } - case "jwk": { - // 1-2. - const jwk = { - kty: "oct", - }; + return data.buffer; + } + case "jwk": { + // 1-2. + const jwk = { + kty: "RSA", + }; - // 3. - const data = ops.op_crypto_export_key({ - format: "jwksecret", - algorithm: key[_algorithm].name, - }, innerKey); - jwk.k = data.k; + // 3. + const hash = key[_algorithm].hash.name; - // 4. - const algorithm = key[_algorithm]; - // 5. - const hash = algorithm.hash; - // 6. - switch (hash.name) { + // 4. + if (key[_algorithm].name === "RSASSA-PKCS1-v1_5") { + switch (hash) { case "SHA-1": - jwk.alg = "HS1"; + jwk.alg = "RS1"; break; case "SHA-256": - jwk.alg = "HS256"; + jwk.alg = "RS256"; break; case "SHA-384": - jwk.alg = "HS384"; + jwk.alg = "RS384"; break; case "SHA-512": - jwk.alg = "HS512"; + jwk.alg = "RS512"; break; default: throw new DOMException( @@ -3880,848 +3957,763 @@ "NotSupportedError", ); } - // 7. - jwk.key_ops = key.usages; - // 8. - jwk.ext = key[_extractable]; - // 9. - return jwk; - } - default: - throw new DOMException("Not implemented", "NotSupportedError"); - } - } - - function exportKeyRSA(format, key, innerKey) { - switch (format) { - case "pkcs8": { - // 1. - if (key[_type] !== "private") { - throw new DOMException( - "Key is not a private key", - "InvalidAccessError", - ); + } else if (key[_algorithm].name === "RSA-PSS") { + switch (hash) { + case "SHA-1": + jwk.alg = "PS1"; + break; + case "SHA-256": + jwk.alg = "PS256"; + break; + case "SHA-384": + jwk.alg = "PS384"; + break; + case "SHA-512": + jwk.alg = "PS512"; + break; + default: + throw new DOMException( + "Hash algorithm not supported", + "NotSupportedError", + ); } - - // 2. - const data = ops.op_crypto_export_key({ - algorithm: key[_algorithm].name, - format: "pkcs8", - }, innerKey); - - // 3. - return data.buffer; - } - case "spki": { - // 1. - if (key[_type] !== "public") { - throw new DOMException( - "Key is not a public key", - "InvalidAccessError", - ); + } else { + switch (hash) { + case "SHA-1": + jwk.alg = "RSA-OAEP"; + break; + case "SHA-256": + jwk.alg = "RSA-OAEP-256"; + break; + case "SHA-384": + jwk.alg = "RSA-OAEP-384"; + break; + case "SHA-512": + jwk.alg = "RSA-OAEP-512"; + break; + default: + throw new DOMException( + "Hash algorithm not supported", + "NotSupportedError", + ); } + } - // 2. - const data = ops.op_crypto_export_key({ - algorithm: key[_algorithm].name, - format: "spki", - }, innerKey); + // 5-6. + const data = ops.op_crypto_export_key({ + format: key[_type] === "private" ? "jwkprivate" : "jwkpublic", + algorithm: key[_algorithm].name, + }, innerKey); + ObjectAssign(jwk, data); - // 3. - return data.buffer; - } - case "jwk": { - // 1-2. - const jwk = { - kty: "RSA", - }; + // 7. + jwk.key_ops = key.usages; - // 3. - const hash = key[_algorithm].hash.name; + // 8. + jwk.ext = key[_extractable]; - // 4. - if (key[_algorithm].name === "RSASSA-PKCS1-v1_5") { - switch (hash) { - case "SHA-1": - jwk.alg = "RS1"; - break; - case "SHA-256": - jwk.alg = "RS256"; - break; - case "SHA-384": - jwk.alg = "RS384"; - break; - case "SHA-512": - jwk.alg = "RS512"; - break; - default: - throw new DOMException( - "Hash algorithm not supported", - "NotSupportedError", - ); - } - } else if (key[_algorithm].name === "RSA-PSS") { - switch (hash) { - case "SHA-1": - jwk.alg = "PS1"; - break; - case "SHA-256": - jwk.alg = "PS256"; - break; - case "SHA-384": - jwk.alg = "PS384"; - break; - case "SHA-512": - jwk.alg = "PS512"; - break; - default: - throw new DOMException( - "Hash algorithm not supported", - "NotSupportedError", - ); - } - } else { - switch (hash) { - case "SHA-1": - jwk.alg = "RSA-OAEP"; - break; - case "SHA-256": - jwk.alg = "RSA-OAEP-256"; - break; - case "SHA-384": - jwk.alg = "RSA-OAEP-384"; - break; - case "SHA-512": - jwk.alg = "RSA-OAEP-512"; - break; - default: - throw new DOMException( - "Hash algorithm not supported", - "NotSupportedError", - ); - } - } + return jwk; + } + default: + throw new DOMException("Not implemented", "NotSupportedError"); + } +} - // 5-6. - const data = ops.op_crypto_export_key({ - format: key[_type] === "private" ? "jwkprivate" : "jwkpublic", - algorithm: key[_algorithm].name, - }, innerKey); - ObjectAssign(jwk, data); +function exportKeyEd25519(format, key, innerKey) { + switch (format) { + case "raw": { + // 1. + if (key[_type] !== "public") { + throw new DOMException( + "Key is not a public key", + "InvalidAccessError", + ); + } - // 7. - jwk.key_ops = key.usages; + // 2-3. + return innerKey.buffer; + } + case "spki": { + // 1. + if (key[_type] !== "public") { + throw new DOMException( + "Key is not a public key", + "InvalidAccessError", + ); + } - // 8. - jwk.ext = key[_extractable]; + const spkiDer = ops.op_export_spki_ed25519(innerKey); + return spkiDer.buffer; + } + case "pkcs8": { + // 1. + if (key[_type] !== "private") { + throw new DOMException( + "Key is not a public key", + "InvalidAccessError", + ); + } - return jwk; + const pkcs8Der = ops.op_export_pkcs8_ed25519( + new Uint8Array([0x04, 0x22, ...new SafeArrayIterator(innerKey)]), + ); + pkcs8Der[15] = 0x20; + return pkcs8Der.buffer; + } + case "jwk": { + const x = key[_type] === "private" + ? ops.op_jwk_x_ed25519(innerKey) + : ops.op_crypto_base64url_encode(innerKey); + const jwk = { + kty: "OKP", + alg: "EdDSA", + crv: "Ed25519", + x, + "key_ops": key.usages, + ext: key[_extractable], + }; + if (key[_type] === "private") { + jwk.d = ops.op_crypto_base64url_encode(innerKey); } - default: - throw new DOMException("Not implemented", "NotSupportedError"); + return jwk; } + default: + throw new DOMException("Not implemented", "NotSupportedError"); } +} - function exportKeyEd25519(format, key, innerKey) { - switch (format) { - case "raw": { - // 1. - if (key[_type] !== "public") { - throw new DOMException( - "Key is not a public key", - "InvalidAccessError", - ); - } - - // 2-3. - return innerKey.buffer; +function exportKeyX25519(format, key, innerKey) { + switch (format) { + case "raw": { + // 1. + if (key[_type] !== "public") { + throw new DOMException( + "Key is not a public key", + "InvalidAccessError", + ); } - case "spki": { - // 1. - if (key[_type] !== "public") { - throw new DOMException( - "Key is not a public key", - "InvalidAccessError", - ); - } - const spkiDer = ops.op_export_spki_ed25519(innerKey); - return spkiDer.buffer; + // 2-3. + return innerKey.buffer; + } + case "spki": { + // 1. + if (key[_type] !== "public") { + throw new DOMException( + "Key is not a public key", + "InvalidAccessError", + ); } - case "pkcs8": { - // 1. - if (key[_type] !== "private") { - throw new DOMException( - "Key is not a public key", - "InvalidAccessError", - ); - } - const pkcs8Der = ops.op_export_pkcs8_ed25519( - new Uint8Array([0x04, 0x22, ...new SafeArrayIterator(innerKey)]), + const spkiDer = ops.op_export_spki_x25519(innerKey); + return spkiDer.buffer; + } + case "pkcs8": { + // 1. + if (key[_type] !== "private") { + throw new DOMException( + "Key is not a public key", + "InvalidAccessError", ); - pkcs8Der[15] = 0x20; - return pkcs8Der.buffer; - } - case "jwk": { - const x = key[_type] === "private" - ? ops.op_jwk_x_ed25519(innerKey) - : ops.op_crypto_base64url_encode(innerKey); - const jwk = { - kty: "OKP", - alg: "EdDSA", - crv: "Ed25519", - x, - "key_ops": key.usages, - ext: key[_extractable], - }; - if (key[_type] === "private") { - jwk.d = ops.op_crypto_base64url_encode(innerKey); - } - return jwk; } - default: + + const pkcs8Der = ops.op_export_pkcs8_x25519( + new Uint8Array([0x04, 0x22, ...new SafeArrayIterator(innerKey)]), + ); + pkcs8Der[15] = 0x20; + return pkcs8Der.buffer; + } + case "jwk": { + if (key[_type] === "private") { throw new DOMException("Not implemented", "NotSupportedError"); + } + const x = ops.op_crypto_base64url_encode(innerKey); + const jwk = { + kty: "OKP", + crv: "X25519", + x, + "key_ops": key.usages, + ext: key[_extractable], + }; + return jwk; } + default: + throw new DOMException("Not implemented", "NotSupportedError"); } +} - function exportKeyX25519(format, key, innerKey) { - switch (format) { - case "raw": { - // 1. - if (key[_type] !== "public") { - throw new DOMException( - "Key is not a public key", - "InvalidAccessError", - ); - } - - // 2-3. - return innerKey.buffer; +function exportKeyEC(format, key, innerKey) { + switch (format) { + case "raw": { + // 1. + if (key[_type] !== "public") { + throw new DOMException( + "Key is not a public key", + "InvalidAccessError", + ); } - case "spki": { - // 1. - if (key[_type] !== "public") { - throw new DOMException( - "Key is not a public key", - "InvalidAccessError", - ); - } - const spkiDer = ops.op_export_spki_x25519(innerKey); - return spkiDer.buffer; + // 2. + const data = ops.op_crypto_export_key({ + algorithm: key[_algorithm].name, + namedCurve: key[_algorithm].namedCurve, + format: "raw", + }, innerKey); + + return data.buffer; + } + case "pkcs8": { + // 1. + if (key[_type] !== "private") { + throw new DOMException( + "Key is not a private key", + "InvalidAccessError", + ); } - case "pkcs8": { - // 1. - if (key[_type] !== "private") { - throw new DOMException( - "Key is not a public key", - "InvalidAccessError", - ); - } - const pkcs8Der = ops.op_export_pkcs8_x25519( - new Uint8Array([0x04, 0x22, ...new SafeArrayIterator(innerKey)]), + // 2. + const data = ops.op_crypto_export_key({ + algorithm: key[_algorithm].name, + namedCurve: key[_algorithm].namedCurve, + format: "pkcs8", + }, innerKey); + + return data.buffer; + } + case "spki": { + // 1. + if (key[_type] !== "public") { + throw new DOMException( + "Key is not a public key", + "InvalidAccessError", ); - pkcs8Der[15] = 0x20; - return pkcs8Der.buffer; } - case "jwk": { - if (key[_type] === "private") { - throw new DOMException("Not implemented", "NotSupportedError"); - } - const x = ops.op_crypto_base64url_encode(innerKey); + + // 2. + const data = ops.op_crypto_export_key({ + algorithm: key[_algorithm].name, + namedCurve: key[_algorithm].namedCurve, + format: "spki", + }, innerKey); + + return data.buffer; + } + case "jwk": { + if (key[_algorithm].name == "ECDSA") { + // 1-2. const jwk = { - kty: "OKP", - crv: "X25519", - x, - "key_ops": key.usages, - ext: key[_extractable], + kty: "EC", }; - return jwk; - } - default: - throw new DOMException("Not implemented", "NotSupportedError"); - } - } - function exportKeyEC(format, key, innerKey) { - switch (format) { - case "raw": { - // 1. - if (key[_type] !== "public") { - throw new DOMException( - "Key is not a public key", - "InvalidAccessError", - ); - } + // 3.1 + jwk.crv = key[_algorithm].namedCurve; - // 2. - const data = ops.op_crypto_export_key({ - algorithm: key[_algorithm].name, - namedCurve: key[_algorithm].namedCurve, - format: "raw", - }, innerKey); + // Missing from spec + let algNamedCurve; - return data.buffer; - } - case "pkcs8": { - // 1. - if (key[_type] !== "private") { - throw new DOMException( - "Key is not a private key", - "InvalidAccessError", - ); + switch (key[_algorithm].namedCurve) { + case "P-256": { + algNamedCurve = "ES256"; + break; + } + case "P-384": { + algNamedCurve = "ES384"; + break; + } + case "P-521": { + algNamedCurve = "ES512"; + break; + } + default: + throw new DOMException( + "Curve algorithm not supported", + "DataError", + ); } - // 2. - const data = ops.op_crypto_export_key({ - algorithm: key[_algorithm].name, - namedCurve: key[_algorithm].namedCurve, - format: "pkcs8", - }, innerKey); - - return data.buffer; - } - case "spki": { - // 1. - if (key[_type] !== "public") { - throw new DOMException( - "Key is not a public key", - "InvalidAccessError", - ); - } + jwk.alg = algNamedCurve; - // 2. + // 3.2 - 3.4. const data = ops.op_crypto_export_key({ + format: key[_type] === "private" ? "jwkprivate" : "jwkpublic", algorithm: key[_algorithm].name, namedCurve: key[_algorithm].namedCurve, - format: "spki", }, innerKey); + ObjectAssign(jwk, data); - return data.buffer; - } - case "jwk": { - if (key[_algorithm].name == "ECDSA") { - // 1-2. - const jwk = { - kty: "EC", - }; - - // 3.1 - jwk.crv = key[_algorithm].namedCurve; - - // Missing from spec - let algNamedCurve; - - switch (key[_algorithm].namedCurve) { - case "P-256": { - algNamedCurve = "ES256"; - break; - } - case "P-384": { - algNamedCurve = "ES384"; - break; - } - case "P-521": { - algNamedCurve = "ES512"; - break; - } - default: - throw new DOMException( - "Curve algorithm not supported", - "DataError", - ); - } - - jwk.alg = algNamedCurve; - - // 3.2 - 3.4. - const data = ops.op_crypto_export_key({ - format: key[_type] === "private" ? "jwkprivate" : "jwkpublic", - algorithm: key[_algorithm].name, - namedCurve: key[_algorithm].namedCurve, - }, innerKey); - ObjectAssign(jwk, data); - - // 4. - jwk.key_ops = key.usages; + // 4. + jwk.key_ops = key.usages; - // 5. - jwk.ext = key[_extractable]; + // 5. + jwk.ext = key[_extractable]; - return jwk; - } else { // ECDH - // 1-2. - const jwk = { - kty: "EC", - }; + return jwk; + } else { // ECDH + // 1-2. + const jwk = { + kty: "EC", + }; - // missing step from spec - jwk.alg = "ECDH"; + // missing step from spec + jwk.alg = "ECDH"; - // 3.1 - jwk.crv = key[_algorithm].namedCurve; + // 3.1 + jwk.crv = key[_algorithm].namedCurve; - // 3.2 - 3.4 - const data = ops.op_crypto_export_key({ - format: key[_type] === "private" ? "jwkprivate" : "jwkpublic", - algorithm: key[_algorithm].name, - namedCurve: key[_algorithm].namedCurve, - }, innerKey); - ObjectAssign(jwk, data); + // 3.2 - 3.4 + const data = ops.op_crypto_export_key({ + format: key[_type] === "private" ? "jwkprivate" : "jwkpublic", + algorithm: key[_algorithm].name, + namedCurve: key[_algorithm].namedCurve, + }, innerKey); + ObjectAssign(jwk, data); - // 4. - jwk.key_ops = key.usages; + // 4. + jwk.key_ops = key.usages; - // 5. - jwk.ext = key[_extractable]; + // 5. + jwk.ext = key[_extractable]; - return jwk; - } + return jwk; } - default: - throw new DOMException("Not implemented", "NotSupportedError"); } + default: + throw new DOMException("Not implemented", "NotSupportedError"); } +} - async function generateKeyAES(normalizedAlgorithm, extractable, usages) { - const algorithmName = normalizedAlgorithm.name; - - // 2. - if (!ArrayPrototypeIncludes([128, 192, 256], normalizedAlgorithm.length)) { - throw new DOMException("Invalid key length", "OperationError"); - } +async function generateKeyAES(normalizedAlgorithm, extractable, usages) { + const algorithmName = normalizedAlgorithm.name; - // 3. - const keyData = await core.opAsync("op_crypto_generate_key", { - algorithm: "AES", - length: normalizedAlgorithm.length, - }); - const handle = {}; - WeakMapPrototypeSet(KEY_STORE, handle, { - type: "secret", - data: keyData, - }); + // 2. + if (!ArrayPrototypeIncludes([128, 192, 256], normalizedAlgorithm.length)) { + throw new DOMException("Invalid key length", "OperationError"); + } - // 6-8. - const algorithm = { - name: algorithmName, - length: normalizedAlgorithm.length, - }; + // 3. + const keyData = await core.opAsync("op_crypto_generate_key", { + algorithm: "AES", + length: normalizedAlgorithm.length, + }); + const handle = {}; + WeakMapPrototypeSet(KEY_STORE, handle, { + type: "secret", + data: keyData, + }); + + // 6-8. + const algorithm = { + name: algorithmName, + length: normalizedAlgorithm.length, + }; - // 9-11. - const key = constructKey( - "secret", - extractable, - usages, - algorithm, - handle, - ); + // 9-11. + const key = constructKey( + "secret", + extractable, + usages, + algorithm, + handle, + ); + + // 12. + return key; +} + +async function deriveBits(normalizedAlgorithm, baseKey, length) { + switch (normalizedAlgorithm.name) { + case "PBKDF2": { + // 1. + if (length == null || length == 0 || length % 8 !== 0) { + throw new DOMException("Invalid length", "OperationError"); + } - // 12. - return key; - } + if (normalizedAlgorithm.iterations == 0) { + throw new DOMException( + "iterations must not be zero", + "OperationError", + ); + } - async function deriveBits(normalizedAlgorithm, baseKey, length) { - switch (normalizedAlgorithm.name) { - case "PBKDF2": { - // 1. - if (length == null || length == 0 || length % 8 !== 0) { - throw new DOMException("Invalid length", "OperationError"); - } + const handle = baseKey[_handle]; + const keyData = WeakMapPrototypeGet(KEY_STORE, handle); - if (normalizedAlgorithm.iterations == 0) { - throw new DOMException( - "iterations must not be zero", - "OperationError", - ); - } + normalizedAlgorithm.salt = copyBuffer(normalizedAlgorithm.salt); - const handle = baseKey[_handle]; - const keyData = WeakMapPrototypeGet(KEY_STORE, handle); + const buf = await core.opAsync("op_crypto_derive_bits", { + key: keyData, + algorithm: "PBKDF2", + hash: normalizedAlgorithm.hash.name, + iterations: normalizedAlgorithm.iterations, + length, + }, normalizedAlgorithm.salt); - normalizedAlgorithm.salt = copyBuffer(normalizedAlgorithm.salt); + return buf.buffer; + } + case "ECDH": { + // 1. + if (baseKey[_type] !== "private") { + throw new DOMException("Invalid key type", "InvalidAccessError"); + } + // 2. + const publicKey = normalizedAlgorithm.public; + // 3. + if (publicKey[_type] !== "public") { + throw new DOMException("Invalid key type", "InvalidAccessError"); + } + // 4. + if (publicKey[_algorithm].name !== baseKey[_algorithm].name) { + throw new DOMException( + "Algorithm mismatch", + "InvalidAccessError", + ); + } + // 5. + if ( + publicKey[_algorithm].namedCurve !== baseKey[_algorithm].namedCurve + ) { + throw new DOMException( + "namedCurve mismatch", + "InvalidAccessError", + ); + } + // 6. + if ( + ArrayPrototypeIncludes( + supportedNamedCurves, + publicKey[_algorithm].namedCurve, + ) + ) { + const baseKeyhandle = baseKey[_handle]; + const baseKeyData = WeakMapPrototypeGet(KEY_STORE, baseKeyhandle); + const publicKeyhandle = publicKey[_handle]; + const publicKeyData = WeakMapPrototypeGet(KEY_STORE, publicKeyhandle); const buf = await core.opAsync("op_crypto_derive_bits", { - key: keyData, - algorithm: "PBKDF2", - hash: normalizedAlgorithm.hash.name, - iterations: normalizedAlgorithm.iterations, + key: baseKeyData, + publicKey: publicKeyData, + algorithm: "ECDH", + namedCurve: publicKey[_algorithm].namedCurve, length, - }, normalizedAlgorithm.salt); - - return buf.buffer; - } - case "ECDH": { - // 1. - if (baseKey[_type] !== "private") { - throw new DOMException("Invalid key type", "InvalidAccessError"); - } - // 2. - const publicKey = normalizedAlgorithm.public; - // 3. - if (publicKey[_type] !== "public") { - throw new DOMException("Invalid key type", "InvalidAccessError"); - } - // 4. - if (publicKey[_algorithm].name !== baseKey[_algorithm].name) { - throw new DOMException( - "Algorithm mismatch", - "InvalidAccessError", - ); - } - // 5. - if ( - publicKey[_algorithm].namedCurve !== baseKey[_algorithm].namedCurve - ) { - throw new DOMException( - "namedCurve mismatch", - "InvalidAccessError", - ); - } - // 6. - if ( - ArrayPrototypeIncludes( - supportedNamedCurves, - publicKey[_algorithm].namedCurve, - ) - ) { - const baseKeyhandle = baseKey[_handle]; - const baseKeyData = WeakMapPrototypeGet(KEY_STORE, baseKeyhandle); - const publicKeyhandle = publicKey[_handle]; - const publicKeyData = WeakMapPrototypeGet(KEY_STORE, publicKeyhandle); - - const buf = await core.opAsync("op_crypto_derive_bits", { - key: baseKeyData, - publicKey: publicKeyData, - algorithm: "ECDH", - namedCurve: publicKey[_algorithm].namedCurve, - length, - }); - - // 8. - if (length === null) { - return buf.buffer; - } else if (buf.buffer.byteLength * 8 < length) { - throw new DOMException("Invalid length", "OperationError"); - } else { - return buf.buffer.slice(0, MathCeil(length / 8)); - } + }); + + // 8. + if (length === null) { + return buf.buffer; + } else if (buf.buffer.byteLength * 8 < length) { + throw new DOMException("Invalid length", "OperationError"); } else { - throw new DOMException("Not implemented", "NotSupportedError"); + return buf.buffer.slice(0, MathCeil(length / 8)); } + } else { + throw new DOMException("Not implemented", "NotSupportedError"); + } + } + case "HKDF": { + // 1. + if (length === null || length === 0 || length % 8 !== 0) { + throw new DOMException("Invalid length", "OperationError"); } - case "HKDF": { - // 1. - if (length === null || length === 0 || length % 8 !== 0) { - throw new DOMException("Invalid length", "OperationError"); - } - const handle = baseKey[_handle]; - const keyDerivationKey = WeakMapPrototypeGet(KEY_STORE, handle); + const handle = baseKey[_handle]; + const keyDerivationKey = WeakMapPrototypeGet(KEY_STORE, handle); - normalizedAlgorithm.salt = copyBuffer(normalizedAlgorithm.salt); + normalizedAlgorithm.salt = copyBuffer(normalizedAlgorithm.salt); - normalizedAlgorithm.info = copyBuffer(normalizedAlgorithm.info); + normalizedAlgorithm.info = copyBuffer(normalizedAlgorithm.info); - const buf = await core.opAsync("op_crypto_derive_bits", { - key: keyDerivationKey, - algorithm: "HKDF", - hash: normalizedAlgorithm.hash.name, - info: normalizedAlgorithm.info, - length, - }, normalizedAlgorithm.salt); + const buf = await core.opAsync("op_crypto_derive_bits", { + key: keyDerivationKey, + algorithm: "HKDF", + hash: normalizedAlgorithm.hash.name, + info: normalizedAlgorithm.info, + length, + }, normalizedAlgorithm.salt); - return buf.buffer; + return buf.buffer; + } + case "X25519": { + // 1. + if (baseKey[_type] !== "private") { + throw new DOMException("Invalid key type", "InvalidAccessError"); + } + // 2. + const publicKey = normalizedAlgorithm.public; + // 3. + if (publicKey[_type] !== "public") { + throw new DOMException("Invalid key type", "InvalidAccessError"); + } + // 4. + if (publicKey[_algorithm].name !== baseKey[_algorithm].name) { + throw new DOMException( + "Algorithm mismatch", + "InvalidAccessError", + ); } - case "X25519": { - // 1. - if (baseKey[_type] !== "private") { - throw new DOMException("Invalid key type", "InvalidAccessError"); - } - // 2. - const publicKey = normalizedAlgorithm.public; - // 3. - if (publicKey[_type] !== "public") { - throw new DOMException("Invalid key type", "InvalidAccessError"); - } - // 4. - if (publicKey[_algorithm].name !== baseKey[_algorithm].name) { - throw new DOMException( - "Algorithm mismatch", - "InvalidAccessError", - ); - } - // 5. - const kHandle = baseKey[_handle]; - const k = WeakMapPrototypeGet(KEY_STORE, kHandle); + // 5. + const kHandle = baseKey[_handle]; + const k = WeakMapPrototypeGet(KEY_STORE, kHandle); - const uHandle = publicKey[_handle]; - const u = WeakMapPrototypeGet(KEY_STORE, uHandle); + const uHandle = publicKey[_handle]; + const u = WeakMapPrototypeGet(KEY_STORE, uHandle); - const secret = new Uint8Array(32); - const isIdentity = ops.op_derive_bits_x25519(k, u, secret); + const secret = new Uint8Array(32); + const isIdentity = ops.op_derive_bits_x25519(k, u, secret); - // 6. - if (isIdentity) { - throw new DOMException("Invalid key", "OperationError"); - } + // 6. + if (isIdentity) { + throw new DOMException("Invalid key", "OperationError"); + } - // 7. - if (length === null) { - return secret.buffer; - } else if ( - secret.buffer.byteLength * 8 < length - ) { - throw new DOMException("Invalid length", "OperationError"); - } else { - return secret.buffer.slice(0, MathCeil(length / 8)); - } + // 7. + if (length === null) { + return secret.buffer; + } else if ( + secret.buffer.byteLength * 8 < length + ) { + throw new DOMException("Invalid length", "OperationError"); + } else { + return secret.buffer.slice(0, MathCeil(length / 8)); } - default: - throw new DOMException("Not implemented", "NotSupportedError"); } + default: + throw new DOMException("Not implemented", "NotSupportedError"); } +} - async function encrypt(normalizedAlgorithm, key, data) { - const handle = key[_handle]; - const keyData = WeakMapPrototypeGet(KEY_STORE, handle); - - switch (normalizedAlgorithm.name) { - case "RSA-OAEP": { - // 1. - if (key[_type] !== "public") { - throw new DOMException( - "Key type not supported", - "InvalidAccessError", - ); - } - - // 2. - if (normalizedAlgorithm.label) { - normalizedAlgorithm.label = copyBuffer(normalizedAlgorithm.label); - } else { - normalizedAlgorithm.label = new Uint8Array(); - } +async function encrypt(normalizedAlgorithm, key, data) { + const handle = key[_handle]; + const keyData = WeakMapPrototypeGet(KEY_STORE, handle); - // 3-5. - const hashAlgorithm = key[_algorithm].hash.name; - const cipherText = await core.opAsync("op_crypto_encrypt", { - key: keyData, - algorithm: "RSA-OAEP", - hash: hashAlgorithm, - label: normalizedAlgorithm.label, - }, data); + switch (normalizedAlgorithm.name) { + case "RSA-OAEP": { + // 1. + if (key[_type] !== "public") { + throw new DOMException( + "Key type not supported", + "InvalidAccessError", + ); + } - // 6. - return cipherText.buffer; + // 2. + if (normalizedAlgorithm.label) { + normalizedAlgorithm.label = copyBuffer(normalizedAlgorithm.label); + } else { + normalizedAlgorithm.label = new Uint8Array(); } - case "AES-CBC": { - normalizedAlgorithm.iv = copyBuffer(normalizedAlgorithm.iv); - // 1. - if (normalizedAlgorithm.iv.byteLength !== 16) { - throw new DOMException( - "Initialization vector must be 16 bytes", - "OperationError", - ); - } + // 3-5. + const hashAlgorithm = key[_algorithm].hash.name; + const cipherText = await core.opAsync("op_crypto_encrypt", { + key: keyData, + algorithm: "RSA-OAEP", + hash: hashAlgorithm, + label: normalizedAlgorithm.label, + }, data); - // 2. - const cipherText = await core.opAsync("op_crypto_encrypt", { - key: keyData, - algorithm: "AES-CBC", - length: key[_algorithm].length, - iv: normalizedAlgorithm.iv, - }, data); + // 6. + return cipherText.buffer; + } + case "AES-CBC": { + normalizedAlgorithm.iv = copyBuffer(normalizedAlgorithm.iv); - // 4. - return cipherText.buffer; + // 1. + if (normalizedAlgorithm.iv.byteLength !== 16) { + throw new DOMException( + "Initialization vector must be 16 bytes", + "OperationError", + ); } - case "AES-CTR": { - normalizedAlgorithm.counter = copyBuffer(normalizedAlgorithm.counter); - - // 1. - if (normalizedAlgorithm.counter.byteLength !== 16) { - throw new DOMException( - "Counter vector must be 16 bytes", - "OperationError", - ); - } - // 2. - if ( - normalizedAlgorithm.length == 0 || normalizedAlgorithm.length > 128 - ) { - throw new DOMException( - "Counter length must not be 0 or greater than 128", - "OperationError", - ); - } + // 2. + const cipherText = await core.opAsync("op_crypto_encrypt", { + key: keyData, + algorithm: "AES-CBC", + length: key[_algorithm].length, + iv: normalizedAlgorithm.iv, + }, data); + + // 4. + return cipherText.buffer; + } + case "AES-CTR": { + normalizedAlgorithm.counter = copyBuffer(normalizedAlgorithm.counter); - // 3. - const cipherText = await core.opAsync("op_crypto_encrypt", { - key: keyData, - algorithm: "AES-CTR", - keyLength: key[_algorithm].length, - counter: normalizedAlgorithm.counter, - ctrLength: normalizedAlgorithm.length, - }, data); + // 1. + if (normalizedAlgorithm.counter.byteLength !== 16) { + throw new DOMException( + "Counter vector must be 16 bytes", + "OperationError", + ); + } - // 4. - return cipherText.buffer; + // 2. + if ( + normalizedAlgorithm.length == 0 || normalizedAlgorithm.length > 128 + ) { + throw new DOMException( + "Counter length must not be 0 or greater than 128", + "OperationError", + ); } - case "AES-GCM": { - normalizedAlgorithm.iv = copyBuffer(normalizedAlgorithm.iv); - // 1. - if (data.byteLength > (2 ** 39) - 256) { - throw new DOMException( - "Plaintext too large", - "OperationError", - ); - } + // 3. + const cipherText = await core.opAsync("op_crypto_encrypt", { + key: keyData, + algorithm: "AES-CTR", + keyLength: key[_algorithm].length, + counter: normalizedAlgorithm.counter, + ctrLength: normalizedAlgorithm.length, + }, data); + + // 4. + return cipherText.buffer; + } + case "AES-GCM": { + normalizedAlgorithm.iv = copyBuffer(normalizedAlgorithm.iv); - // 2. - // We only support 96-bit and 128-bit nonce. - if ( - ArrayPrototypeIncludes( - [12, 16], - normalizedAlgorithm.iv.byteLength, - ) === undefined - ) { - throw new DOMException( - "Initialization vector length not supported", - "NotSupportedError", - ); - } + // 1. + if (data.byteLength > (2 ** 39) - 256) { + throw new DOMException( + "Plaintext too large", + "OperationError", + ); + } - // 3. - if (normalizedAlgorithm.additionalData !== undefined) { - if (normalizedAlgorithm.additionalData.byteLength > (2 ** 64) - 1) { - throw new DOMException( - "Additional data too large", - "OperationError", - ); - } - } + // 2. + // We only support 96-bit and 128-bit nonce. + if ( + ArrayPrototypeIncludes( + [12, 16], + normalizedAlgorithm.iv.byteLength, + ) === undefined + ) { + throw new DOMException( + "Initialization vector length not supported", + "NotSupportedError", + ); + } - // 4. - if (normalizedAlgorithm.tagLength == undefined) { - normalizedAlgorithm.tagLength = 128; - } else if ( - !ArrayPrototypeIncludes( - [32, 64, 96, 104, 112, 120, 128], - normalizedAlgorithm.tagLength, - ) - ) { + // 3. + if (normalizedAlgorithm.additionalData !== undefined) { + if (normalizedAlgorithm.additionalData.byteLength > (2 ** 64) - 1) { throw new DOMException( - "Invalid tag length", + "Additional data too large", "OperationError", ); } - // 5. - if (normalizedAlgorithm.additionalData) { - normalizedAlgorithm.additionalData = copyBuffer( - normalizedAlgorithm.additionalData, - ); - } - // 6-7. - const cipherText = await core.opAsync("op_crypto_encrypt", { - key: keyData, - algorithm: "AES-GCM", - length: key[_algorithm].length, - iv: normalizedAlgorithm.iv, - additionalData: normalizedAlgorithm.additionalData || null, - tagLength: normalizedAlgorithm.tagLength, - }, data); - - // 8. - return cipherText.buffer; } - default: - throw new DOMException("Not implemented", "NotSupportedError"); - } - } - - webidl.configurePrototype(SubtleCrypto); - const subtle = webidl.createBranded(SubtleCrypto); - - class Crypto { - constructor() { - webidl.illegalConstructor(); - } - getRandomValues(arrayBufferView) { - webidl.assertBranded(this, CryptoPrototype); - const prefix = "Failed to execute 'getRandomValues' on 'Crypto'"; - webidl.requiredArguments(arguments.length, 1, { prefix }); - // Fast path for Uint8Array - if (ObjectPrototypeIsPrototypeOf(Uint8ArrayPrototype, arrayBufferView)) { - ops.op_crypto_get_random_values(arrayBufferView); - return arrayBufferView; - } - arrayBufferView = webidl.converters.ArrayBufferView(arrayBufferView, { - prefix, - context: "Argument 1", - }); - if ( - !( - ObjectPrototypeIsPrototypeOf(Int8ArrayPrototype, arrayBufferView) || - ObjectPrototypeIsPrototypeOf(Uint8ArrayPrototype, arrayBufferView) || - ObjectPrototypeIsPrototypeOf( - Uint8ClampedArrayPrototype, - arrayBufferView, - ) || - ObjectPrototypeIsPrototypeOf(Int16ArrayPrototype, arrayBufferView) || - ObjectPrototypeIsPrototypeOf(Uint16ArrayPrototype, arrayBufferView) || - ObjectPrototypeIsPrototypeOf(Int32ArrayPrototype, arrayBufferView) || - ObjectPrototypeIsPrototypeOf(Uint32ArrayPrototype, arrayBufferView) || - ObjectPrototypeIsPrototypeOf( - BigInt64ArrayPrototype, - arrayBufferView, - ) || - ObjectPrototypeIsPrototypeOf(BigUint64ArrayPrototype, arrayBufferView) + // 4. + if (normalizedAlgorithm.tagLength == undefined) { + normalizedAlgorithm.tagLength = 128; + } else if ( + !ArrayPrototypeIncludes( + [32, 64, 96, 104, 112, 120, 128], + normalizedAlgorithm.tagLength, ) ) { throw new DOMException( - "The provided ArrayBufferView is not an integer array type", - "TypeMismatchError", + "Invalid tag length", + "OperationError", ); } - const ui8 = new Uint8Array( - arrayBufferView.buffer, - arrayBufferView.byteOffset, - arrayBufferView.byteLength, - ); - ops.op_crypto_get_random_values(ui8); - return arrayBufferView; - } + // 5. + if (normalizedAlgorithm.additionalData) { + normalizedAlgorithm.additionalData = copyBuffer( + normalizedAlgorithm.additionalData, + ); + } + // 6-7. + const cipherText = await core.opAsync("op_crypto_encrypt", { + key: keyData, + algorithm: "AES-GCM", + length: key[_algorithm].length, + iv: normalizedAlgorithm.iv, + additionalData: normalizedAlgorithm.additionalData || null, + tagLength: normalizedAlgorithm.tagLength, + }, data); - randomUUID() { - webidl.assertBranded(this, CryptoPrototype); - return ops.op_crypto_random_uuid(); + // 8. + return cipherText.buffer; } + default: + throw new DOMException("Not implemented", "NotSupportedError"); + } +} - get subtle() { - webidl.assertBranded(this, CryptoPrototype); - return subtle; - } +webidl.configurePrototype(SubtleCrypto); +const subtle = webidl.createBranded(SubtleCrypto); + +class Crypto { + constructor() { + webidl.illegalConstructor(); + } - [SymbolFor("Deno.customInspect")](inspect) { - return `${this.constructor.name} ${inspect({})}`; + getRandomValues(arrayBufferView) { + webidl.assertBranded(this, CryptoPrototype); + const prefix = "Failed to execute 'getRandomValues' on 'Crypto'"; + webidl.requiredArguments(arguments.length, 1, { prefix }); + // Fast path for Uint8Array + if (ObjectPrototypeIsPrototypeOf(Uint8ArrayPrototype, arrayBufferView)) { + ops.op_crypto_get_random_values(arrayBufferView); + return arrayBufferView; + } + arrayBufferView = webidl.converters.ArrayBufferView(arrayBufferView, { + prefix, + context: "Argument 1", + }); + if ( + !( + ObjectPrototypeIsPrototypeOf(Int8ArrayPrototype, arrayBufferView) || + ObjectPrototypeIsPrototypeOf(Uint8ArrayPrototype, arrayBufferView) || + ObjectPrototypeIsPrototypeOf( + Uint8ClampedArrayPrototype, + arrayBufferView, + ) || + ObjectPrototypeIsPrototypeOf(Int16ArrayPrototype, arrayBufferView) || + ObjectPrototypeIsPrototypeOf(Uint16ArrayPrototype, arrayBufferView) || + ObjectPrototypeIsPrototypeOf(Int32ArrayPrototype, arrayBufferView) || + ObjectPrototypeIsPrototypeOf(Uint32ArrayPrototype, arrayBufferView) || + ObjectPrototypeIsPrototypeOf( + BigInt64ArrayPrototype, + arrayBufferView, + ) || + ObjectPrototypeIsPrototypeOf(BigUint64ArrayPrototype, arrayBufferView) + ) + ) { + throw new DOMException( + "The provided ArrayBufferView is not an integer array type", + "TypeMismatchError", + ); } + const ui8 = new Uint8Array( + arrayBufferView.buffer, + arrayBufferView.byteOffset, + arrayBufferView.byteLength, + ); + ops.op_crypto_get_random_values(ui8); + return arrayBufferView; } - webidl.configurePrototype(Crypto); - const CryptoPrototype = Crypto.prototype; + randomUUID() { + webidl.assertBranded(this, CryptoPrototype); + return ops.op_crypto_random_uuid(); + } - window.__bootstrap.crypto = { - SubtleCrypto, - crypto: webidl.createBranded(Crypto), - Crypto, - CryptoKey, - }; -})(this); + get subtle() { + webidl.assertBranded(this, CryptoPrototype); + return subtle; + } + + [SymbolFor("Deno.customInspect")](inspect) { + return `${this.constructor.name} ${inspect({})}`; + } +} + +webidl.configurePrototype(Crypto); +const CryptoPrototype = Crypto.prototype; + +const crypto = webidl.createBranded(Crypto); +export { Crypto, crypto, CryptoKey, SubtleCrypto }; diff --git a/ext/crypto/01_webidl.js b/ext/crypto/01_webidl.js index d381672fd1f9de..ceb1de06137014 100644 --- a/ext/crypto/01_webidl.js +++ b/ext/crypto/01_webidl.js @@ -4,484 +4,481 @@ /// /// -"use strict"; - -((window) => { - const webidl = window.__bootstrap.webidl; - const { CryptoKey } = window.__bootstrap.crypto; - const { - ArrayBufferIsView, - ArrayBufferPrototype, - ObjectPrototypeIsPrototypeOf, - SafeArrayIterator, - } = window.__bootstrap.primordials; - - webidl.converters.AlgorithmIdentifier = (V, opts) => { - // Union for (object or DOMString) - if (webidl.type(V) == "Object") { - return webidl.converters.object(V, opts); - } - return webidl.converters.DOMString(V, opts); - }; - - webidl.converters["BufferSource or JsonWebKey"] = (V, opts) => { - // Union for (BufferSource or JsonWebKey) - if ( - ArrayBufferIsView(V) || - ObjectPrototypeIsPrototypeOf(ArrayBufferPrototype, V) - ) { - return webidl.converters.BufferSource(V, opts); - } - return webidl.converters.JsonWebKey(V, opts); - }; - - webidl.converters.KeyType = webidl.createEnumConverter("KeyType", [ - "public", - "private", - "secret", - ]); - - webidl.converters.KeyFormat = webidl.createEnumConverter("KeyFormat", [ - "raw", - "pkcs8", - "spki", - "jwk", - ]); - - webidl.converters.KeyUsage = webidl.createEnumConverter("KeyUsage", [ - "encrypt", - "decrypt", - "sign", - "verify", - "deriveKey", - "deriveBits", - "wrapKey", - "unwrapKey", - ]); - - webidl.converters["sequence"] = webidl.createSequenceConverter( - webidl.converters.KeyUsage, - ); +const primordials = globalThis.__bootstrap.primordials; +import * as webidl from "internal:deno_webidl/00_webidl.js"; +import { CryptoKey } from "internal:deno_crypto/00_crypto.js"; +const { + ArrayBufferIsView, + ArrayBufferPrototype, + ObjectPrototypeIsPrototypeOf, + SafeArrayIterator, +} = primordials; + +webidl.converters.AlgorithmIdentifier = (V, opts) => { + // Union for (object or DOMString) + if (webidl.type(V) == "Object") { + return webidl.converters.object(V, opts); + } + return webidl.converters.DOMString(V, opts); +}; + +webidl.converters["BufferSource or JsonWebKey"] = (V, opts) => { + // Union for (BufferSource or JsonWebKey) + if ( + ArrayBufferIsView(V) || + ObjectPrototypeIsPrototypeOf(ArrayBufferPrototype, V) + ) { + return webidl.converters.BufferSource(V, opts); + } + return webidl.converters.JsonWebKey(V, opts); +}; + +webidl.converters.KeyType = webidl.createEnumConverter("KeyType", [ + "public", + "private", + "secret", +]); + +webidl.converters.KeyFormat = webidl.createEnumConverter("KeyFormat", [ + "raw", + "pkcs8", + "spki", + "jwk", +]); + +webidl.converters.KeyUsage = webidl.createEnumConverter("KeyUsage", [ + "encrypt", + "decrypt", + "sign", + "verify", + "deriveKey", + "deriveBits", + "wrapKey", + "unwrapKey", +]); + +webidl.converters["sequence"] = webidl.createSequenceConverter( + webidl.converters.KeyUsage, +); + +webidl.converters.HashAlgorithmIdentifier = + webidl.converters.AlgorithmIdentifier; + +/** @type {webidl.Dictionary} */ +const dictAlgorithm = [{ + key: "name", + converter: webidl.converters.DOMString, + required: true, +}]; + +webidl.converters.Algorithm = webidl + .createDictionaryConverter("Algorithm", dictAlgorithm); + +webidl.converters.BigInteger = webidl.converters.Uint8Array; + +/** @type {webidl.Dictionary} */ +const dictRsaKeyGenParams = [ + ...new SafeArrayIterator(dictAlgorithm), + { + key: "modulusLength", + converter: (V, opts) => + webidl.converters["unsigned long"](V, { ...opts, enforceRange: true }), + required: true, + }, + { + key: "publicExponent", + converter: webidl.converters.BigInteger, + required: true, + }, +]; - webidl.converters.HashAlgorithmIdentifier = - webidl.converters.AlgorithmIdentifier; +webidl.converters.RsaKeyGenParams = webidl + .createDictionaryConverter("RsaKeyGenParams", dictRsaKeyGenParams); - /** @type {__bootstrap.webidl.Dictionary} */ - const dictAlgorithm = [{ - key: "name", - converter: webidl.converters.DOMString, +const dictRsaHashedKeyGenParams = [ + ...new SafeArrayIterator(dictRsaKeyGenParams), + { + key: "hash", + converter: webidl.converters.HashAlgorithmIdentifier, required: true, - }]; - - webidl.converters.Algorithm = webidl - .createDictionaryConverter("Algorithm", dictAlgorithm); - - webidl.converters.BigInteger = webidl.converters.Uint8Array; - - /** @type {__bootstrap.webidl.Dictionary} */ - const dictRsaKeyGenParams = [ - ...new SafeArrayIterator(dictAlgorithm), - { - key: "modulusLength", - converter: (V, opts) => - webidl.converters["unsigned long"](V, { ...opts, enforceRange: true }), - required: true, - }, - { - key: "publicExponent", - converter: webidl.converters.BigInteger, - required: true, - }, - ]; - - webidl.converters.RsaKeyGenParams = webidl - .createDictionaryConverter("RsaKeyGenParams", dictRsaKeyGenParams); - - const dictRsaHashedKeyGenParams = [ - ...new SafeArrayIterator(dictRsaKeyGenParams), - { - key: "hash", - converter: webidl.converters.HashAlgorithmIdentifier, - required: true, - }, - ]; - - webidl.converters.RsaHashedKeyGenParams = webidl.createDictionaryConverter( - "RsaHashedKeyGenParams", - dictRsaHashedKeyGenParams, - ); + }, +]; + +webidl.converters.RsaHashedKeyGenParams = webidl.createDictionaryConverter( + "RsaHashedKeyGenParams", + dictRsaHashedKeyGenParams, +); + +const dictRsaHashedImportParams = [ + ...new SafeArrayIterator(dictAlgorithm), + { + key: "hash", + converter: webidl.converters.HashAlgorithmIdentifier, + required: true, + }, +]; - const dictRsaHashedImportParams = [ - ...new SafeArrayIterator(dictAlgorithm), - { - key: "hash", - converter: webidl.converters.HashAlgorithmIdentifier, - required: true, - }, - ]; - - webidl.converters.RsaHashedImportParams = webidl.createDictionaryConverter( - "RsaHashedImportParams", - dictRsaHashedImportParams, - ); +webidl.converters.RsaHashedImportParams = webidl.createDictionaryConverter( + "RsaHashedImportParams", + dictRsaHashedImportParams, +); - webidl.converters.NamedCurve = webidl.converters.DOMString; +webidl.converters.NamedCurve = webidl.converters.DOMString; + +const dictEcKeyImportParams = [ + ...new SafeArrayIterator(dictAlgorithm), + { + key: "namedCurve", + converter: webidl.converters.NamedCurve, + required: true, + }, +]; + +webidl.converters.EcKeyImportParams = webidl.createDictionaryConverter( + "EcKeyImportParams", + dictEcKeyImportParams, +); + +const dictEcKeyGenParams = [ + ...new SafeArrayIterator(dictAlgorithm), + { + key: "namedCurve", + converter: webidl.converters.NamedCurve, + required: true, + }, +]; + +webidl.converters.EcKeyGenParams = webidl + .createDictionaryConverter("EcKeyGenParams", dictEcKeyGenParams); + +const dictAesKeyGenParams = [ + ...new SafeArrayIterator(dictAlgorithm), + { + key: "length", + converter: (V, opts) => + webidl.converters["unsigned short"](V, { ...opts, enforceRange: true }), + required: true, + }, +]; - const dictEcKeyImportParams = [ - ...new SafeArrayIterator(dictAlgorithm), - { - key: "namedCurve", - converter: webidl.converters.NamedCurve, - required: true, - }, - ]; +webidl.converters.AesKeyGenParams = webidl + .createDictionaryConverter("AesKeyGenParams", dictAesKeyGenParams); - webidl.converters.EcKeyImportParams = webidl.createDictionaryConverter( - "EcKeyImportParams", - dictEcKeyImportParams, - ); +const dictHmacKeyGenParams = [ + ...new SafeArrayIterator(dictAlgorithm), + { + key: "hash", + converter: webidl.converters.HashAlgorithmIdentifier, + required: true, + }, + { + key: "length", + converter: (V, opts) => + webidl.converters["unsigned long"](V, { ...opts, enforceRange: true }), + }, +]; + +webidl.converters.HmacKeyGenParams = webidl + .createDictionaryConverter("HmacKeyGenParams", dictHmacKeyGenParams); + +const dictRsaPssParams = [ + ...new SafeArrayIterator(dictAlgorithm), + { + key: "saltLength", + converter: (V, opts) => + webidl.converters["unsigned long"](V, { ...opts, enforceRange: true }), + required: true, + }, +]; + +webidl.converters.RsaPssParams = webidl + .createDictionaryConverter("RsaPssParams", dictRsaPssParams); + +const dictRsaOaepParams = [ + ...new SafeArrayIterator(dictAlgorithm), + { + key: "label", + converter: webidl.converters["BufferSource"], + }, +]; + +webidl.converters.RsaOaepParams = webidl + .createDictionaryConverter("RsaOaepParams", dictRsaOaepParams); + +const dictEcdsaParams = [ + ...new SafeArrayIterator(dictAlgorithm), + { + key: "hash", + converter: webidl.converters.HashAlgorithmIdentifier, + required: true, + }, +]; - const dictEcKeyGenParams = [ - ...new SafeArrayIterator(dictAlgorithm), - { - key: "namedCurve", - converter: webidl.converters.NamedCurve, - required: true, - }, - ]; - - webidl.converters.EcKeyGenParams = webidl - .createDictionaryConverter("EcKeyGenParams", dictEcKeyGenParams); - - const dictAesKeyGenParams = [ - ...new SafeArrayIterator(dictAlgorithm), - { - key: "length", - converter: (V, opts) => - webidl.converters["unsigned short"](V, { ...opts, enforceRange: true }), - required: true, - }, - ]; - - webidl.converters.AesKeyGenParams = webidl - .createDictionaryConverter("AesKeyGenParams", dictAesKeyGenParams); - - const dictHmacKeyGenParams = [ - ...new SafeArrayIterator(dictAlgorithm), - { - key: "hash", - converter: webidl.converters.HashAlgorithmIdentifier, - required: true, - }, - { - key: "length", - converter: (V, opts) => - webidl.converters["unsigned long"](V, { ...opts, enforceRange: true }), - }, - ]; - - webidl.converters.HmacKeyGenParams = webidl - .createDictionaryConverter("HmacKeyGenParams", dictHmacKeyGenParams); - - const dictRsaPssParams = [ - ...new SafeArrayIterator(dictAlgorithm), - { - key: "saltLength", - converter: (V, opts) => - webidl.converters["unsigned long"](V, { ...opts, enforceRange: true }), - required: true, - }, - ]; - - webidl.converters.RsaPssParams = webidl - .createDictionaryConverter("RsaPssParams", dictRsaPssParams); - - const dictRsaOaepParams = [ - ...new SafeArrayIterator(dictAlgorithm), - { - key: "label", - converter: webidl.converters["BufferSource"], - }, - ]; - - webidl.converters.RsaOaepParams = webidl - .createDictionaryConverter("RsaOaepParams", dictRsaOaepParams); - - const dictEcdsaParams = [ - ...new SafeArrayIterator(dictAlgorithm), - { - key: "hash", - converter: webidl.converters.HashAlgorithmIdentifier, - required: true, - }, - ]; - - webidl.converters["EcdsaParams"] = webidl - .createDictionaryConverter("EcdsaParams", dictEcdsaParams); - - const dictHmacImportParams = [ - ...new SafeArrayIterator(dictAlgorithm), - { - key: "hash", - converter: webidl.converters.HashAlgorithmIdentifier, - required: true, - }, - { - key: "length", - converter: (V, opts) => - webidl.converters["unsigned long"](V, { ...opts, enforceRange: true }), - }, - ]; - - webidl.converters.HmacImportParams = webidl - .createDictionaryConverter("HmacImportParams", dictHmacImportParams); - - const dictRsaOtherPrimesInfo = [ - { - key: "r", - converter: webidl.converters["DOMString"], - }, - { - key: "d", - converter: webidl.converters["DOMString"], - }, - { - key: "t", - converter: webidl.converters["DOMString"], - }, - ]; - - webidl.converters.RsaOtherPrimesInfo = webidl.createDictionaryConverter( - "RsaOtherPrimesInfo", - dictRsaOtherPrimesInfo, - ); - webidl.converters["sequence"] = webidl - .createSequenceConverter( - webidl.converters.RsaOtherPrimesInfo, - ); - - const dictJsonWebKey = [ - // Sections 4.2 and 4.3 of RFC7517. - // https://datatracker.ietf.org/doc/html/rfc7517#section-4 - { - key: "kty", - converter: webidl.converters["DOMString"], - }, - { - key: "use", - converter: webidl.converters["DOMString"], - }, - { - key: "key_ops", - converter: webidl.converters["sequence"], - }, - { - key: "alg", - converter: webidl.converters["DOMString"], - }, - // JSON Web Key Parameters Registration - { - key: "ext", - converter: webidl.converters["boolean"], - }, - // Section 6 of RFC7518 JSON Web Algorithms - // https://datatracker.ietf.org/doc/html/rfc7518#section-6 - { - key: "crv", - converter: webidl.converters["DOMString"], - }, - { - key: "x", - converter: webidl.converters["DOMString"], - }, - { - key: "y", - converter: webidl.converters["DOMString"], - }, - { - key: "d", - converter: webidl.converters["DOMString"], - }, - { - key: "n", - converter: webidl.converters["DOMString"], - }, - { - key: "e", - converter: webidl.converters["DOMString"], - }, - { - key: "p", - converter: webidl.converters["DOMString"], - }, - { - key: "q", - converter: webidl.converters["DOMString"], - }, - { - key: "dp", - converter: webidl.converters["DOMString"], - }, - { - key: "dq", - converter: webidl.converters["DOMString"], - }, - { - key: "qi", - converter: webidl.converters["DOMString"], - }, - { - key: "oth", - converter: webidl.converters["sequence"], - }, - { - key: "k", - converter: webidl.converters["DOMString"], - }, - ]; - - webidl.converters.JsonWebKey = webidl.createDictionaryConverter( - "JsonWebKey", - dictJsonWebKey, - ); +webidl.converters["EcdsaParams"] = webidl + .createDictionaryConverter("EcdsaParams", dictEcdsaParams); - const dictHkdfParams = [ - ...new SafeArrayIterator(dictAlgorithm), - { - key: "hash", - converter: webidl.converters.HashAlgorithmIdentifier, - required: true, - }, - { - key: "salt", - converter: webidl.converters["BufferSource"], - required: true, - }, - { - key: "info", - converter: webidl.converters["BufferSource"], - required: true, - }, - ]; - - webidl.converters.HkdfParams = webidl - .createDictionaryConverter("HkdfParams", dictHkdfParams); - - const dictPbkdf2Params = [ - ...new SafeArrayIterator(dictAlgorithm), - { - key: "hash", - converter: webidl.converters.HashAlgorithmIdentifier, - required: true, - }, - { - key: "iterations", - converter: (V, opts) => - webidl.converters["unsigned long"](V, { ...opts, enforceRange: true }), - required: true, - }, - { - key: "salt", - converter: webidl.converters["BufferSource"], - required: true, - }, - ]; - - webidl.converters.Pbkdf2Params = webidl - .createDictionaryConverter("Pbkdf2Params", dictPbkdf2Params); - - const dictAesDerivedKeyParams = [ - ...new SafeArrayIterator(dictAlgorithm), - { - key: "length", - converter: (V, opts) => - webidl.converters["unsigned long"](V, { ...opts, enforceRange: true }), - required: true, - }, - ]; - - const dictAesCbcParams = [ - ...new SafeArrayIterator(dictAlgorithm), - { - key: "iv", - converter: webidl.converters["BufferSource"], - required: true, - }, - ]; - - const dictAesGcmParams = [ - ...new SafeArrayIterator(dictAlgorithm), - { - key: "iv", - converter: webidl.converters["BufferSource"], - required: true, - }, - { - key: "tagLength", - converter: (V, opts) => - webidl.converters["unsigned long"](V, { ...opts, enforceRange: true }), - }, - { - key: "additionalData", - converter: webidl.converters["BufferSource"], - }, - ]; - - const dictAesCtrParams = [ - ...new SafeArrayIterator(dictAlgorithm), - { - key: "counter", - converter: webidl.converters["BufferSource"], - required: true, - }, - { - key: "length", - converter: (V, opts) => - webidl.converters["unsigned short"](V, { ...opts, enforceRange: true }), - required: true, - }, - ]; - - webidl.converters.AesDerivedKeyParams = webidl - .createDictionaryConverter("AesDerivedKeyParams", dictAesDerivedKeyParams); - - webidl.converters.AesCbcParams = webidl - .createDictionaryConverter("AesCbcParams", dictAesCbcParams); - - webidl.converters.AesGcmParams = webidl - .createDictionaryConverter("AesGcmParams", dictAesGcmParams); - - webidl.converters.AesCtrParams = webidl - .createDictionaryConverter("AesCtrParams", dictAesCtrParams); - - webidl.converters.CryptoKey = webidl.createInterfaceConverter( - "CryptoKey", - CryptoKey.prototype, +const dictHmacImportParams = [ + ...new SafeArrayIterator(dictAlgorithm), + { + key: "hash", + converter: webidl.converters.HashAlgorithmIdentifier, + required: true, + }, + { + key: "length", + converter: (V, opts) => + webidl.converters["unsigned long"](V, { ...opts, enforceRange: true }), + }, +]; + +webidl.converters.HmacImportParams = webidl + .createDictionaryConverter("HmacImportParams", dictHmacImportParams); + +const dictRsaOtherPrimesInfo = [ + { + key: "r", + converter: webidl.converters["DOMString"], + }, + { + key: "d", + converter: webidl.converters["DOMString"], + }, + { + key: "t", + converter: webidl.converters["DOMString"], + }, +]; + +webidl.converters.RsaOtherPrimesInfo = webidl.createDictionaryConverter( + "RsaOtherPrimesInfo", + dictRsaOtherPrimesInfo, +); +webidl.converters["sequence"] = webidl + .createSequenceConverter( + webidl.converters.RsaOtherPrimesInfo, ); - const dictCryptoKeyPair = [ - { - key: "publicKey", - converter: webidl.converters.CryptoKey, - }, - { - key: "privateKey", - converter: webidl.converters.CryptoKey, - }, - ]; - - webidl.converters.CryptoKeyPair = webidl - .createDictionaryConverter("CryptoKeyPair", dictCryptoKeyPair); - - const dictEcdhKeyDeriveParams = [ - ...new SafeArrayIterator(dictAlgorithm), - { - key: "public", - converter: webidl.converters.CryptoKey, - required: true, - }, - ]; - - webidl.converters.EcdhKeyDeriveParams = webidl - .createDictionaryConverter("EcdhKeyDeriveParams", dictEcdhKeyDeriveParams); -})(this); +const dictJsonWebKey = [ + // Sections 4.2 and 4.3 of RFC7517. + // https://datatracker.ietf.org/doc/html/rfc7517#section-4 + { + key: "kty", + converter: webidl.converters["DOMString"], + }, + { + key: "use", + converter: webidl.converters["DOMString"], + }, + { + key: "key_ops", + converter: webidl.converters["sequence"], + }, + { + key: "alg", + converter: webidl.converters["DOMString"], + }, + // JSON Web Key Parameters Registration + { + key: "ext", + converter: webidl.converters["boolean"], + }, + // Section 6 of RFC7518 JSON Web Algorithms + // https://datatracker.ietf.org/doc/html/rfc7518#section-6 + { + key: "crv", + converter: webidl.converters["DOMString"], + }, + { + key: "x", + converter: webidl.converters["DOMString"], + }, + { + key: "y", + converter: webidl.converters["DOMString"], + }, + { + key: "d", + converter: webidl.converters["DOMString"], + }, + { + key: "n", + converter: webidl.converters["DOMString"], + }, + { + key: "e", + converter: webidl.converters["DOMString"], + }, + { + key: "p", + converter: webidl.converters["DOMString"], + }, + { + key: "q", + converter: webidl.converters["DOMString"], + }, + { + key: "dp", + converter: webidl.converters["DOMString"], + }, + { + key: "dq", + converter: webidl.converters["DOMString"], + }, + { + key: "qi", + converter: webidl.converters["DOMString"], + }, + { + key: "oth", + converter: webidl.converters["sequence"], + }, + { + key: "k", + converter: webidl.converters["DOMString"], + }, +]; + +webidl.converters.JsonWebKey = webidl.createDictionaryConverter( + "JsonWebKey", + dictJsonWebKey, +); + +const dictHkdfParams = [ + ...new SafeArrayIterator(dictAlgorithm), + { + key: "hash", + converter: webidl.converters.HashAlgorithmIdentifier, + required: true, + }, + { + key: "salt", + converter: webidl.converters["BufferSource"], + required: true, + }, + { + key: "info", + converter: webidl.converters["BufferSource"], + required: true, + }, +]; + +webidl.converters.HkdfParams = webidl + .createDictionaryConverter("HkdfParams", dictHkdfParams); + +const dictPbkdf2Params = [ + ...new SafeArrayIterator(dictAlgorithm), + { + key: "hash", + converter: webidl.converters.HashAlgorithmIdentifier, + required: true, + }, + { + key: "iterations", + converter: (V, opts) => + webidl.converters["unsigned long"](V, { ...opts, enforceRange: true }), + required: true, + }, + { + key: "salt", + converter: webidl.converters["BufferSource"], + required: true, + }, +]; + +webidl.converters.Pbkdf2Params = webidl + .createDictionaryConverter("Pbkdf2Params", dictPbkdf2Params); + +const dictAesDerivedKeyParams = [ + ...new SafeArrayIterator(dictAlgorithm), + { + key: "length", + converter: (V, opts) => + webidl.converters["unsigned long"](V, { ...opts, enforceRange: true }), + required: true, + }, +]; + +const dictAesCbcParams = [ + ...new SafeArrayIterator(dictAlgorithm), + { + key: "iv", + converter: webidl.converters["BufferSource"], + required: true, + }, +]; + +const dictAesGcmParams = [ + ...new SafeArrayIterator(dictAlgorithm), + { + key: "iv", + converter: webidl.converters["BufferSource"], + required: true, + }, + { + key: "tagLength", + converter: (V, opts) => + webidl.converters["unsigned long"](V, { ...opts, enforceRange: true }), + }, + { + key: "additionalData", + converter: webidl.converters["BufferSource"], + }, +]; + +const dictAesCtrParams = [ + ...new SafeArrayIterator(dictAlgorithm), + { + key: "counter", + converter: webidl.converters["BufferSource"], + required: true, + }, + { + key: "length", + converter: (V, opts) => + webidl.converters["unsigned short"](V, { ...opts, enforceRange: true }), + required: true, + }, +]; + +webidl.converters.AesDerivedKeyParams = webidl + .createDictionaryConverter("AesDerivedKeyParams", dictAesDerivedKeyParams); + +webidl.converters.AesCbcParams = webidl + .createDictionaryConverter("AesCbcParams", dictAesCbcParams); + +webidl.converters.AesGcmParams = webidl + .createDictionaryConverter("AesGcmParams", dictAesGcmParams); + +webidl.converters.AesCtrParams = webidl + .createDictionaryConverter("AesCtrParams", dictAesCtrParams); + +webidl.converters.CryptoKey = webidl.createInterfaceConverter( + "CryptoKey", + CryptoKey.prototype, +); + +const dictCryptoKeyPair = [ + { + key: "publicKey", + converter: webidl.converters.CryptoKey, + }, + { + key: "privateKey", + converter: webidl.converters.CryptoKey, + }, +]; + +webidl.converters.CryptoKeyPair = webidl + .createDictionaryConverter("CryptoKeyPair", dictCryptoKeyPair); + +const dictEcdhKeyDeriveParams = [ + ...new SafeArrayIterator(dictAlgorithm), + { + key: "public", + converter: webidl.converters.CryptoKey, + required: true, + }, +]; + +webidl.converters.EcdhKeyDeriveParams = webidl + .createDictionaryConverter("EcdhKeyDeriveParams", dictEcdhKeyDeriveParams); diff --git a/ext/crypto/Cargo.toml b/ext/crypto/Cargo.toml index d6542154147d83..437243b980d569 100644 --- a/ext/crypto/Cargo.toml +++ b/ext/crypto/Cargo.toml @@ -2,7 +2,7 @@ [package] name = "deno_crypto" -version = "0.103.0" +version = "0.105.0" authors.workspace = true edition.workspace = true license.workspace = true @@ -33,7 +33,7 @@ p256 = { version = "0.11.1", features = ["ecdh"] } p384 = "0.11.1" rand.workspace = true ring = { workspace = true, features = ["std"] } -rsa = { version = "0.7.0", default-features = false, features = ["std"] } +rsa.workspace = true sec1 = "0.3.0" serde.workspace = true serde_bytes.workspace = true diff --git a/ext/crypto/lib.rs b/ext/crypto/lib.rs index 906e7d06f773cf..9d6017f39f4e42 100644 --- a/ext/crypto/lib.rs +++ b/ext/crypto/lib.rs @@ -75,11 +75,7 @@ use crate::shared::RawKeyData; pub fn init(maybe_seed: Option) -> Extension { Extension::builder(env!("CARGO_PKG_NAME")) .dependencies(vec!["deno_webidl", "deno_web"]) - .js(include_js_files!( - prefix "internal:ext/crypto", - "00_crypto.js", - "01_webidl.js", - )) + .esm(include_js_files!("00_crypto.js", "01_webidl.js",)) .ops(vec![ op_crypto_get_random_values::decl(), op_crypto_generate_key::decl(), diff --git a/ext/fetch/01_fetch_util.js b/ext/fetch/01_fetch_util.js deleted file mode 100644 index 3ed554ecb19d4f..00000000000000 --- a/ext/fetch/01_fetch_util.js +++ /dev/null @@ -1,22 +0,0 @@ -// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. -"use strict"; - -((window) => { - const { TypeError } = window.__bootstrap.primordials; - function requiredArguments( - name, - length, - required, - ) { - if (length < required) { - const errMsg = `${name} requires at least ${required} argument${ - required === 1 ? "" : "s" - }, but only ${length} present`; - throw new TypeError(errMsg); - } - } - - window.__bootstrap.fetchUtil = { - requiredArguments, - }; -})(this); diff --git a/ext/fetch/20_headers.js b/ext/fetch/20_headers.js index 54e635522abdd6..b8fd8ab8356d3b 100644 --- a/ext/fetch/20_headers.js +++ b/ext/fetch/20_headers.js @@ -8,472 +8,470 @@ /// /// /// -"use strict"; - -((window) => { - const webidl = window.__bootstrap.webidl; - const { - HTTP_TAB_OR_SPACE_PREFIX_RE, - HTTP_TAB_OR_SPACE_SUFFIX_RE, - HTTP_TOKEN_CODE_POINT_RE, - byteLowerCase, - collectSequenceOfCodepoints, - collectHttpQuotedString, - httpTrim, - } = window.__bootstrap.infra; - const { - ArrayIsArray, - ArrayPrototypeMap, - ArrayPrototypePush, - ArrayPrototypeSort, - ArrayPrototypeJoin, - ArrayPrototypeSplice, - ArrayPrototypeFilter, - ObjectPrototypeHasOwnProperty, - ObjectEntries, - RegExpPrototypeTest, - SafeArrayIterator, - Symbol, - SymbolFor, - SymbolIterator, - StringPrototypeReplaceAll, - TypeError, - } = window.__bootstrap.primordials; - - const _headerList = Symbol("header list"); - const _iterableHeaders = Symbol("iterable headers"); - const _guard = Symbol("guard"); - - /** - * @typedef Header - * @type {[string, string]} - */ - /** - * @typedef HeaderList - * @type {Header[]} - */ +import * as webidl from "internal:deno_webidl/00_webidl.js"; +import { + byteLowerCase, + collectHttpQuotedString, + collectSequenceOfCodepoints, + HTTP_TAB_OR_SPACE_PREFIX_RE, + HTTP_TAB_OR_SPACE_SUFFIX_RE, + HTTP_TOKEN_CODE_POINT_RE, + httpTrim, +} from "internal:deno_web/00_infra.js"; +const primordials = globalThis.__bootstrap.primordials; +const { + ArrayIsArray, + ArrayPrototypeMap, + ArrayPrototypePush, + ArrayPrototypeSort, + ArrayPrototypeJoin, + ArrayPrototypeSplice, + ArrayPrototypeFilter, + ObjectPrototypeHasOwnProperty, + ObjectEntries, + RegExpPrototypeTest, + SafeArrayIterator, + Symbol, + SymbolFor, + SymbolIterator, + StringPrototypeReplaceAll, + TypeError, +} = primordials; + +const _headerList = Symbol("header list"); +const _iterableHeaders = Symbol("iterable headers"); +const _guard = Symbol("guard"); + +/** + * @typedef Header + * @type {[string, string]} + */ + +/** + * @typedef HeaderList + * @type {Header[]} + */ + +/** + * @param {string} potentialValue + * @returns {string} + */ +function normalizeHeaderValue(potentialValue) { + return httpTrim(potentialValue); +} + +/** + * @param {Headers} headers + * @param {HeadersInit} object + */ +function fillHeaders(headers, object) { + if (ArrayIsArray(object)) { + for (let i = 0; i < object.length; ++i) { + const header = object[i]; + if (header.length !== 2) { + throw new TypeError( + `Invalid header. Length must be 2, but is ${header.length}`, + ); + } + appendHeader(headers, header[0], header[1]); + } + } else { + for (const key in object) { + if (!ObjectPrototypeHasOwnProperty(object, key)) { + continue; + } + appendHeader(headers, key, object[key]); + } + } +} + +// Regex matching illegal chars in a header value +// deno-lint-ignore no-control-regex +const ILLEGAL_VALUE_CHARS = /[\x00\x0A\x0D]/; + +/** + * https://fetch.spec.whatwg.org/#concept-headers-append + * @param {Headers} headers + * @param {string} name + * @param {string} value + */ +function appendHeader(headers, name, value) { + // 1. + value = normalizeHeaderValue(value); + + // 2. + if (!RegExpPrototypeTest(HTTP_TOKEN_CODE_POINT_RE, name)) { + throw new TypeError("Header name is not valid."); + } + if (RegExpPrototypeTest(ILLEGAL_VALUE_CHARS, value)) { + throw new TypeError("Header value is not valid."); + } - /** - * @param {string} potentialValue - * @returns {string} - */ - function normalizeHeaderValue(potentialValue) { - return httpTrim(potentialValue); + // 3. + if (headers[_guard] == "immutable") { + throw new TypeError("Headers are immutable."); } - /** - * @param {Headers} headers - * @param {HeadersInit} object - */ - function fillHeaders(headers, object) { - if (ArrayIsArray(object)) { - for (let i = 0; i < object.length; ++i) { - const header = object[i]; - if (header.length !== 2) { - throw new TypeError( - `Invalid header. Length must be 2, but is ${header.length}`, - ); + // 7. + const list = headers[_headerList]; + const lowercaseName = byteLowerCase(name); + for (let i = 0; i < list.length; i++) { + if (byteLowerCase(list[i][0]) === lowercaseName) { + name = list[i][0]; + break; + } + } + ArrayPrototypePush(list, [name, value]); +} + +/** + * https://fetch.spec.whatwg.org/#concept-header-list-get + * @param {HeaderList} list + * @param {string} name + */ +function getHeader(list, name) { + const lowercaseName = byteLowerCase(name); + const entries = ArrayPrototypeMap( + ArrayPrototypeFilter( + list, + (entry) => byteLowerCase(entry[0]) === lowercaseName, + ), + (entry) => entry[1], + ); + if (entries.length === 0) { + return null; + } else { + return ArrayPrototypeJoin(entries, "\x2C\x20"); + } +} + +/** + * https://fetch.spec.whatwg.org/#concept-header-list-get-decode-split + * @param {HeaderList} list + * @param {string} name + * @returns {string[] | null} + */ +function getDecodeSplitHeader(list, name) { + const initialValue = getHeader(list, name); + if (initialValue === null) return null; + const input = initialValue; + let position = 0; + const values = []; + let value = ""; + while (position < initialValue.length) { + // 7.1. collect up to " or , + const res = collectSequenceOfCodepoints( + initialValue, + position, + (c) => c !== "\u0022" && c !== "\u002C", + ); + value += res.result; + position = res.position; + + if (position < initialValue.length) { + if (input[position] === "\u0022") { + const res = collectHttpQuotedString(input, position, false); + value += res.result; + position = res.position; + if (position < initialValue.length) { + continue; } - appendHeader(headers, header[0], header[1]); + } else { + if (input[position] !== "\u002C") throw new TypeError("Unreachable"); + position += 1; } - } else { - for (const key in object) { - if (!ObjectPrototypeHasOwnProperty(object, key)) { - continue; + } + + value = StringPrototypeReplaceAll(value, HTTP_TAB_OR_SPACE_PREFIX_RE, ""); + value = StringPrototypeReplaceAll(value, HTTP_TAB_OR_SPACE_SUFFIX_RE, ""); + + ArrayPrototypePush(values, value); + value = ""; + } + return values; +} + +class Headers { + /** @type {HeaderList} */ + [_headerList] = []; + /** @type {"immutable" | "request" | "request-no-cors" | "response" | "none"} */ + [_guard]; + + get [_iterableHeaders]() { + const list = this[_headerList]; + + // The order of steps are not similar to the ones suggested by the + // spec but produce the same result. + const headers = {}; + const cookies = []; + for (let i = 0; i < list.length; ++i) { + const entry = list[i]; + const name = byteLowerCase(entry[0]); + const value = entry[1]; + if (value === null) throw new TypeError("Unreachable"); + // The following if statement is not spec compliant. + // `set-cookie` is the only header that can not be concatenated, + // so must be given to the user as multiple headers. + // The else block of the if statement is spec compliant again. + if (name === "set-cookie") { + ArrayPrototypePush(cookies, [name, value]); + } else { + // The following code has the same behaviour as getHeader() + // at the end of loop. But it avoids looping through the entire + // list to combine multiple values with same header name. It + // instead gradually combines them as they are found. + let header = headers[name]; + if (header && header.length > 0) { + header += "\x2C\x20" + value; + } else { + header = value; } - appendHeader(headers, key, object[key]); + headers[name] = header; } } + + return ArrayPrototypeSort( + [ + ...new SafeArrayIterator(ObjectEntries(headers)), + ...new SafeArrayIterator(cookies), + ], + (a, b) => { + const akey = a[0]; + const bkey = b[0]; + if (akey > bkey) return 1; + if (akey < bkey) return -1; + return 0; + }, + ); } - // Regex matching illegal chars in a header value - // deno-lint-ignore no-control-regex - const ILLEGAL_VALUE_CHARS = /[\x00\x0A\x0D]/; + /** @param {HeadersInit} [init] */ + constructor(init = undefined) { + const prefix = "Failed to construct 'Headers'"; + if (init !== undefined) { + init = webidl.converters["HeadersInit"](init, { + prefix, + context: "Argument 1", + }); + } + + this[webidl.brand] = webidl.brand; + this[_guard] = "none"; + if (init !== undefined) { + fillHeaders(this, init); + } + } /** - * https://fetch.spec.whatwg.org/#concept-headers-append - * @param {Headers} headers * @param {string} name * @param {string} value */ - function appendHeader(headers, name, value) { - // 1. - value = normalizeHeaderValue(value); + append(name, value) { + webidl.assertBranded(this, HeadersPrototype); + const prefix = "Failed to execute 'append' on 'Headers'"; + webidl.requiredArguments(arguments.length, 2, { prefix }); + name = webidl.converters["ByteString"](name, { + prefix, + context: "Argument 1", + }); + value = webidl.converters["ByteString"](value, { + prefix, + context: "Argument 2", + }); + appendHeader(this, name, value); + } + + /** + * @param {string} name + */ + delete(name) { + const prefix = "Failed to execute 'delete' on 'Headers'"; + webidl.requiredArguments(arguments.length, 1, { prefix }); + name = webidl.converters["ByteString"](name, { + prefix, + context: "Argument 1", + }); - // 2. if (!RegExpPrototypeTest(HTTP_TOKEN_CODE_POINT_RE, name)) { throw new TypeError("Header name is not valid."); } - if (RegExpPrototypeTest(ILLEGAL_VALUE_CHARS, value)) { - throw new TypeError("Header value is not valid."); - } - - // 3. - if (headers[_guard] == "immutable") { + if (this[_guard] == "immutable") { throw new TypeError("Headers are immutable."); } - // 7. - const list = headers[_headerList]; + const list = this[_headerList]; const lowercaseName = byteLowerCase(name); for (let i = 0; i < list.length; i++) { if (byteLowerCase(list[i][0]) === lowercaseName) { - name = list[i][0]; - break; + ArrayPrototypeSplice(list, i, 1); + i--; } } - ArrayPrototypePush(list, [name, value]); } /** - * https://fetch.spec.whatwg.org/#concept-header-list-get - * @param {HeaderList} list * @param {string} name */ - function getHeader(list, name) { - const lowercaseName = byteLowerCase(name); - const entries = ArrayPrototypeMap( - ArrayPrototypeFilter( - list, - (entry) => byteLowerCase(entry[0]) === lowercaseName, - ), - (entry) => entry[1], - ); - if (entries.length === 0) { - return null; - } else { - return ArrayPrototypeJoin(entries, "\x2C\x20"); + get(name) { + const prefix = "Failed to execute 'get' on 'Headers'"; + webidl.requiredArguments(arguments.length, 1, { prefix }); + name = webidl.converters["ByteString"](name, { + prefix, + context: "Argument 1", + }); + + if (!RegExpPrototypeTest(HTTP_TOKEN_CODE_POINT_RE, name)) { + throw new TypeError("Header name is not valid."); } + + const list = this[_headerList]; + return getHeader(list, name); } /** - * https://fetch.spec.whatwg.org/#concept-header-list-get-decode-split - * @param {HeaderList} list * @param {string} name - * @returns {string[] | null} */ - function getDecodeSplitHeader(list, name) { - const initialValue = getHeader(list, name); - if (initialValue === null) return null; - const input = initialValue; - let position = 0; - const values = []; - let value = ""; - while (position < initialValue.length) { - // 7.1. collect up to " or , - const res = collectSequenceOfCodepoints( - initialValue, - position, - (c) => c !== "\u0022" && c !== "\u002C", - ); - value += res.result; - position = res.position; - - if (position < initialValue.length) { - if (input[position] === "\u0022") { - const res = collectHttpQuotedString(input, position, false); - value += res.result; - position = res.position; - if (position < initialValue.length) { - continue; - } - } else { - if (input[position] !== "\u002C") throw new TypeError("Unreachable"); - position += 1; - } - } - - value = StringPrototypeReplaceAll(value, HTTP_TAB_OR_SPACE_PREFIX_RE, ""); - value = StringPrototypeReplaceAll(value, HTTP_TAB_OR_SPACE_SUFFIX_RE, ""); + has(name) { + const prefix = "Failed to execute 'has' on 'Headers'"; + webidl.requiredArguments(arguments.length, 1, { prefix }); + name = webidl.converters["ByteString"](name, { + prefix, + context: "Argument 1", + }); - ArrayPrototypePush(values, value); - value = ""; + if (!RegExpPrototypeTest(HTTP_TOKEN_CODE_POINT_RE, name)) { + throw new TypeError("Header name is not valid."); } - return values; - } - class Headers { - /** @type {HeaderList} */ - [_headerList] = []; - /** @type {"immutable" | "request" | "request-no-cors" | "response" | "none"} */ - [_guard]; - - get [_iterableHeaders]() { - const list = this[_headerList]; - - // The order of steps are not similar to the ones suggested by the - // spec but produce the same result. - const headers = {}; - const cookies = []; - for (let i = 0; i < list.length; ++i) { - const entry = list[i]; - const name = byteLowerCase(entry[0]); - const value = entry[1]; - if (value === null) throw new TypeError("Unreachable"); - // The following if statement is not spec compliant. - // `set-cookie` is the only header that can not be concatenated, - // so must be given to the user as multiple headers. - // The else block of the if statement is spec compliant again. - if (name === "set-cookie") { - ArrayPrototypePush(cookies, [name, value]); - } else { - // The following code has the same behaviour as getHeader() - // at the end of loop. But it avoids looping through the entire - // list to combine multiple values with same header name. It - // instead gradually combines them as they are found. - let header = headers[name]; - if (header && header.length > 0) { - header += "\x2C\x20" + value; - } else { - header = value; - } - headers[name] = header; - } + const list = this[_headerList]; + const lowercaseName = byteLowerCase(name); + for (let i = 0; i < list.length; i++) { + if (byteLowerCase(list[i][0]) === lowercaseName) { + return true; } - - return ArrayPrototypeSort( - [ - ...new SafeArrayIterator(ObjectEntries(headers)), - ...new SafeArrayIterator(cookies), - ], - (a, b) => { - const akey = a[0]; - const bkey = b[0]; - if (akey > bkey) return 1; - if (akey < bkey) return -1; - return 0; - }, - ); } + return false; + } - /** @param {HeadersInit} [init] */ - constructor(init = undefined) { - const prefix = "Failed to construct 'Headers'"; - if (init !== undefined) { - init = webidl.converters["HeadersInit"](init, { - prefix, - context: "Argument 1", - }); - } + /** + * @param {string} name + * @param {string} value + */ + set(name, value) { + webidl.assertBranded(this, HeadersPrototype); + const prefix = "Failed to execute 'set' on 'Headers'"; + webidl.requiredArguments(arguments.length, 2, { prefix }); + name = webidl.converters["ByteString"](name, { + prefix, + context: "Argument 1", + }); + value = webidl.converters["ByteString"](value, { + prefix, + context: "Argument 2", + }); - this[webidl.brand] = webidl.brand; - this[_guard] = "none"; - if (init !== undefined) { - fillHeaders(this, init); - } - } + value = normalizeHeaderValue(value); - /** - * @param {string} name - * @param {string} value - */ - append(name, value) { - webidl.assertBranded(this, HeadersPrototype); - const prefix = "Failed to execute 'append' on 'Headers'"; - webidl.requiredArguments(arguments.length, 2, { prefix }); - name = webidl.converters["ByteString"](name, { - prefix, - context: "Argument 1", - }); - value = webidl.converters["ByteString"](value, { - prefix, - context: "Argument 2", - }); - appendHeader(this, name, value); + // 2. + if (!RegExpPrototypeTest(HTTP_TOKEN_CODE_POINT_RE, name)) { + throw new TypeError("Header name is not valid."); } - - /** - * @param {string} name - */ - delete(name) { - const prefix = "Failed to execute 'delete' on 'Headers'"; - webidl.requiredArguments(arguments.length, 1, { prefix }); - name = webidl.converters["ByteString"](name, { - prefix, - context: "Argument 1", - }); - - if (!RegExpPrototypeTest(HTTP_TOKEN_CODE_POINT_RE, name)) { - throw new TypeError("Header name is not valid."); - } - if (this[_guard] == "immutable") { - throw new TypeError("Headers are immutable."); - } - - const list = this[_headerList]; - const lowercaseName = byteLowerCase(name); - for (let i = 0; i < list.length; i++) { - if (byteLowerCase(list[i][0]) === lowercaseName) { - ArrayPrototypeSplice(list, i, 1); - i--; - } - } + if (RegExpPrototypeTest(ILLEGAL_VALUE_CHARS, value)) { + throw new TypeError("Header value is not valid."); } - /** - * @param {string} name - */ - get(name) { - const prefix = "Failed to execute 'get' on 'Headers'"; - webidl.requiredArguments(arguments.length, 1, { prefix }); - name = webidl.converters["ByteString"](name, { - prefix, - context: "Argument 1", - }); - - if (!RegExpPrototypeTest(HTTP_TOKEN_CODE_POINT_RE, name)) { - throw new TypeError("Header name is not valid."); - } - - const list = this[_headerList]; - return getHeader(list, name); + if (this[_guard] == "immutable") { + throw new TypeError("Headers are immutable."); } - /** - * @param {string} name - */ - has(name) { - const prefix = "Failed to execute 'has' on 'Headers'"; - webidl.requiredArguments(arguments.length, 1, { prefix }); - name = webidl.converters["ByteString"](name, { - prefix, - context: "Argument 1", - }); - - if (!RegExpPrototypeTest(HTTP_TOKEN_CODE_POINT_RE, name)) { - throw new TypeError("Header name is not valid."); - } - - const list = this[_headerList]; - const lowercaseName = byteLowerCase(name); - for (let i = 0; i < list.length; i++) { - if (byteLowerCase(list[i][0]) === lowercaseName) { - return true; + const list = this[_headerList]; + const lowercaseName = byteLowerCase(name); + let added = false; + for (let i = 0; i < list.length; i++) { + if (byteLowerCase(list[i][0]) === lowercaseName) { + if (!added) { + list[i][1] = value; + added = true; + } else { + ArrayPrototypeSplice(list, i, 1); + i--; } } - return false; } - - /** - * @param {string} name - * @param {string} value - */ - set(name, value) { - webidl.assertBranded(this, HeadersPrototype); - const prefix = "Failed to execute 'set' on 'Headers'"; - webidl.requiredArguments(arguments.length, 2, { prefix }); - name = webidl.converters["ByteString"](name, { - prefix, - context: "Argument 1", - }); - value = webidl.converters["ByteString"](value, { - prefix, - context: "Argument 2", - }); - - value = normalizeHeaderValue(value); - - // 2. - if (!RegExpPrototypeTest(HTTP_TOKEN_CODE_POINT_RE, name)) { - throw new TypeError("Header name is not valid."); - } - if (RegExpPrototypeTest(ILLEGAL_VALUE_CHARS, value)) { - throw new TypeError("Header value is not valid."); - } - - if (this[_guard] == "immutable") { - throw new TypeError("Headers are immutable."); - } - - const list = this[_headerList]; - const lowercaseName = byteLowerCase(name); - let added = false; - for (let i = 0; i < list.length; i++) { - if (byteLowerCase(list[i][0]) === lowercaseName) { - if (!added) { - list[i][1] = value; - added = true; - } else { - ArrayPrototypeSplice(list, i, 1); - i--; - } - } - } - if (!added) { - ArrayPrototypePush(list, [name, value]); - } + if (!added) { + ArrayPrototypePush(list, [name, value]); } + } - [SymbolFor("Deno.privateCustomInspect")](inspect) { - const headers = {}; - // deno-lint-ignore prefer-primordials - for (const header of this) { - headers[header[0]] = header[1]; - } - return `Headers ${inspect(headers)}`; + [SymbolFor("Deno.privateCustomInspect")](inspect) { + const headers = {}; + // deno-lint-ignore prefer-primordials + for (const header of this) { + headers[header[0]] = header[1]; } + return `Headers ${inspect(headers)}`; } +} - webidl.mixinPairIterable("Headers", Headers, _iterableHeaders, 0, 1); +webidl.mixinPairIterable("Headers", Headers, _iterableHeaders, 0, 1); - webidl.configurePrototype(Headers); - const HeadersPrototype = Headers.prototype; +webidl.configurePrototype(Headers); +const HeadersPrototype = Headers.prototype; - webidl.converters["HeadersInit"] = (V, opts) => { - // Union for (sequence> or record) - if (webidl.type(V) === "Object" && V !== null) { - if (V[SymbolIterator] !== undefined) { - return webidl.converters["sequence>"](V, opts); - } - return webidl.converters["record"](V, opts); +webidl.converters["HeadersInit"] = (V, opts) => { + // Union for (sequence> or record) + if (webidl.type(V) === "Object" && V !== null) { + if (V[SymbolIterator] !== undefined) { + return webidl.converters["sequence>"](V, opts); } - throw webidl.makeException( - TypeError, - "The provided value is not of type '(sequence> or record)'", - opts, - ); - }; - webidl.converters["Headers"] = webidl.createInterfaceConverter( - "Headers", - Headers.prototype, - ); - - /** - * @param {HeaderList} list - * @param {"immutable" | "request" | "request-no-cors" | "response" | "none"} guard - * @returns {Headers} - */ - function headersFromHeaderList(list, guard) { - const headers = webidl.createBranded(Headers); - headers[_headerList] = list; - headers[_guard] = guard; - return headers; + return webidl.converters["record"](V, opts); } - - /** - * @param {Headers} - * @returns {HeaderList} - */ - function headerListFromHeaders(headers) { - return headers[_headerList]; - } - - /** - * @param {Headers} - * @returns {"immutable" | "request" | "request-no-cors" | "response" | "none"} - */ - function guardFromHeaders(headers) { - return headers[_guard]; - } - - window.__bootstrap.headers = { - headersFromHeaderList, - headerListFromHeaders, - getDecodeSplitHeader, - guardFromHeaders, - fillHeaders, - getHeader, - Headers, - }; -})(this); + throw webidl.makeException( + TypeError, + "The provided value is not of type '(sequence> or record)'", + opts, + ); +}; +webidl.converters["Headers"] = webidl.createInterfaceConverter( + "Headers", + Headers.prototype, +); + +/** + * @param {HeaderList} list + * @param {"immutable" | "request" | "request-no-cors" | "response" | "none"} guard + * @returns {Headers} + */ +function headersFromHeaderList(list, guard) { + const headers = webidl.createBranded(Headers); + headers[_headerList] = list; + headers[_guard] = guard; + return headers; +} + +/** + * @param {Headers} headers + * @returns {HeaderList} + */ +function headerListFromHeaders(headers) { + return headers[_headerList]; +} + +/** + * @param {Headers} headers + * @returns {"immutable" | "request" | "request-no-cors" | "response" | "none"} + */ +function guardFromHeaders(headers) { + return headers[_guard]; +} + +export { + fillHeaders, + getDecodeSplitHeader, + getHeader, + guardFromHeaders, + headerListFromHeaders, + Headers, + headersFromHeaderList, +}; diff --git a/ext/fetch/21_formdata.js b/ext/fetch/21_formdata.js index d253976ef1bf09..647c716b3eecea 100644 --- a/ext/fetch/21_formdata.js +++ b/ext/fetch/21_formdata.js @@ -8,516 +8,518 @@ /// /// /// -"use strict"; - -((window) => { - const core = window.Deno.core; - const webidl = globalThis.__bootstrap.webidl; - const { Blob, BlobPrototype, File, FilePrototype } = - globalThis.__bootstrap.file; - const { - ArrayPrototypePush, - ArrayPrototypeSlice, - ArrayPrototypeSplice, - Map, - MapPrototypeGet, - MapPrototypeSet, - MathRandom, - ObjectPrototypeIsPrototypeOf, - Symbol, - StringFromCharCode, - StringPrototypeTrim, - StringPrototypeSlice, - StringPrototypeSplit, - StringPrototypeReplace, - StringPrototypeIndexOf, - StringPrototypePadStart, - StringPrototypeCodePointAt, - StringPrototypeReplaceAll, - TypeError, - TypedArrayPrototypeSubarray, - } = window.__bootstrap.primordials; - - const entryList = Symbol("entry list"); - /** - * @param {string} name - * @param {string | Blob} value - * @param {string | undefined} filename - * @returns {FormDataEntry} - */ - function createEntry(name, value, filename) { - if ( - ObjectPrototypeIsPrototypeOf(BlobPrototype, value) && - !ObjectPrototypeIsPrototypeOf(FilePrototype, value) - ) { - value = new File([value], "blob", { type: value.type }); - } - if ( - ObjectPrototypeIsPrototypeOf(FilePrototype, value) && - filename !== undefined - ) { - value = new File([value], filename, { - type: value.type, - lastModified: value.lastModified, - }); +const core = globalThis.Deno.core; +import * as webidl from "internal:deno_webidl/00_webidl.js"; +import { + Blob, + BlobPrototype, + File, + FilePrototype, +} from "internal:deno_web/09_file.js"; +const primordials = globalThis.__bootstrap.primordials; +const { + ArrayPrototypePush, + ArrayPrototypeSlice, + ArrayPrototypeSplice, + Map, + MapPrototypeGet, + MapPrototypeSet, + MathRandom, + ObjectPrototypeIsPrototypeOf, + Symbol, + StringFromCharCode, + StringPrototypeTrim, + StringPrototypeSlice, + StringPrototypeSplit, + StringPrototypeReplace, + StringPrototypeIndexOf, + StringPrototypePadStart, + StringPrototypeCodePointAt, + StringPrototypeReplaceAll, + TypeError, + TypedArrayPrototypeSubarray, +} = primordials; + +const entryList = Symbol("entry list"); + +/** + * @param {string} name + * @param {string | Blob} value + * @param {string | undefined} filename + * @returns {FormDataEntry} + */ +function createEntry(name, value, filename) { + if ( + ObjectPrototypeIsPrototypeOf(BlobPrototype, value) && + !ObjectPrototypeIsPrototypeOf(FilePrototype, value) + ) { + value = new File([value], "blob", { type: value.type }); + } + if ( + ObjectPrototypeIsPrototypeOf(FilePrototype, value) && + filename !== undefined + ) { + value = new File([value], filename, { + type: value.type, + lastModified: value.lastModified, + }); + } + return { + name, + // @ts-expect-error because TS is not smart enough + value, + }; +} + +/** + * @typedef FormDataEntry + * @property {string} name + * @property {FormDataEntryValue} value + */ + +class FormData { + /** @type {FormDataEntry[]} */ + [entryList] = []; + + /** @param {void} form */ + constructor(form) { + if (form !== undefined) { + webidl.illegalConstructor(); } - return { - name, - // @ts-expect-error because TS is not smart enough - value, - }; + this[webidl.brand] = webidl.brand; } /** - * @typedef FormDataEntry - * @property {string} name - * @property {FormDataEntryValue} value + * @param {string} name + * @param {string | Blob} valueOrBlobValue + * @param {string} [filename] + * @returns {void} */ - - class FormData { - /** @type {FormDataEntry[]} */ - [entryList] = []; - - /** @param {void} form */ - constructor(form) { - if (form !== undefined) { - webidl.illegalConstructor(); - } - this[webidl.brand] = webidl.brand; - } - - /** - * @param {string} name - * @param {string | Blob} valueOrBlobValue - * @param {string} [filename] - * @returns {void} - */ - append(name, valueOrBlobValue, filename) { - webidl.assertBranded(this, FormDataPrototype); - const prefix = "Failed to execute 'append' on 'FormData'"; - webidl.requiredArguments(arguments.length, 2, { prefix }); - - name = webidl.converters["USVString"](name, { + append(name, valueOrBlobValue, filename) { + webidl.assertBranded(this, FormDataPrototype); + const prefix = "Failed to execute 'append' on 'FormData'"; + webidl.requiredArguments(arguments.length, 2, { prefix }); + + name = webidl.converters["USVString"](name, { + prefix, + context: "Argument 1", + }); + if (ObjectPrototypeIsPrototypeOf(BlobPrototype, valueOrBlobValue)) { + valueOrBlobValue = webidl.converters["Blob"](valueOrBlobValue, { prefix, - context: "Argument 1", + context: "Argument 2", }); - if (ObjectPrototypeIsPrototypeOf(BlobPrototype, valueOrBlobValue)) { - valueOrBlobValue = webidl.converters["Blob"](valueOrBlobValue, { - prefix, - context: "Argument 2", - }); - if (filename !== undefined) { - filename = webidl.converters["USVString"](filename, { - prefix, - context: "Argument 3", - }); - } - } else { - valueOrBlobValue = webidl.converters["USVString"](valueOrBlobValue, { + if (filename !== undefined) { + filename = webidl.converters["USVString"](filename, { prefix, - context: "Argument 2", + context: "Argument 3", }); } - - const entry = createEntry(name, valueOrBlobValue, filename); - - ArrayPrototypePush(this[entryList], entry); - } - - /** - * @param {string} name - * @returns {void} - */ - delete(name) { - webidl.assertBranded(this, FormDataPrototype); - const prefix = "Failed to execute 'name' on 'FormData'"; - webidl.requiredArguments(arguments.length, 1, { prefix }); - - name = webidl.converters["USVString"](name, { + } else { + valueOrBlobValue = webidl.converters["USVString"](valueOrBlobValue, { prefix, - context: "Argument 1", + context: "Argument 2", }); - - const list = this[entryList]; - for (let i = 0; i < list.length; i++) { - if (list[i].name === name) { - ArrayPrototypeSplice(list, i, 1); - i--; - } - } } - /** - * @param {string} name - * @returns {FormDataEntryValue | null} - */ - get(name) { - webidl.assertBranded(this, FormDataPrototype); - const prefix = "Failed to execute 'get' on 'FormData'"; - webidl.requiredArguments(arguments.length, 1, { prefix }); + const entry = createEntry(name, valueOrBlobValue, filename); - name = webidl.converters["USVString"](name, { - prefix, - context: "Argument 1", - }); + ArrayPrototypePush(this[entryList], entry); + } - const entries = this[entryList]; - for (let i = 0; i < entries.length; ++i) { - const entry = entries[i]; - if (entry.name === name) return entry.value; + /** + * @param {string} name + * @returns {void} + */ + delete(name) { + webidl.assertBranded(this, FormDataPrototype); + const prefix = "Failed to execute 'name' on 'FormData'"; + webidl.requiredArguments(arguments.length, 1, { prefix }); + + name = webidl.converters["USVString"](name, { + prefix, + context: "Argument 1", + }); + + const list = this[entryList]; + for (let i = 0; i < list.length; i++) { + if (list[i].name === name) { + ArrayPrototypeSplice(list, i, 1); + i--; } - return null; } + } - /** - * @param {string} name - * @returns {FormDataEntryValue[]} - */ - getAll(name) { - webidl.assertBranded(this, FormDataPrototype); - const prefix = "Failed to execute 'getAll' on 'FormData'"; - webidl.requiredArguments(arguments.length, 1, { prefix }); - - name = webidl.converters["USVString"](name, { - prefix, - context: "Argument 1", - }); + /** + * @param {string} name + * @returns {FormDataEntryValue | null} + */ + get(name) { + webidl.assertBranded(this, FormDataPrototype); + const prefix = "Failed to execute 'get' on 'FormData'"; + webidl.requiredArguments(arguments.length, 1, { prefix }); + + name = webidl.converters["USVString"](name, { + prefix, + context: "Argument 1", + }); - const returnList = []; - const entries = this[entryList]; - for (let i = 0; i < entries.length; ++i) { - const entry = entries[i]; - if (entry.name === name) ArrayPrototypePush(returnList, entry.value); - } - return returnList; + const entries = this[entryList]; + for (let i = 0; i < entries.length; ++i) { + const entry = entries[i]; + if (entry.name === name) return entry.value; } + return null; + } - /** - * @param {string} name - * @returns {boolean} - */ - has(name) { - webidl.assertBranded(this, FormDataPrototype); - const prefix = "Failed to execute 'has' on 'FormData'"; - webidl.requiredArguments(arguments.length, 1, { prefix }); + /** + * @param {string} name + * @returns {FormDataEntryValue[]} + */ + getAll(name) { + webidl.assertBranded(this, FormDataPrototype); + const prefix = "Failed to execute 'getAll' on 'FormData'"; + webidl.requiredArguments(arguments.length, 1, { prefix }); + + name = webidl.converters["USVString"](name, { + prefix, + context: "Argument 1", + }); - name = webidl.converters["USVString"](name, { - prefix, - context: "Argument 1", - }); + const returnList = []; + const entries = this[entryList]; + for (let i = 0; i < entries.length; ++i) { + const entry = entries[i]; + if (entry.name === name) ArrayPrototypePush(returnList, entry.value); + } + return returnList; + } - const entries = this[entryList]; - for (let i = 0; i < entries.length; ++i) { - const entry = entries[i]; - if (entry.name === name) return true; - } - return false; + /** + * @param {string} name + * @returns {boolean} + */ + has(name) { + webidl.assertBranded(this, FormDataPrototype); + const prefix = "Failed to execute 'has' on 'FormData'"; + webidl.requiredArguments(arguments.length, 1, { prefix }); + + name = webidl.converters["USVString"](name, { + prefix, + context: "Argument 1", + }); + + const entries = this[entryList]; + for (let i = 0; i < entries.length; ++i) { + const entry = entries[i]; + if (entry.name === name) return true; } + return false; + } - /** - * @param {string} name - * @param {string | Blob} valueOrBlobValue - * @param {string} [filename] - * @returns {void} - */ - set(name, valueOrBlobValue, filename) { - webidl.assertBranded(this, FormDataPrototype); - const prefix = "Failed to execute 'set' on 'FormData'"; - webidl.requiredArguments(arguments.length, 2, { prefix }); - - name = webidl.converters["USVString"](name, { + /** + * @param {string} name + * @param {string | Blob} valueOrBlobValue + * @param {string} [filename] + * @returns {void} + */ + set(name, valueOrBlobValue, filename) { + webidl.assertBranded(this, FormDataPrototype); + const prefix = "Failed to execute 'set' on 'FormData'"; + webidl.requiredArguments(arguments.length, 2, { prefix }); + + name = webidl.converters["USVString"](name, { + prefix, + context: "Argument 1", + }); + if (ObjectPrototypeIsPrototypeOf(BlobPrototype, valueOrBlobValue)) { + valueOrBlobValue = webidl.converters["Blob"](valueOrBlobValue, { prefix, - context: "Argument 1", + context: "Argument 2", }); - if (ObjectPrototypeIsPrototypeOf(BlobPrototype, valueOrBlobValue)) { - valueOrBlobValue = webidl.converters["Blob"](valueOrBlobValue, { - prefix, - context: "Argument 2", - }); - if (filename !== undefined) { - filename = webidl.converters["USVString"](filename, { - prefix, - context: "Argument 3", - }); - } - } else { - valueOrBlobValue = webidl.converters["USVString"](valueOrBlobValue, { + if (filename !== undefined) { + filename = webidl.converters["USVString"](filename, { prefix, - context: "Argument 2", + context: "Argument 3", }); } + } else { + valueOrBlobValue = webidl.converters["USVString"](valueOrBlobValue, { + prefix, + context: "Argument 2", + }); + } - const entry = createEntry(name, valueOrBlobValue, filename); + const entry = createEntry(name, valueOrBlobValue, filename); - const list = this[entryList]; - let added = false; - for (let i = 0; i < list.length; i++) { - if (list[i].name === name) { - if (!added) { - list[i] = entry; - added = true; - } else { - ArrayPrototypeSplice(list, i, 1); - i--; - } + const list = this[entryList]; + let added = false; + for (let i = 0; i < list.length; i++) { + if (list[i].name === name) { + if (!added) { + list[i] = entry; + added = true; + } else { + ArrayPrototypeSplice(list, i, 1); + i--; } } - if (!added) { - ArrayPrototypePush(list, entry); - } + } + if (!added) { + ArrayPrototypePush(list, entry); } } +} - webidl.mixinPairIterable("FormData", FormData, entryList, "name", "value"); +webidl.mixinPairIterable("FormData", FormData, entryList, "name", "value"); - webidl.configurePrototype(FormData); - const FormDataPrototype = FormData.prototype; +webidl.configurePrototype(FormData); +const FormDataPrototype = FormData.prototype; - const escape = (str, isFilename) => { - const escapeMap = { - "\n": "%0A", - "\r": "%0D", - '"': "%22", - }; - - return StringPrototypeReplace( - isFilename ? str : StringPrototypeReplace(str, /\r?\n|\r/g, "\r\n"), - /([\n\r"])/g, - (c) => escapeMap[c], - ); +const escape = (str, isFilename) => { + const escapeMap = { + "\n": "%0A", + "\r": "%0D", + '"': "%22", }; - /** - * convert FormData to a Blob synchronous without reading all of the files - * @param {globalThis.FormData} formData - */ - function formDataToBlob(formData) { - const boundary = StringPrototypePadStart( - StringPrototypeSlice( - StringPrototypeReplaceAll(`${MathRandom()}${MathRandom()}`, ".", ""), - -28, - ), - 32, - "-", - ); - const chunks = []; - const prefix = `--${boundary}\r\nContent-Disposition: form-data; name="`; - - // deno-lint-ignore prefer-primordials - for (const { 0: name, 1: value } of formData) { - if (typeof value === "string") { - ArrayPrototypePush( - chunks, - prefix + escape(name) + '"' + CRLF + CRLF + - StringPrototypeReplace(value, /\r(?!\n)|(? escapeMap[c], + ); +}; + +/** + * convert FormData to a Blob synchronous without reading all of the files + * @param {globalThis.FormData} formData + */ +function formDataToBlob(formData) { + const boundary = StringPrototypePadStart( + StringPrototypeSlice( + StringPrototypeReplaceAll(`${MathRandom()}${MathRandom()}`, ".", ""), + -28, + ), + 32, + "-", + ); + const chunks = []; + const prefix = `--${boundary}\r\nContent-Disposition: form-data; name="`; + + // deno-lint-ignore prefer-primordials + for (const { 0: name, 1: value } of formData) { + if (typeof value === "string") { + ArrayPrototypePush( + chunks, + prefix + escape(name) + '"' + CRLF + CRLF + + StringPrototypeReplace(value, /\r(?!\n)|(?} + */ +function parseContentDisposition(value) { + /** @type {Map} */ + const params = new Map(); + // Forced to do so for some Map constructor param mismatch + const values = ArrayPrototypeSlice(StringPrototypeSplit(value, ";"), 1); + for (let i = 0; i < values.length; i++) { + const entries = StringPrototypeSplit(StringPrototypeTrim(values[i]), "="); + if (entries.length > 1) { + MapPrototypeSet( + params, + entries[0], + StringPrototypeReplace(entries[1], /^"([^"]*)"$/, "$1"), + ); + } } + return params; +} +const CRLF = "\r\n"; +const LF = StringPrototypeCodePointAt(CRLF, 1); +const CR = StringPrototypeCodePointAt(CRLF, 0); + +class MultipartParser { /** - * @param {string} value - * @returns {Map} + * @param {Uint8Array} body + * @param {string | undefined} boundary */ - function parseContentDisposition(value) { - /** @type {Map} */ - const params = new Map(); - // Forced to do so for some Map constructor param mismatch - const values = ArrayPrototypeSlice(StringPrototypeSplit(value, ";"), 1); - for (let i = 0; i < values.length; i++) { - const entries = StringPrototypeSplit(StringPrototypeTrim(values[i]), "="); - if (entries.length > 1) { - MapPrototypeSet( - params, - entries[0], - StringPrototypeReplace(entries[1], /^"([^"]*)"$/, "$1"), - ); - } + constructor(body, boundary) { + if (!boundary) { + throw new TypeError("multipart/form-data must provide a boundary"); } - return params; + + this.boundary = `--${boundary}`; + this.body = body; + this.boundaryChars = core.encode(this.boundary); } - const CRLF = "\r\n"; - const LF = StringPrototypeCodePointAt(CRLF, 1); - const CR = StringPrototypeCodePointAt(CRLF, 0); - - class MultipartParser { - /** - * @param {Uint8Array} body - * @param {string | undefined} boundary - */ - constructor(body, boundary) { - if (!boundary) { - throw new TypeError("multipart/form-data must provide a boundary"); + /** + * @param {string} headersText + * @returns {{ headers: Headers, disposition: Map }} + */ + #parseHeaders(headersText) { + const headers = new Headers(); + const rawHeaders = StringPrototypeSplit(headersText, "\r\n"); + for (let i = 0; i < rawHeaders.length; ++i) { + const rawHeader = rawHeaders[i]; + const sepIndex = StringPrototypeIndexOf(rawHeader, ":"); + if (sepIndex < 0) { + continue; // Skip this header } - - this.boundary = `--${boundary}`; - this.body = body; - this.boundaryChars = core.encode(this.boundary); + const key = StringPrototypeSlice(rawHeader, 0, sepIndex); + const value = StringPrototypeSlice(rawHeader, sepIndex + 1); + headers.set(key, value); } - /** - * @param {string} headersText - * @returns {{ headers: Headers, disposition: Map }} - */ - #parseHeaders(headersText) { - const headers = new Headers(); - const rawHeaders = StringPrototypeSplit(headersText, "\r\n"); - for (let i = 0; i < rawHeaders.length; ++i) { - const rawHeader = rawHeaders[i]; - const sepIndex = StringPrototypeIndexOf(rawHeader, ":"); - if (sepIndex < 0) { - continue; // Skip this header - } - const key = StringPrototypeSlice(rawHeader, 0, sepIndex); - const value = StringPrototypeSlice(rawHeader, sepIndex + 1); - headers.set(key, value); - } - - const disposition = parseContentDisposition( - headers.get("Content-Disposition") ?? "", - ); + const disposition = parseContentDisposition( + headers.get("Content-Disposition") ?? "", + ); - return { headers, disposition }; - } + return { headers, disposition }; + } - /** - * @returns {FormData} - */ - parse() { - // To have fields body must be at least 2 boundaries + \r\n + -- - // on the last boundary. - if (this.body.length < (this.boundary.length * 2) + 4) { - const decodedBody = core.decode(this.body); - const lastBoundary = this.boundary + "--"; - // check if it's an empty valid form data - if ( - decodedBody === lastBoundary || - decodedBody === lastBoundary + "\r\n" - ) { - return new FormData(); - } - throw new TypeError("Unable to parse body as form data."); + /** + * @returns {FormData} + */ + parse() { + // To have fields body must be at least 2 boundaries + \r\n + -- + // on the last boundary. + if (this.body.length < (this.boundary.length * 2) + 4) { + const decodedBody = core.decode(this.body); + const lastBoundary = this.boundary + "--"; + // check if it's an empty valid form data + if ( + decodedBody === lastBoundary || + decodedBody === lastBoundary + "\r\n" + ) { + return new FormData(); } + throw new TypeError("Unable to parse body as form data."); + } - const formData = new FormData(); - let headerText = ""; - let boundaryIndex = 0; - let state = 0; - let fileStart = 0; + const formData = new FormData(); + let headerText = ""; + let boundaryIndex = 0; + let state = 0; + let fileStart = 0; - for (let i = 0; i < this.body.length; i++) { - const byte = this.body[i]; - const prevByte = this.body[i - 1]; - const isNewLine = byte === LF && prevByte === CR; + for (let i = 0; i < this.body.length; i++) { + const byte = this.body[i]; + const prevByte = this.body[i - 1]; + const isNewLine = byte === LF && prevByte === CR; - if (state === 1 || state === 2 || state == 3) { - headerText += StringFromCharCode(byte); - } - if (state === 0 && isNewLine) { - state = 1; - } else if (state === 1 && isNewLine) { - state = 2; - const headersDone = this.body[i + 1] === CR && - this.body[i + 2] === LF; - - if (headersDone) { - state = 3; - } - } else if (state === 2 && isNewLine) { + if (state === 1 || state === 2 || state == 3) { + headerText += StringFromCharCode(byte); + } + if (state === 0 && isNewLine) { + state = 1; + } else if (state === 1 && isNewLine) { + state = 2; + const headersDone = this.body[i + 1] === CR && + this.body[i + 2] === LF; + + if (headersDone) { state = 3; - } else if (state === 3 && isNewLine) { - state = 4; - fileStart = i + 1; - } else if (state === 4) { - if (this.boundaryChars[boundaryIndex] !== byte) { - boundaryIndex = 0; - } else { - boundaryIndex++; + } + } else if (state === 2 && isNewLine) { + state = 3; + } else if (state === 3 && isNewLine) { + state = 4; + fileStart = i + 1; + } else if (state === 4) { + if (this.boundaryChars[boundaryIndex] !== byte) { + boundaryIndex = 0; + } else { + boundaryIndex++; + } + + if (boundaryIndex >= this.boundary.length) { + const { headers, disposition } = this.#parseHeaders(headerText); + const content = TypedArrayPrototypeSubarray( + this.body, + fileStart, + i - boundaryIndex - 1, + ); + // https://fetch.spec.whatwg.org/#ref-for-dom-body-formdata + const filename = MapPrototypeGet(disposition, "filename"); + const name = MapPrototypeGet(disposition, "name"); + + state = 5; + // Reset + boundaryIndex = 0; + headerText = ""; + + if (!name) { + continue; // Skip, unknown name } - if (boundaryIndex >= this.boundary.length) { - const { headers, disposition } = this.#parseHeaders(headerText); - const content = TypedArrayPrototypeSubarray( - this.body, - fileStart, - i - boundaryIndex - 1, - ); - // https://fetch.spec.whatwg.org/#ref-for-dom-body-formdata - const filename = MapPrototypeGet(disposition, "filename"); - const name = MapPrototypeGet(disposition, "name"); - - state = 5; - // Reset - boundaryIndex = 0; - headerText = ""; - - if (!name) { - continue; // Skip, unknown name - } - - if (filename) { - const blob = new Blob([content], { - type: headers.get("Content-Type") || "application/octet-stream", - }); - formData.append(name, blob, filename); - } else { - formData.append(name, core.decode(content)); - } + if (filename) { + const blob = new Blob([content], { + type: headers.get("Content-Type") || "application/octet-stream", + }); + formData.append(name, blob, filename); + } else { + formData.append(name, core.decode(content)); } - } else if (state === 5 && isNewLine) { - state = 1; } + } else if (state === 5 && isNewLine) { + state = 1; } - - return formData; } - } - - /** - * @param {Uint8Array} body - * @param {string | undefined} boundary - * @returns {FormData} - */ - function parseFormData(body, boundary) { - const parser = new MultipartParser(body, boundary); - return parser.parse(); - } - /** - * @param {FormDataEntry[]} entries - * @returns {FormData} - */ - function formDataFromEntries(entries) { - const fd = new FormData(); - fd[entryList] = entries; - return fd; + return formData; } - - webidl.converters["FormData"] = webidl - .createInterfaceConverter("FormData", FormDataPrototype); - - globalThis.__bootstrap.formData = { - FormData, - FormDataPrototype, - formDataToBlob, - parseFormData, - formDataFromEntries, - }; -})(globalThis); +} + +/** + * @param {Uint8Array} body + * @param {string | undefined} boundary + * @returns {FormData} + */ +function parseFormData(body, boundary) { + const parser = new MultipartParser(body, boundary); + return parser.parse(); +} + +/** + * @param {FormDataEntry[]} entries + * @returns {FormData} + */ +function formDataFromEntries(entries) { + const fd = new FormData(); + fd[entryList] = entries; + return fd; +} + +webidl.converters["FormData"] = webidl + .createInterfaceConverter("FormData", FormDataPrototype); + +export { + FormData, + formDataFromEntries, + FormDataPrototype, + formDataToBlob, + parseFormData, +}; diff --git a/ext/fetch/22_body.js b/ext/fetch/22_body.js index bea1abce2a9b11..9161a1216baf15 100644 --- a/ext/fetch/22_body.js +++ b/ext/fetch/22_body.js @@ -10,462 +10,462 @@ /// /// /// -"use strict"; -((window) => { - const core = window.Deno.core; - const webidl = globalThis.__bootstrap.webidl; - const { parseUrlEncoded } = globalThis.__bootstrap.url; - const { URLSearchParamsPrototype } = globalThis.__bootstrap.url; - const { - parseFormData, - formDataFromEntries, - formDataToBlob, - FormDataPrototype, - } = globalThis.__bootstrap.formData; - const mimesniff = globalThis.__bootstrap.mimesniff; - const { BlobPrototype } = globalThis.__bootstrap.file; - const { - isReadableStreamDisturbed, - errorReadableStream, - readableStreamClose, - readableStreamDisturb, - readableStreamCollectIntoUint8Array, - readableStreamThrowIfErrored, - createProxy, - ReadableStreamPrototype, - } = globalThis.__bootstrap.streams; - const { - ArrayBufferPrototype, - ArrayBufferIsView, - ArrayPrototypeMap, - JSONParse, - ObjectDefineProperties, - ObjectPrototypeIsPrototypeOf, - // TODO(lucacasonato): add SharedArrayBuffer to primordials - // SharedArrayBufferPrototype - TypedArrayPrototypeSlice, - TypeError, - Uint8Array, - Uint8ArrayPrototype, - } = window.__bootstrap.primordials; +const core = globalThis.Deno.core; +import * as webidl from "internal:deno_webidl/00_webidl.js"; +import { + parseUrlEncoded, + URLSearchParamsPrototype, +} from "internal:deno_url/00_url.js"; +import { + formDataFromEntries, + FormDataPrototype, + formDataToBlob, + parseFormData, +} from "internal:deno_fetch/21_formdata.js"; +import * as mimesniff from "internal:deno_web/01_mimesniff.js"; +import { BlobPrototype } from "internal:deno_web/09_file.js"; +import { + createProxy, + errorReadableStream, + isReadableStreamDisturbed, + readableStreamClose, + readableStreamCollectIntoUint8Array, + readableStreamDisturb, + ReadableStreamPrototype, + readableStreamThrowIfErrored, +} from "internal:deno_web/06_streams.js"; +const primordials = globalThis.__bootstrap.primordials; +const { + ArrayBufferPrototype, + ArrayBufferIsView, + ArrayPrototypeMap, + JSONParse, + ObjectDefineProperties, + ObjectPrototypeIsPrototypeOf, + // TODO(lucacasonato): add SharedArrayBuffer to primordials + // SharedArrayBufferPrototype + TypedArrayPrototypeSlice, + TypeError, + Uint8Array, + Uint8ArrayPrototype, +} = primordials; - /** - * @param {Uint8Array | string} chunk - * @returns {Uint8Array} - */ - function chunkToU8(chunk) { - return typeof chunk === "string" ? core.encode(chunk) : chunk; - } +/** + * @param {Uint8Array | string} chunk + * @returns {Uint8Array} + */ +function chunkToU8(chunk) { + return typeof chunk === "string" ? core.encode(chunk) : chunk; +} +/** + * @param {Uint8Array | string} chunk + * @returns {string} + */ +function chunkToString(chunk) { + return typeof chunk === "string" ? chunk : core.decode(chunk); +} + +class InnerBody { /** - * @param {Uint8Array | string} chunk - * @returns {string} + * @param {ReadableStream | { body: Uint8Array | string, consumed: boolean }} stream */ - function chunkToString(chunk) { - return typeof chunk === "string" ? chunk : core.decode(chunk); + constructor(stream) { + /** @type {ReadableStream | { body: Uint8Array | string, consumed: boolean }} */ + this.streamOrStatic = stream ?? + { body: new Uint8Array(), consumed: false }; + /** @type {null | Uint8Array | string | Blob | FormData} */ + this.source = null; + /** @type {null | number} */ + this.length = null; } - class InnerBody { - /** - * @param {ReadableStream | { body: Uint8Array | string, consumed: boolean }} stream - */ - constructor(stream) { - /** @type {ReadableStream | { body: Uint8Array | string, consumed: boolean }} */ - this.streamOrStatic = stream ?? - { body: new Uint8Array(), consumed: false }; - /** @type {null | Uint8Array | string | Blob | FormData} */ - this.source = null; - /** @type {null | number} */ - this.length = null; - } - - get stream() { - if ( - !ObjectPrototypeIsPrototypeOf( - ReadableStreamPrototype, - this.streamOrStatic, - ) - ) { - const { body, consumed } = this.streamOrStatic; - if (consumed) { - this.streamOrStatic = new ReadableStream(); - this.streamOrStatic.getReader(); - readableStreamDisturb(this.streamOrStatic); - readableStreamClose(this.streamOrStatic); - } else { - this.streamOrStatic = new ReadableStream({ - start(controller) { - controller.enqueue(chunkToU8(body)); - controller.close(); - }, - }); - } - } - return this.streamOrStatic; - } - - /** - * https://fetch.spec.whatwg.org/#body-unusable - * @returns {boolean} - */ - unusable() { - if ( - ObjectPrototypeIsPrototypeOf( - ReadableStreamPrototype, - this.streamOrStatic, - ) - ) { - return this.streamOrStatic.locked || - isReadableStreamDisturbed(this.streamOrStatic); + get stream() { + if ( + !ObjectPrototypeIsPrototypeOf( + ReadableStreamPrototype, + this.streamOrStatic, + ) + ) { + const { body, consumed } = this.streamOrStatic; + if (consumed) { + this.streamOrStatic = new ReadableStream(); + this.streamOrStatic.getReader(); + readableStreamDisturb(this.streamOrStatic); + readableStreamClose(this.streamOrStatic); + } else { + this.streamOrStatic = new ReadableStream({ + start(controller) { + controller.enqueue(chunkToU8(body)); + controller.close(); + }, + }); } - return this.streamOrStatic.consumed; } + return this.streamOrStatic; + } - /** - * @returns {boolean} - */ - consumed() { - if ( - ObjectPrototypeIsPrototypeOf( - ReadableStreamPrototype, - this.streamOrStatic, - ) - ) { - return isReadableStreamDisturbed(this.streamOrStatic); - } - return this.streamOrStatic.consumed; + /** + * https://fetch.spec.whatwg.org/#body-unusable + * @returns {boolean} + */ + unusable() { + if ( + ObjectPrototypeIsPrototypeOf( + ReadableStreamPrototype, + this.streamOrStatic, + ) + ) { + return this.streamOrStatic.locked || + isReadableStreamDisturbed(this.streamOrStatic); } + return this.streamOrStatic.consumed; + } - /** - * https://fetch.spec.whatwg.org/#concept-body-consume-body - * @returns {Promise} - */ - consume() { - if (this.unusable()) throw new TypeError("Body already consumed."); - if ( - ObjectPrototypeIsPrototypeOf( - ReadableStreamPrototype, - this.streamOrStatic, - ) - ) { - readableStreamThrowIfErrored(this.stream); - return readableStreamCollectIntoUint8Array(this.stream); - } else { - this.streamOrStatic.consumed = true; - return this.streamOrStatic.body; - } + /** + * @returns {boolean} + */ + consumed() { + if ( + ObjectPrototypeIsPrototypeOf( + ReadableStreamPrototype, + this.streamOrStatic, + ) + ) { + return isReadableStreamDisturbed(this.streamOrStatic); } + return this.streamOrStatic.consumed; + } - cancel(error) { - if ( - ObjectPrototypeIsPrototypeOf( - ReadableStreamPrototype, - this.streamOrStatic, - ) - ) { - this.streamOrStatic.cancel(error); - } else { - this.streamOrStatic.consumed = true; - } + /** + * https://fetch.spec.whatwg.org/#concept-body-consume-body + * @returns {Promise} + */ + consume() { + if (this.unusable()) throw new TypeError("Body already consumed."); + if ( + ObjectPrototypeIsPrototypeOf( + ReadableStreamPrototype, + this.streamOrStatic, + ) + ) { + readableStreamThrowIfErrored(this.stream); + return readableStreamCollectIntoUint8Array(this.stream); + } else { + this.streamOrStatic.consumed = true; + return this.streamOrStatic.body; } + } - error(error) { - if ( - ObjectPrototypeIsPrototypeOf( - ReadableStreamPrototype, - this.streamOrStatic, - ) - ) { - errorReadableStream(this.streamOrStatic, error); - } else { - this.streamOrStatic.consumed = true; - } + cancel(error) { + if ( + ObjectPrototypeIsPrototypeOf( + ReadableStreamPrototype, + this.streamOrStatic, + ) + ) { + this.streamOrStatic.cancel(error); + } else { + this.streamOrStatic.consumed = true; } + } - /** - * @returns {InnerBody} - */ - clone() { - const { 0: out1, 1: out2 } = this.stream.tee(); - this.streamOrStatic = out1; - const second = new InnerBody(out2); - second.source = core.deserialize(core.serialize(this.source)); - second.length = this.length; - return second; + error(error) { + if ( + ObjectPrototypeIsPrototypeOf( + ReadableStreamPrototype, + this.streamOrStatic, + ) + ) { + errorReadableStream(this.streamOrStatic, error); + } else { + this.streamOrStatic.consumed = true; } + } - /** - * @returns {InnerBody} - */ - createProxy() { - let proxyStreamOrStatic; - if ( - ObjectPrototypeIsPrototypeOf( - ReadableStreamPrototype, - this.streamOrStatic, - ) - ) { - proxyStreamOrStatic = createProxy(this.streamOrStatic); - } else { - proxyStreamOrStatic = { ...this.streamOrStatic }; - this.streamOrStatic.consumed = true; - } - const proxy = new InnerBody(proxyStreamOrStatic); - proxy.source = this.source; - proxy.length = this.length; - return proxy; - } + /** + * @returns {InnerBody} + */ + clone() { + const { 0: out1, 1: out2 } = this.stream.tee(); + this.streamOrStatic = out1; + const second = new InnerBody(out2); + second.source = core.deserialize(core.serialize(this.source)); + second.length = this.length; + return second; } /** - * @param {any} prototype - * @param {symbol} bodySymbol - * @param {symbol} mimeTypeSymbol - * @returns {void} + * @returns {InnerBody} */ - function mixinBody(prototype, bodySymbol, mimeTypeSymbol) { - async function consumeBody(object, type) { - webidl.assertBranded(object, prototype); + createProxy() { + let proxyStreamOrStatic; + if ( + ObjectPrototypeIsPrototypeOf( + ReadableStreamPrototype, + this.streamOrStatic, + ) + ) { + proxyStreamOrStatic = createProxy(this.streamOrStatic); + } else { + proxyStreamOrStatic = { ...this.streamOrStatic }; + this.streamOrStatic.consumed = true; + } + const proxy = new InnerBody(proxyStreamOrStatic); + proxy.source = this.source; + proxy.length = this.length; + return proxy; + } +} - const body = object[bodySymbol] !== null - ? await object[bodySymbol].consume() - : new Uint8Array(); +/** + * @param {any} prototype + * @param {symbol} bodySymbol + * @param {symbol} mimeTypeSymbol + * @returns {void} + */ +function mixinBody(prototype, bodySymbol, mimeTypeSymbol) { + async function consumeBody(object, type) { + webidl.assertBranded(object, prototype); - const mimeType = type === "Blob" || type === "FormData" - ? object[mimeTypeSymbol] - : null; - return packageData(body, type, mimeType); - } + const body = object[bodySymbol] !== null + ? await object[bodySymbol].consume() + : new Uint8Array(); - /** @type {PropertyDescriptorMap} */ - const mixin = { - body: { - /** - * @returns {ReadableStream | null} - */ - get() { - webidl.assertBranded(this, prototype); - if (this[bodySymbol] === null) { - return null; - } else { - return this[bodySymbol].stream; - } - }, - configurable: true, - enumerable: true, + const mimeType = type === "Blob" || type === "FormData" + ? object[mimeTypeSymbol] + : null; + return packageData(body, type, mimeType); + } + + /** @type {PropertyDescriptorMap} */ + const mixin = { + body: { + /** + * @returns {ReadableStream | null} + */ + get() { + webidl.assertBranded(this, prototype); + if (this[bodySymbol] === null) { + return null; + } else { + return this[bodySymbol].stream; + } }, - bodyUsed: { - /** - * @returns {boolean} - */ - get() { - webidl.assertBranded(this, prototype); - if (this[bodySymbol] !== null) { - return this[bodySymbol].consumed(); - } - return false; - }, - configurable: true, - enumerable: true, + configurable: true, + enumerable: true, + }, + bodyUsed: { + /** + * @returns {boolean} + */ + get() { + webidl.assertBranded(this, prototype); + if (this[bodySymbol] !== null) { + return this[bodySymbol].consumed(); + } + return false; }, - arrayBuffer: { - /** @returns {Promise} */ - value: function arrayBuffer() { - return consumeBody(this, "ArrayBuffer"); - }, - writable: true, - configurable: true, - enumerable: true, + configurable: true, + enumerable: true, + }, + arrayBuffer: { + /** @returns {Promise} */ + value: function arrayBuffer() { + return consumeBody(this, "ArrayBuffer"); }, - blob: { - /** @returns {Promise} */ - value: function blob() { - return consumeBody(this, "Blob"); - }, - writable: true, - configurable: true, - enumerable: true, + writable: true, + configurable: true, + enumerable: true, + }, + blob: { + /** @returns {Promise} */ + value: function blob() { + return consumeBody(this, "Blob"); }, - formData: { - /** @returns {Promise} */ - value: function formData() { - return consumeBody(this, "FormData"); - }, - writable: true, - configurable: true, - enumerable: true, + writable: true, + configurable: true, + enumerable: true, + }, + formData: { + /** @returns {Promise} */ + value: function formData() { + return consumeBody(this, "FormData"); }, - json: { - /** @returns {Promise} */ - value: function json() { - return consumeBody(this, "JSON"); - }, - writable: true, - configurable: true, - enumerable: true, + writable: true, + configurable: true, + enumerable: true, + }, + json: { + /** @returns {Promise} */ + value: function json() { + return consumeBody(this, "JSON"); }, - text: { - /** @returns {Promise} */ - value: function text() { - return consumeBody(this, "text"); - }, - writable: true, - configurable: true, - enumerable: true, + writable: true, + configurable: true, + enumerable: true, + }, + text: { + /** @returns {Promise} */ + value: function text() { + return consumeBody(this, "text"); }, - }; - return ObjectDefineProperties(prototype, mixin); - } + writable: true, + configurable: true, + enumerable: true, + }, + }; + return ObjectDefineProperties(prototype, mixin); +} - /** - * https://fetch.spec.whatwg.org/#concept-body-package-data - * @param {Uint8Array | string} bytes - * @param {"ArrayBuffer" | "Blob" | "FormData" | "JSON" | "text"} type - * @param {MimeType | null} [mimeType] - */ - function packageData(bytes, type, mimeType) { - switch (type) { - case "ArrayBuffer": - return chunkToU8(bytes).buffer; - case "Blob": - return new Blob([bytes], { - type: mimeType !== null ? mimesniff.serializeMimeType(mimeType) : "", - }); - case "FormData": { - if (mimeType !== null) { - const essence = mimesniff.essence(mimeType); - if (essence === "multipart/form-data") { - const boundary = mimeType.parameters.get("boundary"); - if (boundary === null) { - throw new TypeError( - "Missing boundary parameter in mime type of multipart formdata.", - ); - } - return parseFormData(chunkToU8(bytes), boundary); - } else if (essence === "application/x-www-form-urlencoded") { - // TODO(@AaronO): pass as-is with StringOrBuffer in op-layer - const entries = parseUrlEncoded(chunkToU8(bytes)); - return formDataFromEntries( - ArrayPrototypeMap( - entries, - (x) => ({ name: x[0], value: x[1] }), - ), +/** + * https://fetch.spec.whatwg.org/#concept-body-package-data + * @param {Uint8Array | string} bytes + * @param {"ArrayBuffer" | "Blob" | "FormData" | "JSON" | "text"} type + * @param {MimeType | null} [mimeType] + */ +function packageData(bytes, type, mimeType) { + switch (type) { + case "ArrayBuffer": + return chunkToU8(bytes).buffer; + case "Blob": + return new Blob([bytes], { + type: mimeType !== null ? mimesniff.serializeMimeType(mimeType) : "", + }); + case "FormData": { + if (mimeType !== null) { + const essence = mimesniff.essence(mimeType); + if (essence === "multipart/form-data") { + const boundary = mimeType.parameters.get("boundary"); + if (boundary === null) { + throw new TypeError( + "Missing boundary parameter in mime type of multipart formdata.", ); } - throw new TypeError("Body can not be decoded as form data"); + return parseFormData(chunkToU8(bytes), boundary); + } else if (essence === "application/x-www-form-urlencoded") { + // TODO(@AaronO): pass as-is with StringOrBuffer in op-layer + const entries = parseUrlEncoded(chunkToU8(bytes)); + return formDataFromEntries( + ArrayPrototypeMap( + entries, + (x) => ({ name: x[0], value: x[1] }), + ), + ); } - throw new TypeError("Missing content type"); + throw new TypeError("Body can not be decoded as form data"); } - case "JSON": - return JSONParse(chunkToString(bytes)); - case "text": - return chunkToString(bytes); + throw new TypeError("Missing content type"); } + case "JSON": + return JSONParse(chunkToString(bytes)); + case "text": + return chunkToString(bytes); } +} - /** - * @param {BodyInit} object - * @returns {{body: InnerBody, contentType: string | null}} - */ - function extractBody(object) { - /** @type {ReadableStream | { body: Uint8Array | string, consumed: boolean }} */ - let stream; - let source = null; - let length = null; - let contentType = null; - if (typeof object === "string") { - source = object; - contentType = "text/plain;charset=UTF-8"; - } else if (ObjectPrototypeIsPrototypeOf(BlobPrototype, object)) { - stream = object.stream(); - source = object; - length = object.size; - if (object.type.length !== 0) { - contentType = object.type; - } - } else if (ObjectPrototypeIsPrototypeOf(Uint8ArrayPrototype, object)) { - // Fast(er) path for common case of Uint8Array - const copy = TypedArrayPrototypeSlice(object, 0, object.byteLength); - source = copy; - } else if ( - ArrayBufferIsView(object) || - ObjectPrototypeIsPrototypeOf(ArrayBufferPrototype, object) - ) { - const u8 = ArrayBufferIsView(object) - ? new Uint8Array( - object.buffer, - object.byteOffset, - object.byteLength, - ) - : new Uint8Array(object); - const copy = TypedArrayPrototypeSlice(u8, 0, u8.byteLength); - source = copy; - } else if (ObjectPrototypeIsPrototypeOf(FormDataPrototype, object)) { - const res = formDataToBlob(object); - stream = res.stream(); - source = res; - length = res.size; - contentType = res.type; - } else if ( - ObjectPrototypeIsPrototypeOf(URLSearchParamsPrototype, object) - ) { - // TODO(@satyarohith): not sure what primordial here. - source = object.toString(); - contentType = "application/x-www-form-urlencoded;charset=UTF-8"; - } else if (ObjectPrototypeIsPrototypeOf(ReadableStreamPrototype, object)) { - stream = object; - if (object.locked || isReadableStreamDisturbed(object)) { - throw new TypeError("ReadableStream is locked or disturbed"); - } +/** + * @param {BodyInit} object + * @returns {{body: InnerBody, contentType: string | null}} + */ +function extractBody(object) { + /** @type {ReadableStream | { body: Uint8Array | string, consumed: boolean }} */ + let stream; + let source = null; + let length = null; + let contentType = null; + if (typeof object === "string") { + source = object; + contentType = "text/plain;charset=UTF-8"; + } else if (ObjectPrototypeIsPrototypeOf(BlobPrototype, object)) { + stream = object.stream(); + source = object; + length = object.size; + if (object.type.length !== 0) { + contentType = object.type; } - if (typeof source === "string") { - // WARNING: this deviates from spec (expects length to be set) - // https://fetch.spec.whatwg.org/#bodyinit > 7. - // no observable side-effect for users so far, but could change - stream = { body: source, consumed: false }; - length = null; // NOTE: string length != byte length - } else if (ObjectPrototypeIsPrototypeOf(Uint8ArrayPrototype, source)) { - stream = { body: source, consumed: false }; - length = source.byteLength; + } else if (ObjectPrototypeIsPrototypeOf(Uint8ArrayPrototype, object)) { + // Fast(er) path for common case of Uint8Array + const copy = TypedArrayPrototypeSlice(object, 0, object.byteLength); + source = copy; + } else if ( + ArrayBufferIsView(object) || + ObjectPrototypeIsPrototypeOf(ArrayBufferPrototype, object) + ) { + const u8 = ArrayBufferIsView(object) + ? new Uint8Array( + object.buffer, + object.byteOffset, + object.byteLength, + ) + : new Uint8Array(object); + const copy = TypedArrayPrototypeSlice(u8, 0, u8.byteLength); + source = copy; + } else if (ObjectPrototypeIsPrototypeOf(FormDataPrototype, object)) { + const res = formDataToBlob(object); + stream = res.stream(); + source = res; + length = res.size; + contentType = res.type; + } else if ( + ObjectPrototypeIsPrototypeOf(URLSearchParamsPrototype, object) + ) { + // TODO(@satyarohith): not sure what primordial here. + source = object.toString(); + contentType = "application/x-www-form-urlencoded;charset=UTF-8"; + } else if (ObjectPrototypeIsPrototypeOf(ReadableStreamPrototype, object)) { + stream = object; + if (object.locked || isReadableStreamDisturbed(object)) { + throw new TypeError("ReadableStream is locked or disturbed"); } - const body = new InnerBody(stream); - body.source = source; - body.length = length; - return { body, contentType }; } + if (typeof source === "string") { + // WARNING: this deviates from spec (expects length to be set) + // https://fetch.spec.whatwg.org/#bodyinit > 7. + // no observable side-effect for users so far, but could change + stream = { body: source, consumed: false }; + length = null; // NOTE: string length != byte length + } else if (ObjectPrototypeIsPrototypeOf(Uint8ArrayPrototype, source)) { + stream = { body: source, consumed: false }; + length = source.byteLength; + } + const body = new InnerBody(stream); + body.source = source; + body.length = length; + return { body, contentType }; +} - webidl.converters["BodyInit_DOMString"] = (V, opts) => { - // Union for (ReadableStream or Blob or ArrayBufferView or ArrayBuffer or FormData or URLSearchParams or USVString) - if (ObjectPrototypeIsPrototypeOf(ReadableStreamPrototype, V)) { - return webidl.converters["ReadableStream"](V, opts); - } else if (ObjectPrototypeIsPrototypeOf(BlobPrototype, V)) { - return webidl.converters["Blob"](V, opts); - } else if (ObjectPrototypeIsPrototypeOf(FormDataPrototype, V)) { - return webidl.converters["FormData"](V, opts); - } else if (ObjectPrototypeIsPrototypeOf(URLSearchParamsPrototype, V)) { - return webidl.converters["URLSearchParams"](V, opts); +webidl.converters["BodyInit_DOMString"] = (V, opts) => { + // Union for (ReadableStream or Blob or ArrayBufferView or ArrayBuffer or FormData or URLSearchParams or USVString) + if (ObjectPrototypeIsPrototypeOf(ReadableStreamPrototype, V)) { + return webidl.converters["ReadableStream"](V, opts); + } else if (ObjectPrototypeIsPrototypeOf(BlobPrototype, V)) { + return webidl.converters["Blob"](V, opts); + } else if (ObjectPrototypeIsPrototypeOf(FormDataPrototype, V)) { + return webidl.converters["FormData"](V, opts); + } else if (ObjectPrototypeIsPrototypeOf(URLSearchParamsPrototype, V)) { + return webidl.converters["URLSearchParams"](V, opts); + } + if (typeof V === "object") { + if ( + ObjectPrototypeIsPrototypeOf(ArrayBufferPrototype, V) || + // deno-lint-ignore prefer-primordials + ObjectPrototypeIsPrototypeOf(SharedArrayBuffer.prototype, V) + ) { + return webidl.converters["ArrayBuffer"](V, opts); } - if (typeof V === "object") { - if ( - ObjectPrototypeIsPrototypeOf(ArrayBufferPrototype, V) || - // deno-lint-ignore prefer-primordials - ObjectPrototypeIsPrototypeOf(SharedArrayBuffer.prototype, V) - ) { - return webidl.converters["ArrayBuffer"](V, opts); - } - if (ArrayBufferIsView(V)) { - return webidl.converters["ArrayBufferView"](V, opts); - } + if (ArrayBufferIsView(V)) { + return webidl.converters["ArrayBufferView"](V, opts); } - // BodyInit conversion is passed to extractBody(), which calls core.encode(). - // core.encode() will UTF-8 encode strings with replacement, being equivalent to the USV normalization. - // Therefore we can convert to DOMString instead of USVString and avoid a costly redundant conversion. - return webidl.converters["DOMString"](V, opts); - }; - webidl.converters["BodyInit_DOMString?"] = webidl.createNullableConverter( - webidl.converters["BodyInit_DOMString"], - ); + } + // BodyInit conversion is passed to extractBody(), which calls core.encode(). + // core.encode() will UTF-8 encode strings with replacement, being equivalent to the USV normalization. + // Therefore we can convert to DOMString instead of USVString and avoid a costly redundant conversion. + return webidl.converters["DOMString"](V, opts); +}; +webidl.converters["BodyInit_DOMString?"] = webidl.createNullableConverter( + webidl.converters["BodyInit_DOMString"], +); - window.__bootstrap.fetchBody = { mixinBody, InnerBody, extractBody }; -})(globalThis); +export { extractBody, InnerBody, mixinBody }; diff --git a/ext/fetch/22_http_client.js b/ext/fetch/22_http_client.js index 7b9f5c446dc246..9d37f1b7fb3bba 100644 --- a/ext/fetch/22_http_client.js +++ b/ext/fetch/22_http_client.js @@ -9,40 +9,34 @@ /// /// /// -"use strict"; -((window) => { - const core = window.Deno.core; - const ops = core.ops; +const core = globalThis.Deno.core; +const ops = core.ops; +/** + * @param {Deno.CreateHttpClientOptions} options + * @returns {HttpClient} + */ +function createHttpClient(options) { + options.caCerts ??= []; + return new HttpClient( + ops.op_fetch_custom_client( + options, + ), + ); +} + +class HttpClient { /** - * @param {Deno.CreateHttpClientOptions} options - * @returns {HttpClient} + * @param {number} rid */ - function createHttpClient(options) { - options.caCerts ??= []; - return new HttpClient( - ops.op_fetch_custom_client( - options, - ), - ); + constructor(rid) { + this.rid = rid; } - - class HttpClient { - /** - * @param {number} rid - */ - constructor(rid) { - this.rid = rid; - } - close() { - core.close(this.rid); - } + close() { + core.close(this.rid); } - const HttpClientPrototype = HttpClient.prototype; +} +const HttpClientPrototype = HttpClient.prototype; - window.__bootstrap.fetch ??= {}; - window.__bootstrap.fetch.createHttpClient = createHttpClient; - window.__bootstrap.fetch.HttpClient = HttpClient; - window.__bootstrap.fetch.HttpClientPrototype = HttpClientPrototype; -})(globalThis); +export { createHttpClient, HttpClient, HttpClientPrototype }; diff --git a/ext/fetch/23_request.js b/ext/fetch/23_request.js index e266a7e44a8f32..15e2efb194e7f5 100644 --- a/ext/fetch/23_request.js +++ b/ext/fetch/23_request.js @@ -8,657 +8,664 @@ /// /// /// -"use strict"; - -((window) => { - const webidl = window.__bootstrap.webidl; - const consoleInternal = window.__bootstrap.console; - const { HTTP_TOKEN_CODE_POINT_RE, byteUpperCase } = window.__bootstrap.infra; - const { URL } = window.__bootstrap.url; - const { guardFromHeaders } = window.__bootstrap.headers; - const { mixinBody, extractBody, InnerBody } = window.__bootstrap.fetchBody; - const { getLocationHref } = window.__bootstrap.location; - const { extractMimeType } = window.__bootstrap.mimesniff; - const { blobFromObjectUrl } = window.__bootstrap.file; - const { - headersFromHeaderList, - headerListFromHeaders, - fillHeaders, - getDecodeSplitHeader, - } = window.__bootstrap.headers; - const { HttpClientPrototype } = window.__bootstrap.fetch; - const abortSignal = window.__bootstrap.abortSignal; - const { - ArrayPrototypeMap, - ArrayPrototypeSlice, - ArrayPrototypeSplice, - ObjectKeys, - ObjectPrototypeIsPrototypeOf, - RegExpPrototypeTest, - Symbol, - SymbolFor, - TypeError, - } = window.__bootstrap.primordials; - - const _request = Symbol("request"); - const _headers = Symbol("headers"); - const _getHeaders = Symbol("get headers"); - const _headersCache = Symbol("headers cache"); - const _signal = Symbol("signal"); - const _mimeType = Symbol("mime type"); - const _body = Symbol("body"); - const _flash = Symbol("flash"); - const _url = Symbol("url"); - const _method = Symbol("method"); - /** - * @param {(() => string)[]} urlList - * @param {string[]} urlListProcessed - */ - function processUrlList(urlList, urlListProcessed) { - for (let i = 0; i < urlList.length; i++) { - if (urlListProcessed[i] === undefined) { - urlListProcessed[i] = urlList[i](); - } +import * as webidl from "internal:deno_webidl/00_webidl.js"; +import { createFilteredInspectProxy } from "internal:deno_console/02_console.js"; +import { + byteUpperCase, + HTTP_TOKEN_CODE_POINT_RE, +} from "internal:deno_web/00_infra.js"; +import { URL } from "internal:deno_url/00_url.js"; +import { + extractBody, + InnerBody, + mixinBody, +} from "internal:deno_fetch/22_body.js"; +import { getLocationHref } from "internal:deno_web/12_location.js"; +import { extractMimeType } from "internal:deno_web/01_mimesniff.js"; +import { blobFromObjectUrl } from "internal:deno_web/09_file.js"; +import { + fillHeaders, + getDecodeSplitHeader, + guardFromHeaders, + headerListFromHeaders, + headersFromHeaderList, +} from "internal:deno_fetch/20_headers.js"; +import { HttpClientPrototype } from "internal:deno_fetch/22_http_client.js"; +import * as abortSignal from "internal:deno_web/03_abort_signal.js"; +const primordials = globalThis.__bootstrap.primordials; +const { + ArrayPrototypeMap, + ArrayPrototypeSlice, + ArrayPrototypeSplice, + ObjectKeys, + ObjectPrototypeIsPrototypeOf, + RegExpPrototypeTest, + Symbol, + SymbolFor, + TypeError, +} = primordials; + +const _request = Symbol("request"); +const _headers = Symbol("headers"); +const _getHeaders = Symbol("get headers"); +const _headersCache = Symbol("headers cache"); +const _signal = Symbol("signal"); +const _mimeType = Symbol("mime type"); +const _body = Symbol("body"); +const _flash = Symbol("flash"); +const _url = Symbol("url"); +const _method = Symbol("method"); + +/** + * @param {(() => string)[]} urlList + * @param {string[]} urlListProcessed + */ +function processUrlList(urlList, urlListProcessed) { + for (let i = 0; i < urlList.length; i++) { + if (urlListProcessed[i] === undefined) { + urlListProcessed[i] = urlList[i](); } - return urlListProcessed; } + return urlListProcessed; +} + +/** + * @typedef InnerRequest + * @property {() => string} method + * @property {() => string} url + * @property {() => string} currentUrl + * @property {() => [string, string][]} headerList + * @property {null | typeof __window.bootstrap.fetchBody.InnerBody} body + * @property {"follow" | "error" | "manual"} redirectMode + * @property {number} redirectCount + * @property {(() => string)[]} urlList + * @property {string[]} urlListProcessed + * @property {number | null} clientRid NOTE: non standard extension for `Deno.HttpClient`. + * @property {Blob | null} blobUrlEntry + */ + +/** + * @param {() => string} method + * @param {string | () => string} url + * @param {() => [string, string][]} headerList + * @param {typeof __window.bootstrap.fetchBody.InnerBody} body + * @param {boolean} maybeBlob + * @returns {InnerRequest} + */ +function newInnerRequest(method, url, headerList, body, maybeBlob) { + let blobUrlEntry = null; + if (maybeBlob && typeof url === "string" && url.startsWith("blob:")) { + blobUrlEntry = blobFromObjectUrl(url); + } + return { + methodInner: null, + get method() { + if (this.methodInner === null) { + try { + this.methodInner = method(); + } catch { + throw new TypeError("cannot read method: request closed"); + } + } + return this.methodInner; + }, + set method(value) { + this.methodInner = value; + }, + headerListInner: null, + get headerList() { + if (this.headerListInner === null) { + try { + this.headerListInner = headerList(); + } catch { + throw new TypeError("cannot read headers: request closed"); + } + } + return this.headerListInner; + }, + set headerList(value) { + this.headerListInner = value; + }, + body, + redirectMode: "follow", + redirectCount: 0, + urlList: [typeof url === "string" ? () => url : url], + urlListProcessed: [], + clientRid: null, + blobUrlEntry, + url() { + if (this.urlListProcessed[0] === undefined) { + try { + this.urlListProcessed[0] = this.urlList[0](); + } catch { + throw new TypeError("cannot read url: request closed"); + } + } + return this.urlListProcessed[0]; + }, + currentUrl() { + const currentIndex = this.urlList.length - 1; + if (this.urlListProcessed[currentIndex] === undefined) { + try { + this.urlListProcessed[currentIndex] = this.urlList[currentIndex](); + } catch { + throw new TypeError("cannot read url: request closed"); + } + } + return this.urlListProcessed[currentIndex]; + }, + }; +} + +/** + * https://fetch.spec.whatwg.org/#concept-request-clone + * @param {InnerRequest} request + * @param {boolean} skipBody + * @param {boolean} flash + * @returns {InnerRequest} + */ +function cloneInnerRequest(request, skipBody = false, flash = false) { + const headerList = ArrayPrototypeMap( + request.headerList, + (x) => [x[0], x[1]], + ); - /** - * @typedef InnerRequest - * @property {() => string} method - * @property {() => string} url - * @property {() => string} currentUrl - * @property {() => [string, string][]} headerList - * @property {null | typeof __window.bootstrap.fetchBody.InnerBody} body - * @property {"follow" | "error" | "manual"} redirectMode - * @property {number} redirectCount - * @property {(() => string)[]} urlList - * @property {string[]} urlListProcessed - * @property {number | null} clientRid NOTE: non standard extension for `Deno.HttpClient`. - * @property {Blob | null} blobUrlEntry - */ + let body = null; + if (request.body !== null && !skipBody) { + body = request.body.clone(); + } - /** - * @param {() => string} method - * @param {string | () => string} url - * @param {() => [string, string][]} headerList - * @param {typeof __window.bootstrap.fetchBody.InnerBody} body - * @param {boolean} maybeBlob - * @returns {InnerRequest} - */ - function newInnerRequest(method, url, headerList, body, maybeBlob) { - let blobUrlEntry = null; - if (maybeBlob && typeof url === "string" && url.startsWith("blob:")) { - blobUrlEntry = blobFromObjectUrl(url); - } + if (flash) { return { - methodInner: null, - get method() { - if (this.methodInner === null) { - try { - this.methodInner = method(); - } catch { - throw new TypeError("cannot read method: request closed"); - } - } - return this.methodInner; - }, - set method(value) { - this.methodInner = value; - }, - headerListInner: null, - get headerList() { - if (this.headerListInner === null) { - try { - this.headerListInner = headerList(); - } catch { - throw new TypeError("cannot read headers: request closed"); - } - } - return this.headerListInner; - }, - set headerList(value) { - this.headerListInner = value; - }, body, + methodCb: request.methodCb, + urlCb: request.urlCb, + headerList: request.headerList, + streamRid: request.streamRid, + serverId: request.serverId, redirectMode: "follow", redirectCount: 0, - urlList: [typeof url === "string" ? () => url : url], - urlListProcessed: [], - clientRid: null, - blobUrlEntry, - url() { - if (this.urlListProcessed[0] === undefined) { - try { - this.urlListProcessed[0] = this.urlList[0](); - } catch { - throw new TypeError("cannot read url: request closed"); - } - } - return this.urlListProcessed[0]; - }, - currentUrl() { - const currentIndex = this.urlList.length - 1; - if (this.urlListProcessed[currentIndex] === undefined) { - try { - this.urlListProcessed[currentIndex] = this.urlList[currentIndex](); - } catch { - throw new TypeError("cannot read url: request closed"); - } - } - return this.urlListProcessed[currentIndex]; - }, }; } - /** - * https://fetch.spec.whatwg.org/#concept-request-clone - * @param {InnerRequest} request - * @param {boolean} skipBody - * @param {boolean} flash - * @returns {InnerRequest} - */ - function cloneInnerRequest(request, skipBody = false, flash = false) { - const headerList = ArrayPrototypeMap( - request.headerList, - (x) => [x[0], x[1]], - ); - - let body = null; - if (request.body !== null && !skipBody) { - body = request.body.clone(); - } - - if (flash) { - return { - body, - methodCb: request.methodCb, - urlCb: request.urlCb, - headerList: request.headerList, - streamRid: request.streamRid, - serverId: request.serverId, - redirectMode: "follow", - redirectCount: 0, - }; - } - - return { - method: request.method, - headerList, - body, - redirectMode: request.redirectMode, - redirectCount: request.redirectCount, - urlList: request.urlList, - urlListProcessed: request.urlListProcessed, - clientRid: request.clientRid, - blobUrlEntry: request.blobUrlEntry, - url() { - if (this.urlListProcessed[0] === undefined) { - try { - this.urlListProcessed[0] = this.urlList[0](); - } catch { - throw new TypeError("cannot read url: request closed"); - } + return { + method: request.method, + headerList, + body, + redirectMode: request.redirectMode, + redirectCount: request.redirectCount, + urlList: request.urlList, + urlListProcessed: request.urlListProcessed, + clientRid: request.clientRid, + blobUrlEntry: request.blobUrlEntry, + url() { + if (this.urlListProcessed[0] === undefined) { + try { + this.urlListProcessed[0] = this.urlList[0](); + } catch { + throw new TypeError("cannot read url: request closed"); } - return this.urlListProcessed[0]; - }, - currentUrl() { - const currentIndex = this.urlList.length - 1; - if (this.urlListProcessed[currentIndex] === undefined) { - try { - this.urlListProcessed[currentIndex] = this.urlList[currentIndex](); - } catch { - throw new TypeError("cannot read url: request closed"); - } + } + return this.urlListProcessed[0]; + }, + currentUrl() { + const currentIndex = this.urlList.length - 1; + if (this.urlListProcessed[currentIndex] === undefined) { + try { + this.urlListProcessed[currentIndex] = this.urlList[currentIndex](); + } catch { + throw new TypeError("cannot read url: request closed"); } - return this.urlListProcessed[currentIndex]; - }, - }; + } + return this.urlListProcessed[currentIndex]; + }, + }; +} + +/** + * @param {string} m + * @returns {boolean} + */ +function isKnownMethod(m) { + return ( + m === "DELETE" || + m === "GET" || + m === "HEAD" || + m === "OPTIONS" || + m === "POST" || + m === "PUT" + ); +} +/** + * @param {string} m + * @returns {string} + */ +function validateAndNormalizeMethod(m) { + // Fast path for well-known methods + if (isKnownMethod(m)) { + return m; } - /** - * @param {string} m - * @returns {boolean} - */ - function isKnownMethod(m) { - return ( - m === "DELETE" || - m === "GET" || - m === "HEAD" || - m === "OPTIONS" || - m === "POST" || - m === "PUT" - ); + // Regular path + if (!RegExpPrototypeTest(HTTP_TOKEN_CODE_POINT_RE, m)) { + throw new TypeError("Method is not valid."); } - /** - * @param {string} m - * @returns {string} - */ - function validateAndNormalizeMethod(m) { - // Fast path for well-known methods - if (isKnownMethod(m)) { - return m; + const upperCase = byteUpperCase(m); + if ( + upperCase === "CONNECT" || upperCase === "TRACE" || upperCase === "TRACK" + ) { + throw new TypeError("Method is forbidden."); + } + return upperCase; +} + +class Request { + /** @type {InnerRequest} */ + [_request]; + /** @type {Headers} */ + [_headersCache]; + [_getHeaders]; + + /** @type {Headers} */ + get [_headers]() { + if (this[_headersCache] === undefined) { + this[_headersCache] = this[_getHeaders](); } + return this[_headersCache]; + } - // Regular path - if (!RegExpPrototypeTest(HTTP_TOKEN_CODE_POINT_RE, m)) { - throw new TypeError("Method is not valid."); - } - const upperCase = byteUpperCase(m); - if ( - upperCase === "CONNECT" || upperCase === "TRACE" || upperCase === "TRACK" - ) { - throw new TypeError("Method is forbidden."); + set [_headers](value) { + this[_headersCache] = value; + } + + /** @type {AbortSignal} */ + [_signal]; + get [_mimeType]() { + const values = getDecodeSplitHeader( + headerListFromHeaders(this[_headers]), + "Content-Type", + ); + return extractMimeType(values); + } + get [_body]() { + if (this[_flash]) { + return this[_flash].body; + } else { + return this[_request].body; } - return upperCase; } - class Request { + /** + * https://fetch.spec.whatwg.org/#dom-request + * @param {RequestInfo} input + * @param {RequestInit} init + */ + constructor(input, init = {}) { + const prefix = "Failed to construct 'Request'"; + webidl.requiredArguments(arguments.length, 1, { prefix }); + input = webidl.converters["RequestInfo_DOMString"](input, { + prefix, + context: "Argument 1", + }); + init = webidl.converters["RequestInit"](init, { + prefix, + context: "Argument 2", + }); + + this[webidl.brand] = webidl.brand; + /** @type {InnerRequest} */ - [_request]; - /** @type {Headers} */ - [_headersCache]; - [_getHeaders]; - - /** @type {Headers} */ - get [_headers]() { - if (this[_headersCache] === undefined) { - this[_headersCache] = this[_getHeaders](); + let request; + const baseURL = getLocationHref(); + + // 4. + let signal = null; + + // 5. + if (typeof input === "string") { + const parsedURL = new URL(input, baseURL); + request = newInnerRequest( + () => "GET", + parsedURL.href, + () => [], + null, + true, + ); + } else { // 6. + if (!ObjectPrototypeIsPrototypeOf(RequestPrototype, input)) { + throw new TypeError("Unreachable"); } - return this[_headersCache]; + const originalReq = input[_request]; + // fold in of step 12 from below + request = cloneInnerRequest(originalReq, true); + request.redirectCount = 0; // reset to 0 - cloneInnerRequest copies the value + signal = input[_signal]; } - set [_headers](value) { - this[_headersCache] = value; + // 12. is folded into the else statement of step 6 above. + + // 22. + if (init.redirect !== undefined) { + request.redirectMode = init.redirect; } - /** @type {AbortSignal} */ - [_signal]; - get [_mimeType]() { - const values = getDecodeSplitHeader( - headerListFromHeaders(this[_headers]), - "Content-Type", - ); - return extractMimeType(values); + // 25. + if (init.method !== undefined) { + let method = init.method; + method = validateAndNormalizeMethod(method); + request.method = method; } - get [_body]() { - if (this[_flash]) { - return this[_flash].body; - } else { - return this[_request].body; - } + + // 26. + if (init.signal !== undefined) { + signal = init.signal; } - /** - * https://fetch.spec.whatwg.org/#dom-request - * @param {RequestInfo} input - * @param {RequestInit} init - */ - constructor(input, init = {}) { - const prefix = "Failed to construct 'Request'"; - webidl.requiredArguments(arguments.length, 1, { prefix }); - input = webidl.converters["RequestInfo_DOMString"](input, { - prefix, - context: "Argument 1", - }); - init = webidl.converters["RequestInit"](init, { - prefix, - context: "Argument 2", - }); - - this[webidl.brand] = webidl.brand; - - /** @type {InnerRequest} */ - let request; - const baseURL = getLocationHref(); - - // 4. - let signal = null; - - // 5. - if (typeof input === "string") { - const parsedURL = new URL(input, baseURL); - request = newInnerRequest( - () => "GET", - parsedURL.href, - () => [], - null, - true, + // NOTE: non standard extension. This handles Deno.HttpClient parameter + if (init.client !== undefined) { + if ( + init.client !== null && + !ObjectPrototypeIsPrototypeOf(HttpClientPrototype, init.client) + ) { + throw webidl.makeException( + TypeError, + "`client` must be a Deno.HttpClient", + { prefix, context: "Argument 2" }, ); - } else { // 6. - if (!ObjectPrototypeIsPrototypeOf(RequestPrototype, input)) { - throw new TypeError("Unreachable"); - } - const originalReq = input[_request]; - // fold in of step 12 from below - request = cloneInnerRequest(originalReq, true); - request.redirectCount = 0; // reset to 0 - cloneInnerRequest copies the value - signal = input[_signal]; } + request.clientRid = init.client?.rid ?? null; + } - // 12. is folded into the else statement of step 6 above. + // 27. + this[_request] = request; - // 22. - if (init.redirect !== undefined) { - request.redirectMode = init.redirect; - } + // 28. + this[_signal] = abortSignal.newSignal(); - // 25. - if (init.method !== undefined) { - let method = init.method; - method = validateAndNormalizeMethod(method); - request.method = method; - } + // 29. + if (signal !== null) { + abortSignal.follow(this[_signal], signal); + } - // 26. - if (init.signal !== undefined) { - signal = init.signal; - } + // 30. + this[_headers] = headersFromHeaderList(request.headerList, "request"); - // NOTE: non standard extension. This handles Deno.HttpClient parameter - if (init.client !== undefined) { - if ( - init.client !== null && - !ObjectPrototypeIsPrototypeOf(HttpClientPrototype, init.client) - ) { - throw webidl.makeException( - TypeError, - "`client` must be a Deno.HttpClient", - { prefix, context: "Argument 2" }, - ); - } - request.clientRid = init.client?.rid ?? null; + // 32. + if (ObjectKeys(init).length > 0) { + let headers = ArrayPrototypeSlice( + headerListFromHeaders(this[_headers]), + 0, + headerListFromHeaders(this[_headers]).length, + ); + if (init.headers !== undefined) { + headers = init.headers; } + ArrayPrototypeSplice( + headerListFromHeaders(this[_headers]), + 0, + headerListFromHeaders(this[_headers]).length, + ); + fillHeaders(this[_headers], headers); + } - // 27. - this[_request] = request; - - // 28. - this[_signal] = abortSignal.newSignal(); - - // 29. - if (signal !== null) { - abortSignal.follow(this[_signal], signal); - } + // 33. + let inputBody = null; + if (ObjectPrototypeIsPrototypeOf(RequestPrototype, input)) { + inputBody = input[_body]; + } - // 30. - this[_headers] = headersFromHeaderList(request.headerList, "request"); + // 34. + if ( + (request.method === "GET" || request.method === "HEAD") && + ((init.body !== undefined && init.body !== null) || + inputBody !== null) + ) { + throw new TypeError("Request with GET/HEAD method cannot have body."); + } - // 32. - if (ObjectKeys(init).length > 0) { - let headers = ArrayPrototypeSlice( - headerListFromHeaders(this[_headers]), - 0, - headerListFromHeaders(this[_headers]).length, - ); - if (init.headers !== undefined) { - headers = init.headers; - } - ArrayPrototypeSplice( - headerListFromHeaders(this[_headers]), - 0, - headerListFromHeaders(this[_headers]).length, - ); - fillHeaders(this[_headers], headers); - } + // 35. + let initBody = null; - // 33. - let inputBody = null; - if (ObjectPrototypeIsPrototypeOf(RequestPrototype, input)) { - inputBody = input[_body]; + // 36. + if (init.body !== undefined && init.body !== null) { + const res = extractBody(init.body); + initBody = res.body; + if (res.contentType !== null && !this[_headers].has("content-type")) { + this[_headers].append("Content-Type", res.contentType); } + } - // 34. - if ( - (request.method === "GET" || request.method === "HEAD") && - ((init.body !== undefined && init.body !== null) || - inputBody !== null) - ) { - throw new TypeError("Request with GET/HEAD method cannot have body."); - } + // 37. + const inputOrInitBody = initBody ?? inputBody; - // 35. - let initBody = null; + // 39. + let finalBody = inputOrInitBody; - // 36. - if (init.body !== undefined && init.body !== null) { - const res = extractBody(init.body); - initBody = res.body; - if (res.contentType !== null && !this[_headers].has("content-type")) { - this[_headers].append("Content-Type", res.contentType); - } + // 40. + if (initBody === null && inputBody !== null) { + if (input[_body] && input[_body].unusable()) { + throw new TypeError("Input request's body is unusable."); } + finalBody = inputBody.createProxy(); + } - // 37. - const inputOrInitBody = initBody ?? inputBody; - - // 39. - let finalBody = inputOrInitBody; - - // 40. - if (initBody === null && inputBody !== null) { - if (input[_body] && input[_body].unusable()) { - throw new TypeError("Input request's body is unusable."); - } - finalBody = inputBody.createProxy(); - } + // 41. + request.body = finalBody; + } - // 41. - request.body = finalBody; + get method() { + webidl.assertBranded(this, RequestPrototype); + if (this[_method]) { + return this[_method]; } - - get method() { - webidl.assertBranded(this, RequestPrototype); - if (this[_method]) { - return this[_method]; - } - if (this[_flash]) { - this[_method] = this[_flash].methodCb(); - return this[_method]; - } else { - this[_method] = this[_request].method; - return this[_method]; - } + if (this[_flash]) { + this[_method] = this[_flash].methodCb(); + return this[_method]; + } else { + this[_method] = this[_request].method; + return this[_method]; } + } - get url() { - webidl.assertBranded(this, RequestPrototype); - if (this[_url]) { - return this[_url]; - } - - if (this[_flash]) { - this[_url] = this[_flash].urlCb(); - return this[_url]; - } else { - this[_url] = this[_request].url(); - return this[_url]; - } + get url() { + webidl.assertBranded(this, RequestPrototype); + if (this[_url]) { + return this[_url]; } - get headers() { - webidl.assertBranded(this, RequestPrototype); - return this[_headers]; + if (this[_flash]) { + this[_url] = this[_flash].urlCb(); + return this[_url]; + } else { + this[_url] = this[_request].url(); + return this[_url]; } + } - get redirect() { - webidl.assertBranded(this, RequestPrototype); - if (this[_flash]) { - return this[_flash].redirectMode; - } - return this[_request].redirectMode; - } + get headers() { + webidl.assertBranded(this, RequestPrototype); + return this[_headers]; + } - get signal() { - webidl.assertBranded(this, RequestPrototype); - return this[_signal]; + get redirect() { + webidl.assertBranded(this, RequestPrototype); + if (this[_flash]) { + return this[_flash].redirectMode; } + return this[_request].redirectMode; + } - clone() { - webidl.assertBranded(this, RequestPrototype); - if (this[_body] && this[_body].unusable()) { - throw new TypeError("Body is unusable."); - } - let newReq; - if (this[_flash]) { - newReq = cloneInnerRequest(this[_flash], false, true); - } else { - newReq = cloneInnerRequest(this[_request]); - } - const newSignal = abortSignal.newSignal(); + get signal() { + webidl.assertBranded(this, RequestPrototype); + return this[_signal]; + } - if (this[_signal]) { - abortSignal.follow(newSignal, this[_signal]); - } + clone() { + webidl.assertBranded(this, RequestPrototype); + if (this[_body] && this[_body].unusable()) { + throw new TypeError("Body is unusable."); + } + let newReq; + if (this[_flash]) { + newReq = cloneInnerRequest(this[_flash], false, true); + } else { + newReq = cloneInnerRequest(this[_request]); + } + const newSignal = abortSignal.newSignal(); - if (this[_flash]) { - return fromInnerRequest( - newReq, - newSignal, - guardFromHeaders(this[_headers]), - true, - ); - } + if (this[_signal]) { + abortSignal.follow(newSignal, this[_signal]); + } + if (this[_flash]) { return fromInnerRequest( newReq, newSignal, guardFromHeaders(this[_headers]), - false, + true, ); } - [SymbolFor("Deno.customInspect")](inspect) { - return inspect(consoleInternal.createFilteredInspectProxy({ - object: this, - evaluate: ObjectPrototypeIsPrototypeOf(RequestPrototype, this), - keys: [ - "bodyUsed", - "headers", - "method", - "redirect", - "url", - ], - })); - } + return fromInnerRequest( + newReq, + newSignal, + guardFromHeaders(this[_headers]), + false, + ); } - webidl.configurePrototype(Request); - const RequestPrototype = Request.prototype; - mixinBody(RequestPrototype, _body, _mimeType); - - webidl.converters["Request"] = webidl.createInterfaceConverter( - "Request", - RequestPrototype, - ); - webidl.converters["RequestInfo_DOMString"] = (V, opts) => { - // Union for (Request or USVString) - if (typeof V == "object") { - if (ObjectPrototypeIsPrototypeOf(RequestPrototype, V)) { - return webidl.converters["Request"](V, opts); - } - } - // Passed to new URL(...) which implicitly converts DOMString -> USVString - return webidl.converters["DOMString"](V, opts); - }; - webidl.converters["RequestRedirect"] = webidl.createEnumConverter( - "RequestRedirect", - [ - "follow", - "error", - "manual", - ], - ); - webidl.converters["RequestInit"] = webidl.createDictionaryConverter( - "RequestInit", - [ - { key: "method", converter: webidl.converters["ByteString"] }, - { key: "headers", converter: webidl.converters["HeadersInit"] }, - { - key: "body", - converter: webidl.createNullableConverter( - webidl.converters["BodyInit_DOMString"], - ), - }, - { key: "redirect", converter: webidl.converters["RequestRedirect"] }, - { - key: "signal", - converter: webidl.createNullableConverter( - webidl.converters["AbortSignal"], - ), - }, - { key: "client", converter: webidl.converters.any }, - ], - ); - - /** - * @param {Request} request - * @returns {InnerRequest} - */ - function toInnerRequest(request) { - return request[_request]; + [SymbolFor("Deno.customInspect")](inspect) { + return inspect(createFilteredInspectProxy({ + object: this, + evaluate: ObjectPrototypeIsPrototypeOf(RequestPrototype, this), + keys: [ + "bodyUsed", + "headers", + "method", + "redirect", + "url", + ], + })); } - - /** - * @param {InnerRequest} inner - * @param {AbortSignal} signal - * @param {"request" | "immutable" | "request-no-cors" | "response" | "none"} guard - * @param {boolean} flash - * @returns {Request} - */ - function fromInnerRequest(inner, signal, guard, flash) { - const request = webidl.createBranded(Request); - if (flash) { - request[_flash] = inner; - } else { - request[_request] = inner; +} + +webidl.configurePrototype(Request); +const RequestPrototype = Request.prototype; +mixinBody(RequestPrototype, _body, _mimeType); + +webidl.converters["Request"] = webidl.createInterfaceConverter( + "Request", + RequestPrototype, +); +webidl.converters["RequestInfo_DOMString"] = (V, opts) => { + // Union for (Request or USVString) + if (typeof V == "object") { + if (ObjectPrototypeIsPrototypeOf(RequestPrototype, V)) { + return webidl.converters["Request"](V, opts); } - request[_signal] = signal; - request[_getHeaders] = flash - ? () => headersFromHeaderList(inner.headerList(), guard) - : () => headersFromHeaderList(inner.headerList, guard); - return request; } - - /** - * @param {number} serverId - * @param {number} streamRid - * @param {ReadableStream} body - * @param {() => string} methodCb - * @param {() => string} urlCb - * @param {() => [string, string][]} headersCb - * @returns {Request} - */ - function fromFlashRequest( - serverId, - streamRid, - body, + // Passed to new URL(...) which implicitly converts DOMString -> USVString + return webidl.converters["DOMString"](V, opts); +}; +webidl.converters["RequestRedirect"] = webidl.createEnumConverter( + "RequestRedirect", + [ + "follow", + "error", + "manual", + ], +); +webidl.converters["RequestInit"] = webidl.createDictionaryConverter( + "RequestInit", + [ + { key: "method", converter: webidl.converters["ByteString"] }, + { key: "headers", converter: webidl.converters["HeadersInit"] }, + { + key: "body", + converter: webidl.createNullableConverter( + webidl.converters["BodyInit_DOMString"], + ), + }, + { key: "redirect", converter: webidl.converters["RequestRedirect"] }, + { + key: "signal", + converter: webidl.createNullableConverter( + webidl.converters["AbortSignal"], + ), + }, + { key: "client", converter: webidl.converters.any }, + ], +); + +/** + * @param {Request} request + * @returns {InnerRequest} + */ +function toInnerRequest(request) { + return request[_request]; +} + +/** + * @param {InnerRequest} inner + * @param {AbortSignal} signal + * @param {"request" | "immutable" | "request-no-cors" | "response" | "none"} guard + * @param {boolean} flash + * @returns {Request} + */ +function fromInnerRequest(inner, signal, guard, flash) { + const request = webidl.createBranded(Request); + if (flash) { + request[_flash] = inner; + } else { + request[_request] = inner; + } + request[_signal] = signal; + request[_getHeaders] = flash + ? () => headersFromHeaderList(inner.headerList(), guard) + : () => headersFromHeaderList(inner.headerList, guard); + return request; +} + +/** + * @param {number} serverId + * @param {number} streamRid + * @param {ReadableStream} body + * @param {() => string} methodCb + * @param {() => string} urlCb + * @param {() => [string, string][]} headersCb + * @returns {Request} + */ +function fromFlashRequest( + serverId, + streamRid, + body, + methodCb, + urlCb, + headersCb, +) { + const request = webidl.createBranded(Request); + request[_flash] = { + body: body !== null ? new InnerBody(body) : null, methodCb, urlCb, - headersCb, - ) { - const request = webidl.createBranded(Request); - request[_flash] = { - body: body !== null ? new InnerBody(body) : null, - methodCb, - urlCb, - headerList: headersCb, - streamRid, - serverId, - redirectMode: "follow", - redirectCount: 0, - }; - request[_getHeaders] = () => headersFromHeaderList(headersCb(), "request"); - return request; - } - - window.__bootstrap.fetch ??= {}; - window.__bootstrap.fetch.Request = Request; - window.__bootstrap.fetch.toInnerRequest = toInnerRequest; - window.__bootstrap.fetch.fromFlashRequest = fromFlashRequest; - window.__bootstrap.fetch.fromInnerRequest = fromInnerRequest; - window.__bootstrap.fetch.newInnerRequest = newInnerRequest; - window.__bootstrap.fetch.processUrlList = processUrlList; - window.__bootstrap.fetch._flash = _flash; -})(globalThis); + headerList: headersCb, + streamRid, + serverId, + redirectMode: "follow", + redirectCount: 0, + }; + request[_getHeaders] = () => headersFromHeaderList(headersCb(), "request"); + return request; +} + +export { + _flash, + fromFlashRequest, + fromInnerRequest, + newInnerRequest, + processUrlList, + Request, + RequestPrototype, + toInnerRequest, +}; diff --git a/ext/fetch/23_response.js b/ext/fetch/23_response.js index 070068d28a480b..4b579a3064fa0a 100644 --- a/ext/fetch/23_response.js +++ b/ext/fetch/23_response.js @@ -9,510 +9,510 @@ /// /// /// -"use strict"; - -((window) => { - const { isProxy } = Deno.core; - const webidl = window.__bootstrap.webidl; - const consoleInternal = window.__bootstrap.console; - const { - byteLowerCase, - } = window.__bootstrap.infra; - const { HTTP_TAB_OR_SPACE, regexMatcher, serializeJSValueToJSONString } = - window.__bootstrap.infra; - const { extractBody, mixinBody } = window.__bootstrap.fetchBody; - const { getLocationHref } = window.__bootstrap.location; - const { extractMimeType } = window.__bootstrap.mimesniff; - const { URL } = window.__bootstrap.url; - const { - getDecodeSplitHeader, - headerListFromHeaders, - headersFromHeaderList, - guardFromHeaders, - fillHeaders, - } = window.__bootstrap.headers; - const { - ArrayPrototypeMap, - ArrayPrototypePush, - ObjectDefineProperties, - ObjectPrototypeIsPrototypeOf, - RangeError, - RegExp, - RegExpPrototypeTest, - SafeArrayIterator, - Symbol, - SymbolFor, - TypeError, - } = window.__bootstrap.primordials; - - const VCHAR = ["\x21-\x7E"]; - const OBS_TEXT = ["\x80-\xFF"]; - - const REASON_PHRASE = [ - ...new SafeArrayIterator(HTTP_TAB_OR_SPACE), - ...new SafeArrayIterator(VCHAR), - ...new SafeArrayIterator(OBS_TEXT), - ]; - const REASON_PHRASE_MATCHER = regexMatcher(REASON_PHRASE); - const REASON_PHRASE_RE = new RegExp(`^[${REASON_PHRASE_MATCHER}]*$`); - - const _response = Symbol("response"); - const _headers = Symbol("headers"); - const _mimeType = Symbol("mime type"); - const _body = Symbol("body"); + +const core = globalThis.Deno.core; +import * as webidl from "internal:deno_webidl/00_webidl.js"; +import { createFilteredInspectProxy } from "internal:deno_console/02_console.js"; +import { + byteLowerCase, + HTTP_TAB_OR_SPACE, + regexMatcher, + serializeJSValueToJSONString, +} from "internal:deno_web/00_infra.js"; +import { extractBody, mixinBody } from "internal:deno_fetch/22_body.js"; +import { getLocationHref } from "internal:deno_web/12_location.js"; +import { extractMimeType } from "internal:deno_web/01_mimesniff.js"; +import { URL } from "internal:deno_url/00_url.js"; +import { + fillHeaders, + getDecodeSplitHeader, + guardFromHeaders, + headerListFromHeaders, + headersFromHeaderList, +} from "internal:deno_fetch/20_headers.js"; +const primordials = globalThis.__bootstrap.primordials; +const { + ArrayPrototypeMap, + ArrayPrototypePush, + ObjectDefineProperties, + ObjectPrototypeIsPrototypeOf, + RangeError, + RegExp, + RegExpPrototypeTest, + SafeArrayIterator, + Symbol, + SymbolFor, + TypeError, +} = primordials; + +const VCHAR = ["\x21-\x7E"]; +const OBS_TEXT = ["\x80-\xFF"]; + +const REASON_PHRASE = [ + ...new SafeArrayIterator(HTTP_TAB_OR_SPACE), + ...new SafeArrayIterator(VCHAR), + ...new SafeArrayIterator(OBS_TEXT), +]; +const REASON_PHRASE_MATCHER = regexMatcher(REASON_PHRASE); +const REASON_PHRASE_RE = new RegExp(`^[${REASON_PHRASE_MATCHER}]*$`); + +const _response = Symbol("response"); +const _headers = Symbol("headers"); +const _mimeType = Symbol("mime type"); +const _body = Symbol("body"); + +/** + * @typedef InnerResponse + * @property {"basic" | "cors" | "default" | "error" | "opaque" | "opaqueredirect"} type + * @property {() => string | null} url + * @property {string[]} urlList + * @property {number} status + * @property {string} statusMessage + * @property {[string, string][]} headerList + * @property {null | typeof __window.bootstrap.fetchBody.InnerBody} body + * @property {boolean} aborted + * @property {string} [error] + */ + +/** + * @param {number} status + * @returns {boolean} + */ +function nullBodyStatus(status) { + return status === 101 || status === 204 || status === 205 || status === 304; +} + +/** + * @param {number} status + * @returns {boolean} + */ +function redirectStatus(status) { + return status === 301 || status === 302 || status === 303 || + status === 307 || status === 308; +} + +/** + * https://fetch.spec.whatwg.org/#concept-response-clone + * @param {InnerResponse} response + * @returns {InnerResponse} + */ +function cloneInnerResponse(response) { + const urlList = [...new SafeArrayIterator(response.urlList)]; + const headerList = ArrayPrototypeMap( + response.headerList, + (x) => [x[0], x[1]], + ); + + let body = null; + if (response.body !== null) { + body = response.body.clone(); + } + + return { + type: response.type, + body, + headerList, + urlList, + status: response.status, + statusMessage: response.statusMessage, + aborted: response.aborted, + url() { + if (this.urlList.length == 0) return null; + return this.urlList[this.urlList.length - 1]; + }, + }; +} + +/** + * @returns {InnerResponse} + */ +function newInnerResponse(status = 200, statusMessage = "") { + return { + type: "default", + body: null, + headerList: [], + urlList: [], + status, + statusMessage, + aborted: false, + url() { + if (this.urlList.length == 0) return null; + return this.urlList[this.urlList.length - 1]; + }, + }; +} + +/** + * @param {string} error + * @returns {InnerResponse} + */ +function networkError(error) { + const resp = newInnerResponse(0); + resp.type = "error"; + resp.error = error; + return resp; +} + +/** + * @returns {InnerResponse} + */ +function abortedNetworkError() { + const resp = networkError("aborted"); + resp.aborted = true; + return resp; +} + +/** + * https://fetch.spec.whatwg.org#initialize-a-response + * @param {Response} response + * @param {ResponseInit} init + * @param {{ body: fetchBody.InnerBody, contentType: string | null } | null} bodyWithType + */ +function initializeAResponse(response, init, bodyWithType) { + // 1. + if ((init.status < 200 || init.status > 599) && init.status != 101) { + throw new RangeError( + `The status provided (${init.status}) is not equal to 101 and outside the range [200, 599].`, + ); + } + + // 2. + if ( + init.statusText && + !RegExpPrototypeTest(REASON_PHRASE_RE, init.statusText) + ) { + throw new TypeError("Status text is not valid."); + } + + // 3. + response[_response].status = init.status; + + // 4. + response[_response].statusMessage = init.statusText; + // 5. + /** @type {headers.Headers} */ + const headers = response[_headers]; + if (init.headers) { + fillHeaders(headers, init.headers); + } + + // 6. + if (bodyWithType !== null) { + if (nullBodyStatus(response[_response].status)) { + throw new TypeError( + "Response with null body status cannot have body", + ); + } + + const { body, contentType } = bodyWithType; + response[_response].body = body; + + if (contentType !== null) { + let hasContentType = false; + const list = headerListFromHeaders(headers); + for (let i = 0; i < list.length; i++) { + if (byteLowerCase(list[i][0]) === "content-type") { + hasContentType = true; + break; + } + } + if (!hasContentType) { + ArrayPrototypePush(list, ["Content-Type", contentType]); + } + } + } +} + +class Response { + get [_mimeType]() { + const values = getDecodeSplitHeader( + headerListFromHeaders(this[_headers]), + "Content-Type", + ); + return extractMimeType(values); + } + get [_body]() { + return this[_response].body; + } /** - * @typedef InnerResponse - * @property {"basic" | "cors" | "default" | "error" | "opaque" | "opaqueredirect"} type - * @property {() => string | null} url - * @property {string[]} urlList - * @property {number} status - * @property {string} statusMessage - * @property {[string, string][]} headerList - * @property {null | typeof __window.bootstrap.fetchBody.InnerBody} body - * @property {boolean} aborted - * @property {string} [error] + * @returns {Response} */ + static error() { + const inner = newInnerResponse(0); + inner.type = "error"; + const response = webidl.createBranded(Response); + response[_response] = inner; + response[_headers] = headersFromHeaderList( + response[_response].headerList, + "immutable", + ); + return response; + } /** + * @param {string} url * @param {number} status - * @returns {boolean} + * @returns {Response} */ - function nullBodyStatus(status) { - return status === 101 || status === 204 || status === 205 || status === 304; + static redirect(url, status = 302) { + const prefix = "Failed to call 'Response.redirect'"; + url = webidl.converters["USVString"](url, { + prefix, + context: "Argument 1", + }); + status = webidl.converters["unsigned short"](status, { + prefix, + context: "Argument 2", + }); + + const baseURL = getLocationHref(); + const parsedURL = new URL(url, baseURL); + if (!redirectStatus(status)) { + throw new RangeError("Invalid redirect status code."); + } + const inner = newInnerResponse(status); + inner.type = "default"; + ArrayPrototypePush(inner.headerList, ["Location", parsedURL.href]); + const response = webidl.createBranded(Response); + response[_response] = inner; + response[_headers] = headersFromHeaderList( + response[_response].headerList, + "immutable", + ); + return response; } /** - * @param {number} status - * @returns {boolean} + * @param {any} data + * @param {ResponseInit} init + * @returns {Response} */ - function redirectStatus(status) { - return status === 301 || status === 302 || status === 303 || - status === 307 || status === 308; + static json(data = undefined, init = {}) { + const prefix = "Failed to call 'Response.json'"; + data = webidl.converters.any(data); + init = webidl.converters["ResponseInit_fast"](init, { + prefix, + context: "Argument 2", + }); + + const str = serializeJSValueToJSONString(data); + const res = extractBody(str); + res.contentType = "application/json"; + const response = webidl.createBranded(Response); + response[_response] = newInnerResponse(); + response[_headers] = headersFromHeaderList( + response[_response].headerList, + "response", + ); + initializeAResponse(response, init, res); + return response; } /** - * https://fetch.spec.whatwg.org/#concept-response-clone - * @param {InnerResponse} response - * @returns {InnerResponse} + * @param {BodyInit | null} body + * @param {ResponseInit} init */ - function cloneInnerResponse(response) { - const urlList = [...new SafeArrayIterator(response.urlList)]; - const headerList = ArrayPrototypeMap( - response.headerList, - (x) => [x[0], x[1]], + constructor(body = null, init = undefined) { + const prefix = "Failed to construct 'Response'"; + body = webidl.converters["BodyInit_DOMString?"](body, { + prefix, + context: "Argument 1", + }); + init = webidl.converters["ResponseInit_fast"](init, { + prefix, + context: "Argument 2", + }); + + this[_response] = newInnerResponse(); + this[_headers] = headersFromHeaderList( + this[_response].headerList, + "response", ); - let body = null; - if (response.body !== null) { - body = response.body.clone(); + let bodyWithType = null; + if (body !== null) { + bodyWithType = extractBody(body); } - - return { - type: response.type, - body, - headerList, - urlList, - status: response.status, - statusMessage: response.statusMessage, - aborted: response.aborted, - url() { - if (this.urlList.length == 0) return null; - return this.urlList[this.urlList.length - 1]; - }, - }; + initializeAResponse(this, init, bodyWithType); + this[webidl.brand] = webidl.brand; } /** - * @returns {InnerResponse} + * @returns {"basic" | "cors" | "default" | "error" | "opaque" | "opaqueredirect"} */ - function newInnerResponse(status = 200, statusMessage = "") { - return { - type: "default", - body: null, - headerList: [], - urlList: [], - status, - statusMessage, - aborted: false, - url() { - if (this.urlList.length == 0) return null; - return this.urlList[this.urlList.length - 1]; - }, - }; + get type() { + webidl.assertBranded(this, ResponsePrototype); + return this[_response].type; } /** - * @param {string} error - * @returns {InnerResponse} + * @returns {string} */ - function networkError(error) { - const resp = newInnerResponse(0); - resp.type = "error"; - resp.error = error; - return resp; + get url() { + webidl.assertBranded(this, ResponsePrototype); + const url = this[_response].url(); + if (url === null) return ""; + const newUrl = new URL(url); + newUrl.hash = ""; + return newUrl.href; } /** - * @returns {InnerResponse} + * @returns {boolean} */ - function abortedNetworkError() { - const resp = networkError("aborted"); - resp.aborted = true; - return resp; + get redirected() { + webidl.assertBranded(this, ResponsePrototype); + return this[_response].urlList.length > 1; } /** - * https://fetch.spec.whatwg.org#initialize-a-response - * @param {Response} response - * @param {ResponseInit} init - * @param {{ body: __bootstrap.fetchBody.InnerBody, contentType: string | null } | null} bodyWithType + * @returns {number} */ - function initializeAResponse(response, init, bodyWithType) { - // 1. - if ((init.status < 200 || init.status > 599) && init.status != 101) { - throw new RangeError( - `The status provided (${init.status}) is not equal to 101 and outside the range [200, 599].`, - ); - } - - // 2. - if ( - init.statusText && - !RegExpPrototypeTest(REASON_PHRASE_RE, init.statusText) - ) { - throw new TypeError("Status text is not valid."); - } - - // 3. - response[_response].status = init.status; - - // 4. - response[_response].statusMessage = init.statusText; - // 5. - /** @type {__bootstrap.headers.Headers} */ - const headers = response[_headers]; - if (init.headers) { - fillHeaders(headers, init.headers); - } - - // 6. - if (bodyWithType !== null) { - if (nullBodyStatus(response[_response].status)) { - throw new TypeError( - "Response with null body status cannot have body", - ); - } - - const { body, contentType } = bodyWithType; - response[_response].body = body; - - if (contentType !== null) { - let hasContentType = false; - const list = headerListFromHeaders(headers); - for (let i = 0; i < list.length; i++) { - if (byteLowerCase(list[i][0]) === "content-type") { - hasContentType = true; - break; - } - } - if (!hasContentType) { - ArrayPrototypePush(list, ["Content-Type", contentType]); - } - } - } + get status() { + webidl.assertBranded(this, ResponsePrototype); + return this[_response].status; } - class Response { - get [_mimeType]() { - const values = getDecodeSplitHeader( - headerListFromHeaders(this[_headers]), - "Content-Type", - ); - return extractMimeType(values); - } - get [_body]() { - return this[_response].body; - } - - /** - * @returns {Response} - */ - static error() { - const inner = newInnerResponse(0); - inner.type = "error"; - const response = webidl.createBranded(Response); - response[_response] = inner; - response[_headers] = headersFromHeaderList( - response[_response].headerList, - "immutable", - ); - return response; - } - - /** - * @param {string} url - * @param {number} status - * @returns {Response} - */ - static redirect(url, status = 302) { - const prefix = "Failed to call 'Response.redirect'"; - url = webidl.converters["USVString"](url, { - prefix, - context: "Argument 1", - }); - status = webidl.converters["unsigned short"](status, { - prefix, - context: "Argument 2", - }); - - const baseURL = getLocationHref(); - const parsedURL = new URL(url, baseURL); - if (!redirectStatus(status)) { - throw new RangeError("Invalid redirect status code."); - } - const inner = newInnerResponse(status); - inner.type = "default"; - ArrayPrototypePush(inner.headerList, ["Location", parsedURL.href]); - const response = webidl.createBranded(Response); - response[_response] = inner; - response[_headers] = headersFromHeaderList( - response[_response].headerList, - "immutable", - ); - return response; - } - - /** - * @param {any} data - * @param {ResponseInit} init - * @returns {Response} - */ - static json(data = undefined, init = {}) { - const prefix = "Failed to call 'Response.json'"; - data = webidl.converters.any(data); - init = webidl.converters["ResponseInit_fast"](init, { - prefix, - context: "Argument 2", - }); - - const str = serializeJSValueToJSONString(data); - const res = extractBody(str); - res.contentType = "application/json"; - const response = webidl.createBranded(Response); - response[_response] = newInnerResponse(); - response[_headers] = headersFromHeaderList( - response[_response].headerList, - "response", - ); - initializeAResponse(response, init, res); - return response; - } - - /** - * @param {BodyInit | null} body - * @param {ResponseInit} init - */ - constructor(body = null, init = undefined) { - const prefix = "Failed to construct 'Response'"; - body = webidl.converters["BodyInit_DOMString?"](body, { - prefix, - context: "Argument 1", - }); - init = webidl.converters["ResponseInit_fast"](init, { - prefix, - context: "Argument 2", - }); - - this[_response] = newInnerResponse(); - this[_headers] = headersFromHeaderList( - this[_response].headerList, - "response", - ); - - let bodyWithType = null; - if (body !== null) { - bodyWithType = extractBody(body); - } - initializeAResponse(this, init, bodyWithType); - this[webidl.brand] = webidl.brand; - } - - /** - * @returns {"basic" | "cors" | "default" | "error" | "opaque" | "opaqueredirect"} - */ - get type() { - webidl.assertBranded(this, ResponsePrototype); - return this[_response].type; - } - - /** - * @returns {string} - */ - get url() { - webidl.assertBranded(this, ResponsePrototype); - const url = this[_response].url(); - if (url === null) return ""; - const newUrl = new URL(url); - newUrl.hash = ""; - return newUrl.href; - } - - /** - * @returns {boolean} - */ - get redirected() { - webidl.assertBranded(this, ResponsePrototype); - return this[_response].urlList.length > 1; - } - - /** - * @returns {number} - */ - get status() { - webidl.assertBranded(this, ResponsePrototype); - return this[_response].status; - } - - /** - * @returns {boolean} - */ - get ok() { - webidl.assertBranded(this, ResponsePrototype); - const status = this[_response].status; - return status >= 200 && status <= 299; - } - - /** - * @returns {string} - */ - get statusText() { - webidl.assertBranded(this, ResponsePrototype); - return this[_response].statusMessage; - } - - /** - * @returns {Headers} - */ - get headers() { - webidl.assertBranded(this, ResponsePrototype); - return this[_headers]; - } - - /** - * @returns {Response} - */ - clone() { - webidl.assertBranded(this, ResponsePrototype); - if (this[_body] && this[_body].unusable()) { - throw new TypeError("Body is unusable."); - } - const second = webidl.createBranded(Response); - const newRes = cloneInnerResponse(this[_response]); - second[_response] = newRes; - second[_headers] = headersFromHeaderList( - newRes.headerList, - guardFromHeaders(this[_headers]), - ); - return second; - } - - [SymbolFor("Deno.customInspect")](inspect) { - return inspect(consoleInternal.createFilteredInspectProxy({ - object: this, - evaluate: ObjectPrototypeIsPrototypeOf(ResponsePrototype, this), - keys: [ - "body", - "bodyUsed", - "headers", - "ok", - "redirected", - "status", - "statusText", - "url", - ], - })); - } + /** + * @returns {boolean} + */ + get ok() { + webidl.assertBranded(this, ResponsePrototype); + const status = this[_response].status; + return status >= 200 && status <= 299; } - webidl.configurePrototype(Response); - ObjectDefineProperties(Response, { - json: { enumerable: true }, - redirect: { enumerable: true }, - error: { enumerable: true }, - }); - const ResponsePrototype = Response.prototype; - mixinBody(ResponsePrototype, _body, _mimeType); - - webidl.converters["Response"] = webidl.createInterfaceConverter( - "Response", - ResponsePrototype, - ); - webidl.converters["ResponseInit"] = webidl.createDictionaryConverter( - "ResponseInit", - [{ - key: "status", - defaultValue: 200, - converter: webidl.converters["unsigned short"], - }, { - key: "statusText", - defaultValue: "", - converter: webidl.converters["ByteString"], - }, { - key: "headers", - converter: webidl.converters["HeadersInit"], - }], - ); - webidl.converters["ResponseInit_fast"] = function (init, opts) { - if (init === undefined || init === null) { - return { status: 200, statusText: "", headers: undefined }; - } - // Fast path, if not a proxy - if (typeof init === "object" && !isProxy(init)) { - // Not a proxy fast path - const status = init.status !== undefined - ? webidl.converters["unsigned short"](init.status) - : 200; - const statusText = init.statusText !== undefined - ? webidl.converters["ByteString"](init.statusText) - : ""; - const headers = init.headers !== undefined - ? webidl.converters["HeadersInit"](init.headers) - : undefined; - return { status, statusText, headers }; - } - // Slow default path - return webidl.converters["ResponseInit"](init, opts); - }; + /** + * @returns {string} + */ + get statusText() { + webidl.assertBranded(this, ResponsePrototype); + return this[_response].statusMessage; + } /** - * @param {Response} response - * @returns {InnerResponse} + * @returns {Headers} */ - function toInnerResponse(response) { - return response[_response]; + get headers() { + webidl.assertBranded(this, ResponsePrototype); + return this[_headers]; } /** - * @param {InnerResponse} inner - * @param {"request" | "immutable" | "request-no-cors" | "response" | "none"} guard * @returns {Response} */ - function fromInnerResponse(inner, guard) { - const response = webidl.createBranded(Response); - response[_response] = inner; - response[_headers] = headersFromHeaderList(inner.headerList, guard); - return response; + clone() { + webidl.assertBranded(this, ResponsePrototype); + if (this[_body] && this[_body].unusable()) { + throw new TypeError("Body is unusable."); + } + const second = webidl.createBranded(Response); + const newRes = cloneInnerResponse(this[_response]); + second[_response] = newRes; + second[_headers] = headersFromHeaderList( + newRes.headerList, + guardFromHeaders(this[_headers]), + ); + return second; } - window.__bootstrap.fetch ??= {}; - window.__bootstrap.fetch.Response = Response; - window.__bootstrap.fetch.ResponsePrototype = ResponsePrototype; - window.__bootstrap.fetch.newInnerResponse = newInnerResponse; - window.__bootstrap.fetch.toInnerResponse = toInnerResponse; - window.__bootstrap.fetch.fromInnerResponse = fromInnerResponse; - window.__bootstrap.fetch.redirectStatus = redirectStatus; - window.__bootstrap.fetch.nullBodyStatus = nullBodyStatus; - window.__bootstrap.fetch.networkError = networkError; - window.__bootstrap.fetch.abortedNetworkError = abortedNetworkError; -})(globalThis); + [SymbolFor("Deno.customInspect")](inspect) { + return inspect(createFilteredInspectProxy({ + object: this, + evaluate: ObjectPrototypeIsPrototypeOf(ResponsePrototype, this), + keys: [ + "body", + "bodyUsed", + "headers", + "ok", + "redirected", + "status", + "statusText", + "url", + ], + })); + } +} + +webidl.configurePrototype(Response); +ObjectDefineProperties(Response, { + json: { enumerable: true }, + redirect: { enumerable: true }, + error: { enumerable: true }, +}); +const ResponsePrototype = Response.prototype; +mixinBody(ResponsePrototype, _body, _mimeType); + +webidl.converters["Response"] = webidl.createInterfaceConverter( + "Response", + ResponsePrototype, +); +webidl.converters["ResponseInit"] = webidl.createDictionaryConverter( + "ResponseInit", + [{ + key: "status", + defaultValue: 200, + converter: webidl.converters["unsigned short"], + }, { + key: "statusText", + defaultValue: "", + converter: webidl.converters["ByteString"], + }, { + key: "headers", + converter: webidl.converters["HeadersInit"], + }], +); +webidl.converters["ResponseInit_fast"] = function (init, opts) { + if (init === undefined || init === null) { + return { status: 200, statusText: "", headers: undefined }; + } + // Fast path, if not a proxy + if (typeof init === "object" && !core.isProxy(init)) { + // Not a proxy fast path + const status = init.status !== undefined + ? webidl.converters["unsigned short"](init.status) + : 200; + const statusText = init.statusText !== undefined + ? webidl.converters["ByteString"](init.statusText) + : ""; + const headers = init.headers !== undefined + ? webidl.converters["HeadersInit"](init.headers) + : undefined; + return { status, statusText, headers }; + } + // Slow default path + return webidl.converters["ResponseInit"](init, opts); +}; + +/** + * @param {Response} response + * @returns {InnerResponse} + */ +function toInnerResponse(response) { + return response[_response]; +} + +/** + * @param {InnerResponse} inner + * @param {"request" | "immutable" | "request-no-cors" | "response" | "none"} guard + * @returns {Response} + */ +function fromInnerResponse(inner, guard) { + const response = webidl.createBranded(Response); + response[_response] = inner; + response[_headers] = headersFromHeaderList(inner.headerList, guard); + return response; +} + +export { + abortedNetworkError, + fromInnerResponse, + networkError, + newInnerResponse, + nullBodyStatus, + redirectStatus, + Response, + ResponsePrototype, + toInnerResponse, +}; diff --git a/ext/fetch/26_fetch.js b/ext/fetch/26_fetch.js index ddb023a377cd32..6de68e8d6a758c 100644 --- a/ext/fetch/26_fetch.js +++ b/ext/fetch/26_fetch.js @@ -9,578 +9,577 @@ /// /// /// -"use strict"; - -((window) => { - const core = window.Deno.core; - const ops = core.ops; - const webidl = window.__bootstrap.webidl; - const { byteLowerCase } = window.__bootstrap.infra; - const { BlobPrototype } = window.__bootstrap.file; - const { errorReadableStream, ReadableStreamPrototype, readableStreamForRid } = - window.__bootstrap.streams; - const { InnerBody, extractBody } = window.__bootstrap.fetchBody; - const { - toInnerRequest, - toInnerResponse, - fromInnerResponse, - redirectStatus, - nullBodyStatus, - networkError, - abortedNetworkError, - processUrlList, - } = window.__bootstrap.fetch; - const abortSignal = window.__bootstrap.abortSignal; - const { - ArrayPrototypePush, - ArrayPrototypeSplice, - ArrayPrototypeFilter, - ArrayPrototypeIncludes, - ObjectPrototypeIsPrototypeOf, - Promise, - PromisePrototypeThen, - PromisePrototypeCatch, - SafeArrayIterator, - String, - StringPrototypeStartsWith, - StringPrototypeToLowerCase, - TypeError, - Uint8Array, - Uint8ArrayPrototype, - WeakMap, - WeakMapPrototypeDelete, - WeakMapPrototypeGet, - WeakMapPrototypeHas, - WeakMapPrototypeSet, - } = window.__bootstrap.primordials; - - const REQUEST_BODY_HEADER_NAMES = [ - "content-encoding", - "content-language", - "content-location", - "content-type", - ]; - - const requestBodyReaders = new WeakMap(); - - /** - * @param {{ method: string, url: string, headers: [string, string][], clientRid: number | null, hasBody: boolean }} args - * @param {Uint8Array | null} body - * @returns {{ requestRid: number, requestBodyRid: number | null }} - */ - function opFetch(method, url, headers, clientRid, hasBody, bodyLength, body) { - return ops.op_fetch( - method, - url, - headers, - clientRid, - hasBody, - bodyLength, - body, - ); - } - /** - * @param {number} rid - * @returns {Promise<{ status: number, statusText: string, headers: [string, string][], url: string, responseRid: number }>} - */ - function opFetchSend(rid) { - return core.opAsync("op_fetch_send", rid); +const core = globalThis.Deno.core; +const ops = core.ops; +import * as webidl from "internal:deno_webidl/00_webidl.js"; +import { byteLowerCase } from "internal:deno_web/00_infra.js"; +import { BlobPrototype } from "internal:deno_web/09_file.js"; +import { + errorReadableStream, + readableStreamForRid, + ReadableStreamPrototype, +} from "internal:deno_web/06_streams.js"; +import { extractBody, InnerBody } from "internal:deno_fetch/22_body.js"; +import { + processUrlList, + toInnerRequest, +} from "internal:deno_fetch/23_request.js"; +import { + abortedNetworkError, + fromInnerResponse, + networkError, + nullBodyStatus, + redirectStatus, + toInnerResponse, +} from "internal:deno_fetch/23_response.js"; +import * as abortSignal from "internal:deno_web/03_abort_signal.js"; +const primordials = globalThis.__bootstrap.primordials; +const { + ArrayPrototypePush, + ArrayPrototypeSplice, + ArrayPrototypeFilter, + ArrayPrototypeIncludes, + ObjectPrototypeIsPrototypeOf, + Promise, + PromisePrototypeThen, + PromisePrototypeCatch, + SafeArrayIterator, + String, + StringPrototypeStartsWith, + StringPrototypeToLowerCase, + TypeError, + Uint8Array, + Uint8ArrayPrototype, + WeakMap, + WeakMapPrototypeDelete, + WeakMapPrototypeGet, + WeakMapPrototypeHas, + WeakMapPrototypeSet, +} = primordials; + +const REQUEST_BODY_HEADER_NAMES = [ + "content-encoding", + "content-language", + "content-location", + "content-type", +]; + +const requestBodyReaders = new WeakMap(); + +/** + * @param {{ method: string, url: string, headers: [string, string][], clientRid: number | null, hasBody: boolean }} args + * @param {Uint8Array | null} body + * @returns {{ requestRid: number, requestBodyRid: number | null }} + */ +function opFetch(method, url, headers, clientRid, hasBody, bodyLength, body) { + return ops.op_fetch( + method, + url, + headers, + clientRid, + hasBody, + bodyLength, + body, + ); +} + +/** + * @param {number} rid + * @returns {Promise<{ status: number, statusText: string, headers: [string, string][], url: string, responseRid: number }>} + */ +function opFetchSend(rid) { + return core.opAsync("op_fetch_send", rid); +} + +/** + * @param {number} responseBodyRid + * @param {AbortSignal} [terminator] + * @returns {ReadableStream} + */ +function createResponseBodyStream(responseBodyRid, terminator) { + const readable = readableStreamForRid(responseBodyRid); + + function onAbort() { + errorReadableStream(readable, terminator.reason); + core.tryClose(responseBodyRid); } - /** - * @param {number} responseBodyRid - * @param {AbortSignal} [terminator] - * @returns {ReadableStream} - */ - function createResponseBodyStream(responseBodyRid, terminator) { - const readable = readableStreamForRid(responseBodyRid); - - function onAbort() { - errorReadableStream(readable, terminator.reason); - core.tryClose(responseBodyRid); + // TODO(lucacasonato): clean up registration + terminator[abortSignal.add](onAbort); + + return readable; +} + +/** + * @param {InnerRequest} req + * @param {boolean} recursive + * @param {AbortSignal} terminator + * @returns {Promise} + */ +async function mainFetch(req, recursive, terminator) { + if (req.blobUrlEntry !== null) { + if (req.method !== "GET") { + throw new TypeError("Blob URL fetch only supports GET method."); } - // TODO(lucacasonato): clean up registration - terminator[abortSignal.add](onAbort); + const body = new InnerBody(req.blobUrlEntry.stream()); + terminator[abortSignal.add](() => body.error(terminator.reason)); + processUrlList(req.urlList, req.urlListProcessed); - return readable; + return { + headerList: [ + ["content-length", String(req.blobUrlEntry.size)], + ["content-type", req.blobUrlEntry.type], + ], + status: 200, + statusMessage: "OK", + body, + type: "basic", + url() { + if (this.urlList.length == 0) return null; + return this.urlList[this.urlList.length - 1]; + }, + urlList: recursive + ? [] + : [...new SafeArrayIterator(req.urlListProcessed)], + }; } - /** - * @param {InnerRequest} req - * @param {boolean} recursive - * @param {AbortSignal} terminator - * @returns {Promise} - */ - async function mainFetch(req, recursive, terminator) { - if (req.blobUrlEntry !== null) { - if (req.method !== "GET") { - throw new TypeError("Blob URL fetch only supports GET method."); - } - - const body = new InnerBody(req.blobUrlEntry.stream()); - terminator[abortSignal.add](() => body.error(terminator.reason)); - processUrlList(req.urlList, req.urlListProcessed); - - return { - headerList: [ - ["content-length", String(req.blobUrlEntry.size)], - ["content-type", req.blobUrlEntry.type], - ], - status: 200, - statusMessage: "OK", - body, - type: "basic", - url() { - if (this.urlList.length == 0) return null; - return this.urlList[this.urlList.length - 1]; - }, - urlList: recursive - ? [] - : [...new SafeArrayIterator(req.urlListProcessed)], - }; - } + /** @type {ReadableStream | Uint8Array | null} */ + let reqBody = null; - /** @type {ReadableStream | Uint8Array | null} */ - let reqBody = null; - - if (req.body !== null) { + if (req.body !== null) { + if ( + ObjectPrototypeIsPrototypeOf( + ReadableStreamPrototype, + req.body.streamOrStatic, + ) + ) { if ( - ObjectPrototypeIsPrototypeOf( - ReadableStreamPrototype, - req.body.streamOrStatic, - ) + req.body.length === null || + ObjectPrototypeIsPrototypeOf(BlobPrototype, req.body.source) ) { - if ( - req.body.length === null || - ObjectPrototypeIsPrototypeOf(BlobPrototype, req.body.source) - ) { - reqBody = req.body.stream; + reqBody = req.body.stream; + } else { + const reader = req.body.stream.getReader(); + WeakMapPrototypeSet(requestBodyReaders, req, reader); + const r1 = await reader.read(); + if (r1.done) { + reqBody = new Uint8Array(0); } else { - const reader = req.body.stream.getReader(); - WeakMapPrototypeSet(requestBodyReaders, req, reader); - const r1 = await reader.read(); - if (r1.done) { - reqBody = new Uint8Array(0); - } else { - reqBody = r1.value; - const r2 = await reader.read(); - if (!r2.done) throw new TypeError("Unreachable"); - } - WeakMapPrototypeDelete(requestBodyReaders, req); + reqBody = r1.value; + const r2 = await reader.read(); + if (!r2.done) throw new TypeError("Unreachable"); } - } else { - req.body.streamOrStatic.consumed = true; - reqBody = req.body.streamOrStatic.body; - // TODO(@AaronO): plumb support for StringOrBuffer all the way - reqBody = typeof reqBody === "string" ? core.encode(reqBody) : reqBody; + WeakMapPrototypeDelete(requestBodyReaders, req); } + } else { + req.body.streamOrStatic.consumed = true; + reqBody = req.body.streamOrStatic.body; + // TODO(@AaronO): plumb support for StringOrBuffer all the way + reqBody = typeof reqBody === "string" ? core.encode(reqBody) : reqBody; } + } - const { requestRid, requestBodyRid, cancelHandleRid } = opFetch( - req.method, - req.currentUrl(), - req.headerList, - req.clientRid, - reqBody !== null, - req.body?.length, - ObjectPrototypeIsPrototypeOf(Uint8ArrayPrototype, reqBody) - ? reqBody - : null, - ); - - function onAbort() { - if (cancelHandleRid !== null) { - core.tryClose(cancelHandleRid); - } - if (requestBodyRid !== null) { - core.tryClose(requestBodyRid); - } + const { requestRid, requestBodyRid, cancelHandleRid } = opFetch( + req.method, + req.currentUrl(), + req.headerList, + req.clientRid, + reqBody !== null, + req.body?.length, + ObjectPrototypeIsPrototypeOf(Uint8ArrayPrototype, reqBody) ? reqBody : null, + ); + + function onAbort() { + if (cancelHandleRid !== null) { + core.tryClose(cancelHandleRid); } - terminator[abortSignal.add](onAbort); - - let requestSendError; - let requestSendErrorSet = false; if (requestBodyRid !== null) { - if ( - reqBody === null || - !ObjectPrototypeIsPrototypeOf(ReadableStreamPrototype, reqBody) - ) { - throw new TypeError("Unreachable"); + core.tryClose(requestBodyRid); + } + } + terminator[abortSignal.add](onAbort); + + let requestSendError; + let requestSendErrorSet = false; + if (requestBodyRid !== null) { + if ( + reqBody === null || + !ObjectPrototypeIsPrototypeOf(ReadableStreamPrototype, reqBody) + ) { + throw new TypeError("Unreachable"); + } + const reader = reqBody.getReader(); + WeakMapPrototypeSet(requestBodyReaders, req, reader); + (async () => { + let done = false; + while (!done) { + let val; + try { + const res = await reader.read(); + done = res.done; + val = res.value; + } catch (err) { + if (terminator.aborted) break; + // TODO(lucacasonato): propagate error into response body stream + requestSendError = err; + requestSendErrorSet = true; + break; + } + if (done) break; + if (!ObjectPrototypeIsPrototypeOf(Uint8ArrayPrototype, val)) { + const error = new TypeError( + "Item in request body ReadableStream is not a Uint8Array", + ); + await reader.cancel(error); + // TODO(lucacasonato): propagate error into response body stream + requestSendError = error; + requestSendErrorSet = true; + break; + } + try { + await core.writeAll(requestBodyRid, val); + } catch (err) { + if (terminator.aborted) break; + await reader.cancel(err); + // TODO(lucacasonato): propagate error into response body stream + requestSendError = err; + requestSendErrorSet = true; + break; + } } - const reader = reqBody.getReader(); - WeakMapPrototypeSet(requestBodyReaders, req, reader); - (async () => { - let done = false; - while (!done) { - let val; - try { - const res = await reader.read(); - done = res.done; - val = res.value; - } catch (err) { - if (terminator.aborted) break; - // TODO(lucacasonato): propagate error into response body stream + if (done && !terminator.aborted) { + try { + await core.shutdown(requestBodyRid); + } catch (err) { + if (!terminator.aborted) { requestSendError = err; requestSendErrorSet = true; - break; - } - if (done) break; - if (!ObjectPrototypeIsPrototypeOf(Uint8ArrayPrototype, val)) { - const error = new TypeError( - "Item in request body ReadableStream is not a Uint8Array", - ); - await reader.cancel(error); - // TODO(lucacasonato): propagate error into response body stream - requestSendError = error; - requestSendErrorSet = true; - break; - } - try { - await core.writeAll(requestBodyRid, val); - } catch (err) { - if (terminator.aborted) break; - await reader.cancel(err); - // TODO(lucacasonato): propagate error into response body stream - requestSendError = err; - requestSendErrorSet = true; - break; } } - if (done && !terminator.aborted) { - try { - await core.shutdown(requestBodyRid); - } catch (err) { - if (!terminator.aborted) { - requestSendError = err; - requestSendErrorSet = true; - } - } - } - WeakMapPrototypeDelete(requestBodyReaders, req); - core.tryClose(requestBodyRid); - })(); - } - let resp; - try { - resp = await opFetchSend(requestRid); - } catch (err) { - if (terminator.aborted) return; - if (requestSendErrorSet) { - // if the request body stream errored, we want to propagate that error - // instead of the original error from opFetchSend - throw new TypeError("Failed to fetch: request body stream errored", { - cause: requestSendError, - }); - } - throw err; - } finally { - if (cancelHandleRid !== null) { - core.tryClose(cancelHandleRid); } + WeakMapPrototypeDelete(requestBodyReaders, req); + core.tryClose(requestBodyRid); + })(); + } + let resp; + try { + resp = await opFetchSend(requestRid); + } catch (err) { + if (terminator.aborted) return; + if (requestSendErrorSet) { + // if the request body stream errored, we want to propagate that error + // instead of the original error from opFetchSend + throw new TypeError("Failed to fetch: request body stream errored", { + cause: requestSendError, + }); } - if (terminator.aborted) return abortedNetworkError(); - - processUrlList(req.urlList, req.urlListProcessed); - - /** @type {InnerResponse} */ - const response = { - headerList: resp.headers, - status: resp.status, - body: null, - statusMessage: resp.statusText, - type: "basic", - url() { - if (this.urlList.length == 0) return null; - return this.urlList[this.urlList.length - 1]; - }, - urlList: req.urlListProcessed, - }; - if (redirectStatus(resp.status)) { - switch (req.redirectMode) { - case "error": - core.close(resp.responseRid); - return networkError( - "Encountered redirect while redirect mode is set to 'error'", - ); - case "follow": - core.close(resp.responseRid); - return httpRedirectFetch(req, response, terminator); - case "manual": - break; - } + throw err; + } finally { + if (cancelHandleRid !== null) { + core.tryClose(cancelHandleRid); + } + } + if (terminator.aborted) return abortedNetworkError(); + + processUrlList(req.urlList, req.urlListProcessed); + + /** @type {InnerResponse} */ + const response = { + headerList: resp.headers, + status: resp.status, + body: null, + statusMessage: resp.statusText, + type: "basic", + url() { + if (this.urlList.length == 0) return null; + return this.urlList[this.urlList.length - 1]; + }, + urlList: req.urlListProcessed, + }; + if (redirectStatus(resp.status)) { + switch (req.redirectMode) { + case "error": + core.close(resp.responseRid); + return networkError( + "Encountered redirect while redirect mode is set to 'error'", + ); + case "follow": + core.close(resp.responseRid); + return httpRedirectFetch(req, response, terminator); + case "manual": + break; } + } - if (nullBodyStatus(response.status)) { + if (nullBodyStatus(response.status)) { + core.close(resp.responseRid); + } else { + if (req.method === "HEAD" || req.method === "CONNECT") { + response.body = null; core.close(resp.responseRid); } else { - if (req.method === "HEAD" || req.method === "CONNECT") { - response.body = null; - core.close(resp.responseRid); - } else { - response.body = new InnerBody( - createResponseBodyStream(resp.responseRid, terminator), - ); - } + response.body = new InnerBody( + createResponseBodyStream(resp.responseRid, terminator), + ); } + } - if (recursive) return response; + if (recursive) return response; - if (response.urlList.length === 0) { - processUrlList(req.urlList, req.urlListProcessed); - response.urlList = [...new SafeArrayIterator(req.urlListProcessed)]; - } + if (response.urlList.length === 0) { + processUrlList(req.urlList, req.urlListProcessed); + response.urlList = [...new SafeArrayIterator(req.urlListProcessed)]; + } + return response; +} + +/** + * @param {InnerRequest} request + * @param {InnerResponse} response + * @param {AbortSignal} terminator + * @returns {Promise} + */ +function httpRedirectFetch(request, response, terminator) { + const locationHeaders = ArrayPrototypeFilter( + response.headerList, + (entry) => byteLowerCase(entry[0]) === "location", + ); + if (locationHeaders.length === 0) { return response; } - - /** - * @param {InnerRequest} request - * @param {InnerResponse} response - * @param {AbortSignal} terminator - * @returns {Promise} - */ - function httpRedirectFetch(request, response, terminator) { - const locationHeaders = ArrayPrototypeFilter( - response.headerList, - (entry) => byteLowerCase(entry[0]) === "location", - ); - if (locationHeaders.length === 0) { - return response; - } - const locationURL = new URL( - locationHeaders[0][1], - response.url() ?? undefined, + const locationURL = new URL( + locationHeaders[0][1], + response.url() ?? undefined, + ); + if (locationURL.hash === "") { + locationURL.hash = request.currentUrl().hash; + } + if (locationURL.protocol !== "https:" && locationURL.protocol !== "http:") { + return networkError("Can not redirect to a non HTTP(s) url"); + } + if (request.redirectCount === 20) { + return networkError("Maximum number of redirects (20) reached"); + } + request.redirectCount++; + if ( + response.status !== 303 && + request.body !== null && + request.body.source === null + ) { + return networkError( + "Can not redeliver a streaming request body after a redirect", ); - if (locationURL.hash === "") { - locationURL.hash = request.currentUrl().hash; - } - if (locationURL.protocol !== "https:" && locationURL.protocol !== "http:") { - return networkError("Can not redirect to a non HTTP(s) url"); - } - if (request.redirectCount === 20) { - return networkError("Maximum number of redirects (20) reached"); - } - request.redirectCount++; - if ( - response.status !== 303 && - request.body !== null && - request.body.source === null - ) { - return networkError( - "Can not redeliver a streaming request body after a redirect", - ); - } - if ( - ((response.status === 301 || response.status === 302) && - request.method === "POST") || - (response.status === 303 && - request.method !== "GET" && - request.method !== "HEAD") - ) { - request.method = "GET"; - request.body = null; - for (let i = 0; i < request.headerList.length; i++) { - if ( - ArrayPrototypeIncludes( - REQUEST_BODY_HEADER_NAMES, - byteLowerCase(request.headerList[i][0]), - ) - ) { - ArrayPrototypeSplice(request.headerList, i, 1); - i--; - } + } + if ( + ((response.status === 301 || response.status === 302) && + request.method === "POST") || + (response.status === 303 && + request.method !== "GET" && + request.method !== "HEAD") + ) { + request.method = "GET"; + request.body = null; + for (let i = 0; i < request.headerList.length; i++) { + if ( + ArrayPrototypeIncludes( + REQUEST_BODY_HEADER_NAMES, + byteLowerCase(request.headerList[i][0]), + ) + ) { + ArrayPrototypeSplice(request.headerList, i, 1); + i--; } } - if (request.body !== null) { - const res = extractBody(request.body.source); - request.body = res.body; - } - ArrayPrototypePush(request.urlList, () => locationURL.href); - return mainFetch(request, true, terminator); } + if (request.body !== null) { + const res = extractBody(request.body.source); + request.body = res.body; + } + ArrayPrototypePush(request.urlList, () => locationURL.href); + return mainFetch(request, true, terminator); +} + +/** + * @param {RequestInfo} input + * @param {RequestInit} init + */ +function fetch(input, init = {}) { + // There is an async dispatch later that causes a stack trace disconnect. + // We reconnect it by assigning the result of that dispatch to `opPromise`, + // awaiting `opPromise` in an inner function also named `fetch()` and + // returning the result from that. + let opPromise = undefined; + // 1. + const result = new Promise((resolve, reject) => { + const prefix = "Failed to call 'fetch'"; + webidl.requiredArguments(arguments.length, 1, { prefix }); + // 2. + const requestObject = new Request(input, init); + // 3. + const request = toInnerRequest(requestObject); + // 4. + if (requestObject.signal.aborted) { + reject(abortFetch(request, null, requestObject.signal.reason)); + return; + } - /** - * @param {RequestInfo} input - * @param {RequestInit} init - */ - function fetch(input, init = {}) { - // There is an async dispatch later that causes a stack trace disconnect. - // We reconnect it by assigning the result of that dispatch to `opPromise`, - // awaiting `opPromise` in an inner function also named `fetch()` and - // returning the result from that. - let opPromise = undefined; - // 1. - const result = new Promise((resolve, reject) => { - const prefix = "Failed to call 'fetch'"; - webidl.requiredArguments(arguments.length, 1, { prefix }); - // 2. - const requestObject = new Request(input, init); - // 3. - const request = toInnerRequest(requestObject); - // 4. - if (requestObject.signal.aborted) { - reject(abortFetch(request, null, requestObject.signal.reason)); - return; - } - - // 7. - let responseObject = null; - // 9. - let locallyAborted = false; - // 10. - function onabort() { - locallyAborted = true; - reject( - abortFetch(request, responseObject, requestObject.signal.reason), - ); - } - requestObject.signal[abortSignal.add](onabort); + // 7. + let responseObject = null; + // 9. + let locallyAborted = false; + // 10. + function onabort() { + locallyAborted = true; + reject( + abortFetch(request, responseObject, requestObject.signal.reason), + ); + } + requestObject.signal[abortSignal.add](onabort); - if (!requestObject.headers.has("Accept")) { - ArrayPrototypePush(request.headerList, ["Accept", "*/*"]); - } + if (!requestObject.headers.has("Accept")) { + ArrayPrototypePush(request.headerList, ["Accept", "*/*"]); + } - if (!requestObject.headers.has("Accept-Language")) { - ArrayPrototypePush(request.headerList, ["Accept-Language", "*"]); - } + if (!requestObject.headers.has("Accept-Language")) { + ArrayPrototypePush(request.headerList, ["Accept-Language", "*"]); + } - // 12. - opPromise = PromisePrototypeCatch( - PromisePrototypeThen( - mainFetch(request, false, requestObject.signal), - (response) => { - // 12.1. - if (locallyAborted) return; - // 12.2. - if (response.aborted) { - reject( - abortFetch( - request, - responseObject, - requestObject.signal.reason, - ), - ); - requestObject.signal[abortSignal.remove](onabort); - return; - } - // 12.3. - if (response.type === "error") { - const err = new TypeError( - "Fetch failed: " + (response.error ?? "unknown error"), - ); - reject(err); - requestObject.signal[abortSignal.remove](onabort); - return; - } - responseObject = fromInnerResponse(response, "immutable"); - resolve(responseObject); + // 12. + opPromise = PromisePrototypeCatch( + PromisePrototypeThen( + mainFetch(request, false, requestObject.signal), + (response) => { + // 12.1. + if (locallyAborted) return; + // 12.2. + if (response.aborted) { + reject( + abortFetch( + request, + responseObject, + requestObject.signal.reason, + ), + ); requestObject.signal[abortSignal.remove](onabort); - }, - ), - (err) => { - reject(err); + return; + } + // 12.3. + if (response.type === "error") { + const err = new TypeError( + "Fetch failed: " + (response.error ?? "unknown error"), + ); + reject(err); + requestObject.signal[abortSignal.remove](onabort); + return; + } + responseObject = fromInnerResponse(response, "immutable"); + resolve(responseObject); requestObject.signal[abortSignal.remove](onabort); }, - ); - }); - if (opPromise) { - PromisePrototypeCatch(result, () => {}); - return (async function fetch() { - await opPromise; - return result; - })(); - } - return result; + ), + (err) => { + reject(err); + requestObject.signal[abortSignal.remove](onabort); + }, + ); + }); + if (opPromise) { + PromisePrototypeCatch(result, () => {}); + return (async function fetch() { + await opPromise; + return result; + })(); } + return result; +} - function abortFetch(request, responseObject, error) { - if (request.body !== null) { - if (WeakMapPrototypeHas(requestBodyReaders, request)) { - WeakMapPrototypeGet(requestBodyReaders, request).cancel(error); - } else { - request.body.cancel(error); - } - } - if (responseObject !== null) { - const response = toInnerResponse(responseObject); - if (response.body !== null) response.body.error(error); +function abortFetch(request, responseObject, error) { + if (request.body !== null) { + if (WeakMapPrototypeHas(requestBodyReaders, request)) { + WeakMapPrototypeGet(requestBodyReaders, request).cancel(error); + } else { + request.body.cancel(error); } - return error; } + if (responseObject !== null) { + const response = toInnerResponse(responseObject); + if (response.body !== null) response.body.error(error); + } + return error; +} + +/** + * Handle the Response argument to the WebAssembly streaming APIs, after + * resolving if it was passed as a promise. This function should be registered + * through `Deno.core.setWasmStreamingCallback`. + * + * @param {any} source The source parameter that the WebAssembly streaming API + * was called with. If it was called with a Promise, `source` is the resolved + * value of that promise. + * @param {number} rid An rid that represents the wasm streaming resource. + */ +function handleWasmStreaming(source, rid) { + // This implements part of + // https://webassembly.github.io/spec/web-api/#compile-a-potential-webassembly-response + try { + const res = webidl.converters["Response"](source, { + prefix: "Failed to call 'WebAssembly.compileStreaming'", + context: "Argument 1", + }); - /** - * Handle the Response argument to the WebAssembly streaming APIs, after - * resolving if it was passed as a promise. This function should be registered - * through `Deno.core.setWasmStreamingCallback`. - * - * @param {any} source The source parameter that the WebAssembly streaming API - * was called with. If it was called with a Promise, `source` is the resolved - * value of that promise. - * @param {number} rid An rid that represents the wasm streaming resource. - */ - function handleWasmStreaming(source, rid) { - // This implements part of - // https://webassembly.github.io/spec/web-api/#compile-a-potential-webassembly-response - try { - const res = webidl.converters["Response"](source, { - prefix: "Failed to call 'WebAssembly.compileStreaming'", - context: "Argument 1", - }); - - // 2.3. - // The spec is ambiguous here, see - // https://github.com/WebAssembly/spec/issues/1138. The WPT tests expect - // the raw value of the Content-Type attribute lowercased. We ignore this - // for file:// because file fetches don't have a Content-Type. - if (!StringPrototypeStartsWith(res.url, "file://")) { - const contentType = res.headers.get("Content-Type"); - if ( - typeof contentType !== "string" || - StringPrototypeToLowerCase(contentType) !== "application/wasm" - ) { - throw new TypeError("Invalid WebAssembly content type."); - } + // 2.3. + // The spec is ambiguous here, see + // https://github.com/WebAssembly/spec/issues/1138. The WPT tests expect + // the raw value of the Content-Type attribute lowercased. We ignore this + // for file:// because file fetches don't have a Content-Type. + if (!StringPrototypeStartsWith(res.url, "file://")) { + const contentType = res.headers.get("Content-Type"); + if ( + typeof contentType !== "string" || + StringPrototypeToLowerCase(contentType) !== "application/wasm" + ) { + throw new TypeError("Invalid WebAssembly content type."); } + } - // 2.5. - if (!res.ok) { - throw new TypeError(`HTTP status code ${res.status}`); - } + // 2.5. + if (!res.ok) { + throw new TypeError(`HTTP status code ${res.status}`); + } - // Pass the resolved URL to v8. - ops.op_wasm_streaming_set_url(rid, res.url); - - if (res.body !== null) { - // 2.6. - // Rather than consuming the body as an ArrayBuffer, this passes each - // chunk to the feed as soon as it's available. - PromisePrototypeThen( - (async () => { - const reader = res.body.getReader(); - while (true) { - const { value: chunk, done } = await reader.read(); - if (done) break; - ops.op_wasm_streaming_feed(rid, chunk); - } - })(), - // 2.7 - () => core.close(rid), - // 2.8 - (err) => core.abortWasmStreaming(rid, err), - ); - } else { + // Pass the resolved URL to v8. + ops.op_wasm_streaming_set_url(rid, res.url); + + if (res.body !== null) { + // 2.6. + // Rather than consuming the body as an ArrayBuffer, this passes each + // chunk to the feed as soon as it's available. + PromisePrototypeThen( + (async () => { + const reader = res.body.getReader(); + while (true) { + const { value: chunk, done } = await reader.read(); + if (done) break; + ops.op_wasm_streaming_feed(rid, chunk); + } + })(), // 2.7 - core.close(rid); - } - } catch (err) { - // 2.8 - core.abortWasmStreaming(rid, err); + () => core.close(rid), + // 2.8 + (err) => core.abortWasmStreaming(rid, err), + ); + } else { + // 2.7 + core.close(rid); } + } catch (err) { + // 2.8 + core.abortWasmStreaming(rid, err); } +} - window.__bootstrap.fetch ??= {}; - window.__bootstrap.fetch.fetch = fetch; - window.__bootstrap.fetch.handleWasmStreaming = handleWasmStreaming; -})(this); +export { fetch, handleWasmStreaming }; diff --git a/ext/fetch/Cargo.toml b/ext/fetch/Cargo.toml index 06f7c6b7743f3a..1f37526e4ea9fb 100644 --- a/ext/fetch/Cargo.toml +++ b/ext/fetch/Cargo.toml @@ -2,7 +2,7 @@ [package] name = "deno_fetch" -version = "0.113.0" +version = "0.115.0" authors.workspace = true edition.workspace = true license.workspace = true diff --git a/ext/fetch/internal.d.ts b/ext/fetch/internal.d.ts index 13a91d2d0e7de1..09a7681b01996d 100644 --- a/ext/fetch/internal.d.ts +++ b/ext/fetch/internal.d.ts @@ -5,106 +5,98 @@ /// /// -declare namespace globalThis { - declare namespace __bootstrap { - declare var fetchUtil: { - requiredArguments(name: string, length: number, required: number): void; - }; +declare var domIterable: { + DomIterableMixin(base: any, dataSymbol: symbol): any; +}; - declare var domIterable: { - DomIterableMixin(base: any, dataSymbol: symbol): any; - }; - - declare namespace headers { - class Headers { - } - type HeaderList = [string, string][]; - function headersFromHeaderList( - list: HeaderList, - guard: - | "immutable" - | "request" - | "request-no-cors" - | "response" - | "none", - ): Headers; - function headerListFromHeaders(headers: Headers): HeaderList; - function fillHeaders(headers: Headers, object: HeadersInit): void; - function getDecodeSplitHeader( - list: HeaderList, - name: string, - ): string[] | null; - function guardFromHeaders( - headers: Headers, - ): "immutable" | "request" | "request-no-cors" | "response" | "none"; - } - - declare namespace formData { - declare type FormData = typeof FormData; - declare function formDataToBlob( - formData: globalThis.FormData, - ): Blob; - declare function parseFormData( - body: Uint8Array, - boundary: string | undefined, - ): FormData; - declare function formDataFromEntries(entries: FormDataEntry[]): FormData; - } +declare module "internal:deno_fetch/20_headers.js" { + class Headers { + } + type HeaderList = [string, string][]; + function headersFromHeaderList( + list: HeaderList, + guard: + | "immutable" + | "request" + | "request-no-cors" + | "response" + | "none", + ): Headers; + function headerListFromHeaders(headers: Headers): HeaderList; + function fillHeaders(headers: Headers, object: HeadersInit): void; + function getDecodeSplitHeader( + list: HeaderList, + name: string, + ): string[] | null; + function guardFromHeaders( + headers: Headers, + ): "immutable" | "request" | "request-no-cors" | "response" | "none"; +} - declare namespace fetchBody { - function mixinBody( - prototype: any, - bodySymbol: symbol, - mimeTypeSymbol: symbol, - ): void; - class InnerBody { - constructor(stream?: ReadableStream); - stream: ReadableStream; - source: null | Uint8Array | Blob | FormData; - length: null | number; - unusable(): boolean; - consume(): Promise; - clone(): InnerBody; - } - function extractBody(object: BodyInit): { - body: InnerBody; - contentType: string | null; - }; - } +declare module "internal:deno_fetch/21_formdata.js" { + type FormData = typeof FormData; + function formDataToBlob( + formData: FormData, + ): Blob; + function parseFormData( + body: Uint8Array, + boundary: string | undefined, + ): FormData; + function formDataFromEntries(entries: FormDataEntry[]): FormData; +} - declare namespace fetch { - function toInnerRequest(request: Request): InnerRequest; - function fromInnerRequest( - inner: InnerRequest, - signal: AbortSignal | null, - guard: - | "request" - | "immutable" - | "request-no-cors" - | "response" - | "none", - skipBody: boolean, - flash: boolean, - ): Request; - function redirectStatus(status: number): boolean; - function nullBodyStatus(status: number): boolean; - function newInnerRequest( - method: string, - url: any, - headerList?: [string, string][], - body?: globalThis.__bootstrap.fetchBody.InnerBody, - ): InnerResponse; - function toInnerResponse(response: Response): InnerResponse; - function fromInnerResponse( - inner: InnerResponse, - guard: - | "request" - | "immutable" - | "request-no-cors" - | "response" - | "none", - ): Response; - function networkError(error: string): InnerResponse; - } +declare module "internal:deno_fetch/22_body.js" { + function mixinBody( + prototype: any, + bodySymbol: symbol, + mimeTypeSymbol: symbol, + ): void; + class InnerBody { + constructor(stream?: ReadableStream); + stream: ReadableStream; + source: null | Uint8Array | Blob | FormData; + length: null | number; + unusable(): boolean; + consume(): Promise; + clone(): InnerBody; } + function extractBody(object: BodyInit): { + body: InnerBody; + contentType: string | null; + }; +} + +declare module "internal:deno_fetch/26_fetch.js" { + function toInnerRequest(request: Request): InnerRequest; + function fromInnerRequest( + inner: InnerRequest, + signal: AbortSignal | null, + guard: + | "request" + | "immutable" + | "request-no-cors" + | "response" + | "none", + skipBody: boolean, + flash: boolean, + ): Request; + function redirectStatus(status: number): boolean; + function nullBodyStatus(status: number): boolean; + function newInnerRequest( + method: string, + url: any, + headerList?: [string, string][], + body?: fetchBody.InnerBody, + ): InnerResponse; + function toInnerResponse(response: Response): InnerResponse; + function fromInnerResponse( + inner: InnerResponse, + guard: + | "request" + | "immutable" + | "request-no-cors" + | "response" + | "none", + ): Response; + function networkError(error: string): InnerResponse; } diff --git a/ext/fetch/lib.rs b/ext/fetch/lib.rs index 78a42cd84814aa..766dabb620ca2c 100644 --- a/ext/fetch/lib.rs +++ b/ext/fetch/lib.rs @@ -97,9 +97,7 @@ where { Extension::builder(env!("CARGO_PKG_NAME")) .dependencies(vec!["deno_webidl", "deno_web", "deno_url", "deno_console"]) - .js(include_js_files!( - prefix "internal:ext/fetch", - "01_fetch_util.js", + .esm(include_js_files!( "20_headers.js", "21_formdata.js", "22_body.js", diff --git a/ext/ffi/00_ffi.js b/ext/ffi/00_ffi.js index ce2e7237006572..d107e89fa926ff 100644 --- a/ext/ffi/00_ffi.js +++ b/ext/ffi/00_ffi.js @@ -1,510 +1,551 @@ // Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. -"use strict"; - -((window) => { - const core = window.Deno.core; - const ops = core.ops; - const __bootstrap = window.__bootstrap; - const { - ArrayPrototypeMap, - ArrayPrototypeJoin, - ObjectDefineProperty, - ObjectPrototypeHasOwnProperty, - ObjectPrototypeIsPrototypeOf, - Number, - NumberIsSafeInteger, - TypeError, - Uint8Array, - Int32Array, - Uint32Array, - BigInt64Array, - BigUint64Array, - Function, - ReflectHas, - PromisePrototypeThen, - MathMax, - MathCeil, - SafeMap, - SafeArrayIterator, - } = window.__bootstrap.primordials; - - const U32_BUFFER = new Uint32Array(2); - const U64_BUFFER = new BigUint64Array(U32_BUFFER.buffer); - const I64_BUFFER = new BigInt64Array(U32_BUFFER.buffer); - class UnsafePointerView { - pointer; - - constructor(pointer) { - this.pointer = pointer; - } - getBool(offset = 0) { - return ops.op_ffi_read_bool( - this.pointer, - offset, - ); - } +const core = globalThis.Deno.core; +const ops = core.ops; +const internals = globalThis.__bootstrap.internals; +const primordials = globalThis.__bootstrap.primordials; +const { + ArrayPrototypeMap, + ArrayPrototypeJoin, + ObjectDefineProperty, + ObjectPrototypeHasOwnProperty, + ObjectPrototypeIsPrototypeOf, + Number, + NumberIsSafeInteger, + TypeError, + Uint8Array, + Int32Array, + Uint32Array, + BigInt64Array, + BigUint64Array, + Function, + ReflectHas, + PromisePrototypeThen, + MathMax, + MathCeil, + SafeMap, + SafeArrayIterator, + SymbolFor, +} = primordials; + +const promiseIdSymbol = SymbolFor("Deno.core.internalPromiseId"); + +const U32_BUFFER = new Uint32Array(2); +const U64_BUFFER = new BigUint64Array(U32_BUFFER.buffer); +const I64_BUFFER = new BigInt64Array(U32_BUFFER.buffer); +class UnsafePointerView { + pointer; + + constructor(pointer) { + this.pointer = pointer; + } - getUint8(offset = 0) { - return ops.op_ffi_read_u8( - this.pointer, - offset, - ); - } + getBool(offset = 0) { + return ops.op_ffi_read_bool( + this.pointer, + offset, + ); + } - getInt8(offset = 0) { - return ops.op_ffi_read_i8( - this.pointer, - offset, - ); - } + getUint8(offset = 0) { + return ops.op_ffi_read_u8( + this.pointer, + offset, + ); + } - getUint16(offset = 0) { - return ops.op_ffi_read_u16( - this.pointer, - offset, - ); - } + getInt8(offset = 0) { + return ops.op_ffi_read_i8( + this.pointer, + offset, + ); + } - getInt16(offset = 0) { - return ops.op_ffi_read_i16( - this.pointer, - offset, - ); - } + getUint16(offset = 0) { + return ops.op_ffi_read_u16( + this.pointer, + offset, + ); + } - getUint32(offset = 0) { - return ops.op_ffi_read_u32( - this.pointer, - offset, - ); - } + getInt16(offset = 0) { + return ops.op_ffi_read_i16( + this.pointer, + offset, + ); + } - getInt32(offset = 0) { - return ops.op_ffi_read_i32( - this.pointer, - offset, - ); - } + getUint32(offset = 0) { + return ops.op_ffi_read_u32( + this.pointer, + offset, + ); + } - getBigUint64(offset = 0) { - ops.op_ffi_read_u64( - this.pointer, - offset, - U32_BUFFER, - ); - return U64_BUFFER[0]; - } + getInt32(offset = 0) { + return ops.op_ffi_read_i32( + this.pointer, + offset, + ); + } - getBigInt64(offset = 0) { - ops.op_ffi_read_i64( - this.pointer, - offset, - U32_BUFFER, - ); - return I64_BUFFER[0]; - } + getBigUint64(offset = 0) { + ops.op_ffi_read_u64( + this.pointer, + offset, + U32_BUFFER, + ); + return U64_BUFFER[0]; + } - getFloat32(offset = 0) { - return ops.op_ffi_read_f32( - this.pointer, - offset, - ); - } + getBigInt64(offset = 0) { + ops.op_ffi_read_i64( + this.pointer, + offset, + U32_BUFFER, + ); + return I64_BUFFER[0]; + } - getFloat64(offset = 0) { - return ops.op_ffi_read_f64( - this.pointer, - offset, - ); - } + getFloat32(offset = 0) { + return ops.op_ffi_read_f32( + this.pointer, + offset, + ); + } - getCString(offset = 0) { - return ops.op_ffi_cstr_read( - this.pointer, - offset, - ); - } + getFloat64(offset = 0) { + return ops.op_ffi_read_f64( + this.pointer, + offset, + ); + } - static getCString(pointer, offset = 0) { - return ops.op_ffi_cstr_read( - pointer, - offset, - ); - } + getPointer(offset = 0) { + return ops.op_ffi_read_ptr( + this.pointer, + offset, + ); + } - getArrayBuffer(byteLength, offset = 0) { - return ops.op_ffi_get_buf( - this.pointer, - offset, - byteLength, - ); - } + getCString(offset = 0) { + return ops.op_ffi_cstr_read( + this.pointer, + offset, + ); + } - static getArrayBuffer(pointer, byteLength, offset = 0) { - return ops.op_ffi_get_buf( - pointer, - offset, - byteLength, - ); - } + static getCString(pointer, offset = 0) { + return ops.op_ffi_cstr_read( + pointer, + offset, + ); + } - copyInto(destination, offset = 0) { - ops.op_ffi_buf_copy_into( - this.pointer, - offset, - destination, - destination.byteLength, - ); - } + getArrayBuffer(byteLength, offset = 0) { + return ops.op_ffi_get_buf( + this.pointer, + offset, + byteLength, + ); + } - static copyInto(pointer, destination, offset = 0) { - ops.op_ffi_buf_copy_into( - pointer, - offset, - destination, - destination.byteLength, - ); - } + static getArrayBuffer(pointer, byteLength, offset = 0) { + return ops.op_ffi_get_buf( + pointer, + offset, + byteLength, + ); } - const OUT_BUFFER = new Uint32Array(2); - const OUT_BUFFER_64 = new BigInt64Array(OUT_BUFFER.buffer); - class UnsafePointer { - static of(value) { - if (ObjectPrototypeIsPrototypeOf(UnsafeCallbackPrototype, value)) { - return value.pointer; - } - ops.op_ffi_ptr_of(value, OUT_BUFFER); - const result = OUT_BUFFER[0] + 2 ** 32 * OUT_BUFFER[1]; - if (NumberIsSafeInteger(result)) { - return result; - } - return OUT_BUFFER_64[0]; - } + copyInto(destination, offset = 0) { + ops.op_ffi_buf_copy_into( + this.pointer, + offset, + destination, + destination.byteLength, + ); } - class UnsafeFnPointer { - pointer; - definition; - #structSize; + static copyInto(pointer, destination, offset = 0) { + ops.op_ffi_buf_copy_into( + pointer, + offset, + destination, + destination.byteLength, + ); + } +} - constructor(pointer, definition) { - this.pointer = pointer; - this.definition = definition; - this.#structSize = isStruct(definition.result) - ? getTypeSizeAndAlignment(definition.result)[0] - : null; - } +const OUT_BUFFER = new Uint32Array(2); +const OUT_BUFFER_64 = new BigInt64Array(OUT_BUFFER.buffer); +class UnsafePointer { + static create(value) { + return ops.op_ffi_ptr_create(value); + } - call(...parameters) { - if (this.definition.nonblocking) { - if (this.#structSize === null) { - return core.opAsync( - "op_ffi_call_ptr_nonblocking", - this.pointer, - this.definition, - parameters, - ); - } else { - const buffer = new Uint8Array(this.#structSize); - return PromisePrototypeThen( - core.opAsync( - "op_ffi_call_ptr_nonblocking", - this.pointer, - this.definition, - parameters, - buffer, - ), - () => buffer, - ); - } - } else { - if (this.#structSize === null) { - return ops.op_ffi_call_ptr( - this.pointer, - this.definition, - parameters, - ); - } else { - const buffer = new Uint8Array(this.#structSize); - ops.op_ffi_call_ptr( - this.pointer, - this.definition, - parameters, - buffer, - ); - return buffer; - } - } + static equals(a, b) { + if (a === null || b === null) { + return a === b; } + return ops.op_ffi_ptr_equals(a, b); } - function isReturnedAsBigInt(type) { - return type === "buffer" || type === "pointer" || type === "function" || - type === "u64" || type === "i64" || - type === "usize" || type === "isize"; + static of(value) { + if (ObjectPrototypeIsPrototypeOf(UnsafeCallbackPrototype, value)) { + return value.pointer; + } + return ops.op_ffi_ptr_of(value); } - function isI64(type) { - return type === "i64" || type === "isize"; + static offset(value, offset) { + return ops.op_ffi_ptr_offset(value, offset); } - function isStruct(type) { - return typeof type === "object" && type !== null && - typeof type.struct === "object"; + static value(value) { + if (ObjectPrototypeIsPrototypeOf(UnsafeCallbackPrototype, value)) { + value = value.pointer; + } + ops.op_ffi_ptr_value(value, OUT_BUFFER); + const result = OUT_BUFFER[0] + 2 ** 32 * OUT_BUFFER[1]; + if (NumberIsSafeInteger(result)) { + return result; + } + return OUT_BUFFER_64[0]; + } +} + +class UnsafeFnPointer { + pointer; + definition; + #structSize; + + constructor(pointer, definition) { + this.pointer = pointer; + this.definition = definition; + this.#structSize = isStruct(definition.result) + ? getTypeSizeAndAlignment(definition.result)[0] + : null; } - function getTypeSizeAndAlignment(type, cache = new SafeMap()) { - if (isStruct(type)) { - const cached = cache.get(type); - if (cached !== undefined) { - if (cached === null) { - throw new TypeError("Recursive struct definition"); - } - return cached; + call(...parameters) { + if (this.definition.nonblocking) { + if (this.#structSize === null) { + return core.opAsync( + "op_ffi_call_ptr_nonblocking", + this.pointer, + this.definition, + parameters, + ); + } else { + const buffer = new Uint8Array(this.#structSize); + return PromisePrototypeThen( + core.opAsync( + "op_ffi_call_ptr_nonblocking", + this.pointer, + this.definition, + parameters, + buffer, + ), + () => buffer, + ); } - cache.set(type, null); - let size = 0; - let alignment = 1; - for (const field of new SafeArrayIterator(type.struct)) { - const { 0: fieldSize, 1: fieldAlign } = getTypeSizeAndAlignment( - field, - cache, + } else { + if (this.#structSize === null) { + return ops.op_ffi_call_ptr( + this.pointer, + this.definition, + parameters, ); - alignment = MathMax(alignment, fieldAlign); - size = MathCeil(size / fieldAlign) * fieldAlign; - size += fieldSize; + } else { + const buffer = new Uint8Array(this.#structSize); + ops.op_ffi_call_ptr( + this.pointer, + this.definition, + parameters, + buffer, + ); + return buffer; } - size = MathCeil(size / alignment) * alignment; - cache.set(type, size); - return [size, alignment]; } - - switch (type) { - case "bool": - case "u8": - case "i8": - return [1, 1]; - case "u16": - case "i16": - return [2, 2]; - case "u32": - case "i32": - case "f32": - return [4, 4]; - case "u64": - case "i64": - case "f64": - case "pointer": - case "buffer": - case "function": - case "usize": - case "isize": - return [8, 8]; - default: - throw new TypeError(`Unsupported type: ${type}`); + } +} + +function isReturnedAsBigInt(type) { + return type === "u64" || type === "i64" || + type === "usize" || type === "isize"; +} + +function isI64(type) { + return type === "i64" || type === "isize"; +} + +function isStruct(type) { + return typeof type === "object" && type !== null && + typeof type.struct === "object"; +} + +function getTypeSizeAndAlignment(type, cache = new SafeMap()) { + if (isStruct(type)) { + const cached = cache.get(type); + if (cached !== undefined) { + if (cached === null) { + throw new TypeError("Recursive struct definition"); + } + return cached; + } + cache.set(type, null); + let size = 0; + let alignment = 1; + for (const field of new SafeArrayIterator(type.struct)) { + const { 0: fieldSize, 1: fieldAlign } = getTypeSizeAndAlignment( + field, + cache, + ); + alignment = MathMax(alignment, fieldAlign); + size = MathCeil(size / fieldAlign) * fieldAlign; + size += fieldSize; } + size = MathCeil(size / alignment) * alignment; + cache.set(type, size); + return [size, alignment]; } - class UnsafeCallback { - #refcount; - // Internal promise only meant to keep Deno from exiting - #refpromise; - #rid; - definition; - callback; - pointer; - - constructor(definition, callback) { - if (definition.nonblocking) { - throw new TypeError( - "Invalid UnsafeCallback, cannot be nonblocking", - ); - } - const { 0: rid, 1: pointer } = ops.op_ffi_unsafe_callback_create( - definition, - callback, + switch (type) { + case "bool": + case "u8": + case "i8": + return [1, 1]; + case "u16": + case "i16": + return [2, 2]; + case "u32": + case "i32": + case "f32": + return [4, 4]; + case "u64": + case "i64": + case "f64": + case "pointer": + case "buffer": + case "function": + case "usize": + case "isize": + return [8, 8]; + default: + throw new TypeError(`Unsupported type: ${type}`); + } +} + +class UnsafeCallback { + #refcount; + // Internal promise only meant to keep Deno from exiting + #refpromise; + #rid; + definition; + callback; + pointer; + + constructor(definition, callback) { + if (definition.nonblocking) { + throw new TypeError( + "Invalid UnsafeCallback, cannot be nonblocking", ); - this.#refcount = 0; - this.#rid = rid; - this.pointer = pointer; - this.definition = definition; - this.callback = callback; } + const { 0: rid, 1: pointer } = ops.op_ffi_unsafe_callback_create( + definition, + callback, + ); + this.#refcount = 0; + this.#rid = rid; + this.pointer = pointer; + this.definition = definition; + this.callback = callback; + } - ref() { - if (this.#refcount++ === 0) { + static threadSafe(definition, callback) { + const unsafeCallback = new UnsafeCallback(definition, callback); + unsafeCallback.ref(); + return unsafeCallback; + } + + ref() { + if (this.#refcount++ === 0) { + if (this.#refpromise) { + // Re-refing + core.refOp(this.#refpromise[promiseIdSymbol]); + } else { this.#refpromise = core.opAsync( "op_ffi_unsafe_callback_ref", this.#rid, ); } - return this.#refcount; } + return this.#refcount; + } - unref() { - // Only decrement refcount if it is positive, and only - // unref the callback if refcount reaches zero. - if (this.#refcount > 0 && --this.#refcount === 0) { - ops.op_ffi_unsafe_callback_unref(this.#rid); - } - return this.#refcount; + unref() { + // Only decrement refcount if it is positive, and only + // unref the callback if refcount reaches zero. + if (this.#refcount > 0 && --this.#refcount === 0) { + core.unrefOp(this.#refpromise[promiseIdSymbol]); } + return this.#refcount; + } - close() { - this.#refcount = 0; - core.close(this.#rid); - } + close() { + this.#refcount = 0; + core.close(this.#rid); } +} - const UnsafeCallbackPrototype = UnsafeCallback.prototype; +const UnsafeCallbackPrototype = UnsafeCallback.prototype; - class DynamicLibrary { - #rid; - symbols = {}; +class DynamicLibrary { + #rid; + symbols = {}; - constructor(path, symbols) { - ({ 0: this.#rid, 1: this.symbols } = ops.op_ffi_load({ path, symbols })); - for (const symbol in symbols) { - if (!ObjectPrototypeHasOwnProperty(symbols, symbol)) { - continue; - } + constructor(path, symbols) { + ({ 0: this.#rid, 1: this.symbols } = ops.op_ffi_load({ path, symbols })); + for (const symbol in symbols) { + if (!ObjectPrototypeHasOwnProperty(symbols, symbol)) { + continue; + } - if (ReflectHas(symbols[symbol], "type")) { - const type = symbols[symbol].type; - if (type === "void") { - throw new TypeError( - "Foreign symbol of type 'void' is not supported.", - ); - } - - const name = symbols[symbol].name || symbol; - const value = ops.op_ffi_get_static( - this.#rid, - name, - type, - ); - ObjectDefineProperty( - this.symbols, - symbol, - { - configurable: false, - enumerable: true, - value, - writable: false, - }, + if (ReflectHas(symbols[symbol], "type")) { + const type = symbols[symbol].type; + if (type === "void") { + throw new TypeError( + "Foreign symbol of type 'void' is not supported.", ); - continue; } - const resultType = symbols[symbol].result; - const isStructResult = isStruct(resultType); - const structSize = isStructResult - ? getTypeSizeAndAlignment(resultType)[0] - : 0; - const needsUnpacking = isReturnedAsBigInt(resultType); - - const isNonBlocking = symbols[symbol].nonblocking; - if (isNonBlocking) { - ObjectDefineProperty( - this.symbols, - symbol, - { - configurable: false, - enumerable: true, - value: (...parameters) => { - if (isStructResult) { - const buffer = new Uint8Array(structSize); - const ret = core.opAsync( - "op_ffi_call_nonblocking", - this.#rid, - symbol, - parameters, - buffer, - ); - return PromisePrototypeThen( - ret, - () => buffer, - ); - } else { - return core.opAsync( - "op_ffi_call_nonblocking", - this.#rid, - symbol, - parameters, - ); - } - }, - writable: false, + + const name = symbols[symbol].name || symbol; + const value = ops.op_ffi_get_static( + this.#rid, + name, + type, + ); + ObjectDefineProperty( + this.symbols, + symbol, + { + configurable: false, + enumerable: true, + value, + writable: false, + }, + ); + continue; + } + const resultType = symbols[symbol].result; + const isStructResult = isStruct(resultType); + const structSize = isStructResult + ? getTypeSizeAndAlignment(resultType)[0] + : 0; + const needsUnpacking = isReturnedAsBigInt(resultType); + + const isNonBlocking = symbols[symbol].nonblocking; + if (isNonBlocking) { + ObjectDefineProperty( + this.symbols, + symbol, + { + configurable: false, + enumerable: true, + value: (...parameters) => { + if (isStructResult) { + const buffer = new Uint8Array(structSize); + const ret = core.opAsync( + "op_ffi_call_nonblocking", + this.#rid, + symbol, + parameters, + buffer, + ); + return PromisePrototypeThen( + ret, + () => buffer, + ); + } else { + return core.opAsync( + "op_ffi_call_nonblocking", + this.#rid, + symbol, + parameters, + ); + } }, - ); - } + writable: false, + }, + ); + } - if (needsUnpacking && !isNonBlocking) { - const call = this.symbols[symbol]; - const parameters = symbols[symbol].parameters; - const vi = new Int32Array(2); - const vui = new Uint32Array(vi.buffer); - const b = new BigInt64Array(vi.buffer); + if (needsUnpacking && !isNonBlocking) { + const call = this.symbols[symbol]; + const parameters = symbols[symbol].parameters; + const vi = new Int32Array(2); + const vui = new Uint32Array(vi.buffer); + const b = new BigInt64Array(vi.buffer); - const params = ArrayPrototypeJoin( - ArrayPrototypeMap(parameters, (_, index) => `p${index}`), - ", ", - ); - // Make sure V8 has no excuse to not optimize this function. - this.symbols[symbol] = new Function( - "vi", - "vui", - "b", - "call", - "NumberIsSafeInteger", - "Number", - `return function (${params}) { + const params = ArrayPrototypeJoin( + ArrayPrototypeMap(parameters, (_, index) => `p${index}`), + ", ", + ); + // Make sure V8 has no excuse to not optimize this function. + this.symbols[symbol] = new Function( + "vi", + "vui", + "b", + "call", + "NumberIsSafeInteger", + "Number", + `return function (${params}) { call(${params}${parameters.length > 0 ? ", " : ""}vi); ${ - isI64(resultType) - ? `const n1 = Number(b[0])` - : `const n1 = vui[0] + 2 ** 32 * vui[1]` // Faster path for u64 - }; + isI64(resultType) + ? `const n1 = Number(b[0])` + : `const n1 = vui[0] + 2 ** 32 * vui[1]` // Faster path for u64 + }; if (NumberIsSafeInteger(n1)) return n1; return b[0]; }`, - )(vi, vui, b, call, NumberIsSafeInteger, Number); - } else if (isStructResult && !isNonBlocking) { - const call = this.symbols[symbol]; - const parameters = symbols[symbol].parameters; - const params = ArrayPrototypeJoin( - ArrayPrototypeMap(parameters, (_, index) => `p${index}`), - ", ", - ); - this.symbols[symbol] = new Function( - "call", - `return function (${params}) { + )(vi, vui, b, call, NumberIsSafeInteger, Number); + } else if (isStructResult && !isNonBlocking) { + const call = this.symbols[symbol]; + const parameters = symbols[symbol].parameters; + const params = ArrayPrototypeJoin( + ArrayPrototypeMap(parameters, (_, index) => `p${index}`), + ", ", + ); + this.symbols[symbol] = new Function( + "call", + `return function (${params}) { const buffer = new Uint8Array(${structSize}); call(${params}${parameters.length > 0 ? ", " : ""}buffer); return buffer; }`, - )(call); - } + )(call); } } - - close() { - core.close(this.#rid); - } } - function dlopen(path, symbols) { - // URL support is progressively enhanced by util in `runtime/js`. - const pathFromURL = __bootstrap.util.pathFromURL ?? ((p) => p); - return new DynamicLibrary(pathFromURL(path), symbols); + close() { + core.close(this.#rid); } - - window.__bootstrap.ffi = { - dlopen, - UnsafeCallback, - UnsafePointer, - UnsafePointerView, - UnsafeFnPointer, - }; -})(this); +} + +function dlopen(path, symbols) { + // TODO(@crowlKats): remove me + // URL support is progressively enhanced by util in `runtime/js`. + const pathFromURL = internals.pathFromURL ?? ((p) => p); + return new DynamicLibrary(pathFromURL(path), symbols); +} + +export { + dlopen, + UnsafeCallback, + UnsafeFnPointer, + UnsafePointer, + UnsafePointerView, +}; diff --git a/ext/ffi/Cargo.toml b/ext/ffi/Cargo.toml index 2c09ef115e8d3e..ed69c920131e78 100644 --- a/ext/ffi/Cargo.toml +++ b/ext/ffi/Cargo.toml @@ -2,7 +2,7 @@ [package] name = "deno_ffi" -version = "0.76.0" +version = "0.78.0" authors.workspace = true edition.workspace = true license.workspace = true @@ -19,6 +19,8 @@ dlopen.workspace = true dynasmrt = "1.2.3" libffi = "3.1.0" serde.workspace = true +serde-value = "0.7" +serde_json = "1.0" tokio.workspace = true [target.'cfg(windows)'.dependencies] diff --git a/ext/ffi/call.rs b/ext/ffi/call.rs index 731460af94aa8a..8a9b393c397aff 100644 --- a/ext/ffi/call.rs +++ b/ext/ffi/call.rs @@ -14,9 +14,11 @@ use deno_core::error::AnyError; use deno_core::op; use deno_core::serde_json::Value; use deno_core::serde_v8; +use deno_core::serde_v8::ExternalPointer; use deno_core::v8; use deno_core::ResourceId; use libffi::middle::Arg; +use serde::Serialize; use std::cell::RefCell; use std::ffi::c_void; use std::future::Future; @@ -28,14 +30,13 @@ unsafe fn ffi_call_rtype_struct( fn_ptr: &libffi::middle::CodePtr, call_args: Vec, out_buffer: *mut u8, -) -> NativeValue { +) { libffi::raw::ffi_call( cif.as_raw_ptr(), Some(*fn_ptr.as_safe_fun()), out_buffer as *mut c_void, call_args.as_ptr() as *mut *mut c_void, ); - NativeValue { void_value: () } } // A one-off synchronous FFI call. @@ -174,16 +175,25 @@ where pointer: cif.call::<*mut c_void>(*fun_ptr, &call_args), } } - NativeType::Struct(_) => ffi_call_rtype_struct( - &symbol.cif, - &symbol.ptr, - call_args, - out_buffer.unwrap().0, - ), + NativeType::Struct(_) => NativeValue { + void_value: ffi_call_rtype_struct( + &symbol.cif, + &symbol.ptr, + call_args, + out_buffer.unwrap().0, + ), + }, }) } } +#[derive(Serialize)] +#[serde(untagged)] +pub enum FfiValue { + Value(Value), + External(ExternalPointer), +} + fn ffi_call( call_args: Vec, cif: &libffi::middle::Cif, @@ -191,7 +201,7 @@ fn ffi_call( parameter_types: &[NativeType], result_type: NativeType, out_buffer: Option, -) -> Result { +) -> Result { let call_args: Vec = call_args .iter() .enumerate() @@ -205,55 +215,57 @@ fn ffi_call( // types of symbol. unsafe { Ok(match result_type { - NativeType::Void => NativeValue { - void_value: cif.call::<()>(fun_ptr, &call_args), - }, - NativeType::Bool => NativeValue { - bool_value: cif.call::(fun_ptr, &call_args), - }, - NativeType::U8 => NativeValue { - u8_value: cif.call::(fun_ptr, &call_args), - }, - NativeType::I8 => NativeValue { - i8_value: cif.call::(fun_ptr, &call_args), - }, - NativeType::U16 => NativeValue { - u16_value: cif.call::(fun_ptr, &call_args), - }, - NativeType::I16 => NativeValue { - i16_value: cif.call::(fun_ptr, &call_args), - }, - NativeType::U32 => NativeValue { - u32_value: cif.call::(fun_ptr, &call_args), - }, - NativeType::I32 => NativeValue { - i32_value: cif.call::(fun_ptr, &call_args), - }, - NativeType::U64 => NativeValue { - u64_value: cif.call::(fun_ptr, &call_args), - }, - NativeType::I64 => NativeValue { - i64_value: cif.call::(fun_ptr, &call_args), - }, - NativeType::USize => NativeValue { - usize_value: cif.call::(fun_ptr, &call_args), - }, - NativeType::ISize => NativeValue { - isize_value: cif.call::(fun_ptr, &call_args), - }, - NativeType::F32 => NativeValue { - f32_value: cif.call::(fun_ptr, &call_args), - }, - NativeType::F64 => NativeValue { - f64_value: cif.call::(fun_ptr, &call_args), - }, + NativeType::Void => { + cif.call::<()>(fun_ptr, &call_args); + FfiValue::Value(Value::from(())) + } + NativeType::Bool => { + FfiValue::Value(Value::from(cif.call::(fun_ptr, &call_args))) + } + NativeType::U8 => { + FfiValue::Value(Value::from(cif.call::(fun_ptr, &call_args))) + } + NativeType::I8 => { + FfiValue::Value(Value::from(cif.call::(fun_ptr, &call_args))) + } + NativeType::U16 => { + FfiValue::Value(Value::from(cif.call::(fun_ptr, &call_args))) + } + NativeType::I16 => { + FfiValue::Value(Value::from(cif.call::(fun_ptr, &call_args))) + } + NativeType::U32 => { + FfiValue::Value(Value::from(cif.call::(fun_ptr, &call_args))) + } + NativeType::I32 => { + FfiValue::Value(Value::from(cif.call::(fun_ptr, &call_args))) + } + NativeType::U64 => { + FfiValue::Value(Value::from(cif.call::(fun_ptr, &call_args))) + } + NativeType::I64 => { + FfiValue::Value(Value::from(cif.call::(fun_ptr, &call_args))) + } + NativeType::USize => { + FfiValue::Value(Value::from(cif.call::(fun_ptr, &call_args))) + } + NativeType::ISize => { + FfiValue::Value(Value::from(cif.call::(fun_ptr, &call_args))) + } + NativeType::F32 => { + FfiValue::Value(Value::from(cif.call::(fun_ptr, &call_args))) + } + NativeType::F64 => { + FfiValue::Value(Value::from(cif.call::(fun_ptr, &call_args))) + } NativeType::Pointer | NativeType::Function | NativeType::Buffer => { - NativeValue { - pointer: cif.call::<*mut c_void>(fun_ptr, &call_args), - } + FfiValue::External(ExternalPointer::from( + cif.call::<*mut c_void>(fun_ptr, &call_args), + )) } NativeType::Struct(_) => { - ffi_call_rtype_struct(cif, &fun_ptr, call_args, out_buffer.unwrap().0) + ffi_call_rtype_struct(cif, &fun_ptr, call_args, out_buffer.unwrap().0); + FfiValue::Value(Value::Null) } }) } @@ -263,11 +275,11 @@ fn ffi_call( pub fn op_ffi_call_ptr_nonblocking<'scope, FP>( scope: &mut v8::HandleScope<'scope>, state: Rc>, - pointer: usize, + pointer: *mut c_void, def: ForeignFunction, parameters: serde_v8::Value<'scope>, out_buffer: Option>, -) -> Result>, AnyError> +) -> Result>, AnyError> where FP: FfiPermissions + 'static, { @@ -280,7 +292,6 @@ where let symbol = PtrSymbol::new(pointer, &def)?; let call_args = ffi_parse_args(scope, parameters, &def.parameters)?; - let def_result = def.result.clone(); let out_buffer = out_buffer .map(|v| v8::Local::::try_from(v.v8_value).unwrap()); @@ -303,7 +314,7 @@ where .await .map_err(|err| anyhow!("Nonblocking FFI call failed: {}", err))??; // SAFETY: Same return type declared to libffi; trust user to have it right beyond that. - Ok(unsafe { result.to_value(def_result) }) + Ok(result) }) } @@ -316,7 +327,8 @@ pub fn op_ffi_call_nonblocking<'scope>( symbol: String, parameters: serde_v8::Value<'scope>, out_buffer: Option>, -) -> Result> + 'static, AnyError> { +) -> Result> + 'static, AnyError> +{ let symbol = { let state = state.borrow(); let resource = state.resource_table.get::(rid)?; @@ -332,7 +344,6 @@ pub fn op_ffi_call_nonblocking<'scope>( .map(|v| v8::Local::::try_from(v.v8_value).unwrap()); let out_buffer_ptr = out_buffer_as_ptr(scope, out_buffer); - let result_type = symbol.result_type.clone(); let join_handle = tokio::task::spawn_blocking(move || { let Symbol { cif, @@ -356,7 +367,7 @@ pub fn op_ffi_call_nonblocking<'scope>( .await .map_err(|err| anyhow!("Nonblocking FFI call failed: {}", err))??; // SAFETY: Same return type declared to libffi; trust user to have it right beyond that. - Ok(unsafe { result.to_value(result_type) }) + Ok(result) }) } @@ -364,11 +375,11 @@ pub fn op_ffi_call_nonblocking<'scope>( pub fn op_ffi_call_ptr( scope: &mut v8::HandleScope<'scope>, state: Rc>, - pointer: usize, + pointer: *mut c_void, def: ForeignFunction, parameters: serde_v8::Value<'scope>, out_buffer: Option>, -) -> Result, AnyError> +) -> Result where FP: FfiPermissions + 'static, { @@ -395,6 +406,5 @@ where out_buffer_ptr, )?; // SAFETY: Same return type declared to libffi; trust user to have it right beyond that. - let result = unsafe { result.to_v8(scope, def.result) }; Ok(result) } diff --git a/ext/ffi/callback.rs b/ext/ffi/callback.rs index 1558d950ee68ef..ae2780391b027b 100644 --- a/ext/ffi/callback.rs +++ b/ext/ffi/callback.rs @@ -40,7 +40,10 @@ pub struct PtrSymbol { } impl PtrSymbol { - pub fn new(fn_ptr: usize, def: &ForeignFunction) -> Result { + pub fn new( + fn_ptr: *mut c_void, + def: &ForeignFunction, + ) -> Result { let ptr = libffi::middle::CodePtr::from_ptr(fn_ptr as _); let cif = libffi::middle::Cif::new( def @@ -236,11 +239,11 @@ unsafe fn do_ffi_callback( } } NativeType::Pointer | NativeType::Buffer | NativeType::Function => { - let result = *((*val) as *const usize); - if result > MAX_SAFE_INTEGER as usize { - v8::BigInt::new_from_u64(scope, result as u64).into() + let result = *((*val) as *const *mut c_void); + if result.is_null() { + v8::null(scope).into() } else { - v8::Number::new(scope, result as f64).into() + v8::External::new(scope, result).into() } } NativeType::Struct(_) => { @@ -353,34 +356,43 @@ unsafe fn do_ffi_callback( }; *(result as *mut f64) = value; } - NativeType::Pointer | NativeType::Buffer | NativeType::Function => { - let pointer = if let Ok(value) = + NativeType::Buffer => { + let pointer: *mut u8 = if let Ok(value) = v8::Local::::try_from(value) { let byte_offset = value.byte_offset(); - let backing_store = value + let pointer = value .buffer(scope) .expect("Unable to deserialize result parameter.") - .get_backing_store(); - &backing_store[byte_offset..] as *const _ as *const u8 - } else if let Ok(value) = v8::Local::::try_from(value) { - value.u64_value().0 as usize as *const u8 + .data(); + if let Some(non_null) = pointer { + // SAFETY: Pointer is non-null, and V8 guarantees that the byte_offset + // is within the buffer backing store. + unsafe { non_null.as_ptr().add(byte_offset) as *mut u8 } + } else { + ptr::null_mut() + } } else if let Ok(value) = v8::Local::::try_from(value) { - let backing_store = value.get_backing_store(); - &backing_store[..] as *const _ as *const u8 - } else if let Ok(value) = v8::Local::::try_from(value) { - value.value() as usize as *const u8 - } else if value.is_null() { - ptr::null() + let pointer = value.data(); + if let Some(non_null) = pointer { + non_null.as_ptr() as *mut u8 + } else { + ptr::null_mut() + } } else { - // Fallthrough: Probably someone returned a number but this could - // also be eg. a string. This is essentially UB. - value - .integer_value(scope) - .expect("Unable to deserialize result parameter.") as usize - as *const u8 + ptr::null_mut() }; - *(result as *mut *const u8) = pointer; + *(result as *mut *mut u8) = pointer; + } + NativeType::Pointer | NativeType::Function => { + let pointer: *mut c_void = + if let Ok(external) = v8::Local::::try_from(value) { + external.value() + } else { + // TODO(@aapoalas): Start throwing errors into JS about invalid callback return values. + ptr::null_mut() + }; + *(result as *mut *mut c_void) = pointer; } NativeType::I8 => { let value = if let Ok(value) = v8::Local::::try_from(value) { @@ -520,19 +532,6 @@ pub fn op_ffi_unsafe_callback_ref( }) } -#[op(fast)] -pub fn op_ffi_unsafe_callback_unref( - state: &mut deno_core::OpState, - rid: u32, -) -> Result<(), AnyError> { - state - .resource_table - .get::(rid)? - .cancel - .cancel(); - Ok(()) -} - #[derive(Deserialize)] pub struct RegisterCallbackArgs { parameters: Vec, @@ -591,7 +590,7 @@ where let closure = libffi::middle::Closure::new(cif, deno_ffi_callback, unsafe { info.as_ref().unwrap() }); - let ptr = *closure.code_ptr() as usize; + let ptr = *closure.code_ptr() as *mut c_void; let resource = UnsafeCallbackResource { cancel: CancelHandle::new_rc(), closure, @@ -600,11 +599,7 @@ where let rid = state.resource_table.add(resource); let rid_local = v8::Integer::new_from_unsigned(scope, rid); - let ptr_local: v8::Local = if ptr > MAX_SAFE_INTEGER as usize { - v8::BigInt::new_from_u64(scope, ptr as u64).into() - } else { - v8::Number::new(scope, ptr as f64).into() - }; + let ptr_local: v8::Local = v8::External::new(scope, ptr).into(); let array = v8::Array::new(scope, 2); array.set_index(scope, 0, rid_local.into()); array.set_index(scope, 1, ptr_local); diff --git a/ext/ffi/dlfcn.rs b/ext/ffi/dlfcn.rs index 570af09fce215d..cb5009de71fdf0 100644 --- a/ext/ffi/dlfcn.rs +++ b/ext/ffi/dlfcn.rs @@ -15,6 +15,7 @@ use deno_core::Resource; use deno_core::ResourceId; use dlopen::raw::Library; use serde::Deserialize; +use serde_value::ValueDeserializer; use std::borrow::Cow; use std::collections::HashMap; use std::ffi::c_void; @@ -37,13 +38,13 @@ impl Resource for DynamicLibraryResource { } impl DynamicLibraryResource { - pub fn get_static(&self, symbol: String) -> Result<*const c_void, AnyError> { + pub fn get_static(&self, symbol: String) -> Result<*mut c_void, AnyError> { // By default, Err returned by this function does not tell // which symbol wasn't exported. So we'll modify the error // message to include the name of symbol. // // SAFETY: The obtained T symbol is the size of a pointer. - match unsafe { self.lib.symbol::<*const c_void>(&symbol) } { + match unsafe { self.lib.symbol::<*mut c_void>(&symbol) } { Ok(value) => Ok(Ok(value)), Err(err) => Err(generic_error(format!( "Failed to register symbol {symbol}: {err}" @@ -55,13 +56,7 @@ impl DynamicLibraryResource { pub fn needs_unwrap(rv: &NativeType) -> bool { matches!( rv, - NativeType::Function - | NativeType::Pointer - | NativeType::Buffer - | NativeType::I64 - | NativeType::ISize - | NativeType::U64 - | NativeType::USize + NativeType::I64 | NativeType::ISize | NativeType::U64 | NativeType::USize ) } @@ -97,13 +92,31 @@ struct ForeignStatic { _type: String, } -#[derive(Deserialize, Debug)] -#[serde(untagged)] +#[derive(Debug)] enum ForeignSymbol { ForeignFunction(ForeignFunction), ForeignStatic(ForeignStatic), } +impl<'de> Deserialize<'de> for ForeignSymbol { + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + let value = serde_value::Value::deserialize(deserializer)?; + + // Probe a ForeignStatic and if that doesn't match, assume ForeignFunction to improve error messages + if let Ok(res) = ForeignStatic::deserialize( + ValueDeserializer::::new(value.clone()), + ) { + Ok(ForeignSymbol::ForeignStatic(res)) + } else { + ForeignFunction::deserialize(ValueDeserializer::::new(value)) + .map(ForeignSymbol::ForeignFunction) + } + } +} + #[derive(Deserialize, Debug)] pub struct FfiLoadArgs { path: String, @@ -238,26 +251,20 @@ fn make_sync_fn<'s>( match needs_unwrap { Some(v) => { let view: v8::Local = v.try_into().unwrap(); - let backing_store = - view.buffer(scope).unwrap().get_backing_store(); + let pointer = + view.buffer(scope).unwrap().data().unwrap().as_ptr() as *mut u8; if is_i64(&symbol.result_type) { // SAFETY: v8::SharedRef is similar to Arc<[u8]>, // it points to a fixed continuous slice of bytes on the heap. - let bs = unsafe { - &mut *(&backing_store[..] as *const _ as *mut [u8] - as *mut i64) - }; + let bs = unsafe { &mut *(pointer as *mut i64) }; // SAFETY: We already checked that type == I64 let value = unsafe { result.i64_value }; *bs = value; } else { // SAFETY: v8::SharedRef is similar to Arc<[u8]>, // it points to a fixed continuous slice of bytes on the heap. - let bs = unsafe { - &mut *(&backing_store[..] as *const _ as *mut [u8] - as *mut u64) - }; + let bs = unsafe { &mut *(pointer as *mut u64) }; // SAFETY: We checked that type == U64 let value = unsafe { result.u64_value }; *bs = value; @@ -395,6 +402,11 @@ pub(crate) fn format_error(e: dlopen::Error, path: String) -> String { #[cfg(test)] mod tests { + use super::ForeignFunction; + use super::ForeignSymbol; + use crate::symbol::NativeType; + use serde_json::json; + #[cfg(target_os = "windows")] #[test] fn test_format_error() { @@ -409,4 +421,52 @@ mod tests { "foo.dll is not a valid Win32 application.\r\n".to_string(), ); } + + /// Ensure that our custom serialize for ForeignSymbol is working using `serde_json`. + #[test] + fn test_serialize_foreign_symbol() { + let symbol: ForeignSymbol = serde_json::from_value(json! {{ + "name": "test", + "type": "type is unused" + }}) + .expect("Failed to parse"); + assert!(matches!(symbol, ForeignSymbol::ForeignStatic(..))); + + let symbol: ForeignSymbol = serde_json::from_value(json! {{ + "name": "test", + "parameters": ["i64"], + "result": "bool" + }}) + .expect("Failed to parse"); + if let ForeignSymbol::ForeignFunction(ForeignFunction { + name: Some(expected_name), + parameters, + .. + }) = symbol + { + assert_eq!(expected_name, "test"); + assert_eq!(parameters, vec![NativeType::I64]); + } else { + panic!("Failed to parse ForeignFunction as expected"); + } + } + + #[test] + fn test_serialize_foreign_symbol_failures() { + let error = serde_json::from_value::(json! {{ + "name": "test", + "parameters": ["int"], + "result": "bool" + }}) + .expect_err("Expected this to fail"); + assert!(error.to_string().contains("expected one of")); + + let error = serde_json::from_value::(json! {{ + "name": "test", + "parameters": ["i64"], + "result": "int" + }}) + .expect_err("Expected this to fail"); + assert!(error.to_string().contains("expected one of")); + } } diff --git a/ext/ffi/ir.rs b/ext/ffi/ir.rs index 80f727cd20efe0..8ca96a280ca380 100644 --- a/ext/ffi/ir.rs +++ b/ext/ffi/ir.rs @@ -5,7 +5,6 @@ use crate::MAX_SAFE_INTEGER; use crate::MIN_SAFE_INTEGER; use deno_core::error::type_error; use deno_core::error::AnyError; -use deno_core::serde_json::Value; use deno_core::serde_v8; use deno_core::v8; use libffi::middle::Arg; @@ -80,33 +79,6 @@ impl NativeValue { } } - // SAFETY: native_type must correspond to the type of value represented by the union field - pub unsafe fn to_value(&self, native_type: NativeType) -> Value { - match native_type { - NativeType::Void => Value::Null, - NativeType::Bool => Value::from(self.bool_value), - NativeType::U8 => Value::from(self.u8_value), - NativeType::I8 => Value::from(self.i8_value), - NativeType::U16 => Value::from(self.u16_value), - NativeType::I16 => Value::from(self.i16_value), - NativeType::U32 => Value::from(self.u32_value), - NativeType::I32 => Value::from(self.i32_value), - NativeType::U64 => Value::from(self.u64_value), - NativeType::I64 => Value::from(self.i64_value), - NativeType::USize => Value::from(self.usize_value), - NativeType::ISize => Value::from(self.isize_value), - NativeType::F32 => Value::from(self.f32_value), - NativeType::F64 => Value::from(self.f64_value), - NativeType::Pointer | NativeType::Function | NativeType::Buffer => { - Value::from(self.pointer as usize) - } - NativeType::Struct(_) => { - // Return value is written to out_buffer - Value::Null - } - } - } - // SAFETY: native_type must correspond to the type of value represented by the union field #[inline] pub unsafe fn to_v8<'scope>( @@ -206,13 +178,11 @@ impl NativeValue { local_value.into() } NativeType::Pointer | NativeType::Buffer | NativeType::Function => { - let value = self.pointer as u64; - let local_value: v8::Local = - if value > MAX_SAFE_INTEGER as u64 { - v8::BigInt::new_from_u64(scope, value).into() - } else { - v8::Number::new(scope, value as f64).into() - }; + let local_value: v8::Local = if self.pointer.is_null() { + v8::null(scope).into() + } else { + v8::External::new(scope, self.pointer).into() + }; local_value.into() } NativeType::Struct(_) => { @@ -396,22 +366,16 @@ pub fn ffi_parse_f64_arg( #[inline] pub fn ffi_parse_pointer_arg( - scope: &mut v8::HandleScope, + _scope: &mut v8::HandleScope, arg: v8::Local, ) -> Result { - // Order of checking: - // 1. BigInt: Uncommon and not supported by Fast API, optimise this case. - // 2. Number: Common and supported by Fast API. - // 3. Null: Very uncommon / can be represented by a 0. - let pointer = if let Ok(value) = v8::Local::::try_from(arg) { - value.u64_value().0 as usize as *mut c_void - } else if let Ok(value) = v8::Local::::try_from(arg) { - value.integer_value(scope).unwrap() as usize as *mut c_void + let pointer = if let Ok(value) = v8::Local::::try_from(arg) { + value.value() } else if arg.is_null() { ptr::null_mut() } else { return Err(type_error( - "Invalid FFI pointer type, expected null, integer or BigInt", + "Invalid FFI pointer type, expected null, or External", )); }; Ok(NativeValue { pointer }) @@ -502,22 +466,16 @@ pub fn ffi_parse_struct_arg( #[inline] pub fn ffi_parse_function_arg( - scope: &mut v8::HandleScope, + _scope: &mut v8::HandleScope, arg: v8::Local, ) -> Result { - // Order of checking: - // 1. BigInt: Uncommon and not supported by Fast API, optimise this case. - // 2. Number: Common and supported by Fast API, optimise this case as second. - // 3. Null: Very uncommon / can be represented by a 0. - let pointer = if let Ok(value) = v8::Local::::try_from(arg) { - value.u64_value().0 as usize as *mut c_void - } else if let Ok(value) = v8::Local::::try_from(arg) { - value.integer_value(scope).unwrap() as usize as *mut c_void + let pointer = if let Ok(value) = v8::Local::::try_from(arg) { + value.value() } else if arg.is_null() { ptr::null_mut() } else { return Err(type_error( - "Invalid FFI function type, expected null, integer, or BigInt", + "Invalid FFI function type, expected null, or External", )); }; Ok(NativeValue { pointer }) diff --git a/ext/ffi/lib.rs b/ext/ffi/lib.rs index 88e7884575534e..b8e3ac50320343 100644 --- a/ext/ffi/lib.rs +++ b/ext/ffi/lib.rs @@ -29,7 +29,6 @@ use call::op_ffi_call_ptr; use call::op_ffi_call_ptr_nonblocking; use callback::op_ffi_unsafe_callback_create; use callback::op_ffi_unsafe_callback_ref; -use callback::op_ffi_unsafe_callback_unref; use dlfcn::op_ffi_load; use dlfcn::ForeignFunction; use r#static::op_ffi_get_static; @@ -84,17 +83,18 @@ pub(crate) struct FfiState { pub fn init(unstable: bool) -> Extension { Extension::builder(env!("CARGO_PKG_NAME")) - .js(include_js_files!( - prefix "internal:ext/ffi", - "00_ffi.js", - )) + .esm(include_js_files!("00_ffi.js",)) .ops(vec![ op_ffi_load::decl::

(), op_ffi_get_static::decl(), op_ffi_call_nonblocking::decl(), op_ffi_call_ptr::decl::

(), op_ffi_call_ptr_nonblocking::decl::

(), + op_ffi_ptr_create::decl::

(), + op_ffi_ptr_equals::decl::

(), op_ffi_ptr_of::decl::

(), + op_ffi_ptr_offset::decl::

(), + op_ffi_ptr_value::decl::

(), op_ffi_get_buf::decl::

(), op_ffi_buf_copy_into::decl::

(), op_ffi_cstr_read::decl::

(), @@ -109,9 +109,9 @@ pub fn init(unstable: bool) -> Extension { op_ffi_read_i64::decl::

(), op_ffi_read_f32::decl::

(), op_ffi_read_f64::decl::

(), + op_ffi_read_ptr::decl::

(), op_ffi_unsafe_callback_create::decl::

(), op_ffi_unsafe_callback_ref::decl(), - op_ffi_unsafe_callback_unref::decl(), ]) .event_loop_middleware(|op_state_rc, _cx| { // FFI callbacks coming in from other threads will call in and get queued. diff --git a/ext/ffi/repr.rs b/ext/ffi/repr.rs index db03479dc4203d..4372b0f1bb5af4 100644 --- a/ext/ffi/repr.rs +++ b/ext/ffi/repr.rs @@ -13,16 +13,90 @@ use std::ffi::c_void; use std::ffi::CStr; use std::ptr; +#[op(fast)] +fn op_ffi_ptr_create( + state: &mut deno_core::OpState, + ptr_number: usize, +) -> Result<*mut c_void, AnyError> +where + FP: FfiPermissions + 'static, +{ + check_unstable(state, "Deno.UnsafePointer#create"); + let permissions = state.borrow_mut::(); + permissions.check(None)?; + + Ok(ptr_number as *mut c_void) +} + +#[op(fast)] +pub fn op_ffi_ptr_equals( + state: &mut deno_core::OpState, + a: *const c_void, + b: *const c_void, +) -> Result +where + FP: FfiPermissions + 'static, +{ + check_unstable(state, "Deno.UnsafePointer#equals"); + let permissions = state.borrow_mut::(); + permissions.check(None)?; + + Ok(a == b) +} + #[op(fast)] pub fn op_ffi_ptr_of( state: &mut deno_core::OpState, buf: *const u8, +) -> Result<*mut c_void, AnyError> +where + FP: FfiPermissions + 'static, +{ + check_unstable(state, "Deno.UnsafePointer#of"); + let permissions = state.borrow_mut::(); + permissions.check(None)?; + + Ok(buf as *mut c_void) +} + +#[op(fast)] +fn op_ffi_ptr_offset( + state: &mut deno_core::OpState, + ptr: *mut c_void, + offset: isize, +) -> Result<*mut c_void, AnyError> +where + FP: FfiPermissions + 'static, +{ + check_unstable(state, "Deno.UnsafePointer#offset"); + let permissions = state.borrow_mut::(); + permissions.check(None)?; + + if ptr.is_null() { + return Err(type_error("Invalid pointer to offset, pointer is null")); + } + + // SAFETY: Pointer and offset are user provided. + Ok(unsafe { ptr.offset(offset) }) +} + +unsafe extern "C" fn noop_deleter_callback( + _data: *mut c_void, + _byte_length: usize, + _deleter_data: *mut c_void, +) { +} + +#[op(fast)] +fn op_ffi_ptr_value( + state: &mut deno_core::OpState, + ptr: *mut c_void, out: &mut [u32], ) -> Result<(), AnyError> where FP: FfiPermissions + 'static, { - check_unstable(state, "Deno.UnsafePointer#of"); + check_unstable(state, "Deno.UnsafePointer#value"); let permissions = state.borrow_mut::(); permissions.check(None)?; @@ -35,47 +109,35 @@ where // SAFETY: Out buffer was asserted to be at least large enough to hold a usize, and properly aligned. let out = unsafe { &mut *outptr }; - *out = buf as usize; + *out = ptr as usize; Ok(()) } -unsafe extern "C" fn noop_deleter_callback( - _data: *mut c_void, - _byte_length: usize, - _deleter_data: *mut c_void, -) { -} - #[op(v8)] pub fn op_ffi_get_buf( scope: &mut v8::HandleScope<'scope>, state: &mut deno_core::OpState, - ptr: usize, - offset: usize, + ptr: *mut c_void, + offset: isize, len: usize, ) -> Result, AnyError> where FP: FfiPermissions + 'static, { - check_unstable(state, "Deno.UnsafePointerView#arrayBuffer"); + check_unstable(state, "Deno.UnsafePointerView#getArrayBuffer"); let permissions = state.borrow_mut::(); permissions.check(None)?; - let ptr = ptr as *mut c_void; - if ptr.is_null() { - return Err(type_error("Invalid FFI pointer value, got nullptr")); + return Err(type_error("Invalid ArrayBuffer pointer, pointer is null")); } - // SAFETY: Offset is user defined. - let ptr = unsafe { ptr.add(offset) }; - - // SAFETY: Trust the user to have provided a real pointer, and a valid matching size to it. Since this is a foreign pointer, we should not do any deletion. + // SAFETY: Trust the user to have provided a real pointer, offset, and a valid matching size to it. Since this is a foreign pointer, we should not do any deletion. let backing_store = unsafe { v8::ArrayBuffer::new_backing_store_from_ptr( - ptr, + ptr.offset(offset), len, noop_deleter_callback, std::ptr::null_mut(), @@ -90,8 +152,8 @@ where #[op(fast)] pub fn op_ffi_buf_copy_into( state: &mut deno_core::OpState, - src: usize, - offset: usize, + src: *mut c_void, + offset: isize, dst: &mut [u8], len: usize, ) -> Result<(), AnyError> @@ -103,19 +165,20 @@ where let permissions = state.borrow_mut::(); permissions.check(None)?; - if dst.len() < len { + if src.is_null() { + Err(type_error("Invalid ArrayBuffer pointer, pointer is null")) + } else if dst.len() < len { Err(range_error( "Destination length is smaller than source length", )) } else { let src = src as *const c_void; - // SAFETY: Offset is user defined. - let src = unsafe { src.add(offset) as *const u8 }; - - // SAFETY: src is user defined. + // SAFETY: src and offset are user defined. // dest is properly aligned and is valid for writes of len * size_of::() bytes. - unsafe { ptr::copy::(src, dst.as_mut_ptr(), len) }; + unsafe { + ptr::copy::(src.offset(offset) as *const u8, dst.as_mut_ptr(), len) + }; Ok(()) } } @@ -124,8 +187,8 @@ where pub fn op_ffi_cstr_read( scope: &mut v8::HandleScope<'scope>, state: &mut deno_core::OpState, - ptr: usize, - offset: usize, + ptr: *mut c_void, + offset: isize, ) -> Result, AnyError> where FP: FfiPermissions + 'static, @@ -135,32 +198,27 @@ where let permissions = state.borrow_mut::(); permissions.check(None)?; - let ptr = ptr as *const c_void; - if ptr.is_null() { return Err(type_error("Invalid CString pointer, pointer is null")); } - // SAFETY: Offset is user defined. - let ptr = unsafe { ptr.add(offset) }; - - // SAFETY: Pointer is user provided. - let cstr = unsafe { CStr::from_ptr(ptr as *const c_char) } - .to_str() - .map_err(|_| type_error("Invalid CString pointer, not valid UTF-8"))?; - let value: v8::Local = v8::String::new(scope, cstr) - .ok_or_else(|| { - type_error("Invalid CString pointer, string exceeds max length") - })? - .into(); + let cstr = + // SAFETY: Pointer and offset are user provided. + unsafe { CStr::from_ptr(ptr.offset(offset) as *const c_char) }.to_bytes(); + let value: v8::Local = + v8::String::new_from_utf8(scope, cstr, v8::NewStringType::Normal) + .ok_or_else(|| { + type_error("Invalid CString pointer, string exceeds max length") + })? + .into(); Ok(value.into()) } #[op(fast)] pub fn op_ffi_read_bool( state: &mut deno_core::OpState, - ptr: usize, - offset: usize, + ptr: *mut c_void, + offset: isize, ) -> Result where FP: FfiPermissions + 'static, @@ -170,21 +228,19 @@ where let permissions = state.borrow_mut::(); permissions.check(None)?; - let ptr = ptr as *const c_void; - if ptr.is_null() { return Err(type_error("Invalid bool pointer, pointer is null")); } // SAFETY: ptr and offset are user provided. - Ok(unsafe { ptr::read_unaligned::(ptr.add(offset) as *const bool) }) + Ok(unsafe { ptr::read_unaligned::(ptr.offset(offset) as *const bool) }) } #[op(fast)] pub fn op_ffi_read_u8( state: &mut deno_core::OpState, - ptr: usize, - offset: usize, + ptr: *mut c_void, + offset: isize, ) -> Result where FP: FfiPermissions + 'static, @@ -194,21 +250,21 @@ where let permissions = state.borrow_mut::(); permissions.check(None)?; - let ptr = ptr as *const c_void; - if ptr.is_null() { return Err(type_error("Invalid u8 pointer, pointer is null")); } // SAFETY: ptr and offset are user provided. - Ok(unsafe { ptr::read_unaligned::(ptr.add(offset) as *const u8) as u32 }) + Ok(unsafe { + ptr::read_unaligned::(ptr.offset(offset) as *const u8) as u32 + }) } #[op(fast)] pub fn op_ffi_read_i8( state: &mut deno_core::OpState, - ptr: usize, - offset: usize, + ptr: *mut c_void, + offset: isize, ) -> Result where FP: FfiPermissions + 'static, @@ -218,21 +274,21 @@ where let permissions = state.borrow_mut::(); permissions.check(None)?; - let ptr = ptr as *const c_void; - if ptr.is_null() { return Err(type_error("Invalid i8 pointer, pointer is null")); } // SAFETY: ptr and offset are user provided. - Ok(unsafe { ptr::read_unaligned::(ptr.add(offset) as *const i8) as i32 }) + Ok(unsafe { + ptr::read_unaligned::(ptr.offset(offset) as *const i8) as i32 + }) } #[op(fast)] pub fn op_ffi_read_u16( state: &mut deno_core::OpState, - ptr: usize, - offset: usize, + ptr: *mut c_void, + offset: isize, ) -> Result where FP: FfiPermissions + 'static, @@ -242,23 +298,21 @@ where let permissions = state.borrow_mut::(); permissions.check(None)?; - let ptr = ptr as *const c_void; - if ptr.is_null() { return Err(type_error("Invalid u16 pointer, pointer is null")); } // SAFETY: ptr and offset are user provided. Ok(unsafe { - ptr::read_unaligned::(ptr.add(offset) as *const u16) as u32 + ptr::read_unaligned::(ptr.offset(offset) as *const u16) as u32 }) } #[op(fast)] pub fn op_ffi_read_i16( state: &mut deno_core::OpState, - ptr: usize, - offset: usize, + ptr: *mut c_void, + offset: isize, ) -> Result where FP: FfiPermissions + 'static, @@ -268,23 +322,21 @@ where let permissions = state.borrow_mut::(); permissions.check(None)?; - let ptr = ptr as *const c_void; - if ptr.is_null() { return Err(type_error("Invalid i16 pointer, pointer is null")); } // SAFETY: ptr and offset are user provided. Ok(unsafe { - ptr::read_unaligned::(ptr.add(offset) as *const i16) as i32 + ptr::read_unaligned::(ptr.offset(offset) as *const i16) as i32 }) } #[op(fast)] pub fn op_ffi_read_u32( state: &mut deno_core::OpState, - ptr: usize, - offset: usize, + ptr: *mut c_void, + offset: isize, ) -> Result where FP: FfiPermissions + 'static, @@ -294,21 +346,19 @@ where let permissions = state.borrow_mut::(); permissions.check(None)?; - let ptr = ptr as *const c_void; - if ptr.is_null() { return Err(type_error("Invalid u32 pointer, pointer is null")); } // SAFETY: ptr and offset are user provided. - Ok(unsafe { ptr::read_unaligned::(ptr.add(offset) as *const u32) }) + Ok(unsafe { ptr::read_unaligned::(ptr.offset(offset) as *const u32) }) } #[op(fast)] pub fn op_ffi_read_i32( state: &mut deno_core::OpState, - ptr: usize, - offset: usize, + ptr: *mut c_void, + offset: isize, ) -> Result where FP: FfiPermissions + 'static, @@ -318,21 +368,19 @@ where let permissions = state.borrow_mut::(); permissions.check(None)?; - let ptr = ptr as *const c_void; - if ptr.is_null() { return Err(type_error("Invalid i32 pointer, pointer is null")); } // SAFETY: ptr and offset are user provided. - Ok(unsafe { ptr::read_unaligned::(ptr.add(offset) as *const i32) }) + Ok(unsafe { ptr::read_unaligned::(ptr.offset(offset) as *const i32) }) } #[op] pub fn op_ffi_read_u64( state: &mut deno_core::OpState, - ptr: usize, - offset: usize, + ptr: *mut c_void, + offset: isize, out: &mut [u32], ) -> Result<(), AnyError> where @@ -350,15 +398,13 @@ where ); assert_eq!((outptr as usize % std::mem::size_of::()), 0); - let ptr = ptr as *const c_void; - if ptr.is_null() { return Err(type_error("Invalid u64 pointer, pointer is null")); } let value = // SAFETY: ptr and offset are user provided. - unsafe { ptr::read_unaligned::(ptr.add(offset) as *const u64) }; + unsafe { ptr::read_unaligned::(ptr.offset(offset) as *const u64) }; // SAFETY: Length and alignment of out slice were asserted to be correct. unsafe { *outptr = value }; @@ -368,8 +414,8 @@ where #[op(fast)] pub fn op_ffi_read_i64( state: &mut deno_core::OpState, - ptr: usize, - offset: usize, + ptr: *mut c_void, + offset: isize, out: &mut [u32], ) -> Result<(), AnyError> where @@ -387,15 +433,13 @@ where ); assert_eq!((outptr as usize % std::mem::size_of::()), 0); - let ptr = ptr as *const c_void; - if ptr.is_null() { return Err(type_error("Invalid i64 pointer, pointer is null")); } let value = // SAFETY: ptr and offset are user provided. - unsafe { ptr::read_unaligned::(ptr.add(offset) as *const i64) }; + unsafe { ptr::read_unaligned::(ptr.offset(offset) as *const i64) }; // SAFETY: Length and alignment of out slice were asserted to be correct. unsafe { *outptr = value }; Ok(()) @@ -404,8 +448,8 @@ where #[op(fast)] pub fn op_ffi_read_f32( state: &mut deno_core::OpState, - ptr: usize, - offset: usize, + ptr: *mut c_void, + offset: isize, ) -> Result where FP: FfiPermissions + 'static, @@ -415,21 +459,19 @@ where let permissions = state.borrow_mut::(); permissions.check(None)?; - let ptr = ptr as *const c_void; - if ptr.is_null() { return Err(type_error("Invalid f32 pointer, pointer is null")); } // SAFETY: ptr and offset are user provided. - Ok(unsafe { ptr::read_unaligned::(ptr.add(offset) as *const f32) }) + Ok(unsafe { ptr::read_unaligned::(ptr.offset(offset) as *const f32) }) } #[op(fast)] pub fn op_ffi_read_f64( state: &mut deno_core::OpState, - ptr: usize, - offset: usize, + ptr: *mut c_void, + offset: isize, ) -> Result where FP: FfiPermissions + 'static, @@ -439,12 +481,34 @@ where let permissions = state.borrow_mut::(); permissions.check(None)?; - let ptr = ptr as *const c_void; - if ptr.is_null() { return Err(type_error("Invalid f64 pointer, pointer is null")); } // SAFETY: ptr and offset are user provided. - Ok(unsafe { ptr::read_unaligned::(ptr.add(offset) as *const f64) }) + Ok(unsafe { ptr::read_unaligned::(ptr.offset(offset) as *const f64) }) +} + +#[op(fast)] +pub fn op_ffi_read_ptr( + state: &mut deno_core::OpState, + ptr: *mut c_void, + offset: isize, +) -> Result<*mut c_void, AnyError> +where + FP: FfiPermissions + 'static, +{ + check_unstable(state, "Deno.UnsafePointerView#getPointer"); + + let permissions = state.borrow_mut::(); + permissions.check(None)?; + + if ptr.is_null() { + return Err(type_error("Invalid pointer pointer, pointer is null")); + } + + // SAFETY: ptr and offset are user provided. + Ok(unsafe { + ptr::read_unaligned::<*mut c_void>(ptr.offset(offset) as *const *mut c_void) + }) } diff --git a/ext/ffi/static.rs b/ext/ffi/static.rs index 9ea0d616d1360f..2f32f03fd1d5ce 100644 --- a/ext/ffi/static.rs +++ b/ext/ffi/static.rs @@ -10,6 +10,7 @@ use deno_core::op; use deno_core::serde_v8; use deno_core::v8; use deno_core::ResourceId; +use std::ffi::c_void; use std::ptr; #[op(v8)] @@ -134,13 +135,9 @@ pub fn op_ffi_get_static<'scope>( number.into() } NativeType::Pointer | NativeType::Function | NativeType::Buffer => { - let result = data_ptr as u64; - let integer: v8::Local = if result > MAX_SAFE_INTEGER as u64 { - v8::BigInt::new_from_u64(scope, result).into() - } else { - v8::Number::new(scope, result as f64).into() - }; - integer.into() + let external: v8::Local = + v8::External::new(scope, data_ptr as *mut c_void).into(); + external.into() } NativeType::Struct(_) => { return Err(type_error("Invalid FFI static type 'struct'")); diff --git a/ext/ffi/turbocall.rs b/ext/ffi/turbocall.rs index 1eb1655e150819..3d01f00f580b1c 100644 --- a/ext/ffi/turbocall.rs +++ b/ext/ffi/turbocall.rs @@ -48,6 +48,9 @@ pub(crate) fn make_template(sym: &Symbol, trampoline: &Trampoline) -> Template { let ret = if needs_unwrap(&sym.result_type) { params.push(fast_api::Type::TypedArray(fast_api::CType::Int32)); fast_api::Type::Void + } else if sym.result_type == NativeType::Buffer { + // Buffer can be used as a return type and converts differently than in parameters. + fast_api::Type::Pointer } else { fast_api::Type::from(&sym.result_type) }; @@ -71,9 +74,9 @@ impl Trampoline { } pub(crate) struct Template { - args: Box<[fast_api::Type]>, - ret: fast_api::CType, - symbol_ptr: *const c_void, + pub args: Box<[fast_api::Type]>, + pub ret: fast_api::CType, + pub symbol_ptr: *const c_void, } impl fast_api::FastFunction for Template { @@ -106,9 +109,8 @@ impl From<&NativeType> for fast_api::Type { NativeType::I64 => fast_api::Type::Int64, NativeType::U64 => fast_api::Type::Uint64, NativeType::ISize => fast_api::Type::Int64, - NativeType::USize | NativeType::Pointer | NativeType::Function => { - fast_api::Type::Uint64 - } + NativeType::USize => fast_api::Type::Uint64, + NativeType::Pointer | NativeType::Function => fast_api::Type::Pointer, NativeType::Buffer => fast_api::Type::TypedArray(fast_api::CType::Uint8), NativeType::Struct(_) => { fast_api::Type::TypedArray(fast_api::CType::Uint8) diff --git a/ext/flash/01_http.js b/ext/flash/01_http.js index 357bdfbe2f5d13..267a3551c5cdd0 100644 --- a/ext/flash/01_http.js +++ b/ext/flash/01_http.js @@ -1,611 +1,620 @@ // Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. -"use strict"; - -((window) => { - const { BlobPrototype } = window.__bootstrap.file; - const { TcpConn } = window.__bootstrap.net; - const { fromFlashRequest, toInnerResponse, _flash } = - window.__bootstrap.fetch; - const core = window.Deno.core; - const { Event } = window.__bootstrap.event; - const { - ReadableStream, - ReadableStreamPrototype, - getReadableStreamResourceBacking, - readableStreamClose, - _state, - } = window.__bootstrap.streams; - const { - WebSocket, - _rid, - _readyState, - _eventLoop, - _protocol, - _idleTimeoutDuration, - _idleTimeoutTimeout, - _serverHandleIdleTimeout, - } = window.__bootstrap.webSocket; - const { _ws } = window.__bootstrap.http; - const { - ObjectPrototypeIsPrototypeOf, - PromisePrototype, - PromisePrototypeCatch, - PromisePrototypeThen, - SafePromiseAll, - TypedArrayPrototypeSubarray, - TypeError, - Uint8Array, - Uint8ArrayPrototype, - } = window.__bootstrap.primordials; - - const statusCodes = { - 100: "Continue", - 101: "Switching Protocols", - 102: "Processing", - 200: "OK", - 201: "Created", - 202: "Accepted", - 203: "Non Authoritative Information", - 204: "No Content", - 205: "Reset Content", - 206: "Partial Content", - 207: "Multi-Status", - 208: "Already Reported", - 226: "IM Used", - 300: "Multiple Choices", - 301: "Moved Permanently", - 302: "Found", - 303: "See Other", - 304: "Not Modified", - 305: "Use Proxy", - 307: "Temporary Redirect", - 308: "Permanent Redirect", - 400: "Bad Request", - 401: "Unauthorized", - 402: "Payment Required", - 403: "Forbidden", - 404: "Not Found", - 405: "Method Not Allowed", - 406: "Not Acceptable", - 407: "Proxy Authentication Required", - 408: "Request Timeout", - 409: "Conflict", - 410: "Gone", - 411: "Length Required", - 412: "Precondition Failed", - 413: "Payload Too Large", - 414: "URI Too Long", - 415: "Unsupported Media Type", - 416: "Range Not Satisfiable", - 418: "I'm a teapot", - 421: "Misdirected Request", - 422: "Unprocessable Entity", - 423: "Locked", - 424: "Failed Dependency", - 426: "Upgrade Required", - 428: "Precondition Required", - 429: "Too Many Requests", - 431: "Request Header Fields Too Large", - 451: "Unavailable For Legal Reasons", - 500: "Internal Server Error", - 501: "Not Implemented", - 502: "Bad Gateway", - 503: "Service Unavailable", - 504: "Gateway Timeout", - 505: "HTTP Version Not Supported", - 506: "Variant Also Negotiates", - 507: "Insufficient Storage", - 508: "Loop Detected", - 510: "Not Extended", - 511: "Network Authentication Required", - }; - - const methods = { - 0: "GET", - 1: "HEAD", - 2: "CONNECT", - 3: "PUT", - 4: "DELETE", - 5: "OPTIONS", - 6: "TRACE", - 7: "POST", - 8: "PATCH", - }; - - let dateInterval; - let date; - - // Construct an HTTP response message. - // All HTTP/1.1 messages consist of a start-line followed by a sequence - // of octets. - // - // HTTP-message = start-line - // *( header-field CRLF ) - // CRLF - // [ message-body ] +const core = globalThis.Deno.core; +const ops = core.ops; +const primordials = globalThis.__bootstrap.primordials; +import { BlobPrototype } from "internal:deno_web/09_file.js"; +import { TcpConn } from "internal:deno_net/01_net.js"; +import { toInnerResponse } from "internal:deno_fetch/23_response.js"; +import { _flash, fromFlashRequest } from "internal:deno_fetch/23_request.js"; +import { Event } from "internal:deno_web/02_event.js"; +import { + _state, + getReadableStreamResourceBacking, + ReadableStream, + readableStreamClose, + ReadableStreamPrototype, +} from "internal:deno_web/06_streams.js"; +import { + _eventLoop, + _idleTimeoutDuration, + _idleTimeoutTimeout, + _protocol, + _readyState, + _rid, + _serverHandleIdleTimeout, + WebSocket, +} from "internal:deno_websocket/01_websocket.js"; +import { _ws } from "internal:deno_http/01_http.js"; +const { + ObjectPrototypeIsPrototypeOf, + PromisePrototype, + PromisePrototypeCatch, + PromisePrototypeThen, + SafePromiseAll, + TypedArrayPrototypeSet, + TypedArrayPrototypeSubarray, + TypeError, + Uint8Array, + Uint8ArrayPrototype, +} = primordials; + +const statusCodes = { + 100: "Continue", + 101: "Switching Protocols", + 102: "Processing", + 200: "OK", + 201: "Created", + 202: "Accepted", + 203: "Non Authoritative Information", + 204: "No Content", + 205: "Reset Content", + 206: "Partial Content", + 207: "Multi-Status", + 208: "Already Reported", + 226: "IM Used", + 300: "Multiple Choices", + 301: "Moved Permanently", + 302: "Found", + 303: "See Other", + 304: "Not Modified", + 305: "Use Proxy", + 307: "Temporary Redirect", + 308: "Permanent Redirect", + 400: "Bad Request", + 401: "Unauthorized", + 402: "Payment Required", + 403: "Forbidden", + 404: "Not Found", + 405: "Method Not Allowed", + 406: "Not Acceptable", + 407: "Proxy Authentication Required", + 408: "Request Timeout", + 409: "Conflict", + 410: "Gone", + 411: "Length Required", + 412: "Precondition Failed", + 413: "Payload Too Large", + 414: "URI Too Long", + 415: "Unsupported Media Type", + 416: "Range Not Satisfiable", + 418: "I'm a teapot", + 421: "Misdirected Request", + 422: "Unprocessable Entity", + 423: "Locked", + 424: "Failed Dependency", + 426: "Upgrade Required", + 428: "Precondition Required", + 429: "Too Many Requests", + 431: "Request Header Fields Too Large", + 451: "Unavailable For Legal Reasons", + 500: "Internal Server Error", + 501: "Not Implemented", + 502: "Bad Gateway", + 503: "Service Unavailable", + 504: "Gateway Timeout", + 505: "HTTP Version Not Supported", + 506: "Variant Also Negotiates", + 507: "Insufficient Storage", + 508: "Loop Detected", + 510: "Not Extended", + 511: "Network Authentication Required", +}; + +const methods = { + 0: "GET", + 1: "HEAD", + 2: "CONNECT", + 3: "PUT", + 4: "DELETE", + 5: "OPTIONS", + 6: "TRACE", + 7: "POST", + 8: "PATCH", +}; + +let dateInterval; +let date; + +/** + * Construct an HTTP response message. + * All HTTP/1.1 messages consist of a start-line followed by a sequence + * of octets. + * + * HTTP-message = start-line + * *( header-field CRLF ) + * CRLF + * [ message-body ] + * + * @param {keyof typeof methods} method + * @param {keyof typeof statusCodes} status + * @param {[name: string, value: string][]} headerList + * @param {Uint8Array | string | null} body + * @param {number} bodyLen + * @param {boolean} earlyEnd + * @returns {Uint8Array | string} + */ +function http1Response( + method, + status, + headerList, + body, + bodyLen, + earlyEnd = false, +) { + // HTTP uses a "." numbering scheme + // HTTP-version = HTTP-name "/" DIGIT "." DIGIT + // HTTP-name = %x48.54.54.50 ; "HTTP", case-sensitive // - function http1Response( - method, - status, - headerList, - body, - bodyLen, - earlyEnd = false, - ) { - // HTTP uses a "." numbering scheme - // HTTP-version = HTTP-name "/" DIGIT "." DIGIT - // HTTP-name = %x48.54.54.50 ; "HTTP", case-sensitive - // - // status-line = HTTP-version SP status-code SP reason-phrase CRLF - // Date header: https://datatracker.ietf.org/doc/html/rfc7231#section-7.1.1.2 - let str = `HTTP/1.1 ${status} ${statusCodes[status]}\r\nDate: ${date}\r\n`; - for (let i = 0; i < headerList.length; ++i) { - const { 0: name, 1: value } = headerList[i]; - // header-field = field-name ":" OWS field-value OWS - str += `${name}: ${value}\r\n`; - } - - // https://datatracker.ietf.org/doc/html/rfc7231#section-6.3.6 - if (status === 205 || status === 304) { - // MUST NOT generate a payload in a 205 response. - // indicate a zero-length body for the response by - // including a Content-Length header field with a value of 0. - str += "Content-Length: 0\r\n\r\n"; - return str; - } - - // MUST NOT send Content-Length or Transfer-Encoding if status code is 1xx or 204. - if (status === 204 || status < 200) { - str += "\r\n"; - return str; - } - - if (earlyEnd === true) { - return str; - } - - // null body status is validated by inititalizeAResponse in ext/fetch - if (body !== null && body !== undefined) { - str += `Content-Length: ${bodyLen}\r\n\r\n`; - } else { - str += "Transfer-Encoding: chunked\r\n\r\n"; - return str; - } - - // A HEAD request. - if (method === 1) return str; + // status-line = HTTP-version SP status-code SP reason-phrase CRLF + // Date header: https://datatracker.ietf.org/doc/html/rfc7231#section-7.1.1.2 + let str = `HTTP/1.1 ${status} ${statusCodes[status]}\r\nDate: ${date}\r\n`; + for (let i = 0; i < headerList.length; ++i) { + const { 0: name, 1: value } = headerList[i]; + // header-field = field-name ":" OWS field-value OWS + str += `${name}: ${value}\r\n`; + } - if (typeof body === "string") { - str += body ?? ""; - } else { - const head = core.encode(str); - const response = new Uint8Array(head.byteLength + body.byteLength); - response.set(head, 0); - response.set(body, head.byteLength); - return response; - } + // https://datatracker.ietf.org/doc/html/rfc7231#section-6.3.6 + if (status === 205 || status === 304) { + // MUST NOT generate a payload in a 205 response. + // indicate a zero-length body for the response by + // including a Content-Length header field with a value of 0. + str += "Content-Length: 0\r\n\r\n"; + return str; + } + // MUST NOT send Content-Length or Transfer-Encoding if status code is 1xx or 204. + if (status === 204 || status < 200) { + str += "\r\n"; return str; } - function prepareFastCalls() { - return core.ops.op_flash_make_request(); + if (earlyEnd === true) { + return str; } - function hostnameForDisplay(hostname) { - // If the hostname is "0.0.0.0", we display "localhost" in console - // because browsers in Windows don't resolve "0.0.0.0". - // See the discussion in https://github.com/denoland/deno_std/issues/1165 - return hostname === "0.0.0.0" ? "localhost" : hostname; + // null body status is validated by inititalizeAResponse in ext/fetch + if (body !== null && body !== undefined) { + str += `Content-Length: ${bodyLen}\r\n\r\n`; + } else { + str += "Transfer-Encoding: chunked\r\n\r\n"; + return str; } - function writeFixedResponse( - server, - requestId, - response, - responseLen, - end, - respondFast, - ) { - let nwritten = 0; - // TypedArray - if (typeof response !== "string") { - nwritten = respondFast(requestId, response, end); - } else { - // string - nwritten = core.ops.op_flash_respond( - server, - requestId, - response, - end, - ); - } + // A HEAD request. + if (method === 1) return str; + + if (typeof body === "string") { + str += body ?? ""; + } else { + const head = core.encode(str); + const response = new Uint8Array(head.byteLength + bodyLen); + TypedArrayPrototypeSet(response, head, 0); + TypedArrayPrototypeSet(response, body, head.byteLength); + return response; + } - if (nwritten < responseLen) { - core.opAsync( - "op_flash_respond_async", - server, - requestId, - response.slice(nwritten), - end, - ); - } + return str; +} + +function prepareFastCalls() { + return ops.op_flash_make_request(); +} + +function hostnameForDisplay(hostname) { + // If the hostname is "0.0.0.0", we display "localhost" in console + // because browsers in Windows don't resolve "0.0.0.0". + // See the discussion in https://github.com/denoland/deno_std/issues/1165 + return hostname === "0.0.0.0" ? "localhost" : hostname; +} + +function writeFixedResponse( + server, + requestId, + response, + responseLen, + end, + respondFast, +) { + let nwritten = 0; + // TypedArray + if (typeof response !== "string") { + nwritten = respondFast(requestId, response, end); + } else { + // string + nwritten = ops.op_flash_respond( + server, + requestId, + response, + end, + ); } - // TODO(@littledivy): Woah woah, cut down the number of arguments. - async function handleResponse( - req, - resp, - body, - hasBody, - method, - serverId, - i, - respondFast, - respondChunked, - tryRespondChunked, - ) { - // there might've been an HTTP upgrade. - if (resp === undefined) { - return; - } - const innerResp = toInnerResponse(resp); - // If response body length is known, it will be sent synchronously in a - // single op, in other case a "response body" resource will be created and - // we'll be streaming it. - /** @type {ReadableStream | Uint8Array | null} */ - let respBody = null; - let isStreamingResponseBody = false; - if (innerResp.body !== null) { - if (typeof innerResp.body.streamOrStatic?.body === "string") { - if (innerResp.body.streamOrStatic.consumed === true) { - throw new TypeError("Body is unusable."); - } - innerResp.body.streamOrStatic.consumed = true; - respBody = innerResp.body.streamOrStatic.body; - isStreamingResponseBody = false; - } else if ( + if (nwritten < responseLen) { + core.opAsync( + "op_flash_respond_async", + server, + requestId, + response.slice(nwritten), + end, + ); + } +} + +// TODO(@littledivy): Woah woah, cut down the number of arguments. +async function handleResponse( + req, + resp, + body, + hasBody, + method, + serverId, + i, + respondFast, + respondChunked, + tryRespondChunked, +) { + // there might've been an HTTP upgrade. + if (resp === undefined) { + return; + } + const innerResp = toInnerResponse(resp); + // If response body length is known, it will be sent synchronously in a + // single op, in other case a "response body" resource will be created and + // we'll be streaming it. + /** @type {ReadableStream | Uint8Array | string | null} */ + let respBody = null; + let isStreamingResponseBody = false; + if (innerResp.body !== null) { + if (typeof innerResp.body.streamOrStatic?.body === "string") { + if (innerResp.body.streamOrStatic.consumed === true) { + throw new TypeError("Body is unusable."); + } + innerResp.body.streamOrStatic.consumed = true; + respBody = innerResp.body.streamOrStatic.body; + isStreamingResponseBody = false; + } else if ( + ObjectPrototypeIsPrototypeOf( + ReadableStreamPrototype, + innerResp.body.streamOrStatic, + ) + ) { + if (innerResp.body.unusable()) { + throw new TypeError("Body is unusable."); + } + if ( + innerResp.body.length === null || ObjectPrototypeIsPrototypeOf( - ReadableStreamPrototype, - innerResp.body.streamOrStatic, + BlobPrototype, + innerResp.body.source, ) ) { - if (innerResp.body.unusable()) { - throw new TypeError("Body is unusable."); - } - if ( - innerResp.body.length === null || - ObjectPrototypeIsPrototypeOf( - BlobPrototype, - innerResp.body.source, - ) - ) { - respBody = innerResp.body.stream; - } else { - const reader = innerResp.body.stream.getReader(); - const r1 = await reader.read(); - if (r1.done) { - respBody = new Uint8Array(0); - } else { - respBody = r1.value; - const r2 = await reader.read(); - if (!r2.done) throw new TypeError("Unreachable"); - } - } - isStreamingResponseBody = !( - typeof respBody === "string" || - ObjectPrototypeIsPrototypeOf(Uint8ArrayPrototype, respBody) - ); + respBody = innerResp.body.stream; } else { - if (innerResp.body.streamOrStatic.consumed === true) { - throw new TypeError("Body is unusable."); + const reader = innerResp.body.stream.getReader(); + const r1 = await reader.read(); + if (r1.done) { + respBody = new Uint8Array(0); + } else { + respBody = r1.value; + const r2 = await reader.read(); + if (!r2.done) throw new TypeError("Unreachable"); } - innerResp.body.streamOrStatic.consumed = true; - respBody = innerResp.body.streamOrStatic.body; } + isStreamingResponseBody = !( + typeof respBody === "string" || + ObjectPrototypeIsPrototypeOf(Uint8ArrayPrototype, respBody) + ); } else { - respBody = new Uint8Array(0); + if (innerResp.body.streamOrStatic.consumed === true) { + throw new TypeError("Body is unusable."); + } + innerResp.body.streamOrStatic.consumed = true; + respBody = innerResp.body.streamOrStatic.body; } + } else { + respBody = new Uint8Array(0); + } - const ws = resp[_ws]; - if (isStreamingResponseBody === false) { - const length = respBody.byteLength || core.byteLength(respBody); - const responseStr = http1Response( - method, - innerResp.status ?? 200, - innerResp.headerList, - respBody, - length, - ); - writeFixedResponse( - serverId, - i, - responseStr, - length, - !ws, // Don't close socket if there is a deferred websocket upgrade. - respondFast, - ); - } + const ws = resp[_ws]; + if (isStreamingResponseBody === false) { + const length = respBody.byteLength || core.byteLength(respBody); + const responseStr = http1Response( + method, + innerResp.status ?? 200, + innerResp.headerList, + respBody, + length, + ); + // A HEAD request always ignores body, but includes the correct content-length size. + const responseLen = method === 1 ? core.byteLength(responseStr) : length; + writeFixedResponse( + serverId, + i, + responseStr, + responseLen, + !ws, // Don't close socket if there is a deferred websocket upgrade. + respondFast, + ); + } - return (async () => { - if (!ws) { - if (hasBody && body[_state] !== "closed") { - // TODO(@littledivy): Optimize by draining in a single op. - try { - await req.arrayBuffer(); - } catch { /* pass */ } - } + return (async () => { + if (!ws) { + if (hasBody && body[_state] !== "closed") { + // TODO(@littledivy): Optimize by draining in a single op. + try { + await req.arrayBuffer(); + } catch { /* pass */ } } + } - if (isStreamingResponseBody === true) { - const resourceBacking = getReadableStreamResourceBacking(respBody); - if (resourceBacking) { - if (respBody.locked) { - throw new TypeError("ReadableStream is locked."); - } - const reader = respBody.getReader(); // Aquire JS lock. - try { - PromisePrototypeThen( - core.opAsync( - "op_flash_write_resource", - http1Response( - method, - innerResp.status ?? 200, - innerResp.headerList, - 0, // Content-Length will be set by the op. - null, - true, - ), - serverId, - i, - resourceBacking.rid, - resourceBacking.autoClose, + if (isStreamingResponseBody === true) { + const resourceBacking = getReadableStreamResourceBacking(respBody); + if (resourceBacking) { + if (respBody.locked) { + throw new TypeError("ReadableStream is locked."); + } + const reader = respBody.getReader(); // Aquire JS lock. + try { + PromisePrototypeThen( + core.opAsync( + "op_flash_write_resource", + http1Response( + method, + innerResp.status ?? 200, + innerResp.headerList, + null, + 0, // Content-Length will be set by the op. + true, ), - () => { - // Release JS lock. - readableStreamClose(respBody); - }, - ); - } catch (error) { - await reader.cancel(error); - throw error; - } - } else { - const reader = respBody.getReader(); - - // Best case: sends headers + first chunk in a single go. - const { value, done } = await reader.read(); - writeFixedResponse( - serverId, - i, - http1Response( - method, - innerResp.status ?? 200, - innerResp.headerList, - respBody.byteLength, - null, + serverId, + i, + resourceBacking.rid, + resourceBacking.autoClose, ), - respBody.byteLength, - false, - respondFast, - ); - - await tryRespondChunked( - i, - value, - done, + () => { + // Release JS lock. + readableStreamClose(respBody); + }, ); - - if (!done) { - while (true) { - const chunk = await reader.read(); - await respondChunked( - i, - chunk.value, - chunk.done, - ); - if (chunk.done) break; - } - } + } catch (error) { + await reader.cancel(error); + throw error; } - } + } else { + const reader = respBody.getReader(); - if (ws) { - const wsRid = await core.opAsync( - "op_flash_upgrade_websocket", + // Best case: sends headers + first chunk in a single go. + const { value, done } = await reader.read(); + writeFixedResponse( serverId, i, + http1Response( + method, + innerResp.status ?? 200, + innerResp.headerList, + null, + respBody.byteLength, + ), + respBody.byteLength, + false, + respondFast, ); - ws[_rid] = wsRid; - ws[_protocol] = resp.headers.get("sec-websocket-protocol"); - - ws[_readyState] = WebSocket.OPEN; - const event = new Event("open"); - ws.dispatchEvent(event); - - ws[_eventLoop](); - if (ws[_idleTimeoutDuration]) { - ws.addEventListener( - "close", - () => clearTimeout(ws[_idleTimeoutTimeout]), - ); - } - ws[_serverHandleIdleTimeout](); - } - })(); - } - function createServe(opFn) { - return async function serve(arg1, arg2) { - let options = undefined; - let handler = undefined; - if (typeof arg1 === "function") { - handler = arg1; - options = arg2; - } else if (typeof arg2 === "function") { - handler = arg2; - options = arg1; - } else { - options = arg1; - } - if (handler === undefined) { - if (options === undefined) { - throw new TypeError( - "No handler was provided, so an options bag is mandatory.", - ); + await tryRespondChunked( + i, + value, + done, + ); + + if (!done) { + while (true) { + const chunk = await reader.read(); + await respondChunked( + i, + chunk.value, + chunk.done, + ); + if (chunk.done) break; + } } - handler = options.handler; } - if (typeof handler !== "function") { - throw new TypeError("A handler function must be provided."); + } + + if (ws) { + const wsRid = await core.opAsync( + "op_flash_upgrade_websocket", + serverId, + i, + ); + ws[_rid] = wsRid; + ws[_protocol] = resp.headers.get("sec-websocket-protocol"); + + ws[_readyState] = WebSocket.OPEN; + const event = new Event("open"); + ws.dispatchEvent(event); + + ws[_eventLoop](); + if (ws[_idleTimeoutDuration]) { + ws.addEventListener( + "close", + () => clearTimeout(ws[_idleTimeoutTimeout]), + ); } + ws[_serverHandleIdleTimeout](); + } + })(); +} + +function createServe(opFn) { + return async function serve(arg1, arg2) { + let options = undefined; + let handler = undefined; + if (typeof arg1 === "function") { + handler = arg1; + options = arg2; + } else if (typeof arg2 === "function") { + handler = arg2; + options = arg1; + } else { + options = arg1; + } + if (handler === undefined) { if (options === undefined) { - options = {}; + throw new TypeError( + "No handler was provided, so an options bag is mandatory.", + ); } + handler = options.handler; + } + if (typeof handler !== "function") { + throw new TypeError("A handler function must be provided."); + } + if (options === undefined) { + options = {}; + } + + const signal = options.signal; - const signal = options.signal; + const onError = options.onError ?? function (error) { + console.error(error); + return new Response("Internal Server Error", { status: 500 }); + }; - const onError = options.onError ?? function (error) { - console.error(error); - return new Response("Internal Server Error", { status: 500 }); - }; + const onListen = options.onListen ?? function ({ port }) { + console.log( + `Listening on http://${ + hostnameForDisplay(listenOpts.hostname) + }:${port}/`, + ); + }; - const onListen = options.onListen ?? function ({ port }) { - console.log( - `Listening on http://${ - hostnameForDisplay(listenOpts.hostname) - }:${port}/`, + const listenOpts = { + hostname: options.hostname ?? "127.0.0.1", + port: options.port ?? 9000, + reuseport: options.reusePort ?? false, + }; + if (options.cert || options.key) { + if (!options.cert || !options.key) { + throw new TypeError( + "Both cert and key must be provided to enable HTTPS.", ); - }; - - const listenOpts = { - hostname: options.hostname ?? "127.0.0.1", - port: options.port ?? 9000, - reuseport: options.reusePort ?? false, - }; - if (options.cert || options.key) { - if (!options.cert || !options.key) { - throw new TypeError( - "Both cert and key must be provided to enable HTTPS.", - ); - } - listenOpts.cert = options.cert; - listenOpts.key = options.key; } + listenOpts.cert = options.cert; + listenOpts.key = options.key; + } - const serverId = opFn(listenOpts); - const serverPromise = core.opAsync("op_flash_drive_server", serverId); - - PromisePrototypeCatch( - PromisePrototypeThen( - core.opAsync("op_flash_wait_for_listening", serverId), - (port) => { - onListen({ hostname: listenOpts.hostname, port }); - }, - ), - () => {}, - ); - const finishedPromise = PromisePrototypeCatch(serverPromise, () => {}); - - const server = { - id: serverId, - transport: listenOpts.cert && listenOpts.key ? "https" : "http", - hostname: listenOpts.hostname, - port: listenOpts.port, - closed: false, - finished: finishedPromise, - async close() { + const serverId = opFn(listenOpts); + const serverPromise = core.opAsync("op_flash_drive_server", serverId); + + PromisePrototypeCatch( + PromisePrototypeThen( + core.opAsync("op_flash_wait_for_listening", serverId), + (port) => { + onListen({ hostname: listenOpts.hostname, port }); + }, + ), + () => {}, + ); + const finishedPromise = PromisePrototypeCatch(serverPromise, () => {}); + + const server = { + id: serverId, + transport: listenOpts.cert && listenOpts.key ? "https" : "http", + hostname: listenOpts.hostname, + port: listenOpts.port, + closed: false, + finished: finishedPromise, + async close() { + if (server.closed) { + return; + } + server.closed = true; + await core.opAsync("op_flash_close_server", serverId); + await server.finished; + }, + async serve() { + let offset = 0; + while (true) { if (server.closed) { - return; + break; } - server.closed = true; - await core.opAsync("op_flash_close_server", serverId); - await server.finished; - }, - async serve() { - let offset = 0; - while (true) { + + let tokens = nextRequestSync(); + if (tokens === 0) { + tokens = await core.opAsync("op_flash_next_async", serverId); if (server.closed) { break; } + } - let tokens = nextRequestSync(); - if (tokens === 0) { - tokens = await core.opAsync("op_flash_next_async", serverId); - if (server.closed) { - break; + for (let i = offset; i < offset + tokens; i++) { + let body = null; + // There might be a body, but we don't expose it for GET/HEAD requests. + // It will be closed automatically once the request has been handled and + // the response has been sent. + const method = getMethodSync(i); + let hasBody = method > 2; // Not GET/HEAD/CONNECT + if (hasBody) { + body = createRequestBodyStream(serverId, i); + if (body === null) { + hasBody = false; } } - for (let i = offset; i < offset + tokens; i++) { - let body = null; - // There might be a body, but we don't expose it for GET/HEAD requests. - // It will be closed automatically once the request has been handled and - // the response has been sent. - const method = getMethodSync(i); - let hasBody = method > 2; // Not GET/HEAD/CONNECT - if (hasBody) { - body = createRequestBodyStream(serverId, i); - if (body === null) { - hasBody = false; - } - } + const req = fromFlashRequest( + serverId, + /* streamRid */ + i, + body, + /* methodCb */ + () => methods[method], + /* urlCb */ + () => { + const path = ops.op_flash_path(serverId, i); + return `${server.transport}://${server.hostname}:${server.port}${path}`; + }, + /* headersCb */ + () => ops.op_flash_headers(serverId, i), + ); - const req = fromFlashRequest( - serverId, - /* streamRid */ - i, - body, - /* methodCb */ - () => methods[method], - /* urlCb */ - () => { - const path = core.ops.op_flash_path(serverId, i); - return `${server.transport}://${server.hostname}:${server.port}${path}`; - }, - /* headersCb */ - () => core.ops.op_flash_headers(serverId, i), - ); - - let resp; - try { - resp = handler(req); - if (ObjectPrototypeIsPrototypeOf(PromisePrototype, resp)) { - PromisePrototypeCatch( - PromisePrototypeThen( - resp, - (resp) => - handleResponse( - req, - resp, - body, - hasBody, - method, - serverId, - i, - respondFast, - respondChunked, - tryRespondChunked, - ), - ), - onError, - ); - } else if (typeof resp?.then === "function") { - resp.then((resp) => - handleResponse( - req, - resp, - body, - hasBody, - method, + let resp; + let remoteAddr; + try { + resp = handler(req, { + get remoteAddr() { + if (!remoteAddr) { + const { 0: hostname, 1: port } = core.ops.op_flash_addr( serverId, i, - respondFast, - respondChunked, - tryRespondChunked, - ) - ).catch(onError); - } else { + ); + remoteAddr = { hostname, port }; + } + return remoteAddr; + }, + }); + if (ObjectPrototypeIsPrototypeOf(PromisePrototype, resp)) { + PromisePrototypeCatch( + PromisePrototypeThen( + resp, + (resp) => + handleResponse( + req, + resp, + body, + hasBody, + method, + serverId, + i, + respondFast, + respondChunked, + tryRespondChunked, + ), + ), + onError, + ); + } else if (typeof resp?.then === "function") { + resp.then((resp) => handleResponse( req, resp, @@ -617,147 +626,157 @@ respondFast, respondChunked, tryRespondChunked, - ).catch(onError); - } - } catch (e) { - resp = await onError(e); + ) + ).catch(onError); + } else { + handleResponse( + req, + resp, + body, + hasBody, + method, + serverId, + i, + respondFast, + respondChunked, + tryRespondChunked, + ).catch(onError); } + } catch (e) { + resp = await onError(e); } - - offset += tokens; } - await server.finished; - }, - }; - - signal?.addEventListener("abort", () => { - clearInterval(dateInterval); - PromisePrototypeThen(server.close(), () => {}, () => {}); - }, { - once: true, - }); - function tryRespondChunked(token, chunk, shutdown) { - const nwritten = core.ops.op_try_flash_respond_chunked( - serverId, - token, - chunk ?? new Uint8Array(), - shutdown, - ); - if (nwritten > 0) { - return core.opAsync( - "op_flash_respond_chunked", - serverId, - token, - chunk, - shutdown, - nwritten, - ); + offset += tokens; } - } + await server.finished; + }, + }; - function respondChunked(token, chunk, shutdown) { + signal?.addEventListener("abort", () => { + clearInterval(dateInterval); + PromisePrototypeThen(server.close(), () => {}, () => {}); + }, { + once: true, + }); + + function tryRespondChunked(token, chunk, shutdown) { + const nwritten = ops.op_try_flash_respond_chunked( + serverId, + token, + chunk ?? new Uint8Array(), + shutdown, + ); + if (nwritten > 0) { return core.opAsync( "op_flash_respond_chunked", serverId, token, chunk, shutdown, + nwritten, ); } + } - const fastOp = prepareFastCalls(); - let nextRequestSync = () => fastOp.nextRequest(); - let getMethodSync = (token) => fastOp.getMethod(token); - let respondFast = (token, response, shutdown) => - fastOp.respond(token, response, shutdown); - if (serverId > 0) { - nextRequestSync = () => core.ops.op_flash_next_server(serverId); - getMethodSync = (token) => core.ops.op_flash_method(serverId, token); - respondFast = (token, response, shutdown) => - core.ops.op_flash_respond(serverId, token, response, null, shutdown); - } + function respondChunked(token, chunk, shutdown) { + return core.opAsync( + "op_flash_respond_chunked", + serverId, + token, + chunk, + shutdown, + ); + } - if (!dateInterval) { - date = new Date().toUTCString(); - dateInterval = setInterval(() => { - date = new Date().toUTCString(); - }, 1000); - } + const fastOp = prepareFastCalls(); + let nextRequestSync = () => fastOp.nextRequest(); + let getMethodSync = (token) => fastOp.getMethod(token); + let respondFast = (token, response, shutdown) => + fastOp.respond(token, response, shutdown); + if (serverId > 0) { + nextRequestSync = () => ops.op_flash_next_server(serverId); + getMethodSync = (token) => ops.op_flash_method(serverId, token); + respondFast = (token, response, shutdown) => + ops.op_flash_respond(serverId, token, response, null, shutdown); + } - await SafePromiseAll([ - PromisePrototypeCatch(server.serve(), console.error), - serverPromise, - ]); - }; - } + if (!dateInterval) { + date = new Date().toUTCString(); + dateInterval = setInterval(() => { + date = new Date().toUTCString(); + }, 1000); + } - function createRequestBodyStream(serverId, token) { - // The first packet is left over bytes after parsing the request - const firstRead = core.ops.op_flash_first_packet( - serverId, - token, - ); - if (!firstRead) return null; - let firstEnqueued = firstRead.byteLength == 0; + await SafePromiseAll([ + PromisePrototypeCatch(server.serve(), console.error), + serverPromise, + ]); + }; +} - return new ReadableStream({ - type: "bytes", - async pull(controller) { - try { - if (firstEnqueued === false) { - controller.enqueue(firstRead); - firstEnqueued = true; - return; - } - // This is the largest possible size for a single packet on a TLS - // stream. - const chunk = new Uint8Array(16 * 1024 + 256); - const read = await core.opAsync( - "op_flash_read_body", - serverId, - token, - chunk, - ); - if (read > 0) { - // We read some data. Enqueue it onto the stream. - controller.enqueue(TypedArrayPrototypeSubarray(chunk, 0, read)); - } else { - // We have reached the end of the body, so we close the stream. - controller.close(); - } - } catch (err) { - // There was an error while reading a chunk of the body, so we - // error. - controller.error(err); +function createRequestBodyStream(serverId, token) { + // The first packet is left over bytes after parsing the request + const firstRead = ops.op_flash_first_packet( + serverId, + token, + ); + if (!firstRead) return null; + let firstEnqueued = firstRead.byteLength == 0; + + return new ReadableStream({ + type: "bytes", + async pull(controller) { + try { + if (firstEnqueued === false) { + controller.enqueue(firstRead); + firstEnqueued = true; + return; + } + // This is the largest possible size for a single packet on a TLS + // stream. + const chunk = new Uint8Array(16 * 1024 + 256); + const read = await core.opAsync( + "op_flash_read_body", + serverId, + token, + chunk, + ); + if (read > 0) { + // We read some data. Enqueue it onto the stream. + controller.enqueue(TypedArrayPrototypeSubarray(chunk, 0, read)); + } else { + // We have reached the end of the body, so we close the stream. controller.close(); } - }, - }); + } catch (err) { + // There was an error while reading a chunk of the body, so we + // error. + controller.error(err); + controller.close(); + } + }, + }); +} + +function upgradeHttpRaw(req) { + if (!req[_flash]) { + throw new TypeError( + "Non-flash requests can not be upgraded with `upgradeHttpRaw`. Use `upgradeHttp` instead.", + ); } - function upgradeHttpRaw(req) { - if (!req[_flash]) { - throw new TypeError( - "Non-flash requests can not be upgraded with `upgradeHttpRaw`. Use `upgradeHttp` instead.", - ); - } + // NOTE(bartlomieju): + // Access these fields so they are cached on `req` object, otherwise + // they wouldn't be available after the connection gets upgraded. + req.url; + req.method; + req.headers; - // NOTE(bartlomieju): - // Access these fields so they are cached on `req` object, otherwise - // they wouldn't be available after the connection gets upgraded. - req.url; - req.method; - req.headers; - - const { serverId, streamRid } = req[_flash]; - const connRid = core.ops.op_flash_upgrade_http(streamRid, serverId); - // TODO(@littledivy): return already read first packet too. - return [new TcpConn(connRid), new Uint8Array()]; - } + const { serverId, streamRid } = req[_flash]; + const connRid = ops.op_flash_upgrade_http(streamRid, serverId); + // TODO(@littledivy): return already read first packet too. + return [new TcpConn(connRid), new Uint8Array()]; +} - window.__bootstrap.flash = { - createServe, - upgradeHttpRaw, - }; -})(this); +export { createServe, upgradeHttpRaw }; diff --git a/ext/flash/Cargo.toml b/ext/flash/Cargo.toml index d62dacd1d02b16..3bcfb25f7b4347 100644 --- a/ext/flash/Cargo.toml +++ b/ext/flash/Cargo.toml @@ -2,7 +2,7 @@ [package] name = "deno_flash" -version = "0.25.0" +version = "0.27.0" authors.workspace = true edition.workspace = true license.workspace = true diff --git a/ext/flash/lib.rs b/ext/flash/lib.rs index d31e78caae4736..4ae064173e1a4f 100644 --- a/ext/flash/lib.rs +++ b/ext/flash/lib.rs @@ -665,6 +665,26 @@ fn op_flash_headers( ) } +#[op] +fn op_flash_addr( + state: Rc>, + server_id: u32, + token: u32, +) -> Result<(String, u16), AnyError> { + let mut op_state = state.borrow_mut(); + let flash_ctx = op_state.borrow_mut::(); + let ctx = flash_ctx + .servers + .get_mut(&server_id) + .ok_or_else(|| type_error("server closed"))?; + let req = &ctx + .requests + .get(&token) + .ok_or_else(|| type_error("request closed"))?; + let socket = req.socket(); + Ok((socket.addr.ip().to_string(), socket.addr.port())) +} + // Remember the first packet we read? It probably also has some body data. This op quickly copies it into // a buffer and sets up channels for streaming the rest. #[op] @@ -934,7 +954,7 @@ fn run_server( match token { Token(0) => loop { match listener.accept() { - Ok((mut socket, _)) => { + Ok((mut socket, addr)) => { counter += 1; let token = Token(counter); poll @@ -960,6 +980,7 @@ fn run_server( read_lock: Arc::new(Mutex::new(())), parse_done: ParseStatus::None, buffer: UnsafeCell::new(vec![0_u8; 1024]), + addr, }); trace!("New connection: {}", token.0); @@ -1514,10 +1535,7 @@ pub fn init(unstable: bool) -> Extension { "deno_websocket", "deno_http", ]) - .js(deno_core::include_js_files!( - prefix "internal:ext/flash", - "01_http.js", - )) + .esm(deno_core::include_js_files!("01_http.js",)) .ops(vec![ op_flash_serve::decl::

(), op_node_unstable_flash_serve::decl::

(), @@ -1527,6 +1545,7 @@ pub fn init(unstable: bool) -> Extension { op_flash_method::decl(), op_flash_path::decl(), op_flash_headers::decl(), + op_flash_addr::decl(), op_flash_next::decl(), op_flash_next_server::decl(), op_flash_next_async::decl(), diff --git a/ext/flash/socket.rs b/ext/flash/socket.rs index 27906e74e33f90..cf9501634bfe52 100644 --- a/ext/flash/socket.rs +++ b/ext/flash/socket.rs @@ -29,6 +29,7 @@ pub struct Stream { pub parse_done: ParseStatus, pub buffer: UnsafeCell>, pub read_lock: Arc>, + pub addr: std::net::SocketAddr, } impl Stream { diff --git a/ext/http/01_http.js b/ext/http/01_http.js index cb98d246d801aa..f9e15e7d59e6c1 100644 --- a/ext/http/01_http.js +++ b/ext/http/01_http.js @@ -1,296 +1,320 @@ // Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. -"use strict"; - -((window) => { - const webidl = window.__bootstrap.webidl; - const { InnerBody } = window.__bootstrap.fetchBody; - const { Event } = window.__bootstrap.event; - const { setEventTargetData } = window.__bootstrap.eventTarget; - const { BlobPrototype } = window.__bootstrap.file; - const { - ResponsePrototype, - fromInnerRequest, - toInnerResponse, - newInnerRequest, - newInnerResponse, - fromInnerResponse, - _flash, - } = window.__bootstrap.fetch; - const core = window.Deno.core; - const { BadResourcePrototype, InterruptedPrototype, ops } = core; - const { ReadableStreamPrototype } = window.__bootstrap.streams; - const abortSignal = window.__bootstrap.abortSignal; - const { - WebSocket, - _rid, - _readyState, - _eventLoop, - _protocol, - _server, - _idleTimeoutDuration, - _idleTimeoutTimeout, - _serverHandleIdleTimeout, - } = window.__bootstrap.webSocket; - const { TcpConn, UnixConn } = window.__bootstrap.net; - const { TlsConn } = window.__bootstrap.tls; - const { - Deferred, - getReadableStreamResourceBacking, - readableStreamForRid, - readableStreamClose, - } = window.__bootstrap.streams; - const { - ArrayPrototypeIncludes, - ArrayPrototypePush, - ArrayPrototypeSome, - Error, - ObjectPrototypeIsPrototypeOf, - SafeSetIterator, - Set, - SetPrototypeAdd, - SetPrototypeDelete, - StringPrototypeIncludes, - StringPrototypeToLowerCase, - StringPrototypeSplit, - Symbol, - SymbolAsyncIterator, - TypeError, - Uint8Array, - Uint8ArrayPrototype, - } = window.__bootstrap.primordials; - - const connErrorSymbol = Symbol("connError"); - const _deferred = Symbol("upgradeHttpDeferred"); - - class HttpConn { - #rid = 0; - #closed = false; - #remoteAddr; - #localAddr; - - // This set holds resource ids of resources - // that were created during lifecycle of this request. - // When the connection is closed these resources should be closed - // as well. - managedResources = new Set(); - - constructor(rid, remoteAddr, localAddr) { - this.#rid = rid; - this.#remoteAddr = remoteAddr; - this.#localAddr = localAddr; - } - - /** @returns {number} */ - get rid() { - return this.#rid; - } +const core = globalThis.Deno.core; +const internals = globalThis.__bootstrap.internals; +const primordials = globalThis.__bootstrap.primordials; +const { BadResourcePrototype, InterruptedPrototype, ops } = core; +import * as webidl from "internal:deno_webidl/00_webidl.js"; +import { InnerBody } from "internal:deno_fetch/22_body.js"; +import { Event, setEventTargetData } from "internal:deno_web/02_event.js"; +import { BlobPrototype } from "internal:deno_web/09_file.js"; +import { + fromInnerResponse, + newInnerResponse, + ResponsePrototype, + toInnerResponse, +} from "internal:deno_fetch/23_response.js"; +import { + _flash, + fromInnerRequest, + newInnerRequest, +} from "internal:deno_fetch/23_request.js"; +import * as abortSignal from "internal:deno_web/03_abort_signal.js"; +import { + _eventLoop, + _idleTimeoutDuration, + _idleTimeoutTimeout, + _protocol, + _readyState, + _rid, + _server, + _serverHandleIdleTimeout, + WebSocket, +} from "internal:deno_websocket/01_websocket.js"; +import { TcpConn, UnixConn } from "internal:deno_net/01_net.js"; +import { TlsConn } from "internal:deno_net/02_tls.js"; +import { + Deferred, + getReadableStreamResourceBacking, + readableStreamClose, + readableStreamForRid, + ReadableStreamPrototype, +} from "internal:deno_web/06_streams.js"; +const { + ArrayPrototypeIncludes, + ArrayPrototypeMap, + ArrayPrototypePush, + Error, + ObjectPrototypeIsPrototypeOf, + SafeSetIterator, + Set, + SetPrototypeAdd, + SetPrototypeDelete, + StringPrototypeCharCodeAt, + StringPrototypeIncludes, + StringPrototypeToLowerCase, + StringPrototypeSplit, + Symbol, + SymbolAsyncIterator, + TypeError, + Uint8Array, + Uint8ArrayPrototype, +} = primordials; + +const connErrorSymbol = Symbol("connError"); +const _deferred = Symbol("upgradeHttpDeferred"); + +class HttpConn { + #rid = 0; + #closed = false; + #remoteAddr; + #localAddr; + + // This set holds resource ids of resources + // that were created during lifecycle of this request. + // When the connection is closed these resources should be closed + // as well. + managedResources = new Set(); + + constructor(rid, remoteAddr, localAddr) { + this.#rid = rid; + this.#remoteAddr = remoteAddr; + this.#localAddr = localAddr; + } - /** @returns {Promise} */ - async nextRequest() { - let nextRequest; - try { - nextRequest = await core.opAsync("op_http_accept", this.#rid); - } catch (error) { - this.close(); - // A connection error seen here would cause disrupted responses to throw - // a generic `BadResource` error. Instead store this error and replace - // those with it. - this[connErrorSymbol] = error; - if ( - ObjectPrototypeIsPrototypeOf(BadResourcePrototype, error) || - ObjectPrototypeIsPrototypeOf(InterruptedPrototype, error) || - StringPrototypeIncludes(error.message, "connection closed") - ) { - return null; - } - throw error; - } - if (nextRequest == null) { - // Work-around for servers (deno_std/http in particular) that call - // `nextRequest()` before upgrading a previous request which has a - // `connection: upgrade` header. - await null; + /** @returns {number} */ + get rid() { + return this.#rid; + } - this.close(); + /** @returns {Promise} */ + async nextRequest() { + let nextRequest; + try { + nextRequest = await core.opAsync("op_http_accept", this.#rid); + } catch (error) { + this.close(); + // A connection error seen here would cause disrupted responses to throw + // a generic `BadResource` error. Instead store this error and replace + // those with it. + this[connErrorSymbol] = error; + if ( + ObjectPrototypeIsPrototypeOf(BadResourcePrototype, error) || + ObjectPrototypeIsPrototypeOf(InterruptedPrototype, error) || + StringPrototypeIncludes(error.message, "connection closed") + ) { return null; } + throw error; + } + if (nextRequest == null) { + // Work-around for servers (deno_std/http in particular) that call + // `nextRequest()` before upgrading a previous request which has a + // `connection: upgrade` header. + await null; + + this.close(); + return null; + } - const { 0: streamRid, 1: method, 2: url } = nextRequest; - SetPrototypeAdd(this.managedResources, streamRid); - - /** @type {ReadableStream | undefined} */ - let body = null; - // There might be a body, but we don't expose it for GET/HEAD requests. - // It will be closed automatically once the request has been handled and - // the response has been sent. - if (method !== "GET" && method !== "HEAD") { - body = readableStreamForRid(streamRid, false); - } - - const innerRequest = newInnerRequest( - () => method, - url, - () => ops.op_http_headers(streamRid), - body !== null ? new InnerBody(body) : null, - false, - ); - const signal = abortSignal.newSignal(); - const request = fromInnerRequest( - innerRequest, - signal, - "immutable", - false, - ); - - const respondWith = createRespondWith( - this, - streamRid, - request, - this.#remoteAddr, - this.#localAddr, - ); + const { 0: streamRid, 1: method, 2: url } = nextRequest; + SetPrototypeAdd(this.managedResources, streamRid); - return { request, respondWith }; + /** @type {ReadableStream | undefined} */ + let body = null; + // There might be a body, but we don't expose it for GET/HEAD requests. + // It will be closed automatically once the request has been handled and + // the response has been sent. + if (method !== "GET" && method !== "HEAD") { + body = readableStreamForRid(streamRid, false); } - /** @returns {void} */ - close() { - if (!this.#closed) { - this.#closed = true; - core.close(this.#rid); - for (const rid of new SafeSetIterator(this.managedResources)) { - SetPrototypeDelete(this.managedResources, rid); - core.close(rid); - } - } - } + const innerRequest = newInnerRequest( + () => method, + url, + () => ops.op_http_headers(streamRid), + body !== null ? new InnerBody(body) : null, + false, + ); + const signal = abortSignal.newSignal(); + const request = fromInnerRequest( + innerRequest, + signal, + "immutable", + false, + ); + + const respondWith = createRespondWith( + this, + streamRid, + request, + this.#remoteAddr, + this.#localAddr, + ); + + return { request, respondWith }; + } - [SymbolAsyncIterator]() { - // deno-lint-ignore no-this-alias - const httpConn = this; - return { - async next() { - const reqEvt = await httpConn.nextRequest(); - // Change with caution, current form avoids a v8 deopt - return { value: reqEvt ?? undefined, done: reqEvt === null }; - }, - }; + /** @returns {void} */ + close() { + if (!this.#closed) { + this.#closed = true; + core.close(this.#rid); + for (const rid of new SafeSetIterator(this.managedResources)) { + SetPrototypeDelete(this.managedResources, rid); + core.close(rid); + } } } - function createRespondWith( - httpConn, - streamRid, - request, - remoteAddr, - localAddr, - ) { - return async function respondWith(resp) { - try { - resp = await resp; - if (!(ObjectPrototypeIsPrototypeOf(ResponsePrototype, resp))) { - throw new TypeError( - "First argument to respondWith must be a Response or a promise resolving to a Response.", - ); - } + [SymbolAsyncIterator]() { + // deno-lint-ignore no-this-alias + const httpConn = this; + return { + async next() { + const reqEvt = await httpConn.nextRequest(); + // Change with caution, current form avoids a v8 deopt + return { value: reqEvt ?? undefined, done: reqEvt === null }; + }, + }; + } +} + +function createRespondWith( + httpConn, + streamRid, + request, + remoteAddr, + localAddr, +) { + return async function respondWith(resp) { + try { + resp = await resp; + if (!(ObjectPrototypeIsPrototypeOf(ResponsePrototype, resp))) { + throw new TypeError( + "First argument to respondWith must be a Response or a promise resolving to a Response.", + ); + } - const innerResp = toInnerResponse(resp); + const innerResp = toInnerResponse(resp); - // If response body length is known, it will be sent synchronously in a - // single op, in other case a "response body" resource will be created and - // we'll be streaming it. - /** @type {ReadableStream | Uint8Array | null} */ - let respBody = null; - if (innerResp.body !== null) { - if (innerResp.body.unusable()) { - throw new TypeError("Body is unusable."); - } + // If response body length is known, it will be sent synchronously in a + // single op, in other case a "response body" resource will be created and + // we'll be streaming it. + /** @type {ReadableStream | Uint8Array | null} */ + let respBody = null; + if (innerResp.body !== null) { + if (innerResp.body.unusable()) { + throw new TypeError("Body is unusable."); + } + if ( + ObjectPrototypeIsPrototypeOf( + ReadableStreamPrototype, + innerResp.body.streamOrStatic, + ) + ) { if ( + innerResp.body.length === null || ObjectPrototypeIsPrototypeOf( - ReadableStreamPrototype, - innerResp.body.streamOrStatic, + BlobPrototype, + innerResp.body.source, ) ) { - if ( - innerResp.body.length === null || - ObjectPrototypeIsPrototypeOf( - BlobPrototype, - innerResp.body.source, - ) - ) { - respBody = innerResp.body.stream; + respBody = innerResp.body.stream; + } else { + const reader = innerResp.body.stream.getReader(); + const r1 = await reader.read(); + if (r1.done) { + respBody = new Uint8Array(0); } else { - const reader = innerResp.body.stream.getReader(); - const r1 = await reader.read(); - if (r1.done) { - respBody = new Uint8Array(0); - } else { - respBody = r1.value; - const r2 = await reader.read(); - if (!r2.done) throw new TypeError("Unreachable"); - } + respBody = r1.value; + const r2 = await reader.read(); + if (!r2.done) throw new TypeError("Unreachable"); } - } else { - innerResp.body.streamOrStatic.consumed = true; - respBody = innerResp.body.streamOrStatic.body; } } else { - respBody = new Uint8Array(0); + innerResp.body.streamOrStatic.consumed = true; + respBody = innerResp.body.streamOrStatic.body; } - const isStreamingResponseBody = !( - typeof respBody === "string" || - ObjectPrototypeIsPrototypeOf(Uint8ArrayPrototype, respBody) + } else { + respBody = new Uint8Array(0); + } + const isStreamingResponseBody = !( + typeof respBody === "string" || + ObjectPrototypeIsPrototypeOf(Uint8ArrayPrototype, respBody) + ); + try { + await core.opAsync( + "op_http_write_headers", + streamRid, + innerResp.status ?? 200, + innerResp.headerList, + isStreamingResponseBody ? null : respBody, ); - try { - await core.opAsync( - "op_http_write_headers", - streamRid, - innerResp.status ?? 200, - innerResp.headerList, - isStreamingResponseBody ? null : respBody, - ); - } catch (error) { - const connError = httpConn[connErrorSymbol]; - if ( - ObjectPrototypeIsPrototypeOf(BadResourcePrototype, error) && - connError != null - ) { - // deno-lint-ignore no-ex-assign - error = new connError.constructor(connError.message); - } - if ( - respBody !== null && - ObjectPrototypeIsPrototypeOf(ReadableStreamPrototype, respBody) - ) { - await respBody.cancel(error); - } - throw error; + } catch (error) { + const connError = httpConn[connErrorSymbol]; + if ( + ObjectPrototypeIsPrototypeOf(BadResourcePrototype, error) && + connError != null + ) { + // deno-lint-ignore no-ex-assign + error = new connError.constructor(connError.message); + } + if ( + respBody !== null && + ObjectPrototypeIsPrototypeOf(ReadableStreamPrototype, respBody) + ) { + await respBody.cancel(error); } + throw error; + } - if (isStreamingResponseBody) { - let success = false; - if ( - respBody === null || - !ObjectPrototypeIsPrototypeOf(ReadableStreamPrototype, respBody) - ) { - throw new TypeError("Unreachable"); + if (isStreamingResponseBody) { + let success = false; + if ( + respBody === null || + !ObjectPrototypeIsPrototypeOf(ReadableStreamPrototype, respBody) + ) { + throw new TypeError("Unreachable"); + } + const resourceBacking = getReadableStreamResourceBacking(respBody); + let reader; + if (resourceBacking) { + if (respBody.locked) { + throw new TypeError("ReadableStream is locked."); } - const resourceBacking = getReadableStreamResourceBacking(respBody); - let reader; - if (resourceBacking) { - if (respBody.locked) { - throw new TypeError("ReadableStream is locked."); + reader = respBody.getReader(); // Aquire JS lock. + try { + await core.opAsync( + "op_http_write_resource", + streamRid, + resourceBacking.rid, + ); + if (resourceBacking.autoClose) core.tryClose(resourceBacking.rid); + readableStreamClose(respBody); // Release JS lock. + success = true; + } catch (error) { + const connError = httpConn[connErrorSymbol]; + if ( + ObjectPrototypeIsPrototypeOf(BadResourcePrototype, error) && + connError != null + ) { + // deno-lint-ignore no-ex-assign + error = new connError.constructor(connError.message); + } + await reader.cancel(error); + throw error; + } + } else { + reader = respBody.getReader(); + while (true) { + const { value, done } = await reader.read(); + if (done) break; + if (!ObjectPrototypeIsPrototypeOf(Uint8ArrayPrototype, value)) { + await reader.cancel(new TypeError("Value not a Uint8Array")); + break; } - reader = respBody.getReader(); // Aquire JS lock. try { - await core.opAsync( - "op_http_write_resource", - streamRid, - resourceBacking.rid, - ); - if (resourceBacking.autoClose) core.tryClose(resourceBacking.rid); - readableStreamClose(respBody); // Release JS lock. - success = true; + await core.opAsync("op_http_write", streamRid, value); } catch (error) { const connError = httpConn[connErrorSymbol]; if ( @@ -303,176 +327,216 @@ await reader.cancel(error); throw error; } - } else { - reader = respBody.getReader(); - while (true) { - const { value, done } = await reader.read(); - if (done) break; - if (!ObjectPrototypeIsPrototypeOf(Uint8ArrayPrototype, value)) { - await reader.cancel(new TypeError("Value not a Uint8Array")); - break; - } - try { - await core.opAsync("op_http_write", streamRid, value); - } catch (error) { - const connError = httpConn[connErrorSymbol]; - if ( - ObjectPrototypeIsPrototypeOf(BadResourcePrototype, error) && - connError != null - ) { - // deno-lint-ignore no-ex-assign - error = new connError.constructor(connError.message); - } - await reader.cancel(error); - throw error; - } - } - success = true; - } - - if (success) { - try { - await core.opAsync("op_http_shutdown", streamRid); - } catch (error) { - await reader.cancel(error); - throw error; - } } + success = true; } - const deferred = request[_deferred]; - if (deferred) { - const res = await core.opAsync("op_http_upgrade", streamRid); - let conn; - if (res.connType === "tcp") { - conn = new TcpConn(res.connRid, remoteAddr, localAddr); - } else if (res.connType === "tls") { - conn = new TlsConn(res.connRid, remoteAddr, localAddr); - } else if (res.connType === "unix") { - conn = new UnixConn(res.connRid, remoteAddr, localAddr); - } else { - throw new Error("unreachable"); + if (success) { + try { + await core.opAsync("op_http_shutdown", streamRid); + } catch (error) { + await reader.cancel(error); + throw error; } + } + } - deferred.resolve([conn, res.readBuf]); + const deferred = request[_deferred]; + if (deferred) { + const res = await core.opAsync("op_http_upgrade", streamRid); + let conn; + if (res.connType === "tcp") { + conn = new TcpConn(res.connRid, remoteAddr, localAddr); + } else if (res.connType === "tls") { + conn = new TlsConn(res.connRid, remoteAddr, localAddr); + } else if (res.connType === "unix") { + conn = new UnixConn(res.connRid, remoteAddr, localAddr); + } else { + throw new Error("unreachable"); } - const ws = resp[_ws]; - if (ws) { - const wsRid = await core.opAsync( - "op_http_upgrade_websocket", - streamRid, - ); - ws[_rid] = wsRid; - ws[_protocol] = resp.headers.get("sec-websocket-protocol"); - httpConn.close(); + deferred.resolve([conn, res.readBuf]); + } + const ws = resp[_ws]; + if (ws) { + const wsRid = await core.opAsync( + "op_http_upgrade_websocket", + streamRid, + ); + ws[_rid] = wsRid; + ws[_protocol] = resp.headers.get("sec-websocket-protocol"); - ws[_readyState] = WebSocket.OPEN; - const event = new Event("open"); - ws.dispatchEvent(event); + httpConn.close(); - ws[_eventLoop](); - if (ws[_idleTimeoutDuration]) { - ws.addEventListener( - "close", - () => clearTimeout(ws[_idleTimeoutTimeout]), - ); - } - ws[_serverHandleIdleTimeout](); - } - } finally { - if (SetPrototypeDelete(httpConn.managedResources, streamRid)) { - core.close(streamRid); + ws[_readyState] = WebSocket.OPEN; + const event = new Event("open"); + ws.dispatchEvent(event); + + ws[_eventLoop](); + if (ws[_idleTimeoutDuration]) { + ws.addEventListener( + "close", + () => clearTimeout(ws[_idleTimeoutTimeout]), + ); } + ws[_serverHandleIdleTimeout](); } - }; + } finally { + if (SetPrototypeDelete(httpConn.managedResources, streamRid)) { + core.close(streamRid); + } + } + }; +} + +const _ws = Symbol("[[associated_ws]]"); +const websocketCvf = buildCaseInsensitiveCommaValueFinder("websocket"); +const upgradeCvf = buildCaseInsensitiveCommaValueFinder("upgrade"); + +function upgradeWebSocket(request, options = {}) { + const upgrade = request.headers.get("upgrade"); + const upgradeHasWebSocketOption = upgrade !== null && + websocketCvf(upgrade); + if (!upgradeHasWebSocketOption) { + throw new TypeError( + "Invalid Header: 'upgrade' header must contain 'websocket'", + ); } - const _ws = Symbol("[[associated_ws]]"); + const connection = request.headers.get("connection"); + const connectionHasUpgradeOption = connection !== null && + upgradeCvf(connection); + if (!connectionHasUpgradeOption) { + throw new TypeError( + "Invalid Header: 'connection' header must contain 'Upgrade'", + ); + } - function upgradeWebSocket(request, options = {}) { - const upgrade = request.headers.get("upgrade"); - const upgradeHasWebSocketOption = upgrade !== null && - ArrayPrototypeSome( - StringPrototypeSplit(upgrade, /\s*,\s*/), - (option) => StringPrototypeToLowerCase(option) === "websocket", - ); - if (!upgradeHasWebSocketOption) { - throw new TypeError( - "Invalid Header: 'upgrade' header must contain 'websocket'", - ); - } + const websocketKey = request.headers.get("sec-websocket-key"); + if (websocketKey === null) { + throw new TypeError( + "Invalid Header: 'sec-websocket-key' header must be set", + ); + } - const connection = request.headers.get("connection"); - const connectionHasUpgradeOption = connection !== null && - ArrayPrototypeSome( - StringPrototypeSplit(connection, /\s*,\s*/), - (option) => StringPrototypeToLowerCase(option) === "upgrade", - ); - if (!connectionHasUpgradeOption) { + const accept = ops.op_http_websocket_accept_header(websocketKey); + + const r = newInnerResponse(101); + r.headerList = [ + ["upgrade", "websocket"], + ["connection", "Upgrade"], + ["sec-websocket-accept", accept], + ]; + + const protocolsStr = request.headers.get("sec-websocket-protocol") || ""; + const protocols = StringPrototypeSplit(protocolsStr, ", "); + if (protocols && options.protocol) { + if (ArrayPrototypeIncludes(protocols, options.protocol)) { + ArrayPrototypePush(r.headerList, [ + "sec-websocket-protocol", + options.protocol, + ]); + } else { throw new TypeError( - "Invalid Header: 'connection' header must contain 'Upgrade'", + `Protocol '${options.protocol}' not in the request's protocol list (non negotiable)`, ); } + } - const websocketKey = request.headers.get("sec-websocket-key"); - if (websocketKey === null) { - throw new TypeError( - "Invalid Header: 'sec-websocket-key' header must be set", - ); - } + const response = fromInnerResponse(r, "immutable"); + + const socket = webidl.createBranded(WebSocket); + setEventTargetData(socket); + socket[_server] = true; + response[_ws] = socket; + socket[_idleTimeoutDuration] = options.idleTimeout ?? 120; + socket[_idleTimeoutTimeout] = null; + + return { response, socket }; +} - const accept = ops.op_http_websocket_accept_header(websocketKey); - - const r = newInnerResponse(101); - r.headerList = [ - ["upgrade", "websocket"], - ["connection", "Upgrade"], - ["sec-websocket-accept", accept], - ]; - - const protocolsStr = request.headers.get("sec-websocket-protocol") || ""; - const protocols = StringPrototypeSplit(protocolsStr, ", "); - if (protocols && options.protocol) { - if (ArrayPrototypeIncludes(protocols, options.protocol)) { - ArrayPrototypePush(r.headerList, [ - "sec-websocket-protocol", - options.protocol, - ]); +function upgradeHttp(req) { + if (req[_flash]) { + throw new TypeError( + "Flash requests can not be upgraded with `upgradeHttp`. Use `upgradeHttpRaw` instead.", + ); + } + + req[_deferred] = new Deferred(); + return req[_deferred].promise; +} + +const spaceCharCode = StringPrototypeCharCodeAt(" ", 0); +const tabCharCode = StringPrototypeCharCodeAt("\t", 0); +const commaCharCode = StringPrototypeCharCodeAt(",", 0); + +/** Builds a case function that can be used to find a case insensitive + * value in some text that's separated by commas. + * + * This is done because it doesn't require any allocations. + * @param checkText {string} - The text to find. (ex. "websocket") + */ +function buildCaseInsensitiveCommaValueFinder(checkText) { + const charCodes = ArrayPrototypeMap( + StringPrototypeSplit( + StringPrototypeToLowerCase(checkText), + "", + ), + (c) => [c.charCodeAt(0), c.toUpperCase().charCodeAt(0)], + ); + /** @type {number} */ + let i; + /** @type {number} */ + let char; + + /** @param value {string} */ + return function (value) { + for (i = 0; i < value.length; i++) { + char = value.charCodeAt(i); + skipWhitespace(value); + + if (hasWord(value)) { + skipWhitespace(value); + if (i === value.length || char === commaCharCode) { + return true; + } } else { - throw new TypeError( - `Protocol '${options.protocol}' not in the request's protocol list (non negotiable)`, - ); + skipUntilComma(value); } } - const response = fromInnerResponse(r, "immutable"); - - const socket = webidl.createBranded(WebSocket); - setEventTargetData(socket); - socket[_server] = true; - response[_ws] = socket; - socket[_idleTimeoutDuration] = options.idleTimeout ?? 120; - socket[_idleTimeoutTimeout] = null; + return false; + }; - return { response, socket }; + /** @param value {string} */ + function hasWord(value) { + for (const [cLower, cUpper] of charCodes) { + if (cLower === char || cUpper === char) { + char = StringPrototypeCharCodeAt(value, ++i); + } else { + return false; + } + } + return true; } - function upgradeHttp(req) { - if (req[_flash]) { - throw new TypeError( - "Flash requests can not be upgraded with `upgradeHttp`. Use `upgradeHttpRaw` instead.", - ); + /** @param value {string} */ + function skipWhitespace(value) { + while (char === spaceCharCode || char === tabCharCode) { + char = StringPrototypeCharCodeAt(value, ++i); } + } - req[_deferred] = new Deferred(); - return req[_deferred].promise; + /** @param value {string} */ + function skipUntilComma(value) { + while (char !== commaCharCode && i < value.length) { + char = StringPrototypeCharCodeAt(value, ++i); + } } +} - window.__bootstrap.http = { - HttpConn, - upgradeWebSocket, - upgradeHttp, - _ws, - }; -})(this); +// Expose this function for unit tests +internals.buildCaseInsensitiveCommaValueFinder = + buildCaseInsensitiveCommaValueFinder; + +export { _ws, HttpConn, upgradeHttp, upgradeWebSocket }; diff --git a/ext/http/Cargo.toml b/ext/http/Cargo.toml index c09f59bf21ec02..7b53f2f85c34fb 100644 --- a/ext/http/Cargo.toml +++ b/ext/http/Cargo.toml @@ -2,7 +2,7 @@ [package] name = "deno_http" -version = "0.84.0" +version = "0.86.0" authors.workspace = true edition.workspace = true license.workspace = true diff --git a/ext/http/lib.rs b/ext/http/lib.rs index 0e3ebf766d8d97..eb738177356292 100644 --- a/ext/http/lib.rs +++ b/ext/http/lib.rs @@ -80,10 +80,7 @@ mod reader_stream; pub fn init() -> Extension { Extension::builder(env!("CARGO_PKG_NAME")) .dependencies(vec!["deno_web", "deno_net", "deno_fetch", "deno_websocket"]) - .js(include_js_files!( - prefix "internal:ext/http", - "01_http.js", - )) + .esm(include_js_files!("01_http.js",)) .ops(vec![ op_http_accept::decl(), op_http_write_headers::decl(), diff --git a/ext/napi/Cargo.toml b/ext/napi/Cargo.toml index c9ae4acd9831b8..4b61714720fc65 100644 --- a/ext/napi/Cargo.toml +++ b/ext/napi/Cargo.toml @@ -2,7 +2,7 @@ [package] name = "deno_napi" -version = "0.19.0" +version = "0.21.0" authors.workspace = true edition.workspace = true license.workspace = true diff --git a/ext/napi/lib.rs b/ext/napi/lib.rs index 59a07136df5d2f..628ecd5a25eff7 100644 --- a/ext/napi/lib.rs +++ b/ext/napi/lib.rs @@ -514,7 +514,7 @@ impl Env { } } -pub fn init(unstable: bool) -> Extension { +pub fn init() -> Extension { Extension::builder(env!("CARGO_PKG_NAME")) .ops(vec![op_napi_open::decl::

()]) .event_loop_middleware(|op_state_rc, cx| { @@ -578,7 +578,6 @@ pub fn init(unstable: bool) -> Extension { env_cleanup_hooks: Rc::new(RefCell::new(vec![])), tsfn_ref_counters: Arc::new(Mutex::new(vec![])), }); - state.put(Unstable(unstable)); Ok(()) }) .build() @@ -589,17 +588,6 @@ pub trait NapiPermissions { -> std::result::Result<(), AnyError>; } -pub struct Unstable(pub bool); - -fn check_unstable(state: &OpState) { - let unstable = state.borrow::(); - - if !unstable.0 { - eprintln!("Unstable API 'node-api'. The --unstable flag must be provided."); - std::process::exit(70); - } -} - #[op(v8)] fn op_napi_open( scope: &mut v8::HandleScope<'scope>, @@ -610,7 +598,6 @@ fn op_napi_open( where NP: NapiPermissions + 'static, { - check_unstable(op_state); let permissions = op_state.borrow_mut::(); permissions.check(Some(&PathBuf::from(&path)))?; diff --git a/ext/net/01_net.js b/ext/net/01_net.js index a6043786f5bdb5..c56f837b8a7522 100644 --- a/ext/net/01_net.js +++ b/ext/net/01_net.js @@ -1,423 +1,445 @@ // Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. -"use strict"; - -((window) => { - const core = window.Deno.core; - const { BadResourcePrototype, InterruptedPrototype, ops } = core; - const { - readableStreamForRidUnrefable, - readableStreamForRidUnrefableRef, - readableStreamForRidUnrefableUnref, - writableStreamForRid, - } = window.__bootstrap.streams; - const { - Error, - ObjectPrototypeIsPrototypeOf, - PromiseResolve, - SymbolAsyncIterator, - SymbolFor, - TypedArrayPrototypeSubarray, - TypeError, - Uint8Array, - } = window.__bootstrap.primordials; - - const promiseIdSymbol = SymbolFor("Deno.core.internalPromiseId"); - - async function write(rid, data) { - return await core.write(rid, data); - } - - function shutdown(rid) { - return core.shutdown(rid); - } - - function resolveDns(query, recordType, options) { - return core.opAsync("op_dns_resolve", { query, recordType, options }); - } - - class Conn { - #rid = 0; - #remoteAddr = null; - #localAddr = null; - #unref = false; - #pendingReadPromiseIds = []; - - #readable; - #writable; - - constructor(rid, remoteAddr, localAddr) { - this.#rid = rid; - this.#remoteAddr = remoteAddr; - this.#localAddr = localAddr; - } - get rid() { - return this.#rid; - } +const core = globalThis.Deno.core; +const { BadResourcePrototype, InterruptedPrototype, ops } = core; +import { + readableStreamForRidUnrefable, + readableStreamForRidUnrefableRef, + readableStreamForRidUnrefableUnref, + writableStreamForRid, +} from "internal:deno_web/06_streams.js"; +import * as abortSignal from "internal:deno_web/03_abort_signal.js"; +const primordials = globalThis.__bootstrap.primordials; +const { + Error, + ObjectPrototypeIsPrototypeOf, + PromiseResolve, + SymbolAsyncIterator, + SymbolFor, + TypedArrayPrototypeSubarray, + TypeError, + Uint8Array, +} = primordials; + +const promiseIdSymbol = SymbolFor("Deno.core.internalPromiseId"); + +async function write(rid, data) { + return await core.write(rid, data); +} + +function shutdown(rid) { + return core.shutdown(rid); +} + +async function resolveDns(query, recordType, options) { + let cancelRid; + let abortHandler; + if (options?.signal) { + options.signal.throwIfAborted(); + cancelRid = ops.op_cancel_handle(); + abortHandler = () => core.tryClose(cancelRid); + options.signal[abortSignal.add](abortHandler); + } - get remoteAddr() { - return this.#remoteAddr; - } + try { + return await core.opAsync("op_dns_resolve", { + cancelRid, + query, + recordType, + options, + }); + } finally { + if (options?.signal) { + options.signal[abortSignal.remove](abortHandler); - get localAddr() { - return this.#localAddr; + // always throw the abort error when aborted + options.signal.throwIfAborted(); } + } +} + +class Conn { + #rid = 0; + #remoteAddr = null; + #localAddr = null; + #unref = false; + #pendingReadPromiseIds = []; + + #readable; + #writable; + + constructor(rid, remoteAddr, localAddr) { + this.#rid = rid; + this.#remoteAddr = remoteAddr; + this.#localAddr = localAddr; + } - write(p) { - return write(this.rid, p); - } + get rid() { + return this.#rid; + } - async read(buffer) { - if (buffer.length === 0) { - return 0; - } - const promise = core.read(this.rid, buffer); - const promiseId = promise[promiseIdSymbol]; - if (this.#unref) core.unrefOp(promiseId); - this.#pendingReadPromiseIds.push(promiseId); - let nread; - try { - nread = await promise; - } catch (e) { - throw e; - } finally { - this.#pendingReadPromiseIds = this.#pendingReadPromiseIds.filter((id) => - id !== promiseId - ); - } - return nread === 0 ? null : nread; - } + get remoteAddr() { + return this.#remoteAddr; + } - close() { - core.close(this.rid); - } + get localAddr() { + return this.#localAddr; + } - closeWrite() { - return shutdown(this.rid); - } + write(p) { + return write(this.rid, p); + } - get readable() { - if (this.#readable === undefined) { - this.#readable = readableStreamForRidUnrefable(this.rid); - if (this.#unref) { - readableStreamForRidUnrefableUnref(this.#readable); - } - } - return this.#readable; - } + async read(buffer) { + if (buffer.length === 0) { + return 0; + } + const promise = core.read(this.rid, buffer); + const promiseId = promise[promiseIdSymbol]; + if (this.#unref) core.unrefOp(promiseId); + this.#pendingReadPromiseIds.push(promiseId); + let nread; + try { + nread = await promise; + } catch (e) { + throw e; + } finally { + this.#pendingReadPromiseIds = this.#pendingReadPromiseIds.filter((id) => + id !== promiseId + ); + } + return nread === 0 ? null : nread; + } - get writable() { - if (this.#writable === undefined) { - this.#writable = writableStreamForRid(this.rid); - } - return this.#writable; - } + close() { + core.close(this.rid); + } - ref() { - this.#unref = false; - if (this.#readable) { - readableStreamForRidUnrefableRef(this.#readable); - } - this.#pendingReadPromiseIds.forEach((id) => core.refOp(id)); - } + closeWrite() { + return shutdown(this.rid); + } - unref() { - this.#unref = true; - if (this.#readable) { + get readable() { + if (this.#readable === undefined) { + this.#readable = readableStreamForRidUnrefable(this.rid); + if (this.#unref) { readableStreamForRidUnrefableUnref(this.#readable); } - this.#pendingReadPromiseIds.forEach((id) => core.unrefOp(id)); } + return this.#readable; } - class TcpConn extends Conn { - setNoDelay(noDelay = true) { - return ops.op_set_nodelay(this.rid, noDelay); + get writable() { + if (this.#writable === undefined) { + this.#writable = writableStreamForRid(this.rid); } + return this.#writable; + } - setKeepAlive(keepAlive = true) { - return ops.op_set_keepalive(this.rid, keepAlive); + ref() { + this.#unref = false; + if (this.#readable) { + readableStreamForRidUnrefableRef(this.#readable); } + this.#pendingReadPromiseIds.forEach((id) => core.refOp(id)); } - class UnixConn extends Conn {} - - class Listener { - #rid = 0; - #addr = null; - #unref = false; - #promiseId = null; - - constructor(rid, addr) { - this.#rid = rid; - this.#addr = addr; + unref() { + this.#unref = true; + if (this.#readable) { + readableStreamForRidUnrefableUnref(this.#readable); } + this.#pendingReadPromiseIds.forEach((id) => core.unrefOp(id)); + } +} - get rid() { - return this.#rid; - } +class TcpConn extends Conn { + setNoDelay(noDelay = true) { + return ops.op_set_nodelay(this.rid, noDelay); + } - get addr() { - return this.#addr; - } + setKeepAlive(keepAlive = true) { + return ops.op_set_keepalive(this.rid, keepAlive); + } +} - async accept() { - let promise; - switch (this.addr.transport) { - case "tcp": - promise = core.opAsync("op_net_accept_tcp", this.rid); - break; - case "unix": - promise = core.opAsync("op_net_accept_unix", this.rid); - break; - default: - throw new Error(`Unsupported transport: ${this.addr.transport}`); - } - this.#promiseId = promise[promiseIdSymbol]; - if (this.#unref) core.unrefOp(this.#promiseId); - const { 0: rid, 1: localAddr, 2: remoteAddr } = await promise; - this.#promiseId = null; - if (this.addr.transport == "tcp") { - localAddr.transport = "tcp"; - remoteAddr.transport = "tcp"; - return new TcpConn(rid, remoteAddr, localAddr); - } else if (this.addr.transport == "unix") { - return new UnixConn( - rid, - { transport: "unix", path: remoteAddr }, - { transport: "unix", path: localAddr }, - ); - } else { - throw new Error("unreachable"); - } - } +class UnixConn extends Conn {} - async next() { - let conn; - try { - conn = await this.accept(); - } catch (error) { - if ( - ObjectPrototypeIsPrototypeOf(BadResourcePrototype, error) || - ObjectPrototypeIsPrototypeOf(InterruptedPrototype, error) - ) { - return { value: undefined, done: true }; - } - throw error; - } - return { value: conn, done: false }; - } +class Listener { + #rid = 0; + #addr = null; + #unref = false; + #promiseId = null; - return(value) { - this.close(); - return PromiseResolve({ value, done: true }); - } + constructor(rid, addr) { + this.#rid = rid; + this.#addr = addr; + } - close() { - core.close(this.rid); - } + get rid() { + return this.#rid; + } - [SymbolAsyncIterator]() { - return this; - } + get addr() { + return this.#addr; + } - ref() { - this.#unref = false; - if (typeof this.#promiseId === "number") { - core.refOp(this.#promiseId); - } + async accept() { + let promise; + switch (this.addr.transport) { + case "tcp": + promise = core.opAsync("op_net_accept_tcp", this.rid); + break; + case "unix": + promise = core.opAsync("op_net_accept_unix", this.rid); + break; + default: + throw new Error(`Unsupported transport: ${this.addr.transport}`); + } + this.#promiseId = promise[promiseIdSymbol]; + if (this.#unref) core.unrefOp(this.#promiseId); + const { 0: rid, 1: localAddr, 2: remoteAddr } = await promise; + this.#promiseId = null; + if (this.addr.transport == "tcp") { + localAddr.transport = "tcp"; + remoteAddr.transport = "tcp"; + return new TcpConn(rid, remoteAddr, localAddr); + } else if (this.addr.transport == "unix") { + return new UnixConn( + rid, + { transport: "unix", path: remoteAddr }, + { transport: "unix", path: localAddr }, + ); + } else { + throw new Error("unreachable"); } + } - unref() { - this.#unref = true; - if (typeof this.#promiseId === "number") { - core.unrefOp(this.#promiseId); + async next() { + let conn; + try { + conn = await this.accept(); + } catch (error) { + if ( + ObjectPrototypeIsPrototypeOf(BadResourcePrototype, error) || + ObjectPrototypeIsPrototypeOf(InterruptedPrototype, error) + ) { + return { value: undefined, done: true }; } + throw error; } + return { value: conn, done: false }; } - class Datagram { - #rid = 0; - #addr = null; + return(value) { + this.close(); + return PromiseResolve({ value, done: true }); + } - constructor(rid, addr, bufSize = 1024) { - this.#rid = rid; - this.#addr = addr; - this.bufSize = bufSize; - } + close() { + core.close(this.rid); + } - get rid() { - return this.#rid; - } + [SymbolAsyncIterator]() { + return this; + } - get addr() { - return this.#addr; + ref() { + this.#unref = false; + if (typeof this.#promiseId === "number") { + core.refOp(this.#promiseId); } + } - async receive(p) { - const buf = p || new Uint8Array(this.bufSize); - let nread; - let remoteAddr; - switch (this.addr.transport) { - case "udp": { - ({ 0: nread, 1: remoteAddr } = await core.opAsync( - "op_net_recv_udp", - this.rid, - buf, - )); - remoteAddr.transport = "udp"; - break; - } - case "unixpacket": { - let path; - ({ 0: nread, 1: path } = await core.opAsync( - "op_net_recv_unixpacket", - this.rid, - buf, - )); - remoteAddr = { transport: "unixpacket", path }; - break; - } - default: - throw new Error(`Unsupported transport: ${this.addr.transport}`); - } - const sub = TypedArrayPrototypeSubarray(buf, 0, nread); - return [sub, remoteAddr]; + unref() { + this.#unref = true; + if (typeof this.#promiseId === "number") { + core.unrefOp(this.#promiseId); } + } +} - async send(p, opts) { - switch (this.addr.transport) { - case "udp": - return await core.opAsync( - "op_net_send_udp", - this.rid, - { hostname: opts.hostname ?? "127.0.0.1", port: opts.port }, - p, - ); - case "unixpacket": - return await core.opAsync( - "op_net_send_unixpacket", - this.rid, - opts.path, - p, - ); - default: - throw new Error(`Unsupported transport: ${this.addr.transport}`); - } - } +class Datagram { + #rid = 0; + #addr = null; - close() { - core.close(this.rid); - } + constructor(rid, addr, bufSize = 1024) { + this.#rid = rid; + this.#addr = addr; + this.bufSize = bufSize; + } - async *[SymbolAsyncIterator]() { - while (true) { - try { - yield await this.receive(); - } catch (err) { - if ( - ObjectPrototypeIsPrototypeOf(BadResourcePrototype, err) || - ObjectPrototypeIsPrototypeOf(InterruptedPrototype, err) - ) { - break; - } - throw err; - } - } - } + get rid() { + return this.#rid; } - function listen(args) { - switch (args.transport ?? "tcp") { - case "tcp": { - const { 0: rid, 1: addr } = ops.op_net_listen_tcp({ - hostname: args.hostname ?? "0.0.0.0", - port: args.port, - }, args.reusePort); - addr.transport = "tcp"; - return new Listener(rid, addr); + get addr() { + return this.#addr; + } + + async receive(p) { + const buf = p || new Uint8Array(this.bufSize); + let nread; + let remoteAddr; + switch (this.addr.transport) { + case "udp": { + ({ 0: nread, 1: remoteAddr } = await core.opAsync( + "op_net_recv_udp", + this.rid, + buf, + )); + remoteAddr.transport = "udp"; + break; } - case "unix": { - const { 0: rid, 1: path } = ops.op_net_listen_unix(args.path); - const addr = { - transport: "unix", - path, - }; - return new Listener(rid, addr); + case "unixpacket": { + let path; + ({ 0: nread, 1: path } = await core.opAsync( + "op_net_recv_unixpacket", + this.rid, + buf, + )); + remoteAddr = { transport: "unixpacket", path }; + break; } default: - throw new TypeError(`Unsupported transport: '${transport}'`); + throw new Error(`Unsupported transport: ${this.addr.transport}`); } + const sub = TypedArrayPrototypeSubarray(buf, 0, nread); + return [sub, remoteAddr]; } - function createListenDatagram(udpOpFn, unixOpFn) { - return function listenDatagram(args) { - switch (args.transport) { - case "udp": { - const { 0: rid, 1: addr } = udpOpFn( - { - hostname: args.hostname ?? "127.0.0.1", - port: args.port, - }, - args.reuseAddress ?? false, - ); - addr.transport = "udp"; - return new Datagram(rid, addr); - } - case "unixpacket": { - const { 0: rid, 1: path } = unixOpFn(args.path); - const addr = { - transport: "unixpacket", - path, - }; - return new Datagram(rid, addr); + async send(p, opts) { + switch (this.addr.transport) { + case "udp": + return await core.opAsync( + "op_net_send_udp", + this.rid, + { hostname: opts.hostname ?? "127.0.0.1", port: opts.port }, + p, + ); + case "unixpacket": + return await core.opAsync( + "op_net_send_unixpacket", + this.rid, + opts.path, + p, + ); + default: + throw new Error(`Unsupported transport: ${this.addr.transport}`); + } + } + + close() { + core.close(this.rid); + } + + async *[SymbolAsyncIterator]() { + while (true) { + try { + yield await this.receive(); + } catch (err) { + if ( + ObjectPrototypeIsPrototypeOf(BadResourcePrototype, err) || + ObjectPrototypeIsPrototypeOf(InterruptedPrototype, err) + ) { + break; } - default: - throw new TypeError(`Unsupported transport: '${transport}'`); + throw err; } - }; + } + } +} + +function listen(args) { + switch (args.transport ?? "tcp") { + case "tcp": { + const { 0: rid, 1: addr } = ops.op_net_listen_tcp({ + hostname: args.hostname ?? "0.0.0.0", + port: args.port, + }, args.reusePort); + addr.transport = "tcp"; + return new Listener(rid, addr); + } + case "unix": { + const { 0: rid, 1: path } = ops.op_net_listen_unix(args.path); + const addr = { + transport: "unix", + path, + }; + return new Listener(rid, addr); + } + default: + throw new TypeError(`Unsupported transport: '${transport}'`); } +} - async function connect(args) { - switch (args.transport ?? "tcp") { - case "tcp": { - const { 0: rid, 1: localAddr, 2: remoteAddr } = await core.opAsync( - "op_net_connect_tcp", +function createListenDatagram(udpOpFn, unixOpFn) { + return function listenDatagram(args) { + switch (args.transport) { + case "udp": { + const { 0: rid, 1: addr } = udpOpFn( { hostname: args.hostname ?? "127.0.0.1", port: args.port, }, + args.reuseAddress ?? false, ); - localAddr.transport = "tcp"; - remoteAddr.transport = "tcp"; - return new TcpConn(rid, remoteAddr, localAddr); + addr.transport = "udp"; + return new Datagram(rid, addr); } - case "unix": { - const { 0: rid, 1: localAddr, 2: remoteAddr } = await core.opAsync( - "op_net_connect_unix", - args.path, - ); - return new UnixConn( - rid, - { transport: "unix", path: remoteAddr }, - { transport: "unix", path: localAddr }, - ); + case "unixpacket": { + const { 0: rid, 1: path } = unixOpFn(args.path); + const addr = { + transport: "unixpacket", + path, + }; + return new Datagram(rid, addr); } default: throw new TypeError(`Unsupported transport: '${transport}'`); } - } - - window.__bootstrap.net = { - connect, - Conn, - TcpConn, - UnixConn, - listen, - createListenDatagram, - Listener, - shutdown, - Datagram, - resolveDns, }; -})(this); +} + +async function connect(args) { + switch (args.transport ?? "tcp") { + case "tcp": { + const { 0: rid, 1: localAddr, 2: remoteAddr } = await core.opAsync( + "op_net_connect_tcp", + { + hostname: args.hostname ?? "127.0.0.1", + port: args.port, + }, + ); + localAddr.transport = "tcp"; + remoteAddr.transport = "tcp"; + return new TcpConn(rid, remoteAddr, localAddr); + } + case "unix": { + const { 0: rid, 1: localAddr, 2: remoteAddr } = await core.opAsync( + "op_net_connect_unix", + args.path, + ); + return new UnixConn( + rid, + { transport: "unix", path: remoteAddr }, + { transport: "unix", path: localAddr }, + ); + } + default: + throw new TypeError(`Unsupported transport: '${transport}'`); + } +} + +export { + Conn, + connect, + createListenDatagram, + Datagram, + listen, + Listener, + resolveDns, + shutdown, + TcpConn, + UnixConn, +}; diff --git a/ext/net/02_tls.js b/ext/net/02_tls.js index 632e1fbd4a3173..14bf3dce869b2a 100644 --- a/ext/net/02_tls.js +++ b/ext/net/02_tls.js @@ -1,106 +1,98 @@ // Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. -"use strict"; -((window) => { - const core = window.Deno.core; - const ops = core.ops; - const { Listener, Conn } = window.__bootstrap.net; - const { TypeError } = window.__bootstrap.primordials; +const core = globalThis.Deno.core; +const ops = core.ops; +import { Conn, Listener } from "internal:deno_net/01_net.js"; +const primordials = globalThis.__bootstrap.primordials; +const { TypeError } = primordials; - function opStartTls(args) { - return core.opAsync("op_tls_start", args); - } +function opStartTls(args) { + return core.opAsync("op_tls_start", args); +} + +function opTlsHandshake(rid) { + return core.opAsync("op_tls_handshake", rid); +} - function opTlsHandshake(rid) { - return core.opAsync("op_tls_handshake", rid); +class TlsConn extends Conn { + handshake() { + return opTlsHandshake(this.rid); } +} - class TlsConn extends Conn { - handshake() { - return opTlsHandshake(this.rid); - } +async function connectTls({ + port, + hostname = "127.0.0.1", + transport = "tcp", + certFile = undefined, + caCerts = [], + certChain = undefined, + privateKey = undefined, + alpnProtocols = undefined, +}) { + if (transport !== "tcp") { + throw new TypeError(`Unsupported transport: '${transport}'`); } + const { 0: rid, 1: localAddr, 2: remoteAddr } = await core.opAsync( + "op_net_connect_tls", + { hostname, port }, + { certFile, caCerts, certChain, privateKey, alpnProtocols }, + ); + localAddr.transport = "tcp"; + remoteAddr.transport = "tcp"; + return new TlsConn(rid, remoteAddr, localAddr); +} - async function connectTls({ - port, - hostname = "127.0.0.1", - transport = "tcp", - certFile = undefined, - caCerts = [], - certChain = undefined, - privateKey = undefined, - alpnProtocols = undefined, - }) { - if (transport !== "tcp") { - throw new TypeError(`Unsupported transport: '${transport}'`); - } +class TlsListener extends Listener { + async accept() { const { 0: rid, 1: localAddr, 2: remoteAddr } = await core.opAsync( - "op_net_connect_tls", - { hostname, port }, - { certFile, caCerts, certChain, privateKey, alpnProtocols }, + "op_net_accept_tls", + this.rid, ); localAddr.transport = "tcp"; remoteAddr.transport = "tcp"; return new TlsConn(rid, remoteAddr, localAddr); } +} - class TlsListener extends Listener { - async accept() { - const { 0: rid, 1: localAddr, 2: remoteAddr } = await core.opAsync( - "op_net_accept_tls", - this.rid, - ); - localAddr.transport = "tcp"; - remoteAddr.transport = "tcp"; - return new TlsConn(rid, remoteAddr, localAddr); - } +function listenTls({ + port, + cert, + certFile, + key, + keyFile, + hostname = "0.0.0.0", + transport = "tcp", + alpnProtocols = undefined, + reusePort = false, +}) { + if (transport !== "tcp") { + throw new TypeError(`Unsupported transport: '${transport}'`); } + const { 0: rid, 1: localAddr } = ops.op_net_listen_tls( + { hostname, port }, + { cert, certFile, key, keyFile, alpnProtocols, reusePort }, + ); + return new TlsListener(rid, localAddr); +} - function listenTls({ - port, - cert, - certFile, - key, - keyFile, - hostname = "0.0.0.0", - transport = "tcp", +async function startTls( + conn, + { + hostname = "127.0.0.1", + certFile = undefined, + caCerts = [], alpnProtocols = undefined, - reusePort = false, - }) { - if (transport !== "tcp") { - throw new TypeError(`Unsupported transport: '${transport}'`); - } - const { 0: rid, 1: localAddr } = ops.op_net_listen_tls( - { hostname, port }, - { cert, certFile, key, keyFile, alpnProtocols, reusePort }, - ); - return new TlsListener(rid, localAddr); - } - - async function startTls( - conn, - { - hostname = "127.0.0.1", - certFile = undefined, - caCerts = [], - alpnProtocols = undefined, - } = {}, - ) { - const { 0: rid, 1: localAddr, 2: remoteAddr } = await opStartTls({ - rid: conn.rid, - hostname, - certFile, - caCerts, - alpnProtocols, - }); - return new TlsConn(rid, remoteAddr, localAddr); - } + } = {}, +) { + const { 0: rid, 1: localAddr, 2: remoteAddr } = await opStartTls({ + rid: conn.rid, + hostname, + certFile, + caCerts, + alpnProtocols, + }); + return new TlsConn(rid, remoteAddr, localAddr); +} - window.__bootstrap.tls = { - startTls, - listenTls, - connectTls, - TlsConn, - TlsListener, - }; -})(this); +export { connectTls, listenTls, startTls, TlsConn, TlsListener }; diff --git a/ext/net/Cargo.toml b/ext/net/Cargo.toml index fe98408761c733..d38dd3fad469a1 100644 --- a/ext/net/Cargo.toml +++ b/ext/net/Cargo.toml @@ -2,7 +2,7 @@ [package] name = "deno_net" -version = "0.81.0" +version = "0.83.0" authors.workspace = true edition.workspace = true license.workspace = true diff --git a/ext/net/lib.rs b/ext/net/lib.rs index 932f8c8c5c2836..0536f323348dbd 100644 --- a/ext/net/lib.rs +++ b/ext/net/lib.rs @@ -86,11 +86,7 @@ pub fn init( ops.extend(ops_tls::init::

()); Extension::builder(env!("CARGO_PKG_NAME")) .dependencies(vec!["deno_web"]) - .js(include_js_files!( - prefix "internal:ext/net", - "01_net.js", - "02_tls.js", - )) + .esm(include_js_files!("01_net.js", "02_tls.js",)) .ops(ops) .state(move |state| { state.put(DefaultTlsOptions { diff --git a/ext/net/ops.rs b/ext/net/ops.rs index 1d84a106768ac4..737a46d8c84859 100644 --- a/ext/net/ops.rs +++ b/ext/net/ops.rs @@ -9,6 +9,7 @@ use deno_core::error::custom_error; use deno_core::error::generic_error; use deno_core::error::AnyError; use deno_core::op; +use deno_core::CancelFuture; use deno_core::AsyncRefCell; use deno_core::ByteString; @@ -416,6 +417,7 @@ pub enum DnsReturnRecord { #[derive(Deserialize)] #[serde(rename_all = "camelCase")] pub struct ResolveAddrArgs { + cancel_rid: Option, query: String, record_type: RecordType, options: Option, @@ -451,6 +453,7 @@ where query, record_type, options, + cancel_rid, } = args; let (config, opts) = if let Some(name_server) = @@ -484,9 +487,29 @@ where let resolver = AsyncResolver::tokio(config, opts)?; - resolver - .lookup(query, record_type) - .await + let lookup_fut = resolver.lookup(query, record_type); + + let cancel_handle = cancel_rid.and_then(|rid| { + state + .borrow_mut() + .resource_table + .get::(rid) + .ok() + }); + + let lookup = if let Some(cancel_handle) = cancel_handle { + let lookup_rv = lookup_fut.or_cancel(cancel_handle).await; + + if let Some(cancel_rid) = cancel_rid { + state.borrow_mut().resource_table.close(cancel_rid).ok(); + }; + + lookup_rv? + } else { + lookup_fut.await + }; + + lookup .map_err(|e| { let message = format!("{e}"); match e.kind() { diff --git a/ext/node/01_node.js b/ext/node/01_node.js index 4ed5b3edab4f42..85346a44ba62f5 100644 --- a/ext/node/01_node.js +++ b/ext/node/01_node.js @@ -2,127 +2,120 @@ // deno-lint-ignore-file -"use strict"; +const internals = globalThis.__bootstrap.internals; +const primordials = globalThis.__bootstrap.primordials; +const { + ArrayPrototypePush, + ArrayPrototypeFilter, + ObjectEntries, + ObjectCreate, + ObjectDefineProperty, + Proxy, + ReflectDefineProperty, + ReflectDeleteProperty, + ReflectGet, + ReflectGetOwnPropertyDescriptor, + ReflectHas, + ReflectOwnKeys, + ReflectSet, + Set, + SetPrototypeHas, +} = primordials; -((window) => { - const { - ArrayPrototypePush, - ArrayPrototypeFilter, - ObjectEntries, - ObjectCreate, - ObjectDefineProperty, - Proxy, - ReflectDefineProperty, - ReflectGetOwnPropertyDescriptor, - ReflectOwnKeys, - Set, - SetPrototypeHas, - } = window.__bootstrap.primordials; - - function assert(cond) { - if (!cond) { - throw Error("assert"); - } +function assert(cond) { + if (!cond) { + throw Error("assert"); } +} - let initialized = false; - const nodeGlobals = {}; - const nodeGlobalThis = new Proxy(globalThis, { - get(_target, prop, _receiver) { - if (prop in nodeGlobals) { - return nodeGlobals[prop]; - } else { - return globalThis[prop]; - } - }, - set(_target, prop, value) { - if (prop in nodeGlobals) { - nodeGlobals[prop] = value; - } else { - globalThis[prop] = value; - } - return true; - }, - deleteProperty(_target, prop) { - let success = false; - if (prop in nodeGlobals) { - delete nodeGlobals[prop]; - success = true; - } - if (prop in globalThis) { - delete globalThis[prop]; - success = true; - } - return success; - }, - ownKeys(_target) { - const globalThisKeys = ReflectOwnKeys(globalThis); - const nodeGlobalsKeys = ReflectOwnKeys(nodeGlobals); - const nodeGlobalsKeySet = new Set(nodeGlobalsKeys); - return [ - ...ArrayPrototypeFilter( - globalThisKeys, - (k) => !SetPrototypeHas(nodeGlobalsKeySet, k), - ), - ...nodeGlobalsKeys, - ]; - }, - defineProperty(_target, prop, desc) { - if (prop in nodeGlobals) { - return ReflectDefineProperty(nodeGlobals, prop, desc); - } else { - return ReflectDefineProperty(globalThis, prop, desc); - } - }, - getOwnPropertyDescriptor(_target, prop) { - if (prop in nodeGlobals) { - return ReflectGetOwnPropertyDescriptor(nodeGlobals, prop); - } else { - return ReflectGetOwnPropertyDescriptor(globalThis, prop); - } - }, - has(_target, prop) { - return prop in nodeGlobals || prop in globalThis; - }, - }); - - const nativeModuleExports = ObjectCreate(null); - const builtinModules = []; - - function initialize(nodeModules, nodeGlobalThisName) { - assert(!initialized); - initialized = true; - for (const [name, exports] of ObjectEntries(nodeModules)) { - nativeModuleExports[name] = exports; - ArrayPrototypePush(builtinModules, name); +let initialized = false; +const nodeGlobals = {}; +const nodeGlobalThis = new Proxy(globalThis, { + get(target, prop) { + if (ReflectHas(nodeGlobals, prop)) { + return ReflectGet(nodeGlobals, prop); + } else { + return ReflectGet(target, prop); + } + }, + set(target, prop, value) { + if (ReflectHas(nodeGlobals, prop)) { + return ReflectSet(nodeGlobals, prop, value); + } else { + return ReflectSet(target, prop, value); } - nodeGlobals.Buffer = nativeModuleExports["buffer"].Buffer; - nodeGlobals.clearImmediate = nativeModuleExports["timers"].clearImmediate; - nodeGlobals.clearInterval = nativeModuleExports["timers"].clearInterval; - nodeGlobals.clearTimeout = nativeModuleExports["timers"].clearTimeout; - nodeGlobals.console = nativeModuleExports["console"]; - nodeGlobals.global = nodeGlobalThis; - nodeGlobals.process = nativeModuleExports["process"]; - nodeGlobals.setImmediate = nativeModuleExports["timers"].setImmediate; - nodeGlobals.setInterval = nativeModuleExports["timers"].setInterval; - nodeGlobals.setTimeout = nativeModuleExports["timers"].setTimeout; + }, + has(target, prop) { + return ReflectHas(nodeGlobals, prop) || ReflectHas(target, prop); + }, + deleteProperty(target, prop) { + const nodeDeleted = ReflectDeleteProperty(nodeGlobals, prop); + const targetDeleted = ReflectDeleteProperty(target, prop); + return nodeDeleted || targetDeleted; + }, + ownKeys(target) { + const targetKeys = ReflectOwnKeys(target); + const nodeGlobalsKeys = ReflectOwnKeys(nodeGlobals); + const nodeGlobalsKeySet = new Set(nodeGlobalsKeys); + return [ + ...ArrayPrototypeFilter( + targetKeys, + (k) => !SetPrototypeHas(nodeGlobalsKeySet, k), + ), + ...nodeGlobalsKeys, + ]; + }, + defineProperty(target, prop, desc) { + if (ReflectHas(nodeGlobals, prop)) { + return ReflectDefineProperty(nodeGlobals, prop, desc); + } else { + return ReflectDefineProperty(target, prop, desc); + } + }, + getOwnPropertyDescriptor(target, prop) { + if (ReflectHas(nodeGlobals, prop)) { + return ReflectGetOwnPropertyDescriptor(nodeGlobals, prop); + } else { + return ReflectGetOwnPropertyDescriptor(target, prop); + } + }, +}); + +const nativeModuleExports = ObjectCreate(null); +const builtinModules = []; - // add a hidden global for the esm code to use in order to reliably - // get node's globalThis - ObjectDefineProperty(globalThis, nodeGlobalThisName, { - enumerable: false, - writable: false, - value: nodeGlobalThis, - }); +function initialize(nodeModules, nodeGlobalThisName) { + assert(!initialized); + initialized = true; + for (const [name, exports] of ObjectEntries(nodeModules)) { + nativeModuleExports[name] = exports; + ArrayPrototypePush(builtinModules, name); } + nodeGlobals.Buffer = nativeModuleExports["buffer"].Buffer; + nodeGlobals.clearImmediate = nativeModuleExports["timers"].clearImmediate; + nodeGlobals.clearInterval = nativeModuleExports["timers"].clearInterval; + nodeGlobals.clearTimeout = nativeModuleExports["timers"].clearTimeout; + nodeGlobals.console = nativeModuleExports["console"]; + nodeGlobals.global = nodeGlobalThis; + nodeGlobals.process = nativeModuleExports["process"]; + nodeGlobals.setImmediate = nativeModuleExports["timers"].setImmediate; + nodeGlobals.setInterval = nativeModuleExports["timers"].setInterval; + nodeGlobals.setTimeout = nativeModuleExports["timers"].setTimeout; + + // add a hidden global for the esm code to use in order to reliably + // get node's globalThis + ObjectDefineProperty(globalThis, nodeGlobalThisName, { + enumerable: false, + writable: false, + value: nodeGlobalThis, + }); + // FIXME(bartlomieju): not nice to depend on `Deno` namespace here + internals.__bootstrapNodeProcess(Deno.args, Deno.version); +} - window.__bootstrap.internals = { - ...window.__bootstrap.internals ?? {}, - node: { - globalThis: nodeGlobalThis, - initialize, - nativeModuleExports, - builtinModules, - }, - }; -})(globalThis); +internals.node = { + globalThis: nodeGlobalThis, + initialize, + nativeModuleExports, + builtinModules, +}; diff --git a/ext/node/02_require.js b/ext/node/02_require.js index bda74c01fc1df5..d334a60a501595 100644 --- a/ext/node/02_require.js +++ b/ext/node/02_require.js @@ -2,943 +2,937 @@ // deno-lint-ignore-file -"use strict"; - -((window) => { - const { - ArrayIsArray, - ArrayPrototypeIncludes, - ArrayPrototypeIndexOf, - ArrayPrototypeJoin, - ArrayPrototypePush, - ArrayPrototypeSlice, - ArrayPrototypeSplice, - ObjectGetOwnPropertyDescriptor, - ObjectGetPrototypeOf, - ObjectPrototypeHasOwnProperty, - ObjectSetPrototypeOf, - ObjectKeys, - ObjectPrototype, - ObjectCreate, - Proxy, - SafeMap, - SafeWeakMap, - SafeArrayIterator, - JSONParse, - String, - StringPrototypeEndsWith, - StringPrototypeIndexOf, - StringPrototypeIncludes, - StringPrototypeMatch, - StringPrototypeSlice, - StringPrototypeSplit, - StringPrototypeStartsWith, - StringPrototypeCharCodeAt, - RegExpPrototypeTest, - Error, - TypeError, - } = window.__bootstrap.primordials; - const core = window.Deno.core; - const ops = core.ops; - const { node } = window.__bootstrap.internals; - - // Map used to store CJS parsing data. - const cjsParseCache = new SafeWeakMap(); - - function pathDirname(filepath) { - if (filepath == null || filepath === "") { - throw new Error("Empty filepath."); - } - return ops.op_require_path_dirname(filepath); +const core = globalThis.Deno.core; +const ops = core.ops; +const internals = globalThis.__bootstrap.internals; +const primordials = globalThis.__bootstrap.primordials; +const { + ArrayIsArray, + ArrayPrototypeIncludes, + ArrayPrototypeIndexOf, + ArrayPrototypeJoin, + ArrayPrototypePush, + ArrayPrototypeSlice, + ArrayPrototypeSplice, + ObjectGetOwnPropertyDescriptor, + ObjectGetPrototypeOf, + ObjectPrototypeHasOwnProperty, + ObjectSetPrototypeOf, + ObjectKeys, + ObjectPrototype, + ObjectCreate, + Proxy, + SafeMap, + SafeWeakMap, + SafeArrayIterator, + JSONParse, + String, + StringPrototypeEndsWith, + StringPrototypeIndexOf, + StringPrototypeIncludes, + StringPrototypeMatch, + StringPrototypeSlice, + StringPrototypeSplit, + StringPrototypeStartsWith, + StringPrototypeCharCodeAt, + RegExpPrototypeTest, + Error, + TypeError, +} = primordials; +const node = internals.node; + +// Map used to store CJS parsing data. +const cjsParseCache = new SafeWeakMap(); + +function pathDirname(filepath) { + if (filepath == null || filepath === "") { + throw new Error("Empty filepath."); } + return ops.op_require_path_dirname(filepath); +} - function pathResolve(...args) { - return ops.op_require_path_resolve(args); - } +function pathResolve(...args) { + return ops.op_require_path_resolve(args); +} - function assert(cond) { - if (!cond) { - throw Error("assert"); - } +function assert(cond) { + if (!cond) { + throw Error("assert"); } - - const nativeModulePolyfill = new SafeMap(); - - const relativeResolveCache = ObjectCreate(null); - let requireDepth = 0; - let statCache = null; - let isPreloading = false; - let mainModule = null; - let hasBrokenOnInspectBrk = false; - let hasInspectBrk = false; - // Are we running with --node-modules-dir flag? - let usesLocalNodeModulesDir = false; - - function stat(filename) { - // TODO: required only on windows - // filename = path.toNamespacedPath(filename); - if (statCache !== null) { - const result = statCache.get(filename); - if (result !== undefined) { - return result; - } - } - const result = ops.op_require_stat(filename); - if (statCache !== null && result >= 0) { - statCache.set(filename, result); +} + +const nativeModulePolyfill = new SafeMap(); + +const relativeResolveCache = ObjectCreate(null); +let requireDepth = 0; +let statCache = null; +let isPreloading = false; +let mainModule = null; +let hasBrokenOnInspectBrk = false; +let hasInspectBrk = false; +// Are we running with --node-modules-dir flag? +let usesLocalNodeModulesDir = false; + +function stat(filename) { + // TODO: required only on windows + // filename = path.toNamespacedPath(filename); + if (statCache !== null) { + const result = statCache.get(filename); + if (result !== undefined) { + return result; } - - return result; + } + const result = ops.op_require_stat(filename); + if (statCache !== null && result >= 0) { + statCache.set(filename, result); } - function updateChildren(parent, child, scan) { - if (!parent) { - return; - } + return result; +} - const children = parent.children; - if (children && !(scan && ArrayPrototypeIncludes(children, child))) { - ArrayPrototypePush(children, child); - } +function updateChildren(parent, child, scan) { + if (!parent) { + return; } - function tryFile(requestPath, _isMain) { - const rc = stat(requestPath); - if (rc !== 0) return; - return toRealPath(requestPath); + const children = parent.children; + if (children && !(scan && ArrayPrototypeIncludes(children, child))) { + ArrayPrototypePush(children, child); + } +} + +function tryFile(requestPath, _isMain) { + const rc = stat(requestPath); + if (rc !== 0) return; + return toRealPath(requestPath); +} + +function tryPackage(requestPath, exts, isMain, originalPath) { + const packageJsonPath = pathResolve( + requestPath, + "package.json", + ); + const pkg = ops.op_require_read_package_scope(packageJsonPath)?.main; + if (!pkg) { + return tryExtensions( + pathResolve(requestPath, "index"), + exts, + isMain, + ); } - function tryPackage(requestPath, exts, isMain, originalPath) { - const packageJsonPath = pathResolve( - requestPath, - "package.json", + const filename = pathResolve(requestPath, pkg); + let actual = tryFile(filename, isMain) || + tryExtensions(filename, exts, isMain) || + tryExtensions( + pathResolve(filename, "index"), + exts, + isMain, + ); + if (actual === false) { + actual = tryExtensions( + pathResolve(requestPath, "index"), + exts, + isMain, ); - const pkg = core.ops.op_require_read_package_scope(packageJsonPath)?.main; - if (!pkg) { - return tryExtensions( - pathResolve(requestPath, "index"), - exts, - isMain, + if (!actual) { + // eslint-disable-next-line no-restricted-syntax + const err = new Error( + `Cannot find module '${filename}'. ` + + 'Please verify that the package.json has a valid "main" entry', ); - } - - const filename = pathResolve(requestPath, pkg); - let actual = tryFile(filename, isMain) || - tryExtensions(filename, exts, isMain) || - tryExtensions( - pathResolve(filename, "index"), - exts, - isMain, + err.code = "MODULE_NOT_FOUND"; + err.path = pathResolve( + requestPath, + "package.json", ); - if (actual === false) { - actual = tryExtensions( - pathResolve(requestPath, "index"), - exts, - isMain, + err.requestPath = originalPath; + throw err; + } else { + node.globalThis.process.emitWarning( + `Invalid 'main' field in '${packageJsonPath}' of '${pkg}'. ` + + "Please either fix that or report it to the module author", + "DeprecationWarning", + "DEP0128", ); - if (!actual) { - // eslint-disable-next-line no-restricted-syntax - const err = new Error( - `Cannot find module '${filename}'. ` + - 'Please verify that the package.json has a valid "main" entry', - ); - err.code = "MODULE_NOT_FOUND"; - err.path = pathResolve( - requestPath, - "package.json", - ); - err.requestPath = originalPath; - throw err; - } else { - node.globalThis.process.emitWarning( - `Invalid 'main' field in '${packageJsonPath}' of '${pkg}'. ` + - "Please either fix that or report it to the module author", - "DeprecationWarning", - "DEP0128", - ); - } } - return actual; } - - const realpathCache = new SafeMap(); - function toRealPath(requestPath) { - const maybeCached = realpathCache.get(requestPath); - if (maybeCached) { - return maybeCached; - } - const rp = ops.op_require_real_path(requestPath); - realpathCache.set(requestPath, rp); - return rp; + return actual; +} + +const realpathCache = new SafeMap(); +function toRealPath(requestPath) { + const maybeCached = realpathCache.get(requestPath); + if (maybeCached) { + return maybeCached; } + const rp = ops.op_require_real_path(requestPath); + realpathCache.set(requestPath, rp); + return rp; +} - function tryExtensions(p, exts, isMain) { - for (let i = 0; i < exts.length; i++) { - const filename = tryFile(p + exts[i], isMain); +function tryExtensions(p, exts, isMain) { + for (let i = 0; i < exts.length; i++) { + const filename = tryFile(p + exts[i], isMain); - if (filename) { - return filename; - } + if (filename) { + return filename; } - return false; } - - // Find the longest (possibly multi-dot) extension registered in - // Module._extensions - function findLongestRegisteredExtension(filename) { - const name = ops.op_require_path_basename(filename); - let currentExtension; - let index; - let startIndex = 0; - while ((index = StringPrototypeIndexOf(name, ".", startIndex)) !== -1) { - startIndex = index + 1; - if (index === 0) continue; // Skip dotfiles like .gitignore - currentExtension = StringPrototypeSlice(name, index); - if (Module._extensions[currentExtension]) { - return currentExtension; - } + return false; +} + +// Find the longest (possibly multi-dot) extension registered in +// Module._extensions +function findLongestRegisteredExtension(filename) { + const name = ops.op_require_path_basename(filename); + let currentExtension; + let index; + let startIndex = 0; + while ((index = StringPrototypeIndexOf(name, ".", startIndex)) !== -1) { + startIndex = index + 1; + if (index === 0) continue; // Skip dotfiles like .gitignore + currentExtension = StringPrototypeSlice(name, index); + if (Module._extensions[currentExtension]) { + return currentExtension; } - return ".js"; } + return ".js"; +} + +function getExportsForCircularRequire(module) { + if ( + module.exports && + ObjectGetPrototypeOf(module.exports) === ObjectPrototype && + // Exclude transpiled ES6 modules / TypeScript code because those may + // employ unusual patterns for accessing 'module.exports'. That should + // be okay because ES6 modules have a different approach to circular + // dependencies anyway. + !module.exports.__esModule + ) { + // This is later unset once the module is done loading. + ObjectSetPrototypeOf( + module.exports, + CircularRequirePrototypeWarningProxy, + ); + } + + return module.exports; +} + +function emitCircularRequireWarning(prop) { + node.globalThis.process.emitWarning( + `Accessing non-existent property '${String(prop)}' of module exports ` + + "inside circular dependency", + ); +} + +// A Proxy that can be used as the prototype of a module.exports object and +// warns when non-existent properties are accessed. +const CircularRequirePrototypeWarningProxy = new Proxy({}, { + get(target, prop) { + // Allow __esModule access in any case because it is used in the output + // of transpiled code to determine whether something comes from an + // ES module, and is not used as a regular key of `module.exports`. + if (prop in target || prop === "__esModule") return target[prop]; + emitCircularRequireWarning(prop); + return undefined; + }, - function getExportsForCircularRequire(module) { + getOwnPropertyDescriptor(target, prop) { if ( - module.exports && - ObjectGetPrototypeOf(module.exports) === ObjectPrototype && - // Exclude transpiled ES6 modules / TypeScript code because those may - // employ unusual patterns for accessing 'module.exports'. That should - // be okay because ES6 modules have a different approach to circular - // dependencies anyway. - !module.exports.__esModule + ObjectPrototypeHasOwnProperty(target, prop) || prop === "__esModule" ) { - // This is later unset once the module is done loading. - ObjectSetPrototypeOf( - module.exports, - CircularRequirePrototypeWarningProxy, - ); + return ObjectGetOwnPropertyDescriptor(target, prop); } - - return module.exports; - } - - function emitCircularRequireWarning(prop) { - node.globalThis.process.emitWarning( - `Accessing non-existent property '${String(prop)}' of module exports ` + - "inside circular dependency", + emitCircularRequireWarning(prop); + return undefined; + }, +}); + +const moduleParentCache = new SafeWeakMap(); +function Module(id = "", parent) { + this.id = id; + this.path = pathDirname(id); + this.exports = {}; + moduleParentCache.set(this, parent); + updateChildren(parent, this, false); + this.filename = null; + this.loaded = false; + this.children = []; +} + +Module.builtinModules = node.builtinModules; + +Module._extensions = ObjectCreate(null); +Module._cache = ObjectCreate(null); +Module._pathCache = ObjectCreate(null); +let modulePaths = []; +Module.globalPaths = modulePaths; + +const CHAR_FORWARD_SLASH = 47; +const TRAILING_SLASH_REGEX = /(?:^|\/)\.?\.$/; +const encodedSepRegEx = /%2F|%2C/i; + +function finalizeEsmResolution( + resolved, + parentPath, + pkgPath, +) { + if (RegExpPrototypeTest(encodedSepRegEx, resolved)) { + throw new ERR_INVALID_MODULE_SPECIFIER( + resolved, + 'must not include encoded "/" or "\\" characters', + parentPath, ); } - - // A Proxy that can be used as the prototype of a module.exports object and - // warns when non-existent properties are accessed. - const CircularRequirePrototypeWarningProxy = new Proxy({}, { - get(target, prop) { - // Allow __esModule access in any case because it is used in the output - // of transpiled code to determine whether something comes from an - // ES module, and is not used as a regular key of `module.exports`. - if (prop in target || prop === "__esModule") return target[prop]; - emitCircularRequireWarning(prop); - return undefined; - }, - - getOwnPropertyDescriptor(target, prop) { - if ( - ObjectPrototypeHasOwnProperty(target, prop) || prop === "__esModule" - ) { - return ObjectGetOwnPropertyDescriptor(target, prop); - } - emitCircularRequireWarning(prop); - return undefined; - }, - }); - - const moduleParentCache = new SafeWeakMap(); - function Module(id = "", parent) { - this.id = id; - this.path = pathDirname(id); - this.exports = {}; - moduleParentCache.set(this, parent); - updateChildren(parent, this, false); - this.filename = null; - this.loaded = false; - this.children = []; - } - - Module.builtinModules = node.builtinModules; - - Module._extensions = ObjectCreate(null); - Module._cache = ObjectCreate(null); - Module._pathCache = ObjectCreate(null); - let modulePaths = []; - Module.globalPaths = modulePaths; - - const CHAR_FORWARD_SLASH = 47; - const TRAILING_SLASH_REGEX = /(?:^|\/)\.?\.$/; - const encodedSepRegEx = /%2F|%2C/i; - - function finalizeEsmResolution( - resolved, - parentPath, - pkgPath, - ) { - if (RegExpPrototypeTest(encodedSepRegEx, resolved)) { - throw new ERR_INVALID_MODULE_SPECIFIER( - resolved, - 'must not include encoded "/" or "\\" characters', - parentPath, - ); - } - // const filename = fileURLToPath(resolved); - const filename = resolved; - const actual = tryFile(filename, false); - if (actual) { - return actual; - } - throw new ERR_MODULE_NOT_FOUND( - filename, - path.resolve(pkgPath, "package.json"), - ); + // const filename = fileURLToPath(resolved); + const filename = resolved; + const actual = tryFile(filename, false); + if (actual) { + return actual; + } + throw new ERR_MODULE_NOT_FOUND( + filename, + path.resolve(pkgPath, "package.json"), + ); +} + +// This only applies to requests of a specific form: +// 1. name/.* +// 2. @scope/name/.* +const EXPORTS_PATTERN = /^((?:@[^/\\%]+\/)?[^./\\%][^/\\%]*)(\/.*)?$/; +function resolveExports( + modulesPath, + request, + parentPath, + usesLocalNodeModulesDir, +) { + // The implementation's behavior is meant to mirror resolution in ESM. + const [, name, expansion = ""] = + StringPrototypeMatch(request, EXPORTS_PATTERN) || []; + if (!name) { + return; } - // This only applies to requests of a specific form: - // 1. name/.* - // 2. @scope/name/.* - const EXPORTS_PATTERN = /^((?:@[^/\\%]+\/)?[^./\\%][^/\\%]*)(\/.*)?$/; - function resolveExports( + return ops.op_require_resolve_exports( + usesLocalNodeModulesDir, modulesPath, request, + name, + expansion, parentPath, - usesLocalNodeModulesDir, - ) { - // The implementation's behavior is meant to mirror resolution in ESM. - const [, name, expansion = ""] = - StringPrototypeMatch(request, EXPORTS_PATTERN) || []; - if (!name) { - return; - } - - return core.ops.op_require_resolve_exports( - usesLocalNodeModulesDir, - modulesPath, - request, - name, - expansion, - parentPath, - ) ?? false; + ) ?? false; +} + +Module._findPath = function (request, paths, isMain, parentPath) { + const absoluteRequest = ops.op_require_path_is_absolute(request); + if (absoluteRequest) { + paths = [""]; + } else if (!paths || paths.length === 0) { + return false; } - Module._findPath = function (request, paths, isMain, parentPath) { - const absoluteRequest = ops.op_require_path_is_absolute(request); - if (absoluteRequest) { - paths = [""]; - } else if (!paths || paths.length === 0) { - return false; - } - - const cacheKey = request + "\x00" + ArrayPrototypeJoin(paths, "\x00"); - const entry = Module._pathCache[cacheKey]; - if (entry) { - return entry; - } + const cacheKey = request + "\x00" + ArrayPrototypeJoin(paths, "\x00"); + const entry = Module._pathCache[cacheKey]; + if (entry) { + return entry; + } - let exts; - let trailingSlash = request.length > 0 && - StringPrototypeCharCodeAt(request, request.length - 1) === - CHAR_FORWARD_SLASH; - if (!trailingSlash) { - trailingSlash = RegExpPrototypeTest(TRAILING_SLASH_REGEX, request); - } + let exts; + let trailingSlash = request.length > 0 && + StringPrototypeCharCodeAt(request, request.length - 1) === + CHAR_FORWARD_SLASH; + if (!trailingSlash) { + trailingSlash = RegExpPrototypeTest(TRAILING_SLASH_REGEX, request); + } - // For each path - for (let i = 0; i < paths.length; i++) { - // Don't search further if path doesn't exist - const curPath = paths[i]; - if (curPath && stat(curPath) < 1) continue; - - if (!absoluteRequest) { - const exportsResolved = resolveExports( - curPath, - request, - parentPath, - usesLocalNodeModulesDir, - ); - if (exportsResolved) { - return exportsResolved; - } - } + // For each path + for (let i = 0; i < paths.length; i++) { + // Don't search further if path doesn't exist + const curPath = paths[i]; + if (curPath && stat(curPath) < 1) continue; - const isDenoDirPackage = core.ops.op_require_is_deno_dir_package( + if (!absoluteRequest) { + const exportsResolved = resolveExports( curPath, - ); - const isRelative = ops.op_require_is_request_relative( request, + parentPath, + usesLocalNodeModulesDir, ); - const basePath = - (isDenoDirPackage && !isRelative && !usesLocalNodeModulesDir) - ? pathResolve(curPath, packageSpecifierSubPath(request)) - : pathResolve(curPath, request); - let filename; - - const rc = stat(basePath); - if (!trailingSlash) { - if (rc === 0) { // File. - filename = toRealPath(basePath); - } + if (exportsResolved) { + return exportsResolved; + } + } - if (!filename) { - // Try it with each of the extensions - if (exts === undefined) { - exts = ObjectKeys(Module._extensions); - } - filename = tryExtensions(basePath, exts, isMain); - } + const isDenoDirPackage = ops.op_require_is_deno_dir_package( + curPath, + ); + const isRelative = ops.op_require_is_request_relative( + request, + ); + const basePath = (isDenoDirPackage && !isRelative) + ? pathResolve(curPath, packageSpecifierSubPath(request)) + : pathResolve(curPath, request); + let filename; + + const rc = stat(basePath); + if (!trailingSlash) { + if (rc === 0) { // File. + filename = toRealPath(basePath); } - if (!filename && rc === 1) { // Directory. - // try it with each of the extensions at "index" + if (!filename) { + // Try it with each of the extensions if (exts === undefined) { exts = ObjectKeys(Module._extensions); } - filename = tryPackage(basePath, exts, isMain, request); + filename = tryExtensions(basePath, exts, isMain); } + } - if (filename) { - Module._pathCache[cacheKey] = filename; - return filename; + if (!filename && rc === 1) { // Directory. + // try it with each of the extensions at "index" + if (exts === undefined) { + exts = ObjectKeys(Module._extensions); } + filename = tryPackage(basePath, exts, isMain, request); } - return false; - }; + if (filename) { + Module._pathCache[cacheKey] = filename; + return filename; + } + } - Module._nodeModulePaths = function (fromPath) { - return ops.op_require_node_module_paths(fromPath); - }; + return false; +}; - Module._resolveLookupPaths = function (request, parent) { - const paths = []; +Module._nodeModulePaths = function (fromPath) { + return ops.op_require_node_module_paths(fromPath); +}; - if (core.ops.op_require_is_request_relative(request) && parent?.filename) { - ArrayPrototypePush( - paths, - core.ops.op_require_path_dirname(parent.filename), - ); - return paths; - } +Module._resolveLookupPaths = function (request, parent) { + const paths = []; - if (parent?.filename && parent.filename.length > 0) { - const denoDirPath = core.ops.op_require_resolve_deno_dir( - request, - parent.filename, - ); - if (denoDirPath) { - ArrayPrototypePush(paths, denoDirPath); - } - } - const lookupPathsResult = ops.op_require_resolve_lookup_paths( - request, - parent?.paths, - parent?.filename ?? "", + if (ops.op_require_is_request_relative(request) && parent?.filename) { + ArrayPrototypePush( + paths, + ops.op_require_path_dirname(parent.filename), ); - if (lookupPathsResult) { - ArrayPrototypePush(paths, ...new SafeArrayIterator(lookupPathsResult)); - } return paths; - }; + } - Module._load = function (request, parent, isMain) { - let relResolveCacheIdentifier; - if (parent) { - // Fast path for (lazy loaded) modules in the same directory. The indirect - // caching is required to allow cache invalidation without changing the old - // cache key names. - relResolveCacheIdentifier = `${parent.path}\x00${request}`; - const filename = relativeResolveCache[relResolveCacheIdentifier]; - if (filename !== undefined) { - const cachedModule = Module._cache[filename]; - if (cachedModule !== undefined) { - updateChildren(parent, cachedModule, true); - if (!cachedModule.loaded) { - return getExportsForCircularRequire(cachedModule); - } - return cachedModule.exports; + if (parent?.filename && parent.filename.length > 0) { + const denoDirPath = ops.op_require_resolve_deno_dir( + request, + parent.filename, + ); + if (denoDirPath) { + ArrayPrototypePush(paths, denoDirPath); + } + } + const lookupPathsResult = ops.op_require_resolve_lookup_paths( + request, + parent?.paths, + parent?.filename ?? "", + ); + if (lookupPathsResult) { + ArrayPrototypePush(paths, ...new SafeArrayIterator(lookupPathsResult)); + } + return paths; +}; + +Module._load = function (request, parent, isMain) { + let relResolveCacheIdentifier; + if (parent) { + // Fast path for (lazy loaded) modules in the same directory. The indirect + // caching is required to allow cache invalidation without changing the old + // cache key names. + relResolveCacheIdentifier = `${parent.path}\x00${request}`; + const filename = relativeResolveCache[relResolveCacheIdentifier]; + if (filename !== undefined) { + const cachedModule = Module._cache[filename]; + if (cachedModule !== undefined) { + updateChildren(parent, cachedModule, true); + if (!cachedModule.loaded) { + return getExportsForCircularRequire(cachedModule); } - delete relativeResolveCache[relResolveCacheIdentifier]; + return cachedModule.exports; } + delete relativeResolveCache[relResolveCacheIdentifier]; } + } - const filename = Module._resolveFilename(request, parent, isMain); - if (StringPrototypeStartsWith(filename, "node:")) { - // Slice 'node:' prefix - const id = StringPrototypeSlice(filename, 5); - - const module = loadNativeModule(id, id); - if (!module) { - // TODO: - // throw new ERR_UNKNOWN_BUILTIN_MODULE(filename); - throw new Error("Unknown built-in module"); - } + const filename = Module._resolveFilename(request, parent, isMain); + if (StringPrototypeStartsWith(filename, "node:")) { + // Slice 'node:' prefix + const id = StringPrototypeSlice(filename, 5); - return module.exports; + const module = loadNativeModule(id, id); + if (!module) { + // TODO: + // throw new ERR_UNKNOWN_BUILTIN_MODULE(filename); + throw new Error("Unknown built-in module"); } - const cachedModule = Module._cache[filename]; - if (cachedModule !== undefined) { - updateChildren(parent, cachedModule, true); - if (!cachedModule.loaded) { - return getExportsForCircularRequire(cachedModule); - } - return cachedModule.exports; - } + return module.exports; + } - const mod = loadNativeModule(filename, request); - if ( - mod - ) { - return mod.exports; + const cachedModule = Module._cache[filename]; + if (cachedModule !== undefined) { + updateChildren(parent, cachedModule, true); + if (!cachedModule.loaded) { + return getExportsForCircularRequire(cachedModule); } - // Don't call updateChildren(), Module constructor already does. - const module = cachedModule || new Module(filename, parent); + return cachedModule.exports; + } - if (isMain) { - node.globalThis.process.mainModule = module; - mainModule = module; - module.id = "."; - } + const mod = loadNativeModule(filename, request); + if ( + mod + ) { + return mod.exports; + } + // Don't call updateChildren(), Module constructor already does. + const module = cachedModule || new Module(filename, parent); - Module._cache[filename] = module; - if (parent !== undefined) { - relativeResolveCache[relResolveCacheIdentifier] = filename; - } + if (isMain) { + node.globalThis.process.mainModule = module; + mainModule = module; + module.id = "."; + } - let threw = true; - try { - module.load(filename); - threw = false; - } finally { - if (threw) { - delete Module._cache[filename]; - if (parent !== undefined) { - delete relativeResolveCache[relResolveCacheIdentifier]; - const children = parent?.children; - if (ArrayIsArray(children)) { - const index = ArrayPrototypeIndexOf(children, module); - if (index !== -1) { - ArrayPrototypeSplice(children, index, 1); - } + Module._cache[filename] = module; + if (parent !== undefined) { + relativeResolveCache[relResolveCacheIdentifier] = filename; + } + + let threw = true; + try { + module.load(filename); + threw = false; + } finally { + if (threw) { + delete Module._cache[filename]; + if (parent !== undefined) { + delete relativeResolveCache[relResolveCacheIdentifier]; + const children = parent?.children; + if (ArrayIsArray(children)) { + const index = ArrayPrototypeIndexOf(children, module); + if (index !== -1) { + ArrayPrototypeSplice(children, index, 1); } } - } else if ( - module.exports && - ObjectGetPrototypeOf(module.exports) === - CircularRequirePrototypeWarningProxy - ) { - ObjectSetPrototypeOf(module.exports, ObjectPrototype); } + } else if ( + module.exports && + ObjectGetPrototypeOf(module.exports) === + CircularRequirePrototypeWarningProxy + ) { + ObjectSetPrototypeOf(module.exports, ObjectPrototype); } + } - return module.exports; - }; - - Module._resolveFilename = function ( - request, - parent, - isMain, - options, + return module.exports; +}; + +Module._resolveFilename = function ( + request, + parent, + isMain, + options, +) { + if ( + StringPrototypeStartsWith(request, "node:") || + nativeModuleCanBeRequiredByUsers(request) ) { - if ( - StringPrototypeStartsWith(request, "node:") || - nativeModuleCanBeRequiredByUsers(request) - ) { - return request; - } + return request; + } - let paths; + let paths; - if (typeof options === "object" && options !== null) { - if (ArrayIsArray(options.paths)) { - const isRelative = ops.op_require_is_request_relative( - request, - ); + if (typeof options === "object" && options !== null) { + if (ArrayIsArray(options.paths)) { + const isRelative = ops.op_require_is_request_relative( + request, + ); - if (isRelative) { - paths = options.paths; - } else { - const fakeParent = new Module("", null); + if (isRelative) { + paths = options.paths; + } else { + const fakeParent = new Module("", null); - paths = []; + paths = []; - for (let i = 0; i < options.paths.length; i++) { - const path = options.paths[i]; - fakeParent.paths = Module._nodeModulePaths(path); - const lookupPaths = Module._resolveLookupPaths(request, fakeParent); + for (let i = 0; i < options.paths.length; i++) { + const path = options.paths[i]; + fakeParent.paths = Module._nodeModulePaths(path); + const lookupPaths = Module._resolveLookupPaths(request, fakeParent); - for (let j = 0; j < lookupPaths.length; j++) { - if (!ArrayPrototypeIncludes(paths, lookupPaths[j])) { - ArrayPrototypePush(paths, lookupPaths[j]); - } + for (let j = 0; j < lookupPaths.length; j++) { + if (!ArrayPrototypeIncludes(paths, lookupPaths[j])) { + ArrayPrototypePush(paths, lookupPaths[j]); } } } - } else if (options.paths === undefined) { - paths = Module._resolveLookupPaths(request, parent); - } else { - // TODO: - // throw new ERR_INVALID_ARG_VALUE("options.paths", options.paths); - throw new Error("Invalid arg value options.paths", options.path); } - } else { + } else if (options.paths === undefined) { paths = Module._resolveLookupPaths(request, parent); + } else { + // TODO: + // throw new ERR_INVALID_ARG_VALUE("options.paths", options.paths); + throw new Error("Invalid arg value options.paths", options.path); } + } else { + paths = Module._resolveLookupPaths(request, parent); + } - if (parent?.filename) { - if (request[0] === "#") { - const maybeResolved = core.ops.op_require_package_imports_resolve( - parent.filename, - request, - ); - if (maybeResolved) { - return maybeResolved; - } + if (parent?.filename) { + if (request[0] === "#") { + const maybeResolved = ops.op_require_package_imports_resolve( + parent.filename, + request, + ); + if (maybeResolved) { + return maybeResolved; } } + } - // Try module self resolution first - const parentPath = ops.op_require_try_self_parent_path( - !!parent, - parent?.filename, - parent?.id, - ); - const selfResolved = ops.op_require_try_self(parentPath, request); - if (selfResolved) { - const cacheKey = request + "\x00" + - (paths.length === 1 ? paths[0] : ArrayPrototypeJoin(paths, "\x00")); - Module._pathCache[cacheKey] = selfResolved; - return selfResolved; - } - - // Look up the filename first, since that's the cache key. - const filename = Module._findPath( - request, - paths, - isMain, - parentPath, - ); - if (filename) return filename; - const requireStack = []; - for (let cursor = parent; cursor; cursor = moduleParentCache.get(cursor)) { - ArrayPrototypePush(requireStack, cursor.filename || cursor.id); - } - let message = `Cannot find module '${request}'`; - if (requireStack.length > 0) { - message = message + "\nRequire stack:\n- " + - ArrayPrototypeJoin(requireStack, "\n- "); - } - // eslint-disable-next-line no-restricted-syntax - const err = new Error(message); - err.code = "MODULE_NOT_FOUND"; - err.requireStack = requireStack; - throw err; - }; - - Module.prototype.load = function (filename) { - assert(!this.loaded); - this.filename = filename; - this.paths = Module._nodeModulePaths( - pathDirname(filename), - ); - const extension = findLongestRegisteredExtension(filename); - // allow .mjs to be overriden - if ( - StringPrototypeEndsWith(filename, ".mjs") && !Module._extensions[".mjs"] - ) { - // TODO: use proper error class - throw new Error("require ESM", filename); - } - - Module._extensions[extension](this, filename); - this.loaded = true; - - // TODO: do caching - }; - - // Loads a module at the given file path. Returns that module's - // `exports` property. - Module.prototype.require = function (id) { - if (typeof id !== "string") { - // TODO(bartlomieju): it should use different error type - // ("ERR_INVALID_ARG_VALUE") - throw new TypeError("Invalid argument type"); - } - - if (id === "") { - // TODO(bartlomieju): it should use different error type - // ("ERR_INVALID_ARG_VALUE") - throw new TypeError("id must be non empty"); - } - requireDepth++; - try { - return Module._load(id, this, /* isMain */ false); - } finally { - requireDepth--; - } - }; - - Module.wrapper = [ - // We provide the non-standard APIs in the CommonJS wrapper - // to avoid exposing them in global namespace. - "(function (exports, require, module, __filename, __dirname, globalThis) { const { Buffer, clearImmediate, clearInterval, clearTimeout, console, global, process, setImmediate, setInterval, setTimeout} = globalThis; var window = undefined; (function () {", - "\n}).call(this); })", - ]; - Module.wrap = function (script) { - script = script.replace(/^#!.*?\n/, ""); - return `${Module.wrapper[0]}${script}${Module.wrapper[1]}`; - }; - - function enrichCJSError(error) { - if (error instanceof SyntaxError) { - if ( - StringPrototypeIncludes( - error.message, - "Cannot use import statement outside a module", - ) || - StringPrototypeIncludes(error.message, "Unexpected token 'export'") - ) { - console.error( - 'To load an ES module, set "type": "module" in the package.json or use ' + - "the .mjs extension.", - ); - } - } + // Try module self resolution first + const parentPath = ops.op_require_try_self_parent_path( + !!parent, + parent?.filename, + parent?.id, + ); + const selfResolved = ops.op_require_try_self(parentPath, request); + if (selfResolved) { + const cacheKey = request + "\x00" + + (paths.length === 1 ? paths[0] : ArrayPrototypeJoin(paths, "\x00")); + Module._pathCache[cacheKey] = selfResolved; + return selfResolved; } - function wrapSafe( - filename, - content, - cjsModuleInstance, + // Look up the filename first, since that's the cache key. + const filename = Module._findPath( + request, + paths, + isMain, + parentPath, + ); + if (filename) return filename; + const requireStack = []; + for (let cursor = parent; cursor; cursor = moduleParentCache.get(cursor)) { + ArrayPrototypePush(requireStack, cursor.filename || cursor.id); + } + let message = `Cannot find module '${request}'`; + if (requireStack.length > 0) { + message = message + "\nRequire stack:\n- " + + ArrayPrototypeJoin(requireStack, "\n- "); + } + // eslint-disable-next-line no-restricted-syntax + const err = new Error(message); + err.code = "MODULE_NOT_FOUND"; + err.requireStack = requireStack; + throw err; +}; + +Module.prototype.load = function (filename) { + assert(!this.loaded); + this.filename = filename; + this.paths = Module._nodeModulePaths( + pathDirname(filename), + ); + const extension = findLongestRegisteredExtension(filename); + // allow .mjs to be overriden + if ( + StringPrototypeEndsWith(filename, ".mjs") && !Module._extensions[".mjs"] ) { - const wrapper = Module.wrap(content); - const [f, err] = core.evalContext(wrapper, filename); - if (err) { - if (node.globalThis.process.mainModule === cjsModuleInstance) { - enrichCJSError(err.thrown); - } - throw err.thrown; - } - return f; + // TODO: use proper error class + throw new Error("require ESM", filename); } - Module.prototype._compile = function (content, filename) { - const compiledWrapper = wrapSafe(filename, content, this); + Module._extensions[extension](this, filename); + this.loaded = true; - const dirname = pathDirname(filename); - const require = makeRequireFunction(this); - const exports = this.exports; - const thisValue = exports; - const module = this; - if (requireDepth === 0) { - statCache = new SafeMap(); - } + // TODO: do caching +}; - if (hasInspectBrk && !hasBrokenOnInspectBrk) { - hasBrokenOnInspectBrk = true; - core.ops.op_require_break_on_next_statement(); - } +// Loads a module at the given file path. Returns that module's +// `exports` property. +Module.prototype.require = function (id) { + if (typeof id !== "string") { + // TODO(bartlomieju): it should use different error type + // ("ERR_INVALID_ARG_VALUE") + throw new TypeError("Invalid argument type"); + } - const result = compiledWrapper.call( - thisValue, - exports, - require, - this, - filename, - dirname, - node.globalThis, - ); - if (requireDepth === 0) { - statCache = null; + if (id === "") { + // TODO(bartlomieju): it should use different error type + // ("ERR_INVALID_ARG_VALUE") + throw new TypeError("id must be non empty"); + } + requireDepth++; + try { + return Module._load(id, this, /* isMain */ false); + } finally { + requireDepth--; + } +}; + +Module.wrapper = [ + // We provide the non-standard APIs in the CommonJS wrapper + // to avoid exposing them in global namespace. + "(function (exports, require, module, __filename, __dirname, globalThis) { const { Buffer, clearImmediate, clearInterval, clearTimeout, console, global, process, setImmediate, setInterval, setTimeout} = globalThis; var window = undefined; (function () {", + "\n}).call(this); })", +]; +Module.wrap = function (script) { + script = script.replace(/^#!.*?\n/, ""); + return `${Module.wrapper[0]}${script}${Module.wrapper[1]}`; +}; + +function enrichCJSError(error) { + if (error instanceof SyntaxError) { + if ( + StringPrototypeIncludes( + error.message, + "Cannot use import statement outside a module", + ) || + StringPrototypeIncludes(error.message, "Unexpected token 'export'") + ) { + console.error( + 'To load an ES module, set "type": "module" in the package.json or use ' + + "the .mjs extension.", + ); } - return result; - }; + } +} + +function wrapSafe( + filename, + content, + cjsModuleInstance, +) { + const wrapper = Module.wrap(content); + const [f, err] = core.evalContext(wrapper, filename); + if (err) { + if (node.globalThis.process.mainModule === cjsModuleInstance) { + enrichCJSError(err.thrown); + } + throw err.thrown; + } + return f; +} + +Module.prototype._compile = function (content, filename) { + const compiledWrapper = wrapSafe(filename, content, this); + + const dirname = pathDirname(filename); + const require = makeRequireFunction(this); + const exports = this.exports; + const thisValue = exports; + const module = this; + if (requireDepth === 0) { + statCache = new SafeMap(); + } - Module._extensions[".js"] = function (module, filename) { - const content = ops.op_require_read_file(filename); + if (hasInspectBrk && !hasBrokenOnInspectBrk) { + hasBrokenOnInspectBrk = true; + ops.op_require_break_on_next_statement(); + } - if (StringPrototypeEndsWith(filename, ".js")) { - const pkg = core.ops.op_require_read_closest_package_json(filename); - if (pkg && pkg.exists && pkg.typ == "module") { - let message = `Trying to import ESM module: ${filename}`; + const result = compiledWrapper.call( + thisValue, + exports, + require, + this, + filename, + dirname, + node.globalThis, + ); + if (requireDepth === 0) { + statCache = null; + } + return result; +}; - if (module.parent) { - message += ` from ${module.parent.filename}`; - } +Module._extensions[".js"] = function (module, filename) { + const content = ops.op_require_read_file(filename); - message += ` using require()`; + if (StringPrototypeEndsWith(filename, ".js")) { + const pkg = ops.op_require_read_closest_package_json(filename); + if (pkg && pkg.exists && pkg.typ == "module") { + let message = `Trying to import ESM module: ${filename}`; - throw new Error(message); + if (module.parent) { + message += ` from ${module.parent.filename}`; } - } - module._compile(content, filename); - }; + message += ` using require()`; - function stripBOM(content) { - if (StringPrototypeCharCodeAt(content, 0) === 0xfeff) { - content = StringPrototypeSlice(content, 1); + throw new Error(message); } - return content; } - // Native extension for .json - Module._extensions[".json"] = function (module, filename) { - const content = ops.op_require_read_file(filename); - - try { - module.exports = JSONParse(stripBOM(content)); - } catch (err) { - err.message = filename + ": " + err.message; - throw err; - } - }; + module._compile(content, filename); +}; - // Native extension for .node - Module._extensions[".node"] = function (module, filename) { - if (filename.endsWith("fsevents.node")) { - throw new Error("Using fsevents module is currently not supported"); - } - module.exports = ops.op_napi_open(filename, node.globalThis); - }; - - function createRequireFromPath(filename) { - const proxyPath = ops.op_require_proxy_path(filename); - const mod = new Module(proxyPath); - mod.filename = proxyPath; - mod.paths = Module._nodeModulePaths(mod.path); - return makeRequireFunction(mod); +function stripBOM(content) { + if (StringPrototypeCharCodeAt(content, 0) === 0xfeff) { + content = StringPrototypeSlice(content, 1); } + return content; +} - function makeRequireFunction(mod) { - const require = function require(path) { - return mod.require(path); - }; - - function resolve(request, options) { - return Module._resolveFilename(request, mod, false, options); - } - - require.resolve = resolve; +// Native extension for .json +Module._extensions[".json"] = function (module, filename) { + const content = ops.op_require_read_file(filename); - function paths(request) { - return Module._resolveLookupPaths(request, mod); - } + try { + module.exports = JSONParse(stripBOM(content)); + } catch (err) { + err.message = filename + ": " + err.message; + throw err; + } +}; - resolve.paths = paths; - require.main = mainModule; - // Enable support to add extra extension types. - require.extensions = Module._extensions; - require.cache = Module._cache; +// Native extension for .node +Module._extensions[".node"] = function (module, filename) { + if (filename.endsWith("fsevents.node")) { + throw new Error("Using fsevents module is currently not supported"); + } + module.exports = ops.op_napi_open(filename, node.globalThis); +}; + +function createRequireFromPath(filename) { + const proxyPath = ops.op_require_proxy_path(filename); + const mod = new Module(proxyPath); + mod.filename = proxyPath; + mod.paths = Module._nodeModulePaths(mod.path); + return makeRequireFunction(mod); +} + +function makeRequireFunction(mod) { + const require = function require(path) { + return mod.require(path); + }; - return require; + function resolve(request, options) { + return Module._resolveFilename(request, mod, false, options); } - // Matches to: - // - /foo/... - // - \foo\... - // - C:/foo/... - // - C:\foo\... - const RE_START_OF_ABS_PATH = /^([/\\]|[a-zA-Z]:[/\\])/; + require.resolve = resolve; - function isAbsolute(filenameOrUrl) { - return RE_START_OF_ABS_PATH.test(filenameOrUrl); + function paths(request) { + return Module._resolveLookupPaths(request, mod); } - function createRequire(filenameOrUrl) { - let fileUrlStr; - if (filenameOrUrl instanceof URL) { - if (filenameOrUrl.protocol !== "file:") { - throw new Error( - `The argument 'filename' must be a file URL object, file URL string, or absolute path string. Received ${filenameOrUrl}`, - ); - } - fileUrlStr = filenameOrUrl.toString(); - } else if (typeof filenameOrUrl === "string") { - if (!filenameOrUrl.startsWith("file:") && !isAbsolute(filenameOrUrl)) { - throw new Error( - `The argument 'filename' must be a file URL object, file URL string, or absolute path string. Received ${filenameOrUrl}`, - ); - } - fileUrlStr = filenameOrUrl; - } else { + resolve.paths = paths; + require.main = mainModule; + // Enable support to add extra extension types. + require.extensions = Module._extensions; + require.cache = Module._cache; + + return require; +} + +// Matches to: +// - /foo/... +// - \foo\... +// - C:/foo/... +// - C:\foo\... +const RE_START_OF_ABS_PATH = /^([/\\]|[a-zA-Z]:[/\\])/; + +function isAbsolute(filenameOrUrl) { + return RE_START_OF_ABS_PATH.test(filenameOrUrl); +} + +function createRequire(filenameOrUrl) { + let fileUrlStr; + if (filenameOrUrl instanceof URL) { + if (filenameOrUrl.protocol !== "file:") { + throw new Error( + `The argument 'filename' must be a file URL object, file URL string, or absolute path string. Received ${filenameOrUrl}`, + ); + } + fileUrlStr = filenameOrUrl.toString(); + } else if (typeof filenameOrUrl === "string") { + if (!filenameOrUrl.startsWith("file:") && !isAbsolute(filenameOrUrl)) { throw new Error( `The argument 'filename' must be a file URL object, file URL string, or absolute path string. Received ${filenameOrUrl}`, ); } - const filename = core.ops.op_require_as_file_path(fileUrlStr); - return createRequireFromPath(filename); + fileUrlStr = filenameOrUrl; + } else { + throw new Error( + `The argument 'filename' must be a file URL object, file URL string, or absolute path string. Received ${filenameOrUrl}`, + ); } + const filename = ops.op_require_as_file_path(fileUrlStr); + return createRequireFromPath(filename); +} - Module.createRequire = createRequire; +Module.createRequire = createRequire; - Module._initPaths = function () { - const paths = ops.op_require_init_paths(); - modulePaths = paths; - Module.globalPaths = ArrayPrototypeSlice(modulePaths); - }; +Module._initPaths = function () { + const paths = ops.op_require_init_paths(); + modulePaths = paths; + Module.globalPaths = ArrayPrototypeSlice(modulePaths); +}; - Module.syncBuiltinESMExports = function syncBuiltinESMExports() { - throw new Error("not implemented"); - }; +Module.syncBuiltinESMExports = function syncBuiltinESMExports() { + throw new Error("not implemented"); +}; - Module.Module = Module; +Module.Module = Module; - node.nativeModuleExports.module = Module; +node.nativeModuleExports.module = Module; - function loadNativeModule(_id, request) { - if (nativeModulePolyfill.has(request)) { - return nativeModulePolyfill.get(request); - } - const modExports = node.nativeModuleExports[request]; - if (modExports) { - const nodeMod = new Module(request); - nodeMod.exports = modExports; - nodeMod.loaded = true; - nativeModulePolyfill.set(request, nodeMod); - return nodeMod; - } - return undefined; +function loadNativeModule(_id, request) { + if (nativeModulePolyfill.has(request)) { + return nativeModulePolyfill.get(request); } - - function nativeModuleCanBeRequiredByUsers(request) { - return !!node.nativeModuleExports[request]; + const modExports = node.nativeModuleExports[request]; + if (modExports) { + const nodeMod = new Module(request); + nodeMod.exports = modExports; + nodeMod.loaded = true; + nativeModulePolyfill.set(request, nodeMod); + return nodeMod; } - - function readPackageScope() { - throw new Error("not implemented"); + return undefined; +} + +function nativeModuleCanBeRequiredByUsers(request) { + return !!node.nativeModuleExports[request]; +} + +function readPackageScope() { + throw new Error("not implemented"); +} + +/** @param specifier {string} */ +function packageSpecifierSubPath(specifier) { + let parts = StringPrototypeSplit(specifier, "/"); + if (StringPrototypeStartsWith(parts[0], "@")) { + parts = ArrayPrototypeSlice(parts, 2); + } else { + parts = ArrayPrototypeSlice(parts, 1); } - - /** @param specifier {string} */ - function packageSpecifierSubPath(specifier) { - let parts = StringPrototypeSplit(specifier, "/"); - if (StringPrototypeStartsWith(parts[0], "@")) { - parts = ArrayPrototypeSlice(parts, 2); - } else { - parts = ArrayPrototypeSlice(parts, 1); - } - return ArrayPrototypeJoin(parts, "/"); - } - - window.__bootstrap.internals = { - ...window.__bootstrap.internals ?? {}, - require: { - setUsesLocalNodeModulesDir() { - usesLocalNodeModulesDir = true; - }, - setInspectBrk() { - hasInspectBrk = true; - }, - Module, - wrapSafe, - toRealPath, - cjsParseCache, - readPackageScope, - }, - }; -})(globalThis); + return ArrayPrototypeJoin(parts, "/"); +} + +internals.require = { + setUsesLocalNodeModulesDir() { + usesLocalNodeModulesDir = true; + }, + setInspectBrk() { + hasInspectBrk = true; + }, + Module, + wrapSafe, + toRealPath, + cjsParseCache, + readPackageScope, +}; diff --git a/ext/node/Cargo.toml b/ext/node/Cargo.toml index a2b6a6835f8570..e21c8c3040cd97 100644 --- a/ext/node/Cargo.toml +++ b/ext/node/Cargo.toml @@ -2,7 +2,7 @@ [package] name = "deno_node" -version = "0.26.0" +version = "0.28.0" authors.workspace = true edition.workspace = true license.workspace = true @@ -15,7 +15,19 @@ path = "lib.rs" [dependencies] deno_core.workspace = true +digest = { version = "0.10.5", features = ["core-api", "std"] } +idna = "0.3.0" +indexmap.workspace = true +md-5 = "0.10.5" +md4 = "0.10.2" once_cell.workspace = true path-clean = "=0.1.0" +rand.workspace = true regex.workspace = true +ripemd = "0.1.3" +rsa.workspace = true serde = "1.0.149" +sha-1 = "0.10.0" +sha2 = "0.10.6" +sha3 = "0.10.5" +typenum = "1.15.0" diff --git a/ext/node/crypto/digest.rs b/ext/node/crypto/digest.rs new file mode 100644 index 00000000000000..4fab58a4320e89 --- /dev/null +++ b/ext/node/crypto/digest.rs @@ -0,0 +1,117 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. +use deno_core::error::type_error; +use deno_core::error::AnyError; +use deno_core::Resource; +use digest::Digest; +use digest::DynDigest; +use std::borrow::Cow; +use std::cell::RefCell; +use std::rc::Rc; + +pub enum Hash { + Md4(Box), + Md5(Box), + Ripemd160(Box), + Sha1(Box), + Sha224(Box), + Sha256(Box), + Sha384(Box), + Sha512(Box), +} + +pub struct Context { + pub hash: Rc>, +} + +impl Context { + pub fn new(algorithm: &str) -> Result { + Ok(Self { + hash: Rc::new(RefCell::new(Hash::new(algorithm)?)), + }) + } + + pub fn update(&self, data: &[u8]) { + self.hash.borrow_mut().update(data); + } + + pub fn digest(self) -> Result, AnyError> { + let hash = Rc::try_unwrap(self.hash) + .map_err(|_| type_error("Hash context is already in use"))?; + + let hash = hash.into_inner(); + Ok(hash.digest_and_drop()) + } +} + +impl Clone for Context { + fn clone(&self) -> Self { + Self { + hash: Rc::new(RefCell::new(self.hash.borrow().clone())), + } + } +} + +impl Resource for Context { + fn name(&self) -> Cow { + "cryptoDigest".into() + } +} + +use Hash::*; + +impl Hash { + pub fn new(algorithm_name: &str) -> Result { + Ok(match algorithm_name { + "md4" => Md4(Default::default()), + "md5" => Md5(Default::default()), + "ripemd160" => Ripemd160(Default::default()), + "sha1" => Sha1(Default::default()), + "sha224" => Sha224(Default::default()), + "sha256" => Sha256(Default::default()), + "sha384" => Sha384(Default::default()), + "sha512" => Sha512(Default::default()), + _ => return Err(type_error("unsupported algorithm")), + }) + } + + pub fn update(&mut self, data: &[u8]) { + match self { + Md4(context) => Digest::update(&mut **context, data), + Md5(context) => Digest::update(&mut **context, data), + Ripemd160(context) => Digest::update(&mut **context, data), + Sha1(context) => Digest::update(&mut **context, data), + Sha224(context) => Digest::update(&mut **context, data), + Sha256(context) => Digest::update(&mut **context, data), + Sha384(context) => Digest::update(&mut **context, data), + Sha512(context) => Digest::update(&mut **context, data), + }; + } + + pub fn digest_and_drop(self) -> Box<[u8]> { + match self { + Md4(context) => context.finalize(), + Md5(context) => context.finalize(), + Ripemd160(context) => context.finalize(), + Sha1(context) => context.finalize(), + Sha224(context) => context.finalize(), + Sha256(context) => context.finalize(), + Sha384(context) => context.finalize(), + Sha512(context) => context.finalize(), + } + } +} + +impl Clone for Hash { + fn clone(&self) -> Self { + match self { + Md4(_) => Md4(Default::default()), + Md5(_) => Md5(Default::default()), + Ripemd160(_) => Ripemd160(Default::default()), + Sha1(_) => Sha1(Default::default()), + Sha224(_) => Sha224(Default::default()), + Sha256(_) => Sha256(Default::default()), + Sha384(_) => Sha384(Default::default()), + Sha512(_) => Sha512(Default::default()), + } + } +} diff --git a/ext/node/crypto/mod.rs b/ext/node/crypto/mod.rs new file mode 100644 index 00000000000000..597779328fcc6d --- /dev/null +++ b/ext/node/crypto/mod.rs @@ -0,0 +1,128 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. +use deno_core::error::type_error; +use deno_core::error::AnyError; +use deno_core::op; +use deno_core::OpState; +use deno_core::ResourceId; +use deno_core::StringOrBuffer; +use deno_core::ZeroCopyBuf; +use std::rc::Rc; + +use rsa::padding::PaddingScheme; +use rsa::pkcs8::DecodePrivateKey; +use rsa::pkcs8::DecodePublicKey; +use rsa::PublicKey; +use rsa::RsaPrivateKey; +use rsa::RsaPublicKey; + +mod digest; + +#[op] +pub fn op_node_create_hash( + state: &mut OpState, + algorithm: String, +) -> Result { + Ok(state.resource_table.add(digest::Context::new(&algorithm)?)) +} + +#[op] +pub fn op_node_hash_update( + state: &mut OpState, + rid: ResourceId, + data: &[u8], +) -> Result<(), AnyError> { + let context = state.resource_table.get::(rid)?; + context.update(data); + Ok(()) +} + +#[op] +pub fn op_node_hash_digest( + state: &mut OpState, + rid: ResourceId, +) -> Result { + let context = state.resource_table.take::(rid)?; + let context = Rc::try_unwrap(context) + .map_err(|_| type_error("Hash context is already in use"))?; + Ok(context.digest()?.into()) +} + +#[op] +pub fn op_node_hash_clone( + state: &mut OpState, + rid: ResourceId, +) -> Result { + let context = state.resource_table.get::(rid)?; + Ok(state.resource_table.add(context.as_ref().clone())) +} + +#[op] +pub fn op_node_private_encrypt( + key: StringOrBuffer, + msg: StringOrBuffer, + padding: u32, +) -> Result { + let key = RsaPrivateKey::from_pkcs8_pem((&key).try_into()?)?; + + let mut rng = rand::thread_rng(); + match padding { + 1 => Ok( + key + .encrypt(&mut rng, PaddingScheme::new_pkcs1v15_encrypt(), &msg)? + .into(), + ), + 4 => Ok( + key + .encrypt(&mut rng, PaddingScheme::new_oaep::(), &msg)? + .into(), + ), + _ => Err(type_error("Unknown padding")), + } +} + +#[op] +pub fn op_node_private_decrypt( + key: StringOrBuffer, + msg: StringOrBuffer, + padding: u32, +) -> Result { + let key = RsaPrivateKey::from_pkcs8_pem((&key).try_into()?)?; + + match padding { + 1 => Ok( + key + .decrypt(PaddingScheme::new_pkcs1v15_encrypt(), &msg)? + .into(), + ), + 4 => Ok( + key + .decrypt(PaddingScheme::new_oaep::(), &msg)? + .into(), + ), + _ => Err(type_error("Unknown padding")), + } +} + +#[op] +pub fn op_node_public_encrypt( + key: StringOrBuffer, + msg: StringOrBuffer, + padding: u32, +) -> Result { + let key = RsaPublicKey::from_public_key_pem((&key).try_into()?)?; + + let mut rng = rand::thread_rng(); + match padding { + 1 => Ok( + key + .encrypt(&mut rng, PaddingScheme::new_pkcs1v15_encrypt(), &msg)? + .into(), + ), + 4 => Ok( + key + .encrypt(&mut rng, PaddingScheme::new_oaep::(), &msg)? + .into(), + ), + _ => Err(type_error("Unknown padding")), + } +} diff --git a/ext/node/idna.rs b/ext/node/idna.rs new file mode 100644 index 00000000000000..8f7cfe34ab200d --- /dev/null +++ b/ext/node/idna.rs @@ -0,0 +1,26 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. + +use deno_core::error::AnyError; +use deno_core::op; + +#[op] +pub fn op_node_idna_domain_to_ascii( + domain: String, +) -> Result { + Ok(idna::domain_to_ascii(&domain)?) +} + +#[op] +pub fn op_node_idna_domain_to_unicode(domain: String) -> String { + idna::domain_to_unicode(&domain).0 +} + +#[op] +pub fn op_node_idna_punycode_decode(domain: String) -> String { + idna::punycode::decode_to_string(&domain).unwrap_or_default() +} + +#[op] +pub fn op_node_idna_punycode_encode(domain: String) -> String { + idna::punycode::encode_str(&domain).unwrap_or_default() +} diff --git a/ext/node/lib.rs b/ext/node/lib.rs index ad8619889d70d1..fb389d85058848 100644 --- a/ext/node/lib.rs +++ b/ext/node/lib.rs @@ -1,27 +1,34 @@ // Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. -use deno_core::error::generic_error; use deno_core::error::AnyError; use deno_core::include_js_files; -use deno_core::normalize_path; +use deno_core::located_script_name; use deno_core::op; -use deno_core::url::Url; use deno_core::Extension; -use deno_core::JsRuntimeInspector; -use deno_core::OpState; +use deno_core::JsRuntime; use once_cell::sync::Lazy; use std::collections::HashSet; use std::path::Path; use std::path::PathBuf; use std::rc::Rc; +mod crypto; pub mod errors; +mod idna; +mod ops; mod package_json; mod path; +mod polyfill; mod resolution; +mod v8; +mod winerror; pub use package_json::PackageJson; pub use path::PathClean; +pub use polyfill::find_builtin_node_module; +pub use polyfill::is_builtin_node_module; +pub use polyfill::NodeModulePolyfill; +pub use polyfill::SUPPORTED_BUILTIN_NODE_MODULES; pub use resolution::get_closest_package_json; pub use resolution::get_package_scope_config; pub use resolution::legacy_main_resolve; @@ -32,7 +39,6 @@ pub use resolution::path_to_declaration_path; pub use resolution::NodeModuleKind; pub use resolution::NodeResolutionMode; pub use resolution::DEFAULT_CONDITIONS; -use std::cell::RefCell; pub trait NodePermissions { fn check_read(&mut self, path: &Path) -> Result<(), AnyError>; @@ -60,8 +66,6 @@ pub trait RequireNpmResolver { ) -> Result<(), AnyError>; } -pub const MODULE_ES_SHIM: &str = include_str!("./module_es_shim.js"); - pub static NODE_GLOBAL_THIS_NAME: Lazy = Lazy::new(|| { let now = std::time::SystemTime::now(); let seconds = now @@ -81,38 +85,339 @@ pub static NODE_ENV_VAR_ALLOWLIST: Lazy> = Lazy::new(|| { set }); +#[op] +fn op_node_build_os() -> String { + std::env::var("TARGET") + .unwrap() + .split('-') + .nth(2) + .unwrap() + .to_string() +} + +pub fn init_polyfill() -> Extension { + let esm_files = include_js_files!( + dir "polyfills", + "_core.ts", + "_events.mjs", + "_fs/_fs_access.ts", + "_fs/_fs_appendFile.ts", + "_fs/_fs_chmod.ts", + "_fs/_fs_chown.ts", + "_fs/_fs_close.ts", + "_fs/_fs_common.ts", + "_fs/_fs_constants.ts", + "_fs/_fs_copy.ts", + "_fs/_fs_dir.ts", + "_fs/_fs_dirent.ts", + "_fs/_fs_exists.ts", + "_fs/_fs_fdatasync.ts", + "_fs/_fs_fstat.ts", + "_fs/_fs_fsync.ts", + "_fs/_fs_ftruncate.ts", + "_fs/_fs_futimes.ts", + "_fs/_fs_link.ts", + "_fs/_fs_lstat.ts", + "_fs/_fs_mkdir.ts", + "_fs/_fs_mkdtemp.ts", + "_fs/_fs_open.ts", + "_fs/_fs_opendir.ts", + "_fs/_fs_read.ts", + "_fs/_fs_readdir.ts", + "_fs/_fs_readFile.ts", + "_fs/_fs_readlink.ts", + "_fs/_fs_realpath.ts", + "_fs/_fs_rename.ts", + "_fs/_fs_rm.ts", + "_fs/_fs_rmdir.ts", + "_fs/_fs_stat.ts", + "_fs/_fs_symlink.ts", + "_fs/_fs_truncate.ts", + "_fs/_fs_unlink.ts", + "_fs/_fs_utimes.ts", + "_fs/_fs_watch.ts", + "_fs/_fs_write.mjs", + "_fs/_fs_writeFile.ts", + "_fs/_fs_writev.mjs", + "_http_agent.mjs", + "_http_common.ts", + "_http_outgoing.ts", + "_next_tick.ts", + "_pako.mjs", + "_process/exiting.ts", + "_process/process.ts", + "_process/stdio.mjs", + "_process/streams.mjs", + "_readline.mjs", + "_stream.mjs", + "_tls_common.ts", + "_tls_wrap.ts", + "_util/_util_callbackify.ts", + "_util/asserts.ts", + "_util/async.ts", + "_util/os.ts", + "_util/std_asserts.ts", + "_util/std_fmt_colors.ts", + "_util/std_testing_diff.ts", + "_utils.ts", + "_zlib_binding.mjs", + "_zlib.mjs", + "assert.ts", + "assert/strict.ts", + "assertion_error.ts", + "async_hooks.ts", + "buffer.ts", + "child_process.ts", + "cluster.ts", + "console.ts", + "constants.ts", + "crypto.ts", + "dgram.ts", + "diagnostics_channel.ts", + "dns.ts", + "dns/promises.ts", + "domain.ts", + "events.ts", + "fs.ts", + "fs/promises.ts", + "global.ts", + "http.ts", + "http2.ts", + "https.ts", + "inspector.ts", + "internal_binding/_libuv_winerror.ts", + "internal_binding/_listen.ts", + "internal_binding/_node.ts", + "internal_binding/_timingSafeEqual.ts", + "internal_binding/_utils.ts", + "internal_binding/ares.ts", + "internal_binding/async_wrap.ts", + "internal_binding/buffer.ts", + "internal_binding/cares_wrap.ts", + "internal_binding/config.ts", + "internal_binding/connection_wrap.ts", + "internal_binding/constants.ts", + "internal_binding/contextify.ts", + "internal_binding/credentials.ts", + "internal_binding/crypto.ts", + "internal_binding/errors.ts", + "internal_binding/fs_dir.ts", + "internal_binding/fs_event_wrap.ts", + "internal_binding/fs.ts", + "internal_binding/handle_wrap.ts", + "internal_binding/heap_utils.ts", + "internal_binding/http_parser.ts", + "internal_binding/icu.ts", + "internal_binding/inspector.ts", + "internal_binding/js_stream.ts", + "internal_binding/messaging.ts", + "internal_binding/mod.ts", + "internal_binding/module_wrap.ts", + "internal_binding/native_module.ts", + "internal_binding/natives.ts", + "internal_binding/node_file.ts", + "internal_binding/node_options.ts", + "internal_binding/options.ts", + "internal_binding/os.ts", + "internal_binding/performance.ts", + "internal_binding/pipe_wrap.ts", + "internal_binding/process_methods.ts", + "internal_binding/report.ts", + "internal_binding/serdes.ts", + "internal_binding/signal_wrap.ts", + "internal_binding/spawn_sync.ts", + "internal_binding/stream_wrap.ts", + "internal_binding/string_decoder.ts", + "internal_binding/symbols.ts", + "internal_binding/task_queue.ts", + "internal_binding/tcp_wrap.ts", + "internal_binding/timers.ts", + "internal_binding/tls_wrap.ts", + "internal_binding/trace_events.ts", + "internal_binding/tty_wrap.ts", + "internal_binding/types.ts", + "internal_binding/udp_wrap.ts", + "internal_binding/url.ts", + "internal_binding/util.ts", + "internal_binding/uv.ts", + "internal_binding/v8.ts", + "internal_binding/worker.ts", + "internal_binding/zlib.ts", + "internal/assert.mjs", + "internal/async_hooks.ts", + "internal/blob.mjs", + "internal/buffer.mjs", + "internal/child_process.ts", + "internal/cli_table.ts", + "internal/console/constructor.mjs", + "internal/constants.ts", + "internal/crypto/_hex.ts", + "internal/crypto/_keys.ts", + "internal/crypto/_randomBytes.ts", + "internal/crypto/_randomFill.ts", + "internal/crypto/_randomInt.ts", + "internal/crypto/certificate.ts", + "internal/crypto/cipher.ts", + "internal/crypto/constants.ts", + "internal/crypto/diffiehellman.ts", + "internal/crypto/hash.ts", + "internal/crypto/hkdf.ts", + "internal/crypto/keygen.ts", + "internal/crypto/keys.ts", + "internal/crypto/pbkdf2.ts", + "internal/crypto/random.ts", + "internal/crypto/scrypt.ts", + "internal/crypto/sig.ts", + "internal/crypto/types.ts", + "internal/crypto/util.ts", + "internal/crypto/x509.ts", + "internal/dgram.ts", + "internal/dns/promises.ts", + "internal/dns/utils.ts", + "internal/dtrace.ts", + "internal/error_codes.ts", + "internal/errors.ts", + "internal/event_target.mjs", + "internal/fixed_queue.ts", + "internal/freelist.ts", + "internal/fs/streams.mjs", + "internal/fs/utils.mjs", + "internal/hide_stack_frames.ts", + "internal/http.ts", + "internal/idna.ts", + "internal/net.ts", + "internal/normalize_encoding.mjs", + "internal/options.ts", + "internal/primordials.mjs", + "internal/process/per_thread.mjs", + "internal/querystring.ts", + "internal/readline/callbacks.mjs", + "internal/readline/emitKeypressEvents.mjs", + "internal/readline/interface.mjs", + "internal/readline/promises.mjs", + "internal/readline/symbols.mjs", + "internal/readline/utils.mjs", + "internal/stream_base_commons.ts", + "internal/streams/add-abort-signal.mjs", + "internal/streams/buffer_list.mjs", + "internal/streams/destroy.mjs", + "internal/streams/duplex.mjs", + "internal/streams/end-of-stream.mjs", + "internal/streams/lazy_transform.mjs", + "internal/streams/legacy.mjs", + "internal/streams/passthrough.mjs", + "internal/streams/readable.mjs", + "internal/streams/state.mjs", + "internal/streams/transform.mjs", + "internal/streams/utils.mjs", + "internal/streams/writable.mjs", + "internal/test/binding.ts", + "internal/timers.mjs", + "internal/url.ts", + "internal/util.mjs", + "internal/util/comparisons.ts", + "internal/util/debuglog.ts", + "internal/util/inspect.mjs", + "internal/util/types.ts", + "internal/validators.mjs", + "module_all.ts", + "module_esm.ts", + "module.js", + "net.ts", + "os.ts", + "path.ts", + "path/_constants.ts", + "path/_interface.ts", + "path/_util.ts", + "path/common.ts", + "path/glob.ts", + "path/mod.ts", + "path/posix.ts", + "path/separator.ts", + "path/win32.ts", + "perf_hooks.ts", + "process.ts", + "punycode.ts", + "querystring.ts", + "readline.ts", + "readline/promises.ts", + "repl.ts", + "stream.ts", + "stream/consumers.mjs", + "stream/promises.mjs", + "stream/web.ts", + "string_decoder.ts", + "sys.ts", + "timers.ts", + "timers/promises.ts", + "tls.ts", + "tty.ts", + "upstream_modules.ts", + "url.ts", + "util.ts", + "util/types.ts", + "v8.ts", + "vm.ts", + "wasi.ts", + "worker_threads.ts", + "zlib.ts", + ); + + Extension::builder(env!("CARGO_PKG_NAME")) + .esm(esm_files) + .esm_entry_point("internal:deno_node/polyfills/module_all.ts") + .ops(vec![ + crypto::op_node_create_hash::decl(), + crypto::op_node_hash_update::decl(), + crypto::op_node_hash_digest::decl(), + crypto::op_node_hash_clone::decl(), + crypto::op_node_private_encrypt::decl(), + crypto::op_node_private_decrypt::decl(), + crypto::op_node_public_encrypt::decl(), + winerror::op_node_sys_to_uv_error::decl(), + v8::op_v8_cached_data_version_tag::decl(), + v8::op_v8_get_heap_statistics::decl(), + idna::op_node_idna_domain_to_ascii::decl(), + idna::op_node_idna_domain_to_unicode::decl(), + idna::op_node_idna_punycode_decode::decl(), + idna::op_node_idna_punycode_encode::decl(), + op_node_build_os::decl(), + ]) + .build() +} + pub fn init( maybe_npm_resolver: Option>, ) -> Extension { - Extension::builder(env!("CARGO_PKG_NAME")) - .js(include_js_files!( - prefix "internal:ext/node", + Extension::builder("deno_node_loading") + .esm(include_js_files!( "01_node.js", "02_require.js", + "module_es_shim.js", )) .ops(vec![ - op_require_init_paths::decl(), - op_require_node_module_paths::decl::

(), - op_require_proxy_path::decl(), - op_require_is_deno_dir_package::decl(), - op_require_resolve_deno_dir::decl(), - op_require_is_request_relative::decl(), - op_require_resolve_lookup_paths::decl(), - op_require_try_self_parent_path::decl::

(), - op_require_try_self::decl::

(), - op_require_real_path::decl::

(), - op_require_path_is_absolute::decl(), - op_require_path_dirname::decl(), - op_require_stat::decl::

(), - op_require_path_resolve::decl(), - op_require_path_basename::decl(), - op_require_read_file::decl::

(), - op_require_as_file_path::decl(), - op_require_resolve_exports::decl::

(), - op_require_read_closest_package_json::decl::

(), - op_require_read_package_scope::decl::

(), - op_require_package_imports_resolve::decl::

(), - op_require_break_on_next_statement::decl(), + ops::op_require_init_paths::decl(), + ops::op_require_node_module_paths::decl::

(), + ops::op_require_proxy_path::decl(), + ops::op_require_is_deno_dir_package::decl(), + ops::op_require_resolve_deno_dir::decl(), + ops::op_require_is_request_relative::decl(), + ops::op_require_resolve_lookup_paths::decl(), + ops::op_require_try_self_parent_path::decl::

(), + ops::op_require_try_self::decl::

(), + ops::op_require_real_path::decl::

(), + ops::op_require_path_is_absolute::decl(), + ops::op_require_path_dirname::decl(), + ops::op_require_stat::decl::

(), + ops::op_require_path_resolve::decl(), + ops::op_require_path_basename::decl(), + ops::op_require_read_file::decl::

(), + ops::op_require_as_file_path::decl(), + ops::op_require_resolve_exports::decl::

(), + ops::op_require_read_closest_package_json::decl::

(), + ops::op_require_read_package_scope::decl::

(), + ops::op_require_package_imports_resolve::decl::

(), + ops::op_require_break_on_next_statement::decl(), ]) .state(move |state| { if let Some(npm_resolver) = maybe_npm_resolver.clone() { @@ -123,749 +428,69 @@ pub fn init( .build() } -fn ensure_read_permission

( - state: &mut OpState, - file_path: &Path, -) -> Result<(), AnyError> -where - P: NodePermissions + 'static, -{ - let resolver = { - let resolver = state.borrow::>(); - resolver.clone() - }; - let permissions = state.borrow_mut::

(); - resolver.ensure_read_permission(permissions, file_path) -} - -#[op] -pub fn op_require_init_paths() -> Vec { - // todo(dsherret): this code is node compat mode specific and - // we probably don't want it for small mammal, so ignore it for now - - // let (home_dir, node_path) = if cfg!(windows) { - // ( - // std::env::var("USERPROFILE").unwrap_or_else(|_| "".into()), - // std::env::var("NODE_PATH").unwrap_or_else(|_| "".into()), - // ) - // } else { - // ( - // std::env::var("HOME").unwrap_or_else(|_| "".into()), - // std::env::var("NODE_PATH").unwrap_or_else(|_| "".into()), - // ) - // }; - - // let mut prefix_dir = std::env::current_exe().unwrap(); - // if cfg!(windows) { - // prefix_dir = prefix_dir.join("..").join("..") - // } else { - // prefix_dir = prefix_dir.join("..") - // } - - // let mut paths = vec![prefix_dir.join("lib").join("node")]; - - // if !home_dir.is_empty() { - // paths.insert(0, PathBuf::from(&home_dir).join(".node_libraries")); - // paths.insert(0, PathBuf::from(&home_dir).join(".nod_modules")); - // } - - // let mut paths = paths - // .into_iter() - // .map(|p| p.to_string_lossy().to_string()) - // .collect(); - - // if !node_path.is_empty() { - // let delimiter = if cfg!(windows) { ";" } else { ":" }; - // let mut node_paths: Vec = node_path - // .split(delimiter) - // .filter(|e| !e.is_empty()) - // .map(|s| s.to_string()) - // .collect(); - // node_paths.append(&mut paths); - // paths = node_paths; - // } - - vec![] -} - -#[op] -pub fn op_require_node_module_paths

( - state: &mut OpState, - from: String, -) -> Result, AnyError> -where - P: NodePermissions + 'static, -{ - // Guarantee that "from" is absolute. - let from = deno_core::resolve_path(&from) - .unwrap() - .to_file_path() - .unwrap(); - - ensure_read_permission::

(state, &from)?; - - if cfg!(windows) { - // return root node_modules when path is 'D:\\'. - let from_str = from.to_str().unwrap(); - if from_str.len() >= 3 { - let bytes = from_str.as_bytes(); - if bytes[from_str.len() - 1] == b'\\' && bytes[from_str.len() - 2] == b':' - { - let p = from_str.to_owned() + "node_modules"; - return Ok(vec![p]); - } - } - } else { - // Return early not only to avoid unnecessary work, but to *avoid* returning - // an array of two items for a root: [ '//node_modules', '/node_modules' ] - if from.to_string_lossy() == "/" { - return Ok(vec!["/node_modules".to_string()]); - } - } - - let mut paths = vec![]; - let mut current_path = from.as_path(); - let mut maybe_parent = Some(current_path); - while let Some(parent) = maybe_parent { - if !parent.ends_with("/node_modules") { - paths.push(parent.join("node_modules").to_string_lossy().to_string()); - current_path = parent; - maybe_parent = current_path.parent(); - } - } - - if !cfg!(windows) { - // Append /node_modules to handle root paths. - paths.push("/node_modules".to_string()); - } - - Ok(paths) -} - -#[op] -fn op_require_proxy_path(filename: String) -> String { - // Allow a directory to be passed as the filename - let trailing_slash = if cfg!(windows) { - // Node also counts a trailing forward slash as a - // directory for node on Windows, but not backslashes - // on non-Windows platforms - filename.ends_with('\\') || filename.ends_with('/') - } else { - filename.ends_with('/') - }; - - if trailing_slash { - let p = PathBuf::from(filename); - p.join("noop.js").to_string_lossy().to_string() - } else { - filename - } -} - -#[op] -fn op_require_is_request_relative(request: String) -> bool { - if request.starts_with("./") || request.starts_with("../") || request == ".." - { - return true; - } - - if cfg!(windows) { - if request.starts_with(".\\") { - return true; - } - - if request.starts_with("..\\") { - return true; - } - } - - false -} - -#[op] -fn op_require_resolve_deno_dir( - state: &mut OpState, - request: String, - parent_filename: String, -) -> Option { - let resolver = state.borrow::>(); - resolver - .resolve_package_folder_from_package( - &request, - &PathBuf::from(parent_filename), - NodeResolutionMode::Execution, - ) - .ok() - .map(|p| p.to_string_lossy().to_string()) -} - -#[op] -fn op_require_is_deno_dir_package(state: &mut OpState, path: String) -> bool { - let resolver = state.borrow::>(); - resolver.in_npm_package(&PathBuf::from(path)) -} - -#[op] -fn op_require_resolve_lookup_paths( - request: String, - maybe_parent_paths: Option>, - parent_filename: String, -) -> Option> { - if !request.starts_with('.') - || (request.len() > 1 - && !request.starts_with("..") - && !request.starts_with("./") - && (!cfg!(windows) || !request.starts_with(".\\"))) - { - let module_paths = vec![]; - let mut paths = module_paths; - if let Some(mut parent_paths) = maybe_parent_paths { - if !parent_paths.is_empty() { - paths.append(&mut parent_paths); - } - } - - if !paths.is_empty() { - return Some(paths); - } else { - return None; - } - } - - // In REPL, parent.filename is null. - // if (!parent || !parent.id || !parent.filename) { - // // Make require('./path/to/foo') work - normally the path is taken - // // from realpath(__filename) but in REPL there is no filename - // const mainPaths = ['.']; - - // debug('looking for %j in %j', request, mainPaths); - // return mainPaths; - // } - - let p = PathBuf::from(parent_filename); - Some(vec![p.parent().unwrap().to_string_lossy().to_string()]) -} - -#[op] -fn op_require_path_is_absolute(p: String) -> bool { - PathBuf::from(p).is_absolute() -} - -#[op] -fn op_require_stat

( - state: &mut OpState, - path: String, -) -> Result -where - P: NodePermissions + 'static, -{ - let path = PathBuf::from(path); - ensure_read_permission::

(state, &path)?; - if let Ok(metadata) = std::fs::metadata(&path) { - if metadata.is_file() { - return Ok(0); - } else { - return Ok(1); - } - } - - Ok(-1) -} - -#[op] -fn op_require_real_path

( - state: &mut OpState, - request: String, -) -> Result -where - P: NodePermissions + 'static, -{ - let path = PathBuf::from(request); - ensure_read_permission::

(state, &path)?; - let mut canonicalized_path = path.canonicalize()?; - if cfg!(windows) { - canonicalized_path = PathBuf::from( - canonicalized_path - .display() - .to_string() - .trim_start_matches("\\\\?\\"), - ); - } - Ok(canonicalized_path.to_string_lossy().to_string()) -} - -fn path_resolve(parts: Vec) -> String { - assert!(!parts.is_empty()); - let mut p = PathBuf::from(&parts[0]); - if parts.len() > 1 { - for part in &parts[1..] { - p = p.join(part); - } - } - normalize_path(p).to_string_lossy().to_string() -} - -#[op] -fn op_require_path_resolve(parts: Vec) -> String { - path_resolve(parts) -} - -#[op] -fn op_require_path_dirname(request: String) -> Result { - let p = PathBuf::from(request); - if let Some(parent) = p.parent() { - Ok(parent.to_string_lossy().to_string()) - } else { - Err(generic_error("Path doesn't have a parent")) - } -} - -#[op] -fn op_require_path_basename(request: String) -> Result { - let p = PathBuf::from(request); - if let Some(path) = p.file_name() { - Ok(path.to_string_lossy().to_string()) - } else { - Err(generic_error("Path doesn't have a file name")) - } -} - -#[op] -fn op_require_try_self_parent_path

( - state: &mut OpState, - has_parent: bool, - maybe_parent_filename: Option, - maybe_parent_id: Option, -) -> Result, AnyError> -where - P: NodePermissions + 'static, -{ - if !has_parent { - return Ok(None); - } - - if let Some(parent_filename) = maybe_parent_filename { - return Ok(Some(parent_filename)); - } - - if let Some(parent_id) = maybe_parent_id { - if parent_id == "" || parent_id == "internal/preload" { - if let Ok(cwd) = std::env::current_dir() { - ensure_read_permission::

(state, &cwd)?; - return Ok(Some(cwd.to_string_lossy().to_string())); - } - } - } - Ok(None) -} - -#[op] -fn op_require_try_self

( - state: &mut OpState, - parent_path: Option, - request: String, -) -> Result, AnyError> -where - P: NodePermissions + 'static, -{ - if parent_path.is_none() { - return Ok(None); - } - - let resolver = state.borrow::>().clone(); - let permissions = state.borrow_mut::

(); - let pkg = resolution::get_package_scope_config( - &Url::from_file_path(parent_path.unwrap()).unwrap(), - &*resolver, - permissions, - ) - .ok(); - if pkg.is_none() { - return Ok(None); - } - - let pkg = pkg.unwrap(); - if pkg.exports.is_none() { - return Ok(None); - } - if pkg.name.is_none() { - return Ok(None); - } - - let pkg_name = pkg.name.as_ref().unwrap().to_string(); - let mut expansion = ".".to_string(); - - if request == pkg_name { - // pass - } else if request.starts_with(&format!("{pkg_name}/")) { - expansion += &request[pkg_name.len()..]; - } else { - return Ok(None); - } - - let referrer = deno_core::url::Url::from_file_path(&pkg.path).unwrap(); - if let Some(exports) = &pkg.exports { - resolution::package_exports_resolve( - &pkg.path, - expansion, - exports, - &referrer, - NodeModuleKind::Cjs, - resolution::REQUIRE_CONDITIONS, - NodeResolutionMode::Execution, - &*resolver, - permissions, - ) - .map(|r| Some(r.to_string_lossy().to_string())) - } else { - Ok(None) - } -} - -#[op] -fn op_require_read_file

( - state: &mut OpState, - file_path: String, -) -> Result -where - P: NodePermissions + 'static, -{ - let file_path = PathBuf::from(file_path); - ensure_read_permission::

(state, &file_path)?; - Ok(std::fs::read_to_string(file_path)?) -} - -#[op] -pub fn op_require_as_file_path(file_or_url: String) -> String { - if let Ok(url) = Url::parse(&file_or_url) { - if let Ok(p) = url.to_file_path() { - return p.to_string_lossy().to_string(); - } - } - - file_or_url -} - -#[op] -fn op_require_resolve_exports

( - state: &mut OpState, +pub async fn initialize_runtime( + js_runtime: &mut JsRuntime, uses_local_node_modules_dir: bool, - modules_path: String, - _request: String, - name: String, - expansion: String, - parent_path: String, -) -> Result, AnyError> -where - P: NodePermissions + 'static, -{ - let resolver = state.borrow::>().clone(); - let permissions = state.borrow_mut::

(); - - let pkg_path = if resolver.in_npm_package(&PathBuf::from(&modules_path)) - && !uses_local_node_modules_dir - { - modules_path - } else { - path_resolve(vec![modules_path, name]) - }; - let pkg = PackageJson::load( - &*resolver, - permissions, - PathBuf::from(&pkg_path).join("package.json"), - )?; - - if let Some(exports) = &pkg.exports { - let referrer = Url::from_file_path(parent_path).unwrap(); - resolution::package_exports_resolve( - &pkg.path, - format!(".{expansion}"), - exports, - &referrer, - NodeModuleKind::Cjs, - resolution::REQUIRE_CONDITIONS, - NodeResolutionMode::Execution, - &*resolver, - permissions, - ) - .map(|r| Some(r.to_string_lossy().to_string())) - } else { - Ok(None) - } +) -> Result<(), AnyError> { + let source_code = &format!( + r#"(async function loadBuiltinNodeModules(nodeGlobalThisName, usesLocalNodeModulesDir) {{ + Deno[Deno.internal].node.initialize(Deno[Deno.internal].nodeModuleAll, nodeGlobalThisName); + if (usesLocalNodeModulesDir) {{ + Deno[Deno.internal].require.setUsesLocalNodeModulesDir(); + }} + }})('{}', {});"#, + NODE_GLOBAL_THIS_NAME.as_str(), + uses_local_node_modules_dir, + ); + + let value = + js_runtime.execute_script(&located_script_name!(), source_code)?; + js_runtime.resolve_value(value).await?; + Ok(()) } -#[op] -fn op_require_read_closest_package_json

( - state: &mut OpState, - filename: String, -) -> Result -where - P: NodePermissions + 'static, -{ - ensure_read_permission::

( - state, - PathBuf::from(&filename).parent().unwrap(), - )?; - let resolver = state.borrow::>().clone(); - let permissions = state.borrow_mut::

(); - resolution::get_closest_package_json( - &Url::from_file_path(filename).unwrap(), - &*resolver, - permissions, - ) -} - -#[op] -fn op_require_read_package_scope

( - state: &mut OpState, - package_json_path: String, -) -> Option -where - P: NodePermissions + 'static, -{ - let resolver = state.borrow::>().clone(); - let permissions = state.borrow_mut::

(); - let package_json_path = PathBuf::from(package_json_path); - PackageJson::load(&*resolver, permissions, package_json_path).ok() -} - -#[op] -fn op_require_package_imports_resolve

( - state: &mut OpState, - parent_filename: String, - request: String, -) -> Result, AnyError> -where - P: NodePermissions + 'static, -{ - let parent_path = PathBuf::from(&parent_filename); - ensure_read_permission::

(state, &parent_path)?; - let resolver = state.borrow::>().clone(); - let permissions = state.borrow_mut::

(); - let pkg = PackageJson::load( - &*resolver, - permissions, - parent_path.join("package.json"), - )?; - - if pkg.imports.is_some() { - let referrer = - deno_core::url::Url::from_file_path(&parent_filename).unwrap(); - let r = resolution::package_imports_resolve( - &request, - &referrer, - NodeModuleKind::Cjs, - resolution::REQUIRE_CONDITIONS, - NodeResolutionMode::Execution, - &*resolver, - permissions, - ) - .map(|r| Some(Url::from_file_path(r).unwrap().to_string())); - state.put(resolver); - r - } else { - Ok(None) +pub fn load_cjs_module( + js_runtime: &mut JsRuntime, + module: &str, + main: bool, + inspect_brk: bool, +) -> Result<(), AnyError> { + fn escape_for_single_quote_string(text: &str) -> String { + text.replace('\\', r"\\").replace('\'', r"\'") } -} -#[op] -fn op_require_break_on_next_statement(state: &mut OpState) { - let inspector = state.borrow::>>(); - inspector - .borrow_mut() - .wait_for_session_and_break_on_next_statement() + let source_code = &format!( + r#"(function loadCjsModule(module, inspectBrk) {{ + if (inspectBrk) {{ + Deno[Deno.internal].require.setInspectBrk(); + }} + Deno[Deno.internal].require.Module._load(module, null, {main}); + }})('{module}', {inspect_brk});"#, + main = main, + module = escape_for_single_quote_string(module), + inspect_brk = inspect_brk, + ); + + js_runtime.execute_script(&located_script_name!(), source_code)?; + Ok(()) } -pub struct NodeModulePolyfill { - /// Name of the module like "assert" or "timers/promises" - pub name: &'static str, - - /// Specifier relative to the root of `deno_std` repo, like "node/assert.ts" - pub specifier: &'static str, +pub async fn initialize_binary_command( + js_runtime: &mut JsRuntime, + binary_name: &str, +) -> Result<(), AnyError> { + // overwrite what's done in deno_std in order to set the binary arg name + let source_code = &format!( + r#"(async function initializeBinaryCommand(binaryName) {{ + const process = Deno[Deno.internal].node.globalThis.process; + Object.defineProperty(process.argv, "0", {{ + get: () => binaryName, + }}); + }})('{binary_name}');"#, + ); + + let value = + js_runtime.execute_script(&located_script_name!(), source_code)?; + js_runtime.resolve_value(value).await?; + Ok(()) } - -pub static SUPPORTED_BUILTIN_NODE_MODULES: &[NodeModulePolyfill] = &[ - NodeModulePolyfill { - name: "assert", - specifier: "node/assert.ts", - }, - NodeModulePolyfill { - name: "assert/strict", - specifier: "node/assert/strict.ts", - }, - NodeModulePolyfill { - name: "async_hooks", - specifier: "node/async_hooks.ts", - }, - NodeModulePolyfill { - name: "buffer", - specifier: "node/buffer.ts", - }, - NodeModulePolyfill { - name: "child_process", - specifier: "node/child_process.ts", - }, - NodeModulePolyfill { - name: "cluster", - specifier: "node/cluster.ts", - }, - NodeModulePolyfill { - name: "console", - specifier: "node/console.ts", - }, - NodeModulePolyfill { - name: "constants", - specifier: "node/constants.ts", - }, - NodeModulePolyfill { - name: "crypto", - specifier: "node/crypto.ts", - }, - NodeModulePolyfill { - name: "dgram", - specifier: "node/dgram.ts", - }, - NodeModulePolyfill { - name: "dns", - specifier: "node/dns.ts", - }, - NodeModulePolyfill { - name: "dns/promises", - specifier: "node/dns/promises.ts", - }, - NodeModulePolyfill { - name: "domain", - specifier: "node/domain.ts", - }, - NodeModulePolyfill { - name: "events", - specifier: "node/events.ts", - }, - NodeModulePolyfill { - name: "fs", - specifier: "node/fs.ts", - }, - NodeModulePolyfill { - name: "fs/promises", - specifier: "node/fs/promises.ts", - }, - NodeModulePolyfill { - name: "http", - specifier: "node/http.ts", - }, - NodeModulePolyfill { - name: "https", - specifier: "node/https.ts", - }, - NodeModulePolyfill { - name: "module", - // NOTE(bartlomieju): `module` is special, because we don't want to use - // `deno_std/node/module.ts`, but instead use a special shim that we - // provide in `ext/node`. - specifier: "[USE `deno_node::MODULE_ES_SHIM` to get this module]", - }, - NodeModulePolyfill { - name: "net", - specifier: "node/net.ts", - }, - NodeModulePolyfill { - name: "os", - specifier: "node/os.ts", - }, - NodeModulePolyfill { - name: "path", - specifier: "node/path.ts", - }, - NodeModulePolyfill { - name: "path/posix", - specifier: "node/path/posix.ts", - }, - NodeModulePolyfill { - name: "path/win32", - specifier: "node/path/win32.ts", - }, - NodeModulePolyfill { - name: "perf_hooks", - specifier: "node/perf_hooks.ts", - }, - NodeModulePolyfill { - name: "process", - specifier: "node/process.ts", - }, - NodeModulePolyfill { - name: "querystring", - specifier: "node/querystring.ts", - }, - NodeModulePolyfill { - name: "readline", - specifier: "node/readline.ts", - }, - NodeModulePolyfill { - name: "stream", - specifier: "node/stream.ts", - }, - NodeModulePolyfill { - name: "stream/consumers", - specifier: "node/stream/consumers.mjs", - }, - NodeModulePolyfill { - name: "stream/promises", - specifier: "node/stream/promises.mjs", - }, - NodeModulePolyfill { - name: "stream/web", - specifier: "node/stream/web.ts", - }, - NodeModulePolyfill { - name: "string_decoder", - specifier: "node/string_decoder.ts", - }, - NodeModulePolyfill { - name: "sys", - specifier: "node/sys.ts", - }, - NodeModulePolyfill { - name: "timers", - specifier: "node/timers.ts", - }, - NodeModulePolyfill { - name: "timers/promises", - specifier: "node/timers/promises.ts", - }, - NodeModulePolyfill { - name: "tls", - specifier: "node/tls.ts", - }, - NodeModulePolyfill { - name: "tty", - specifier: "node/tty.ts", - }, - NodeModulePolyfill { - name: "url", - specifier: "node/url.ts", - }, - NodeModulePolyfill { - name: "util", - specifier: "node/util.ts", - }, - NodeModulePolyfill { - name: "util/types", - specifier: "node/util/types.ts", - }, - NodeModulePolyfill { - name: "v8", - specifier: "node/v8.ts", - }, - NodeModulePolyfill { - name: "vm", - specifier: "node/vm.ts", - }, - NodeModulePolyfill { - name: "worker_threads", - specifier: "node/worker_threads.ts", - }, - NodeModulePolyfill { - name: "zlib", - specifier: "node/zlib.ts", - }, -]; diff --git a/ext/node/module_es_shim.js b/ext/node/module_es_shim.js index f32006b4a5094e..2b7c20e26da3b2 100644 --- a/ext/node/module_es_shim.js +++ b/ext/node/module_es_shim.js @@ -1,6 +1,7 @@ // Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. -const m = Deno[Deno.internal].require.Module; +const internals = globalThis.__bootstrap.internals; +const m = internals.require.Module; export const _cache = m._cache; export const _extensions = m._extensions; export const _findPath = m._findPath; diff --git a/ext/node/ops.rs b/ext/node/ops.rs new file mode 100644 index 00000000000000..046578ca53df30 --- /dev/null +++ b/ext/node/ops.rs @@ -0,0 +1,573 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. + +use deno_core::error::generic_error; +use deno_core::error::AnyError; +use deno_core::normalize_path; +use deno_core::op; +use deno_core::url::Url; +use deno_core::JsRuntimeInspector; +use deno_core::OpState; +use std::cell::RefCell; +use std::path::Path; +use std::path::PathBuf; +use std::rc::Rc; + +use super::resolution; +use super::NodeModuleKind; +use super::NodePermissions; +use super::NodeResolutionMode; +use super::PackageJson; +use super::RequireNpmResolver; + +fn ensure_read_permission

( + state: &mut OpState, + file_path: &Path, +) -> Result<(), AnyError> +where + P: NodePermissions + 'static, +{ + let resolver = { + let resolver = state.borrow::>(); + resolver.clone() + }; + let permissions = state.borrow_mut::

(); + resolver.ensure_read_permission(permissions, file_path) +} + +#[op] +pub fn op_require_init_paths() -> Vec { + // todo(dsherret): this code is node compat mode specific and + // we probably don't want it for small mammal, so ignore it for now + + // let (home_dir, node_path) = if cfg!(windows) { + // ( + // std::env::var("USERPROFILE").unwrap_or_else(|_| "".into()), + // std::env::var("NODE_PATH").unwrap_or_else(|_| "".into()), + // ) + // } else { + // ( + // std::env::var("HOME").unwrap_or_else(|_| "".into()), + // std::env::var("NODE_PATH").unwrap_or_else(|_| "".into()), + // ) + // }; + + // let mut prefix_dir = std::env::current_exe().unwrap(); + // if cfg!(windows) { + // prefix_dir = prefix_dir.join("..").join("..") + // } else { + // prefix_dir = prefix_dir.join("..") + // } + + // let mut paths = vec![prefix_dir.join("lib").join("node")]; + + // if !home_dir.is_empty() { + // paths.insert(0, PathBuf::from(&home_dir).join(".node_libraries")); + // paths.insert(0, PathBuf::from(&home_dir).join(".nod_modules")); + // } + + // let mut paths = paths + // .into_iter() + // .map(|p| p.to_string_lossy().to_string()) + // .collect(); + + // if !node_path.is_empty() { + // let delimiter = if cfg!(windows) { ";" } else { ":" }; + // let mut node_paths: Vec = node_path + // .split(delimiter) + // .filter(|e| !e.is_empty()) + // .map(|s| s.to_string()) + // .collect(); + // node_paths.append(&mut paths); + // paths = node_paths; + // } + + vec![] +} + +#[op] +pub fn op_require_node_module_paths

( + state: &mut OpState, + from: String, +) -> Result, AnyError> +where + P: NodePermissions + 'static, +{ + // Guarantee that "from" is absolute. + let from = deno_core::resolve_path(&from) + .unwrap() + .to_file_path() + .unwrap(); + + ensure_read_permission::

(state, &from)?; + + if cfg!(windows) { + // return root node_modules when path is 'D:\\'. + let from_str = from.to_str().unwrap(); + if from_str.len() >= 3 { + let bytes = from_str.as_bytes(); + if bytes[from_str.len() - 1] == b'\\' && bytes[from_str.len() - 2] == b':' + { + let p = from_str.to_owned() + "node_modules"; + return Ok(vec![p]); + } + } + } else { + // Return early not only to avoid unnecessary work, but to *avoid* returning + // an array of two items for a root: [ '//node_modules', '/node_modules' ] + if from.to_string_lossy() == "/" { + return Ok(vec!["/node_modules".to_string()]); + } + } + + let mut paths = vec![]; + let mut current_path = from.as_path(); + let mut maybe_parent = Some(current_path); + while let Some(parent) = maybe_parent { + if !parent.ends_with("/node_modules") { + paths.push(parent.join("node_modules").to_string_lossy().to_string()); + current_path = parent; + maybe_parent = current_path.parent(); + } + } + + if !cfg!(windows) { + // Append /node_modules to handle root paths. + paths.push("/node_modules".to_string()); + } + + Ok(paths) +} + +#[op] +fn op_require_proxy_path(filename: String) -> String { + // Allow a directory to be passed as the filename + let trailing_slash = if cfg!(windows) { + // Node also counts a trailing forward slash as a + // directory for node on Windows, but not backslashes + // on non-Windows platforms + filename.ends_with('\\') || filename.ends_with('/') + } else { + filename.ends_with('/') + }; + + if trailing_slash { + let p = PathBuf::from(filename); + p.join("noop.js").to_string_lossy().to_string() + } else { + filename + } +} + +#[op] +fn op_require_is_request_relative(request: String) -> bool { + if request.starts_with("./") || request.starts_with("../") || request == ".." + { + return true; + } + + if cfg!(windows) { + if request.starts_with(".\\") { + return true; + } + + if request.starts_with("..\\") { + return true; + } + } + + false +} + +#[op] +fn op_require_resolve_deno_dir( + state: &mut OpState, + request: String, + parent_filename: String, +) -> Option { + let resolver = state.borrow::>(); + resolver + .resolve_package_folder_from_package( + &request, + &PathBuf::from(parent_filename), + NodeResolutionMode::Execution, + ) + .ok() + .map(|p| p.to_string_lossy().to_string()) +} + +#[op] +fn op_require_is_deno_dir_package(state: &mut OpState, path: String) -> bool { + let resolver = state.borrow::>(); + resolver.in_npm_package(&PathBuf::from(path)) +} + +#[op] +fn op_require_resolve_lookup_paths( + request: String, + maybe_parent_paths: Option>, + parent_filename: String, +) -> Option> { + if !request.starts_with('.') + || (request.len() > 1 + && !request.starts_with("..") + && !request.starts_with("./") + && (!cfg!(windows) || !request.starts_with(".\\"))) + { + let module_paths = vec![]; + let mut paths = module_paths; + if let Some(mut parent_paths) = maybe_parent_paths { + if !parent_paths.is_empty() { + paths.append(&mut parent_paths); + } + } + + if !paths.is_empty() { + return Some(paths); + } else { + return None; + } + } + + // In REPL, parent.filename is null. + // if (!parent || !parent.id || !parent.filename) { + // // Make require('./path/to/foo') work - normally the path is taken + // // from realpath(__filename) but in REPL there is no filename + // const mainPaths = ['.']; + + // debug('looking for %j in %j', request, mainPaths); + // return mainPaths; + // } + + let p = PathBuf::from(parent_filename); + Some(vec![p.parent().unwrap().to_string_lossy().to_string()]) +} + +#[op] +fn op_require_path_is_absolute(p: String) -> bool { + PathBuf::from(p).is_absolute() +} + +#[op] +fn op_require_stat

( + state: &mut OpState, + path: String, +) -> Result +where + P: NodePermissions + 'static, +{ + let path = PathBuf::from(path); + ensure_read_permission::

(state, &path)?; + if let Ok(metadata) = std::fs::metadata(&path) { + if metadata.is_file() { + return Ok(0); + } else { + return Ok(1); + } + } + + Ok(-1) +} + +#[op] +fn op_require_real_path

( + state: &mut OpState, + request: String, +) -> Result +where + P: NodePermissions + 'static, +{ + let path = PathBuf::from(request); + ensure_read_permission::

(state, &path)?; + let mut canonicalized_path = path.canonicalize()?; + if cfg!(windows) { + canonicalized_path = PathBuf::from( + canonicalized_path + .display() + .to_string() + .trim_start_matches("\\\\?\\"), + ); + } + Ok(canonicalized_path.to_string_lossy().to_string()) +} + +fn path_resolve(parts: Vec) -> String { + assert!(!parts.is_empty()); + let mut p = PathBuf::from(&parts[0]); + if parts.len() > 1 { + for part in &parts[1..] { + p = p.join(part); + } + } + normalize_path(p).to_string_lossy().to_string() +} + +#[op] +fn op_require_path_resolve(parts: Vec) -> String { + path_resolve(parts) +} + +#[op] +fn op_require_path_dirname(request: String) -> Result { + let p = PathBuf::from(request); + if let Some(parent) = p.parent() { + Ok(parent.to_string_lossy().to_string()) + } else { + Err(generic_error("Path doesn't have a parent")) + } +} + +#[op] +fn op_require_path_basename(request: String) -> Result { + let p = PathBuf::from(request); + if let Some(path) = p.file_name() { + Ok(path.to_string_lossy().to_string()) + } else { + Err(generic_error("Path doesn't have a file name")) + } +} + +#[op] +fn op_require_try_self_parent_path

( + state: &mut OpState, + has_parent: bool, + maybe_parent_filename: Option, + maybe_parent_id: Option, +) -> Result, AnyError> +where + P: NodePermissions + 'static, +{ + if !has_parent { + return Ok(None); + } + + if let Some(parent_filename) = maybe_parent_filename { + return Ok(Some(parent_filename)); + } + + if let Some(parent_id) = maybe_parent_id { + if parent_id == "" || parent_id == "internal/preload" { + if let Ok(cwd) = std::env::current_dir() { + ensure_read_permission::

(state, &cwd)?; + return Ok(Some(cwd.to_string_lossy().to_string())); + } + } + } + Ok(None) +} + +#[op] +fn op_require_try_self

( + state: &mut OpState, + parent_path: Option, + request: String, +) -> Result, AnyError> +where + P: NodePermissions + 'static, +{ + if parent_path.is_none() { + return Ok(None); + } + + let resolver = state.borrow::>().clone(); + let permissions = state.borrow_mut::

(); + let pkg = resolution::get_package_scope_config( + &Url::from_file_path(parent_path.unwrap()).unwrap(), + &*resolver, + permissions, + ) + .ok(); + if pkg.is_none() { + return Ok(None); + } + + let pkg = pkg.unwrap(); + if pkg.exports.is_none() { + return Ok(None); + } + if pkg.name.is_none() { + return Ok(None); + } + + let pkg_name = pkg.name.as_ref().unwrap().to_string(); + let mut expansion = ".".to_string(); + + if request == pkg_name { + // pass + } else if request.starts_with(&format!("{pkg_name}/")) { + expansion += &request[pkg_name.len()..]; + } else { + return Ok(None); + } + + let referrer = deno_core::url::Url::from_file_path(&pkg.path).unwrap(); + if let Some(exports) = &pkg.exports { + resolution::package_exports_resolve( + &pkg.path, + expansion, + exports, + &referrer, + NodeModuleKind::Cjs, + resolution::REQUIRE_CONDITIONS, + NodeResolutionMode::Execution, + &*resolver, + permissions, + ) + .map(|r| Some(r.to_string_lossy().to_string())) + } else { + Ok(None) + } +} + +#[op] +fn op_require_read_file

( + state: &mut OpState, + file_path: String, +) -> Result +where + P: NodePermissions + 'static, +{ + let file_path = PathBuf::from(file_path); + ensure_read_permission::

(state, &file_path)?; + Ok(std::fs::read_to_string(file_path)?) +} + +#[op] +pub fn op_require_as_file_path(file_or_url: String) -> String { + if let Ok(url) = Url::parse(&file_or_url) { + if let Ok(p) = url.to_file_path() { + return p.to_string_lossy().to_string(); + } + } + + file_or_url +} + +#[op] +fn op_require_resolve_exports

( + state: &mut OpState, + uses_local_node_modules_dir: bool, + modules_path: String, + _request: String, + name: String, + expansion: String, + parent_path: String, +) -> Result, AnyError> +where + P: NodePermissions + 'static, +{ + let resolver = state.borrow::>().clone(); + let permissions = state.borrow_mut::

(); + + let pkg_path = if resolver.in_npm_package(&PathBuf::from(&modules_path)) + && !uses_local_node_modules_dir + { + modules_path + } else { + path_resolve(vec![modules_path, name]) + }; + let pkg = PackageJson::load( + &*resolver, + permissions, + PathBuf::from(&pkg_path).join("package.json"), + )?; + + if let Some(exports) = &pkg.exports { + let referrer = Url::from_file_path(parent_path).unwrap(); + resolution::package_exports_resolve( + &pkg.path, + format!(".{expansion}"), + exports, + &referrer, + NodeModuleKind::Cjs, + resolution::REQUIRE_CONDITIONS, + NodeResolutionMode::Execution, + &*resolver, + permissions, + ) + .map(|r| Some(r.to_string_lossy().to_string())) + } else { + Ok(None) + } +} + +#[op] +fn op_require_read_closest_package_json

( + state: &mut OpState, + filename: String, +) -> Result +where + P: NodePermissions + 'static, +{ + ensure_read_permission::

( + state, + PathBuf::from(&filename).parent().unwrap(), + )?; + let resolver = state.borrow::>().clone(); + let permissions = state.borrow_mut::

(); + resolution::get_closest_package_json( + &Url::from_file_path(filename).unwrap(), + &*resolver, + permissions, + ) +} + +#[op] +fn op_require_read_package_scope

( + state: &mut OpState, + package_json_path: String, +) -> Option +where + P: NodePermissions + 'static, +{ + let resolver = state.borrow::>().clone(); + let permissions = state.borrow_mut::

(); + let package_json_path = PathBuf::from(package_json_path); + PackageJson::load(&*resolver, permissions, package_json_path).ok() +} + +#[op] +fn op_require_package_imports_resolve

( + state: &mut OpState, + parent_filename: String, + request: String, +) -> Result, AnyError> +where + P: NodePermissions + 'static, +{ + let parent_path = PathBuf::from(&parent_filename); + ensure_read_permission::

(state, &parent_path)?; + let resolver = state.borrow::>().clone(); + let permissions = state.borrow_mut::

(); + let pkg = PackageJson::load( + &*resolver, + permissions, + parent_path.join("package.json"), + )?; + + if pkg.imports.is_some() { + let referrer = + deno_core::url::Url::from_file_path(&parent_filename).unwrap(); + let r = resolution::package_imports_resolve( + &request, + &referrer, + NodeModuleKind::Cjs, + resolution::REQUIRE_CONDITIONS, + NodeResolutionMode::Execution, + &*resolver, + permissions, + ) + .map(|r| Some(Url::from_file_path(r).unwrap().to_string())); + state.put(resolver); + r + } else { + Ok(None) + } +} + +#[op] +fn op_require_break_on_next_statement(state: &mut OpState) { + let inspector = state.borrow::>>(); + inspector + .borrow_mut() + .wait_for_session_and_break_on_next_statement() +} diff --git a/ext/node/package_json.rs b/ext/node/package_json.rs index e40448930b96db..b0816dd85ec36c 100644 --- a/ext/node/package_json.rs +++ b/ext/node/package_json.rs @@ -4,12 +4,15 @@ use crate::NodeModuleKind; use crate::NodePermissions; use super::RequireNpmResolver; + use deno_core::anyhow; use deno_core::anyhow::bail; use deno_core::error::AnyError; use deno_core::serde_json; use deno_core::serde_json::Map; use deno_core::serde_json::Value; +use deno_core::ModuleSpecifier; +use indexmap::IndexMap; use serde::Serialize; use std::cell::RefCell; use std::collections::HashMap; @@ -33,6 +36,9 @@ pub struct PackageJson { pub path: PathBuf, pub typ: String, pub types: Option, + pub dependencies: Option>, + pub dev_dependencies: Option>, + pub scripts: Option>, } impl PackageJson { @@ -49,6 +55,9 @@ impl PackageJson { path, typ: "none".to_string(), types: None, + dependencies: None, + dev_dependencies: None, + scripts: None, } } @@ -86,6 +95,13 @@ impl PackageJson { return Ok(PackageJson::empty(path)); } + Self::load_from_string(path, source) + } + + pub fn load_from_string( + path: PathBuf, + source: String, + ) -> Result { let package_json: Value = serde_json::from_str(&source) .map_err(|err| anyhow::anyhow!("malformed package.json {}", err))?; @@ -114,6 +130,29 @@ impl PackageJson { let version = version_val.and_then(|s| s.as_str()).map(|s| s.to_string()); let module = module_val.and_then(|s| s.as_str()).map(|s| s.to_string()); + let dependencies = package_json.get("dependencies").and_then(|d| { + if d.is_object() { + let deps: HashMap = + serde_json::from_value(d.to_owned()).unwrap(); + Some(deps) + } else { + None + } + }); + let dev_dependencies = package_json.get("devDependencies").and_then(|d| { + if d.is_object() { + let deps: HashMap = + serde_json::from_value(d.to_owned()).unwrap(); + Some(deps) + } else { + None + } + }); + + let scripts: Option> = package_json + .get("scripts") + .and_then(|d| serde_json::from_value(d.to_owned()).ok()); + // Ignore unknown types for forwards compatibility let typ = if let Some(t) = type_val { if let Some(t) = t.as_str() { @@ -147,6 +186,9 @@ impl PackageJson { exports, imports, bin, + dependencies, + dev_dependencies, + scripts, }; CACHE.with(|cache| { @@ -164,6 +206,10 @@ impl PackageJson { self.main.as_ref() } } + + pub fn specifier(&self) -> ModuleSpecifier { + ModuleSpecifier::from_file_path(&self.path).unwrap() + } } fn is_conditional_exports_main_sugar(exports: &Value) -> bool { diff --git a/ext/node/polyfill.rs b/ext/node/polyfill.rs new file mode 100644 index 00000000000000..18faa6ea5ba96d --- /dev/null +++ b/ext/node/polyfill.rs @@ -0,0 +1,202 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. + +pub fn find_builtin_node_module( + module_name: &str, +) -> Option<&NodeModulePolyfill> { + SUPPORTED_BUILTIN_NODE_MODULES + .iter() + .find(|m| m.name == module_name) +} + +pub fn is_builtin_node_module(module_name: &str) -> bool { + find_builtin_node_module(module_name).is_some() +} + +pub struct NodeModulePolyfill { + /// Name of the module like "assert" or "timers/promises" + pub name: &'static str, + pub specifier: &'static str, +} + +pub static SUPPORTED_BUILTIN_NODE_MODULES: &[NodeModulePolyfill] = &[ + NodeModulePolyfill { + name: "assert", + specifier: "internal:deno_node/polyfills/assert.ts", + }, + NodeModulePolyfill { + name: "assert/strict", + specifier: "internal:deno_node/polyfills/assert/strict.ts", + }, + NodeModulePolyfill { + name: "async_hooks", + specifier: "internal:deno_node/polyfills/async_hooks.ts", + }, + NodeModulePolyfill { + name: "buffer", + specifier: "internal:deno_node/polyfills/buffer.ts", + }, + NodeModulePolyfill { + name: "child_process", + specifier: "internal:deno_node/polyfills/child_process.ts", + }, + NodeModulePolyfill { + name: "cluster", + specifier: "internal:deno_node/polyfills/cluster.ts", + }, + NodeModulePolyfill { + name: "console", + specifier: "internal:deno_node/polyfills/console.ts", + }, + NodeModulePolyfill { + name: "constants", + specifier: "internal:deno_node/polyfills/constants.ts", + }, + NodeModulePolyfill { + name: "crypto", + specifier: "internal:deno_node/polyfills/crypto.ts", + }, + NodeModulePolyfill { + name: "dgram", + specifier: "internal:deno_node/polyfills/dgram.ts", + }, + NodeModulePolyfill { + name: "dns", + specifier: "internal:deno_node/polyfills/dns.ts", + }, + NodeModulePolyfill { + name: "dns/promises", + specifier: "internal:deno_node/polyfills/dns/promises.ts", + }, + NodeModulePolyfill { + name: "domain", + specifier: "internal:deno_node/polyfills/domain.ts", + }, + NodeModulePolyfill { + name: "events", + specifier: "internal:deno_node/polyfills/events.ts", + }, + NodeModulePolyfill { + name: "fs", + specifier: "internal:deno_node/polyfills/fs.ts", + }, + NodeModulePolyfill { + name: "fs/promises", + specifier: "internal:deno_node/polyfills/fs/promises.ts", + }, + NodeModulePolyfill { + name: "http", + specifier: "internal:deno_node/polyfills/http.ts", + }, + NodeModulePolyfill { + name: "https", + specifier: "internal:deno_node/polyfills/https.ts", + }, + NodeModulePolyfill { + name: "module", + specifier: "internal:deno_node_loading/module_es_shim.js", + }, + NodeModulePolyfill { + name: "net", + specifier: "internal:deno_node/polyfills/net.ts", + }, + NodeModulePolyfill { + name: "os", + specifier: "internal:deno_node/polyfills/os.ts", + }, + NodeModulePolyfill { + name: "path", + specifier: "internal:deno_node/polyfills/path.ts", + }, + NodeModulePolyfill { + name: "path/posix", + specifier: "internal:deno_node/polyfills/path/posix.ts", + }, + NodeModulePolyfill { + name: "path/win32", + specifier: "internal:deno_node/polyfills/path/win32.ts", + }, + NodeModulePolyfill { + name: "perf_hooks", + specifier: "internal:deno_node/polyfills/perf_hooks.ts", + }, + NodeModulePolyfill { + name: "process", + specifier: "internal:deno_node/polyfills/process.ts", + }, + NodeModulePolyfill { + name: "querystring", + specifier: "internal:deno_node/polyfills/querystring.ts", + }, + NodeModulePolyfill { + name: "readline", + specifier: "internal:deno_node/polyfills/readline.ts", + }, + NodeModulePolyfill { + name: "stream", + specifier: "internal:deno_node/polyfills/stream.ts", + }, + NodeModulePolyfill { + name: "stream/consumers", + specifier: "internal:deno_node/polyfills/stream/consumers.mjs", + }, + NodeModulePolyfill { + name: "stream/promises", + specifier: "internal:deno_node/polyfills/stream/promises.mjs", + }, + NodeModulePolyfill { + name: "stream/web", + specifier: "internal:deno_node/polyfills/stream/web.ts", + }, + NodeModulePolyfill { + name: "string_decoder", + specifier: "internal:deno_node/polyfills/string_decoder.ts", + }, + NodeModulePolyfill { + name: "sys", + specifier: "internal:deno_node/polyfills/sys.ts", + }, + NodeModulePolyfill { + name: "timers", + specifier: "internal:deno_node/polyfills/timers.ts", + }, + NodeModulePolyfill { + name: "timers/promises", + specifier: "internal:deno_node/polyfills/timers/promises.ts", + }, + NodeModulePolyfill { + name: "tls", + specifier: "internal:deno_node/polyfills/tls.ts", + }, + NodeModulePolyfill { + name: "tty", + specifier: "internal:deno_node/polyfills/tty.ts", + }, + NodeModulePolyfill { + name: "url", + specifier: "internal:deno_node/polyfills/url.ts", + }, + NodeModulePolyfill { + name: "util", + specifier: "internal:deno_node/polyfills/util.ts", + }, + NodeModulePolyfill { + name: "util/types", + specifier: "internal:deno_node/polyfills/util/types.ts", + }, + NodeModulePolyfill { + name: "v8", + specifier: "internal:deno_node/polyfills/v8.ts", + }, + NodeModulePolyfill { + name: "vm", + specifier: "internal:deno_node/polyfills/vm.ts", + }, + NodeModulePolyfill { + name: "worker_threads", + specifier: "internal:deno_node/polyfills/worker_threads.ts", + }, + NodeModulePolyfill { + name: "zlib", + specifier: "internal:deno_node/polyfills/zlib.ts", + }, +]; diff --git a/ext/node/polyfills/README.md b/ext/node/polyfills/README.md new file mode 100644 index 00000000000000..ddad19dd68de6c --- /dev/null +++ b/ext/node/polyfills/README.md @@ -0,0 +1,248 @@ +# Deno Node.js compatibility + +This module is meant to have a compatibility layer for the +[Node.js standard library](https://nodejs.org/docs/latest/api/). + +**Warning**: Any function of this module should not be referred anywhere in the +Deno standard library as it's a compatibility module. + +## Supported modules + +- [x] assert +- [x] assert/strict _partly_ +- [x] async_hooks _partly_ +- [x] buffer +- [x] child_process _partly_ +- [x] cluster _partly_ +- [x] console _partly_ +- [x] constants _partly_ +- [x] crypto _partly_ +- [x] dgram _partly_ +- [x] diagnostics_channel _partly_ +- [x] dns _partly_ +- [x] events +- [x] fs _partly_ +- [x] fs/promises _partly_ +- [x] http _partly_ +- [ ] http2 +- [x] https _partly_ +- [x] inspector _partly_ +- [x] module +- [x] net +- [x] os _partly_ +- [x] path +- [x] path/posix +- [x] path/win32 +- [x] perf_hooks +- [x] process _partly_ +- [x] punycode +- [x] querystring +- [x] readline +- [x] repl _partly_ +- [x] stream +- [x] stream/promises +- [x] stream/web _partly_ +- [x] string_decoder +- [x] sys +- [x] timers +- [x] timers/promises +- [ ] tls +- [ ] trace_events +- [x] tty _partly_ +- [x] url +- [x] util _partly_ +- [x] util/types _partly_ +- [ ] v8 +- [x] vm _partly_ +- [x] wasi +- [ ] webcrypto +- [x] worker_threads +- [ ] zlib + +* [x] node globals _partly_ + +### Deprecated + +These modules are deprecated in Node.js and will probably not be polyfilled: + +- domain +- freelist + +### Experimental + +These modules are experimental in Node.js and will not be polyfilled until they +are stable: + +- diagnostics_channel +- async_hooks +- policies +- trace_events +- wasi +- webcrypto + +## CommonJS modules loading + +`createRequire(...)` is provided to create a `require` function for loading CJS +modules. It also sets supported globals. + +```ts +import { createRequire } from "https://deno.land/std@$STD_VERSION/node/module.ts"; + +const require = createRequire(import.meta.url); +// Loads native module polyfill. +const path = require("path"); +// Loads extensionless module. +const cjsModule = require("./my_mod"); +// Visits node_modules. +const leftPad = require("left-pad"); +``` + +## Contributing + +### Setting up the test runner + +This library contains automated tests pulled directly from the Node.js repo in +order ensure compatibility. + +Setting up the test runner is as simple as running the `node/_tools/setup.ts` +file, this will pull the configured tests in and then add them to the test +workflow. + +```zsh +$ deno task node:setup +``` + +You can additionally pass the `-y`/`-n` flag to use test cache or generating +tests from scratch instead of being prompted at the moment of running it. + +```zsh +# Will use downloaded tests instead of prompting user +$ deno run --allow-read --allow-net --allow-write node/_tools/setup.ts -y +# Will not prompt but will download and extract the tests directly +$ deno run --allow-read --allow-net --allow-write node/_tools/setup.ts -n +``` + +To run the tests you have set up, do the following: + +```zsh +$ deno test --allow-read --allow-run node/_tools/test.ts +``` + +If you want to run specific Node.js test files, you can use the following +command + +```shellsession +$ deno test -A node/_tools/test.ts -- +``` + +For example, if you want to run only +`node/_tools/test/parallel/test-event-emitter-check-listener-leaks.js`, you can +use: + +```shellsession +$ deno test -A node/_tools/test.ts -- test-event-emitter-check-listener-leaks.js +``` + +If you want to run all test files which contains `event-emitter` in filename, +then you can use: + +```shellsession +$ deno test -A node/_tools/test.ts -- event-emitter +``` + +The test should be passing with the latest deno, so if the test fails, try the +following: + +- `$ deno upgrade` +- `$ git submodule update --init` +- Use + [`--unstable` flag](https://deno.land/manual@v1.15.3/runtime/stability#standard-modules) + +To enable new tests, simply add a new entry inside `node/_tools/config.json` +under the `tests` property. The structure this entries must have has to resemble +a path inside `https://github.com/nodejs/node/tree/main/test`. + +Adding a new entry under the `ignore` option will indicate the test runner that +it should not regenerate that file from scratch the next time the setup is run, +this is specially useful to keep track of files that have been manually edited +to pass certain tests. However, avoid doing such manual changes to the test +files, since that may cover up inconsistencies between the node library and +actual node behavior. + +### Working with child processes ? Use `DENO_NODE_COMPAT_URL` + +When working with `child_process` modules, you will have to run tests pulled +from Node.js. These tests usually spawn deno child processes via the use of +`process.execPath`. The `deno` executable will use its own embedded version of +std modules, then you may get the impression your code is not really working as +it should. + +To prevent this, set `DENO_NODE_COMPAT_URL` with the absolute path to your +`deno_std` repo, ending with a trailing slash: + +``` +export DENO_NODE_COMPAT_URL=$PWD/ +# or +export DENO_NODE_COMPAT_URL=file:///path/to/deno_std/dir/ +``` + +Then, `deno` will use your local copy of `deno_std` instead of latest version. + +### Best practices + +When converting from promise-based to callback-based APIs, the most obvious way +is like this: + +```ts, ignore +promise.then((value) => callback(null, value)).catch(callback); +``` + +This has a subtle bug - if the callback throws an error, the catch statement +will also catch _that_ error, and the callback will be called twice. The correct +way to do it is like this: + +```ts, ignore +promise.then((value) => callback(null, value), callback); +``` + +The second parameter of `then` can also be used to catch errors, but only errors +from the existing promise, not the new one created by the callback. + +If the Deno equivalent is actually synchronous, there's a similar problem with +try/catch statements: + +```ts, ignore +try { + const value = process(); + callback(null, value); +} catch (err) { + callback(err); +} +``` + +Since the callback is called within the `try` block, any errors from it will be +caught and call the callback again. + +The correct way to do it is like this: + +```ts, ignore +let err, value; +try { + value = process(); +} catch (e) { + err = e; +} +if (err) { + callback(err); // Make sure arguments.length === 1 +} else { + callback(null, value); +} +``` + +It's not as clean, but prevents the callback being called twice. + +### Remaining Tests + +Node compatibility can be measured by how many native Node tests pass. If you'd +like to know what you can work on, check out the list of Node tests remaining +[here](_tools/TODO.md). diff --git a/ext/node/polyfills/_core.ts b/ext/node/polyfills/_core.ts new file mode 100644 index 00000000000000..73a74172fa3c96 --- /dev/null +++ b/ext/node/polyfills/_core.ts @@ -0,0 +1,83 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. + +// This module provides an interface to `Deno.core`. For environments +// that don't have access to `Deno.core` some APIs are polyfilled, while +// some are unavailble and throw on call. +// Note: deno_std shouldn't use Deno.core namespace. We should minimize these +// usages. + +import { TextEncoder } from "internal:deno_web/08_text_encoding.js"; + +// deno-lint-ignore no-explicit-any +let DenoCore: any; + +// deno-lint-ignore no-explicit-any +const { Deno } = globalThis as any; + +// @ts-ignore Deno.core is not defined in types +if (Deno?.[Deno.internal]?.core) { + // @ts-ignore Deno[Deno.internal].core is not defined in types + DenoCore = Deno[Deno.internal].core; +} else if (Deno?.core) { + // @ts-ignore Deno.core is not defined in types + DenoCore = Deno.core; +} else { + DenoCore = {}; +} + +export const core = { + runMicrotasks: DenoCore.runMicrotasks ?? function () { + throw new Error( + "Deno.core.runMicrotasks() is not supported in this environment", + ); + }, + setHasTickScheduled: DenoCore.setHasTickScheduled ?? function () { + throw new Error( + "Deno.core.setHasTickScheduled() is not supported in this environment", + ); + }, + hasTickScheduled: DenoCore.hasTickScheduled ?? function () { + throw new Error( + "Deno.core.hasTickScheduled() is not supported in this environment", + ); + }, + setNextTickCallback: DenoCore.setNextTickCallback ?? undefined, + setMacrotaskCallback: DenoCore.setMacrotaskCallback ?? function () { + throw new Error( + "Deno.core.setNextTickCallback() is not supported in this environment", + ); + }, + evalContext: DenoCore.evalContext ?? + function (_code: string, _filename: string) { + throw new Error( + "Deno.core.evalContext is not supported in this environment", + ); + }, + encode: DenoCore.encode ?? function (chunk: string): Uint8Array { + return new TextEncoder().encode(chunk); + }, + eventLoopHasMoreWork: DenoCore.eventLoopHasMoreWork ?? function (): boolean { + return false; + }, + isProxy: DenoCore.isProxy ?? function (): boolean { + return false; + }, + getPromiseDetails: DenoCore.getPromiseDetails ?? + function (_promise: Promise): [number, unknown] { + throw new Error( + "Deno.core.getPromiseDetails is not supported in this environment", + ); + }, + setPromiseHooks: DenoCore.setPromiseHooks ?? function () { + throw new Error( + "Deno.core.setPromiseHooks is not supported in this environment", + ); + }, + ops: DenoCore.ops ?? { + op_napi_open(_filename: string) { + throw new Error( + "Node API is not supported in this environment", + ); + }, + }, +}; diff --git a/ext/node/polyfills/_events.d.ts b/ext/node/polyfills/_events.d.ts new file mode 100644 index 00000000000000..c4d89bfa480432 --- /dev/null +++ b/ext/node/polyfills/_events.d.ts @@ -0,0 +1,826 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. +// deno-lint-ignore-file no-explicit-any + +// Forked from https://github.com/DefinitelyTyped/DefinitelyTyped/blob/9b9cd671114a2a5178809798d8e7f4d8ca6c2671/types/node/events.d.ts + +export const captureRejectionSymbol: unique symbol; +export const defaultMaxListeners: number; +export const errorMonitor: unique symbol; + +export interface Abortable { + /** + * When provided the corresponding `AbortController` can be used to cancel an asynchronous action. + */ + signal?: AbortSignal | undefined; +} +/** + * Returns a copy of the array of listeners for the event named `eventName`. + * + * For `EventEmitter`s this behaves exactly the same as calling `.listeners` on + * the emitter. + * + * For `EventTarget`s this is the only way to get the event listeners for the + * event target. This is useful for debugging and diagnostic purposes. + * + * ```js + * const { getEventListeners, EventEmitter } = require('events'); + * + * { + * const ee = new EventEmitter(); + * const listener = () => console.log('Events are fun'); + * ee.on('foo', listener); + * getEventListeners(ee, 'foo'); // [listener] + * } + * { + * const et = new EventTarget(); + * const listener = () => console.log('Events are fun'); + * et.addEventListener('foo', listener); + * getEventListeners(et, 'foo'); // [listener] + * } + * ``` + * @since v15.2.0 + */ +export function getEventListeners( + emitter: EventTarget | EventEmitter, + name: string | symbol, + // deno-lint-ignore ban-types +): Function[]; + +/** + * ```js + * const { on, EventEmitter } = require('events'); + * + * (async () => { + * const ee = new EventEmitter(); + * + * // Emit later on + * process.nextTick(() => { + * ee.emit('foo', 'bar'); + * ee.emit('foo', 42); + * }); + * + * for await (const event of on(ee, 'foo')) { + * // The execution of this inner block is synchronous and it + * // processes one event at a time (even with await). Do not use + * // if concurrent execution is required. + * console.log(event); // prints ['bar'] [42] + * } + * // Unreachable here + * })(); + * ``` + * + * Returns an `AsyncIterator` that iterates `eventName` events. It will throw + * if the `EventEmitter` emits `'error'`. It removes all listeners when + * exiting the loop. The `value` returned by each iteration is an array + * composed of the emitted event arguments. + * + * An `AbortSignal` can be used to cancel waiting on events: + * + * ```js + * const { on, EventEmitter } = require('events'); + * const ac = new AbortController(); + * + * (async () => { + * const ee = new EventEmitter(); + * + * // Emit later on + * process.nextTick(() => { + * ee.emit('foo', 'bar'); + * ee.emit('foo', 42); + * }); + * + * for await (const event of on(ee, 'foo', { signal: ac.signal })) { + * // The execution of this inner block is synchronous and it + * // processes one event at a time (even with await). Do not use + * // if concurrent execution is required. + * console.log(event); // prints ['bar'] [42] + * } + * // Unreachable here + * })(); + * + * process.nextTick(() => ac.abort()); + * ``` + * @since v13.6.0, v12.16.0 + * @param eventName The name of the event being listened for + * @return that iterates `eventName` events emitted by the `emitter` + */ +export function on( + emitter: EventEmitter, + eventName: string, + options?: StaticEventEmitterOptions, +): AsyncIterableIterator; + +/** + * Creates a `Promise` that is fulfilled when the `EventEmitter` emits the given + * event or that is rejected if the `EventEmitter` emits `'error'` while waiting. + * The `Promise` will resolve with an array of all the arguments emitted to the + * given event. + * + * This method is intentionally generic and works with the web platform [EventTarget](https://dom.spec.whatwg.org/#interface-eventtarget) interface, which has no special`'error'` event + * semantics and does not listen to the `'error'` event. + * + * ```js + * const { once, EventEmitter } = require('events'); + * + * async function run() { + * const ee = new EventEmitter(); + * + * process.nextTick(() => { + * ee.emit('myevent', 42); + * }); + * + * const [value] = await once(ee, 'myevent'); + * console.log(value); + * + * const err = new Error('kaboom'); + * process.nextTick(() => { + * ee.emit('error', err); + * }); + * + * try { + * await once(ee, 'myevent'); + * } catch (err) { + * console.log('error happened', err); + * } + * } + * + * run(); + * ``` + * + * The special handling of the `'error'` event is only used when `events.once()`is used to wait for another event. If `events.once()` is used to wait for the + * '`error'` event itself, then it is treated as any other kind of event without + * special handling: + * + * ```js + * const { EventEmitter, once } = require('events'); + * + * const ee = new EventEmitter(); + * + * once(ee, 'error') + * .then(([err]) => console.log('ok', err.message)) + * .catch((err) => console.log('error', err.message)); + * + * ee.emit('error', new Error('boom')); + * + * // Prints: ok boom + * ``` + * + * An `AbortSignal` can be used to cancel waiting for the event: + * + * ```js + * const { EventEmitter, once } = require('events'); + * + * const ee = new EventEmitter(); + * const ac = new AbortController(); + * + * async function foo(emitter, event, signal) { + * try { + * await once(emitter, event, { signal }); + * console.log('event emitted!'); + * } catch (error) { + * if (error.name === 'AbortError') { + * console.error('Waiting for the event was canceled!'); + * } else { + * console.error('There was an error', error.message); + * } + * } + * } + * + * foo(ee, 'foo', ac.signal); + * ac.abort(); // Abort waiting for the event + * ee.emit('foo'); // Prints: Waiting for the event was canceled! + * ``` + * @since v11.13.0, v10.16.0 + */ +export function once( + emitter: NodeEventTarget, + eventName: string | symbol, + options?: StaticEventEmitterOptions, +): Promise; +export function once( + emitter: EventTarget, + eventName: string, + options?: StaticEventEmitterOptions, +): Promise; + +/** + * `n` {number} A non-negative number. The maximum number of listeners per `EventTarget` event. + * `...eventsTargets` {EventTarget\[]|EventEmitter\[]} Zero or more {EventTarget} + * or {EventEmitter} instances. If none are specified, `n` is set as the default + * max for all newly created {EventTarget} and {EventEmitter} objects. + */ +export function setMaxListeners(n: number): EventEmitter; + +/** + * A class method that returns the number of listeners for the given `eventName`registered on the given `emitter`. + * + * ```js + * const { EventEmitter, listenerCount } = require('events'); + * const myEmitter = new EventEmitter(); + * myEmitter.on('event', () => {}); + * myEmitter.on('event', () => {}); + * console.log(listenerCount(myEmitter, 'event')); + * // Prints: 2 + * ``` + * @since v0.9.12 + * @deprecated Since v3.2.0 - Use `listenerCount` instead. + * @param emitter The emitter to query + * @param eventName The event name + */ +export function listenerCount( + emitter: EventEmitter, + eventName: string | symbol, +): number; + +interface EventEmitterOptions { + /** + * Enables automatic capturing of promise rejection. + */ + captureRejections?: boolean | undefined; +} +interface NodeEventTarget { + once(eventName: string | symbol, listener: (...args: any[]) => void): this; +} +interface EventTarget { + addEventListener( + eventName: string, + listener: (...args: any[]) => void, + opts?: { + once: boolean; + }, + ): any; +} +interface StaticEventEmitterOptions { + signal?: AbortSignal | undefined; +} +/** + * The `EventEmitter` class is defined and exposed by the `events` module: + * + * ```js + * const EventEmitter = require('events'); + * ``` + * + * All `EventEmitter`s emit the event `'newListener'` when new listeners are + * added and `'removeListener'` when existing listeners are removed. + * + * It supports the following option: + * @since v0.1.26 + */ +export class EventEmitter { + /** + * Alias for `emitter.on(eventName, listener)`. + * @since v0.1.26 + */ + addListener( + eventName: string | symbol, + listener: (...args: any[]) => void, + ): this; + /** + * Adds the `listener` function to the end of the listeners array for the + * event named `eventName`. No checks are made to see if the `listener` has + * already been added. Multiple calls passing the same combination of `eventName`and `listener` will result in the `listener` being added, and called, multiple + * times. + * + * ```js + * server.on('connection', (stream) => { + * console.log('someone connected!'); + * }); + * ``` + * + * Returns a reference to the `EventEmitter`, so that calls can be chained. + * + * By default, event listeners are invoked in the order they are added. The`emitter.prependListener()` method can be used as an alternative to add the + * event listener to the beginning of the listeners array. + * + * ```js + * const myEE = new EventEmitter(); + * myEE.on('foo', () => console.log('a')); + * myEE.prependListener('foo', () => console.log('b')); + * myEE.emit('foo'); + * // Prints: + * // b + * // a + * ``` + * @since v0.1.101 + * @param eventName The name of the event. + * @param listener The callback function + */ + on(eventName: string | symbol, listener: (...args: any[]) => void): this; + /** + * Adds a **one-time**`listener` function for the event named `eventName`. The + * next time `eventName` is triggered, this listener is removed and then invoked. + * + * ```js + * server.once('connection', (stream) => { + * console.log('Ah, we have our first user!'); + * }); + * ``` + * + * Returns a reference to the `EventEmitter`, so that calls can be chained. + * + * By default, event listeners are invoked in the order they are added. The`emitter.prependOnceListener()` method can be used as an alternative to add the + * event listener to the beginning of the listeners array. + * + * ```js + * const myEE = new EventEmitter(); + * myEE.once('foo', () => console.log('a')); + * myEE.prependOnceListener('foo', () => console.log('b')); + * myEE.emit('foo'); + * // Prints: + * // b + * // a + * ``` + * @since v0.3.0 + * @param eventName The name of the event. + * @param listener The callback function + */ + once(eventName: string | symbol, listener: (...args: any[]) => void): this; + /** + * Removes the specified `listener` from the listener array for the event named`eventName`. + * + * ```js + * const callback = (stream) => { + * console.log('someone connected!'); + * }; + * server.on('connection', callback); + * // ... + * server.removeListener('connection', callback); + * ``` + * + * `removeListener()` will remove, at most, one instance of a listener from the + * listener array. If any single listener has been added multiple times to the + * listener array for the specified `eventName`, then `removeListener()` must be + * called multiple times to remove each instance. + * + * Once an event is emitted, all listeners attached to it at the + * time of emitting are called in order. This implies that any`removeListener()` or `removeAllListeners()` calls _after_ emitting and_before_ the last listener finishes execution will + * not remove them from`emit()` in progress. Subsequent events behave as expected. + * + * ```js + * const myEmitter = new MyEmitter(); + * + * const callbackA = () => { + * console.log('A'); + * myEmitter.removeListener('event', callbackB); + * }; + * + * const callbackB = () => { + * console.log('B'); + * }; + * + * myEmitter.on('event', callbackA); + * + * myEmitter.on('event', callbackB); + * + * // callbackA removes listener callbackB but it will still be called. + * // Internal listener array at time of emit [callbackA, callbackB] + * myEmitter.emit('event'); + * // Prints: + * // A + * // B + * + * // callbackB is now removed. + * // Internal listener array [callbackA] + * myEmitter.emit('event'); + * // Prints: + * // A + * ``` + * + * Because listeners are managed using an internal array, calling this will + * change the position indices of any listener registered _after_ the listener + * being removed. This will not impact the order in which listeners are called, + * but it means that any copies of the listener array as returned by + * the `emitter.listeners()` method will need to be recreated. + * + * When a single function has been added as a handler multiple times for a single + * event (as in the example below), `removeListener()` will remove the most + * recently added instance. In the example the `once('ping')`listener is removed: + * + * ```js + * const ee = new EventEmitter(); + * + * function pong() { + * console.log('pong'); + * } + * + * ee.on('ping', pong); + * ee.once('ping', pong); + * ee.removeListener('ping', pong); + * + * ee.emit('ping'); + * ee.emit('ping'); + * ``` + * + * Returns a reference to the `EventEmitter`, so that calls can be chained. + * @since v0.1.26 + */ + removeListener( + eventName: string | symbol, + listener: (...args: any[]) => void, + ): this; + /** + * Alias for `emitter.removeListener()`. + * @since v10.0.0 + */ + off(eventName: string | symbol, listener: (...args: any[]) => void): this; + /** + * Removes all listeners, or those of the specified `eventName`. + * + * It is bad practice to remove listeners added elsewhere in the code, + * particularly when the `EventEmitter` instance was created by some other + * component or module (e.g. sockets or file streams). + * + * Returns a reference to the `EventEmitter`, so that calls can be chained. + * @since v0.1.26 + */ + removeAllListeners(event?: string | symbol): this; + /** + * By default `EventEmitter`s will print a warning if more than `10` listeners are + * added for a particular event. This is a useful default that helps finding + * memory leaks. The `emitter.setMaxListeners()` method allows the limit to be + * modified for this specific `EventEmitter` instance. The value can be set to`Infinity` (or `0`) to indicate an unlimited number of listeners. + * + * Returns a reference to the `EventEmitter`, so that calls can be chained. + * @since v0.3.5 + */ + setMaxListeners(n: number): this; + /** + * Returns the current max listener value for the `EventEmitter` which is either + * set by `emitter.setMaxListeners(n)` or defaults to {@link defaultMaxListeners}. + * @since v1.0.0 + */ + getMaxListeners(): number; + /** + * Returns a copy of the array of listeners for the event named `eventName`. + * + * ```js + * server.on('connection', (stream) => { + * console.log('someone connected!'); + * }); + * console.log(util.inspect(server.listeners('connection'))); + * // Prints: [ [Function] ] + * ``` + * @since v0.1.26 + */ + // deno-lint-ignore ban-types + listeners(eventName: string | symbol): Function[]; + /** + * Returns a copy of the array of listeners for the event named `eventName`, + * including any wrappers (such as those created by `.once()`). + * + * ```js + * const emitter = new EventEmitter(); + * emitter.once('log', () => console.log('log once')); + * + * // Returns a new Array with a function `onceWrapper` which has a property + * // `listener` which contains the original listener bound above + * const listeners = emitter.rawListeners('log'); + * const logFnWrapper = listeners[0]; + * + * // Logs "log once" to the console and does not unbind the `once` event + * logFnWrapper.listener(); + * + * // Logs "log once" to the console and removes the listener + * logFnWrapper(); + * + * emitter.on('log', () => console.log('log persistently')); + * // Will return a new Array with a single function bound by `.on()` above + * const newListeners = emitter.rawListeners('log'); + * + * // Logs "log persistently" twice + * newListeners[0](); + * emitter.emit('log'); + * ``` + * @since v9.4.0 + */ + // deno-lint-ignore ban-types + rawListeners(eventName: string | symbol): Function[]; + /** + * Synchronously calls each of the listeners registered for the event named`eventName`, in the order they were registered, passing the supplied arguments + * to each. + * + * Returns `true` if the event had listeners, `false` otherwise. + * + * ```js + * const EventEmitter = require('events'); + * const myEmitter = new EventEmitter(); + * + * // First listener + * myEmitter.on('event', function firstListener() { + * console.log('Helloooo! first listener'); + * }); + * // Second listener + * myEmitter.on('event', function secondListener(arg1, arg2) { + * console.log(`event with parameters ${arg1}, ${arg2} in second listener`); + * }); + * // Third listener + * myEmitter.on('event', function thirdListener(...args) { + * const parameters = args.join(', '); + * console.log(`event with parameters ${parameters} in third listener`); + * }); + * + * console.log(myEmitter.listeners('event')); + * + * myEmitter.emit('event', 1, 2, 3, 4, 5); + * + * // Prints: + * // [ + * // [Function: firstListener], + * // [Function: secondListener], + * // [Function: thirdListener] + * // ] + * // Helloooo! first listener + * // event with parameters 1, 2 in second listener + * // event with parameters 1, 2, 3, 4, 5 in third listener + * ``` + * @since v0.1.26 + */ + emit(eventName: string | symbol, ...args: any[]): boolean; + /** + * Returns the number of listeners listening to the event named `eventName`. + * @since v3.2.0 + * @param eventName The name of the event being listened for + */ + listenerCount(eventName: string | symbol): number; + /** + * Adds the `listener` function to the _beginning_ of the listeners array for the + * event named `eventName`. No checks are made to see if the `listener` has + * already been added. Multiple calls passing the same combination of `eventName`and `listener` will result in the `listener` being added, and called, multiple + * times. + * + * ```js + * server.prependListener('connection', (stream) => { + * console.log('someone connected!'); + * }); + * ``` + * + * Returns a reference to the `EventEmitter`, so that calls can be chained. + * @since v6.0.0 + * @param eventName The name of the event. + * @param listener The callback function + */ + prependListener( + eventName: string | symbol, + listener: (...args: any[]) => void, + ): this; + /** + * Adds a **one-time**`listener` function for the event named `eventName` to the_beginning_ of the listeners array. The next time `eventName` is triggered, this + * listener is removed, and then invoked. + * + * ```js + * server.prependOnceListener('connection', (stream) => { + * console.log('Ah, we have our first user!'); + * }); + * ``` + * + * Returns a reference to the `EventEmitter`, so that calls can be chained. + * @since v6.0.0 + * @param eventName The name of the event. + * @param listener The callback function + */ + prependOnceListener( + eventName: string | symbol, + listener: (...args: any[]) => void, + ): this; + /** + * Returns an array listing the events for which the emitter has registered + * listeners. The values in the array are strings or `Symbol`s. + * + * ```js + * const EventEmitter = require('events'); + * const myEE = new EventEmitter(); + * myEE.on('foo', () => {}); + * myEE.on('bar', () => {}); + * + * const sym = Symbol('symbol'); + * myEE.on(sym, () => {}); + * + * console.log(myEE.eventNames()); + * // Prints: [ 'foo', 'bar', Symbol(symbol) ] + * ``` + * @since v6.0.0 + */ + eventNames(): Array; + + constructor(options?: EventEmitterOptions); + /** + * Creates a `Promise` that is fulfilled when the `EventEmitter` emits the given + * event or that is rejected if the `EventEmitter` emits `'error'` while waiting. + * The `Promise` will resolve with an array of all the arguments emitted to the + * given event. + * + * This method is intentionally generic and works with the web platform [EventTarget](https://dom.spec.whatwg.org/#interface-eventtarget) interface, which has no special`'error'` event + * semantics and does not listen to the `'error'` event. + * + * ```js + * const { once, EventEmitter } = require('events'); + * + * async function run() { + * const ee = new EventEmitter(); + * + * process.nextTick(() => { + * ee.emit('myevent', 42); + * }); + * + * const [value] = await once(ee, 'myevent'); + * console.log(value); + * + * const err = new Error('kaboom'); + * process.nextTick(() => { + * ee.emit('error', err); + * }); + * + * try { + * await once(ee, 'myevent'); + * } catch (err) { + * console.log('error happened', err); + * } + * } + * + * run(); + * ``` + * + * The special handling of the `'error'` event is only used when `events.once()`is used to wait for another event. If `events.once()` is used to wait for the + * '`error'` event itself, then it is treated as any other kind of event without + * special handling: + * + * ```js + * const { EventEmitter, once } = require('events'); + * + * const ee = new EventEmitter(); + * + * once(ee, 'error') + * .then(([err]) => console.log('ok', err.message)) + * .catch((err) => console.log('error', err.message)); + * + * ee.emit('error', new Error('boom')); + * + * // Prints: ok boom + * ``` + * + * An `AbortSignal` can be used to cancel waiting for the event: + * + * ```js + * const { EventEmitter, once } = require('events'); + * + * const ee = new EventEmitter(); + * const ac = new AbortController(); + * + * async function foo(emitter, event, signal) { + * try { + * await once(emitter, event, { signal }); + * console.log('event emitted!'); + * } catch (error) { + * if (error.name === 'AbortError') { + * console.error('Waiting for the event was canceled!'); + * } else { + * console.error('There was an error', error.message); + * } + * } + * } + * + * foo(ee, 'foo', ac.signal); + * ac.abort(); // Abort waiting for the event + * ee.emit('foo'); // Prints: Waiting for the event was canceled! + * ``` + * @since v11.13.0, v10.16.0 + */ + static once( + emitter: NodeEventTarget, + eventName: string | symbol, + options?: StaticEventEmitterOptions, + ): Promise; + static once( + emitter: EventTarget, + eventName: string, + options?: StaticEventEmitterOptions, + ): Promise; + /** + * ```js + * const { on, EventEmitter } = require('events'); + * + * (async () => { + * const ee = new EventEmitter(); + * + * // Emit later on + * process.nextTick(() => { + * ee.emit('foo', 'bar'); + * ee.emit('foo', 42); + * }); + * + * for await (const event of on(ee, 'foo')) { + * // The execution of this inner block is synchronous and it + * // processes one event at a time (even with await). Do not use + * // if concurrent execution is required. + * console.log(event); // prints ['bar'] [42] + * } + * // Unreachable here + * })(); + * ``` + * + * Returns an `AsyncIterator` that iterates `eventName` events. It will throw + * if the `EventEmitter` emits `'error'`. It removes all listeners when + * exiting the loop. The `value` returned by each iteration is an array + * composed of the emitted event arguments. + * + * An `AbortSignal` can be used to cancel waiting on events: + * + * ```js + * const { on, EventEmitter } = require('events'); + * const ac = new AbortController(); + * + * (async () => { + * const ee = new EventEmitter(); + * + * // Emit later on + * process.nextTick(() => { + * ee.emit('foo', 'bar'); + * ee.emit('foo', 42); + * }); + * + * for await (const event of on(ee, 'foo', { signal: ac.signal })) { + * // The execution of this inner block is synchronous and it + * // processes one event at a time (even with await). Do not use + * // if concurrent execution is required. + * console.log(event); // prints ['bar'] [42] + * } + * // Unreachable here + * })(); + * + * process.nextTick(() => ac.abort()); + * ``` + * @since v13.6.0, v12.16.0 + * @param eventName The name of the event being listened for + * @return that iterates `eventName` events emitted by the `emitter` + */ + static on: typeof on; + /** + * A class method that returns the number of listeners for the given `eventName`registered on the given `emitter`. + * + * ```js + * const { EventEmitter, listenerCount } = require('events'); + * const myEmitter = new EventEmitter(); + * myEmitter.on('event', () => {}); + * myEmitter.on('event', () => {}); + * console.log(listenerCount(myEmitter, 'event')); + * // Prints: 2 + * ``` + * @since v0.9.12 + * @deprecated Since v3.2.0 - Use `listenerCount` instead. + * @param emitter The emitter to query + * @param eventName The event name + */ + static listenerCount( + emitter: EventEmitter, + eventName: string | symbol, + ): number; + + /** + * Returns a copy of the array of listeners for the event named `eventName`. + * + * For `EventEmitter`s this behaves exactly the same as calling `.listeners` on + * the emitter. + * + * For `EventTarget`s this is the only way to get the event listeners for the + * event target. This is useful for debugging and diagnostic purposes. + * + * ```js + * const { getEventListeners, EventEmitter } = require('events'); + * + * { + * const ee = new EventEmitter(); + * const listener = () => console.log('Events are fun'); + * ee.on('foo', listener); + * getEventListeners(ee, 'foo'); // [listener] + * } + * { + * const et = new EventTarget(); + * const listener = () => console.log('Events are fun'); + * et.addEventListener('foo', listener); + * getEventListeners(et, 'foo'); // [listener] + * } + * ``` + * @since v15.2.0 + */ + static getEventListeners: typeof getEventListeners; + + /** + * This symbol shall be used to install a listener for only monitoring `'error'` + * events. Listeners installed using this symbol are called before the regular + * `'error'` listeners are called. + * + * Installing a listener using this symbol does not change the behavior once an + * `'error'` event is emitted, therefore the process will still crash if no + * regular `'error'` listener is installed. + */ + static readonly errorMonitor: unique symbol; + static readonly captureRejectionSymbol: unique symbol; + /** + * Sets or gets the default captureRejection value for all emitters. + */ + // TODO(@bartlomieju): These should be described using static getter/setter pairs: + static captureRejections: boolean; + static defaultMaxListeners: number; +} + +export default EventEmitter; diff --git a/ext/node/polyfills/_events.mjs b/ext/node/polyfills/_events.mjs new file mode 100644 index 00000000000000..cd9871a6ca0830 --- /dev/null +++ b/ext/node/polyfills/_events.mjs @@ -0,0 +1,1033 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. +// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. + +"use strict"; + +const kRejection = Symbol.for("nodejs.rejection"); + +import { inspect } from "internal:deno_node/polyfills/internal/util/inspect.mjs"; +import { + AbortError, + // kEnhanceStackBeforeInspector, + ERR_INVALID_ARG_TYPE, + ERR_OUT_OF_RANGE, + ERR_UNHANDLED_ERROR, +} from "internal:deno_node/polyfills/internal/errors.ts"; + +import { + validateAbortSignal, + validateBoolean, + validateFunction, +} from "internal:deno_node/polyfills/internal/validators.mjs"; +import { spliceOne } from "internal:deno_node/polyfills/_utils.ts"; + +const kCapture = Symbol("kCapture"); +const kErrorMonitor = Symbol("events.errorMonitor"); +const kMaxEventTargetListeners = Symbol("events.maxEventTargetListeners"); +const kMaxEventTargetListenersWarned = Symbol( + "events.maxEventTargetListenersWarned", +); + +/** + * Creates a new `EventEmitter` instance. + * @param {{ captureRejections?: boolean; }} [opts] + * @returns {EventEmitter} + */ +export function EventEmitter(opts) { + EventEmitter.init.call(this, opts); +} +export default EventEmitter; +EventEmitter.on = on; +EventEmitter.once = once; +EventEmitter.getEventListeners = getEventListeners; +EventEmitter.setMaxListeners = setMaxListeners; +EventEmitter.listenerCount = listenerCount; +// Backwards-compat with node 0.10.x +EventEmitter.EventEmitter = EventEmitter; +EventEmitter.usingDomains = false; + +EventEmitter.captureRejectionSymbol = kRejection; +export const captureRejectionSymbol = EventEmitter.captureRejectionSymbol; +export const errorMonitor = EventEmitter.errorMonitor; + +Object.defineProperty(EventEmitter, "captureRejections", { + get() { + return EventEmitter.prototype[kCapture]; + }, + set(value) { + validateBoolean(value, "EventEmitter.captureRejections"); + + EventEmitter.prototype[kCapture] = value; + }, + enumerable: true, +}); + +EventEmitter.errorMonitor = kErrorMonitor; + +// The default for captureRejections is false +Object.defineProperty(EventEmitter.prototype, kCapture, { + value: false, + writable: true, + enumerable: false, +}); + +EventEmitter.prototype._events = undefined; +EventEmitter.prototype._eventsCount = 0; +EventEmitter.prototype._maxListeners = undefined; + +// By default EventEmitters will print a warning if more than 10 listeners are +// added to it. This is a useful default which helps finding memory leaks. +export let defaultMaxListeners = 10; + +function checkListener(listener) { + validateFunction(listener, "listener"); +} + +Object.defineProperty(EventEmitter, "defaultMaxListeners", { + enumerable: true, + get: function () { + return defaultMaxListeners; + }, + set: function (arg) { + if (typeof arg !== "number" || arg < 0 || Number.isNaN(arg)) { + throw new ERR_OUT_OF_RANGE( + "defaultMaxListeners", + "a non-negative number", + arg, + ); + } + defaultMaxListeners = arg; + }, +}); + +Object.defineProperties(EventEmitter, { + kMaxEventTargetListeners: { + value: kMaxEventTargetListeners, + enumerable: false, + configurable: false, + writable: false, + }, + kMaxEventTargetListenersWarned: { + value: kMaxEventTargetListenersWarned, + enumerable: false, + configurable: false, + writable: false, + }, +}); + +/** + * Sets the max listeners. + * @param {number} n + * @param {EventTarget[] | EventEmitter[]} [eventTargets] + * @returns {void} + */ +export function setMaxListeners( + n = defaultMaxListeners, + ...eventTargets +) { + if (typeof n !== "number" || n < 0 || Number.isNaN(n)) { + throw new ERR_OUT_OF_RANGE("n", "a non-negative number", n); + } + if (eventTargets.length === 0) { + defaultMaxListeners = n; + } else { + for (let i = 0; i < eventTargets.length; i++) { + const target = eventTargets[i]; + if (target instanceof EventTarget) { + target[kMaxEventTargetListeners] = n; + target[kMaxEventTargetListenersWarned] = false; + } else if (typeof target.setMaxListeners === "function") { + target.setMaxListeners(n); + } else { + throw new ERR_INVALID_ARG_TYPE( + "eventTargets", + ["EventEmitter", "EventTarget"], + target, + ); + } + } + } +} + +EventEmitter.init = function (opts) { + if ( + this._events === undefined || + this._events === Object.getPrototypeOf(this)._events + ) { + this._events = Object.create(null); + this._eventsCount = 0; + } + + this._maxListeners = this._maxListeners || undefined; + + if (opts?.captureRejections) { + validateBoolean(opts.captureRejections, "options.captureRejections"); + this[kCapture] = Boolean(opts.captureRejections); + } else { + // Assigning the kCapture property directly saves an expensive + // prototype lookup in a very sensitive hot path. + this[kCapture] = EventEmitter.prototype[kCapture]; + } +}; + +function addCatch(that, promise, type, args) { + if (!that[kCapture]) { + return; + } + + // Handle Promises/A+ spec, then could be a getter + // that throws on second use. + try { + const then = promise.then; + + if (typeof then === "function") { + then.call(promise, undefined, function (err) { + // The callback is called with nextTick to avoid a follow-up + // rejection from this promise. + process.nextTick(emitUnhandledRejectionOrErr, that, err, type, args); + }); + } + } catch (err) { + that.emit("error", err); + } +} + +function emitUnhandledRejectionOrErr(ee, err, type, args) { + if (typeof ee[kRejection] === "function") { + ee[kRejection](err, type, ...args); + } else { + // We have to disable the capture rejections mechanism, otherwise + // we might end up in an infinite loop. + const prev = ee[kCapture]; + + // If the error handler throws, it is not catcheable and it + // will end up in 'uncaughtException'. We restore the previous + // value of kCapture in case the uncaughtException is present + // and the exception is handled. + try { + ee[kCapture] = false; + ee.emit("error", err); + } finally { + ee[kCapture] = prev; + } + } +} + +/** + * Increases the max listeners of the event emitter. + * @param {number} n + * @returns {EventEmitter} + */ +EventEmitter.prototype.setMaxListeners = function setMaxListeners(n) { + if (typeof n !== "number" || n < 0 || Number.isNaN(n)) { + throw new ERR_OUT_OF_RANGE("n", "a non-negative number", n); + } + this._maxListeners = n; + return this; +}; + +function _getMaxListeners(that) { + if (that._maxListeners === undefined) { + return EventEmitter.defaultMaxListeners; + } + return that._maxListeners; +} + +/** + * Returns the current max listener value for the event emitter. + * @returns {number} + */ +EventEmitter.prototype.getMaxListeners = function getMaxListeners() { + return _getMaxListeners(this); +}; + +// Returns the length and line number of the first sequence of `a` that fully +// appears in `b` with a length of at least 4. +function identicalSequenceRange(a, b) { + for (let i = 0; i < a.length - 3; i++) { + // Find the first entry of b that matches the current entry of a. + const pos = b.indexOf(a[i]); + if (pos !== -1) { + const rest = b.length - pos; + if (rest > 3) { + let len = 1; + const maxLen = Math.min(a.length - i, rest); + // Count the number of consecutive entries. + while (maxLen > len && a[i + len] === b[pos + len]) { + len++; + } + if (len > 3) { + return [len, i]; + } + } + } + } + + return [0, 0]; +} + +// deno-lint-ignore no-unused-vars +function enhanceStackTrace(err, own) { + let ctorInfo = ""; + try { + const { name } = this.constructor; + if (name !== "EventEmitter") { + ctorInfo = ` on ${name} instance`; + } + } catch { + // pass + } + const sep = `\nEmitted 'error' event${ctorInfo} at:\n`; + + const errStack = err.stack.split("\n").slice(1); + const ownStack = own.stack.split("\n").slice(1); + + const { 0: len, 1: off } = identicalSequenceRange(ownStack, errStack); + if (len > 0) { + ownStack.splice( + off + 1, + len - 2, + " [... lines matching original stack trace ...]", + ); + } + + return err.stack + sep + ownStack.join("\n"); +} + +/** + * Synchronously calls each of the listeners registered + * for the event. + * @param {string | symbol} type + * @param {...any} [args] + * @returns {boolean} + */ +EventEmitter.prototype.emit = function emit(type, ...args) { + let doError = type === "error"; + + const events = this._events; + if (events !== undefined) { + if (doError && events[kErrorMonitor] !== undefined) { + this.emit(kErrorMonitor, ...args); + } + doError = doError && events.error === undefined; + } else if (!doError) { + return false; + } + + // If there is no 'error' event listener then throw. + if (doError) { + let er; + if (args.length > 0) { + er = args[0]; + } + if (er instanceof Error) { + try { + const capture = {}; + Error.captureStackTrace(capture, EventEmitter.prototype.emit); + // Object.defineProperty(er, kEnhanceStackBeforeInspector, { + // value: enhanceStackTrace.bind(this, er, capture), + // configurable: true + // }); + } catch { + // pass + } + + // Note: The comments on the `throw` lines are intentional, they show + // up in Node's output if this results in an unhandled exception. + throw er; // Unhandled 'error' event + } + + let stringifiedEr; + try { + stringifiedEr = inspect(er); + } catch { + stringifiedEr = er; + } + + // At least give some kind of context to the user + const err = new ERR_UNHANDLED_ERROR(stringifiedEr); + err.context = er; + throw err; // Unhandled 'error' event + } + + const handler = events[type]; + + if (handler === undefined) { + return false; + } + + if (typeof handler === "function") { + const result = handler.apply(this, args); + + // We check if result is undefined first because that + // is the most common case so we do not pay any perf + // penalty + if (result !== undefined && result !== null) { + addCatch(this, result, type, args); + } + } else { + const len = handler.length; + const listeners = arrayClone(handler); + for (let i = 0; i < len; ++i) { + const result = listeners[i].apply(this, args); + + // We check if result is undefined first because that + // is the most common case so we do not pay any perf + // penalty. + // This code is duplicated because extracting it away + // would make it non-inlineable. + if (result !== undefined && result !== null) { + addCatch(this, result, type, args); + } + } + } + + return true; +}; + +function _addListener(target, type, listener, prepend) { + let m; + let events; + let existing; + + checkListener(listener); + + events = target._events; + if (events === undefined) { + events = target._events = Object.create(null); + target._eventsCount = 0; + } else { + // To avoid recursion in the case that type === "newListener"! Before + // adding it to the listeners, first emit "newListener". + if (events.newListener !== undefined) { + target.emit("newListener", type, listener.listener ?? listener); + + // Re-assign `events` because a newListener handler could have caused the + // this._events to be assigned to a new object + events = target._events; + } + existing = events[type]; + } + + if (existing === undefined) { + // Optimize the case of one listener. Don't need the extra array object. + events[type] = listener; + ++target._eventsCount; + } else { + if (typeof existing === "function") { + // Adding the second element, need to change to array. + existing = events[type] = prepend + ? [listener, existing] + : [existing, listener]; + // If we've already got an array, just append. + } else if (prepend) { + existing.unshift(listener); + } else { + existing.push(listener); + } + + // Check for listener leak + m = _getMaxListeners(target); + if (m > 0 && existing.length > m && !existing.warned) { + existing.warned = true; + // No error code for this since it is a Warning + // eslint-disable-next-line no-restricted-syntax + const w = new Error( + "Possible EventEmitter memory leak detected. " + + `${existing.length} ${String(type)} listeners ` + + `added to ${inspect(target, { depth: -1 })}. Use ` + + "emitter.setMaxListeners() to increase limit", + ); + w.name = "MaxListenersExceededWarning"; + w.emitter = target; + w.type = type; + w.count = existing.length; + process.emitWarning(w); + } + } + + return target; +} + +/** + * Adds a listener to the event emitter. + * @param {string | symbol} type + * @param {Function} listener + * @returns {EventEmitter} + */ +EventEmitter.prototype.addListener = function addListener(type, listener) { + return _addListener(this, type, listener, false); +}; + +EventEmitter.prototype.on = EventEmitter.prototype.addListener; + +/** + * Adds the `listener` function to the beginning of + * the listeners array. + * @param {string | symbol} type + * @param {Function} listener + * @returns {EventEmitter} + */ +EventEmitter.prototype.prependListener = function prependListener( + type, + listener, +) { + return _addListener(this, type, listener, true); +}; + +function onceWrapper() { + if (!this.fired) { + this.target.removeListener(this.type, this.wrapFn); + this.fired = true; + if (arguments.length === 0) { + return this.listener.call(this.target); + } + return this.listener.apply(this.target, arguments); + } +} + +function _onceWrap(target, type, listener) { + const state = { fired: false, wrapFn: undefined, target, type, listener }; + const wrapped = onceWrapper.bind(state); + wrapped.listener = listener; + state.wrapFn = wrapped; + return wrapped; +} + +/** + * Adds a one-time `listener` function to the event emitter. + * @param {string | symbol} type + * @param {Function} listener + * @returns {EventEmitter} + */ +EventEmitter.prototype.once = function once(type, listener) { + checkListener(listener); + + this.on(type, _onceWrap(this, type, listener)); + return this; +}; + +/** + * Adds a one-time `listener` function to the beginning of + * the listeners array. + * @param {string | symbol} type + * @param {Function} listener + * @returns {EventEmitter} + */ +EventEmitter.prototype.prependOnceListener = function prependOnceListener( + type, + listener, +) { + checkListener(listener); + + this.prependListener(type, _onceWrap(this, type, listener)); + return this; +}; + +/** + * Removes the specified `listener` from the listeners array. + * @param {string | symbol} type + * @param {Function} listener + * @returns {EventEmitter} + */ +EventEmitter.prototype.removeListener = function removeListener( + type, + listener, +) { + checkListener(listener); + + const events = this._events; + if (events === undefined) { + return this; + } + + const list = events[type]; + if (list === undefined) { + return this; + } + + if (list === listener || list.listener === listener) { + if (--this._eventsCount === 0) { + this._events = Object.create(null); + } else { + delete events[type]; + if (events.removeListener) { + this.emit("removeListener", type, list.listener || listener); + } + } + } else if (typeof list !== "function") { + let position = -1; + + for (let i = list.length - 1; i >= 0; i--) { + if (list[i] === listener || list[i].listener === listener) { + position = i; + break; + } + } + + if (position < 0) { + return this; + } + + if (position === 0) { + list.shift(); + } else { + spliceOne(list, position); + } + + if (list.length === 1) { + events[type] = list[0]; + } + + if (events.removeListener !== undefined) { + this.emit("removeListener", type, listener); + } + } + + return this; +}; + +EventEmitter.prototype.off = EventEmitter.prototype.removeListener; + +/** + * Removes all listeners from the event emitter. (Only + * removes listeners for a specific event name if specified + * as `type`). + * @param {string | symbol} [type] + * @returns {EventEmitter} + */ +EventEmitter.prototype.removeAllListeners = function removeAllListeners(type) { + const events = this._events; + if (events === undefined) { + return this; + } + + // Not listening for removeListener, no need to emit + if (events.removeListener === undefined) { + if (arguments.length === 0) { + this._events = Object.create(null); + this._eventsCount = 0; + } else if (events[type] !== undefined) { + if (--this._eventsCount === 0) { + this._events = Object.create(null); + } else { + delete events[type]; + } + } + return this; + } + + // Emit removeListener for all listeners on all events + if (arguments.length === 0) { + for (const key of Reflect.ownKeys(events)) { + if (key === "removeListener") continue; + this.removeAllListeners(key); + } + this.removeAllListeners("removeListener"); + this._events = Object.create(null); + this._eventsCount = 0; + return this; + } + + const listeners = events[type]; + + if (typeof listeners === "function") { + this.removeListener(type, listeners); + } else if (listeners !== undefined) { + // LIFO order + for (let i = listeners.length - 1; i >= 0; i--) { + this.removeListener(type, listeners[i]); + } + } + + return this; +}; + +function _listeners(target, type, unwrap) { + const events = target._events; + + if (events === undefined) { + return []; + } + + const evlistener = events[type]; + if (evlistener === undefined) { + return []; + } + + if (typeof evlistener === "function") { + return unwrap ? [evlistener.listener || evlistener] : [evlistener]; + } + + return unwrap ? unwrapListeners(evlistener) : arrayClone(evlistener); +} + +/** + * Returns a copy of the array of listeners for the event name + * specified as `type`. + * @param {string | symbol} type + * @returns {Function[]} + */ +EventEmitter.prototype.listeners = function listeners(type) { + return _listeners(this, type, true); +}; + +/** + * Returns a copy of the array of listeners and wrappers for + * the event name specified as `type`. + * @param {string | symbol} type + * @returns {Function[]} + */ +EventEmitter.prototype.rawListeners = function rawListeners(type) { + return _listeners(this, type, false); +}; + +/** + * Returns the number of listeners listening to event name + * specified as `type`. + * @param {string | symbol} type + * @returns {number} + */ +const _listenerCount = function listenerCount(type) { + const events = this._events; + + if (events !== undefined) { + const evlistener = events[type]; + + if (typeof evlistener === "function") { + return 1; + } else if (evlistener !== undefined) { + return evlistener.length; + } + } + + return 0; +}; + +EventEmitter.prototype.listenerCount = _listenerCount; + +/** + * Returns the number of listeners listening to the event name + * specified as `type`. + * @deprecated since v3.2.0 + * @param {EventEmitter} emitter + * @param {string | symbol} type + * @returns {number} + */ +export function listenerCount(emitter, type) { + if (typeof emitter.listenerCount === "function") { + return emitter.listenerCount(type); + } + return _listenerCount.call(emitter, type); +} + +/** + * Returns an array listing the events for which + * the emitter has registered listeners. + * @returns {any[]} + */ +EventEmitter.prototype.eventNames = function eventNames() { + return this._eventsCount > 0 ? Reflect.ownKeys(this._events) : []; +}; + +function arrayClone(arr) { + // At least since V8 8.3, this implementation is faster than the previous + // which always used a simple for-loop + switch (arr.length) { + case 2: + return [arr[0], arr[1]]; + case 3: + return [arr[0], arr[1], arr[2]]; + case 4: + return [arr[0], arr[1], arr[2], arr[3]]; + case 5: + return [arr[0], arr[1], arr[2], arr[3], arr[4]]; + case 6: + return [arr[0], arr[1], arr[2], arr[3], arr[4], arr[5]]; + } + return arr.slice(); +} + +function unwrapListeners(arr) { + const ret = arrayClone(arr); + for (let i = 0; i < ret.length; ++i) { + const orig = ret[i].listener; + if (typeof orig === "function") { + ret[i] = orig; + } + } + return ret; +} + +/** + * Returns a copy of the array of listeners for the event name + * specified as `type`. + * @param {EventEmitter | EventTarget} emitterOrTarget + * @param {string | symbol} type + * @returns {Function[]} + */ +export function getEventListeners(emitterOrTarget, type) { + // First check if EventEmitter + if (typeof emitterOrTarget.listeners === "function") { + return emitterOrTarget.listeners(type); + } + if (emitterOrTarget instanceof EventTarget) { + // TODO: kEvents is not defined + const root = emitterOrTarget[kEvents].get(type); + const listeners = []; + let handler = root?.next; + while (handler?.listener !== undefined) { + const listener = handler.listener?.deref + ? handler.listener.deref() + : handler.listener; + listeners.push(listener); + handler = handler.next; + } + return listeners; + } + throw new ERR_INVALID_ARG_TYPE( + "emitter", + ["EventEmitter", "EventTarget"], + emitterOrTarget, + ); +} + +/** + * Creates a `Promise` that is fulfilled when the emitter + * emits the given event. + * @param {EventEmitter} emitter + * @param {string} name + * @param {{ signal: AbortSignal; }} [options] + * @returns {Promise} + */ +// deno-lint-ignore require-await +export async function once(emitter, name, options = {}) { + const signal = options?.signal; + validateAbortSignal(signal, "options.signal"); + if (signal?.aborted) { + throw new AbortError(); + } + return new Promise((resolve, reject) => { + const errorListener = (err) => { + emitter.removeListener(name, resolver); + if (signal != null) { + eventTargetAgnosticRemoveListener(signal, "abort", abortListener); + } + reject(err); + }; + const resolver = (...args) => { + if (typeof emitter.removeListener === "function") { + emitter.removeListener("error", errorListener); + } + if (signal != null) { + eventTargetAgnosticRemoveListener(signal, "abort", abortListener); + } + resolve(args); + }; + eventTargetAgnosticAddListener(emitter, name, resolver, { once: true }); + if (name !== "error" && typeof emitter.once === "function") { + emitter.once("error", errorListener); + } + function abortListener() { + eventTargetAgnosticRemoveListener(emitter, name, resolver); + eventTargetAgnosticRemoveListener(emitter, "error", errorListener); + reject(new AbortError()); + } + if (signal != null) { + eventTargetAgnosticAddListener( + signal, + "abort", + abortListener, + { once: true }, + ); + } + }); +} + +const AsyncIteratorPrototype = Object.getPrototypeOf( + Object.getPrototypeOf(async function* () {}).prototype, +); + +function createIterResult(value, done) { + return { value, done }; +} + +function eventTargetAgnosticRemoveListener(emitter, name, listener, flags) { + if (typeof emitter.removeListener === "function") { + emitter.removeListener(name, listener); + } else if (typeof emitter.removeEventListener === "function") { + emitter.removeEventListener(name, listener, flags); + } else { + throw new ERR_INVALID_ARG_TYPE("emitter", "EventEmitter", emitter); + } +} + +function eventTargetAgnosticAddListener(emitter, name, listener, flags) { + if (typeof emitter.on === "function") { + if (flags?.once) { + emitter.once(name, listener); + } else { + emitter.on(name, listener); + } + } else if (typeof emitter.addEventListener === "function") { + // EventTarget does not have `error` event semantics like Node + // EventEmitters, we do not listen to `error` events here. + emitter.addEventListener(name, (arg) => { + listener(arg); + }, flags); + } else { + throw new ERR_INVALID_ARG_TYPE("emitter", "EventEmitter", emitter); + } +} + +/** + * Returns an `AsyncIterator` that iterates `event` events. + * @param {EventEmitter} emitter + * @param {string | symbol} event + * @param {{ signal: AbortSignal; }} [options] + * @returns {AsyncIterator} + */ +export function on(emitter, event, options) { + const signal = options?.signal; + validateAbortSignal(signal, "options.signal"); + if (signal?.aborted) { + throw new AbortError(); + } + + const unconsumedEvents = []; + const unconsumedPromises = []; + let error = null; + let finished = false; + + const iterator = Object.setPrototypeOf({ + next() { + // First, we consume all unread events + const value = unconsumedEvents.shift(); + if (value) { + return Promise.resolve(createIterResult(value, false)); + } + + // Then we error, if an error happened + // This happens one time if at all, because after 'error' + // we stop listening + if (error) { + const p = Promise.reject(error); + // Only the first element errors + error = null; + return p; + } + + // If the iterator is finished, resolve to done + if (finished) { + return Promise.resolve(createIterResult(undefined, true)); + } + + // Wait until an event happens + return new Promise(function (resolve, reject) { + unconsumedPromises.push({ resolve, reject }); + }); + }, + + return() { + eventTargetAgnosticRemoveListener(emitter, event, eventHandler); + eventTargetAgnosticRemoveListener(emitter, "error", errorHandler); + + if (signal) { + eventTargetAgnosticRemoveListener( + signal, + "abort", + abortListener, + { once: true }, + ); + } + + finished = true; + + for (const promise of unconsumedPromises) { + promise.resolve(createIterResult(undefined, true)); + } + + return Promise.resolve(createIterResult(undefined, true)); + }, + + throw(err) { + if (!err || !(err instanceof Error)) { + throw new ERR_INVALID_ARG_TYPE( + "EventEmitter.AsyncIterator", + "Error", + err, + ); + } + error = err; + eventTargetAgnosticRemoveListener(emitter, event, eventHandler); + eventTargetAgnosticRemoveListener(emitter, "error", errorHandler); + }, + + [Symbol.asyncIterator]() { + return this; + }, + }, AsyncIteratorPrototype); + + eventTargetAgnosticAddListener(emitter, event, eventHandler); + if (event !== "error" && typeof emitter.on === "function") { + emitter.on("error", errorHandler); + } + + if (signal) { + eventTargetAgnosticAddListener( + signal, + "abort", + abortListener, + { once: true }, + ); + } + + return iterator; + + function abortListener() { + errorHandler(new AbortError()); + } + + function eventHandler(...args) { + const promise = unconsumedPromises.shift(); + if (promise) { + promise.resolve(createIterResult(args, false)); + } else { + unconsumedEvents.push(args); + } + } + + function errorHandler(err) { + finished = true; + + const toError = unconsumedPromises.shift(); + + if (toError) { + toError.reject(err); + } else { + // The next time we call next() + error = err; + } + + iterator.return(); + } +} diff --git a/ext/node/polyfills/_fs/_fs_access.ts b/ext/node/polyfills/_fs/_fs_access.ts new file mode 100644 index 00000000000000..9450c2f01127d6 --- /dev/null +++ b/ext/node/polyfills/_fs/_fs_access.ts @@ -0,0 +1,127 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. + +import { + type CallbackWithError, + makeCallback, +} from "internal:deno_node/polyfills/_fs/_fs_common.ts"; +import { fs } from "internal:deno_node/polyfills/internal_binding/constants.ts"; +import { codeMap } from "internal:deno_node/polyfills/internal_binding/uv.ts"; +import { + getValidatedPath, + getValidMode, +} from "internal:deno_node/polyfills/internal/fs/utils.mjs"; +import type { Buffer } from "internal:deno_node/polyfills/buffer.ts"; +import { promisify } from "internal:deno_node/polyfills/internal/util.mjs"; + +export function access( + path: string | Buffer | URL, + mode: number | CallbackWithError, + callback?: CallbackWithError, +) { + if (typeof mode === "function") { + callback = mode; + mode = fs.F_OK; + } + + path = getValidatedPath(path).toString(); + mode = getValidMode(mode, "access"); + const cb = makeCallback(callback); + + Deno.lstat(path).then((info) => { + if (info.mode === null) { + // If the file mode is unavailable, we pretend it has + // the permission + cb(null); + return; + } + const m = +mode || 0; + let fileMode = +info.mode || 0; + if (Deno.build.os !== "windows" && info.uid === Deno.uid()) { + // If the user is the owner of the file, then use the owner bits of + // the file permission + fileMode >>= 6; + } + // TODO(kt3k): Also check the case when the user belong to the group + // of the file + if ((m & fileMode) === m) { + // all required flags exist + cb(null); + } else { + // some required flags don't + // deno-lint-ignore no-explicit-any + const e: any = new Error(`EACCES: permission denied, access '${path}'`); + e.path = path; + e.syscall = "access"; + e.errno = codeMap.get("EACCES"); + e.code = "EACCES"; + cb(e); + } + }, (err) => { + if (err instanceof Deno.errors.NotFound) { + // deno-lint-ignore no-explicit-any + const e: any = new Error( + `ENOENT: no such file or directory, access '${path}'`, + ); + e.path = path; + e.syscall = "access"; + e.errno = codeMap.get("ENOENT"); + e.code = "ENOENT"; + cb(e); + } else { + cb(err); + } + }); +} + +export const accessPromise = promisify(access) as ( + path: string | Buffer | URL, + mode?: number, +) => Promise; + +export function accessSync(path: string | Buffer | URL, mode?: number) { + path = getValidatedPath(path).toString(); + mode = getValidMode(mode, "access"); + try { + const info = Deno.lstatSync(path.toString()); + if (info.mode === null) { + // If the file mode is unavailable, we pretend it has + // the permission + return; + } + const m = +mode! || 0; + let fileMode = +info.mode! || 0; + if (Deno.build.os !== "windows" && info.uid === Deno.uid()) { + // If the user is the owner of the file, then use the owner bits of + // the file permission + fileMode >>= 6; + } + // TODO(kt3k): Also check the case when the user belong to the group + // of the file + if ((m & fileMode) === m) { + // all required flags exist + } else { + // some required flags don't + // deno-lint-ignore no-explicit-any + const e: any = new Error(`EACCES: permission denied, access '${path}'`); + e.path = path; + e.syscall = "access"; + e.errno = codeMap.get("EACCES"); + e.code = "EACCES"; + throw e; + } + } catch (err) { + if (err instanceof Deno.errors.NotFound) { + // deno-lint-ignore no-explicit-any + const e: any = new Error( + `ENOENT: no such file or directory, access '${path}'`, + ); + e.path = path; + e.syscall = "access"; + e.errno = codeMap.get("ENOENT"); + e.code = "ENOENT"; + throw e; + } else { + throw err; + } + } +} diff --git a/ext/node/polyfills/_fs/_fs_appendFile.ts b/ext/node/polyfills/_fs/_fs_appendFile.ts new file mode 100644 index 00000000000000..d47afe81b60938 --- /dev/null +++ b/ext/node/polyfills/_fs/_fs_appendFile.ts @@ -0,0 +1,73 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. +import { + CallbackWithError, + isFd, + maybeCallback, + WriteFileOptions, +} from "internal:deno_node/polyfills/_fs/_fs_common.ts"; +import { Encodings } from "internal:deno_node/polyfills/_utils.ts"; +import { + copyObject, + getOptions, +} from "internal:deno_node/polyfills/internal/fs/utils.mjs"; +import { + writeFile, + writeFileSync, +} from "internal:deno_node/polyfills/_fs/_fs_writeFile.ts"; +import { promisify } from "internal:deno_node/polyfills/internal/util.mjs"; + +/** + * TODO: Also accept 'data' parameter as a Node polyfill Buffer type once these + * are implemented. See https://github.com/denoland/deno/issues/3403 + */ +export function appendFile( + path: string | number | URL, + data: string | Uint8Array, + options: Encodings | WriteFileOptions | CallbackWithError, + callback?: CallbackWithError, +) { + callback = maybeCallback(callback || options); + options = getOptions(options, { encoding: "utf8", mode: 0o666, flag: "a" }); + + // Don't make changes directly on options object + options = copyObject(options); + + // Force append behavior when using a supplied file descriptor + if (!options.flag || isFd(path)) { + options.flag = "a"; + } + + writeFile(path, data, options, callback); +} + +/** + * TODO: Also accept 'data' parameter as a Node polyfill Buffer type once these + * are implemented. See https://github.com/denoland/deno/issues/3403 + */ +export const appendFilePromise = promisify(appendFile) as ( + path: string | number | URL, + data: string | Uint8Array, + options?: Encodings | WriteFileOptions, +) => Promise; + +/** + * TODO: Also accept 'data' parameter as a Node polyfill Buffer type once these + * are implemented. See https://github.com/denoland/deno/issues/3403 + */ +export function appendFileSync( + path: string | number | URL, + data: string | Uint8Array, + options?: Encodings | WriteFileOptions, +) { + options = getOptions(options, { encoding: "utf8", mode: 0o666, flag: "a" }); + + // Don't make changes directly on options object + options = copyObject(options); + + // Force append behavior when using a supplied file descriptor + if (!options.flag || isFd(path)) { + options.flag = "a"; + } + + writeFileSync(path, data, options); +} diff --git a/ext/node/polyfills/_fs/_fs_chmod.ts b/ext/node/polyfills/_fs/_fs_chmod.ts new file mode 100644 index 00000000000000..3a19e5622b0f21 --- /dev/null +++ b/ext/node/polyfills/_fs/_fs_chmod.ts @@ -0,0 +1,69 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. +import type { CallbackWithError } from "internal:deno_node/polyfills/_fs/_fs_common.ts"; +import { getValidatedPath } from "internal:deno_node/polyfills/internal/fs/utils.mjs"; +import * as pathModule from "internal:deno_node/polyfills/path.ts"; +import { parseFileMode } from "internal:deno_node/polyfills/internal/validators.mjs"; +import { Buffer } from "internal:deno_node/polyfills/buffer.ts"; +import { promisify } from "internal:deno_node/polyfills/internal/util.mjs"; + +export function chmod( + path: string | Buffer | URL, + mode: string | number, + callback: CallbackWithError, +) { + path = getValidatedPath(path).toString(); + + try { + mode = parseFileMode(mode, "mode"); + } catch (error) { + // TODO(PolarETech): Errors should not be ignored when Deno.chmod is supported on Windows. + // https://github.com/denoland/deno_std/issues/2916 + if (Deno.build.os === "windows") { + mode = 0; // set dummy value to avoid type checking error at Deno.chmod + } else { + throw error; + } + } + + Deno.chmod(pathModule.toNamespacedPath(path), mode).catch((error) => { + // Ignore NotSupportedError that occurs on windows + // https://github.com/denoland/deno_std/issues/2995 + if (!(error instanceof Deno.errors.NotSupported)) { + throw error; + } + }).then( + () => callback(null), + callback, + ); +} + +export const chmodPromise = promisify(chmod) as ( + path: string | Buffer | URL, + mode: string | number, +) => Promise; + +export function chmodSync(path: string | URL, mode: string | number) { + path = getValidatedPath(path).toString(); + + try { + mode = parseFileMode(mode, "mode"); + } catch (error) { + // TODO(PolarETech): Errors should not be ignored when Deno.chmodSync is supported on Windows. + // https://github.com/denoland/deno_std/issues/2916 + if (Deno.build.os === "windows") { + mode = 0; // set dummy value to avoid type checking error at Deno.chmodSync + } else { + throw error; + } + } + + try { + Deno.chmodSync(pathModule.toNamespacedPath(path), mode); + } catch (error) { + // Ignore NotSupportedError that occurs on windows + // https://github.com/denoland/deno_std/issues/2995 + if (!(error instanceof Deno.errors.NotSupported)) { + throw error; + } + } +} diff --git a/ext/node/polyfills/_fs/_fs_chown.ts b/ext/node/polyfills/_fs/_fs_chown.ts new file mode 100644 index 00000000000000..55a469fbaf9f6c --- /dev/null +++ b/ext/node/polyfills/_fs/_fs_chown.ts @@ -0,0 +1,56 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. +import { + type CallbackWithError, + makeCallback, +} from "internal:deno_node/polyfills/_fs/_fs_common.ts"; +import { + getValidatedPath, + kMaxUserId, +} from "internal:deno_node/polyfills/internal/fs/utils.mjs"; +import * as pathModule from "internal:deno_node/polyfills/path.ts"; +import { validateInteger } from "internal:deno_node/polyfills/internal/validators.mjs"; +import type { Buffer } from "internal:deno_node/polyfills/buffer.ts"; +import { promisify } from "internal:deno_node/polyfills/internal/util.mjs"; + +/** + * Asynchronously changes the owner and group + * of a file. + */ +export function chown( + path: string | Buffer | URL, + uid: number, + gid: number, + callback: CallbackWithError, +) { + callback = makeCallback(callback); + path = getValidatedPath(path).toString(); + validateInteger(uid, "uid", -1, kMaxUserId); + validateInteger(gid, "gid", -1, kMaxUserId); + + Deno.chown(pathModule.toNamespacedPath(path), uid, gid).then( + () => callback(null), + callback, + ); +} + +export const chownPromise = promisify(chown) as ( + path: string | Buffer | URL, + uid: number, + gid: number, +) => Promise; + +/** + * Synchronously changes the owner and group + * of a file. + */ +export function chownSync( + path: string | Buffer | URL, + uid: number, + gid: number, +) { + path = getValidatedPath(path).toString(); + validateInteger(uid, "uid", -1, kMaxUserId); + validateInteger(gid, "gid", -1, kMaxUserId); + + Deno.chownSync(pathModule.toNamespacedPath(path), uid, gid); +} diff --git a/ext/node/polyfills/_fs/_fs_close.ts b/ext/node/polyfills/_fs/_fs_close.ts new file mode 100644 index 00000000000000..ff6082980bb697 --- /dev/null +++ b/ext/node/polyfills/_fs/_fs_close.ts @@ -0,0 +1,21 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. +import type { CallbackWithError } from "internal:deno_node/polyfills/_fs/_fs_common.ts"; +import { getValidatedFd } from "internal:deno_node/polyfills/internal/fs/utils.mjs"; + +export function close(fd: number, callback: CallbackWithError) { + fd = getValidatedFd(fd); + setTimeout(() => { + let error = null; + try { + Deno.close(fd); + } catch (err) { + error = err instanceof Error ? err : new Error("[non-error thrown]"); + } + callback(error); + }, 0); +} + +export function closeSync(fd: number) { + fd = getValidatedFd(fd); + Deno.close(fd); +} diff --git a/ext/node/polyfills/_fs/_fs_common.ts b/ext/node/polyfills/_fs/_fs_common.ts new file mode 100644 index 00000000000000..1e9f0f266806ea --- /dev/null +++ b/ext/node/polyfills/_fs/_fs_common.ts @@ -0,0 +1,233 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. +import { + O_APPEND, + O_CREAT, + O_EXCL, + O_RDONLY, + O_RDWR, + O_TRUNC, + O_WRONLY, +} from "internal:deno_node/polyfills/_fs/_fs_constants.ts"; +import { validateFunction } from "internal:deno_node/polyfills/internal/validators.mjs"; +import type { ErrnoException } from "internal:deno_node/polyfills/_global.d.ts"; +import { + BinaryEncodings, + Encodings, + notImplemented, + TextEncodings, +} from "internal:deno_node/polyfills/_utils.ts"; + +export type CallbackWithError = (err: ErrnoException | null) => void; + +export interface FileOptions { + encoding?: Encodings; + flag?: string; + signal?: AbortSignal; +} + +export type TextOptionsArgument = + | TextEncodings + | ({ encoding: TextEncodings } & FileOptions); +export type BinaryOptionsArgument = + | BinaryEncodings + | ({ encoding: BinaryEncodings } & FileOptions); +export type FileOptionsArgument = Encodings | FileOptions; + +export interface WriteFileOptions extends FileOptions { + mode?: number; +} + +export function isFileOptions( + fileOptions: string | WriteFileOptions | undefined, +): fileOptions is FileOptions { + if (!fileOptions) return false; + + return ( + (fileOptions as FileOptions).encoding != undefined || + (fileOptions as FileOptions).flag != undefined || + (fileOptions as FileOptions).signal != undefined || + (fileOptions as WriteFileOptions).mode != undefined + ); +} + +export function getEncoding( + optOrCallback?: + | FileOptions + | WriteFileOptions + // deno-lint-ignore no-explicit-any + | ((...args: any[]) => any) + | Encodings + | null, +): Encodings | null { + if (!optOrCallback || typeof optOrCallback === "function") { + return null; + } + + const encoding = typeof optOrCallback === "string" + ? optOrCallback + : optOrCallback.encoding; + if (!encoding) return null; + return encoding; +} + +export function checkEncoding(encoding: Encodings | null): Encodings | null { + if (!encoding) return null; + + encoding = encoding.toLowerCase() as Encodings; + if (["utf8", "hex", "base64"].includes(encoding)) return encoding; + + if (encoding === "utf-8") { + return "utf8"; + } + if (encoding === "binary") { + return "binary"; + // before this was buffer, however buffer is not used in Node + // node -e "require('fs').readFile('../world.txt', 'buffer', console.log)" + } + + const notImplementedEncodings = ["utf16le", "latin1", "ascii", "ucs2"]; + + if (notImplementedEncodings.includes(encoding as string)) { + notImplemented(`"${encoding}" encoding`); + } + + throw new Error(`The value "${encoding}" is invalid for option "encoding"`); +} + +export function getOpenOptions( + flag: string | number | undefined, +): Deno.OpenOptions { + if (!flag) { + return { create: true, append: true }; + } + + let openOptions: Deno.OpenOptions = {}; + + if (typeof flag === "string") { + switch (flag) { + case "a": { + // 'a': Open file for appending. The file is created if it does not exist. + openOptions = { create: true, append: true }; + break; + } + case "ax": + case "xa": { + // 'ax', 'xa': Like 'a' but fails if the path exists. + openOptions = { createNew: true, write: true, append: true }; + break; + } + case "a+": { + // 'a+': Open file for reading and appending. The file is created if it does not exist. + openOptions = { read: true, create: true, append: true }; + break; + } + case "ax+": + case "xa+": { + // 'ax+', 'xa+': Like 'a+' but fails if the path exists. + openOptions = { read: true, createNew: true, append: true }; + break; + } + case "r": { + // 'r': Open file for reading. An exception occurs if the file does not exist. + openOptions = { read: true }; + break; + } + case "r+": { + // 'r+': Open file for reading and writing. An exception occurs if the file does not exist. + openOptions = { read: true, write: true }; + break; + } + case "w": { + // 'w': Open file for writing. The file is created (if it does not exist) or truncated (if it exists). + openOptions = { create: true, write: true, truncate: true }; + break; + } + case "wx": + case "xw": { + // 'wx', 'xw': Like 'w' but fails if the path exists. + openOptions = { createNew: true, write: true }; + break; + } + case "w+": { + // 'w+': Open file for reading and writing. The file is created (if it does not exist) or truncated (if it exists). + openOptions = { create: true, write: true, truncate: true, read: true }; + break; + } + case "wx+": + case "xw+": { + // 'wx+', 'xw+': Like 'w+' but fails if the path exists. + openOptions = { createNew: true, write: true, read: true }; + break; + } + case "as": + case "sa": { + // 'as', 'sa': Open file for appending in synchronous mode. The file is created if it does not exist. + openOptions = { create: true, append: true }; + break; + } + case "as+": + case "sa+": { + // 'as+', 'sa+': Open file for reading and appending in synchronous mode. The file is created if it does not exist. + openOptions = { create: true, read: true, append: true }; + break; + } + case "rs+": + case "sr+": { + // 'rs+', 'sr+': Open file for reading and writing in synchronous mode. Instructs the operating system to bypass the local file system cache. + openOptions = { create: true, read: true, write: true }; + break; + } + default: { + throw new Error(`Unrecognized file system flag: ${flag}`); + } + } + } else if (typeof flag === "number") { + if ((flag & O_APPEND) === O_APPEND) { + openOptions.append = true; + } + if ((flag & O_CREAT) === O_CREAT) { + openOptions.create = true; + openOptions.write = true; + } + if ((flag & O_EXCL) === O_EXCL) { + openOptions.createNew = true; + openOptions.read = true; + openOptions.write = true; + } + if ((flag & O_TRUNC) === O_TRUNC) { + openOptions.truncate = true; + } + if ((flag & O_RDONLY) === O_RDONLY) { + openOptions.read = true; + } + if ((flag & O_WRONLY) === O_WRONLY) { + openOptions.write = true; + } + if ((flag & O_RDWR) === O_RDWR) { + openOptions.read = true; + openOptions.write = true; + } + } + + return openOptions; +} + +export { isUint32 as isFd } from "internal:deno_node/polyfills/internal/validators.mjs"; + +export function maybeCallback(cb: unknown) { + validateFunction(cb, "cb"); + + return cb as CallbackWithError; +} + +// Ensure that callbacks run in the global context. Only use this function +// for callbacks that are passed to the binding layer, callbacks that are +// invoked from JS already run in the proper scope. +export function makeCallback( + this: unknown, + cb?: (err: Error | null, result?: unknown) => void, +) { + validateFunction(cb, "cb"); + + return (...args: unknown[]) => Reflect.apply(cb!, this, args); +} diff --git a/ext/node/polyfills/_fs/_fs_constants.ts b/ext/node/polyfills/_fs/_fs_constants.ts new file mode 100644 index 00000000000000..761f6a9b7aabf9 --- /dev/null +++ b/ext/node/polyfills/_fs/_fs_constants.ts @@ -0,0 +1,39 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. + +import { fs } from "internal:deno_node/polyfills/internal_binding/constants.ts"; + +export const { + F_OK, + R_OK, + W_OK, + X_OK, + S_IRUSR, + S_IWUSR, + S_IXUSR, + S_IRGRP, + S_IWGRP, + S_IXGRP, + S_IROTH, + S_IWOTH, + S_IXOTH, + COPYFILE_EXCL, + COPYFILE_FICLONE, + COPYFILE_FICLONE_FORCE, + UV_FS_COPYFILE_EXCL, + UV_FS_COPYFILE_FICLONE, + UV_FS_COPYFILE_FICLONE_FORCE, + O_RDONLY, + O_WRONLY, + O_RDWR, + O_NOCTTY, + O_TRUNC, + O_APPEND, + O_DIRECTORY, + O_NOFOLLOW, + O_SYNC, + O_DSYNC, + O_SYMLINK, + O_NONBLOCK, + O_CREAT, + O_EXCL, +} = fs; diff --git a/ext/node/polyfills/_fs/_fs_copy.ts b/ext/node/polyfills/_fs/_fs_copy.ts new file mode 100644 index 00000000000000..0971da1eb962b8 --- /dev/null +++ b/ext/node/polyfills/_fs/_fs_copy.ts @@ -0,0 +1,88 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. +import type { CallbackWithError } from "internal:deno_node/polyfills/_fs/_fs_common.ts"; +import { makeCallback } from "internal:deno_node/polyfills/_fs/_fs_common.ts"; +import { Buffer } from "internal:deno_node/polyfills/buffer.ts"; +import { + getValidatedPath, + getValidMode, +} from "internal:deno_node/polyfills/internal/fs/utils.mjs"; +import { fs } from "internal:deno_node/polyfills/internal_binding/constants.ts"; +import { codeMap } from "internal:deno_node/polyfills/internal_binding/uv.ts"; +import { promisify } from "internal:deno_node/polyfills/internal/util.mjs"; + +export function copyFile( + src: string | Buffer | URL, + dest: string | Buffer | URL, + callback: CallbackWithError, +): void; +export function copyFile( + src: string | Buffer | URL, + dest: string | Buffer | URL, + mode: number, + callback: CallbackWithError, +): void; +export function copyFile( + src: string | Buffer | URL, + dest: string | Buffer | URL, + mode: number | CallbackWithError, + callback?: CallbackWithError, +) { + if (typeof mode === "function") { + callback = mode; + mode = 0; + } + const srcStr = getValidatedPath(src, "src").toString(); + const destStr = getValidatedPath(dest, "dest").toString(); + const modeNum = getValidMode(mode, "copyFile"); + const cb = makeCallback(callback); + + if ((modeNum & fs.COPYFILE_EXCL) === fs.COPYFILE_EXCL) { + Deno.lstat(destStr).then(() => { + // deno-lint-ignore no-explicit-any + const e: any = new Error( + `EEXIST: file already exists, copyfile '${srcStr}' -> '${destStr}'`, + ); + e.syscall = "copyfile"; + e.errno = codeMap.get("EEXIST"); + e.code = "EEXIST"; + cb(e); + }, (e) => { + if (e instanceof Deno.errors.NotFound) { + Deno.copyFile(srcStr, destStr).then(() => cb(null), cb); + } + cb(e); + }); + } else { + Deno.copyFile(srcStr, destStr).then(() => cb(null), cb); + } +} + +export const copyFilePromise = promisify(copyFile) as ( + src: string | Buffer | URL, + dest: string | Buffer | URL, + mode?: number, +) => Promise; + +export function copyFileSync( + src: string | Buffer | URL, + dest: string | Buffer | URL, + mode?: number, +) { + const srcStr = getValidatedPath(src, "src").toString(); + const destStr = getValidatedPath(dest, "dest").toString(); + const modeNum = getValidMode(mode, "copyFile"); + + if ((modeNum & fs.COPYFILE_EXCL) === fs.COPYFILE_EXCL) { + try { + Deno.lstatSync(destStr); + throw new Error(`A file exists at the destination: ${destStr}`); + } catch (e) { + if (e instanceof Deno.errors.NotFound) { + Deno.copyFileSync(srcStr, destStr); + } + throw e; + } + } else { + Deno.copyFileSync(srcStr, destStr); + } +} diff --git a/ext/node/polyfills/_fs/_fs_dir.ts b/ext/node/polyfills/_fs/_fs_dir.ts new file mode 100644 index 00000000000000..e1354724147403 --- /dev/null +++ b/ext/node/polyfills/_fs/_fs_dir.ts @@ -0,0 +1,104 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. +import Dirent from "internal:deno_node/polyfills/_fs/_fs_dirent.ts"; +import { assert } from "internal:deno_node/polyfills/_util/asserts.ts"; +import { ERR_MISSING_ARGS } from "internal:deno_node/polyfills/internal/errors.ts"; +import { TextDecoder } from "internal:deno_web/08_text_encoding.js"; + +export default class Dir { + #dirPath: string | Uint8Array; + #syncIterator!: Iterator | null; + #asyncIterator!: AsyncIterator | null; + + constructor(path: string | Uint8Array) { + if (!path) { + throw new ERR_MISSING_ARGS("path"); + } + this.#dirPath = path; + } + + get path(): string { + if (this.#dirPath instanceof Uint8Array) { + return new TextDecoder().decode(this.#dirPath); + } + return this.#dirPath; + } + + // deno-lint-ignore no-explicit-any + read(callback?: (...args: any[]) => void): Promise { + return new Promise((resolve, reject) => { + if (!this.#asyncIterator) { + this.#asyncIterator = Deno.readDir(this.path)[Symbol.asyncIterator](); + } + assert(this.#asyncIterator); + this.#asyncIterator + .next() + .then((iteratorResult) => { + resolve( + iteratorResult.done ? null : new Dirent(iteratorResult.value), + ); + if (callback) { + callback( + null, + iteratorResult.done ? null : new Dirent(iteratorResult.value), + ); + } + }, (err) => { + if (callback) { + callback(err); + } + reject(err); + }); + }); + } + + readSync(): Dirent | null { + if (!this.#syncIterator) { + this.#syncIterator = Deno.readDirSync(this.path)![Symbol.iterator](); + } + + const iteratorResult = this.#syncIterator.next(); + if (iteratorResult.done) { + return null; + } else { + return new Dirent(iteratorResult.value); + } + } + + /** + * Unlike Node, Deno does not require managing resource ids for reading + * directories, and therefore does not need to close directories when + * finished reading. + */ + // deno-lint-ignore no-explicit-any + close(callback?: (...args: any[]) => void): Promise { + return new Promise((resolve) => { + if (callback) { + callback(null); + } + resolve(); + }); + } + + /** + * Unlike Node, Deno does not require managing resource ids for reading + * directories, and therefore does not need to close directories when + * finished reading + */ + closeSync() { + //No op + } + + async *[Symbol.asyncIterator](): AsyncIterableIterator { + try { + while (true) { + const dirent: Dirent | null = await this.read(); + if (dirent === null) { + break; + } + yield dirent; + } + } finally { + await this.close(); + } + } +} diff --git a/ext/node/polyfills/_fs/_fs_dirent.ts b/ext/node/polyfills/_fs/_fs_dirent.ts new file mode 100644 index 00000000000000..5a7c243bf4dcd0 --- /dev/null +++ b/ext/node/polyfills/_fs/_fs_dirent.ts @@ -0,0 +1,46 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. +import { notImplemented } from "internal:deno_node/polyfills/_utils.ts"; + +export default class Dirent { + constructor(private entry: Deno.DirEntry) {} + + isBlockDevice(): boolean { + notImplemented("Deno does not yet support identification of block devices"); + return false; + } + + isCharacterDevice(): boolean { + notImplemented( + "Deno does not yet support identification of character devices", + ); + return false; + } + + isDirectory(): boolean { + return this.entry.isDirectory; + } + + isFIFO(): boolean { + notImplemented( + "Deno does not yet support identification of FIFO named pipes", + ); + return false; + } + + isFile(): boolean { + return this.entry.isFile; + } + + isSocket(): boolean { + notImplemented("Deno does not yet support identification of sockets"); + return false; + } + + isSymbolicLink(): boolean { + return this.entry.isSymlink; + } + + get name(): string | null { + return this.entry.name; + } +} diff --git a/ext/node/polyfills/_fs/_fs_exists.ts b/ext/node/polyfills/_fs/_fs_exists.ts new file mode 100644 index 00000000000000..9b0f1830348810 --- /dev/null +++ b/ext/node/polyfills/_fs/_fs_exists.ts @@ -0,0 +1,40 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. +import { fromFileUrl } from "internal:deno_node/polyfills/path.ts"; + +type ExistsCallback = (exists: boolean) => void; + +/** + * TODO: Also accept 'path' parameter as a Node polyfill Buffer type once these + * are implemented. See https://github.com/denoland/deno/issues/3403 + * Deprecated in node api + */ +export function exists(path: string | URL, callback: ExistsCallback) { + path = path instanceof URL ? fromFileUrl(path) : path; + Deno.lstat(path).then(() => callback(true), () => callback(false)); +} + +// The callback of fs.exists doesn't have standard callback signature. +// We need to provide special implementation for promisify. +// See https://github.com/nodejs/node/pull/13316 +const kCustomPromisifiedSymbol = Symbol.for("nodejs.util.promisify.custom"); +Object.defineProperty(exists, kCustomPromisifiedSymbol, { + value: (path: string | URL) => { + return new Promise((resolve) => { + exists(path, (exists) => resolve(exists)); + }); + }, +}); + +/** + * TODO: Also accept 'path' parameter as a Node polyfill Buffer or URL type once these + * are implemented. See https://github.com/denoland/deno/issues/3403 + */ +export function existsSync(path: string | URL): boolean { + path = path instanceof URL ? fromFileUrl(path) : path; + try { + Deno.lstatSync(path); + return true; + } catch (_err) { + return false; + } +} diff --git a/ext/node/polyfills/_fs/_fs_fdatasync.ts b/ext/node/polyfills/_fs/_fs_fdatasync.ts new file mode 100644 index 00000000000000..325ac30da67326 --- /dev/null +++ b/ext/node/polyfills/_fs/_fs_fdatasync.ts @@ -0,0 +1,13 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. +import { CallbackWithError } from "internal:deno_node/polyfills/_fs/_fs_common.ts"; + +export function fdatasync( + fd: number, + callback: CallbackWithError, +) { + Deno.fdatasync(fd).then(() => callback(null), callback); +} + +export function fdatasyncSync(fd: number) { + Deno.fdatasyncSync(fd); +} diff --git a/ext/node/polyfills/_fs/_fs_fstat.ts b/ext/node/polyfills/_fs/_fs_fstat.ts new file mode 100644 index 00000000000000..ab9cbead469dd7 --- /dev/null +++ b/ext/node/polyfills/_fs/_fs_fstat.ts @@ -0,0 +1,60 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. +import { + BigIntStats, + CFISBIS, + statCallback, + statCallbackBigInt, + statOptions, + Stats, +} from "internal:deno_node/polyfills/_fs/_fs_stat.ts"; + +export function fstat(fd: number, callback: statCallback): void; +export function fstat( + fd: number, + options: { bigint: false }, + callback: statCallback, +): void; +export function fstat( + fd: number, + options: { bigint: true }, + callback: statCallbackBigInt, +): void; +export function fstat( + fd: number, + optionsOrCallback: statCallback | statCallbackBigInt | statOptions, + maybeCallback?: statCallback | statCallbackBigInt, +) { + const callback = + (typeof optionsOrCallback === "function" + ? optionsOrCallback + : maybeCallback) as ( + ...args: [Error] | [null, BigIntStats | Stats] + ) => void; + const options = typeof optionsOrCallback === "object" + ? optionsOrCallback + : { bigint: false }; + + if (!callback) throw new Error("No callback function supplied"); + + Deno.fstat(fd).then( + (stat) => callback(null, CFISBIS(stat, options.bigint)), + (err) => callback(err), + ); +} + +export function fstatSync(fd: number): Stats; +export function fstatSync( + fd: number, + options: { bigint: false }, +): Stats; +export function fstatSync( + fd: number, + options: { bigint: true }, +): BigIntStats; +export function fstatSync( + fd: number, + options?: statOptions, +): Stats | BigIntStats { + const origin = Deno.fstatSync(fd); + return CFISBIS(origin, options?.bigint || false); +} diff --git a/ext/node/polyfills/_fs/_fs_fsync.ts b/ext/node/polyfills/_fs/_fs_fsync.ts new file mode 100644 index 00000000000000..02be24abcf472b --- /dev/null +++ b/ext/node/polyfills/_fs/_fs_fsync.ts @@ -0,0 +1,13 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. +import { CallbackWithError } from "internal:deno_node/polyfills/_fs/_fs_common.ts"; + +export function fsync( + fd: number, + callback: CallbackWithError, +) { + Deno.fsync(fd).then(() => callback(null), callback); +} + +export function fsyncSync(fd: number) { + Deno.fsyncSync(fd); +} diff --git a/ext/node/polyfills/_fs/_fs_ftruncate.ts b/ext/node/polyfills/_fs/_fs_ftruncate.ts new file mode 100644 index 00000000000000..9c7bfbd01623bd --- /dev/null +++ b/ext/node/polyfills/_fs/_fs_ftruncate.ts @@ -0,0 +1,23 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. +import { CallbackWithError } from "internal:deno_node/polyfills/_fs/_fs_common.ts"; + +export function ftruncate( + fd: number, + lenOrCallback: number | CallbackWithError, + maybeCallback?: CallbackWithError, +) { + const len: number | undefined = typeof lenOrCallback === "number" + ? lenOrCallback + : undefined; + const callback: CallbackWithError = typeof lenOrCallback === "function" + ? lenOrCallback + : maybeCallback as CallbackWithError; + + if (!callback) throw new Error("No callback function supplied"); + + Deno.ftruncate(fd, len).then(() => callback(null), callback); +} + +export function ftruncateSync(fd: number, len?: number) { + Deno.ftruncateSync(fd, len); +} diff --git a/ext/node/polyfills/_fs/_fs_futimes.ts b/ext/node/polyfills/_fs/_fs_futimes.ts new file mode 100644 index 00000000000000..60f06bc34ea760 --- /dev/null +++ b/ext/node/polyfills/_fs/_fs_futimes.ts @@ -0,0 +1,50 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. + +import type { CallbackWithError } from "internal:deno_node/polyfills/_fs/_fs_common.ts"; + +function getValidTime( + time: number | string | Date, + name: string, +): number | Date { + if (typeof time === "string") { + time = Number(time); + } + + if ( + typeof time === "number" && + (Number.isNaN(time) || !Number.isFinite(time)) + ) { + throw new Deno.errors.InvalidData( + `invalid ${name}, must not be infinity or NaN`, + ); + } + + return time; +} + +export function futimes( + fd: number, + atime: number | string | Date, + mtime: number | string | Date, + callback: CallbackWithError, +) { + if (!callback) { + throw new Deno.errors.InvalidData("No callback function supplied"); + } + + atime = getValidTime(atime, "atime"); + mtime = getValidTime(mtime, "mtime"); + + Deno.futime(fd, atime, mtime).then(() => callback(null), callback); +} + +export function futimesSync( + fd: number, + atime: number | string | Date, + mtime: number | string | Date, +) { + atime = getValidTime(atime, "atime"); + mtime = getValidTime(mtime, "mtime"); + + Deno.futimeSync(fd, atime, mtime); +} diff --git a/ext/node/polyfills/_fs/_fs_link.ts b/ext/node/polyfills/_fs/_fs_link.ts new file mode 100644 index 00000000000000..eb95a10f6dfb2c --- /dev/null +++ b/ext/node/polyfills/_fs/_fs_link.ts @@ -0,0 +1,46 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. +import type { CallbackWithError } from "internal:deno_node/polyfills/_fs/_fs_common.ts"; +import { fromFileUrl } from "internal:deno_node/polyfills/path.ts"; +import { promisify } from "internal:deno_node/polyfills/internal/util.mjs"; + +/** + * TODO: Also accept 'path' parameter as a Node polyfill Buffer type once these + * are implemented. See https://github.com/denoland/deno/issues/3403 + */ +export function link( + existingPath: string | URL, + newPath: string | URL, + callback: CallbackWithError, +) { + existingPath = existingPath instanceof URL + ? fromFileUrl(existingPath) + : existingPath; + newPath = newPath instanceof URL ? fromFileUrl(newPath) : newPath; + + Deno.link(existingPath, newPath).then(() => callback(null), callback); +} + +/** + * TODO: Also accept 'path' parameter as a Node polyfill Buffer type once these + * are implemented. See https://github.com/denoland/deno/issues/3403 + */ +export const linkPromise = promisify(link) as ( + existingPath: string | URL, + newPath: string | URL, +) => Promise; + +/** + * TODO: Also accept 'path' parameter as a Node polyfill Buffer type once these + * are implemented. See https://github.com/denoland/deno/issues/3403 + */ +export function linkSync( + existingPath: string | URL, + newPath: string | URL, +) { + existingPath = existingPath instanceof URL + ? fromFileUrl(existingPath) + : existingPath; + newPath = newPath instanceof URL ? fromFileUrl(newPath) : newPath; + + Deno.linkSync(existingPath, newPath); +} diff --git a/ext/node/polyfills/_fs/_fs_lstat.ts b/ext/node/polyfills/_fs/_fs_lstat.ts new file mode 100644 index 00000000000000..c85f82a11b3743 --- /dev/null +++ b/ext/node/polyfills/_fs/_fs_lstat.ts @@ -0,0 +1,67 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. +import { + BigIntStats, + CFISBIS, + statCallback, + statCallbackBigInt, + statOptions, + Stats, +} from "internal:deno_node/polyfills/_fs/_fs_stat.ts"; +import { promisify } from "internal:deno_node/polyfills/internal/util.mjs"; + +export function lstat(path: string | URL, callback: statCallback): void; +export function lstat( + path: string | URL, + options: { bigint: false }, + callback: statCallback, +): void; +export function lstat( + path: string | URL, + options: { bigint: true }, + callback: statCallbackBigInt, +): void; +export function lstat( + path: string | URL, + optionsOrCallback: statCallback | statCallbackBigInt | statOptions, + maybeCallback?: statCallback | statCallbackBigInt, +) { + const callback = + (typeof optionsOrCallback === "function" + ? optionsOrCallback + : maybeCallback) as ( + ...args: [Error] | [null, BigIntStats | Stats] + ) => void; + const options = typeof optionsOrCallback === "object" + ? optionsOrCallback + : { bigint: false }; + + if (!callback) throw new Error("No callback function supplied"); + + Deno.lstat(path).then( + (stat) => callback(null, CFISBIS(stat, options.bigint)), + (err) => callback(err), + ); +} + +export const lstatPromise = promisify(lstat) as ( + & ((path: string | URL) => Promise) + & ((path: string | URL, options: { bigint: false }) => Promise) + & ((path: string | URL, options: { bigint: true }) => Promise) +); + +export function lstatSync(path: string | URL): Stats; +export function lstatSync( + path: string | URL, + options: { bigint: false }, +): Stats; +export function lstatSync( + path: string | URL, + options: { bigint: true }, +): BigIntStats; +export function lstatSync( + path: string | URL, + options?: statOptions, +): Stats | BigIntStats { + const origin = Deno.lstatSync(path); + return CFISBIS(origin, options?.bigint || false); +} diff --git a/ext/node/polyfills/_fs/_fs_mkdir.ts b/ext/node/polyfills/_fs/_fs_mkdir.ts new file mode 100644 index 00000000000000..ac4b78259189a9 --- /dev/null +++ b/ext/node/polyfills/_fs/_fs_mkdir.ts @@ -0,0 +1,77 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. +import type { CallbackWithError } from "internal:deno_node/polyfills/_fs/_fs_common.ts"; +import { promisify } from "internal:deno_node/polyfills/internal/util.mjs"; +import { denoErrorToNodeError } from "internal:deno_node/polyfills/internal/errors.ts"; +import { getValidatedPath } from "internal:deno_node/polyfills/internal/fs/utils.mjs"; +import { validateBoolean } from "internal:deno_node/polyfills/internal/validators.mjs"; + +/** + * TODO: Also accept 'path' parameter as a Node polyfill Buffer type once these + * are implemented. See https://github.com/denoland/deno/issues/3403 + */ +type MkdirOptions = + | { recursive?: boolean; mode?: number | undefined } + | number + | boolean; + +export function mkdir( + path: string | URL, + options?: MkdirOptions | CallbackWithError, + callback?: CallbackWithError, +) { + path = getValidatedPath(path) as string; + + let mode = 0o777; + let recursive = false; + + if (typeof options == "function") { + callback = options; + } else if (typeof options === "number") { + mode = options; + } else if (typeof options === "boolean") { + recursive = options; + } else if (options) { + if (options.recursive !== undefined) recursive = options.recursive; + if (options.mode !== undefined) mode = options.mode; + } + validateBoolean(recursive, "options.recursive"); + + Deno.mkdir(path, { recursive, mode }) + .then(() => { + if (typeof callback === "function") { + callback(null); + } + }, (err) => { + if (typeof callback === "function") { + callback(err); + } + }); +} + +export const mkdirPromise = promisify(mkdir) as ( + path: string | URL, + options?: MkdirOptions, +) => Promise; + +export function mkdirSync(path: string | URL, options?: MkdirOptions) { + path = getValidatedPath(path) as string; + + let mode = 0o777; + let recursive = false; + + if (typeof options === "number") { + mode = options; + } else if (typeof options === "boolean") { + recursive = options; + } else if (options) { + if (options.recursive !== undefined) recursive = options.recursive; + if (options.mode !== undefined) mode = options.mode; + } + validateBoolean(recursive, "options.recursive"); + + try { + Deno.mkdirSync(path, { recursive, mode }); + } catch (err) { + throw denoErrorToNodeError(err as Error, { syscall: "mkdir", path }); + } +} diff --git a/ext/node/polyfills/_fs/_fs_mkdtemp.ts b/ext/node/polyfills/_fs/_fs_mkdtemp.ts new file mode 100644 index 00000000000000..de227b216e1ac7 --- /dev/null +++ b/ext/node/polyfills/_fs/_fs_mkdtemp.ts @@ -0,0 +1,115 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. +// Copyright Node.js contributors. All rights reserved. MIT License. + +import { + TextDecoder, + TextEncoder, +} from "internal:deno_web/08_text_encoding.js"; +import { existsSync } from "internal:deno_node/polyfills/_fs/_fs_exists.ts"; +import { + mkdir, + mkdirSync, +} from "internal:deno_node/polyfills/_fs/_fs_mkdir.ts"; +import { + ERR_INVALID_ARG_TYPE, + ERR_INVALID_OPT_VALUE_ENCODING, +} from "internal:deno_node/polyfills/internal/errors.ts"; +import { promisify } from "internal:deno_node/polyfills/internal/util.mjs"; + +export type mkdtempCallback = ( + err: Error | null, + directory?: string, +) => void; + +// https://nodejs.org/dist/latest-v15.x/docs/api/fs.html#fs_fs_mkdtemp_prefix_options_callback +export function mkdtemp(prefix: string, callback: mkdtempCallback): void; +export function mkdtemp( + prefix: string, + options: { encoding: string } | string, + callback: mkdtempCallback, +): void; +export function mkdtemp( + prefix: string, + optionsOrCallback: { encoding: string } | string | mkdtempCallback, + maybeCallback?: mkdtempCallback, +) { + const callback: mkdtempCallback | undefined = + typeof optionsOrCallback == "function" ? optionsOrCallback : maybeCallback; + if (!callback) { + throw new ERR_INVALID_ARG_TYPE("callback", "function", callback); + } + + const encoding: string | undefined = parseEncoding(optionsOrCallback); + const path = tempDirPath(prefix); + + mkdir( + path, + { recursive: false, mode: 0o700 }, + (err: Error | null | undefined) => { + if (err) callback(err); + else callback(null, decode(path, encoding)); + }, + ); +} + +export const mkdtempPromise = promisify(mkdtemp) as ( + prefix: string, + options?: { encoding: string } | string, +) => Promise; + +// https://nodejs.org/dist/latest-v15.x/docs/api/fs.html#fs_fs_mkdtempsync_prefix_options +export function mkdtempSync( + prefix: string, + options?: { encoding: string } | string, +): string { + const encoding: string | undefined = parseEncoding(options); + const path = tempDirPath(prefix); + + mkdirSync(path, { recursive: false, mode: 0o700 }); + return decode(path, encoding); +} + +function parseEncoding( + optionsOrCallback?: { encoding: string } | string | mkdtempCallback, +): string | undefined { + let encoding: string | undefined; + if (typeof optionsOrCallback == "function") encoding = undefined; + else if (optionsOrCallback instanceof Object) { + encoding = optionsOrCallback?.encoding; + } else encoding = optionsOrCallback; + + if (encoding) { + try { + new TextDecoder(encoding); + } catch { + throw new ERR_INVALID_OPT_VALUE_ENCODING(encoding); + } + } + + return encoding; +} + +function decode(str: string, encoding?: string): string { + if (!encoding) return str; + else { + const decoder = new TextDecoder(encoding); + const encoder = new TextEncoder(); + return decoder.decode(encoder.encode(str)); + } +} + +const CHARS = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"; +function randomName(): string { + return [...Array(6)].map(() => + CHARS[Math.floor(Math.random() * CHARS.length)] + ).join(""); +} + +function tempDirPath(prefix: string): string { + let path: string; + do { + path = prefix + randomName(); + } while (existsSync(path)); + + return path; +} diff --git a/ext/node/polyfills/_fs/_fs_open.ts b/ext/node/polyfills/_fs/_fs_open.ts new file mode 100644 index 00000000000000..e703da56fd2016 --- /dev/null +++ b/ext/node/polyfills/_fs/_fs_open.ts @@ -0,0 +1,198 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. +import { + O_APPEND, + O_CREAT, + O_EXCL, + O_RDWR, + O_TRUNC, + O_WRONLY, +} from "internal:deno_node/polyfills/_fs/_fs_constants.ts"; +import { getOpenOptions } from "internal:deno_node/polyfills/_fs/_fs_common.ts"; +import { promisify } from "internal:deno_node/polyfills/internal/util.mjs"; +import { parseFileMode } from "internal:deno_node/polyfills/internal/validators.mjs"; +import { ERR_INVALID_ARG_TYPE } from "internal:deno_node/polyfills/internal/errors.ts"; +import { getValidatedPath } from "internal:deno_node/polyfills/internal/fs/utils.mjs"; +import type { Buffer } from "internal:deno_node/polyfills/buffer.ts"; + +function existsSync(filePath: string | URL): boolean { + try { + Deno.lstatSync(filePath); + return true; + } catch (error) { + if (error instanceof Deno.errors.NotFound) { + return false; + } + throw error; + } +} + +const FLAGS_AX = O_APPEND | O_CREAT | O_WRONLY | O_EXCL; +const FLAGS_AX_PLUS = O_APPEND | O_CREAT | O_RDWR | O_EXCL; +const FLAGS_WX = O_TRUNC | O_CREAT | O_WRONLY | O_EXCL; +const FLAGS_WX_PLUS = O_TRUNC | O_CREAT | O_RDWR | O_EXCL; + +export type openFlags = + | "a" + | "ax" + | "a+" + | "ax+" + | "as" + | "as+" + | "r" + | "r+" + | "rs+" + | "w" + | "wx" + | "w+" + | "wx+" + | number; + +type openCallback = (err: Error | null, fd: number) => void; + +function convertFlagAndModeToOptions( + flag?: openFlags, + mode?: number, +): Deno.OpenOptions | undefined { + if (!flag && !mode) return undefined; + if (!flag && mode) return { mode }; + return { ...getOpenOptions(flag), mode }; +} + +export function open(path: string | Buffer | URL, callback: openCallback): void; +export function open( + path: string | Buffer | URL, + flags: openFlags, + callback: openCallback, +): void; +export function open( + path: string | Buffer | URL, + flags: openFlags, + mode: number, + callback: openCallback, +): void; +export function open( + path: string | Buffer | URL, + flags: openCallback | openFlags, + mode?: openCallback | number, + callback?: openCallback, +) { + if (flags === undefined) { + throw new ERR_INVALID_ARG_TYPE( + "flags or callback", + ["string", "function"], + flags, + ); + } + path = getValidatedPath(path); + if (arguments.length < 3) { + // deno-lint-ignore no-explicit-any + callback = flags as any; + flags = "r"; + mode = 0o666; + } else if (typeof mode === "function") { + callback = mode; + mode = 0o666; + } else { + mode = parseFileMode(mode, "mode", 0o666); + } + + if (typeof callback !== "function") { + throw new ERR_INVALID_ARG_TYPE( + "callback", + "function", + callback, + ); + } + + if (flags === undefined) { + flags = "r"; + } + + if ( + existenceCheckRequired(flags as openFlags) && + existsSync(path as string) + ) { + const err = new Error(`EEXIST: file already exists, open '${path}'`); + (callback as (err: Error) => void)(err); + } else { + if (flags === "as" || flags === "as+") { + let err: Error | null = null, res: number; + try { + res = openSync(path, flags, mode); + } catch (error) { + err = error instanceof Error ? error : new Error("[non-error thrown]"); + } + if (err) { + (callback as (err: Error) => void)(err); + } else { + callback(null, res!); + } + return; + } + Deno.open( + path as string, + convertFlagAndModeToOptions(flags as openFlags, mode), + ).then( + (file) => callback!(null, file.rid), + (err) => (callback as (err: Error) => void)(err), + ); + } +} + +export const openPromise = promisify(open) as ( + & ((path: string | Buffer | URL) => Promise) + & ((path: string | Buffer | URL, flags: openFlags) => Promise) + & ((path: string | Buffer | URL, mode?: number) => Promise) + & (( + path: string | Buffer | URL, + flags?: openFlags, + mode?: number, + ) => Promise) +); + +export function openSync(path: string | Buffer | URL): number; +export function openSync( + path: string | Buffer | URL, + flags?: openFlags, +): number; +export function openSync(path: string | Buffer | URL, mode?: number): number; +export function openSync( + path: string | Buffer | URL, + flags?: openFlags, + mode?: number, +): number; +export function openSync( + path: string | Buffer | URL, + flags?: openFlags, + maybeMode?: number, +) { + const mode = parseFileMode(maybeMode, "mode", 0o666); + path = getValidatedPath(path); + + if (flags === undefined) { + flags = "r"; + } + + if ( + existenceCheckRequired(flags) && + existsSync(path as string) + ) { + throw new Error(`EEXIST: file already exists, open '${path}'`); + } + + return Deno.openSync(path as string, convertFlagAndModeToOptions(flags, mode)) + .rid; +} + +function existenceCheckRequired(flags: openFlags | number) { + return ( + (typeof flags === "string" && + ["ax", "ax+", "wx", "wx+"].includes(flags)) || + (typeof flags === "number" && ( + ((flags & FLAGS_AX) === FLAGS_AX) || + ((flags & FLAGS_AX_PLUS) === FLAGS_AX_PLUS) || + ((flags & FLAGS_WX) === FLAGS_WX) || + ((flags & FLAGS_WX_PLUS) === FLAGS_WX_PLUS) + )) + ); +} diff --git a/ext/node/polyfills/_fs/_fs_opendir.ts b/ext/node/polyfills/_fs/_fs_opendir.ts new file mode 100644 index 00000000000000..5ee13f9519a81d --- /dev/null +++ b/ext/node/polyfills/_fs/_fs_opendir.ts @@ -0,0 +1,89 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. + +import Dir from "internal:deno_node/polyfills/_fs/_fs_dir.ts"; +import { Buffer } from "internal:deno_node/polyfills/buffer.ts"; +import { + getOptions, + getValidatedPath, +} from "internal:deno_node/polyfills/internal/fs/utils.mjs"; +import { denoErrorToNodeError } from "internal:deno_node/polyfills/internal/errors.ts"; +import { + validateFunction, + validateInteger, +} from "internal:deno_node/polyfills/internal/validators.mjs"; +import { promisify } from "internal:deno_node/polyfills/internal/util.mjs"; + +/** These options aren't funcitonally used right now, as `Dir` doesn't yet support them. + * However, these values are still validated. + */ +type Options = { + encoding?: string; + bufferSize?: number; +}; +type Callback = (err?: Error | null, dir?: Dir) => void; + +function _validateFunction(callback: unknown): asserts callback is Callback { + validateFunction(callback, "callback"); +} + +/** @link https://nodejs.org/api/fs.html#fsopendirsyncpath-options */ +export function opendir( + path: string | Buffer | URL, + options: Options | Callback, + callback?: Callback, +) { + callback = typeof options === "function" ? options : callback; + _validateFunction(callback); + + path = getValidatedPath(path).toString(); + + let err, dir; + try { + const { bufferSize } = getOptions(options, { + encoding: "utf8", + bufferSize: 32, + }); + validateInteger(bufferSize, "options.bufferSize", 1, 4294967295); + + /** Throws if path is invalid */ + Deno.readDirSync(path); + + dir = new Dir(path); + } catch (error) { + err = denoErrorToNodeError(error as Error, { syscall: "opendir" }); + } + if (err) { + callback(err); + } else { + callback(null, dir); + } +} + +/** @link https://nodejs.org/api/fs.html#fspromisesopendirpath-options */ +export const opendirPromise = promisify(opendir) as ( + path: string | Buffer | URL, + options?: Options, +) => Promise

; + +export function opendirSync( + path: string | Buffer | URL, + options?: Options, +): Dir { + path = getValidatedPath(path).toString(); + + const { bufferSize } = getOptions(options, { + encoding: "utf8", + bufferSize: 32, + }); + + validateInteger(bufferSize, "options.bufferSize", 1, 4294967295); + + try { + /** Throws if path is invalid */ + Deno.readDirSync(path); + + return new Dir(path); + } catch (err) { + throw denoErrorToNodeError(err as Error, { syscall: "opendir" }); + } +} diff --git a/ext/node/polyfills/_fs/_fs_read.ts b/ext/node/polyfills/_fs/_fs_read.ts new file mode 100644 index 00000000000000..d74445829859de --- /dev/null +++ b/ext/node/polyfills/_fs/_fs_read.ts @@ -0,0 +1,197 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. +import { Buffer } from "internal:deno_node/polyfills/buffer.ts"; +import { ERR_INVALID_ARG_TYPE } from "internal:deno_node/polyfills/internal/errors.ts"; +import { + validateOffsetLengthRead, + validatePosition, +} from "internal:deno_node/polyfills/internal/fs/utils.mjs"; +import { + validateBuffer, + validateInteger, +} from "internal:deno_node/polyfills/internal/validators.mjs"; + +type readOptions = { + buffer: Buffer | Uint8Array; + offset: number; + length: number; + position: number | null; +}; + +type readSyncOptions = { + offset: number; + length: number; + position: number | null; +}; + +type BinaryCallback = ( + err: Error | null, + bytesRead: number | null, + data?: Buffer, +) => void; +type Callback = BinaryCallback; + +export function read(fd: number, callback: Callback): void; +export function read( + fd: number, + options: readOptions, + callback: Callback, +): void; +export function read( + fd: number, + buffer: Buffer | Uint8Array, + offset: number, + length: number, + position: number | null, + callback: Callback, +): void; +export function read( + fd: number, + optOrBufferOrCb?: Buffer | Uint8Array | readOptions | Callback, + offsetOrCallback?: number | Callback, + length?: number, + position?: number | null, + callback?: Callback, +) { + let cb: Callback | undefined; + let offset = 0, + buffer: Buffer | Uint8Array; + + if (typeof fd !== "number") { + throw new ERR_INVALID_ARG_TYPE("fd", "number", fd); + } + + if (length == null) { + length = 0; + } + + if (typeof offsetOrCallback === "function") { + cb = offsetOrCallback; + } else if (typeof optOrBufferOrCb === "function") { + cb = optOrBufferOrCb; + } else { + offset = offsetOrCallback as number; + validateInteger(offset, "offset", 0); + cb = callback; + } + + if ( + optOrBufferOrCb instanceof Buffer || optOrBufferOrCb instanceof Uint8Array + ) { + buffer = optOrBufferOrCb; + } else if (typeof optOrBufferOrCb === "function") { + offset = 0; + buffer = Buffer.alloc(16384); + length = buffer.byteLength; + position = null; + } else { + const opt = optOrBufferOrCb as readOptions; + if ( + !(opt.buffer instanceof Buffer) && !(opt.buffer instanceof Uint8Array) + ) { + if (opt.buffer === null) { + // @ts-ignore: Intentionally create TypeError for passing test-fs-read.js#L87 + length = opt.buffer.byteLength; + } + throw new ERR_INVALID_ARG_TYPE("buffer", [ + "Buffer", + "TypedArray", + "DataView", + ], optOrBufferOrCb); + } + offset = opt.offset ?? 0; + buffer = opt.buffer ?? Buffer.alloc(16384); + length = opt.length ?? buffer.byteLength; + position = opt.position ?? null; + } + + if (position == null) { + position = -1; + } + + validatePosition(position); + validateOffsetLengthRead(offset, length, buffer.byteLength); + + if (!cb) throw new ERR_INVALID_ARG_TYPE("cb", "Callback", cb); + + (async () => { + try { + let nread: number | null; + if (typeof position === "number" && position >= 0) { + const currentPosition = await Deno.seek(fd, 0, Deno.SeekMode.Current); + // We use sync calls below to avoid being affected by others during + // these calls. + Deno.seekSync(fd, position, Deno.SeekMode.Start); + nread = Deno.readSync(fd, buffer); + Deno.seekSync(fd, currentPosition, Deno.SeekMode.Start); + } else { + nread = await Deno.read(fd, buffer); + } + cb(null, nread ?? 0, Buffer.from(buffer.buffer, offset, length)); + } catch (error) { + cb(error as Error, null); + } + })(); +} + +export function readSync( + fd: number, + buffer: Buffer | Uint8Array, + offset: number, + length: number, + position: number | null, +): number; +export function readSync( + fd: number, + buffer: Buffer | Uint8Array, + opt: readSyncOptions, +): number; +export function readSync( + fd: number, + buffer: Buffer | Uint8Array, + offsetOrOpt?: number | readSyncOptions, + length?: number, + position?: number | null, +): number { + let offset = 0; + + if (typeof fd !== "number") { + throw new ERR_INVALID_ARG_TYPE("fd", "number", fd); + } + + validateBuffer(buffer); + + if (length == null) { + length = 0; + } + + if (typeof offsetOrOpt === "number") { + offset = offsetOrOpt; + validateInteger(offset, "offset", 0); + } else { + const opt = offsetOrOpt as readSyncOptions; + offset = opt.offset ?? 0; + length = opt.length ?? buffer.byteLength; + position = opt.position ?? null; + } + + if (position == null) { + position = -1; + } + + validatePosition(position); + validateOffsetLengthRead(offset, length, buffer.byteLength); + + let currentPosition = 0; + if (typeof position === "number" && position >= 0) { + currentPosition = Deno.seekSync(fd, 0, Deno.SeekMode.Current); + Deno.seekSync(fd, position, Deno.SeekMode.Start); + } + + const numberOfBytesRead = Deno.readSync(fd, buffer); + + if (typeof position === "number" && position >= 0) { + Deno.seekSync(fd, currentPosition, Deno.SeekMode.Start); + } + + return numberOfBytesRead ?? 0; +} diff --git a/ext/node/polyfills/_fs/_fs_readFile.ts b/ext/node/polyfills/_fs/_fs_readFile.ts new file mode 100644 index 00000000000000..6c5e9fb8bc18eb --- /dev/null +++ b/ext/node/polyfills/_fs/_fs_readFile.ts @@ -0,0 +1,108 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. +import { + BinaryOptionsArgument, + FileOptionsArgument, + getEncoding, + TextOptionsArgument, +} from "internal:deno_node/polyfills/_fs/_fs_common.ts"; +import { Buffer } from "internal:deno_node/polyfills/buffer.ts"; +import { fromFileUrl } from "internal:deno_node/polyfills/path.ts"; +import { + BinaryEncodings, + Encodings, + TextEncodings, +} from "internal:deno_node/polyfills/_utils.ts"; +import { promisify } from "internal:deno_node/polyfills/internal/util.mjs"; + +function maybeDecode(data: Uint8Array, encoding: TextEncodings): string; +function maybeDecode( + data: Uint8Array, + encoding: BinaryEncodings | null, +): Buffer; +function maybeDecode( + data: Uint8Array, + encoding: Encodings | null, +): string | Buffer { + const buffer = Buffer.from(data.buffer, data.byteOffset, data.byteLength); + if (encoding && encoding !== "binary") return buffer.toString(encoding); + return buffer; +} + +type TextCallback = (err: Error | null, data?: string) => void; +type BinaryCallback = (err: Error | null, data?: Buffer) => void; +type GenericCallback = (err: Error | null, data?: string | Buffer) => void; +type Callback = TextCallback | BinaryCallback | GenericCallback; + +export function readFile( + path: string | URL, + options: TextOptionsArgument, + callback: TextCallback, +): void; +export function readFile( + path: string | URL, + options: BinaryOptionsArgument, + callback: BinaryCallback, +): void; +export function readFile( + path: string | URL, + options: null | undefined | FileOptionsArgument, + callback: BinaryCallback, +): void; +export function readFile(path: string | URL, callback: BinaryCallback): void; +export function readFile( + path: string | URL, + optOrCallback?: FileOptionsArgument | Callback | null | undefined, + callback?: Callback, +) { + path = path instanceof URL ? fromFileUrl(path) : path; + let cb: Callback | undefined; + if (typeof optOrCallback === "function") { + cb = optOrCallback; + } else { + cb = callback; + } + + const encoding = getEncoding(optOrCallback); + + const p = Deno.readFile(path); + + if (cb) { + p.then((data: Uint8Array) => { + if (encoding && encoding !== "binary") { + const text = maybeDecode(data, encoding); + return (cb as TextCallback)(null, text); + } + const buffer = maybeDecode(data, encoding); + (cb as BinaryCallback)(null, buffer); + }, (err) => cb && cb(err)); + } +} + +export const readFilePromise = promisify(readFile) as ( + & ((path: string | URL, opt: TextOptionsArgument) => Promise) + & ((path: string | URL, opt?: BinaryOptionsArgument) => Promise) + & ((path: string | URL, opt?: FileOptionsArgument) => Promise) +); + +export function readFileSync( + path: string | URL, + opt: TextOptionsArgument, +): string; +export function readFileSync( + path: string | URL, + opt?: BinaryOptionsArgument, +): Buffer; +export function readFileSync( + path: string | URL, + opt?: FileOptionsArgument, +): string | Buffer { + path = path instanceof URL ? fromFileUrl(path) : path; + const data = Deno.readFileSync(path); + const encoding = getEncoding(opt); + if (encoding && encoding !== "binary") { + const text = maybeDecode(data, encoding); + return text; + } + const buffer = maybeDecode(data, encoding); + return buffer; +} diff --git a/ext/node/polyfills/_fs/_fs_readdir.ts b/ext/node/polyfills/_fs/_fs_readdir.ts new file mode 100644 index 00000000000000..f6cfae4f73c135 --- /dev/null +++ b/ext/node/polyfills/_fs/_fs_readdir.ts @@ -0,0 +1,142 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. + +import { + TextDecoder, + TextEncoder, +} from "internal:deno_web/08_text_encoding.js"; +import { asyncIterableToCallback } from "internal:deno_node/polyfills/_fs/_fs_watch.ts"; +import Dirent from "internal:deno_node/polyfills/_fs/_fs_dirent.ts"; +import { denoErrorToNodeError } from "internal:deno_node/polyfills/internal/errors.ts"; +import { getValidatedPath } from "internal:deno_node/polyfills/internal/fs/utils.mjs"; +import { Buffer } from "internal:deno_node/polyfills/buffer.ts"; +import { promisify } from "internal:deno_node/polyfills/internal/util.mjs"; + +function toDirent(val: Deno.DirEntry): Dirent { + return new Dirent(val); +} + +type readDirOptions = { + encoding?: string; + withFileTypes?: boolean; +}; + +type readDirCallback = (err: Error | null, files: string[]) => void; + +type readDirCallbackDirent = (err: Error | null, files: Dirent[]) => void; + +type readDirBoth = ( + ...args: [Error] | [null, string[] | Dirent[] | Array] +) => void; + +export function readdir( + path: string | Buffer | URL, + options: { withFileTypes?: false; encoding?: string }, + callback: readDirCallback, +): void; +export function readdir( + path: string | Buffer | URL, + options: { withFileTypes: true; encoding?: string }, + callback: readDirCallbackDirent, +): void; +export function readdir(path: string | URL, callback: readDirCallback): void; +export function readdir( + path: string | Buffer | URL, + optionsOrCallback: readDirOptions | readDirCallback | readDirCallbackDirent, + maybeCallback?: readDirCallback | readDirCallbackDirent, +) { + const callback = + (typeof optionsOrCallback === "function" + ? optionsOrCallback + : maybeCallback) as readDirBoth | undefined; + const options = typeof optionsOrCallback === "object" + ? optionsOrCallback + : null; + const result: Array = []; + path = getValidatedPath(path); + + if (!callback) throw new Error("No callback function supplied"); + + if (options?.encoding) { + try { + new TextDecoder(options.encoding); + } catch { + throw new Error( + `TypeError [ERR_INVALID_OPT_VALUE_ENCODING]: The value "${options.encoding}" is invalid for option "encoding"`, + ); + } + } + + try { + asyncIterableToCallback(Deno.readDir(path.toString()), (val, done) => { + if (typeof path !== "string") return; + if (done) { + callback(null, result); + return; + } + if (options?.withFileTypes) { + result.push(toDirent(val)); + } else result.push(decode(val.name)); + }, (e) => { + callback(denoErrorToNodeError(e as Error, { syscall: "readdir" })); + }); + } catch (e) { + callback(denoErrorToNodeError(e as Error, { syscall: "readdir" })); + } +} + +function decode(str: string, encoding?: string): string { + if (!encoding) return str; + else { + const decoder = new TextDecoder(encoding); + const encoder = new TextEncoder(); + return decoder.decode(encoder.encode(str)); + } +} + +export const readdirPromise = promisify(readdir) as ( + & ((path: string | Buffer | URL, options: { + withFileTypes: true; + encoding?: string; + }) => Promise) + & ((path: string | Buffer | URL, options?: { + withFileTypes?: false; + encoding?: string; + }) => Promise) +); + +export function readdirSync( + path: string | Buffer | URL, + options: { withFileTypes: true; encoding?: string }, +): Dirent[]; +export function readdirSync( + path: string | Buffer | URL, + options?: { withFileTypes?: false; encoding?: string }, +): string[]; +export function readdirSync( + path: string | Buffer | URL, + options?: readDirOptions, +): Array { + const result = []; + path = getValidatedPath(path); + + if (options?.encoding) { + try { + new TextDecoder(options.encoding); + } catch { + throw new Error( + `TypeError [ERR_INVALID_OPT_VALUE_ENCODING]: The value "${options.encoding}" is invalid for option "encoding"`, + ); + } + } + + try { + for (const file of Deno.readDirSync(path.toString())) { + if (options?.withFileTypes) { + result.push(toDirent(file)); + } else result.push(decode(file.name)); + } + } catch (e) { + throw denoErrorToNodeError(e as Error, { syscall: "readdir" }); + } + return result; +} diff --git a/ext/node/polyfills/_fs/_fs_readlink.ts b/ext/node/polyfills/_fs/_fs_readlink.ts new file mode 100644 index 00000000000000..07d1b6f6f15ab2 --- /dev/null +++ b/ext/node/polyfills/_fs/_fs_readlink.ts @@ -0,0 +1,89 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. + +import { TextEncoder } from "internal:deno_web/08_text_encoding.js"; +import { + intoCallbackAPIWithIntercept, + MaybeEmpty, + notImplemented, +} from "internal:deno_node/polyfills/_utils.ts"; +import { fromFileUrl } from "internal:deno_node/polyfills/path.ts"; +import { promisify } from "internal:deno_node/polyfills/internal/util.mjs"; + +type ReadlinkCallback = ( + err: MaybeEmpty, + linkString: MaybeEmpty, +) => void; + +interface ReadlinkOptions { + encoding?: string | null; +} + +function maybeEncode( + data: string, + encoding: string | null, +): string | Uint8Array { + if (encoding === "buffer") { + return new TextEncoder().encode(data); + } + return data; +} + +function getEncoding( + optOrCallback?: ReadlinkOptions | ReadlinkCallback, +): string | null { + if (!optOrCallback || typeof optOrCallback === "function") { + return null; + } else { + if (optOrCallback.encoding) { + if ( + optOrCallback.encoding === "utf8" || + optOrCallback.encoding === "utf-8" + ) { + return "utf8"; + } else if (optOrCallback.encoding === "buffer") { + return "buffer"; + } else { + notImplemented(`fs.readlink encoding=${optOrCallback.encoding}`); + } + } + return null; + } +} + +export function readlink( + path: string | URL, + optOrCallback: ReadlinkCallback | ReadlinkOptions, + callback?: ReadlinkCallback, +) { + path = path instanceof URL ? fromFileUrl(path) : path; + + let cb: ReadlinkCallback | undefined; + if (typeof optOrCallback === "function") { + cb = optOrCallback; + } else { + cb = callback; + } + + const encoding = getEncoding(optOrCallback); + + intoCallbackAPIWithIntercept( + Deno.readLink, + (data: string): string | Uint8Array => maybeEncode(data, encoding), + cb, + path, + ); +} + +export const readlinkPromise = promisify(readlink) as ( + path: string | URL, + opt?: ReadlinkOptions, +) => Promise; + +export function readlinkSync( + path: string | URL, + opt?: ReadlinkOptions, +): string | Uint8Array { + path = path instanceof URL ? fromFileUrl(path) : path; + + return maybeEncode(Deno.readLinkSync(path), getEncoding(opt)); +} diff --git a/ext/node/polyfills/_fs/_fs_realpath.ts b/ext/node/polyfills/_fs/_fs_realpath.ts new file mode 100644 index 00000000000000..5892b2c0f4bd06 --- /dev/null +++ b/ext/node/polyfills/_fs/_fs_realpath.ts @@ -0,0 +1,35 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. +import { promisify } from "internal:deno_node/polyfills/internal/util.mjs"; + +type Options = { encoding: string }; +type Callback = (err: Error | null, path?: string) => void; + +export function realpath( + path: string, + options?: Options | Callback, + callback?: Callback, +) { + if (typeof options === "function") { + callback = options; + } + if (!callback) { + throw new Error("No callback function supplied"); + } + Deno.realPath(path).then( + (path) => callback!(null, path), + (err) => callback!(err), + ); +} + +realpath.native = realpath; + +export const realpathPromise = promisify(realpath) as ( + path: string, + options?: Options, +) => Promise; + +export function realpathSync(path: string): string { + return Deno.realPathSync(path); +} + +realpathSync.native = realpathSync; diff --git a/ext/node/polyfills/_fs/_fs_rename.ts b/ext/node/polyfills/_fs/_fs_rename.ts new file mode 100644 index 00000000000000..3f8b5bd7e3a97c --- /dev/null +++ b/ext/node/polyfills/_fs/_fs_rename.ts @@ -0,0 +1,28 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. +import { fromFileUrl } from "internal:deno_node/polyfills/path.ts"; +import { promisify } from "internal:deno_node/polyfills/internal/util.mjs"; + +export function rename( + oldPath: string | URL, + newPath: string | URL, + callback: (err?: Error) => void, +) { + oldPath = oldPath instanceof URL ? fromFileUrl(oldPath) : oldPath; + newPath = newPath instanceof URL ? fromFileUrl(newPath) : newPath; + + if (!callback) throw new Error("No callback function supplied"); + + Deno.rename(oldPath, newPath).then((_) => callback(), callback); +} + +export const renamePromise = promisify(rename) as ( + oldPath: string | URL, + newPath: string | URL, +) => Promise; + +export function renameSync(oldPath: string | URL, newPath: string | URL) { + oldPath = oldPath instanceof URL ? fromFileUrl(oldPath) : oldPath; + newPath = newPath instanceof URL ? fromFileUrl(newPath) : newPath; + + Deno.renameSync(oldPath, newPath); +} diff --git a/ext/node/polyfills/_fs/_fs_rm.ts b/ext/node/polyfills/_fs/_fs_rm.ts new file mode 100644 index 00000000000000..80ba0b5f8d175a --- /dev/null +++ b/ext/node/polyfills/_fs/_fs_rm.ts @@ -0,0 +1,81 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. +import { + validateRmOptions, + validateRmOptionsSync, +} from "internal:deno_node/polyfills/internal/fs/utils.mjs"; +import { denoErrorToNodeError } from "internal:deno_node/polyfills/internal/errors.ts"; +import { promisify } from "internal:deno_node/polyfills/internal/util.mjs"; + +type rmOptions = { + force?: boolean; + maxRetries?: number; + recursive?: boolean; + retryDelay?: number; +}; + +type rmCallback = (err: Error | null) => void; + +export function rm(path: string | URL, callback: rmCallback): void; +export function rm( + path: string | URL, + options: rmOptions, + callback: rmCallback, +): void; +export function rm( + path: string | URL, + optionsOrCallback: rmOptions | rmCallback, + maybeCallback?: rmCallback, +) { + const callback = typeof optionsOrCallback === "function" + ? optionsOrCallback + : maybeCallback; + const options = typeof optionsOrCallback === "object" + ? optionsOrCallback + : undefined; + + if (!callback) throw new Error("No callback function supplied"); + + validateRmOptions( + path, + options, + false, + (err: Error | null, options: rmOptions) => { + if (err) { + return callback(err); + } + Deno.remove(path, { recursive: options?.recursive }) + .then((_) => callback(null), (err: unknown) => { + if (options?.force && err instanceof Deno.errors.NotFound) { + callback(null); + } else { + callback( + err instanceof Error + ? denoErrorToNodeError(err, { syscall: "rm" }) + : err, + ); + } + }); + }, + ); +} + +export const rmPromise = promisify(rm) as ( + path: string | URL, + options?: rmOptions, +) => Promise; + +export function rmSync(path: string | URL, options?: rmOptions) { + options = validateRmOptionsSync(path, options, false); + try { + Deno.removeSync(path, { recursive: options?.recursive }); + } catch (err: unknown) { + if (options?.force && err instanceof Deno.errors.NotFound) { + return; + } + if (err instanceof Error) { + throw denoErrorToNodeError(err, { syscall: "stat" }); + } else { + throw err; + } + } +} diff --git a/ext/node/polyfills/_fs/_fs_rmdir.ts b/ext/node/polyfills/_fs/_fs_rmdir.ts new file mode 100644 index 00000000000000..ba753a74323865 --- /dev/null +++ b/ext/node/polyfills/_fs/_fs_rmdir.ts @@ -0,0 +1,108 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. +import { + emitRecursiveRmdirWarning, + getValidatedPath, + validateRmdirOptions, + validateRmOptions, + validateRmOptionsSync, +} from "internal:deno_node/polyfills/internal/fs/utils.mjs"; +import { toNamespacedPath } from "internal:deno_node/polyfills/path.ts"; +import { + denoErrorToNodeError, + ERR_FS_RMDIR_ENOTDIR, +} from "internal:deno_node/polyfills/internal/errors.ts"; +import { Buffer } from "internal:deno_node/polyfills/buffer.ts"; +import { promisify } from "internal:deno_node/polyfills/internal/util.mjs"; + +type rmdirOptions = { + maxRetries?: number; + recursive?: boolean; + retryDelay?: number; +}; + +type rmdirCallback = (err?: Error) => void; + +export function rmdir(path: string | URL, callback: rmdirCallback): void; +export function rmdir( + path: string | URL, + options: rmdirOptions, + callback: rmdirCallback, +): void; +export function rmdir( + path: string | URL, + optionsOrCallback: rmdirOptions | rmdirCallback, + maybeCallback?: rmdirCallback, +) { + path = toNamespacedPath(getValidatedPath(path) as string); + + const callback = typeof optionsOrCallback === "function" + ? optionsOrCallback + : maybeCallback; + const options = typeof optionsOrCallback === "object" + ? optionsOrCallback + : undefined; + + if (!callback) throw new Error("No callback function supplied"); + + if (options?.recursive) { + emitRecursiveRmdirWarning(); + validateRmOptions( + path, + { ...options, force: false }, + true, + (err: Error | null | false, options: rmdirOptions) => { + if (err === false) { + return callback(new ERR_FS_RMDIR_ENOTDIR(path.toString())); + } + if (err) { + return callback(err); + } + + Deno.remove(path, { recursive: options?.recursive }) + .then((_) => callback(), callback); + }, + ); + } else { + validateRmdirOptions(options); + Deno.remove(path, { recursive: options?.recursive }) + .then((_) => callback(), (err: unknown) => { + callback( + err instanceof Error + ? denoErrorToNodeError(err, { syscall: "rmdir" }) + : err, + ); + }); + } +} + +export const rmdirPromise = promisify(rmdir) as ( + path: string | Buffer | URL, + options?: rmdirOptions, +) => Promise; + +export function rmdirSync(path: string | Buffer | URL, options?: rmdirOptions) { + path = getValidatedPath(path); + if (options?.recursive) { + emitRecursiveRmdirWarning(); + const optionsOrFalse: rmdirOptions | false = validateRmOptionsSync(path, { + ...options, + force: false, + }, true); + if (optionsOrFalse === false) { + throw new ERR_FS_RMDIR_ENOTDIR(path.toString()); + } + options = optionsOrFalse; + } else { + validateRmdirOptions(options); + } + + try { + Deno.removeSync(toNamespacedPath(path as string), { + recursive: options?.recursive, + }); + } catch (err: unknown) { + throw (err instanceof Error + ? denoErrorToNodeError(err, { syscall: "rmdir" }) + : err); + } +} diff --git a/ext/node/polyfills/_fs/_fs_stat.ts b/ext/node/polyfills/_fs/_fs_stat.ts new file mode 100644 index 00000000000000..3a006084df7412 --- /dev/null +++ b/ext/node/polyfills/_fs/_fs_stat.ts @@ -0,0 +1,314 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. +import { denoErrorToNodeError } from "internal:deno_node/polyfills/internal/errors.ts"; +import { promisify } from "internal:deno_node/polyfills/internal/util.mjs"; + +export type statOptions = { + bigint: boolean; + throwIfNoEntry?: boolean; +}; + +export type Stats = { + /** ID of the device containing the file. + * + * _Linux/Mac OS only._ */ + dev: number | null; + /** Inode number. + * + * _Linux/Mac OS only._ */ + ino: number | null; + /** **UNSTABLE**: Match behavior with Go on Windows for `mode`. + * + * The underlying raw `st_mode` bits that contain the standard Unix + * permissions for this file/directory. */ + mode: number | null; + /** Number of hard links pointing to this file. + * + * _Linux/Mac OS only._ */ + nlink: number | null; + /** User ID of the owner of this file. + * + * _Linux/Mac OS only._ */ + uid: number | null; + /** Group ID of the owner of this file. + * + * _Linux/Mac OS only._ */ + gid: number | null; + /** Device ID of this file. + * + * _Linux/Mac OS only._ */ + rdev: number | null; + /** The size of the file, in bytes. */ + size: number; + /** Blocksize for filesystem I/O. + * + * _Linux/Mac OS only._ */ + blksize: number | null; + /** Number of blocks allocated to the file, in 512-byte units. + * + * _Linux/Mac OS only._ */ + blocks: number | null; + /** The last modification time of the file. This corresponds to the `mtime` + * field from `stat` on Linux/Mac OS and `ftLastWriteTime` on Windows. This + * may not be available on all platforms. */ + mtime: Date | null; + /** The last access time of the file. This corresponds to the `atime` + * field from `stat` on Unix and `ftLastAccessTime` on Windows. This may not + * be available on all platforms. */ + atime: Date | null; + /** The creation time of the file. This corresponds to the `birthtime` + * field from `stat` on Mac/BSD and `ftCreationTime` on Windows. This may + * not be available on all platforms. */ + birthtime: Date | null; + /** change time */ + ctime: Date | null; + /** atime in milliseconds */ + atimeMs: number | null; + /** atime in milliseconds */ + mtimeMs: number | null; + /** atime in milliseconds */ + ctimeMs: number | null; + /** atime in milliseconds */ + birthtimeMs: number | null; + isBlockDevice: () => boolean; + isCharacterDevice: () => boolean; + isDirectory: () => boolean; + isFIFO: () => boolean; + isFile: () => boolean; + isSocket: () => boolean; + isSymbolicLink: () => boolean; +}; + +export type BigIntStats = { + /** ID of the device containing the file. + * + * _Linux/Mac OS only._ */ + dev: bigint | null; + /** Inode number. + * + * _Linux/Mac OS only._ */ + ino: bigint | null; + /** **UNSTABLE**: Match behavior with Go on Windows for `mode`. + * + * The underlying raw `st_mode` bits that contain the standard Unix + * permissions for this file/directory. */ + mode: bigint | null; + /** Number of hard links pointing to this file. + * + * _Linux/Mac OS only._ */ + nlink: bigint | null; + /** User ID of the owner of this file. + * + * _Linux/Mac OS only._ */ + uid: bigint | null; + /** Group ID of the owner of this file. + * + * _Linux/Mac OS only._ */ + gid: bigint | null; + /** Device ID of this file. + * + * _Linux/Mac OS only._ */ + rdev: bigint | null; + /** The size of the file, in bytes. */ + size: bigint; + /** Blocksize for filesystem I/O. + * + * _Linux/Mac OS only._ */ + blksize: bigint | null; + /** Number of blocks allocated to the file, in 512-byte units. + * + * _Linux/Mac OS only._ */ + blocks: bigint | null; + /** The last modification time of the file. This corresponds to the `mtime` + * field from `stat` on Linux/Mac OS and `ftLastWriteTime` on Windows. This + * may not be available on all platforms. */ + mtime: Date | null; + /** The last access time of the file. This corresponds to the `atime` + * field from `stat` on Unix and `ftLastAccessTime` on Windows. This may not + * be available on all platforms. */ + atime: Date | null; + /** The creation time of the file. This corresponds to the `birthtime` + * field from `stat` on Mac/BSD and `ftCreationTime` on Windows. This may + * not be available on all platforms. */ + birthtime: Date | null; + /** change time */ + ctime: Date | null; + /** atime in milliseconds */ + atimeMs: bigint | null; + /** atime in milliseconds */ + mtimeMs: bigint | null; + /** atime in milliseconds */ + ctimeMs: bigint | null; + /** atime in nanoseconds */ + birthtimeMs: bigint | null; + /** atime in nanoseconds */ + atimeNs: bigint | null; + /** atime in nanoseconds */ + mtimeNs: bigint | null; + /** atime in nanoseconds */ + ctimeNs: bigint | null; + /** atime in nanoseconds */ + birthtimeNs: bigint | null; + isBlockDevice: () => boolean; + isCharacterDevice: () => boolean; + isDirectory: () => boolean; + isFIFO: () => boolean; + isFile: () => boolean; + isSocket: () => boolean; + isSymbolicLink: () => boolean; +}; + +export function convertFileInfoToStats(origin: Deno.FileInfo): Stats { + return { + dev: origin.dev, + ino: origin.ino, + mode: origin.mode, + nlink: origin.nlink, + uid: origin.uid, + gid: origin.gid, + rdev: origin.rdev, + size: origin.size, + blksize: origin.blksize, + blocks: origin.blocks, + mtime: origin.mtime, + atime: origin.atime, + birthtime: origin.birthtime, + mtimeMs: origin.mtime?.getTime() || null, + atimeMs: origin.atime?.getTime() || null, + birthtimeMs: origin.birthtime?.getTime() || null, + isFile: () => origin.isFile, + isDirectory: () => origin.isDirectory, + isSymbolicLink: () => origin.isSymlink, + // not sure about those + isBlockDevice: () => false, + isFIFO: () => false, + isCharacterDevice: () => false, + isSocket: () => false, + ctime: origin.mtime, + ctimeMs: origin.mtime?.getTime() || null, + }; +} + +function toBigInt(number?: number | null) { + if (number === null || number === undefined) return null; + return BigInt(number); +} + +export function convertFileInfoToBigIntStats( + origin: Deno.FileInfo, +): BigIntStats { + return { + dev: toBigInt(origin.dev), + ino: toBigInt(origin.ino), + mode: toBigInt(origin.mode), + nlink: toBigInt(origin.nlink), + uid: toBigInt(origin.uid), + gid: toBigInt(origin.gid), + rdev: toBigInt(origin.rdev), + size: toBigInt(origin.size) || 0n, + blksize: toBigInt(origin.blksize), + blocks: toBigInt(origin.blocks), + mtime: origin.mtime, + atime: origin.atime, + birthtime: origin.birthtime, + mtimeMs: origin.mtime ? BigInt(origin.mtime.getTime()) : null, + atimeMs: origin.atime ? BigInt(origin.atime.getTime()) : null, + birthtimeMs: origin.birthtime ? BigInt(origin.birthtime.getTime()) : null, + mtimeNs: origin.mtime ? BigInt(origin.mtime.getTime()) * 1000000n : null, + atimeNs: origin.atime ? BigInt(origin.atime.getTime()) * 1000000n : null, + birthtimeNs: origin.birthtime + ? BigInt(origin.birthtime.getTime()) * 1000000n + : null, + isFile: () => origin.isFile, + isDirectory: () => origin.isDirectory, + isSymbolicLink: () => origin.isSymlink, + // not sure about those + isBlockDevice: () => false, + isFIFO: () => false, + isCharacterDevice: () => false, + isSocket: () => false, + ctime: origin.mtime, + ctimeMs: origin.mtime ? BigInt(origin.mtime.getTime()) : null, + ctimeNs: origin.mtime ? BigInt(origin.mtime.getTime()) * 1000000n : null, + }; +} + +// shortcut for Convert File Info to Stats or BigIntStats +export function CFISBIS(fileInfo: Deno.FileInfo, bigInt: boolean) { + if (bigInt) return convertFileInfoToBigIntStats(fileInfo); + return convertFileInfoToStats(fileInfo); +} + +export type statCallbackBigInt = (err: Error | null, stat: BigIntStats) => void; + +export type statCallback = (err: Error | null, stat: Stats) => void; + +export function stat(path: string | URL, callback: statCallback): void; +export function stat( + path: string | URL, + options: { bigint: false }, + callback: statCallback, +): void; +export function stat( + path: string | URL, + options: { bigint: true }, + callback: statCallbackBigInt, +): void; +export function stat( + path: string | URL, + optionsOrCallback: statCallback | statCallbackBigInt | statOptions, + maybeCallback?: statCallback | statCallbackBigInt, +) { + const callback = + (typeof optionsOrCallback === "function" + ? optionsOrCallback + : maybeCallback) as ( + ...args: [Error] | [null, BigIntStats | Stats] + ) => void; + const options = typeof optionsOrCallback === "object" + ? optionsOrCallback + : { bigint: false }; + + if (!callback) throw new Error("No callback function supplied"); + + Deno.stat(path).then( + (stat) => callback(null, CFISBIS(stat, options.bigint)), + (err) => callback(denoErrorToNodeError(err, { syscall: "stat" })), + ); +} + +export const statPromise = promisify(stat) as ( + & ((path: string | URL) => Promise) + & ((path: string | URL, options: { bigint: false }) => Promise) + & ((path: string | URL, options: { bigint: true }) => Promise) +); + +export function statSync(path: string | URL): Stats; +export function statSync( + path: string | URL, + options: { bigint: false; throwIfNoEntry?: boolean }, +): Stats; +export function statSync( + path: string | URL, + options: { bigint: true; throwIfNoEntry?: boolean }, +): BigIntStats; +export function statSync( + path: string | URL, + options: statOptions = { bigint: false, throwIfNoEntry: true }, +): Stats | BigIntStats | undefined { + try { + const origin = Deno.statSync(path); + return CFISBIS(origin, options.bigint); + } catch (err) { + if ( + options?.throwIfNoEntry === false && + err instanceof Deno.errors.NotFound + ) { + return; + } + if (err instanceof Error) { + throw denoErrorToNodeError(err, { syscall: "stat" }); + } else { + throw err; + } + } +} diff --git a/ext/node/polyfills/_fs/_fs_symlink.ts b/ext/node/polyfills/_fs/_fs_symlink.ts new file mode 100644 index 00000000000000..c8652885fb3dcf --- /dev/null +++ b/ext/node/polyfills/_fs/_fs_symlink.ts @@ -0,0 +1,46 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. +import { CallbackWithError } from "internal:deno_node/polyfills/_fs/_fs_common.ts"; +import { fromFileUrl } from "internal:deno_node/polyfills/path.ts"; +import { promisify } from "internal:deno_node/polyfills/internal/util.mjs"; + +type SymlinkType = "file" | "dir"; + +export function symlink( + target: string | URL, + path: string | URL, + typeOrCallback: SymlinkType | CallbackWithError, + maybeCallback?: CallbackWithError, +) { + target = target instanceof URL ? fromFileUrl(target) : target; + path = path instanceof URL ? fromFileUrl(path) : path; + + const type: SymlinkType = typeof typeOrCallback === "string" + ? typeOrCallback + : "file"; + + const callback: CallbackWithError = typeof typeOrCallback === "function" + ? typeOrCallback + : (maybeCallback as CallbackWithError); + + if (!callback) throw new Error("No callback function supplied"); + + Deno.symlink(target, path, { type }).then(() => callback(null), callback); +} + +export const symlinkPromise = promisify(symlink) as ( + target: string | URL, + path: string | URL, + type?: SymlinkType, +) => Promise; + +export function symlinkSync( + target: string | URL, + path: string | URL, + type?: SymlinkType, +) { + target = target instanceof URL ? fromFileUrl(target) : target; + path = path instanceof URL ? fromFileUrl(path) : path; + type = type || "file"; + + Deno.symlinkSync(target, path, { type }); +} diff --git a/ext/node/polyfills/_fs/_fs_truncate.ts b/ext/node/polyfills/_fs/_fs_truncate.ts new file mode 100644 index 00000000000000..105555abcf9ac8 --- /dev/null +++ b/ext/node/polyfills/_fs/_fs_truncate.ts @@ -0,0 +1,33 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. +import { CallbackWithError } from "internal:deno_node/polyfills/_fs/_fs_common.ts"; +import { fromFileUrl } from "internal:deno_node/polyfills/path.ts"; +import { promisify } from "internal:deno_node/polyfills/internal/util.mjs"; + +export function truncate( + path: string | URL, + lenOrCallback: number | CallbackWithError, + maybeCallback?: CallbackWithError, +) { + path = path instanceof URL ? fromFileUrl(path) : path; + const len: number | undefined = typeof lenOrCallback === "number" + ? lenOrCallback + : undefined; + const callback: CallbackWithError = typeof lenOrCallback === "function" + ? lenOrCallback + : maybeCallback as CallbackWithError; + + if (!callback) throw new Error("No callback function supplied"); + + Deno.truncate(path, len).then(() => callback(null), callback); +} + +export const truncatePromise = promisify(truncate) as ( + path: string | URL, + len?: number, +) => Promise; + +export function truncateSync(path: string | URL, len?: number) { + path = path instanceof URL ? fromFileUrl(path) : path; + + Deno.truncateSync(path, len); +} diff --git a/ext/node/polyfills/_fs/_fs_unlink.ts b/ext/node/polyfills/_fs/_fs_unlink.ts new file mode 100644 index 00000000000000..ed43bb1b311f8c --- /dev/null +++ b/ext/node/polyfills/_fs/_fs_unlink.ts @@ -0,0 +1,15 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. +import { promisify } from "internal:deno_node/polyfills/internal/util.mjs"; + +export function unlink(path: string | URL, callback: (err?: Error) => void) { + if (!callback) throw new Error("No callback function supplied"); + Deno.remove(path).then((_) => callback(), callback); +} + +export const unlinkPromise = promisify(unlink) as ( + path: string | URL, +) => Promise; + +export function unlinkSync(path: string | URL) { + Deno.removeSync(path); +} diff --git a/ext/node/polyfills/_fs/_fs_utimes.ts b/ext/node/polyfills/_fs/_fs_utimes.ts new file mode 100644 index 00000000000000..7423a1060e7d1b --- /dev/null +++ b/ext/node/polyfills/_fs/_fs_utimes.ts @@ -0,0 +1,61 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. + +import type { CallbackWithError } from "internal:deno_node/polyfills/_fs/_fs_common.ts"; +import { fromFileUrl } from "internal:deno_node/polyfills/path.ts"; +import { promisify } from "internal:deno_node/polyfills/internal/util.mjs"; + +function getValidTime( + time: number | string | Date, + name: string, +): number | Date { + if (typeof time === "string") { + time = Number(time); + } + + if ( + typeof time === "number" && + (Number.isNaN(time) || !Number.isFinite(time)) + ) { + throw new Deno.errors.InvalidData( + `invalid ${name}, must not be infinity or NaN`, + ); + } + + return time; +} + +export function utimes( + path: string | URL, + atime: number | string | Date, + mtime: number | string | Date, + callback: CallbackWithError, +) { + path = path instanceof URL ? fromFileUrl(path) : path; + + if (!callback) { + throw new Deno.errors.InvalidData("No callback function supplied"); + } + + atime = getValidTime(atime, "atime"); + mtime = getValidTime(mtime, "mtime"); + + Deno.utime(path, atime, mtime).then(() => callback(null), callback); +} + +export const utimesPromise = promisify(utimes) as ( + path: string | URL, + atime: number | string | Date, + mtime: number | string | Date, +) => Promise; + +export function utimesSync( + path: string | URL, + atime: number | string | Date, + mtime: number | string | Date, +) { + path = path instanceof URL ? fromFileUrl(path) : path; + atime = getValidTime(atime, "atime"); + mtime = getValidTime(mtime, "mtime"); + + Deno.utimeSync(path, atime, mtime); +} diff --git a/ext/node/polyfills/_fs/_fs_watch.ts b/ext/node/polyfills/_fs/_fs_watch.ts new file mode 100644 index 00000000000000..79f226126ac57a --- /dev/null +++ b/ext/node/polyfills/_fs/_fs_watch.ts @@ -0,0 +1,346 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. +import { basename } from "internal:deno_node/polyfills/path.ts"; +import { EventEmitter } from "internal:deno_node/polyfills/events.ts"; +import { notImplemented } from "internal:deno_node/polyfills/_utils.ts"; +import { promisify } from "internal:deno_node/polyfills/util.ts"; +import { getValidatedPath } from "internal:deno_node/polyfills/internal/fs/utils.mjs"; +import { validateFunction } from "internal:deno_node/polyfills/internal/validators.mjs"; +import { stat, Stats } from "internal:deno_node/polyfills/_fs/_fs_stat.ts"; +import { Stats as StatsClass } from "internal:deno_node/polyfills/internal/fs/utils.mjs"; +import { Buffer } from "internal:deno_node/polyfills/buffer.ts"; +import { delay } from "internal:deno_node/polyfills/_util/async.ts"; + +const statPromisified = promisify(stat); +const statAsync = async (filename: string): Promise => { + try { + return await statPromisified(filename); + } catch { + return emptyStats; + } +}; +const emptyStats = new StatsClass( + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + Date.UTC(1970, 0, 1, 0, 0, 0), + Date.UTC(1970, 0, 1, 0, 0, 0), + Date.UTC(1970, 0, 1, 0, 0, 0), + Date.UTC(1970, 0, 1, 0, 0, 0), +) as unknown as Stats; + +export function asyncIterableIteratorToCallback( + iterator: AsyncIterableIterator, + callback: (val: T, done?: boolean) => void, +) { + function next() { + iterator.next().then((obj) => { + if (obj.done) { + callback(obj.value, true); + return; + } + callback(obj.value); + next(); + }); + } + next(); +} + +export function asyncIterableToCallback( + iter: AsyncIterable, + callback: (val: T, done?: boolean) => void, + errCallback: (e: unknown) => void, +) { + const iterator = iter[Symbol.asyncIterator](); + function next() { + iterator.next().then((obj) => { + if (obj.done) { + callback(obj.value, true); + return; + } + callback(obj.value); + next(); + }, errCallback); + } + next(); +} + +type watchOptions = { + persistent?: boolean; + recursive?: boolean; + encoding?: string; +}; + +type watchListener = (eventType: string, filename: string) => void; + +export function watch( + filename: string | URL, + options: watchOptions, + listener: watchListener, +): FSWatcher; +export function watch( + filename: string | URL, + listener: watchListener, +): FSWatcher; +export function watch( + filename: string | URL, + options: watchOptions, +): FSWatcher; +export function watch(filename: string | URL): FSWatcher; +export function watch( + filename: string | URL, + optionsOrListener?: watchOptions | watchListener, + optionsOrListener2?: watchOptions | watchListener, +) { + const listener = typeof optionsOrListener === "function" + ? optionsOrListener + : typeof optionsOrListener2 === "function" + ? optionsOrListener2 + : undefined; + const options = typeof optionsOrListener === "object" + ? optionsOrListener + : typeof optionsOrListener2 === "object" + ? optionsOrListener2 + : undefined; + + const watchPath = getValidatedPath(filename).toString(); + + let iterator: Deno.FsWatcher; + // Start the actual watcher a few msec later to avoid race condition + // error in test case in compat test case + // (parallel/test-fs-watch.js, parallel/test-fs-watchfile.js) + const timer = setTimeout(() => { + iterator = Deno.watchFs(watchPath, { + recursive: options?.recursive || false, + }); + + asyncIterableToCallback(iterator, (val, done) => { + if (done) return; + fsWatcher.emit( + "change", + convertDenoFsEventToNodeFsEvent(val.kind), + basename(val.paths[0]), + ); + }, (e) => { + fsWatcher.emit("error", e); + }); + }, 5); + + const fsWatcher = new FSWatcher(() => { + clearTimeout(timer); + try { + iterator?.close(); + } catch (e) { + if (e instanceof Deno.errors.BadResource) { + // already closed + return; + } + throw e; + } + }); + + if (listener) { + fsWatcher.on("change", listener.bind({ _handle: fsWatcher })); + } + + return fsWatcher; +} + +export const watchPromise = promisify(watch) as ( + & (( + filename: string | URL, + options: watchOptions, + listener: watchListener, + ) => Promise) + & (( + filename: string | URL, + listener: watchListener, + ) => Promise) + & (( + filename: string | URL, + options: watchOptions, + ) => Promise) + & ((filename: string | URL) => Promise) +); + +type WatchFileListener = (curr: Stats, prev: Stats) => void; +type WatchFileOptions = { + bigint?: boolean; + persistent?: boolean; + interval?: number; +}; + +export function watchFile( + filename: string | Buffer | URL, + listener: WatchFileListener, +): StatWatcher; +export function watchFile( + filename: string | Buffer | URL, + options: WatchFileOptions, + listener: WatchFileListener, +): StatWatcher; +export function watchFile( + filename: string | Buffer | URL, + listenerOrOptions: WatchFileListener | WatchFileOptions, + listener?: WatchFileListener, +): StatWatcher { + const watchPath = getValidatedPath(filename).toString(); + const handler = typeof listenerOrOptions === "function" + ? listenerOrOptions + : listener!; + validateFunction(handler, "listener"); + const { + bigint = false, + persistent = true, + interval = 5007, + } = typeof listenerOrOptions === "object" ? listenerOrOptions : {}; + + let stat = statWatchers.get(watchPath); + if (stat === undefined) { + stat = new StatWatcher(bigint); + stat[kFSStatWatcherStart](watchPath, persistent, interval); + statWatchers.set(watchPath, stat); + } + + stat.addListener("change", listener!); + return stat; +} + +export function unwatchFile( + filename: string | Buffer | URL, + listener?: WatchFileListener, +) { + const watchPath = getValidatedPath(filename).toString(); + const stat = statWatchers.get(watchPath); + + if (!stat) { + return; + } + + if (typeof listener === "function") { + const beforeListenerCount = stat.listenerCount("change"); + stat.removeListener("change", listener); + if (stat.listenerCount("change") < beforeListenerCount) { + stat[kFSStatWatcherAddOrCleanRef]("clean"); + } + } else { + stat.removeAllListeners("change"); + stat[kFSStatWatcherAddOrCleanRef]("cleanAll"); + } + + if (stat.listenerCount("change") === 0) { + stat.stop(); + statWatchers.delete(watchPath); + } +} + +const statWatchers = new Map(); + +const kFSStatWatcherStart = Symbol("kFSStatWatcherStart"); +const kFSStatWatcherAddOrCleanRef = Symbol("kFSStatWatcherAddOrCleanRef"); + +class StatWatcher extends EventEmitter { + #bigint: boolean; + #refCount = 0; + #abortController = new AbortController(); + constructor(bigint: boolean) { + super(); + this.#bigint = bigint; + } + [kFSStatWatcherStart]( + filename: string, + persistent: boolean, + interval: number, + ) { + if (persistent) { + this.#refCount++; + } + + (async () => { + let prev = await statAsync(filename); + + if (prev === emptyStats) { + this.emit("change", prev, prev); + } + + try { + while (true) { + await delay(interval, { signal: this.#abortController.signal }); + const curr = await statAsync(filename); + if (curr?.mtime !== prev?.mtime) { + this.emit("change", curr, prev); + prev = curr; + } + } + } catch (e) { + if (e instanceof DOMException && e.name === "AbortError") { + return; + } + this.emit("error", e); + } + })(); + } + [kFSStatWatcherAddOrCleanRef](addOrClean: "add" | "clean" | "cleanAll") { + if (addOrClean === "add") { + this.#refCount++; + } else if (addOrClean === "clean") { + this.#refCount--; + } else { + this.#refCount = 0; + } + } + stop() { + if (this.#abortController.signal.aborted) { + return; + } + this.#abortController.abort(); + this.emit("stop"); + } + ref() { + notImplemented("FSWatcher.ref() is not implemented"); + } + unref() { + notImplemented("FSWatcher.unref() is not implemented"); + } +} + +class FSWatcher extends EventEmitter { + #closer: () => void; + #closed = false; + constructor(closer: () => void) { + super(); + this.#closer = closer; + } + close() { + if (this.#closed) { + return; + } + this.#closed = true; + this.emit("close"); + this.#closer(); + } + ref() { + notImplemented("FSWatcher.ref() is not implemented"); + } + unref() { + notImplemented("FSWatcher.unref() is not implemented"); + } +} + +type NodeFsEventType = "rename" | "change"; + +function convertDenoFsEventToNodeFsEvent( + kind: Deno.FsEvent["kind"], +): NodeFsEventType { + if (kind === "create" || kind === "remove") { + return "rename"; + } else { + return "change"; + } +} diff --git a/ext/node/polyfills/_fs/_fs_write.d.ts b/ext/node/polyfills/_fs/_fs_write.d.ts new file mode 100644 index 00000000000000..eb6dbcc95d5281 --- /dev/null +++ b/ext/node/polyfills/_fs/_fs_write.d.ts @@ -0,0 +1,207 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. +// Forked from https://github.com/DefinitelyTyped/DefinitelyTyped/blob/d9df51e34526f48bef4e2546a006157b391ad96c/types/node/fs.d.ts + +import { + BufferEncoding, + ErrnoException, +} from "internal:deno_node/polyfills/_global.d.ts"; + +/** + * Write `buffer` to the file specified by `fd`. If `buffer` is a normal object, it + * must have an own `toString` function property. + * + * `offset` determines the part of the buffer to be written, and `length` is + * an integer specifying the number of bytes to write. + * + * `position` refers to the offset from the beginning of the file where this data + * should be written. If `typeof position !== 'number'`, the data will be written + * at the current position. See [`pwrite(2)`](http://man7.org/linux/man-pages/man2/pwrite.2.html). + * + * The callback will be given three arguments `(err, bytesWritten, buffer)` where`bytesWritten` specifies how many _bytes_ were written from `buffer`. + * + * If this method is invoked as its `util.promisify()` ed version, it returns + * a promise for an `Object` with `bytesWritten` and `buffer` properties. + * + * It is unsafe to use `fs.write()` multiple times on the same file without waiting + * for the callback. For this scenario, {@link createWriteStream} is + * recommended. + * + * On Linux, positional writes don't work when the file is opened in append mode. + * The kernel ignores the position argument and always appends the data to + * the end of the file. + * @since v0.0.2 + */ +export function write( + fd: number, + buffer: TBuffer, + offset: number | undefined | null, + length: number | undefined | null, + position: number | undefined | null, + callback: ( + err: ErrnoException | null, + written: number, + buffer: TBuffer, + ) => void, +): void; +/** + * Asynchronously writes `buffer` to the file referenced by the supplied file descriptor. + * @param fd A file descriptor. + * @param offset The part of the buffer to be written. If not supplied, defaults to `0`. + * @param length The number of bytes to write. If not supplied, defaults to `buffer.length - offset`. + */ +export function write( + fd: number, + buffer: TBuffer, + offset: number | undefined | null, + length: number | undefined | null, + callback: ( + err: ErrnoException | null, + written: number, + buffer: TBuffer, + ) => void, +): void; +/** + * Asynchronously writes `buffer` to the file referenced by the supplied file descriptor. + * @param fd A file descriptor. + * @param offset The part of the buffer to be written. If not supplied, defaults to `0`. + */ +export function write( + fd: number, + buffer: TBuffer, + offset: number | undefined | null, + callback: ( + err: ErrnoException | null, + written: number, + buffer: TBuffer, + ) => void, +): void; +/** + * Asynchronously writes `buffer` to the file referenced by the supplied file descriptor. + * @param fd A file descriptor. + */ +export function write( + fd: number, + buffer: TBuffer, + callback: ( + err: ErrnoException | null, + written: number, + buffer: TBuffer, + ) => void, +): void; +/** + * Asynchronously writes `string` to the file referenced by the supplied file descriptor. + * @param fd A file descriptor. + * @param string A string to write. + * @param position The offset from the beginning of the file where this data should be written. If not supplied, defaults to the current position. + * @param encoding The expected string encoding. + */ +export function write( + fd: number, + string: string, + position: number | undefined | null, + encoding: BufferEncoding | undefined | null, + callback: (err: ErrnoException | null, written: number, str: string) => void, +): void; +/** + * Asynchronously writes `string` to the file referenced by the supplied file descriptor. + * @param fd A file descriptor. + * @param string A string to write. + * @param position The offset from the beginning of the file where this data should be written. If not supplied, defaults to the current position. + */ +export function write( + fd: number, + string: string, + position: number | undefined | null, + callback: (err: ErrnoException | null, written: number, str: string) => void, +): void; +/** + * Asynchronously writes `string` to the file referenced by the supplied file descriptor. + * @param fd A file descriptor. + * @param string A string to write. + */ +export function write( + fd: number, + string: string, + callback: (err: ErrnoException | null, written: number, str: string) => void, +): void; +export namespace write { + /** + * Asynchronously writes `buffer` to the file referenced by the supplied file descriptor. + * @param fd A file descriptor. + * @param offset The part of the buffer to be written. If not supplied, defaults to `0`. + * @param length The number of bytes to write. If not supplied, defaults to `buffer.length - offset`. + * @param position The offset from the beginning of the file where this data should be written. If not supplied, defaults to the current position. + */ + function __promisify__( + fd: number, + buffer?: TBuffer, + offset?: number, + length?: number, + position?: number | null, + ): Promise<{ + bytesWritten: number; + buffer: TBuffer; + }>; + /** + * Asynchronously writes `string` to the file referenced by the supplied file descriptor. + * @param fd A file descriptor. + * @param string A string to write. + * @param position The offset from the beginning of the file where this data should be written. If not supplied, defaults to the current position. + * @param encoding The expected string encoding. + */ + function __promisify__( + fd: number, + string: string, + position?: number | null, + encoding?: BufferEncoding | null, + ): Promise<{ + bytesWritten: number; + buffer: string; + }>; +} +/** + * If `buffer` is a plain object, it must have an own (not inherited) `toString`function property. + * + * For detailed information, see the documentation of the asynchronous version of + * this API: {@link write}. + * @since v0.1.21 + * @return The number of bytes written. + */ +export function writeSync( + fd: number, + buffer: ArrayBufferView, + offset?: number | null, + length?: number | null, + position?: number | null, +): number; +/** + * Synchronously writes `string` to the file referenced by the supplied file descriptor, returning the number of bytes written. + * @param fd A file descriptor. + * @param string A string to write. + * @param position The offset from the beginning of the file where this data should be written. If not supplied, defaults to the current position. + * @param encoding The expected string encoding. + */ +export function writeSync( + fd: number, + string: string, + position?: number | null, + encoding?: BufferEncoding | null, +): number; +export type ReadPosition = number | bigint; +/** + * Read data from the file specified by `fd`. + * + * The callback is given the three arguments, `(err, bytesRead, buffer)`. + * + * If the file is not modified concurrently, the end-of-file is reached when the + * number of bytes read is zero. + * + * If this method is invoked as its `util.promisify()` ed version, it returns + * a promise for an `Object` with `bytesRead` and `buffer` properties. + * @since v0.0.2 + * @param buffer The buffer that the data will be written to. + * @param offset The position in `buffer` to write the data to. + * @param length The number of bytes to read. + * @param position Specifies where to begin reading from in the file. If `position` is `null` or `-1 `, data will be read from the current file position, and the file position will be updated. If + * `position` is an integer, the file position will be unchanged. + */ diff --git a/ext/node/polyfills/_fs/_fs_write.mjs b/ext/node/polyfills/_fs/_fs_write.mjs new file mode 100644 index 00000000000000..d44a72921625d4 --- /dev/null +++ b/ext/node/polyfills/_fs/_fs_write.mjs @@ -0,0 +1,132 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. +// Copyright Joyent, Inc. and Node.js contributors. All rights reserved. MIT license. +import { Buffer } from "internal:deno_node/polyfills/buffer.ts"; +import { validateEncoding, validateInteger } from "internal:deno_node/polyfills/internal/validators.mjs"; +import { + getValidatedFd, + showStringCoercionDeprecation, + validateOffsetLengthWrite, + validateStringAfterArrayBufferView, +} from "internal:deno_node/polyfills/internal/fs/utils.mjs"; +import { isArrayBufferView } from "internal:deno_node/polyfills/internal/util/types.ts"; +import { maybeCallback } from "internal:deno_node/polyfills/_fs/_fs_common.ts"; + +export function writeSync(fd, buffer, offset, length, position) { + fd = getValidatedFd(fd); + + const innerWriteSync = (fd, buffer, offset, length, position) => { + if (buffer instanceof DataView) { + buffer = new Uint8Array(buffer.buffer); + } + if (typeof position === "number") { + Deno.seekSync(fd, position, Deno.SeekMode.Start); + } + let currentOffset = offset; + const end = offset + length; + while (currentOffset - offset < length) { + currentOffset += Deno.writeSync(fd, buffer.subarray(currentOffset, end)); + } + return currentOffset - offset; + }; + + if (isArrayBufferView(buffer)) { + if (position === undefined) { + position = null; + } + if (offset == null) { + offset = 0; + } else { + validateInteger(offset, "offset", 0); + } + if (typeof length !== "number") { + length = buffer.byteLength - offset; + } + validateOffsetLengthWrite(offset, length, buffer.byteLength); + return innerWriteSync(fd, buffer, offset, length, position); + } + validateStringAfterArrayBufferView(buffer, "buffer"); + validateEncoding(buffer, length); + if (offset === undefined) { + offset = null; + } + buffer = Buffer.from(buffer, length); + return innerWriteSync(fd, buffer, 0, buffer.length, position); +} + +/** Writes the buffer to the file of the given descriptor. + * https://nodejs.org/api/fs.html#fswritefd-buffer-offset-length-position-callback + * https://github.com/nodejs/node/blob/42ad4137aadda69c51e1df48eee9bc2e5cebca5c/lib/fs.js#L797 + */ +export function write(fd, buffer, offset, length, position, callback) { + fd = getValidatedFd(fd); + + const innerWrite = async (fd, buffer, offset, length, position) => { + if (buffer instanceof DataView) { + buffer = new Uint8Array(buffer.buffer); + } + if (typeof position === "number") { + await Deno.seek(fd, position, Deno.SeekMode.Start); + } + let currentOffset = offset; + const end = offset + length; + while (currentOffset - offset < length) { + currentOffset += await Deno.write( + fd, + buffer.subarray(currentOffset, end), + ); + } + return currentOffset - offset; + }; + + if (isArrayBufferView(buffer)) { + callback = maybeCallback(callback || position || length || offset); + if (offset == null || typeof offset === "function") { + offset = 0; + } else { + validateInteger(offset, "offset", 0); + } + if (typeof length !== "number") { + length = buffer.byteLength - offset; + } + if (typeof position !== "number") { + position = null; + } + validateOffsetLengthWrite(offset, length, buffer.byteLength); + innerWrite(fd, buffer, offset, length, position).then( + (nwritten) => { + callback(null, nwritten, buffer); + }, + (err) => callback(err), + ); + return; + } + + // Here the call signature is + // `fs.write(fd, string[, position[, encoding]], callback)` + + validateStringAfterArrayBufferView(buffer, "buffer"); + if (typeof buffer !== "string") { + showStringCoercionDeprecation(); + } + + if (typeof position !== "function") { + if (typeof offset === "function") { + position = offset; + offset = null; + } else { + position = length; + } + length = "utf-8"; + } + + const str = String(buffer); + validateEncoding(str, length); + callback = maybeCallback(position); + buffer = Buffer.from(str, length); + innerWrite(fd, buffer, 0, buffer.length, offset, callback).then( + (nwritten) => { + callback(null, nwritten, buffer); + }, + (err) => callback(err), + ); +} diff --git a/ext/node/polyfills/_fs/_fs_writeFile.ts b/ext/node/polyfills/_fs/_fs_writeFile.ts new file mode 100644 index 00000000000000..3cad5f947c2aad --- /dev/null +++ b/ext/node/polyfills/_fs/_fs_writeFile.ts @@ -0,0 +1,193 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. +import { Encodings } from "internal:deno_node/polyfills/_utils.ts"; +import { fromFileUrl } from "internal:deno_node/polyfills/path.ts"; +import { Buffer } from "internal:deno_node/polyfills/buffer.ts"; +import { + CallbackWithError, + checkEncoding, + getEncoding, + getOpenOptions, + isFileOptions, + WriteFileOptions, +} from "internal:deno_node/polyfills/_fs/_fs_common.ts"; +import { isWindows } from "internal:deno_node/polyfills/_util/os.ts"; +import { + AbortError, + denoErrorToNodeError, +} from "internal:deno_node/polyfills/internal/errors.ts"; +import { + showStringCoercionDeprecation, + validateStringAfterArrayBufferView, +} from "internal:deno_node/polyfills/internal/fs/utils.mjs"; +import { promisify } from "internal:deno_node/polyfills/internal/util.mjs"; + +interface Writer { + write(p: Uint8Array): Promise; +} + +export function writeFile( + pathOrRid: string | number | URL, + // deno-lint-ignore ban-types + data: string | Uint8Array | Object, + optOrCallback: Encodings | CallbackWithError | WriteFileOptions | undefined, + callback?: CallbackWithError, +) { + const callbackFn: CallbackWithError | undefined = + optOrCallback instanceof Function ? optOrCallback : callback; + const options: Encodings | WriteFileOptions | undefined = + optOrCallback instanceof Function ? undefined : optOrCallback; + + if (!callbackFn) { + throw new TypeError("Callback must be a function."); + } + + pathOrRid = pathOrRid instanceof URL ? fromFileUrl(pathOrRid) : pathOrRid; + + const flag: string | undefined = isFileOptions(options) + ? options.flag + : undefined; + + const mode: number | undefined = isFileOptions(options) + ? options.mode + : undefined; + + const encoding = checkEncoding(getEncoding(options)) || "utf8"; + const openOptions = getOpenOptions(flag || "w"); + + if (!ArrayBuffer.isView(data)) { + validateStringAfterArrayBufferView(data, "data"); + if (typeof data !== "string") { + showStringCoercionDeprecation(); + } + data = Buffer.from(String(data), encoding); + } + + const isRid = typeof pathOrRid === "number"; + let file; + + let error: Error | null = null; + (async () => { + try { + file = isRid + ? new Deno.FsFile(pathOrRid as number) + : await Deno.open(pathOrRid as string, openOptions); + + // ignore mode because it's not supported on windows + // TODO(@bartlomieju): remove `!isWindows` when `Deno.chmod` is supported + if (!isRid && mode && !isWindows) { + await Deno.chmod(pathOrRid as string, mode); + } + + const signal: AbortSignal | undefined = isFileOptions(options) + ? options.signal + : undefined; + await writeAll(file, data as Uint8Array, { signal }); + } catch (e) { + error = e instanceof Error + ? denoErrorToNodeError(e, { syscall: "write" }) + : new Error("[non-error thrown]"); + } finally { + // Make sure to close resource + if (!isRid && file) file.close(); + callbackFn(error); + } + })(); +} + +export const writeFilePromise = promisify(writeFile) as ( + pathOrRid: string | number | URL, + // deno-lint-ignore ban-types + data: string | Uint8Array | Object, + options?: Encodings | WriteFileOptions, +) => Promise; + +export function writeFileSync( + pathOrRid: string | number | URL, + // deno-lint-ignore ban-types + data: string | Uint8Array | Object, + options?: Encodings | WriteFileOptions, +) { + pathOrRid = pathOrRid instanceof URL ? fromFileUrl(pathOrRid) : pathOrRid; + + const flag: string | undefined = isFileOptions(options) + ? options.flag + : undefined; + + const mode: number | undefined = isFileOptions(options) + ? options.mode + : undefined; + + const encoding = checkEncoding(getEncoding(options)) || "utf8"; + const openOptions = getOpenOptions(flag || "w"); + + if (!ArrayBuffer.isView(data)) { + validateStringAfterArrayBufferView(data, "data"); + if (typeof data !== "string") { + showStringCoercionDeprecation(); + } + data = Buffer.from(String(data), encoding); + } + + const isRid = typeof pathOrRid === "number"; + let file; + + let error: Error | null = null; + try { + file = isRid + ? new Deno.FsFile(pathOrRid as number) + : Deno.openSync(pathOrRid as string, openOptions); + + // ignore mode because it's not supported on windows + // TODO(@bartlomieju): remove `!isWindows` when `Deno.chmod` is supported + if (!isRid && mode && !isWindows) { + Deno.chmodSync(pathOrRid as string, mode); + } + + // TODO(crowlKats): duplicate from runtime/js/13_buffer.js + let nwritten = 0; + while (nwritten < (data as Uint8Array).length) { + nwritten += file.writeSync((data as Uint8Array).subarray(nwritten)); + } + } catch (e) { + error = e instanceof Error + ? denoErrorToNodeError(e, { syscall: "write" }) + : new Error("[non-error thrown]"); + } finally { + // Make sure to close resource + if (!isRid && file) file.close(); + } + + if (error) throw error; +} + +interface WriteAllOptions { + offset?: number; + length?: number; + signal?: AbortSignal; +} +async function writeAll( + w: Writer, + arr: Uint8Array, + options: WriteAllOptions = {}, +) { + const { offset = 0, length = arr.byteLength, signal } = options; + checkAborted(signal); + + const written = await w.write(arr.subarray(offset, offset + length)); + + if (written === length) { + return; + } + + await writeAll(w, arr, { + offset: offset + written, + length: length - written, + signal, + }); +} + +function checkAborted(signal?: AbortSignal) { + if (signal?.aborted) { + throw new AbortError(); + } +} diff --git a/ext/node/polyfills/_fs/_fs_writev.d.ts b/ext/node/polyfills/_fs/_fs_writev.d.ts new file mode 100644 index 00000000000000..d828bf6771e3b6 --- /dev/null +++ b/ext/node/polyfills/_fs/_fs_writev.d.ts @@ -0,0 +1,65 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. +// Forked from https://github.com/DefinitelyTyped/DefinitelyTyped/blob/d9df51e34526f48bef4e2546a006157b391ad96c/types/node/fs.d.ts + +import { ErrnoException } from "internal:deno_node/polyfills/_global.d.ts"; + +/** + * Write an array of `ArrayBufferView`s to the file specified by `fd` using`writev()`. + * + * `position` is the offset from the beginning of the file where this data + * should be written. If `typeof position !== 'number'`, the data will be written + * at the current position. + * + * The callback will be given three arguments: `err`, `bytesWritten`, and`buffers`. `bytesWritten` is how many bytes were written from `buffers`. + * + * If this method is `util.promisify()` ed, it returns a promise for an`Object` with `bytesWritten` and `buffers` properties. + * + * It is unsafe to use `fs.writev()` multiple times on the same file without + * waiting for the callback. For this scenario, use {@link createWriteStream}. + * + * On Linux, positional writes don't work when the file is opened in append mode. + * The kernel ignores the position argument and always appends the data to + * the end of the file. + * @since v12.9.0 + */ +export function writev( + fd: number, + buffers: ReadonlyArray, + cb: ( + err: ErrnoException | null, + bytesWritten: number, + buffers: ArrayBufferView[], + ) => void, +): void; +export function writev( + fd: number, + buffers: ReadonlyArray, + position: number | null, + cb: ( + err: ErrnoException | null, + bytesWritten: number, + buffers: ArrayBufferView[], + ) => void, +): void; +export interface WriteVResult { + bytesWritten: number; + buffers: ArrayBufferView[]; +} +export namespace writev { + function __promisify__( + fd: number, + buffers: ReadonlyArray, + position?: number, + ): Promise; +} +/** + * For detailed information, see the documentation of the asynchronous version of + * this API: {@link writev}. + * @since v12.9.0 + * @return The number of bytes written. + */ +export function writevSync( + fd: number, + buffers: ReadonlyArray, + position?: number, +): number; diff --git a/ext/node/polyfills/_fs/_fs_writev.mjs b/ext/node/polyfills/_fs/_fs_writev.mjs new file mode 100644 index 00000000000000..ffc67c81ae1214 --- /dev/null +++ b/ext/node/polyfills/_fs/_fs_writev.mjs @@ -0,0 +1,81 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. +// Copyright Joyent, Inc. and Node.js contributors. All rights reserved. MIT license. +import { Buffer } from "internal:deno_node/polyfills/buffer.ts"; +import { validateBufferArray } from "internal:deno_node/polyfills/internal/fs/utils.mjs"; +import { getValidatedFd } from "internal:deno_node/polyfills/internal/fs/utils.mjs"; +import { maybeCallback } from "internal:deno_node/polyfills/_fs/_fs_common.ts"; + +export function writev(fd, buffers, position, callback) { + const innerWritev = async (fd, buffers, position) => { + const chunks = []; + const offset = 0; + for (let i = 0; i < buffers.length; i++) { + if (Buffer.isBuffer(buffers[i])) { + chunks.push(buffers[i]); + } else { + chunks.push(Buffer.from(buffers[i])); + } + } + if (typeof position === "number") { + await Deno.seekSync(fd, position, Deno.SeekMode.Start); + } + const buffer = Buffer.concat(chunks); + let currentOffset = 0; + while (currentOffset < buffer.byteLength) { + currentOffset += await Deno.writeSync(fd, buffer.subarray(currentOffset)); + } + return currentOffset - offset; + }; + + fd = getValidatedFd(fd); + validateBufferArray(buffers); + callback = maybeCallback(callback || position); + + if (buffers.length === 0) { + process.nextTick(callback, null, 0, buffers); + return; + } + + if (typeof position !== "number") position = null; + + innerWritev(fd, buffers, position).then( + (nwritten) => { + callback(null, nwritten, buffers); + }, + (err) => callback(err), + ); +} + +export function writevSync(fd, buffers, position) { + const innerWritev = (fd, buffers, position) => { + const chunks = []; + const offset = 0; + for (let i = 0; i < buffers.length; i++) { + if (Buffer.isBuffer(buffers[i])) { + chunks.push(buffers[i]); + } else { + chunks.push(Buffer.from(buffers[i])); + } + } + if (typeof position === "number") { + Deno.seekSync(fd, position, Deno.SeekMode.Start); + } + const buffer = Buffer.concat(chunks); + let currentOffset = 0; + while (currentOffset < buffer.byteLength) { + currentOffset += Deno.writeSync(fd, buffer.subarray(currentOffset)); + } + return currentOffset - offset; + }; + + fd = getValidatedFd(fd); + validateBufferArray(buffers); + + if (buffers.length === 0) { + return 0; + } + + if (typeof position !== "number") position = null; + + return innerWritev(fd, buffers, position); +} diff --git a/ext/node/polyfills/_global.d.ts b/ext/node/polyfills/_global.d.ts new file mode 100644 index 00000000000000..4b017c40443a44 --- /dev/null +++ b/ext/node/polyfills/_global.d.ts @@ -0,0 +1,66 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. +import { EventEmitter } from "internal:deno_node/polyfills/_events.d.ts"; +import { Buffer } from "internal:deno_node/polyfills/buffer.ts"; + +/** One of: + * | "ascii" + * | "utf8" + * | "utf-8" + * | "utf16le" + * | "ucs2" + * | "ucs-2" + * | "base64" + * | "base64url" + * | "latin1" + * | "binary" + * | "hex"; + */ +export type BufferEncoding = string; + +export interface Buffered { + chunk: Buffer; + encoding: string; + callback: (err?: Error | null) => void; +} + +export interface ErrnoException extends Error { + errno?: number | undefined; + code?: string | undefined; + path?: string | undefined; + syscall?: string | undefined; +} + +export interface ReadableStream extends EventEmitter { + readable: boolean; + read(size?: number): string | Buffer; + setEncoding(encoding: BufferEncoding): this; + pause(): this; + resume(): this; + isPaused(): boolean; + pipe( + destination: T, + options?: { end?: boolean | undefined }, + ): T; + unpipe(destination?: WritableStream): this; + unshift(chunk: string | Uint8Array, encoding?: BufferEncoding): void; + wrap(oldStream: ReadableStream): this; + [Symbol.asyncIterator](): AsyncIterableIterator; +} + +export interface WritableStream extends EventEmitter { + writable: boolean; + write( + buffer: Uint8Array | string, + cb?: (err?: Error | null) => void, + ): boolean; + write( + str: string, + encoding?: BufferEncoding, + cb?: (err?: Error | null) => void, + ): boolean; + end(cb?: () => void): void; + end(data: string | Uint8Array, cb?: () => void): void; + end(str: string, encoding?: BufferEncoding, cb?: () => void): void; +} + +export interface ReadWriteStream extends ReadableStream, WritableStream {} diff --git a/ext/node/polyfills/_http_agent.mjs b/ext/node/polyfills/_http_agent.mjs new file mode 100644 index 00000000000000..20c1eaf0bf73dc --- /dev/null +++ b/ext/node/polyfills/_http_agent.mjs @@ -0,0 +1,526 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. +// Copyright Joyent and Node contributors. All rights reserved. MIT license. + +import * as net from "internal:deno_node/polyfills/net.ts"; +import EventEmitter from "internal:deno_node/polyfills/events.ts"; +import { debuglog } from "internal:deno_node/polyfills/internal/util/debuglog.ts"; +let debug = debuglog("http", (fn) => { + debug = fn; +}); +import { AsyncResource } from "internal:deno_node/polyfills/async_hooks.ts"; +import { symbols } from "internal:deno_node/polyfills/internal/async_hooks.ts"; +// deno-lint-ignore camelcase +const { async_id_symbol } = symbols; +import { ERR_OUT_OF_RANGE } from "internal:deno_node/polyfills/internal/errors.ts"; +import { once } from "internal:deno_node/polyfills/internal/util.mjs"; +import { + validateNumber, + validateOneOf, + validateString, +} from "internal:deno_node/polyfills/internal/validators.mjs"; + +const kOnKeylog = Symbol("onkeylog"); +const kRequestOptions = Symbol("requestOptions"); +const kRequestAsyncResource = Symbol("requestAsyncResource"); +// New Agent code. + +// The largest departure from the previous implementation is that +// an Agent instance holds connections for a variable number of host:ports. +// Surprisingly, this is still API compatible as far as third parties are +// concerned. The only code that really notices the difference is the +// request object. + +// Another departure is that all code related to HTTP parsing is in +// ClientRequest.onSocket(). The Agent is now *strictly* +// concerned with managing a connection pool. + +class ReusedHandle { + constructor(type, handle) { + this.type = type; + this.handle = handle; + } +} + +function freeSocketErrorListener(err) { + // deno-lint-ignore no-this-alias + const socket = this; + debug("SOCKET ERROR on FREE socket:", err.message, err.stack); + socket.destroy(); + socket.emit("agentRemove"); +} + +export function Agent(options) { + if (!(this instanceof Agent)) { + return new Agent(options); + } + + EventEmitter.call(this); + + this.defaultPort = 80; + this.protocol = "http:"; + + this.options = { __proto__: null, ...options }; + + // Don't confuse net and make it think that we're connecting to a pipe + this.options.path = null; + this.requests = Object.create(null); + this.sockets = Object.create(null); + this.freeSockets = Object.create(null); + this.keepAliveMsecs = this.options.keepAliveMsecs || 1000; + this.keepAlive = this.options.keepAlive || false; + this.maxSockets = this.options.maxSockets || Agent.defaultMaxSockets; + this.maxFreeSockets = this.options.maxFreeSockets || 256; + this.scheduling = this.options.scheduling || "lifo"; + this.maxTotalSockets = this.options.maxTotalSockets; + this.totalSocketCount = 0; + + validateOneOf(this.scheduling, "scheduling", ["fifo", "lifo"]); + + if (this.maxTotalSockets !== undefined) { + validateNumber(this.maxTotalSockets, "maxTotalSockets"); + if (this.maxTotalSockets <= 0 || Number.isNaN(this.maxTotalSockets)) { + throw new ERR_OUT_OF_RANGE( + "maxTotalSockets", + "> 0", + this.maxTotalSockets, + ); + } + } else { + this.maxTotalSockets = Infinity; + } + + this.on("free", (socket, options) => { + const name = this.getName(options); + debug("agent.on(free)", name); + + // TODO(ronag): socket.destroy(err) might have been called + // before coming here and have an 'error' scheduled. In the + // case of socket.destroy() below this 'error' has no handler + // and could cause unhandled exception. + + if (!socket.writable) { + socket.destroy(); + return; + } + + const requests = this.requests[name]; + if (requests && requests.length) { + const req = requests.shift(); + const reqAsyncRes = req[kRequestAsyncResource]; + if (reqAsyncRes) { + // Run request within the original async context. + reqAsyncRes.runInAsyncScope(() => { + asyncResetHandle(socket); + setRequestSocket(this, req, socket); + }); + req[kRequestAsyncResource] = null; + } else { + setRequestSocket(this, req, socket); + } + if (requests.length === 0) { + delete this.requests[name]; + } + return; + } + + // If there are no pending requests, then put it in + // the freeSockets pool, but only if we're allowed to do so. + const req = socket._httpMessage; + if (!req || !req.shouldKeepAlive || !this.keepAlive) { + socket.destroy(); + return; + } + + const freeSockets = this.freeSockets[name] || []; + const freeLen = freeSockets.length; + let count = freeLen; + if (this.sockets[name]) { + count += this.sockets[name].length; + } + + if ( + this.totalSocketCount > this.maxTotalSockets || + count > this.maxSockets || + freeLen >= this.maxFreeSockets || + !this.keepSocketAlive(socket) + ) { + socket.destroy(); + return; + } + + this.freeSockets[name] = freeSockets; + socket[async_id_symbol] = -1; + socket._httpMessage = null; + this.removeSocket(socket, options); + + socket.once("error", freeSocketErrorListener); + freeSockets.push(socket); + }); + + // Don't emit keylog events unless there is a listener for them. + this.on("newListener", maybeEnableKeylog); +} +Object.setPrototypeOf(Agent.prototype, EventEmitter.prototype); +Object.setPrototypeOf(Agent, EventEmitter); + +function maybeEnableKeylog(eventName) { + if (eventName === "keylog") { + this.removeListener("newListener", maybeEnableKeylog); + // Future sockets will listen on keylog at creation. + // deno-lint-ignore no-this-alias + const agent = this; + this[kOnKeylog] = function onkeylog(keylog) { + agent.emit("keylog", keylog, this); + }; + // Existing sockets will start listening on keylog now. + const sockets = ObjectValues(this.sockets); + for (let i = 0; i < sockets.length; i++) { + sockets[i].on("keylog", this[kOnKeylog]); + } + } +} + +Agent.defaultMaxSockets = Infinity; + +Agent.prototype.createConnection = net.createConnection; + +// Get the key for a given set of request options +Agent.prototype.getName = function getName(options = {}) { + let name = options.host || "localhost"; + + name += ":"; + if (options.port) { + name += options.port; + } + + name += ":"; + if (options.localAddress) { + name += options.localAddress; + } + + // Pacify parallel/test-http-agent-getname by only appending + // the ':' when options.family is set. + if (options.family === 4 || options.family === 6) { + name += `:${options.family}`; + } + + if (options.socketPath) { + name += `:${options.socketPath}`; + } + + return name; +}; + +Agent.prototype.addRequest = function addRequest( + req, + options, + port, /* legacy */ + localAddress, /* legacy */ +) { + // Legacy API: addRequest(req, host, port, localAddress) + if (typeof options === "string") { + options = { + __proto__: null, + host: options, + port, + localAddress, + }; + } + + options = { __proto__: null, ...options, ...this.options }; + if (options.socketPath) { + options.path = options.socketPath; + } + + if (!options.servername && options.servername !== "") { + options.servername = calculateServerName(options, req); + } + + const name = this.getName(options); + if (!this.sockets[name]) { + this.sockets[name] = []; + } + + const freeSockets = this.freeSockets[name]; + let socket; + if (freeSockets) { + while (freeSockets.length && freeSockets[0].destroyed) { + freeSockets.shift(); + } + socket = this.scheduling === "fifo" + ? freeSockets.shift() + : freeSockets.pop(); + if (!freeSockets.length) { + delete this.freeSockets[name]; + } + } + + const freeLen = freeSockets ? freeSockets.length : 0; + const sockLen = freeLen + this.sockets[name].length; + + if (socket) { + asyncResetHandle(socket); + this.reuseSocket(socket, req); + setRequestSocket(this, req, socket); + this.sockets[name].push(socket); + } else if ( + sockLen < this.maxSockets && + this.totalSocketCount < this.maxTotalSockets + ) { + debug("call onSocket", sockLen, freeLen); + // If we are under maxSockets create a new one. + this.createSocket(req, options, (err, socket) => { + if (err) { + req.onSocket(socket, err); + } else { + setRequestSocket(this, req, socket); + } + }); + } else { + debug("wait for socket"); + // We are over limit so we'll add it to the queue. + if (!this.requests[name]) { + this.requests[name] = []; + } + + // Used to create sockets for pending requests from different origin + req[kRequestOptions] = options; + // Used to capture the original async context. + req[kRequestAsyncResource] = new AsyncResource("QueuedRequest"); + + this.requests[name].push(req); + } +}; + +Agent.prototype.createSocket = function createSocket(req, options, cb) { + options = { __proto__: null, ...options, ...this.options }; + if (options.socketPath) { + options.path = options.socketPath; + } + + if (!options.servername && options.servername !== "") { + options.servername = calculateServerName(options, req); + } + + const name = this.getName(options); + options._agentKey = name; + + debug("createConnection", name, options); + options.encoding = null; + + const oncreate = once((err, s) => { + if (err) { + return cb(err); + } + if (!this.sockets[name]) { + this.sockets[name] = []; + } + this.sockets[name].push(s); + this.totalSocketCount++; + debug("sockets", name, this.sockets[name].length, this.totalSocketCount); + installListeners(this, s, options); + cb(null, s); + }); + + const newSocket = this.createConnection(options, oncreate); + if (newSocket) { + oncreate(null, newSocket); + } +}; + +function calculateServerName(options, req) { + let servername = options.host; + const hostHeader = req.getHeader("host"); + if (hostHeader) { + validateString(hostHeader, "options.headers.host"); + + // abc => abc + // abc:123 => abc + // [::1] => ::1 + // [::1]:123 => ::1 + if (hostHeader.startsWith("[")) { + const index = hostHeader.indexOf("]"); + if (index === -1) { + // Leading '[', but no ']'. Need to do something... + servername = hostHeader; + } else { + servername = hostHeader.slice(1, index); + } + } else { + servername = hostHeader.split(":", 1)[0]; + } + } + // Don't implicitly set invalid (IP) servernames. + if (net.isIP(servername)) { + servername = ""; + } + return servername; +} + +function installListeners(agent, s, options) { + function onFree() { + debug("CLIENT socket onFree"); + agent.emit("free", s, options); + } + s.on("free", onFree); + + function onClose(_err) { + debug("CLIENT socket onClose"); + // This is the only place where sockets get removed from the Agent. + // If you want to remove a socket from the pool, just close it. + // All socket errors end in a close event anyway. + agent.totalSocketCount--; + agent.removeSocket(s, options); + } + s.on("close", onClose); + + function onTimeout() { + debug("CLIENT socket onTimeout"); + + // Destroy if in free list. + // TODO(ronag): Always destroy, even if not in free list. + const sockets = agent.freeSockets; + if (Object.keys(sockets).some((name) => sockets[name].includes(s))) { + return s.destroy(); + } + } + s.on("timeout", onTimeout); + + function onRemove() { + // We need this function for cases like HTTP 'upgrade' + // (defined by WebSockets) where we need to remove a socket from the + // pool because it'll be locked up indefinitely + debug("CLIENT socket onRemove"); + agent.totalSocketCount--; + agent.removeSocket(s, options); + s.removeListener("close", onClose); + s.removeListener("free", onFree); + s.removeListener("timeout", onTimeout); + s.removeListener("agentRemove", onRemove); + } + s.on("agentRemove", onRemove); + + if (agent[kOnKeylog]) { + s.on("keylog", agent[kOnKeylog]); + } +} + +Agent.prototype.removeSocket = function removeSocket(s, options) { + const name = this.getName(options); + debug("removeSocket", name, "writable:", s.writable); + const sets = [this.sockets]; + + // If the socket was destroyed, remove it from the free buffers too. + if (!s.writable) { + sets.push(this.freeSockets); + } + + for (let sk = 0; sk < sets.length; sk++) { + const sockets = sets[sk]; + + if (sockets[name]) { + const index = sockets[name].indexOf(s); + if (index !== -1) { + sockets[name].splice(index, 1); + // Don't leak + if (sockets[name].length === 0) { + delete sockets[name]; + } + } + } + } + + let req; + if (this.requests[name] && this.requests[name].length) { + debug("removeSocket, have a request, make a socket"); + req = this.requests[name][0]; + } else { + // TODO(rickyes): this logic will not be FIFO across origins. + // There might be older requests in a different origin, but + // if the origin which releases the socket has pending requests + // that will be prioritized. + const keys = Object.keys(this.requests); + for (let i = 0; i < keys.length; i++) { + const prop = keys[i]; + // Check whether this specific origin is already at maxSockets + if (this.sockets[prop] && this.sockets[prop].length) break; + debug( + "removeSocket, have a request with different origin," + + " make a socket", + ); + req = this.requests[prop][0]; + options = req[kRequestOptions]; + break; + } + } + + if (req && options) { + req[kRequestOptions] = undefined; + // If we have pending requests and a socket gets closed make a new one + this.createSocket(req, options, (err, socket) => { + if (err) { + req.onSocket(socket, err); + } else { + socket.emit("free"); + } + }); + } +}; + +Agent.prototype.keepSocketAlive = function keepSocketAlive(socket) { + socket.setKeepAlive(true, this.keepAliveMsecs); + socket.unref(); + + const agentTimeout = this.options.timeout || 0; + if (socket.timeout !== agentTimeout) { + socket.setTimeout(agentTimeout); + } + + return true; +}; + +Agent.prototype.reuseSocket = function reuseSocket(socket, req) { + debug("have free socket"); + socket.removeListener("error", freeSocketErrorListener); + req.reusedSocket = true; + socket.ref(); +}; + +Agent.prototype.destroy = function destroy() { + const sets = [this.freeSockets, this.sockets]; + for (let s = 0; s < sets.length; s++) { + const set = sets[s]; + const keys = Object.keys(set); + for (let v = 0; v < keys.length; v++) { + const setName = set[keys[v]]; + for (let n = 0; n < setName.length; n++) { + setName[n].destroy(); + } + } + } +}; + +function setRequestSocket(agent, req, socket) { + req.onSocket(socket); + const agentTimeout = agent.options.timeout || 0; + if (req.timeout === undefined || req.timeout === agentTimeout) { + return; + } + socket.setTimeout(req.timeout); +} + +function asyncResetHandle(socket) { + // Guard against an uninitialized or user supplied Socket. + const handle = socket._handle; + if (handle && typeof handle.asyncReset === "function") { + // Assign the handle a new asyncId and run any destroy()/init() hooks. + handle.asyncReset(new ReusedHandle(handle.getProviderType(), handle)); + socket[async_id_symbol] = handle.getAsyncId(); + } +} + +export const globalAgent = new Agent(); +export default { + Agent, + globalAgent, +}; diff --git a/ext/node/polyfills/_http_common.ts b/ext/node/polyfills/_http_common.ts new file mode 100644 index 00000000000000..616fbb65daadb5 --- /dev/null +++ b/ext/node/polyfills/_http_common.ts @@ -0,0 +1,29 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. +// Copyright Joyent and Node contributors. All rights reserved. MIT license. + +const tokenRegExp = /^[\^_`a-zA-Z\-0-9!#$%&'*+.|~]+$/; +/** + * Verifies that the given val is a valid HTTP token + * per the rules defined in RFC 7230 + * See https://tools.ietf.org/html/rfc7230#section-3.2.6 + */ +function checkIsHttpToken(val: string) { + return tokenRegExp.test(val); +} + +const headerCharRegex = /[^\t\x20-\x7e\x80-\xff]/; +/** + * True if val contains an invalid field-vchar + * field-value = *( field-content / obs-fold ) + * field-content = field-vchar [ 1*( SP / HTAB ) field-vchar ] + * field-vchar = VCHAR / obs-text + */ +function checkInvalidHeaderChar(val: string) { + return headerCharRegex.test(val); +} + +export const chunkExpression = /(?:^|\W)chunked(?:$|\W)/i; +export { + checkInvalidHeaderChar as _checkInvalidHeaderChar, + checkIsHttpToken as _checkIsHttpToken, +}; diff --git a/ext/node/polyfills/_http_outgoing.ts b/ext/node/polyfills/_http_outgoing.ts new file mode 100644 index 00000000000000..cb0312d4a82c3e --- /dev/null +++ b/ext/node/polyfills/_http_outgoing.ts @@ -0,0 +1,1099 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. +// Copyright Joyent and Node contributors. All rights reserved. MIT license. + +import { getDefaultHighWaterMark } from "internal:deno_node/polyfills/internal/streams/state.mjs"; +import assert from "internal:deno_node/polyfills/internal/assert.mjs"; +import EE from "internal:deno_node/polyfills/events.ts"; +import { Stream } from "internal:deno_node/polyfills/stream.ts"; +import { deprecate } from "internal:deno_node/polyfills/util.ts"; +import type { Socket } from "internal:deno_node/polyfills/net.ts"; +import { + kNeedDrain, + kOutHeaders, + utcDate, +} from "internal:deno_node/polyfills/internal/http.ts"; +import { Buffer } from "internal:deno_node/polyfills/buffer.ts"; +import { + _checkInvalidHeaderChar as checkInvalidHeaderChar, + _checkIsHttpToken as checkIsHttpToken, + chunkExpression as RE_TE_CHUNKED, +} from "internal:deno_node/polyfills/_http_common.ts"; +import { + defaultTriggerAsyncIdScope, + symbols, +} from "internal:deno_node/polyfills/internal/async_hooks.ts"; +// deno-lint-ignore camelcase +const { async_id_symbol } = symbols; +import { + ERR_HTTP_HEADERS_SENT, + ERR_HTTP_INVALID_HEADER_VALUE, + ERR_HTTP_TRAILER_INVALID, + ERR_INVALID_ARG_TYPE, + ERR_INVALID_ARG_VALUE, + ERR_INVALID_CHAR, + ERR_INVALID_HTTP_TOKEN, + ERR_METHOD_NOT_IMPLEMENTED, + ERR_STREAM_ALREADY_FINISHED, + ERR_STREAM_CANNOT_PIPE, + ERR_STREAM_DESTROYED, + ERR_STREAM_NULL_VALUES, + ERR_STREAM_WRITE_AFTER_END, + hideStackFrames, +} from "internal:deno_node/polyfills/internal/errors.ts"; +import { validateString } from "internal:deno_node/polyfills/internal/validators.mjs"; +import { isUint8Array } from "internal:deno_node/polyfills/internal/util/types.ts"; + +import { debuglog } from "internal:deno_node/polyfills/internal/util/debuglog.ts"; +let debug = debuglog("http", (fn) => { + debug = fn; +}); + +const HIGH_WATER_MARK = getDefaultHighWaterMark(); + +const kCorked = Symbol("corked"); + +const nop = () => {}; + +const RE_CONN_CLOSE = /(?:^|\W)close(?:$|\W)/i; + +// isCookieField performs a case-insensitive comparison of a provided string +// against the word "cookie." As of V8 6.6 this is faster than handrolling or +// using a case-insensitive RegExp. +function isCookieField(s: string) { + return s.length === 6 && s.toLowerCase() === "cookie"; +} + +// deno-lint-ignore no-explicit-any +export function OutgoingMessage(this: any) { + Stream.call(this); + + // Queue that holds all currently pending data, until the response will be + // assigned to the socket (until it will its turn in the HTTP pipeline). + this.outputData = []; + + // `outputSize` is an approximate measure of how much data is queued on this + // response. `_onPendingData` will be invoked to update similar global + // per-connection counter. That counter will be used to pause/unpause the + // TCP socket and HTTP Parser and thus handle the backpressure. + this.outputSize = 0; + + this.writable = true; + this.destroyed = false; + + this._last = false; + this.chunkedEncoding = false; + this.shouldKeepAlive = true; + this.maxRequestsOnConnectionReached = false; + this._defaultKeepAlive = true; + this.useChunkedEncodingByDefault = true; + this.sendDate = false; + this._removedConnection = false; + this._removedContLen = false; + this._removedTE = false; + + this._contentLength = null; + this._hasBody = true; + this._trailer = ""; + this[kNeedDrain] = false; + + this.finished = false; + this._headerSent = false; + this[kCorked] = 0; + this._closed = false; + + this.socket = null; + this._header = null; + this[kOutHeaders] = null; + + this._keepAliveTimeout = 0; + + this._onPendingData = nop; +} +Object.setPrototypeOf(OutgoingMessage.prototype, Stream.prototype); +Object.setPrototypeOf(OutgoingMessage, Stream); + +Object.defineProperty(OutgoingMessage.prototype, "writableFinished", { + get() { + return ( + this.finished && + this.outputSize === 0 && + (!this.socket || this.socket.writableLength === 0) + ); + }, +}); + +Object.defineProperty(OutgoingMessage.prototype, "writableObjectMode", { + get() { + return false; + }, +}); + +Object.defineProperty(OutgoingMessage.prototype, "writableLength", { + get() { + return this.outputSize + (this.socket ? this.socket.writableLength : 0); + }, +}); + +Object.defineProperty(OutgoingMessage.prototype, "writableHighWaterMark", { + get() { + return this.socket ? this.socket.writableHighWaterMark : HIGH_WATER_MARK; + }, +}); + +Object.defineProperty(OutgoingMessage.prototype, "writableCorked", { + get() { + const corked = this.socket ? this.socket.writableCorked : 0; + return corked + this[kCorked]; + }, +}); + +Object.defineProperty(OutgoingMessage.prototype, "_headers", { + get: deprecate( + // deno-lint-ignore no-explicit-any + function (this: any) { + return this.getHeaders(); + }, + "OutgoingMessage.prototype._headers is deprecated", + "DEP0066", + ), + set: deprecate( + // deno-lint-ignore no-explicit-any + function (this: any, val: any) { + if (val == null) { + this[kOutHeaders] = null; + } else if (typeof val === "object") { + const headers = this[kOutHeaders] = Object.create(null); + const keys = Object.keys(val); + // Retain for(;;) loop for performance reasons + // Refs: https://github.com/nodejs/node/pull/30958 + for (let i = 0; i < keys.length; ++i) { + const name = keys[i]; + headers[name.toLowerCase()] = [name, val[name]]; + } + } + }, + "OutgoingMessage.prototype._headers is deprecated", + "DEP0066", + ), +}); + +Object.defineProperty(OutgoingMessage.prototype, "connection", { + get: function () { + return this.socket; + }, + set: function (val) { + this.socket = val; + }, +}); + +Object.defineProperty(OutgoingMessage.prototype, "_headerNames", { + get: deprecate( + // deno-lint-ignore no-explicit-any + function (this: any) { + const headers = this[kOutHeaders]; + if (headers !== null) { + const out = Object.create(null); + const keys = Object.keys(headers); + // Retain for(;;) loop for performance reasons + // Refs: https://github.com/nodejs/node/pull/30958 + for (let i = 0; i < keys.length; ++i) { + const key = keys[i]; + const val = headers[key][0]; + out[key] = val; + } + return out; + } + return null; + }, + "OutgoingMessage.prototype._headerNames is deprecated", + "DEP0066", + ), + set: deprecate( + // deno-lint-ignore no-explicit-any + function (this: any, val: any) { + if (typeof val === "object" && val !== null) { + const headers = this[kOutHeaders]; + if (!headers) { + return; + } + const keys = Object.keys(val); + // Retain for(;;) loop for performance reasons + // Refs: https://github.com/nodejs/node/pull/30958 + for (let i = 0; i < keys.length; ++i) { + const header = headers[keys[i]]; + if (header) { + header[0] = val[keys[i]]; + } + } + } + }, + "OutgoingMessage.prototype._headerNames is deprecated", + "DEP0066", + ), +}); + +OutgoingMessage.prototype._renderHeaders = function _renderHeaders() { + if (this._header) { + throw new ERR_HTTP_HEADERS_SENT("render"); + } + + const headersMap = this[kOutHeaders]; + // deno-lint-ignore no-explicit-any + const headers: any = {}; + + if (headersMap !== null) { + const keys = Object.keys(headersMap); + // Retain for(;;) loop for performance reasons + // Refs: https://github.com/nodejs/node/pull/30958 + for (let i = 0, l = keys.length; i < l; i++) { + const key = keys[i]; + headers[headersMap[key][0]] = headersMap[key][1]; + } + } + return headers; +}; + +OutgoingMessage.prototype.cork = function () { + if (this.socket) { + this.socket.cork(); + } else { + this[kCorked]++; + } +}; + +OutgoingMessage.prototype.uncork = function () { + if (this.socket) { + this.socket.uncork(); + } else if (this[kCorked]) { + this[kCorked]--; + } +}; + +OutgoingMessage.prototype.setTimeout = function setTimeout( + msecs: number, + callback?: (...args: unknown[]) => void, +) { + if (callback) { + this.on("timeout", callback); + } + + if (!this.socket) { + // deno-lint-ignore no-explicit-any + this.once("socket", function socketSetTimeoutOnConnect(socket: any) { + socket.setTimeout(msecs); + }); + } else { + this.socket.setTimeout(msecs); + } + return this; +}; + +// It's possible that the socket will be destroyed, and removed from +// any messages, before ever calling this. In that case, just skip +// it, since something else is destroying this connection anyway. +OutgoingMessage.prototype.destroy = function destroy(error: unknown) { + if (this.destroyed) { + return this; + } + this.destroyed = true; + + if (this.socket) { + this.socket.destroy(error); + } else { + // deno-lint-ignore no-explicit-any + this.once("socket", function socketDestroyOnConnect(socket: any) { + socket.destroy(error); + }); + } + + return this; +}; + +// This abstract either writing directly to the socket or buffering it. +OutgoingMessage.prototype._send = function _send( + // deno-lint-ignore no-explicit-any + data: any, + encoding: string | null, + callback: () => void, +) { + // This is a shameful hack to get the headers and first body chunk onto + // the same packet. Future versions of Node are going to take care of + // this at a lower level and in a more general way. + if (!this._headerSent) { + if ( + typeof data === "string" && + (encoding === "utf8" || encoding === "latin1" || !encoding) + ) { + data = this._header + data; + } else { + const header = this._header; + this.outputData.unshift({ + data: header, + encoding: "latin1", + callback: null, + }); + this.outputSize += header.length; + this._onPendingData(header.length); + } + this._headerSent = true; + } + return this._writeRaw(data, encoding, callback); +}; + +OutgoingMessage.prototype._writeRaw = _writeRaw; +function _writeRaw( + // deno-lint-ignore no-explicit-any + this: any, + // deno-lint-ignore no-explicit-any + data: any, + encoding: string | null, + callback: () => void, +) { + const conn = this.socket; + if (conn && conn.destroyed) { + // The socket was destroyed. If we're still trying to write to it, + // then we haven't gotten the 'close' event yet. + return false; + } + + if (typeof encoding === "function") { + callback = encoding; + encoding = null; + } + + if (conn && conn._httpMessage === this && conn.writable) { + // There might be pending data in the this.output buffer. + if (this.outputData.length) { + this._flushOutput(conn); + } + // Directly write to socket. + return conn.write(data, encoding, callback); + } + // Buffer, as long as we're not destroyed. + this.outputData.push({ data, encoding, callback }); + this.outputSize += data.length; + this._onPendingData(data.length); + return this.outputSize < HIGH_WATER_MARK; +} + +OutgoingMessage.prototype._storeHeader = _storeHeader; +// deno-lint-ignore no-explicit-any +function _storeHeader(this: any, firstLine: any, headers: any) { + // firstLine in the case of request is: 'GET /index.html HTTP/1.1\r\n' + // in the case of response it is: 'HTTP/1.1 200 OK\r\n' + const state = { + connection: false, + contLen: false, + te: false, + date: false, + expect: false, + trailer: false, + header: firstLine, + }; + + if (headers) { + if (headers === this[kOutHeaders]) { + for (const key in headers) { + if (Object.hasOwn(headers, key)) { + const entry = headers[key]; + processHeader(this, state, entry[0], entry[1], false); + } + } + } else if (Array.isArray(headers)) { + if (headers.length && Array.isArray(headers[0])) { + for (let i = 0; i < headers.length; i++) { + const entry = headers[i]; + processHeader(this, state, entry[0], entry[1], true); + } + } else { + if (headers.length % 2 !== 0) { + throw new ERR_INVALID_ARG_VALUE("headers", headers); + } + + for (let n = 0; n < headers.length; n += 2) { + processHeader(this, state, headers[n + 0], headers[n + 1], true); + } + } + } else { + for (const key in headers) { + if (Object.hasOwn(headers, key)) { + processHeader(this, state, key, headers[key], true); + } + } + } + } + + let { header } = state; + + // Date header + if (this.sendDate && !state.date) { + header += "Date: " + utcDate() + "\r\n"; + } + + // Force the connection to close when the response is a 204 No Content or + // a 304 Not Modified and the user has set a "Transfer-Encoding: chunked" + // header. + // + // RFC 2616 mandates that 204 and 304 responses MUST NOT have a body but + // node.js used to send out a zero chunk anyway to accommodate clients + // that don't have special handling for those responses. + // + // It was pointed out that this might confuse reverse proxies to the point + // of creating security liabilities, so suppress the zero chunk and force + // the connection to close. + if ( + this.chunkedEncoding && (this.statusCode === 204 || + this.statusCode === 304) + ) { + debug( + this.statusCode + " response should not use chunked encoding," + + " closing connection.", + ); + this.chunkedEncoding = false; + this.shouldKeepAlive = false; + } + + // keep-alive logic + if (this._removedConnection) { + this._last = true; + this.shouldKeepAlive = false; + } else if (!state.connection) { + const shouldSendKeepAlive = this.shouldKeepAlive && + (state.contLen || this.useChunkedEncodingByDefault || this.agent); + if (shouldSendKeepAlive && this.maxRequestsOnConnectionReached) { + header += "Connection: close\r\n"; + } else if (shouldSendKeepAlive) { + header += "Connection: keep-alive\r\n"; + if (this._keepAliveTimeout && this._defaultKeepAlive) { + const timeoutSeconds = Math.floor(this._keepAliveTimeout / 1000); + header += `Keep-Alive: timeout=${timeoutSeconds}\r\n`; + } + } else { + this._last = true; + header += "Connection: close\r\n"; + } + } + + if (!state.contLen && !state.te) { + if (!this._hasBody) { + // Make sure we don't end the 0\r\n\r\n at the end of the message. + this.chunkedEncoding = false; + } else if (!this.useChunkedEncodingByDefault) { + this._last = true; + } else if ( + !state.trailer && + !this._removedContLen && + typeof this._contentLength === "number" + ) { + header += "Content-Length: " + this._contentLength + "\r\n"; + } else if (!this._removedTE) { + header += "Transfer-Encoding: chunked\r\n"; + this.chunkedEncoding = true; + } else { + // We should only be able to get here if both Content-Length and + // Transfer-Encoding are removed by the user. + // See: test/parallel/test-http-remove-header-stays-removed.js + debug("Both Content-Length and Transfer-Encoding are removed"); + } + } + + // Test non-chunked message does not have trailer header set, + // message will be terminated by the first empty line after the + // header fields, regardless of the header fields present in the + // message, and thus cannot contain a message body or 'trailers'. + if (this.chunkedEncoding !== true && state.trailer) { + throw new ERR_HTTP_TRAILER_INVALID(); + } + + this._header = header + "\r\n"; + this._headerSent = false; + + // Wait until the first body chunk, or close(), is sent to flush, + // UNLESS we're sending Expect: 100-continue. + if (state.expect) this._send(""); +} + +function processHeader( + // deno-lint-ignore no-explicit-any + self: any, + // deno-lint-ignore no-explicit-any + state: any, + // deno-lint-ignore no-explicit-any + key: any, + // deno-lint-ignore no-explicit-any + value: any, + // deno-lint-ignore no-explicit-any + validate: any, +) { + if (validate) { + validateHeaderName(key); + } + if (Array.isArray(value)) { + if (value.length < 2 || !isCookieField(key)) { + // Retain for(;;) loop for performance reasons + // Refs: https://github.com/nodejs/node/pull/30958 + for (let i = 0; i < value.length; i++) { + storeHeader(self, state, key, value[i], validate); + } + return; + } + value = value.join("; "); + } + storeHeader(self, state, key, value, validate); +} + +function storeHeader( + // deno-lint-ignore no-explicit-any + self: any, + // deno-lint-ignore no-explicit-any + state: any, + // deno-lint-ignore no-explicit-any + key: any, + // deno-lint-ignore no-explicit-any + value: any, + // deno-lint-ignore no-explicit-any + validate: any, +) { + if (validate) { + validateHeaderValue(key, value); + } + state.header += key + ": " + value + "\r\n"; + matchHeader(self, state, key, value); +} + +// deno-lint-ignore no-explicit-any +function matchHeader(self: any, state: any, field: string, value: any) { + if (field.length < 4 || field.length > 17) { + return; + } + field = field.toLowerCase(); + switch (field) { + case "connection": + state.connection = true; + self._removedConnection = false; + if (RE_CONN_CLOSE.test(value)) { + self._last = true; + } else { + self.shouldKeepAlive = true; + } + break; + case "transfer-encoding": + state.te = true; + self._removedTE = false; + if (RE_TE_CHUNKED.test(value)) { + self.chunkedEncoding = true; + } + break; + case "content-length": + state.contLen = true; + self._removedContLen = false; + break; + case "date": + case "expect": + case "trailer": + state[field] = true; + break; + case "keep-alive": + self._defaultKeepAlive = false; + break; + } +} + +export const validateHeaderName = hideStackFrames((name) => { + if (typeof name !== "string" || !name || !checkIsHttpToken(name)) { + throw new ERR_INVALID_HTTP_TOKEN("Header name", name); + } +}); + +export const validateHeaderValue = hideStackFrames((name, value) => { + if (value === undefined) { + throw new ERR_HTTP_INVALID_HEADER_VALUE(value, name); + } + if (checkInvalidHeaderChar(value)) { + debug('Header "%s" contains invalid characters', name); + throw new ERR_INVALID_CHAR("header content", name); + } +}); + +OutgoingMessage.prototype.setHeader = function setHeader( + name: string, + value: string, +) { + if (this._header) { + throw new ERR_HTTP_HEADERS_SENT("set"); + } + validateHeaderName(name); + validateHeaderValue(name, value); + + let headers = this[kOutHeaders]; + if (headers === null) { + this[kOutHeaders] = headers = Object.create(null); + } + + headers[name.toLowerCase()] = [name, value]; + return this; +}; + +OutgoingMessage.prototype.getHeader = function getHeader(name: string) { + validateString(name, "name"); + + const headers = this[kOutHeaders]; + if (headers === null) { + return; + } + + const entry = headers[name.toLowerCase()]; + return entry && entry[1]; +}; + +// Returns an array of the names of the current outgoing headers. +OutgoingMessage.prototype.getHeaderNames = function getHeaderNames() { + return this[kOutHeaders] !== null ? Object.keys(this[kOutHeaders]) : []; +}; + +// Returns an array of the names of the current outgoing raw headers. +OutgoingMessage.prototype.getRawHeaderNames = function getRawHeaderNames() { + const headersMap = this[kOutHeaders]; + if (headersMap === null) return []; + + const values = Object.values(headersMap); + const headers = Array(values.length); + // Retain for(;;) loop for performance reasons + // Refs: https://github.com/nodejs/node/pull/30958 + for (let i = 0, l = values.length; i < l; i++) { + // deno-lint-ignore no-explicit-any + headers[i] = (values as any)[i][0]; + } + + return headers; +}; + +// Returns a shallow copy of the current outgoing headers. +OutgoingMessage.prototype.getHeaders = function getHeaders() { + const headers = this[kOutHeaders]; + const ret = Object.create(null); + if (headers) { + const keys = Object.keys(headers); + // Retain for(;;) loop for performance reasons + // Refs: https://github.com/nodejs/node/pull/30958 + for (let i = 0; i < keys.length; ++i) { + const key = keys[i]; + const val = headers[key][1]; + ret[key] = val; + } + } + return ret; +}; + +OutgoingMessage.prototype.hasHeader = function hasHeader(name: string) { + validateString(name, "name"); + return this[kOutHeaders] !== null && + !!this[kOutHeaders][name.toLowerCase()]; +}; + +OutgoingMessage.prototype.removeHeader = function removeHeader(name: string) { + validateString(name, "name"); + + if (this._header) { + throw new ERR_HTTP_HEADERS_SENT("remove"); + } + + const key = name.toLowerCase(); + + switch (key) { + case "connection": + this._removedConnection = true; + break; + case "content-length": + this._removedContLen = true; + break; + case "transfer-encoding": + this._removedTE = true; + break; + case "date": + this.sendDate = false; + break; + } + + if (this[kOutHeaders] !== null) { + delete this[kOutHeaders][key]; + } +}; + +OutgoingMessage.prototype._implicitHeader = function _implicitHeader() { + throw new ERR_METHOD_NOT_IMPLEMENTED("_implicitHeader()"); +}; + +Object.defineProperty(OutgoingMessage.prototype, "headersSent", { + configurable: true, + enumerable: true, + get: function () { + return !!this._header; + }, +}); + +Object.defineProperty(OutgoingMessage.prototype, "writableEnded", { + get: function () { + return this.finished; + }, +}); + +Object.defineProperty(OutgoingMessage.prototype, "writableNeedDrain", { + get: function () { + return !this.destroyed && !this.finished && this[kNeedDrain]; + }, +}); + +// deno-lint-ignore camelcase +const crlf_buf = Buffer.from("\r\n"); +OutgoingMessage.prototype.write = function write( + // deno-lint-ignore no-explicit-any + chunk: any, + encoding: string | null, + callback: () => void, +) { + if (typeof encoding === "function") { + callback = encoding; + encoding = null; + } + + const ret = write_(this, chunk, encoding, callback, false); + if (!ret) { + this[kNeedDrain] = true; + } + return ret; +}; + +// deno-lint-ignore no-explicit-any +function onError(msg: any, err: any, callback: any) { + const triggerAsyncId = msg.socket ? msg.socket[async_id_symbol] : undefined; + defaultTriggerAsyncIdScope( + triggerAsyncId, + // deno-lint-ignore no-explicit-any + (globalThis as any).process.nextTick, + emitErrorNt, + msg, + err, + callback, + ); +} + +// deno-lint-ignore no-explicit-any +function emitErrorNt(msg: any, err: any, callback: any) { + callback(err); + if (typeof msg.emit === "function" && !msg._closed) { + msg.emit("error", err); + } +} + +function write_( + // deno-lint-ignore no-explicit-any + msg: any, + // deno-lint-ignore no-explicit-any + chunk: any, + encoding: string | null, + // deno-lint-ignore no-explicit-any + callback: any, + // deno-lint-ignore no-explicit-any + fromEnd: any, +) { + if (typeof callback !== "function") { + callback = nop; + } + + let len; + if (chunk === null) { + throw new ERR_STREAM_NULL_VALUES(); + } else if (typeof chunk === "string") { + len = Buffer.byteLength(chunk, encoding); + } else if (isUint8Array(chunk)) { + len = chunk.length; + } else { + throw new ERR_INVALID_ARG_TYPE( + "chunk", + ["string", "Buffer", "Uint8Array"], + chunk, + ); + } + + let err; + if (msg.finished) { + err = new ERR_STREAM_WRITE_AFTER_END(); + } else if (msg.destroyed) { + err = new ERR_STREAM_DESTROYED("write"); + } + + if (err) { + if (!msg.destroyed) { + onError(msg, err, callback); + } else { + // deno-lint-ignore no-explicit-any + (globalThis as any).process.nextTick(callback, err); + } + return false; + } + + if (!msg._header) { + if (fromEnd) { + msg._contentLength = len; + } + msg._implicitHeader(); + } + + if (!msg._hasBody) { + debug( + "This type of response MUST NOT have a body. " + + "Ignoring write() calls.", + ); + // deno-lint-ignore no-explicit-any + (globalThis as any).process.nextTick(callback); + return true; + } + + if (!fromEnd && msg.socket && !msg.socket.writableCorked) { + msg.socket.cork(); + // deno-lint-ignore no-explicit-any + (globalThis as any).process.nextTick(connectionCorkNT, msg.socket); + } + + let ret; + if (msg.chunkedEncoding && chunk.length !== 0) { + msg._send(len.toString(16), "latin1", null); + msg._send(crlf_buf, null, null); + msg._send(chunk, encoding, null); + ret = msg._send(crlf_buf, null, callback); + } else { + ret = msg._send(chunk, encoding, callback); + } + + debug("write ret = " + ret); + return ret; +} + +// deno-lint-ignore no-explicit-any +function connectionCorkNT(conn: any) { + conn.uncork(); +} + +// deno-lint-ignore no-explicit-any +OutgoingMessage.prototype.addTrailers = function addTrailers(headers: any) { + this._trailer = ""; + const keys = Object.keys(headers); + const isArray = Array.isArray(headers); + // Retain for(;;) loop for performance reasons + // Refs: https://github.com/nodejs/node/pull/30958 + for (let i = 0, l = keys.length; i < l; i++) { + let field, value; + const key = keys[i]; + if (isArray) { + // deno-lint-ignore no-explicit-any + field = headers[key as any][0]; + // deno-lint-ignore no-explicit-any + value = headers[key as any][1]; + } else { + field = key; + value = headers[key]; + } + if (typeof field !== "string" || !field || !checkIsHttpToken(field)) { + throw new ERR_INVALID_HTTP_TOKEN("Trailer name", field); + } + if (checkInvalidHeaderChar(value)) { + debug('Trailer "%s" contains invalid characters', field); + throw new ERR_INVALID_CHAR("trailer content", field); + } + this._trailer += field + ": " + value + "\r\n"; + } +}; + +// deno-lint-ignore no-explicit-any +function onFinish(outmsg: any) { + if (outmsg && outmsg.socket && outmsg.socket._hadError) return; + outmsg.emit("finish"); +} + +OutgoingMessage.prototype.end = function end( + // deno-lint-ignore no-explicit-any + chunk: any, + // deno-lint-ignore no-explicit-any + encoding: any, + // deno-lint-ignore no-explicit-any + callback: any, +) { + if (typeof chunk === "function") { + callback = chunk; + chunk = null; + encoding = null; + } else if (typeof encoding === "function") { + callback = encoding; + encoding = null; + } + + if (chunk) { + if (this.finished) { + onError( + this, + new ERR_STREAM_WRITE_AFTER_END(), + typeof callback !== "function" ? nop : callback, + ); + return this; + } + + if (this.socket) { + this.socket.cork(); + } + + write_(this, chunk, encoding, null, true); + } else if (this.finished) { + if (typeof callback === "function") { + if (!this.writableFinished) { + this.on("finish", callback); + } else { + callback(new ERR_STREAM_ALREADY_FINISHED("end")); + } + } + return this; + } else if (!this._header) { + if (this.socket) { + this.socket.cork(); + } + + this._contentLength = 0; + this._implicitHeader(); + } + + if (typeof callback === "function") { + this.once("finish", callback); + } + + const finish = onFinish.bind(undefined, this); + + if (this._hasBody && this.chunkedEncoding) { + this._send("0\r\n" + this._trailer + "\r\n", "latin1", finish); + } else if (!this._headerSent || this.writableLength || chunk) { + this._send("", "latin1", finish); + } else { + // deno-lint-ignore no-explicit-any + (globalThis as any).process.nextTick(finish); + } + + if (this.socket) { + // Fully uncork connection on end(). + this.socket._writableState.corked = 1; + this.socket.uncork(); + } + this[kCorked] = 0; + + this.finished = true; + + // There is the first message on the outgoing queue, and we've sent + // everything to the socket. + debug("outgoing message end."); + if ( + this.outputData.length === 0 && + this.socket && + this.socket._httpMessage === this + ) { + this._finish(); + } + + return this; +}; + +OutgoingMessage.prototype._finish = function _finish() { + assert(this.socket); + this.emit("prefinish"); +}; + +// This logic is probably a bit confusing. Let me explain a bit: +// +// In both HTTP servers and clients it is possible to queue up several +// outgoing messages. This is easiest to imagine in the case of a client. +// Take the following situation: +// +// req1 = client.request('GET', '/'); +// req2 = client.request('POST', '/'); +// +// When the user does +// +// req2.write('hello world\n'); +// +// it's possible that the first request has not been completely flushed to +// the socket yet. Thus the outgoing messages need to be prepared to queue +// up data internally before sending it on further to the socket's queue. +// +// This function, outgoingFlush(), is called by both the Server and Client +// to attempt to flush any pending messages out to the socket. +OutgoingMessage.prototype._flush = function _flush() { + const socket = this.socket; + + if (socket && socket.writable) { + // There might be remaining data in this.output; write it out + const ret = this._flushOutput(socket); + + if (this.finished) { + // This is a queue to the server or client to bring in the next this. + this._finish(); + } else if (ret && this[kNeedDrain]) { + this[kNeedDrain] = false; + this.emit("drain"); + } + } +}; + +OutgoingMessage.prototype._flushOutput = function _flushOutput(socket: Socket) { + while (this[kCorked]) { + this[kCorked]--; + socket.cork(); + } + + const outputLength = this.outputData.length; + if (outputLength <= 0) { + return undefined; + } + + const outputData = this.outputData; + socket.cork(); + let ret; + // Retain for(;;) loop for performance reasons + // Refs: https://github.com/nodejs/node/pull/30958 + for (let i = 0; i < outputLength; i++) { + const { data, encoding, callback } = outputData[i]; + ret = socket.write(data, encoding, callback); + } + socket.uncork(); + + this.outputData = []; + this._onPendingData(-this.outputSize); + this.outputSize = 0; + + return ret; +}; + +OutgoingMessage.prototype.flushHeaders = function flushHeaders() { + if (!this._header) { + this._implicitHeader(); + } + + // Force-flush the headers. + this._send(""); +}; + +OutgoingMessage.prototype.pipe = function pipe() { + // OutgoingMessage should be write-only. Piping from it is disabled. + this.emit("error", new ERR_STREAM_CANNOT_PIPE()); +}; + +OutgoingMessage.prototype[EE.captureRejectionSymbol] = function ( + // deno-lint-ignore no-explicit-any + err: any, + // deno-lint-ignore no-explicit-any + _event: any, +) { + this.destroy(err); +}; + +export default { + validateHeaderName, + validateHeaderValue, + OutgoingMessage, +}; diff --git a/ext/node/polyfills/_next_tick.ts b/ext/node/polyfills/_next_tick.ts new file mode 100644 index 00000000000000..72a6fc12033b68 --- /dev/null +++ b/ext/node/polyfills/_next_tick.ts @@ -0,0 +1,149 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. +// Copyright Joyent, Inc. and other Node contributors. + +import { core } from "internal:deno_node/polyfills/_core.ts"; +import { validateFunction } from "internal:deno_node/polyfills/internal/validators.mjs"; +import { _exiting } from "internal:deno_node/polyfills/_process/exiting.ts"; +import { FixedQueue } from "internal:deno_node/polyfills/internal/fixed_queue.ts"; + +interface Tock { + callback: (...args: Array) => void; + args: Array; +} + +let nextTickEnabled = false; +export function enableNextTick() { + nextTickEnabled = true; +} + +const queue = new FixedQueue(); + +export function processTicksAndRejections() { + let tock; + do { + // deno-lint-ignore no-cond-assign + while (tock = queue.shift()) { + // FIXME(bartlomieju): Deno currently doesn't support async hooks + // const asyncId = tock[async_id_symbol]; + // emitBefore(asyncId, tock[trigger_async_id_symbol], tock); + + try { + const callback = (tock as Tock).callback; + if ((tock as Tock).args === undefined) { + callback(); + } else { + const args = (tock as Tock).args; + switch (args.length) { + case 1: + callback(args[0]); + break; + case 2: + callback(args[0], args[1]); + break; + case 3: + callback(args[0], args[1], args[2]); + break; + case 4: + callback(args[0], args[1], args[2], args[3]); + break; + default: + callback(...args); + } + } + } finally { + // FIXME(bartlomieju): Deno currently doesn't support async hooks + // if (destroyHooksExist()) + // emitDestroy(asyncId); + } + + // FIXME(bartlomieju): Deno currently doesn't support async hooks + // emitAfter(asyncId); + } + core.runMicrotasks(); + // FIXME(bartlomieju): Deno currently doesn't unhandled rejections + // } while (!queue.isEmpty() || processPromiseRejections()); + } while (!queue.isEmpty()); + core.setHasTickScheduled(false); + // FIXME(bartlomieju): Deno currently doesn't unhandled rejections + // setHasRejectionToWarn(false); +} + +export function runNextTicks() { + // FIXME(bartlomieju): Deno currently doesn't unhandled rejections + // if (!hasTickScheduled() && !hasRejectionToWarn()) + // runMicrotasks(); + // if (!hasTickScheduled() && !hasRejectionToWarn()) + // return; + if (!core.hasTickScheduled()) { + core.runMicrotasks(); + return true; + } + + processTicksAndRejections(); + return true; +} + +// `nextTick()` will not enqueue any callback when the process is about to +// exit since the callback would not have a chance to be executed. +export function nextTick(this: unknown, callback: () => void): void; +export function nextTick>( + this: unknown, + callback: (...args: T) => void, + ...args: T +): void; +export function nextTick>( + this: unknown, + callback: (...args: T) => void, + ...args: T +) { + // If we're snapshotting we don't want to push nextTick to be run. We'll + // enable next ticks in "__bootstrapNodeProcess()"; + if (!nextTickEnabled) { + return; + } + + validateFunction(callback, "callback"); + + if (_exiting) { + return; + } + + // TODO(bartlomieju): seems superfluous if we don't depend on `arguments` + let args_; + switch (args.length) { + case 0: + break; + case 1: + args_ = [args[0]]; + break; + case 2: + args_ = [args[0], args[1]]; + break; + case 3: + args_ = [args[0], args[1], args[2]]; + break; + default: + args_ = new Array(args.length); + for (let i = 0; i < args.length; i++) { + args_[i] = args[i]; + } + } + + if (queue.isEmpty()) { + core.setHasTickScheduled(true); + } + // FIXME(bartlomieju): Deno currently doesn't support async hooks + // const asyncId = newAsyncId(); + // const triggerAsyncId = getDefaultTriggerAsyncId(); + const tickObject = { + // FIXME(bartlomieju): Deno currently doesn't support async hooks + // [async_id_symbol]: asyncId, + // [trigger_async_id_symbol]: triggerAsyncId, + callback, + args: args_, + }; + // FIXME(bartlomieju): Deno currently doesn't support async hooks + // if (initHooksExist()) + // emitInit(asyncId, 'TickObject', triggerAsyncId, tickObject); + queue.push(tickObject); +} diff --git a/ext/node/polyfills/_pako.mjs b/ext/node/polyfills/_pako.mjs new file mode 100644 index 00000000000000..2447ef03bbd161 --- /dev/null +++ b/ext/node/polyfills/_pako.mjs @@ -0,0 +1,7005 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. +/*! pako 2.0.4 https://github.com/nodeca/pako @license (MIT AND Zlib) */ +// (C) 1995-2013 Jean-loup Gailly and Mark Adler +// (C) 2014-2017 Vitaly Puzrin and Andrey Tupitsin +// +// This software is provided 'as-is', without any express or implied +// warranty. In no event will the authors be held liable for any damages +// arising from the use of this software. +// +// Permission is granted to anyone to use this software for any purpose, +// including commercial applications, and to alter it and redistribute it +// freely, subject to the following restrictions: +// +// 1. The origin of this software must not be misrepresented; you must not +// claim that you wrote the original software. If you use this software +// in a product, an acknowledgment in the product documentation would be +// appreciated but is not required. +// 2. Altered source versions must be plainly marked as such, and must not be +// misrepresented as being the original software. +// 3. This notice may not be removed or altered from any source distribution. + +// deno-lint-ignore-file + +import { TextDecoder, TextEncoder } from "internal:deno_web/08_text_encoding.js"; + +/* eslint-disable space-unary-ops */ + +/* Public constants ==========================================================*/ +/* ===========================================================================*/ + +//const Z_FILTERED = 1; +//const Z_HUFFMAN_ONLY = 2; +//const Z_RLE = 3; +const Z_FIXED$1 = 4; +//const Z_DEFAULT_STRATEGY = 0; + +/* Possible values of the data_type field (though see inflate()) */ +const Z_BINARY = 0; +const Z_TEXT = 1; +//const Z_ASCII = 1; // = Z_TEXT +const Z_UNKNOWN$1 = 2; + +/*============================================================================*/ + +function zero$1(buf) { + let len = buf.length; + while (--len >= 0) buf[len] = 0; +} + +// From zutil.h + +const STORED_BLOCK = 0; +const STATIC_TREES = 1; +const DYN_TREES = 2; +/* The three kinds of block type */ + +const MIN_MATCH$1 = 3; +const MAX_MATCH$1 = 258; +/* The minimum and maximum match lengths */ + +// From deflate.h +/* =========================================================================== + * Internal compression state. + */ + +const LENGTH_CODES$1 = 29; +/* number of length codes, not counting the special END_BLOCK code */ + +const LITERALS$1 = 256; +/* number of literal bytes 0..255 */ + +const L_CODES$1 = LITERALS$1 + 1 + LENGTH_CODES$1; +/* number of Literal or Length codes, including the END_BLOCK code */ + +const D_CODES$1 = 30; +/* number of distance codes */ + +const BL_CODES$1 = 19; +/* number of codes used to transfer the bit lengths */ + +const HEAP_SIZE$1 = 2 * L_CODES$1 + 1; +/* maximum heap size */ + +const MAX_BITS$1 = 15; +/* All codes must not exceed MAX_BITS bits */ + +const Buf_size = 16; +/* size of bit buffer in bi_buf */ + +/* =========================================================================== + * Constants + */ + +const MAX_BL_BITS = 7; +/* Bit length codes must not exceed MAX_BL_BITS bits */ + +const END_BLOCK = 256; +/* end of block literal code */ + +const REP_3_6 = 16; +/* repeat previous bit length 3-6 times (2 bits of repeat count) */ + +const REPZ_3_10 = 17; +/* repeat a zero length 3-10 times (3 bits of repeat count) */ + +const REPZ_11_138 = 18; +/* repeat a zero length 11-138 times (7 bits of repeat count) */ + +/* eslint-disable comma-spacing,array-bracket-spacing */ +const extra_lbits = /* extra bits for each length code */ + new Uint8Array([ + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 1, + 1, + 1, + 1, + 2, + 2, + 2, + 2, + 3, + 3, + 3, + 3, + 4, + 4, + 4, + 4, + 5, + 5, + 5, + 5, + 0, + ]); + +const extra_dbits = /* extra bits for each distance code */ + new Uint8Array([ + 0, + 0, + 0, + 0, + 1, + 1, + 2, + 2, + 3, + 3, + 4, + 4, + 5, + 5, + 6, + 6, + 7, + 7, + 8, + 8, + 9, + 9, + 10, + 10, + 11, + 11, + 12, + 12, + 13, + 13, + ]); + +const extra_blbits = /* extra bits for each bit length code */ + new Uint8Array([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 3, 7]); + +const bl_order = new Uint8Array([ + 16, + 17, + 18, + 0, + 8, + 7, + 9, + 6, + 10, + 5, + 11, + 4, + 12, + 3, + 13, + 2, + 14, + 1, + 15, +]); +/* eslint-enable comma-spacing,array-bracket-spacing */ + +/* The lengths of the bit length codes are sent in order of decreasing + * probability, to avoid transmitting the lengths for unused bit length codes. + */ + +/* =========================================================================== + * Local data. These are initialized only once. + */ + +// We pre-fill arrays with 0 to avoid uninitialized gaps + +const DIST_CODE_LEN = 512; /* see definition of array dist_code below */ + +// !!!! Use flat array instead of structure, Freq = i*2, Len = i*2+1 +const static_ltree = new Array((L_CODES$1 + 2) * 2); +zero$1(static_ltree); +/* The static literal tree. Since the bit lengths are imposed, there is no + * need for the L_CODES extra codes used during heap construction. However + * The codes 286 and 287 are needed to build a canonical tree (see _tr_init + * below). + */ + +const static_dtree = new Array(D_CODES$1 * 2); +zero$1(static_dtree); +/* The static distance tree. (Actually a trivial tree since all codes use + * 5 bits.) + */ + +const _dist_code = new Array(DIST_CODE_LEN); +zero$1(_dist_code); +/* Distance codes. The first 256 values correspond to the distances + * 3 .. 258, the last 256 values correspond to the top 8 bits of + * the 15 bit distances. + */ + +const _length_code = new Array(MAX_MATCH$1 - MIN_MATCH$1 + 1); +zero$1(_length_code); +/* length code for each normalized match length (0 == MIN_MATCH) */ + +const base_length = new Array(LENGTH_CODES$1); +zero$1(base_length); +/* First normalized length for each code (0 = MIN_MATCH) */ + +const base_dist = new Array(D_CODES$1); +zero$1(base_dist); +/* First normalized distance for each code (0 = distance of 1) */ + +function StaticTreeDesc( + static_tree, + extra_bits, + extra_base, + elems, + max_length, +) { + this.static_tree = static_tree; /* static tree or NULL */ + this.extra_bits = extra_bits; /* extra bits for each code or NULL */ + this.extra_base = extra_base; /* base index for extra_bits */ + this.elems = elems; /* max number of elements in the tree */ + this.max_length = max_length; /* max bit length for the codes */ + + // show if `static_tree` has data or dummy - needed for monomorphic objects + this.has_stree = static_tree && static_tree.length; +} + +let static_l_desc; +let static_d_desc; +let static_bl_desc; + +function TreeDesc(dyn_tree, stat_desc) { + this.dyn_tree = dyn_tree; /* the dynamic tree */ + this.max_code = 0; /* largest code with non zero frequency */ + this.stat_desc = stat_desc; /* the corresponding static tree */ +} + +const d_code = (dist) => { + return dist < 256 ? _dist_code[dist] : _dist_code[256 + (dist >>> 7)]; +}; + +/* =========================================================================== + * Output a short LSB first on the stream. + * IN assertion: there is enough room in pendingBuf. + */ +const put_short = (s, w) => { + // put_byte(s, (uch)((w) & 0xff)); + // put_byte(s, (uch)((ush)(w) >> 8)); + s.pending_buf[s.pending++] = (w) & 0xff; + s.pending_buf[s.pending++] = (w >>> 8) & 0xff; +}; + +/* =========================================================================== + * Send a value on a given number of bits. + * IN assertion: length <= 16 and value fits in length bits. + */ +const send_bits = (s, value, length) => { + if (s.bi_valid > (Buf_size - length)) { + s.bi_buf |= (value << s.bi_valid) & 0xffff; + put_short(s, s.bi_buf); + s.bi_buf = value >> (Buf_size - s.bi_valid); + s.bi_valid += length - Buf_size; + } else { + s.bi_buf |= (value << s.bi_valid) & 0xffff; + s.bi_valid += length; + } +}; + +const send_code = (s, c, tree) => { + send_bits(s, tree[c * 2], /*.Code*/ tree[c * 2 + 1] /*.Len*/); +}; + +/* =========================================================================== + * Reverse the first len bits of a code, using straightforward code (a faster + * method would use a table) + * IN assertion: 1 <= len <= 15 + */ +const bi_reverse = (code, len) => { + let res = 0; + do { + res |= code & 1; + code >>>= 1; + res <<= 1; + } while (--len > 0); + return res >>> 1; +}; + +/* =========================================================================== + * Flush the bit buffer, keeping at most 7 bits in it. + */ +const bi_flush = (s) => { + if (s.bi_valid === 16) { + put_short(s, s.bi_buf); + s.bi_buf = 0; + s.bi_valid = 0; + } else if (s.bi_valid >= 8) { + s.pending_buf[s.pending++] = s.bi_buf & 0xff; + s.bi_buf >>= 8; + s.bi_valid -= 8; + } +}; + +/* =========================================================================== + * Compute the optimal bit lengths for a tree and update the total bit length + * for the current block. + * IN assertion: the fields freq and dad are set, heap[heap_max] and + * above are the tree nodes sorted by increasing frequency. + * OUT assertions: the field len is set to the optimal bit length, the + * array bl_count contains the frequencies for each bit length. + * The length opt_len is updated; static_len is also updated if stree is + * not null. + */ +const gen_bitlen = (s, desc) => // deflate_state *s; +// tree_desc *desc; /* the tree descriptor */ +{ + const tree = desc.dyn_tree; + const max_code = desc.max_code; + const stree = desc.stat_desc.static_tree; + const has_stree = desc.stat_desc.has_stree; + const extra = desc.stat_desc.extra_bits; + const base = desc.stat_desc.extra_base; + const max_length = desc.stat_desc.max_length; + let h; /* heap index */ + let n, m; /* iterate over the tree elements */ + let bits; /* bit length */ + let xbits; /* extra bits */ + let f; /* frequency */ + let overflow = 0; /* number of elements with bit length too large */ + + for (bits = 0; bits <= MAX_BITS$1; bits++) { + s.bl_count[bits] = 0; + } + + /* In a first pass, compute the optimal bit lengths (which may + * overflow in the case of the bit length tree). + */ + tree[s.heap[s.heap_max] * 2 + 1] /*.Len*/ = 0; /* root of the heap */ + + for (h = s.heap_max + 1; h < HEAP_SIZE$1; h++) { + n = s.heap[h]; + bits = tree[tree[n * 2 + 1] /*.Dad*/ * 2 + 1] /*.Len*/ + 1; + if (bits > max_length) { + bits = max_length; + overflow++; + } + tree[n * 2 + 1] /*.Len*/ = bits; + /* We overwrite tree[n].Dad which is no longer needed */ + + if (n > max_code) continue; /* not a leaf node */ + + s.bl_count[bits]++; + xbits = 0; + if (n >= base) { + xbits = extra[n - base]; + } + f = tree[n * 2] /*.Freq*/; + s.opt_len += f * (bits + xbits); + if (has_stree) { + s.static_len += f * (stree[n * 2 + 1] /*.Len*/ + xbits); + } + } + if (overflow === 0) return; + + // Trace((stderr,"\nbit length overflow\n")); + /* This happens for example on obj2 and pic of the Calgary corpus */ + + /* Find the first bit length which could increase: */ + do { + bits = max_length - 1; + while (s.bl_count[bits] === 0) bits--; + s.bl_count[bits]--; /* move one leaf down the tree */ + s.bl_count[bits + 1] += 2; /* move one overflow item as its brother */ + s.bl_count[max_length]--; + /* The brother of the overflow item also moves one step up, + * but this does not affect bl_count[max_length] + */ + overflow -= 2; + } while (overflow > 0); + + /* Now recompute all bit lengths, scanning in increasing frequency. + * h is still equal to HEAP_SIZE. (It is simpler to reconstruct all + * lengths instead of fixing only the wrong ones. This idea is taken + * from "internal:deno_node/polyfills/ar" written by Haruhiko Okumura.) + */ + for (bits = max_length; bits !== 0; bits--) { + n = s.bl_count[bits]; + while (n !== 0) { + m = s.heap[--h]; + if (m > max_code) continue; + if (tree[m * 2 + 1] /*.Len*/ !== bits) { + // Trace((stderr,"code %d bits %d->%d\n", m, tree[m].Len, bits)); + s.opt_len += (bits - tree[m * 2 + 1] /*.Len*/) * tree[m * 2] /*.Freq*/; + tree[m * 2 + 1] /*.Len*/ = bits; + } + n--; + } + } +}; + +/* =========================================================================== + * Generate the codes for a given tree and bit counts (which need not be + * optimal). + * IN assertion: the array bl_count contains the bit length statistics for + * the given tree and the field len is set for all tree elements. + * OUT assertion: the field code is set for all tree elements of non + * zero code length. + */ +const gen_codes = (tree, max_code, bl_count) => // ct_data *tree; /* the tree to decorate */ +// int max_code; /* largest code with non zero frequency */ +// ushf *bl_count; /* number of codes at each bit length */ +{ + const next_code = new Array( + MAX_BITS$1 + 1, + ); /* next code value for each bit length */ + let code = 0; /* running code value */ + let bits; /* bit index */ + let n; /* code index */ + + /* The distribution counts are first used to generate the code values + * without bit reversal. + */ + for (bits = 1; bits <= MAX_BITS$1; bits++) { + next_code[bits] = code = (code + bl_count[bits - 1]) << 1; + } + /* Check that the bit counts in bl_count are consistent. The last code + * must be all ones. + */ + //Assert (code + bl_count[MAX_BITS]-1 == (1< { + let n; /* iterates over tree elements */ + let bits; /* bit counter */ + let length; /* length value */ + let code; /* code value */ + let dist; /* distance index */ + const bl_count = new Array(MAX_BITS$1 + 1); + /* number of codes at each bit length for an optimal tree */ + + // do check in _tr_init() + //if (static_init_done) return; + + /* For some embedded targets, global variables are not initialized: */ + /*#ifdef NO_INIT_GLOBAL_POINTERS + static_l_desc.static_tree = static_ltree; + static_l_desc.extra_bits = extra_lbits; + static_d_desc.static_tree = static_dtree; + static_d_desc.extra_bits = extra_dbits; + static_bl_desc.extra_bits = extra_blbits; +#endif*/ + + /* Initialize the mapping length (0..255) -> length code (0..28) */ + length = 0; + for (code = 0; code < LENGTH_CODES$1 - 1; code++) { + base_length[code] = length; + for (n = 0; n < (1 << extra_lbits[code]); n++) { + _length_code[length++] = code; + } + } + //Assert (length == 256, "tr_static_init: length != 256"); + /* Note that the length 255 (match length 258) can be represented + * in two different ways: code 284 + 5 bits or code 285, so we + * overwrite length_code[255] to use the best encoding: + */ + _length_code[length - 1] = code; + + /* Initialize the mapping dist (0..32K) -> dist code (0..29) */ + dist = 0; + for (code = 0; code < 16; code++) { + base_dist[code] = dist; + for (n = 0; n < (1 << extra_dbits[code]); n++) { + _dist_code[dist++] = code; + } + } + //Assert (dist == 256, "tr_static_init: dist != 256"); + dist >>= 7; /* from now on, all distances are divided by 128 */ + for (; code < D_CODES$1; code++) { + base_dist[code] = dist << 7; + for (n = 0; n < (1 << (extra_dbits[code] - 7)); n++) { + _dist_code[256 + dist++] = code; + } + } + //Assert (dist == 256, "tr_static_init: 256+dist != 512"); + + /* Construct the codes of the static literal tree */ + for (bits = 0; bits <= MAX_BITS$1; bits++) { + bl_count[bits] = 0; + } + + n = 0; + while (n <= 143) { + static_ltree[n * 2 + 1] /*.Len*/ = 8; + n++; + bl_count[8]++; + } + while (n <= 255) { + static_ltree[n * 2 + 1] /*.Len*/ = 9; + n++; + bl_count[9]++; + } + while (n <= 279) { + static_ltree[n * 2 + 1] /*.Len*/ = 7; + n++; + bl_count[7]++; + } + while (n <= 287) { + static_ltree[n * 2 + 1] /*.Len*/ = 8; + n++; + bl_count[8]++; + } + /* Codes 286 and 287 do not exist, but we must include them in the + * tree construction to get a canonical Huffman tree (longest code + * all ones) + */ + gen_codes(static_ltree, L_CODES$1 + 1, bl_count); + + /* The static distance tree is trivial: */ + for (n = 0; n < D_CODES$1; n++) { + static_dtree[n * 2 + 1] /*.Len*/ = 5; + static_dtree[n * 2] /*.Code*/ = bi_reverse(n, 5); + } + + // Now data ready and we can init static trees + static_l_desc = new StaticTreeDesc( + static_ltree, + extra_lbits, + LITERALS$1 + 1, + L_CODES$1, + MAX_BITS$1, + ); + static_d_desc = new StaticTreeDesc( + static_dtree, + extra_dbits, + 0, + D_CODES$1, + MAX_BITS$1, + ); + static_bl_desc = new StaticTreeDesc( + new Array(0), + extra_blbits, + 0, + BL_CODES$1, + MAX_BL_BITS, + ); + + //static_init_done = true; +}; + +/* =========================================================================== + * Initialize a new block. + */ +const init_block = (s) => { + let n; /* iterates over tree elements */ + + /* Initialize the trees. */ + for (n = 0; n < L_CODES$1; n++) s.dyn_ltree[n * 2] /*.Freq*/ = 0; + for (n = 0; n < D_CODES$1; n++) s.dyn_dtree[n * 2] /*.Freq*/ = 0; + for (n = 0; n < BL_CODES$1; n++) s.bl_tree[n * 2] /*.Freq*/ = 0; + + s.dyn_ltree[END_BLOCK * 2] /*.Freq*/ = 1; + s.opt_len = s.static_len = 0; + s.last_lit = s.matches = 0; +}; + +/* =========================================================================== + * Flush the bit buffer and align the output on a byte boundary + */ +const bi_windup = (s) => { + if (s.bi_valid > 8) { + put_short(s, s.bi_buf); + } else if (s.bi_valid > 0) { + //put_byte(s, (Byte)s->bi_buf); + s.pending_buf[s.pending++] = s.bi_buf; + } + s.bi_buf = 0; + s.bi_valid = 0; +}; + +/* =========================================================================== + * Copy a stored block, storing first the length and its + * one's complement if requested. + */ +const copy_block = (s, buf, len, header) => //DeflateState *s; +//charf *buf; /* the input data */ +//unsigned len; /* its length */ +//int header; /* true if block header must be written */ +{ + bi_windup(s); /* align on byte boundary */ + + if (header) { + put_short(s, len); + put_short(s, ~len); + } + // while (len--) { + // put_byte(s, *buf++); + // } + s.pending_buf.set(s.window.subarray(buf, buf + len), s.pending); + s.pending += len; +}; + +/* =========================================================================== + * Compares to subtrees, using the tree depth as tie breaker when + * the subtrees have equal frequency. This minimizes the worst case length. + */ +const smaller = (tree, n, m, depth) => { + const _n2 = n * 2; + const _m2 = m * 2; + return (tree[_n2] /*.Freq*/ < tree[_m2] /*.Freq*/ || + (tree[_n2] /*.Freq*/ === tree[_m2] /*.Freq*/ && depth[n] <= depth[m])); +}; + +/* =========================================================================== + * Restore the heap property by moving down the tree starting at node k, + * exchanging a node with the smallest of its two sons if necessary, stopping + * when the heap property is re-established (each father smaller than its + * two sons). + */ +const pqdownheap = (s, tree, k) => // deflate_state *s; +// ct_data *tree; /* the tree to restore */ +// int k; /* node to move down */ +{ + const v = s.heap[k]; + let j = k << 1; /* left son of k */ + while (j <= s.heap_len) { + /* Set j to the smallest of the two sons: */ + if ( + j < s.heap_len && + smaller(tree, s.heap[j + 1], s.heap[j], s.depth) + ) { + j++; + } + /* Exit if v is smaller than both sons */ + if (smaller(tree, v, s.heap[j], s.depth)) break; + + /* Exchange v with the smallest son */ + s.heap[k] = s.heap[j]; + k = j; + + /* And continue down the tree, setting j to the left son of k */ + j <<= 1; + } + s.heap[k] = v; +}; + +// inlined manually +// const SMALLEST = 1; + +/* =========================================================================== + * Send the block data compressed using the given Huffman trees + */ +const compress_block = (s, ltree, dtree) => // deflate_state *s; +// const ct_data *ltree; /* literal tree */ +// const ct_data *dtree; /* distance tree */ +{ + let dist; /* distance of matched string */ + let lc; /* match length or unmatched char (if dist == 0) */ + let lx = 0; /* running index in l_buf */ + let code; /* the code to send */ + let extra; /* number of extra bits to send */ + + if (s.last_lit !== 0) { + do { + dist = (s.pending_buf[s.d_buf + lx * 2] << 8) | + (s.pending_buf[s.d_buf + lx * 2 + 1]); + lc = s.pending_buf[s.l_buf + lx]; + lx++; + + if (dist === 0) { + send_code(s, lc, ltree); /* send a literal byte */ + //Tracecv(isgraph(lc), (stderr," '%c' ", lc)); + } else { + /* Here, lc is the match length - MIN_MATCH */ + code = _length_code[lc]; + send_code(s, code + LITERALS$1 + 1, ltree); /* send the length code */ + extra = extra_lbits[code]; + if (extra !== 0) { + lc -= base_length[code]; + send_bits(s, lc, extra); /* send the extra length bits */ + } + dist--; /* dist is now the match distance - 1 */ + code = d_code(dist); + //Assert (code < D_CODES, "bad d_code"); + + send_code(s, code, dtree); /* send the distance code */ + extra = extra_dbits[code]; + if (extra !== 0) { + dist -= base_dist[code]; + send_bits(s, dist, extra); /* send the extra distance bits */ + } + } /* literal or match pair ? */ + + /* Check that the overlay between pending_buf and d_buf+l_buf is ok: */ + //Assert((uInt)(s->pending) < s->lit_bufsize + 2*lx, + // "pendingBuf overflow"); + } while (lx < s.last_lit); + } + + send_code(s, END_BLOCK, ltree); +}; + +/* =========================================================================== + * Construct one Huffman tree and assigns the code bit strings and lengths. + * Update the total bit length for the current block. + * IN assertion: the field freq is set for all tree elements. + * OUT assertions: the fields len and code are set to the optimal bit length + * and corresponding code. The length opt_len is updated; static_len is + * also updated if stree is not null. The field max_code is set. + */ +const build_tree = (s, desc) => // deflate_state *s; +// tree_desc *desc; /* the tree descriptor */ +{ + const tree = desc.dyn_tree; + const stree = desc.stat_desc.static_tree; + const has_stree = desc.stat_desc.has_stree; + const elems = desc.stat_desc.elems; + let n, m; /* iterate over heap elements */ + let max_code = -1; /* largest code with non zero frequency */ + let node; /* new node being created */ + + /* Construct the initial heap, with least frequent element in + * heap[SMALLEST]. The sons of heap[n] are heap[2*n] and heap[2*n+1]. + * heap[0] is not used. + */ + s.heap_len = 0; + s.heap_max = HEAP_SIZE$1; + + for (n = 0; n < elems; n++) { + if (tree[n * 2] /*.Freq*/ !== 0) { + s.heap[++s.heap_len] = max_code = n; + s.depth[n] = 0; + } else { + tree[n * 2 + 1] /*.Len*/ = 0; + } + } + + /* The pkzip format requires that at least one distance code exists, + * and that at least one bit should be sent even if there is only one + * possible code. So to avoid special checks later on we force at least + * two codes of non zero frequency. + */ + while (s.heap_len < 2) { + node = s.heap[++s.heap_len] = max_code < 2 ? ++max_code : 0; + tree[node * 2] /*.Freq*/ = 1; + s.depth[node] = 0; + s.opt_len--; + + if (has_stree) { + s.static_len -= stree[node * 2 + 1] /*.Len*/; + } + /* node is 0 or 1 so it does not have extra bits */ + } + desc.max_code = max_code; + + /* The elements heap[heap_len/2+1 .. heap_len] are leaves of the tree, + * establish sub-heaps of increasing lengths: + */ + for (n = s.heap_len >> 1 /*int /2*/; n >= 1; n--) pqdownheap(s, tree, n); + + /* Construct the Huffman tree by repeatedly combining the least two + * frequent nodes. + */ + node = elems; /* next internal node of the tree */ + do { + //pqremove(s, tree, n); /* n = node of least frequency */ + /*** pqremove ***/ + n = s.heap[1 /*SMALLEST*/]; + s.heap[1 /*SMALLEST*/] = s.heap[s.heap_len--]; + pqdownheap(s, tree, 1 /*SMALLEST*/); + /***/ + + m = s.heap[1 /*SMALLEST*/]; /* m = node of next least frequency */ + + s.heap[--s.heap_max] = n; /* keep the nodes sorted by frequency */ + s.heap[--s.heap_max] = m; + + /* Create a new node father of n and m */ + tree[node * 2] /*.Freq*/ = tree[n * 2] /*.Freq*/ + tree[m * 2] /*.Freq*/; + s.depth[node] = (s.depth[n] >= s.depth[m] ? s.depth[n] : s.depth[m]) + 1; + tree[n * 2 + 1] /*.Dad*/ = tree[m * 2 + 1] /*.Dad*/ = node; + + /* and insert the new node in the heap */ + s.heap[1 /*SMALLEST*/] = node++; + pqdownheap(s, tree, 1 /*SMALLEST*/); + } while (s.heap_len >= 2); + + s.heap[--s.heap_max] = s.heap[1 /*SMALLEST*/]; + + /* At this point, the fields freq and dad are set. We can now + * generate the bit lengths. + */ + gen_bitlen(s, desc); + + /* The field len is now set, we can generate the bit codes */ + gen_codes(tree, max_code, s.bl_count); +}; + +/* =========================================================================== + * Scan a literal or distance tree to determine the frequencies of the codes + * in the bit length tree. + */ +const scan_tree = (s, tree, max_code) => // deflate_state *s; +// ct_data *tree; /* the tree to be scanned */ +// int max_code; /* and its largest code of non zero frequency */ +{ + let n; /* iterates over all tree elements */ + let prevlen = -1; /* last emitted length */ + let curlen; /* length of current code */ + + let nextlen = tree[0 * 2 + 1] /*.Len*/; /* length of next code */ + + let count = 0; /* repeat count of the current code */ + let max_count = 7; /* max repeat count */ + let min_count = 4; /* min repeat count */ + + if (nextlen === 0) { + max_count = 138; + min_count = 3; + } + tree[(max_code + 1) * 2 + 1] /*.Len*/ = 0xffff; /* guard */ + + for (n = 0; n <= max_code; n++) { + curlen = nextlen; + nextlen = tree[(n + 1) * 2 + 1] /*.Len*/; + + if (++count < max_count && curlen === nextlen) { + continue; + } else if (count < min_count) { + s.bl_tree[curlen * 2] /*.Freq*/ += count; + } else if (curlen !== 0) { + if (curlen !== prevlen) s.bl_tree[curlen * 2] /*.Freq*/++; + s.bl_tree[REP_3_6 * 2] /*.Freq*/++; + } else if (count <= 10) { + s.bl_tree[REPZ_3_10 * 2] /*.Freq*/++; + } else { + s.bl_tree[REPZ_11_138 * 2] /*.Freq*/++; + } + + count = 0; + prevlen = curlen; + + if (nextlen === 0) { + max_count = 138; + min_count = 3; + } else if (curlen === nextlen) { + max_count = 6; + min_count = 3; + } else { + max_count = 7; + min_count = 4; + } + } +}; + +/* =========================================================================== + * Send a literal or distance tree in compressed form, using the codes in + * bl_tree. + */ +const send_tree = (s, tree, max_code) => // deflate_state *s; +// ct_data *tree; /* the tree to be scanned */ +// int max_code; /* and its largest code of non zero frequency */ +{ + let n; /* iterates over all tree elements */ + let prevlen = -1; /* last emitted length */ + let curlen; /* length of current code */ + + let nextlen = tree[0 * 2 + 1] /*.Len*/; /* length of next code */ + + let count = 0; /* repeat count of the current code */ + let max_count = 7; /* max repeat count */ + let min_count = 4; /* min repeat count */ + + /* tree[max_code+1].Len = -1; */ + /* guard already set */ + if (nextlen === 0) { + max_count = 138; + min_count = 3; + } + + for (n = 0; n <= max_code; n++) { + curlen = nextlen; + nextlen = tree[(n + 1) * 2 + 1] /*.Len*/; + + if (++count < max_count && curlen === nextlen) { + continue; + } else if (count < min_count) { + do { + send_code(s, curlen, s.bl_tree); + } while (--count !== 0); + } else if (curlen !== 0) { + if (curlen !== prevlen) { + send_code(s, curlen, s.bl_tree); + count--; + } + //Assert(count >= 3 && count <= 6, " 3_6?"); + send_code(s, REP_3_6, s.bl_tree); + send_bits(s, count - 3, 2); + } else if (count <= 10) { + send_code(s, REPZ_3_10, s.bl_tree); + send_bits(s, count - 3, 3); + } else { + send_code(s, REPZ_11_138, s.bl_tree); + send_bits(s, count - 11, 7); + } + + count = 0; + prevlen = curlen; + if (nextlen === 0) { + max_count = 138; + min_count = 3; + } else if (curlen === nextlen) { + max_count = 6; + min_count = 3; + } else { + max_count = 7; + min_count = 4; + } + } +}; + +/* =========================================================================== + * Construct the Huffman tree for the bit lengths and return the index in + * bl_order of the last bit length code to send. + */ +const build_bl_tree = (s) => { + let max_blindex; /* index of last bit length code of non zero freq */ + + /* Determine the bit length frequencies for literal and distance trees */ + scan_tree(s, s.dyn_ltree, s.l_desc.max_code); + scan_tree(s, s.dyn_dtree, s.d_desc.max_code); + + /* Build the bit length tree: */ + build_tree(s, s.bl_desc); + /* opt_len now includes the length of the tree representations, except + * the lengths of the bit lengths codes and the 5+5+4 bits for the counts. + */ + + /* Determine the number of bit length codes to send. The pkzip format + * requires that at least 4 bit length codes be sent. (appnote.txt says + * 3 but the actual value used is 4.) + */ + for (max_blindex = BL_CODES$1 - 1; max_blindex >= 3; max_blindex--) { + if (s.bl_tree[bl_order[max_blindex] * 2 + 1] /*.Len*/ !== 0) { + break; + } + } + /* Update opt_len to include the bit length tree and counts */ + s.opt_len += 3 * (max_blindex + 1) + 5 + 5 + 4; + //Tracev((stderr, "\ndyn trees: dyn %ld, stat %ld", + // s->opt_len, s->static_len)); + + return max_blindex; +}; + +/* =========================================================================== + * Send the header for a block using dynamic Huffman trees: the counts, the + * lengths of the bit length codes, the literal tree and the distance tree. + * IN assertion: lcodes >= 257, dcodes >= 1, blcodes >= 4. + */ +const send_all_trees = (s, lcodes, dcodes, blcodes) => // deflate_state *s; +// int lcodes, dcodes, blcodes; /* number of codes for each tree */ +{ + let rank; /* index in bl_order */ + + //Assert (lcodes >= 257 && dcodes >= 1 && blcodes >= 4, "not enough codes"); + //Assert (lcodes <= L_CODES && dcodes <= D_CODES && blcodes <= BL_CODES, + // "too many codes"); + //Tracev((stderr, "\nbl counts: ")); + send_bits(s, lcodes - 257, 5); /* not +255 as stated in appnote.txt */ + send_bits(s, dcodes - 1, 5); + send_bits(s, blcodes - 4, 4); /* not -3 as stated in appnote.txt */ + for (rank = 0; rank < blcodes; rank++) { + //Tracev((stderr, "\nbl code %2d ", bl_order[rank])); + send_bits(s, s.bl_tree[bl_order[rank] * 2 + 1], /*.Len*/ 3); + } + //Tracev((stderr, "\nbl tree: sent %ld", s->bits_sent)); + + send_tree(s, s.dyn_ltree, lcodes - 1); /* literal tree */ + //Tracev((stderr, "\nlit tree: sent %ld", s->bits_sent)); + + send_tree(s, s.dyn_dtree, dcodes - 1); /* distance tree */ + //Tracev((stderr, "\ndist tree: sent %ld", s->bits_sent)); +}; + +/* =========================================================================== + * Check if the data type is TEXT or BINARY, using the following algorithm: + * - TEXT if the two conditions below are satisfied: + * a) There are no non-portable control characters belonging to the + * "black list" (0..6, 14..25, 28..31). + * b) There is at least one printable character belonging to the + * "white list" (9 {TAB}, 10 {LF}, 13 {CR}, 32..255). + * - BINARY otherwise. + * - The following partially-portable control characters form a + * "gray list" that is ignored in this detection algorithm: + * (7 {BEL}, 8 {BS}, 11 {VT}, 12 {FF}, 26 {SUB}, 27 {ESC}). + * IN assertion: the fields Freq of dyn_ltree are set. + */ +const detect_data_type = (s) => { + /* black_mask is the bit mask of black-listed bytes + * set bits 0..6, 14..25, and 28..31 + * 0xf3ffc07f = binary 11110011111111111100000001111111 + */ + let black_mask = 0xf3ffc07f; + let n; + + /* Check for non-textual ("black-listed") bytes. */ + for (n = 0; n <= 31; n++, black_mask >>>= 1) { + if ((black_mask & 1) && (s.dyn_ltree[n * 2] /*.Freq*/ !== 0)) { + return Z_BINARY; + } + } + + /* Check for textual ("white-listed") bytes. */ + if ( + s.dyn_ltree[9 * 2] /*.Freq*/ !== 0 || s.dyn_ltree[10 * 2] /*.Freq*/ !== 0 || + s.dyn_ltree[13 * 2] /*.Freq*/ !== 0 + ) { + return Z_TEXT; + } + for (n = 32; n < LITERALS$1; n++) { + if (s.dyn_ltree[n * 2] /*.Freq*/ !== 0) { + return Z_TEXT; + } + } + + /* There are no "black-listed" or "white-listed" bytes: + * this stream either is empty or has tolerated ("gray-listed") bytes only. + */ + return Z_BINARY; +}; + +let static_init_done = false; + +/* =========================================================================== + * Initialize the tree data structures for a new zlib stream. + */ +const _tr_init$1 = (s) => { + if (!static_init_done) { + tr_static_init(); + static_init_done = true; + } + + s.l_desc = new TreeDesc(s.dyn_ltree, static_l_desc); + s.d_desc = new TreeDesc(s.dyn_dtree, static_d_desc); + s.bl_desc = new TreeDesc(s.bl_tree, static_bl_desc); + + s.bi_buf = 0; + s.bi_valid = 0; + + /* Initialize the first block of the first file: */ + init_block(s); +}; + +/* =========================================================================== + * Send a stored block + */ +const _tr_stored_block$1 = (s, buf, stored_len, last) => //DeflateState *s; +//charf *buf; /* input block */ +//ulg stored_len; /* length of input block */ +//int last; /* one if this is the last block for a file */ +{ + send_bits(s, (STORED_BLOCK << 1) + (last ? 1 : 0), 3); /* send block type */ + copy_block(s, buf, stored_len, true); /* with header */ +}; + +/* =========================================================================== + * Send one empty static block to give enough lookahead for inflate. + * This takes 10 bits, of which 7 may remain in the bit buffer. + */ +const _tr_align$1 = (s) => { + send_bits(s, STATIC_TREES << 1, 3); + send_code(s, END_BLOCK, static_ltree); + bi_flush(s); +}; + +/* =========================================================================== + * Determine the best encoding for the current block: dynamic trees, static + * trees or store, and output the encoded block to the zip file. + */ +const _tr_flush_block$1 = (s, buf, stored_len, last) => //DeflateState *s; +//charf *buf; /* input block, or NULL if too old */ +//ulg stored_len; /* length of input block */ +//int last; /* one if this is the last block for a file */ +{ + let opt_lenb, static_lenb; /* opt_len and static_len in bytes */ + let max_blindex = 0; /* index of last bit length code of non zero freq */ + + /* Build the Huffman trees unless a stored block is forced */ + if (s.level > 0) { + /* Check if the file is binary or text */ + if (s.strm.data_type === Z_UNKNOWN$1) { + s.strm.data_type = detect_data_type(s); + } + + /* Construct the literal and distance trees */ + build_tree(s, s.l_desc); + // Tracev((stderr, "\nlit data: dyn %ld, stat %ld", s->opt_len, + // s->static_len)); + + build_tree(s, s.d_desc); + // Tracev((stderr, "\ndist data: dyn %ld, stat %ld", s->opt_len, + // s->static_len)); + /* At this point, opt_len and static_len are the total bit lengths of + * the compressed block data, excluding the tree representations. + */ + + /* Build the bit length tree for the above two trees, and get the index + * in bl_order of the last bit length code to send. + */ + max_blindex = build_bl_tree(s); + + /* Determine the best encoding. Compute the block lengths in bytes. */ + opt_lenb = (s.opt_len + 3 + 7) >>> 3; + static_lenb = (s.static_len + 3 + 7) >>> 3; + + // Tracev((stderr, "\nopt %lu(%lu) stat %lu(%lu) stored %lu lit %u ", + // opt_lenb, s->opt_len, static_lenb, s->static_len, stored_len, + // s->last_lit)); + + if (static_lenb <= opt_lenb) opt_lenb = static_lenb; + } else { + // Assert(buf != (char*)0, "lost buf"); + opt_lenb = static_lenb = stored_len + 5; /* force a stored block */ + } + + if ((stored_len + 4 <= opt_lenb) && (buf !== -1)) { + /* 4: two words for the lengths */ + + /* The test buf != NULL is only necessary if LIT_BUFSIZE > WSIZE. + * Otherwise we can't have processed more than WSIZE input bytes since + * the last block flush, because compression would have been + * successful. If LIT_BUFSIZE <= WSIZE, it is never too late to + * transform a block into a stored block. + */ + _tr_stored_block$1(s, buf, stored_len, last); + } else if (s.strategy === Z_FIXED$1 || static_lenb === opt_lenb) { + send_bits(s, (STATIC_TREES << 1) + (last ? 1 : 0), 3); + compress_block(s, static_ltree, static_dtree); + } else { + send_bits(s, (DYN_TREES << 1) + (last ? 1 : 0), 3); + send_all_trees( + s, + s.l_desc.max_code + 1, + s.d_desc.max_code + 1, + max_blindex + 1, + ); + compress_block(s, s.dyn_ltree, s.dyn_dtree); + } + // Assert (s->compressed_len == s->bits_sent, "bad compressed size"); + /* The above check is made mod 2^32, for files larger than 512 MB + * and uLong implemented on 32 bits. + */ + init_block(s); + + if (last) { + bi_windup(s); + } + // Tracev((stderr,"\ncomprlen %lu(%lu) ", s->compressed_len>>3, + // s->compressed_len-7*last)); +}; + +/* =========================================================================== + * Save the match info and tally the frequency counts. Return true if + * the current block must be flushed. + */ +const _tr_tally$1 = (s, dist, lc) => // deflate_state *s; +// unsigned dist; /* distance of matched string */ +// unsigned lc; /* match length-MIN_MATCH or unmatched char (if dist==0) */ +{ + //let out_length, in_length, dcode; + + s.pending_buf[s.d_buf + s.last_lit * 2] = (dist >>> 8) & 0xff; + s.pending_buf[s.d_buf + s.last_lit * 2 + 1] = dist & 0xff; + + s.pending_buf[s.l_buf + s.last_lit] = lc & 0xff; + s.last_lit++; + + if (dist === 0) { + /* lc is the unmatched char */ + s.dyn_ltree[lc * 2] /*.Freq*/++; + } else { + s.matches++; + /* Here, lc is the match length - MIN_MATCH */ + dist--; /* dist = match distance - 1 */ + //Assert((ush)dist < (ush)MAX_DIST(s) && + // (ush)lc <= (ush)(MAX_MATCH-MIN_MATCH) && + // (ush)d_code(dist) < (ush)D_CODES, "_tr_tally: bad match"); + + s.dyn_ltree[(_length_code[lc] + LITERALS$1 + 1) * 2] /*.Freq*/++; + s.dyn_dtree[d_code(dist) * 2] /*.Freq*/++; + } + + // (!) This block is disabled in zlib defaults, + // don't enable it for binary compatibility + + //#ifdef TRUNCATE_BLOCK + // /* Try to guess if it is profitable to stop the current block here */ + // if ((s.last_lit & 0x1fff) === 0 && s.level > 2) { + // /* Compute an upper bound for the compressed length */ + // out_length = s.last_lit*8; + // in_length = s.strstart - s.block_start; + // + // for (dcode = 0; dcode < D_CODES; dcode++) { + // out_length += s.dyn_dtree[dcode*2]/*.Freq*/ * (5 + extra_dbits[dcode]); + // } + // out_length >>>= 3; + // //Tracev((stderr,"\nlast_lit %u, in %ld, out ~%ld(%ld%%) ", + // // s->last_lit, in_length, out_length, + // // 100L - out_length*100L/in_length)); + // if (s.matches < (s.last_lit>>1)/*int /2*/ && out_length < (in_length>>1)/*int /2*/) { + // return true; + // } + // } + //#endif + + return (s.last_lit === s.lit_bufsize - 1); + /* We avoid equality with lit_bufsize because of wraparound at 64K + * on 16 bit machines and because stored blocks are restricted to + * 64K-1 bytes. + */ +}; + +var _tr_init_1 = _tr_init$1; +var _tr_stored_block_1 = _tr_stored_block$1; +var _tr_flush_block_1 = _tr_flush_block$1; +var _tr_tally_1 = _tr_tally$1; +var _tr_align_1 = _tr_align$1; + +var trees = { + _tr_init: _tr_init_1, + _tr_stored_block: _tr_stored_block_1, + _tr_flush_block: _tr_flush_block_1, + _tr_tally: _tr_tally_1, + _tr_align: _tr_align_1, +}; + +// Note: adler32 takes 12% for level 0 and 2% for level 6. +// It isn't worth it to make additional optimizations as in original. +// Small size is preferable. + +// (C) 1995-2013 Jean-loup Gailly and Mark Adler +// (C) 2014-2017 Vitaly Puzrin and Andrey Tupitsin +// +// This software is provided 'as-is', without any express or implied +// warranty. In no event will the authors be held liable for any damages +// arising from the use of this software. +// +// Permission is granted to anyone to use this software for any purpose, +// including commercial applications, and to alter it and redistribute it +// freely, subject to the following restrictions: +// +// 1. The origin of this software must not be misrepresented; you must not +// claim that you wrote the original software. If you use this software +// in a product, an acknowledgment in the product documentation would be +// appreciated but is not required. +// 2. Altered source versions must be plainly marked as such, and must not be +// misrepresented as being the original software. +// 3. This notice may not be removed or altered from any source distribution. + +const adler32 = (adler, buf, len, pos) => { + let s1 = (adler & 0xffff) | 0, + s2 = ((adler >>> 16) & 0xffff) | 0, + n = 0; + + while (len !== 0) { + // Set limit ~ twice less than 5552, to keep + // s2 in 31-bits, because we force signed ints. + // in other case %= will fail. + n = len > 2000 ? 2000 : len; + len -= n; + + do { + s1 = (s1 + buf[pos++]) | 0; + s2 = (s2 + s1) | 0; + } while (--n); + + s1 %= 65521; + s2 %= 65521; + } + + return (s1 | (s2 << 16)) | 0; +}; + +var adler32_1 = adler32; + +// Note: we can't get significant speed boost here. +// So write code to minimize size - no pregenerated tables +// and array tools dependencies. + +// (C) 1995-2013 Jean-loup Gailly and Mark Adler +// (C) 2014-2017 Vitaly Puzrin and Andrey Tupitsin +// +// This software is provided 'as-is', without any express or implied +// warranty. In no event will the authors be held liable for any damages +// arising from the use of this software. +// +// Permission is granted to anyone to use this software for any purpose, +// including commercial applications, and to alter it and redistribute it +// freely, subject to the following restrictions: +// +// 1. The origin of this software must not be misrepresented; you must not +// claim that you wrote the original software. If you use this software +// in a product, an acknowledgment in the product documentation would be +// appreciated but is not required. +// 2. Altered source versions must be plainly marked as such, and must not be +// misrepresented as being the original software. +// 3. This notice may not be removed or altered from any source distribution. + +// Use ordinary array, since untyped makes no boost here +const makeTable = () => { + let c, table = []; + + for (var n = 0; n < 256; n++) { + c = n; + for (var k = 0; k < 8; k++) { + c = (c & 1) ? (0xEDB88320 ^ (c >>> 1)) : (c >>> 1); + } + table[n] = c; + } + + return table; +}; + +// Create table on load. Just 255 signed longs. Not a problem. +const crcTable = new Uint32Array(makeTable()); + +const crc32 = (crc, buf, len, pos) => { + const t = crcTable; + const end = pos + len; + + crc ^= -1; + + for (let i = pos; i < end; i++) { + crc = (crc >>> 8) ^ t[(crc ^ buf[i]) & 0xFF]; + } + + return (crc ^ (-1)); // >>> 0; +}; + +var crc32_1 = crc32; + +// (C) 1995-2013 Jean-loup Gailly and Mark Adler +// (C) 2014-2017 Vitaly Puzrin and Andrey Tupitsin +// +// This software is provided 'as-is', without any express or implied +// warranty. In no event will the authors be held liable for any damages +// arising from the use of this software. +// +// Permission is granted to anyone to use this software for any purpose, +// including commercial applications, and to alter it and redistribute it +// freely, subject to the following restrictions: +// +// 1. The origin of this software must not be misrepresented; you must not +// claim that you wrote the original software. If you use this software +// in a product, an acknowledgment in the product documentation would be +// appreciated but is not required. +// 2. Altered source versions must be plainly marked as such, and must not be +// misrepresented as being the original software. +// 3. This notice may not be removed or altered from any source distribution. + +var messages = { + 2: "need dictionary", /* Z_NEED_DICT 2 */ + 1: "stream end", /* Z_STREAM_END 1 */ + 0: "", /* Z_OK 0 */ + "-1": "file error", /* Z_ERRNO (-1) */ + "-2": "stream error", /* Z_STREAM_ERROR (-2) */ + "-3": "data error", /* Z_DATA_ERROR (-3) */ + "-4": "insufficient memory", /* Z_MEM_ERROR (-4) */ + "-5": "buffer error", /* Z_BUF_ERROR (-5) */ + "-6": "incompatible version", /* Z_VERSION_ERROR (-6) */ +}; + +// (C) 1995-2013 Jean-loup Gailly and Mark Adler +// (C) 2014-2017 Vitaly Puzrin and Andrey Tupitsin +// +// This software is provided 'as-is', without any express or implied +// warranty. In no event will the authors be held liable for any damages +// arising from the use of this software. +// +// Permission is granted to anyone to use this software for any purpose, +// including commercial applications, and to alter it and redistribute it +// freely, subject to the following restrictions: +// +// 1. The origin of this software must not be misrepresented; you must not +// claim that you wrote the original software. If you use this software +// in a product, an acknowledgment in the product documentation would be +// appreciated but is not required. +// 2. Altered source versions must be plainly marked as such, and must not be +// misrepresented as being the original software. +// 3. This notice may not be removed or altered from any source distribution. + +var constants$2 = { + /* Allowed flush values; see deflate() and inflate() below for details */ + Z_NO_FLUSH: 0, + Z_PARTIAL_FLUSH: 1, + Z_SYNC_FLUSH: 2, + Z_FULL_FLUSH: 3, + Z_FINISH: 4, + Z_BLOCK: 5, + Z_TREES: 6, + + /* Return codes for the compression/decompression functions. Negative values + * are errors, positive values are used for special but normal events. + */ + Z_OK: 0, + Z_STREAM_END: 1, + Z_NEED_DICT: 2, + Z_ERRNO: -1, + Z_STREAM_ERROR: -2, + Z_DATA_ERROR: -3, + Z_MEM_ERROR: -4, + Z_BUF_ERROR: -5, + Z_VERSION_ERROR: -6, + + /* compression levels */ + Z_NO_COMPRESSION: 0, + Z_BEST_SPEED: 1, + Z_BEST_COMPRESSION: 9, + Z_DEFAULT_COMPRESSION: -1, + + Z_FILTERED: 1, + Z_HUFFMAN_ONLY: 2, + Z_RLE: 3, + Z_FIXED: 4, + Z_DEFAULT_STRATEGY: 0, + + /* Possible values of the data_type field (though see inflate()) */ + Z_BINARY: 0, + Z_TEXT: 1, + //Z_ASCII: 1, // = Z_TEXT (deprecated) + Z_UNKNOWN: 2, + + /* The deflate compression method */ + Z_DEFLATED: 8, + //Z_NULL: null // Use -1 or null inline, depending on var type +}; + +// (C) 1995-2013 Jean-loup Gailly and Mark Adler +// (C) 2014-2017 Vitaly Puzrin and Andrey Tupitsin +// +// This software is provided 'as-is', without any express or implied +// warranty. In no event will the authors be held liable for any damages +// arising from the use of this software. +// +// Permission is granted to anyone to use this software for any purpose, +// including commercial applications, and to alter it and redistribute it +// freely, subject to the following restrictions: +// +// 1. The origin of this software must not be misrepresented; you must not +// claim that you wrote the original software. If you use this software +// in a product, an acknowledgment in the product documentation would be +// appreciated but is not required. +// 2. Altered source versions must be plainly marked as such, and must not be +// misrepresented as being the original software. +// 3. This notice may not be removed or altered from any source distribution. + +const { _tr_init, _tr_stored_block, _tr_flush_block, _tr_tally, _tr_align } = + trees; + +/* Public constants ==========================================================*/ +/* ===========================================================================*/ + +const { + Z_NO_FLUSH: Z_NO_FLUSH$2, + Z_PARTIAL_FLUSH, + Z_FULL_FLUSH: Z_FULL_FLUSH$1, + Z_FINISH: Z_FINISH$3, + Z_BLOCK: Z_BLOCK$1, + Z_OK: Z_OK$3, + Z_STREAM_END: Z_STREAM_END$3, + Z_STREAM_ERROR: Z_STREAM_ERROR$2, + Z_DATA_ERROR: Z_DATA_ERROR$2, + Z_BUF_ERROR: Z_BUF_ERROR$1, + Z_DEFAULT_COMPRESSION: Z_DEFAULT_COMPRESSION$1, + Z_FILTERED, + Z_HUFFMAN_ONLY, + Z_RLE, + Z_FIXED, + Z_DEFAULT_STRATEGY: Z_DEFAULT_STRATEGY$1, + Z_UNKNOWN, + Z_DEFLATED: Z_DEFLATED$2, +} = constants$2; + +/*============================================================================*/ + +const MAX_MEM_LEVEL = 9; +/* Maximum value for memLevel in deflateInit2 */ +const MAX_WBITS$1 = 15; +/* 32K LZ77 window */ +const DEF_MEM_LEVEL = 8; + +const LENGTH_CODES = 29; +/* number of length codes, not counting the special END_BLOCK code */ +const LITERALS = 256; +/* number of literal bytes 0..255 */ +const L_CODES = LITERALS + 1 + LENGTH_CODES; +/* number of Literal or Length codes, including the END_BLOCK code */ +const D_CODES = 30; +/* number of distance codes */ +const BL_CODES = 19; +/* number of codes used to transfer the bit lengths */ +const HEAP_SIZE = 2 * L_CODES + 1; +/* maximum heap size */ +const MAX_BITS = 15; +/* All codes must not exceed MAX_BITS bits */ + +const MIN_MATCH = 3; +const MAX_MATCH = 258; +const MIN_LOOKAHEAD = MAX_MATCH + MIN_MATCH + 1; + +const PRESET_DICT = 0x20; + +const INIT_STATE = 42; +const EXTRA_STATE = 69; +const NAME_STATE = 73; +const COMMENT_STATE = 91; +const HCRC_STATE = 103; +const BUSY_STATE = 113; +const FINISH_STATE = 666; + +const BS_NEED_MORE = + 1; /* block not completed, need more input or more output */ +const BS_BLOCK_DONE = 2; /* block flush performed */ +const BS_FINISH_STARTED = + 3; /* finish started, need only more output at next deflate */ +const BS_FINISH_DONE = 4; /* finish done, accept no more input or output */ + +const OS_CODE = 0x03; // Unix :) . Don't detect, use this default. + +const err = (strm, errorCode) => { + strm.msg = messages[errorCode]; + return errorCode; +}; + +const rank = (f) => { + return ((f) << 1) - ((f) > 4 ? 9 : 0); +}; + +const zero = (buf) => { + let len = buf.length; + while (--len >= 0) buf[len] = 0; +}; + +/* eslint-disable new-cap */ +let HASH_ZLIB = (s, prev, data) => + ((prev << s.hash_shift) ^ data) & s.hash_mask; +// This hash causes less collisions, https://github.com/nodeca/pako/issues/135 +// But breaks binary compatibility +//let HASH_FAST = (s, prev, data) => ((prev << 8) + (prev >> 8) + (data << 4)) & s.hash_mask; +let HASH = HASH_ZLIB; + +/* ========================================================================= + * Flush as much pending output as possible. All deflate() output goes + * through this function so some applications may wish to modify it + * to avoid allocating a large strm->output buffer and copying into it. + * (See also read_buf()). + */ +const flush_pending = (strm) => { + const s = strm.state; + + //_tr_flush_bits(s); + let len = s.pending; + if (len > strm.avail_out) { + len = strm.avail_out; + } + if (len === 0) return; + + strm.output.set( + s.pending_buf.subarray(s.pending_out, s.pending_out + len), + strm.next_out, + ); + strm.next_out += len; + s.pending_out += len; + strm.total_out += len; + strm.avail_out -= len; + s.pending -= len; + if (s.pending === 0) { + s.pending_out = 0; + } +}; + +const flush_block_only = (s, last) => { + _tr_flush_block( + s, + s.block_start >= 0 ? s.block_start : -1, + s.strstart - s.block_start, + last, + ); + s.block_start = s.strstart; + flush_pending(s.strm); +}; + +const put_byte = (s, b) => { + s.pending_buf[s.pending++] = b; +}; + +/* ========================================================================= + * Put a short in the pending buffer. The 16-bit value is put in MSB order. + * IN assertion: the stream state is correct and there is enough room in + * pending_buf. + */ +const putShortMSB = (s, b) => { + // put_byte(s, (Byte)(b >> 8)); + // put_byte(s, (Byte)(b & 0xff)); + s.pending_buf[s.pending++] = (b >>> 8) & 0xff; + s.pending_buf[s.pending++] = b & 0xff; +}; + +/* =========================================================================== + * Read a new buffer from the current input stream, update the adler32 + * and total number of bytes read. All deflate() input goes through + * this function so some applications may wish to modify it to avoid + * allocating a large strm->input buffer and copying from it. + * (See also flush_pending()). + */ +const read_buf = (strm, buf, start, size) => { + let len = strm.avail_in; + + if (len > size) len = size; + if (len === 0) return 0; + + strm.avail_in -= len; + + // zmemcpy(buf, strm->next_in, len); + buf.set(strm.input.subarray(strm.next_in, strm.next_in + len), start); + if (strm.state.wrap === 1) { + strm.adler = adler32_1(strm.adler, buf, len, start); + } else if (strm.state.wrap === 2) { + strm.adler = crc32_1(strm.adler, buf, len, start); + } + + strm.next_in += len; + strm.total_in += len; + + return len; +}; + +/* =========================================================================== + * Set match_start to the longest match starting at the given string and + * return its length. Matches shorter or equal to prev_length are discarded, + * in which case the result is equal to prev_length and match_start is + * garbage. + * IN assertions: cur_match is the head of the hash chain for the current + * string (strstart) and its distance is <= MAX_DIST, and prev_length >= 1 + * OUT assertion: the match length is not greater than s->lookahead. + */ +const longest_match = (s, cur_match) => { + let chain_length = s.max_chain_length; /* max hash chain length */ + let scan = s.strstart; /* current string */ + let match; /* matched string */ + let len; /* length of current match */ + let best_len = s.prev_length; /* best match length so far */ + let nice_match = s.nice_match; /* stop if match long enough */ + const limit = (s.strstart > (s.w_size - MIN_LOOKAHEAD)) + ? s.strstart - (s.w_size - MIN_LOOKAHEAD) + : 0 /*NIL*/; + + const _win = s.window; // shortcut + + const wmask = s.w_mask; + const prev = s.prev; + + /* Stop when cur_match becomes <= limit. To simplify the code, + * we prevent matches with the string of window index 0. + */ + + const strend = s.strstart + MAX_MATCH; + let scan_end1 = _win[scan + best_len - 1]; + let scan_end = _win[scan + best_len]; + + /* The code is optimized for HASH_BITS >= 8 and MAX_MATCH-2 multiple of 16. + * It is easy to get rid of this optimization if necessary. + */ + // Assert(s->hash_bits >= 8 && MAX_MATCH == 258, "Code too clever"); + + /* Do not waste too much time if we already have a good match: */ + if (s.prev_length >= s.good_match) { + chain_length >>= 2; + } + /* Do not look for matches beyond the end of the input. This is necessary + * to make deflate deterministic. + */ + if (nice_match > s.lookahead) nice_match = s.lookahead; + + // Assert((ulg)s->strstart <= s->window_size-MIN_LOOKAHEAD, "need lookahead"); + + do { + // Assert(cur_match < s->strstart, "no future"); + match = cur_match; + + /* Skip to next match if the match length cannot increase + * or if the match length is less than 2. Note that the checks below + * for insufficient lookahead only occur occasionally for performance + * reasons. Therefore uninitialized memory will be accessed, and + * conditional jumps will be made that depend on those values. + * However the length of the match is limited to the lookahead, so + * the output of deflate is not affected by the uninitialized values. + */ + + if ( + _win[match + best_len] !== scan_end || + _win[match + best_len - 1] !== scan_end1 || + _win[match] !== _win[scan] || + _win[++match] !== _win[scan + 1] + ) { + continue; + } + + /* The check at best_len-1 can be removed because it will be made + * again later. (This heuristic is not always a win.) + * It is not necessary to compare scan[2] and match[2] since they + * are always equal when the other bytes match, given that + * the hash keys are equal and that HASH_BITS >= 8. + */ + scan += 2; + match++; + // Assert(*scan == *match, "match[2]?"); + + /* We check for insufficient lookahead only every 8th comparison; + * the 256th check will be made at strstart+258. + */ + do { + /*jshint noempty:false*/ + } while ( + _win[++scan] === _win[++match] && _win[++scan] === _win[++match] && + _win[++scan] === _win[++match] && _win[++scan] === _win[++match] && + _win[++scan] === _win[++match] && _win[++scan] === _win[++match] && + _win[++scan] === _win[++match] && _win[++scan] === _win[++match] && + scan < strend + ); + + // Assert(scan <= s->window+(unsigned)(s->window_size-1), "wild scan"); + + len = MAX_MATCH - (strend - scan); + scan = strend - MAX_MATCH; + + if (len > best_len) { + s.match_start = cur_match; + best_len = len; + if (len >= nice_match) { + break; + } + scan_end1 = _win[scan + best_len - 1]; + scan_end = _win[scan + best_len]; + } + } while ( + (cur_match = prev[cur_match & wmask]) > limit && --chain_length !== 0 + ); + + if (best_len <= s.lookahead) { + return best_len; + } + return s.lookahead; +}; + +/* =========================================================================== + * Fill the window when the lookahead becomes insufficient. + * Updates strstart and lookahead. + * + * IN assertion: lookahead < MIN_LOOKAHEAD + * OUT assertions: strstart <= window_size-MIN_LOOKAHEAD + * At least one byte has been read, or avail_in == 0; reads are + * performed for at least two bytes (required for the zip translate_eol + * option -- not supported here). + */ +const fill_window = (s) => { + const _w_size = s.w_size; + let p, n, m, more, str; + + //Assert(s->lookahead < MIN_LOOKAHEAD, "already enough lookahead"); + + do { + more = s.window_size - s.lookahead - s.strstart; + + // JS ints have 32 bit, block below not needed + /* Deal with !@#$% 64K limit: */ + //if (sizeof(int) <= 2) { + // if (more == 0 && s->strstart == 0 && s->lookahead == 0) { + // more = wsize; + // + // } else if (more == (unsigned)(-1)) { + // /* Very unlikely, but possible on 16 bit machine if + // * strstart == 0 && lookahead == 1 (input done a byte at time) + // */ + // more--; + // } + //} + + /* If the window is almost full and there is insufficient lookahead, + * move the upper half to the lower one to make room in the upper half. + */ + if (s.strstart >= _w_size + (_w_size - MIN_LOOKAHEAD)) { + s.window.set(s.window.subarray(_w_size, _w_size + _w_size), 0); + s.match_start -= _w_size; + s.strstart -= _w_size; + /* we now have strstart >= MAX_DIST */ + s.block_start -= _w_size; + + /* Slide the hash table (could be avoided with 32 bit values + at the expense of memory usage). We slide even when level == 0 + to keep the hash table consistent if we switch back to level > 0 + later. (Using level 0 permanently is not an optimal usage of + zlib, so we don't care about this pathological case.) + */ + + n = s.hash_size; + p = n; + + do { + m = s.head[--p]; + s.head[p] = m >= _w_size ? m - _w_size : 0; + } while (--n); + + n = _w_size; + p = n; + + do { + m = s.prev[--p]; + s.prev[p] = m >= _w_size ? m - _w_size : 0; + /* If n is not on any hash chain, prev[n] is garbage but + * its value will never be used. + */ + } while (--n); + + more += _w_size; + } + if (s.strm.avail_in === 0) { + break; + } + + /* If there was no sliding: + * strstart <= WSIZE+MAX_DIST-1 && lookahead <= MIN_LOOKAHEAD - 1 && + * more == window_size - lookahead - strstart + * => more >= window_size - (MIN_LOOKAHEAD-1 + WSIZE + MAX_DIST-1) + * => more >= window_size - 2*WSIZE + 2 + * In the BIG_MEM or MMAP case (not yet supported), + * window_size == input_size + MIN_LOOKAHEAD && + * strstart + s->lookahead <= input_size => more >= MIN_LOOKAHEAD. + * Otherwise, window_size == 2*WSIZE so more >= 2. + * If there was sliding, more >= WSIZE. So in all cases, more >= 2. + */ + //Assert(more >= 2, "more < 2"); + n = read_buf(s.strm, s.window, s.strstart + s.lookahead, more); + s.lookahead += n; + + /* Initialize the hash value now that we have some input: */ + if (s.lookahead + s.insert >= MIN_MATCH) { + str = s.strstart - s.insert; + s.ins_h = s.window[str]; + + /* UPDATE_HASH(s, s->ins_h, s->window[str + 1]); */ + s.ins_h = HASH(s, s.ins_h, s.window[str + 1]); + //#if MIN_MATCH != 3 + // Call update_hash() MIN_MATCH-3 more times + //#endif + while (s.insert) { + /* UPDATE_HASH(s, s->ins_h, s->window[str + MIN_MATCH-1]); */ + s.ins_h = HASH(s, s.ins_h, s.window[str + MIN_MATCH - 1]); + + s.prev[str & s.w_mask] = s.head[s.ins_h]; + s.head[s.ins_h] = str; + str++; + s.insert--; + if (s.lookahead + s.insert < MIN_MATCH) { + break; + } + } + } + /* If the whole input has less than MIN_MATCH bytes, ins_h is garbage, + * but this is not important since only literal bytes will be emitted. + */ + } while (s.lookahead < MIN_LOOKAHEAD && s.strm.avail_in !== 0); + + /* If the WIN_INIT bytes after the end of the current data have never been + * written, then zero those bytes in order to avoid memory check reports of + * the use of uninitialized (or uninitialised as Julian writes) bytes by + * the longest match routines. Update the high water mark for the next + * time through here. WIN_INIT is set to MAX_MATCH since the longest match + * routines allow scanning to strstart + MAX_MATCH, ignoring lookahead. + */ + // if (s.high_water < s.window_size) { + // const curr = s.strstart + s.lookahead; + // let init = 0; + // + // if (s.high_water < curr) { + // /* Previous high water mark below current data -- zero WIN_INIT + // * bytes or up to end of window, whichever is less. + // */ + // init = s.window_size - curr; + // if (init > WIN_INIT) + // init = WIN_INIT; + // zmemzero(s->window + curr, (unsigned)init); + // s->high_water = curr + init; + // } + // else if (s->high_water < (ulg)curr + WIN_INIT) { + // /* High water mark at or above current data, but below current data + // * plus WIN_INIT -- zero out to current data plus WIN_INIT, or up + // * to end of window, whichever is less. + // */ + // init = (ulg)curr + WIN_INIT - s->high_water; + // if (init > s->window_size - s->high_water) + // init = s->window_size - s->high_water; + // zmemzero(s->window + s->high_water, (unsigned)init); + // s->high_water += init; + // } + // } + // + // Assert((ulg)s->strstart <= s->window_size - MIN_LOOKAHEAD, + // "not enough room for search"); +}; + +/* =========================================================================== + * Copy without compression as much as possible from the input stream, return + * the current block state. + * This function does not insert new strings in the dictionary since + * uncompressible data is probably not useful. This function is used + * only for the level=0 compression option. + * NOTE: this function should be optimized to avoid extra copying from + * window to pending_buf. + */ +const deflate_stored = (s, flush) => { + /* Stored blocks are limited to 0xffff bytes, pending_buf is limited + * to pending_buf_size, and each stored block has a 5 byte header: + */ + let max_block_size = 0xffff; + + if (max_block_size > s.pending_buf_size - 5) { + max_block_size = s.pending_buf_size - 5; + } + + /* Copy as much as possible from input to output: */ + for (;;) { + /* Fill the window as much as possible: */ + if (s.lookahead <= 1) { + //Assert(s->strstart < s->w_size+MAX_DIST(s) || + // s->block_start >= (long)s->w_size, "slide too late"); + // if (!(s.strstart < s.w_size + (s.w_size - MIN_LOOKAHEAD) || + // s.block_start >= s.w_size)) { + // throw new Error("slide too late"); + // } + + fill_window(s); + if (s.lookahead === 0 && flush === Z_NO_FLUSH$2) { + return BS_NEED_MORE; + } + + if (s.lookahead === 0) { + break; + } + /* flush the current block */ + } + //Assert(s->block_start >= 0L, "block gone"); + // if (s.block_start < 0) throw new Error("block gone"); + + s.strstart += s.lookahead; + s.lookahead = 0; + + /* Emit a stored block if pending_buf will be full: */ + const max_start = s.block_start + max_block_size; + + if (s.strstart === 0 || s.strstart >= max_start) { + /* strstart == 0 is possible when wraparound on 16-bit machine */ + s.lookahead = s.strstart - max_start; + s.strstart = max_start; + /*** FLUSH_BLOCK(s, 0); ***/ + flush_block_only(s, false); + if (s.strm.avail_out === 0) { + return BS_NEED_MORE; + } + /***/ + } + /* Flush if we may have to slide, otherwise block_start may become + * negative and the data will be gone: + */ + if (s.strstart - s.block_start >= (s.w_size - MIN_LOOKAHEAD)) { + /*** FLUSH_BLOCK(s, 0); ***/ + flush_block_only(s, false); + if (s.strm.avail_out === 0) { + return BS_NEED_MORE; + } + /***/ + } + } + + s.insert = 0; + + if (flush === Z_FINISH$3) { + /*** FLUSH_BLOCK(s, 1); ***/ + flush_block_only(s, true); + if (s.strm.avail_out === 0) { + return BS_FINISH_STARTED; + } + /***/ + return BS_FINISH_DONE; + } + + if (s.strstart > s.block_start) { + /*** FLUSH_BLOCK(s, 0); ***/ + flush_block_only(s, false); + if (s.strm.avail_out === 0) { + return BS_NEED_MORE; + } + /***/ + } + + return BS_NEED_MORE; +}; + +/* =========================================================================== + * Compress as much as possible from the input stream, return the current + * block state. + * This function does not perform lazy evaluation of matches and inserts + * new strings in the dictionary only for unmatched strings or for short + * matches. It is used only for the fast compression options. + */ +const deflate_fast = (s, flush) => { + let hash_head; /* head of the hash chain */ + let bflush; /* set if current block must be flushed */ + + for (;;) { + /* Make sure that we always have enough lookahead, except + * at the end of the input file. We need MAX_MATCH bytes + * for the next match, plus MIN_MATCH bytes to insert the + * string following the next match. + */ + if (s.lookahead < MIN_LOOKAHEAD) { + fill_window(s); + if (s.lookahead < MIN_LOOKAHEAD && flush === Z_NO_FLUSH$2) { + return BS_NEED_MORE; + } + if (s.lookahead === 0) { + break; /* flush the current block */ + } + } + + /* Insert the string window[strstart .. strstart+2] in the + * dictionary, and set hash_head to the head of the hash chain: + */ + hash_head = 0 /*NIL*/; + if (s.lookahead >= MIN_MATCH) { + /*** INSERT_STRING(s, s.strstart, hash_head); ***/ + s.ins_h = HASH(s, s.ins_h, s.window[s.strstart + MIN_MATCH - 1]); + hash_head = s.prev[s.strstart & s.w_mask] = s.head[s.ins_h]; + s.head[s.ins_h] = s.strstart; + /***/ + } + + /* Find the longest match, discarding those <= prev_length. + * At this point we have always match_length < MIN_MATCH + */ + if ( + hash_head !== 0 /*NIL*/ && + ((s.strstart - hash_head) <= (s.w_size - MIN_LOOKAHEAD)) + ) { + /* To simplify the code, we prevent matches with the string + * of window index 0 (in particular we have to avoid a match + * of the string with itself at the start of the input file). + */ + s.match_length = longest_match(s, hash_head); + /* longest_match() sets match_start */ + } + if (s.match_length >= MIN_MATCH) { + // check_match(s, s.strstart, s.match_start, s.match_length); // for debug only + + /*** _tr_tally_dist(s, s.strstart - s.match_start, + s.match_length - MIN_MATCH, bflush); ***/ + bflush = _tr_tally( + s, + s.strstart - s.match_start, + s.match_length - MIN_MATCH, + ); + + s.lookahead -= s.match_length; + + /* Insert new strings in the hash table only if the match length + * is not too large. This saves time but degrades compression. + */ + if ( + s.match_length <= s.max_lazy_match /*max_insert_length*/ && + s.lookahead >= MIN_MATCH + ) { + s.match_length--; /* string at strstart already in table */ + do { + s.strstart++; + /*** INSERT_STRING(s, s.strstart, hash_head); ***/ + s.ins_h = HASH(s, s.ins_h, s.window[s.strstart + MIN_MATCH - 1]); + hash_head = s.prev[s.strstart & s.w_mask] = s.head[s.ins_h]; + s.head[s.ins_h] = s.strstart; + /***/ + /* strstart never exceeds WSIZE-MAX_MATCH, so there are + * always MIN_MATCH bytes ahead. + */ + } while (--s.match_length !== 0); + s.strstart++; + } else { + s.strstart += s.match_length; + s.match_length = 0; + s.ins_h = s.window[s.strstart]; + /* UPDATE_HASH(s, s.ins_h, s.window[s.strstart+1]); */ + s.ins_h = HASH(s, s.ins_h, s.window[s.strstart + 1]); + + //#if MIN_MATCH != 3 + // Call UPDATE_HASH() MIN_MATCH-3 more times + //#endif + /* If lookahead < MIN_MATCH, ins_h is garbage, but it does not + * matter since it will be recomputed at next deflate call. + */ + } + } else { + /* No match, output a literal byte */ + //Tracevv((stderr,"%c", s.window[s.strstart])); + /*** _tr_tally_lit(s, s.window[s.strstart], bflush); ***/ + bflush = _tr_tally(s, 0, s.window[s.strstart]); + + s.lookahead--; + s.strstart++; + } + if (bflush) { + /*** FLUSH_BLOCK(s, 0); ***/ + flush_block_only(s, false); + if (s.strm.avail_out === 0) { + return BS_NEED_MORE; + } + /***/ + } + } + s.insert = (s.strstart < (MIN_MATCH - 1)) ? s.strstart : MIN_MATCH - 1; + if (flush === Z_FINISH$3) { + /*** FLUSH_BLOCK(s, 1); ***/ + flush_block_only(s, true); + if (s.strm.avail_out === 0) { + return BS_FINISH_STARTED; + } + /***/ + return BS_FINISH_DONE; + } + if (s.last_lit) { + /*** FLUSH_BLOCK(s, 0); ***/ + flush_block_only(s, false); + if (s.strm.avail_out === 0) { + return BS_NEED_MORE; + } + /***/ + } + return BS_BLOCK_DONE; +}; + +/* =========================================================================== + * Same as above, but achieves better compression. We use a lazy + * evaluation for matches: a match is finally adopted only if there is + * no better match at the next window position. + */ +const deflate_slow = (s, flush) => { + let hash_head; /* head of hash chain */ + let bflush; /* set if current block must be flushed */ + + let max_insert; + + /* Process the input block. */ + for (;;) { + /* Make sure that we always have enough lookahead, except + * at the end of the input file. We need MAX_MATCH bytes + * for the next match, plus MIN_MATCH bytes to insert the + * string following the next match. + */ + if (s.lookahead < MIN_LOOKAHEAD) { + fill_window(s); + if (s.lookahead < MIN_LOOKAHEAD && flush === Z_NO_FLUSH$2) { + return BS_NEED_MORE; + } + if (s.lookahead === 0) break; /* flush the current block */ + } + + /* Insert the string window[strstart .. strstart+2] in the + * dictionary, and set hash_head to the head of the hash chain: + */ + hash_head = 0 /*NIL*/; + if (s.lookahead >= MIN_MATCH) { + /*** INSERT_STRING(s, s.strstart, hash_head); ***/ + s.ins_h = HASH(s, s.ins_h, s.window[s.strstart + MIN_MATCH - 1]); + hash_head = s.prev[s.strstart & s.w_mask] = s.head[s.ins_h]; + s.head[s.ins_h] = s.strstart; + /***/ + } + + /* Find the longest match, discarding those <= prev_length. + */ + s.prev_length = s.match_length; + s.prev_match = s.match_start; + s.match_length = MIN_MATCH - 1; + + if ( + hash_head !== 0 /*NIL*/ && s.prev_length < s.max_lazy_match && + s.strstart - hash_head <= (s.w_size - MIN_LOOKAHEAD) /*MAX_DIST(s)*/ + ) { + /* To simplify the code, we prevent matches with the string + * of window index 0 (in particular we have to avoid a match + * of the string with itself at the start of the input file). + */ + s.match_length = longest_match(s, hash_head); + /* longest_match() sets match_start */ + + if ( + s.match_length <= 5 && + (s.strategy === Z_FILTERED || + (s.match_length === MIN_MATCH && + s.strstart - s.match_start > 4096 /*TOO_FAR*/)) + ) { + /* If prev_match is also MIN_MATCH, match_start is garbage + * but we will ignore the current match anyway. + */ + s.match_length = MIN_MATCH - 1; + } + } + /* If there was a match at the previous step and the current + * match is not better, output the previous match: + */ + if (s.prev_length >= MIN_MATCH && s.match_length <= s.prev_length) { + max_insert = s.strstart + s.lookahead - MIN_MATCH; + /* Do not insert strings in hash table beyond this. */ + + //check_match(s, s.strstart-1, s.prev_match, s.prev_length); + + /***_tr_tally_dist(s, s.strstart - 1 - s.prev_match, + s.prev_length - MIN_MATCH, bflush);***/ + bflush = _tr_tally( + s, + s.strstart - 1 - s.prev_match, + s.prev_length - MIN_MATCH, + ); + /* Insert in hash table all strings up to the end of the match. + * strstart-1 and strstart are already inserted. If there is not + * enough lookahead, the last two strings are not inserted in + * the hash table. + */ + s.lookahead -= s.prev_length - 1; + s.prev_length -= 2; + do { + if (++s.strstart <= max_insert) { + /*** INSERT_STRING(s, s.strstart, hash_head); ***/ + s.ins_h = HASH(s, s.ins_h, s.window[s.strstart + MIN_MATCH - 1]); + hash_head = s.prev[s.strstart & s.w_mask] = s.head[s.ins_h]; + s.head[s.ins_h] = s.strstart; + /***/ + } + } while (--s.prev_length !== 0); + s.match_available = 0; + s.match_length = MIN_MATCH - 1; + s.strstart++; + + if (bflush) { + /*** FLUSH_BLOCK(s, 0); ***/ + flush_block_only(s, false); + if (s.strm.avail_out === 0) { + return BS_NEED_MORE; + } + /***/ + } + } else if (s.match_available) { + /* If there was no match at the previous position, output a + * single literal. If there was a match but the current match + * is longer, truncate the previous match to a single literal. + */ + //Tracevv((stderr,"%c", s->window[s->strstart-1])); + /*** _tr_tally_lit(s, s.window[s.strstart-1], bflush); ***/ + bflush = _tr_tally(s, 0, s.window[s.strstart - 1]); + + if (bflush) { + /*** FLUSH_BLOCK_ONLY(s, 0) ***/ + flush_block_only(s, false); + /***/ + } + s.strstart++; + s.lookahead--; + if (s.strm.avail_out === 0) { + return BS_NEED_MORE; + } + } else { + /* There is no previous match to compare with, wait for + * the next step to decide. + */ + s.match_available = 1; + s.strstart++; + s.lookahead--; + } + } + //Assert (flush != Z_NO_FLUSH, "no flush?"); + if (s.match_available) { + //Tracevv((stderr,"%c", s->window[s->strstart-1])); + /*** _tr_tally_lit(s, s.window[s.strstart-1], bflush); ***/ + bflush = _tr_tally(s, 0, s.window[s.strstart - 1]); + + s.match_available = 0; + } + s.insert = s.strstart < MIN_MATCH - 1 ? s.strstart : MIN_MATCH - 1; + if (flush === Z_FINISH$3) { + /*** FLUSH_BLOCK(s, 1); ***/ + flush_block_only(s, true); + if (s.strm.avail_out === 0) { + return BS_FINISH_STARTED; + } + /***/ + return BS_FINISH_DONE; + } + if (s.last_lit) { + /*** FLUSH_BLOCK(s, 0); ***/ + flush_block_only(s, false); + if (s.strm.avail_out === 0) { + return BS_NEED_MORE; + } + /***/ + } + + return BS_BLOCK_DONE; +}; + +/* =========================================================================== + * For Z_RLE, simply look for runs of bytes, generate matches only of distance + * one. Do not maintain a hash table. (It will be regenerated if this run of + * deflate switches away from Z_RLE.) + */ +const deflate_rle = (s, flush) => { + let bflush; /* set if current block must be flushed */ + let prev; /* byte at distance one to match */ + let scan, strend; /* scan goes up to strend for length of run */ + + const _win = s.window; + + for (;;) { + /* Make sure that we always have enough lookahead, except + * at the end of the input file. We need MAX_MATCH bytes + * for the longest run, plus one for the unrolled loop. + */ + if (s.lookahead <= MAX_MATCH) { + fill_window(s); + if (s.lookahead <= MAX_MATCH && flush === Z_NO_FLUSH$2) { + return BS_NEED_MORE; + } + if (s.lookahead === 0) break; /* flush the current block */ + } + + /* See how many times the previous byte repeats */ + s.match_length = 0; + if (s.lookahead >= MIN_MATCH && s.strstart > 0) { + scan = s.strstart - 1; + prev = _win[scan]; + if ( + prev === _win[++scan] && prev === _win[++scan] && prev === _win[++scan] + ) { + strend = s.strstart + MAX_MATCH; + do { + /*jshint noempty:false*/ + } while ( + prev === _win[++scan] && prev === _win[++scan] && + prev === _win[++scan] && prev === _win[++scan] && + prev === _win[++scan] && prev === _win[++scan] && + prev === _win[++scan] && prev === _win[++scan] && + scan < strend + ); + s.match_length = MAX_MATCH - (strend - scan); + if (s.match_length > s.lookahead) { + s.match_length = s.lookahead; + } + } + //Assert(scan <= s->window+(uInt)(s->window_size-1), "wild scan"); + } + + /* Emit match if have run of MIN_MATCH or longer, else emit literal */ + if (s.match_length >= MIN_MATCH) { + //check_match(s, s.strstart, s.strstart - 1, s.match_length); + + /*** _tr_tally_dist(s, 1, s.match_length - MIN_MATCH, bflush); ***/ + bflush = _tr_tally(s, 1, s.match_length - MIN_MATCH); + + s.lookahead -= s.match_length; + s.strstart += s.match_length; + s.match_length = 0; + } else { + /* No match, output a literal byte */ + //Tracevv((stderr,"%c", s->window[s->strstart])); + /*** _tr_tally_lit(s, s.window[s.strstart], bflush); ***/ + bflush = _tr_tally(s, 0, s.window[s.strstart]); + + s.lookahead--; + s.strstart++; + } + if (bflush) { + /*** FLUSH_BLOCK(s, 0); ***/ + flush_block_only(s, false); + if (s.strm.avail_out === 0) { + return BS_NEED_MORE; + } + /***/ + } + } + s.insert = 0; + if (flush === Z_FINISH$3) { + /*** FLUSH_BLOCK(s, 1); ***/ + flush_block_only(s, true); + if (s.strm.avail_out === 0) { + return BS_FINISH_STARTED; + } + /***/ + return BS_FINISH_DONE; + } + if (s.last_lit) { + /*** FLUSH_BLOCK(s, 0); ***/ + flush_block_only(s, false); + if (s.strm.avail_out === 0) { + return BS_NEED_MORE; + } + /***/ + } + return BS_BLOCK_DONE; +}; + +/* =========================================================================== + * For Z_HUFFMAN_ONLY, do not look for matches. Do not maintain a hash table. + * (It will be regenerated if this run of deflate switches away from Huffman.) + */ +const deflate_huff = (s, flush) => { + let bflush; /* set if current block must be flushed */ + + for (;;) { + /* Make sure that we have a literal to write. */ + if (s.lookahead === 0) { + fill_window(s); + if (s.lookahead === 0) { + if (flush === Z_NO_FLUSH$2) { + return BS_NEED_MORE; + } + break; /* flush the current block */ + } + } + + /* Output a literal byte */ + s.match_length = 0; + //Tracevv((stderr,"%c", s->window[s->strstart])); + /*** _tr_tally_lit(s, s.window[s.strstart], bflush); ***/ + bflush = _tr_tally(s, 0, s.window[s.strstart]); + s.lookahead--; + s.strstart++; + if (bflush) { + /*** FLUSH_BLOCK(s, 0); ***/ + flush_block_only(s, false); + if (s.strm.avail_out === 0) { + return BS_NEED_MORE; + } + /***/ + } + } + s.insert = 0; + if (flush === Z_FINISH$3) { + /*** FLUSH_BLOCK(s, 1); ***/ + flush_block_only(s, true); + if (s.strm.avail_out === 0) { + return BS_FINISH_STARTED; + } + /***/ + return BS_FINISH_DONE; + } + if (s.last_lit) { + /*** FLUSH_BLOCK(s, 0); ***/ + flush_block_only(s, false); + if (s.strm.avail_out === 0) { + return BS_NEED_MORE; + } + /***/ + } + return BS_BLOCK_DONE; +}; + +/* Values for max_lazy_match, good_match and max_chain_length, depending on + * the desired pack level (0..9). The values given below have been tuned to + * exclude worst case performance for pathological files. Better values may be + * found for specific files. + */ +function Config(good_length, max_lazy, nice_length, max_chain, func) { + this.good_length = good_length; + this.max_lazy = max_lazy; + this.nice_length = nice_length; + this.max_chain = max_chain; + this.func = func; +} + +const configuration_table = [ + /* good lazy nice chain */ + new Config(0, 0, 0, 0, deflate_stored), /* 0 store only */ + new Config(4, 4, 8, 4, deflate_fast), /* 1 max speed, no lazy matches */ + new Config(4, 5, 16, 8, deflate_fast), /* 2 */ + new Config(4, 6, 32, 32, deflate_fast), /* 3 */ + + new Config(4, 4, 16, 16, deflate_slow), /* 4 lazy matches */ + new Config(8, 16, 32, 32, deflate_slow), /* 5 */ + new Config(8, 16, 128, 128, deflate_slow), /* 6 */ + new Config(8, 32, 128, 256, deflate_slow), /* 7 */ + new Config(32, 128, 258, 1024, deflate_slow), /* 8 */ + new Config(32, 258, 258, 4096, deflate_slow), /* 9 max compression */ +]; + +/* =========================================================================== + * Initialize the "longest match" routines for a new zlib stream + */ +const lm_init = (s) => { + s.window_size = 2 * s.w_size; + + /*** CLEAR_HASH(s); ***/ + zero(s.head); // Fill with NIL (= 0); + + /* Set the default configuration parameters: + */ + s.max_lazy_match = configuration_table[s.level].max_lazy; + s.good_match = configuration_table[s.level].good_length; + s.nice_match = configuration_table[s.level].nice_length; + s.max_chain_length = configuration_table[s.level].max_chain; + + s.strstart = 0; + s.block_start = 0; + s.lookahead = 0; + s.insert = 0; + s.match_length = s.prev_length = MIN_MATCH - 1; + s.match_available = 0; + s.ins_h = 0; +}; + +function DeflateState() { + this.strm = null; /* pointer back to this zlib stream */ + this.status = 0; /* as the name implies */ + this.pending_buf = null; /* output still pending */ + this.pending_buf_size = 0; /* size of pending_buf */ + this.pending_out = 0; /* next pending byte to output to the stream */ + this.pending = 0; /* nb of bytes in the pending buffer */ + this.wrap = 0; /* bit 0 true for zlib, bit 1 true for gzip */ + this.gzhead = null; /* gzip header information to write */ + this.gzindex = 0; /* where in extra, name, or comment */ + this.method = Z_DEFLATED$2; /* can only be DEFLATED */ + this.last_flush = -1; /* value of flush param for previous deflate call */ + + this.w_size = 0; /* LZ77 window size (32K by default) */ + this.w_bits = 0; /* log2(w_size) (8..16) */ + this.w_mask = 0; /* w_size - 1 */ + + this.window = null; + /* Sliding window. Input bytes are read into the second half of the window, + * and move to the first half later to keep a dictionary of at least wSize + * bytes. With this organization, matches are limited to a distance of + * wSize-MAX_MATCH bytes, but this ensures that IO is always + * performed with a length multiple of the block size. + */ + + this.window_size = 0; + /* Actual size of window: 2*wSize, except when the user input buffer + * is directly used as sliding window. + */ + + this.prev = null; + /* Link to older string with same hash index. To limit the size of this + * array to 64K, this link is maintained only for the last 32K strings. + * An index in this array is thus a window index modulo 32K. + */ + + this.head = null; /* Heads of the hash chains or NIL. */ + + this.ins_h = 0; /* hash index of string to be inserted */ + this.hash_size = 0; /* number of elements in hash table */ + this.hash_bits = 0; /* log2(hash_size) */ + this.hash_mask = 0; /* hash_size-1 */ + + this.hash_shift = 0; + /* Number of bits by which ins_h must be shifted at each input + * step. It must be such that after MIN_MATCH steps, the oldest + * byte no longer takes part in the hash key, that is: + * hash_shift * MIN_MATCH >= hash_bits + */ + + this.block_start = 0; + /* Window position at the beginning of the current output block. Gets + * negative when the window is moved backwards. + */ + + this.match_length = 0; /* length of best match */ + this.prev_match = 0; /* previous match */ + this.match_available = 0; /* set if previous match exists */ + this.strstart = 0; /* start of string to insert */ + this.match_start = 0; /* start of matching string */ + this.lookahead = 0; /* number of valid bytes ahead in window */ + + this.prev_length = 0; + /* Length of the best match at previous step. Matches not greater than this + * are discarded. This is used in the lazy match evaluation. + */ + + this.max_chain_length = 0; + /* To speed up deflation, hash chains are never searched beyond this + * length. A higher limit improves compression ratio but degrades the + * speed. + */ + + this.max_lazy_match = 0; + /* Attempt to find a better match only when the current match is strictly + * smaller than this value. This mechanism is used only for compression + * levels >= 4. + */ + // That's alias to max_lazy_match, don't use directly + //this.max_insert_length = 0; + /* Insert new strings in the hash table only if the match length is not + * greater than this length. This saves time but degrades compression. + * max_insert_length is used only for compression levels <= 3. + */ + + this.level = 0; /* compression level (1..9) */ + this.strategy = 0; /* favor or force Huffman coding*/ + + this.good_match = 0; + /* Use a faster search when the previous match is longer than this */ + + this.nice_match = 0; /* Stop searching when current match exceeds this */ + + /* used by trees.c: */ + + /* Didn't use ct_data typedef below to suppress compiler warning */ + + // struct ct_data_s dyn_ltree[HEAP_SIZE]; /* literal and length tree */ + // struct ct_data_s dyn_dtree[2*D_CODES+1]; /* distance tree */ + // struct ct_data_s bl_tree[2*BL_CODES+1]; /* Huffman tree for bit lengths */ + + // Use flat array of DOUBLE size, with interleaved fata, + // because JS does not support effective + this.dyn_ltree = new Uint16Array(HEAP_SIZE * 2); + this.dyn_dtree = new Uint16Array((2 * D_CODES + 1) * 2); + this.bl_tree = new Uint16Array((2 * BL_CODES + 1) * 2); + zero(this.dyn_ltree); + zero(this.dyn_dtree); + zero(this.bl_tree); + + this.l_desc = null; /* desc. for literal tree */ + this.d_desc = null; /* desc. for distance tree */ + this.bl_desc = null; /* desc. for bit length tree */ + + //ush bl_count[MAX_BITS+1]; + this.bl_count = new Uint16Array(MAX_BITS + 1); + /* number of codes at each bit length for an optimal tree */ + + //int heap[2*L_CODES+1]; /* heap used to build the Huffman trees */ + this.heap = new Uint16Array( + 2 * L_CODES + 1, + ); /* heap used to build the Huffman trees */ + zero(this.heap); + + this.heap_len = 0; /* number of elements in the heap */ + this.heap_max = 0; /* element of largest frequency */ + /* The sons of heap[n] are heap[2*n] and heap[2*n+1]. heap[0] is not used. + * The same heap array is used to build all trees. + */ + + this.depth = new Uint16Array(2 * L_CODES + 1); //uch depth[2*L_CODES+1]; + zero(this.depth); + /* Depth of each subtree used as tie breaker for trees of equal frequency + */ + + this.l_buf = 0; /* buffer index for literals or lengths */ + + this.lit_bufsize = 0; + /* Size of match buffer for literals/lengths. There are 4 reasons for + * limiting lit_bufsize to 64K: + * - frequencies can be kept in 16 bit counters + * - if compression is not successful for the first block, all input + * data is still in the window so we can still emit a stored block even + * when input comes from standard input. (This can also be done for + * all blocks if lit_bufsize is not greater than 32K.) + * - if compression is not successful for a file smaller than 64K, we can + * even emit a stored file instead of a stored block (saving 5 bytes). + * This is applicable only for zip (not gzip or zlib). + * - creating new Huffman trees less frequently may not provide fast + * adaptation to changes in the input data statistics. (Take for + * example a binary file with poorly compressible code followed by + * a highly compressible string table.) Smaller buffer sizes give + * fast adaptation but have of course the overhead of transmitting + * trees more frequently. + * - I can't count above 4 + */ + + this.last_lit = 0; /* running index in l_buf */ + + this.d_buf = 0; + /* Buffer index for distances. To simplify the code, d_buf and l_buf have + * the same number of elements. To use different lengths, an extra flag + * array would be necessary. + */ + + this.opt_len = 0; /* bit length of current block with optimal trees */ + this.static_len = 0; /* bit length of current block with static trees */ + this.matches = 0; /* number of string matches in current block */ + this.insert = 0; /* bytes at end of window left to insert */ + + this.bi_buf = 0; + /* Output buffer. bits are inserted starting at the bottom (least + * significant bits). + */ + this.bi_valid = 0; + /* Number of valid bits in bi_buf. All bits above the last valid bit + * are always zero. + */ + + // Used for window memory init. We safely ignore it for JS. That makes + // sense only for pointers and memory check tools. + //this.high_water = 0; + /* High water mark offset in window for initialized bytes -- bytes above + * this are set to zero in order to avoid memory check warnings when + * longest match routines access bytes past the input. This is then + * updated to the new high water mark. + */ +} + +const deflateResetKeep = (strm) => { + if (!strm || !strm.state) { + return err(strm, Z_STREAM_ERROR$2); + } + + strm.total_in = strm.total_out = 0; + strm.data_type = Z_UNKNOWN; + + const s = strm.state; + s.pending = 0; + s.pending_out = 0; + + if (s.wrap < 0) { + s.wrap = -s.wrap; + /* was made negative by deflate(..., Z_FINISH); */ + } + s.status = s.wrap ? INIT_STATE : BUSY_STATE; + strm.adler = (s.wrap === 2) + ? 0 // crc32(0, Z_NULL, 0) + : 1; // adler32(0, Z_NULL, 0) + s.last_flush = Z_NO_FLUSH$2; + _tr_init(s); + return Z_OK$3; +}; + +const deflateReset = (strm) => { + const ret = deflateResetKeep(strm); + if (ret === Z_OK$3) { + lm_init(strm.state); + } + return ret; +}; + +const deflateSetHeader = (strm, head) => { + if (!strm || !strm.state) return Z_STREAM_ERROR$2; + if (strm.state.wrap !== 2) return Z_STREAM_ERROR$2; + strm.state.gzhead = head; + return Z_OK$3; +}; + +const deflateInit2 = (strm, level, method, windowBits, memLevel, strategy) => { + if (!strm) { // === Z_NULL + return Z_STREAM_ERROR$2; + } + let wrap = 1; + + if (level === Z_DEFAULT_COMPRESSION$1) { + level = 6; + } + + if (windowBits < 0) { + /* suppress zlib wrapper */ + wrap = 0; + windowBits = -windowBits; + } else if (windowBits > 15) { + wrap = 2; /* write gzip wrapper instead */ + windowBits -= 16; + } + + if ( + memLevel < 1 || memLevel > MAX_MEM_LEVEL || method !== Z_DEFLATED$2 || + windowBits < 8 || windowBits > 15 || level < 0 || level > 9 || + strategy < 0 || strategy > Z_FIXED + ) { + return err(strm, Z_STREAM_ERROR$2); + } + + if (windowBits === 8) { + windowBits = 9; + } + /* until 256-byte window bug fixed */ + + const s = new DeflateState(); + + strm.state = s; + s.strm = strm; + + s.wrap = wrap; + s.gzhead = null; + s.w_bits = windowBits; + s.w_size = 1 << s.w_bits; + s.w_mask = s.w_size - 1; + + s.hash_bits = memLevel + 7; + s.hash_size = 1 << s.hash_bits; + s.hash_mask = s.hash_size - 1; + s.hash_shift = ~~((s.hash_bits + MIN_MATCH - 1) / MIN_MATCH); + + s.window = new Uint8Array(s.w_size * 2); + s.head = new Uint16Array(s.hash_size); + s.prev = new Uint16Array(s.w_size); + + // Don't need mem init magic for JS. + //s.high_water = 0; /* nothing written to s->window yet */ + + s.lit_bufsize = 1 << (memLevel + 6); /* 16K elements by default */ + + s.pending_buf_size = s.lit_bufsize * 4; + + //overlay = (ushf *) ZALLOC(strm, s->lit_bufsize, sizeof(ush)+2); + //s->pending_buf = (uchf *) overlay; + s.pending_buf = new Uint8Array(s.pending_buf_size); + + // It is offset from `s.pending_buf` (size is `s.lit_bufsize * 2`) + //s->d_buf = overlay + s->lit_bufsize/sizeof(ush); + s.d_buf = 1 * s.lit_bufsize; + + //s->l_buf = s->pending_buf + (1+sizeof(ush))*s->lit_bufsize; + s.l_buf = (1 + 2) * s.lit_bufsize; + + s.level = level; + s.strategy = strategy; + s.method = method; + + return deflateReset(strm); +}; + +const deflateInit = (strm, level) => { + return deflateInit2( + strm, + level, + Z_DEFLATED$2, + MAX_WBITS$1, + DEF_MEM_LEVEL, + Z_DEFAULT_STRATEGY$1, + ); +}; + +const deflate$2 = (strm, flush) => { + let beg, val; // for gzip header write only + + if ( + !strm || !strm.state || + flush > Z_BLOCK$1 || flush < 0 + ) { + return strm ? err(strm, Z_STREAM_ERROR$2) : Z_STREAM_ERROR$2; + } + + const s = strm.state; + + if ( + !strm.output || + (!strm.input && strm.avail_in !== 0) || + (s.status === FINISH_STATE && flush !== Z_FINISH$3) + ) { + return err(strm, (strm.avail_out === 0) ? Z_BUF_ERROR$1 : Z_STREAM_ERROR$2); + } + + s.strm = strm; /* just in case */ + const old_flush = s.last_flush; + s.last_flush = flush; + + /* Write the header */ + if (s.status === INIT_STATE) { + if (s.wrap === 2) { // GZIP header + strm.adler = 0; //crc32(0L, Z_NULL, 0); + put_byte(s, 31); + put_byte(s, 139); + put_byte(s, 8); + if (!s.gzhead) { // s->gzhead == Z_NULL + put_byte(s, 0); + put_byte(s, 0); + put_byte(s, 0); + put_byte(s, 0); + put_byte(s, 0); + put_byte( + s, + s.level === 9 + ? 2 + : (s.strategy >= Z_HUFFMAN_ONLY || s.level < 2 ? 4 : 0), + ); + put_byte(s, OS_CODE); + s.status = BUSY_STATE; + } else { + put_byte( + s, + (s.gzhead.text ? 1 : 0) + + (s.gzhead.hcrc ? 2 : 0) + + (!s.gzhead.extra ? 0 : 4) + + (!s.gzhead.name ? 0 : 8) + + (!s.gzhead.comment ? 0 : 16), + ); + put_byte(s, s.gzhead.time & 0xff); + put_byte(s, (s.gzhead.time >> 8) & 0xff); + put_byte(s, (s.gzhead.time >> 16) & 0xff); + put_byte(s, (s.gzhead.time >> 24) & 0xff); + put_byte( + s, + s.level === 9 + ? 2 + : (s.strategy >= Z_HUFFMAN_ONLY || s.level < 2 ? 4 : 0), + ); + put_byte(s, s.gzhead.os & 0xff); + if (s.gzhead.extra && s.gzhead.extra.length) { + put_byte(s, s.gzhead.extra.length & 0xff); + put_byte(s, (s.gzhead.extra.length >> 8) & 0xff); + } + if (s.gzhead.hcrc) { + strm.adler = crc32_1(strm.adler, s.pending_buf, s.pending, 0); + } + s.gzindex = 0; + s.status = EXTRA_STATE; + } + } // DEFLATE header + else { + let header = (Z_DEFLATED$2 + ((s.w_bits - 8) << 4)) << 8; + let level_flags = -1; + + if (s.strategy >= Z_HUFFMAN_ONLY || s.level < 2) { + level_flags = 0; + } else if (s.level < 6) { + level_flags = 1; + } else if (s.level === 6) { + level_flags = 2; + } else { + level_flags = 3; + } + header |= level_flags << 6; + if (s.strstart !== 0) header |= PRESET_DICT; + header += 31 - (header % 31); + + s.status = BUSY_STATE; + putShortMSB(s, header); + + /* Save the adler32 of the preset dictionary: */ + if (s.strstart !== 0) { + putShortMSB(s, strm.adler >>> 16); + putShortMSB(s, strm.adler & 0xffff); + } + strm.adler = 1; // adler32(0L, Z_NULL, 0); + } + } + + //#ifdef GZIP + if (s.status === EXTRA_STATE) { + if (s.gzhead.extra /* != Z_NULL*/) { + beg = s.pending; /* start of bytes to update crc */ + + while (s.gzindex < (s.gzhead.extra.length & 0xffff)) { + if (s.pending === s.pending_buf_size) { + if (s.gzhead.hcrc && s.pending > beg) { + strm.adler = crc32_1( + strm.adler, + s.pending_buf, + s.pending - beg, + beg, + ); + } + flush_pending(strm); + beg = s.pending; + if (s.pending === s.pending_buf_size) { + break; + } + } + put_byte(s, s.gzhead.extra[s.gzindex] & 0xff); + s.gzindex++; + } + if (s.gzhead.hcrc && s.pending > beg) { + strm.adler = crc32_1(strm.adler, s.pending_buf, s.pending - beg, beg); + } + if (s.gzindex === s.gzhead.extra.length) { + s.gzindex = 0; + s.status = NAME_STATE; + } + } else { + s.status = NAME_STATE; + } + } + if (s.status === NAME_STATE) { + if (s.gzhead.name /* != Z_NULL*/) { + beg = s.pending; /* start of bytes to update crc */ + //int val; + + do { + if (s.pending === s.pending_buf_size) { + if (s.gzhead.hcrc && s.pending > beg) { + strm.adler = crc32_1( + strm.adler, + s.pending_buf, + s.pending - beg, + beg, + ); + } + flush_pending(strm); + beg = s.pending; + if (s.pending === s.pending_buf_size) { + val = 1; + break; + } + } + // JS specific: little magic to add zero terminator to end of string + if (s.gzindex < s.gzhead.name.length) { + val = s.gzhead.name.charCodeAt(s.gzindex++) & 0xff; + } else { + val = 0; + } + put_byte(s, val); + } while (val !== 0); + + if (s.gzhead.hcrc && s.pending > beg) { + strm.adler = crc32_1(strm.adler, s.pending_buf, s.pending - beg, beg); + } + if (val === 0) { + s.gzindex = 0; + s.status = COMMENT_STATE; + } + } else { + s.status = COMMENT_STATE; + } + } + if (s.status === COMMENT_STATE) { + if (s.gzhead.comment /* != Z_NULL*/) { + beg = s.pending; /* start of bytes to update crc */ + //int val; + + do { + if (s.pending === s.pending_buf_size) { + if (s.gzhead.hcrc && s.pending > beg) { + strm.adler = crc32_1( + strm.adler, + s.pending_buf, + s.pending - beg, + beg, + ); + } + flush_pending(strm); + beg = s.pending; + if (s.pending === s.pending_buf_size) { + val = 1; + break; + } + } + // JS specific: little magic to add zero terminator to end of string + if (s.gzindex < s.gzhead.comment.length) { + val = s.gzhead.comment.charCodeAt(s.gzindex++) & 0xff; + } else { + val = 0; + } + put_byte(s, val); + } while (val !== 0); + + if (s.gzhead.hcrc && s.pending > beg) { + strm.adler = crc32_1(strm.adler, s.pending_buf, s.pending - beg, beg); + } + if (val === 0) { + s.status = HCRC_STATE; + } + } else { + s.status = HCRC_STATE; + } + } + if (s.status === HCRC_STATE) { + if (s.gzhead.hcrc) { + if (s.pending + 2 > s.pending_buf_size) { + flush_pending(strm); + } + if (s.pending + 2 <= s.pending_buf_size) { + put_byte(s, strm.adler & 0xff); + put_byte(s, (strm.adler >> 8) & 0xff); + strm.adler = 0; //crc32(0L, Z_NULL, 0); + s.status = BUSY_STATE; + } + } else { + s.status = BUSY_STATE; + } + } + //#endif + + /* Flush as much pending output as possible */ + if (s.pending !== 0) { + flush_pending(strm); + if (strm.avail_out === 0) { + /* Since avail_out is 0, deflate will be called again with + * more output space, but possibly with both pending and + * avail_in equal to zero. There won't be anything to do, + * but this is not an error situation so make sure we + * return OK instead of BUF_ERROR at next call of deflate: + */ + s.last_flush = -1; + return Z_OK$3; + } + + /* Make sure there is something to do and avoid duplicate consecutive + * flushes. For repeated and useless calls with Z_FINISH, we keep + * returning Z_STREAM_END instead of Z_BUF_ERROR. + */ + } else if ( + strm.avail_in === 0 && rank(flush) <= rank(old_flush) && + flush !== Z_FINISH$3 + ) { + return err(strm, Z_BUF_ERROR$1); + } + + /* User must not provide more input after the first FINISH: */ + if (s.status === FINISH_STATE && strm.avail_in !== 0) { + return err(strm, Z_BUF_ERROR$1); + } + + /* Start a new block or continue the current one. + */ + if ( + strm.avail_in !== 0 || s.lookahead !== 0 || + (flush !== Z_NO_FLUSH$2 && s.status !== FINISH_STATE) + ) { + let bstate = (s.strategy === Z_HUFFMAN_ONLY) + ? deflate_huff(s, flush) + : (s.strategy === Z_RLE + ? deflate_rle(s, flush) + : configuration_table[s.level].func(s, flush)); + + if (bstate === BS_FINISH_STARTED || bstate === BS_FINISH_DONE) { + s.status = FINISH_STATE; + } + if (bstate === BS_NEED_MORE || bstate === BS_FINISH_STARTED) { + if (strm.avail_out === 0) { + s.last_flush = -1; + /* avoid BUF_ERROR next call, see above */ + } + return Z_OK$3; + /* If flush != Z_NO_FLUSH && avail_out == 0, the next call + * of deflate should use the same flush parameter to make sure + * that the flush is complete. So we don't have to output an + * empty block here, this will be done at next call. This also + * ensures that for a very small output buffer, we emit at most + * one empty block. + */ + } + if (bstate === BS_BLOCK_DONE) { + if (flush === Z_PARTIAL_FLUSH) { + _tr_align(s); + } else if (flush !== Z_BLOCK$1) { + /* FULL_FLUSH or SYNC_FLUSH */ + + _tr_stored_block(s, 0, 0, false); + /* For a full flush, this empty block will be recognized + * as a special marker by inflate_sync(). + */ + if (flush === Z_FULL_FLUSH$1) { + /*** CLEAR_HASH(s); ***/ + /* forget history */ + zero(s.head); // Fill with NIL (= 0); + + if (s.lookahead === 0) { + s.strstart = 0; + s.block_start = 0; + s.insert = 0; + } + } + } + flush_pending(strm); + if (strm.avail_out === 0) { + s.last_flush = -1; /* avoid BUF_ERROR at next call, see above */ + return Z_OK$3; + } + } + } + //Assert(strm->avail_out > 0, "bug2"); + //if (strm.avail_out <= 0) { throw new Error("bug2");} + + if (flush !== Z_FINISH$3) return Z_OK$3; + if (s.wrap <= 0) return Z_STREAM_END$3; + + /* Write the trailer */ + if (s.wrap === 2) { + put_byte(s, strm.adler & 0xff); + put_byte(s, (strm.adler >> 8) & 0xff); + put_byte(s, (strm.adler >> 16) & 0xff); + put_byte(s, (strm.adler >> 24) & 0xff); + put_byte(s, strm.total_in & 0xff); + put_byte(s, (strm.total_in >> 8) & 0xff); + put_byte(s, (strm.total_in >> 16) & 0xff); + put_byte(s, (strm.total_in >> 24) & 0xff); + } else { + putShortMSB(s, strm.adler >>> 16); + putShortMSB(s, strm.adler & 0xffff); + } + + flush_pending(strm); + /* If avail_out is zero, the application will call deflate again + * to flush the rest. + */ + if (s.wrap > 0) s.wrap = -s.wrap; + /* write the trailer only once! */ + return s.pending !== 0 ? Z_OK$3 : Z_STREAM_END$3; +}; + +const deflateEnd = (strm) => { + if (!strm /*== Z_NULL*/ || !strm.state /*== Z_NULL*/) { + return Z_STREAM_ERROR$2; + } + + const status = strm.state.status; + if ( + status !== INIT_STATE && + status !== EXTRA_STATE && + status !== NAME_STATE && + status !== COMMENT_STATE && + status !== HCRC_STATE && + status !== BUSY_STATE && + status !== FINISH_STATE + ) { + return err(strm, Z_STREAM_ERROR$2); + } + + strm.state = null; + + return status === BUSY_STATE ? err(strm, Z_DATA_ERROR$2) : Z_OK$3; +}; + +/* ========================================================================= + * Initializes the compression dictionary from the given byte + * sequence without producing any compressed output. + */ +const deflateSetDictionary = (strm, dictionary) => { + let dictLength = dictionary.length; + + if (!strm /*== Z_NULL*/ || !strm.state /*== Z_NULL*/) { + return Z_STREAM_ERROR$2; + } + + const s = strm.state; + const wrap = s.wrap; + + if (wrap === 2 || (wrap === 1 && s.status !== INIT_STATE) || s.lookahead) { + return Z_STREAM_ERROR$2; + } + + /* when using zlib wrappers, compute Adler-32 for provided dictionary */ + if (wrap === 1) { + /* adler32(strm->adler, dictionary, dictLength); */ + strm.adler = adler32_1(strm.adler, dictionary, dictLength, 0); + } + + s.wrap = 0; /* avoid computing Adler-32 in read_buf */ + + /* if dictionary would fill window, just replace the history */ + if (dictLength >= s.w_size) { + if (wrap === 0) { + /* already empty otherwise */ + /*** CLEAR_HASH(s); ***/ + zero(s.head); // Fill with NIL (= 0); + s.strstart = 0; + s.block_start = 0; + s.insert = 0; + } + /* use the tail */ + // dictionary = dictionary.slice(dictLength - s.w_size); + let tmpDict = new Uint8Array(s.w_size); + tmpDict.set(dictionary.subarray(dictLength - s.w_size, dictLength), 0); + dictionary = tmpDict; + dictLength = s.w_size; + } + /* insert dictionary into window and hash */ + const avail = strm.avail_in; + const next = strm.next_in; + const input = strm.input; + strm.avail_in = dictLength; + strm.next_in = 0; + strm.input = dictionary; + fill_window(s); + while (s.lookahead >= MIN_MATCH) { + let str = s.strstart; + let n = s.lookahead - (MIN_MATCH - 1); + do { + /* UPDATE_HASH(s, s->ins_h, s->window[str + MIN_MATCH-1]); */ + s.ins_h = HASH(s, s.ins_h, s.window[str + MIN_MATCH - 1]); + + s.prev[str & s.w_mask] = s.head[s.ins_h]; + + s.head[s.ins_h] = str; + str++; + } while (--n); + s.strstart = str; + s.lookahead = MIN_MATCH - 1; + fill_window(s); + } + s.strstart += s.lookahead; + s.block_start = s.strstart; + s.insert = s.lookahead; + s.lookahead = 0; + s.match_length = s.prev_length = MIN_MATCH - 1; + s.match_available = 0; + strm.next_in = next; + strm.input = input; + strm.avail_in = avail; + s.wrap = wrap; + return Z_OK$3; +}; + +var deflateInit_1 = deflateInit; +var deflateInit2_1 = deflateInit2; +var deflateReset_1 = deflateReset; +var deflateResetKeep_1 = deflateResetKeep; +var deflateSetHeader_1 = deflateSetHeader; +var deflate_2$1 = deflate$2; +var deflateEnd_1 = deflateEnd; +var deflateSetDictionary_1 = deflateSetDictionary; +var deflateInfo = "pako deflate (from Nodeca project)"; + +/* Not implemented +module.exports.deflateBound = deflateBound; +module.exports.deflateCopy = deflateCopy; +module.exports.deflateParams = deflateParams; +module.exports.deflatePending = deflatePending; +module.exports.deflatePrime = deflatePrime; +module.exports.deflateTune = deflateTune; +*/ + +var deflate_1$2 = { + deflateInit: deflateInit_1, + deflateInit2: deflateInit2_1, + deflateReset: deflateReset_1, + deflateResetKeep: deflateResetKeep_1, + deflateSetHeader: deflateSetHeader_1, + deflate: deflate_2$1, + deflateEnd: deflateEnd_1, + deflateSetDictionary: deflateSetDictionary_1, + deflateInfo: deflateInfo, +}; + +const _has = (obj, key) => { + return Object.prototype.hasOwnProperty.call(obj, key); +}; + +var assign = function (obj /*from1, from2, from3, ...*/) { + const sources = Array.prototype.slice.call(arguments, 1); + while (sources.length) { + const source = sources.shift(); + if (!source) continue; + + if (typeof source !== "object") { + throw new TypeError(source + "must be non-object"); + } + + for (const p in source) { + if (_has(source, p)) { + obj[p] = source[p]; + } + } + } + + return obj; +}; + +// Join array of chunks to single array. +var flattenChunks = (chunks) => { + // calculate data length + let len = 0; + + for (let i = 0, l = chunks.length; i < l; i++) { + len += chunks[i].length; + } + + // join chunks + const result = new Uint8Array(len); + + for (let i = 0, pos = 0, l = chunks.length; i < l; i++) { + let chunk = chunks[i]; + result.set(chunk, pos); + pos += chunk.length; + } + + return result; +}; + +var common = { + assign: assign, + flattenChunks: flattenChunks, +}; + +// String encode/decode helpers + +// Quick check if we can use fast array to bin string conversion +// +// - apply(Array) can fail on Android 2.2 +// - apply(Uint8Array) can fail on iOS 5.1 Safari +// +let STR_APPLY_UIA_OK = true; + +try { + String.fromCharCode.apply(null, new Uint8Array(1)); +} catch (__) { + STR_APPLY_UIA_OK = false; +} + +// Table with utf8 lengths (calculated by first byte of sequence) +// Note, that 5 & 6-byte values and some 4-byte values can not be represented in JS, +// because max possible codepoint is 0x10ffff +const _utf8len = new Uint8Array(256); +for (let q = 0; q < 256; q++) { + _utf8len[q] = q >= 252 + ? 6 + : q >= 248 + ? 5 + : q >= 240 + ? 4 + : q >= 224 + ? 3 + : q >= 192 + ? 2 + : 1; +} +_utf8len[254] = _utf8len[254] = 1; // Invalid sequence start + +// convert string to array (typed, when possible) +var string2buf = (str) => { + if (typeof TextEncoder === "function" && TextEncoder.prototype.encode) { + return new TextEncoder().encode(str); + } + + let buf, c, c2, m_pos, i, str_len = str.length, buf_len = 0; + + // count binary size + for (m_pos = 0; m_pos < str_len; m_pos++) { + c = str.charCodeAt(m_pos); + if ((c & 0xfc00) === 0xd800 && (m_pos + 1 < str_len)) { + c2 = str.charCodeAt(m_pos + 1); + if ((c2 & 0xfc00) === 0xdc00) { + c = 0x10000 + ((c - 0xd800) << 10) + (c2 - 0xdc00); + m_pos++; + } + } + buf_len += c < 0x80 ? 1 : c < 0x800 ? 2 : c < 0x10000 ? 3 : 4; + } + + // allocate buffer + buf = new Uint8Array(buf_len); + + // convert + for (i = 0, m_pos = 0; i < buf_len; m_pos++) { + c = str.charCodeAt(m_pos); + if ((c & 0xfc00) === 0xd800 && (m_pos + 1 < str_len)) { + c2 = str.charCodeAt(m_pos + 1); + if ((c2 & 0xfc00) === 0xdc00) { + c = 0x10000 + ((c - 0xd800) << 10) + (c2 - 0xdc00); + m_pos++; + } + } + if (c < 0x80) { + /* one byte */ + buf[i++] = c; + } else if (c < 0x800) { + /* two bytes */ + buf[i++] = 0xC0 | (c >>> 6); + buf[i++] = 0x80 | (c & 0x3f); + } else if (c < 0x10000) { + /* three bytes */ + buf[i++] = 0xE0 | (c >>> 12); + buf[i++] = 0x80 | (c >>> 6 & 0x3f); + buf[i++] = 0x80 | (c & 0x3f); + } else { + /* four bytes */ + buf[i++] = 0xf0 | (c >>> 18); + buf[i++] = 0x80 | (c >>> 12 & 0x3f); + buf[i++] = 0x80 | (c >>> 6 & 0x3f); + buf[i++] = 0x80 | (c & 0x3f); + } + } + + return buf; +}; + +// Helper +const buf2binstring = (buf, len) => { + // On Chrome, the arguments in a function call that are allowed is `65534`. + // If the length of the buffer is smaller than that, we can use this optimization, + // otherwise we will take a slower path. + if (len < 65534) { + if (buf.subarray && STR_APPLY_UIA_OK) { + return String.fromCharCode.apply( + null, + buf.length === len ? buf : buf.subarray(0, len), + ); + } + } + + let result = ""; + for (let i = 0; i < len; i++) { + result += String.fromCharCode(buf[i]); + } + return result; +}; + +// convert array to string +var buf2string = (buf, max) => { + const len = max || buf.length; + + if (typeof TextDecoder === "function" && TextDecoder.prototype.decode) { + return new TextDecoder().decode(buf.subarray(0, max)); + } + + let i, out; + + // Reserve max possible length (2 words per char) + // NB: by unknown reasons, Array is significantly faster for + // String.fromCharCode.apply than Uint16Array. + const utf16buf = new Array(len * 2); + + for (out = 0, i = 0; i < len;) { + let c = buf[i++]; + // quick process ascii + if (c < 0x80) { + utf16buf[out++] = c; + continue; + } + + let c_len = _utf8len[c]; + // skip 5 & 6 byte codes + if (c_len > 4) { + utf16buf[out++] = 0xfffd; + i += c_len - 1; + continue; + } + + // apply mask on first byte + c &= c_len === 2 ? 0x1f : c_len === 3 ? 0x0f : 0x07; + // join the rest + while (c_len > 1 && i < len) { + c = (c << 6) | (buf[i++] & 0x3f); + c_len--; + } + + // terminated by end of string? + if (c_len > 1) { + utf16buf[out++] = 0xfffd; + continue; + } + + if (c < 0x10000) { + utf16buf[out++] = c; + } else { + c -= 0x10000; + utf16buf[out++] = 0xd800 | ((c >> 10) & 0x3ff); + utf16buf[out++] = 0xdc00 | (c & 0x3ff); + } + } + + return buf2binstring(utf16buf, out); +}; + +// Calculate max possible position in utf8 buffer, +// that will not break sequence. If that's not possible +// - (very small limits) return max size as is. +// +// buf[] - utf8 bytes array +// max - length limit (mandatory); +var utf8border = (buf, max) => { + max = max || buf.length; + if (max > buf.length) max = buf.length; + + // go back from last position, until start of sequence found + let pos = max - 1; + while (pos >= 0 && (buf[pos] & 0xC0) === 0x80) pos--; + + // Very small and broken sequence, + // return max, because we should return something anyway. + if (pos < 0) return max; + + // If we came to start of buffer - that means buffer is too small, + // return max too. + if (pos === 0) return max; + + return (pos + _utf8len[buf[pos]] > max) ? pos : max; +}; + +var strings = { + string2buf: string2buf, + buf2string: buf2string, + utf8border: utf8border, +}; + +// (C) 1995-2013 Jean-loup Gailly and Mark Adler +// (C) 2014-2017 Vitaly Puzrin and Andrey Tupitsin +// +// This software is provided 'as-is', without any express or implied +// warranty. In no event will the authors be held liable for any damages +// arising from the use of this software. +// +// Permission is granted to anyone to use this software for any purpose, +// including commercial applications, and to alter it and redistribute it +// freely, subject to the following restrictions: +// +// 1. The origin of this software must not be misrepresented; you must not +// claim that you wrote the original software. If you use this software +// in a product, an acknowledgment in the product documentation would be +// appreciated but is not required. +// 2. Altered source versions must be plainly marked as such, and must not be +// misrepresented as being the original software. +// 3. This notice may not be removed or altered from any source distribution. + +function ZStream() { + /* next input byte */ + this.input = null; // JS specific, because we have no pointers + this.next_in = 0; + /* number of bytes available at input */ + this.avail_in = 0; + /* total number of input bytes read so far */ + this.total_in = 0; + /* next output byte should be put there */ + this.output = null; // JS specific, because we have no pointers + this.next_out = 0; + /* remaining free space at output */ + this.avail_out = 0; + /* total number of bytes output so far */ + this.total_out = 0; + /* last error message, NULL if no error */ + this.msg = "" /*Z_NULL*/; + /* not visible by applications */ + this.state = null; + /* best guess about the data type: binary or text */ + this.data_type = 2 /*Z_UNKNOWN*/; + /* adler32 value of the uncompressed data */ + this.adler = 0; +} + +var zstream = ZStream; + +const toString$1 = Object.prototype.toString; + +/* Public constants ==========================================================*/ +/* ===========================================================================*/ + +const { + Z_NO_FLUSH: Z_NO_FLUSH$1, + Z_SYNC_FLUSH, + Z_FULL_FLUSH, + Z_FINISH: Z_FINISH$2, + Z_OK: Z_OK$2, + Z_STREAM_END: Z_STREAM_END$2, + Z_DEFAULT_COMPRESSION, + Z_DEFAULT_STRATEGY, + Z_DEFLATED: Z_DEFLATED$1, +} = constants$2; + +/* ===========================================================================*/ + +/** + * class Deflate + * + * Generic JS-style wrapper for zlib calls. If you don't need + * streaming behaviour - use more simple functions: [[deflate]], + * [[deflateRaw]] and [[gzip]]. + */ + +/* internal + * Deflate.chunks -> Array + * + * Chunks of output data, if [[Deflate#onData]] not overridden. + **/ + +/** + * Deflate.result -> Uint8Array + * + * Compressed result, generated by default [[Deflate#onData]] + * and [[Deflate#onEnd]] handlers. Filled after you push last chunk + * (call [[Deflate#push]] with `Z_FINISH` / `true` param). + */ + +/** + * Deflate.err -> Number + * + * Error code after deflate finished. 0 (Z_OK) on success. + * You will not need it in real life, because deflate errors + * are possible only on wrong options or bad `onData` / `onEnd` + * custom handlers. + */ + +/** + * Deflate.msg -> String + * + * Error message, if [[Deflate.err]] != 0 + */ + +/** + * new Deflate(options) + * - options (Object): zlib deflate options. + * + * Creates new deflator instance with specified params. Throws exception + * on bad params. Supported options: + * + * - `level` + * - `windowBits` + * - `memLevel` + * - `strategy` + * - `dictionary` + * + * [http://zlib.net/manual.html#Advanced](http://zlib.net/manual.html#Advanced) + * for more information on these. + * + * Additional options, for internal needs: + * + * - `chunkSize` - size of generated data chunks (16K by default) + * - `raw` (Boolean) - do raw deflate + * - `gzip` (Boolean) - create gzip wrapper + * - `header` (Object) - custom header for gzip + * - `text` (Boolean) - true if compressed data believed to be text + * - `time` (Number) - modification time, unix timestamp + * - `os` (Number) - operation system code + * - `extra` (Array) - array of bytes with extra data (max 65536) + * - `name` (String) - file name (binary string) + * - `comment` (String) - comment (binary string) + * - `hcrc` (Boolean) - true if header crc should be added + * + * ##### Example: + * + * ```javascript + * const pako = require('pako') + * , chunk1 = new Uint8Array([1,2,3,4,5,6,7,8,9]) + * , chunk2 = new Uint8Array([10,11,12,13,14,15,16,17,18,19]); + * + * const deflate = new pako.Deflate({ level: 3}); + * + * deflate.push(chunk1, false); + * deflate.push(chunk2, true); // true -> last chunk + * + * if (deflate.err) { throw new Error(deflate.err); } + * + * console.log(deflate.result); + * ``` + */ +function Deflate$1(options) { + this.options = common.assign({ + level: Z_DEFAULT_COMPRESSION, + method: Z_DEFLATED$1, + chunkSize: 16384, + windowBits: 15, + memLevel: 8, + strategy: Z_DEFAULT_STRATEGY, + }, options || {}); + + let opt = this.options; + + if (opt.raw && (opt.windowBits > 0)) { + opt.windowBits = -opt.windowBits; + } else if (opt.gzip && (opt.windowBits > 0) && (opt.windowBits < 16)) { + opt.windowBits += 16; + } + + this.err = 0; // error code, if happens (0 = Z_OK) + this.msg = ""; // error message + this.ended = false; // used to avoid multiple onEnd() calls + this.chunks = []; // chunks of compressed data + + this.strm = new zstream(); + this.strm.avail_out = 0; + + let status = deflate_1$2.deflateInit2( + this.strm, + opt.level, + opt.method, + opt.windowBits, + opt.memLevel, + opt.strategy, + ); + + if (status !== Z_OK$2) { + throw new Error(messages[status]); + } + + if (opt.header) { + deflate_1$2.deflateSetHeader(this.strm, opt.header); + } + + if (opt.dictionary) { + let dict; + // Convert data if needed + if (typeof opt.dictionary === "string") { + // If we need to compress text, change encoding to utf8. + dict = strings.string2buf(opt.dictionary); + } else if (toString$1.call(opt.dictionary) === "[object ArrayBuffer]") { + dict = new Uint8Array(opt.dictionary); + } else { + dict = opt.dictionary; + } + + status = deflate_1$2.deflateSetDictionary(this.strm, dict); + + if (status !== Z_OK$2) { + throw new Error(messages[status]); + } + + this._dict_set = true; + } +} + +/** + * Deflate#push(data[, flush_mode]) -> Boolean + * - data (Uint8Array|ArrayBuffer|String): input data. Strings will be + * converted to utf8 byte sequence. + * - flush_mode (Number|Boolean): 0..6 for corresponding Z_NO_FLUSH..Z_TREE modes. + * See constants. Skipped or `false` means Z_NO_FLUSH, `true` means Z_FINISH. + * + * Sends input data to deflate pipe, generating [[Deflate#onData]] calls with + * new compressed chunks. Returns `true` on success. The last data block must + * have `flush_mode` Z_FINISH (or `true`). That will flush internal pending + * buffers and call [[Deflate#onEnd]]. + * + * On fail call [[Deflate#onEnd]] with error code and return false. + * + * ##### Example + * + * ```javascript + * push(chunk, false); // push one of data chunks + * ... + * push(chunk, true); // push last chunk + * ``` + */ +Deflate$1.prototype.push = function (data, flush_mode) { + const strm = this.strm; + const chunkSize = this.options.chunkSize; + let status, _flush_mode; + + if (this.ended) return false; + + if (flush_mode === ~~flush_mode) _flush_mode = flush_mode; + else _flush_mode = flush_mode === true ? Z_FINISH$2 : Z_NO_FLUSH$1; + + // Convert data if needed + if (typeof data === "string") { + // If we need to compress text, change encoding to utf8. + strm.input = strings.string2buf(data); + } else if (toString$1.call(data) === "[object ArrayBuffer]") { + strm.input = new Uint8Array(data); + } else { + strm.input = data; + } + + strm.next_in = 0; + strm.avail_in = strm.input.length; + + for (;;) { + if (strm.avail_out === 0) { + strm.output = new Uint8Array(chunkSize); + strm.next_out = 0; + strm.avail_out = chunkSize; + } + + // Make sure avail_out > 6 to avoid repeating markers + if ( + (_flush_mode === Z_SYNC_FLUSH || _flush_mode === Z_FULL_FLUSH) && + strm.avail_out <= 6 + ) { + this.onData(strm.output.subarray(0, strm.next_out)); + strm.avail_out = 0; + continue; + } + + status = deflate_1$2.deflate(strm, _flush_mode); + + // Ended => flush and finish + if (status === Z_STREAM_END$2) { + if (strm.next_out > 0) { + this.onData(strm.output.subarray(0, strm.next_out)); + } + status = deflate_1$2.deflateEnd(this.strm); + this.onEnd(status); + this.ended = true; + return status === Z_OK$2; + } + + // Flush if out buffer full + if (strm.avail_out === 0) { + this.onData(strm.output); + continue; + } + + // Flush if requested and has data + if (_flush_mode > 0 && strm.next_out > 0) { + this.onData(strm.output.subarray(0, strm.next_out)); + strm.avail_out = 0; + continue; + } + + if (strm.avail_in === 0) break; + } + + return true; +}; + +/** + * Deflate#onData(chunk) -> Void + * - chunk (Uint8Array): output data. + * + * By default, stores data blocks in `chunks[]` property and glue + * those in `onEnd`. Override this handler, if you need another behaviour. + */ +Deflate$1.prototype.onData = function (chunk) { + this.chunks.push(chunk); +}; + +/** + * Deflate#onEnd(status) -> Void + * - status (Number): deflate status. 0 (Z_OK) on success, + * other if not. + * + * Called once after you tell deflate that the input stream is + * complete (Z_FINISH). By default - join collected chunks, + * free memory and fill `results` / `err` properties. + */ +Deflate$1.prototype.onEnd = function (status) { + // On success - join + if (status === Z_OK$2) { + this.result = common.flattenChunks(this.chunks); + } + this.chunks = []; + this.err = status; + this.msg = this.strm.msg; +}; + +/** + * deflate(data[, options]) -> Uint8Array + * - data (Uint8Array|String): input data to compress. + * - options (Object): zlib deflate options. + * + * Compress `data` with deflate algorithm and `options`. + * + * Supported options are: + * + * - level + * - windowBits + * - memLevel + * - strategy + * - dictionary + * + * [http://zlib.net/manual.html#Advanced](http://zlib.net/manual.html#Advanced) + * for more information on these. + * + * Sugar (options): + * + * - `raw` (Boolean) - say that we work with raw stream, if you don't wish to specify + * negative windowBits implicitly. + * + * ##### Example: + * + * ```javascript + * const pako = require('pako') + * const data = new Uint8Array([1,2,3,4,5,6,7,8,9]); + * + * console.log(pako.deflate(data)); + * ``` + */ +function deflate$1(input, options) { + const deflator = new Deflate$1(options); + + deflator.push(input, true); + + // That will never happens, if you don't cheat with options :) + if (deflator.err) throw deflator.msg || messages[deflator.err]; + + return deflator.result; +} + +/** + * deflateRaw(data[, options]) -> Uint8Array + * - data (Uint8Array|String): input data to compress. + * - options (Object): zlib deflate options. + * + * The same as [[deflate]], but creates raw data, without wrapper + * (header and adler32 crc). + */ +function deflateRaw$1(input, options) { + options = options || {}; + options.raw = true; + return deflate$1(input, options); +} + +/** + * gzip(data[, options]) -> Uint8Array + * - data (Uint8Array|String): input data to compress. + * - options (Object): zlib deflate options. + * + * The same as [[deflate]], but create gzip wrapper instead of + * deflate one. + */ +function gzip$1(input, options) { + options = options || {}; + options.gzip = true; + return deflate$1(input, options); +} + +var Deflate_1$1 = Deflate$1; +var deflate_2 = deflate$1; +var deflateRaw_1$1 = deflateRaw$1; +var gzip_1$1 = gzip$1; +var constants$1 = constants$2; + +var deflate_1$1 = { + Deflate: Deflate_1$1, + deflate: deflate_2, + deflateRaw: deflateRaw_1$1, + gzip: gzip_1$1, + constants: constants$1, +}; + +// (C) 1995-2013 Jean-loup Gailly and Mark Adler +// (C) 2014-2017 Vitaly Puzrin and Andrey Tupitsin +// +// This software is provided 'as-is', without any express or implied +// warranty. In no event will the authors be held liable for any damages +// arising from the use of this software. +// +// Permission is granted to anyone to use this software for any purpose, +// including commercial applications, and to alter it and redistribute it +// freely, subject to the following restrictions: +// +// 1. The origin of this software must not be misrepresented; you must not +// claim that you wrote the original software. If you use this software +// in a product, an acknowledgment in the product documentation would be +// appreciated but is not required. +// 2. Altered source versions must be plainly marked as such, and must not be +// misrepresented as being the original software. +// 3. This notice may not be removed or altered from any source distribution. + +// See state defs from inflate.js +const BAD$1 = 30; /* got a data error -- remain here until reset */ +const TYPE$1 = 12; /* i: waiting for type bits, including last-flag bit */ + +/* + Decode literal, length, and distance codes and write out the resulting + literal and match bytes until either not enough input or output is + available, an end-of-block is encountered, or a data error is encountered. + When large enough input and output buffers are supplied to inflate(), for + example, a 16K input buffer and a 64K output buffer, more than 95% of the + inflate execution time is spent in this routine. + + Entry assumptions: + + state.mode === LEN + strm.avail_in >= 6 + strm.avail_out >= 258 + start >= strm.avail_out + state.bits < 8 + + On return, state.mode is one of: + + LEN -- ran out of enough output space or enough available input + TYPE -- reached end of block code, inflate() to interpret next block + BAD -- error in block data + + Notes: + + - The maximum input bits used by a length/distance pair is 15 bits for the + length code, 5 bits for the length extra, 15 bits for the distance code, + and 13 bits for the distance extra. This totals 48 bits, or six bytes. + Therefore if strm.avail_in >= 6, then there is enough input to avoid + checking for available input while decoding. + + - The maximum bytes that a single length/distance pair can output is 258 + bytes, which is the maximum length that can be coded. inflate_fast() + requires strm.avail_out >= 258 for each loop to avoid checking for + output space. + */ +var inffast = function inflate_fast(strm, start) { + let _in; /* local strm.input */ + let last; /* have enough input while in < last */ + let _out; /* local strm.output */ + let beg; /* inflate()'s initial strm.output */ + let end; /* while out < end, enough space available */ + //#ifdef INFLATE_STRICT + let dmax; /* maximum distance from zlib header */ + //#endif + let wsize; /* window size or zero if not using window */ + let whave; /* valid bytes in the window */ + let wnext; /* window write index */ + // Use `s_window` instead `window`, avoid conflict with instrumentation tools + let s_window; /* allocated sliding window, if wsize != 0 */ + let hold; /* local strm.hold */ + let bits; /* local strm.bits */ + let lcode; /* local strm.lencode */ + let dcode; /* local strm.distcode */ + let lmask; /* mask for first level of length codes */ + let dmask; /* mask for first level of distance codes */ + let here; /* retrieved table entry */ + let op; /* code bits, operation, extra bits, or */ + /* window position, window bytes to copy */ + let len; /* match length, unused bytes */ + let dist; /* match distance */ + let from; /* where to copy match from */ + let from_source; + + let input, output; // JS specific, because we have no pointers + + /* copy state to local variables */ + const state = strm.state; + //here = state.here; + _in = strm.next_in; + input = strm.input; + last = _in + (strm.avail_in - 5); + _out = strm.next_out; + output = strm.output; + beg = _out - (start - strm.avail_out); + end = _out + (strm.avail_out - 257); + //#ifdef INFLATE_STRICT + dmax = state.dmax; + //#endif + wsize = state.wsize; + whave = state.whave; + wnext = state.wnext; + s_window = state.window; + hold = state.hold; + bits = state.bits; + lcode = state.lencode; + dcode = state.distcode; + lmask = (1 << state.lenbits) - 1; + dmask = (1 << state.distbits) - 1; + + /* decode literals and length/distances until end-of-block or not enough + input data or output space */ + + top: + do { + if (bits < 15) { + hold += input[_in++] << bits; + bits += 8; + hold += input[_in++] << bits; + bits += 8; + } + + here = lcode[hold & lmask]; + + dolen: + for (;;) { // Goto emulation + op = here >>> 24 /*here.bits*/; + hold >>>= op; + bits -= op; + op = (here >>> 16) & 0xff /*here.op*/; + if (op === 0) { + /* literal */ + //Tracevv((stderr, here.val >= 0x20 && here.val < 0x7f ? + // "inflate: literal '%c'\n" : + // "inflate: literal 0x%02x\n", here.val)); + output[_out++] = here & 0xffff /*here.val*/; + } else if (op & 16) { + /* length base */ + len = here & 0xffff /*here.val*/; + op &= 15; /* number of extra bits */ + if (op) { + if (bits < op) { + hold += input[_in++] << bits; + bits += 8; + } + len += hold & ((1 << op) - 1); + hold >>>= op; + bits -= op; + } + //Tracevv((stderr, "inflate: length %u\n", len)); + if (bits < 15) { + hold += input[_in++] << bits; + bits += 8; + hold += input[_in++] << bits; + bits += 8; + } + here = dcode[hold & dmask]; + + dodist: + for (;;) { // goto emulation + op = here >>> 24 /*here.bits*/; + hold >>>= op; + bits -= op; + op = (here >>> 16) & 0xff /*here.op*/; + + if (op & 16) { + /* distance base */ + dist = here & 0xffff /*here.val*/; + op &= 15; /* number of extra bits */ + if (bits < op) { + hold += input[_in++] << bits; + bits += 8; + if (bits < op) { + hold += input[_in++] << bits; + bits += 8; + } + } + dist += hold & ((1 << op) - 1); + //#ifdef INFLATE_STRICT + if (dist > dmax) { + strm.msg = "invalid distance too far back"; + state.mode = BAD$1; + break top; + } + //#endif + hold >>>= op; + bits -= op; + //Tracevv((stderr, "inflate: distance %u\n", dist)); + op = _out - beg; /* max distance in output */ + if (dist > op) { + /* see if copy from window */ + op = dist - op; /* distance back in window */ + if (op > whave) { + if (state.sane) { + strm.msg = "invalid distance too far back"; + state.mode = BAD$1; + break top; + } + + // (!) This block is disabled in zlib defaults, + // don't enable it for binary compatibility + //#ifdef INFLATE_ALLOW_INVALID_DISTANCE_TOOFAR_ARRR + // if (len <= op - whave) { + // do { + // output[_out++] = 0; + // } while (--len); + // continue top; + // } + // len -= op - whave; + // do { + // output[_out++] = 0; + // } while (--op > whave); + // if (op === 0) { + // from = _out - dist; + // do { + // output[_out++] = output[from++]; + // } while (--len); + // continue top; + // } + //#endif + } + from = 0; // window index + from_source = s_window; + if (wnext === 0) { + /* very common case */ + from += wsize - op; + if (op < len) { + /* some from window */ + len -= op; + do { + output[_out++] = s_window[from++]; + } while (--op); + from = _out - dist; /* rest from output */ + from_source = output; + } + } else if (wnext < op) { + /* wrap around window */ + from += wsize + wnext - op; + op -= wnext; + if (op < len) { + /* some from end of window */ + len -= op; + do { + output[_out++] = s_window[from++]; + } while (--op); + from = 0; + if (wnext < len) { + /* some from start of window */ + op = wnext; + len -= op; + do { + output[_out++] = s_window[from++]; + } while (--op); + from = _out - dist; /* rest from output */ + from_source = output; + } + } + } else { + /* contiguous in window */ + from += wnext - op; + if (op < len) { + /* some from window */ + len -= op; + do { + output[_out++] = s_window[from++]; + } while (--op); + from = _out - dist; /* rest from output */ + from_source = output; + } + } + while (len > 2) { + output[_out++] = from_source[from++]; + output[_out++] = from_source[from++]; + output[_out++] = from_source[from++]; + len -= 3; + } + if (len) { + output[_out++] = from_source[from++]; + if (len > 1) { + output[_out++] = from_source[from++]; + } + } + } else { + from = _out - dist; /* copy direct from output */ + do { + /* minimum length is three */ + output[_out++] = output[from++]; + output[_out++] = output[from++]; + output[_out++] = output[from++]; + len -= 3; + } while (len > 2); + if (len) { + output[_out++] = output[from++]; + if (len > 1) { + output[_out++] = output[from++]; + } + } + } + } else if ((op & 64) === 0) { + /* 2nd level distance code */ + here = + dcode[(here & 0xffff) /*here.val*/ + (hold & ((1 << op) - 1))]; + continue dodist; + } else { + strm.msg = "invalid distance code"; + state.mode = BAD$1; + break top; + } + + break; // need to emulate goto via "continue" + } + } else if ((op & 64) === 0) { + /* 2nd level length code */ + here = lcode[(here & 0xffff) /*here.val*/ + (hold & ((1 << op) - 1))]; + continue dolen; + } else if (op & 32) { + /* end-of-block */ + //Tracevv((stderr, "inflate: end of block\n")); + state.mode = TYPE$1; + break top; + } else { + strm.msg = "invalid literal/length code"; + state.mode = BAD$1; + break top; + } + + break; // need to emulate goto via "continue" + } + } while (_in < last && _out < end); + + /* return unused bytes (on entry, bits < 8, so in won't go too far back) */ + len = bits >> 3; + _in -= len; + bits -= len << 3; + hold &= (1 << bits) - 1; + + /* update state and return */ + strm.next_in = _in; + strm.next_out = _out; + strm.avail_in = _in < last ? 5 + (last - _in) : 5 - (_in - last); + strm.avail_out = _out < end ? 257 + (end - _out) : 257 - (_out - end); + state.hold = hold; + state.bits = bits; + return; +}; + +// (C) 1995-2013 Jean-loup Gailly and Mark Adler +// (C) 2014-2017 Vitaly Puzrin and Andrey Tupitsin +// +// This software is provided 'as-is', without any express or implied +// warranty. In no event will the authors be held liable for any damages +// arising from the use of this software. +// +// Permission is granted to anyone to use this software for any purpose, +// including commercial applications, and to alter it and redistribute it +// freely, subject to the following restrictions: +// +// 1. The origin of this software must not be misrepresented; you must not +// claim that you wrote the original software. If you use this software +// in a product, an acknowledgment in the product documentation would be +// appreciated but is not required. +// 2. Altered source versions must be plainly marked as such, and must not be +// misrepresented as being the original software. +// 3. This notice may not be removed or altered from any source distribution. + +const MAXBITS = 15; +const ENOUGH_LENS$1 = 852; +const ENOUGH_DISTS$1 = 592; +//const ENOUGH = (ENOUGH_LENS+ENOUGH_DISTS); + +const CODES$1 = 0; +const LENS$1 = 1; +const DISTS$1 = 2; + +const lbase = new Uint16Array([ + /* Length codes 257..285 base */ + 3, + 4, + 5, + 6, + 7, + 8, + 9, + 10, + 11, + 13, + 15, + 17, + 19, + 23, + 27, + 31, + 35, + 43, + 51, + 59, + 67, + 83, + 99, + 115, + 131, + 163, + 195, + 227, + 258, + 0, + 0, +]); + +const lext = new Uint8Array([ + /* Length codes 257..285 extra */ + 16, + 16, + 16, + 16, + 16, + 16, + 16, + 16, + 17, + 17, + 17, + 17, + 18, + 18, + 18, + 18, + 19, + 19, + 19, + 19, + 20, + 20, + 20, + 20, + 21, + 21, + 21, + 21, + 16, + 72, + 78, +]); + +const dbase = new Uint16Array([ + /* Distance codes 0..29 base */ + 1, + 2, + 3, + 4, + 5, + 7, + 9, + 13, + 17, + 25, + 33, + 49, + 65, + 97, + 129, + 193, + 257, + 385, + 513, + 769, + 1025, + 1537, + 2049, + 3073, + 4097, + 6145, + 8193, + 12289, + 16385, + 24577, + 0, + 0, +]); + +const dext = new Uint8Array([ + /* Distance codes 0..29 extra */ + 16, + 16, + 16, + 16, + 17, + 17, + 18, + 18, + 19, + 19, + 20, + 20, + 21, + 21, + 22, + 22, + 23, + 23, + 24, + 24, + 25, + 25, + 26, + 26, + 27, + 27, + 28, + 28, + 29, + 29, + 64, + 64, +]); + +const inflate_table = ( + type, + lens, + lens_index, + codes, + table, + table_index, + work, + opts, +) => { + const bits = opts.bits; + //here = opts.here; /* table entry for duplication */ + + let len = 0; /* a code's length in bits */ + let sym = 0; /* index of code symbols */ + let min = 0, max = 0; /* minimum and maximum code lengths */ + let root = 0; /* number of index bits for root table */ + let curr = 0; /* number of index bits for current table */ + let drop = 0; /* code bits to drop for sub-table */ + let left = 0; /* number of prefix codes available */ + let used = 0; /* code entries in table used */ + let huff = 0; /* Huffman code */ + let incr; /* for incrementing code, index */ + let fill; /* index for replicating entries */ + let low; /* low bits for current root entry */ + let mask; /* mask for low root bits */ + let next; /* next available space in table */ + let base = null; /* base value table to use */ + let base_index = 0; + // let shoextra; /* extra bits table to use */ + let end; /* use base and extra for symbol > end */ + const count = new Uint16Array(MAXBITS + 1); //[MAXBITS+1]; /* number of codes of each length */ + const offs = new Uint16Array(MAXBITS + 1); //[MAXBITS+1]; /* offsets in table for each length */ + let extra = null; + let extra_index = 0; + + let here_bits, here_op, here_val; + + /* + Process a set of code lengths to create a canonical Huffman code. The + code lengths are lens[0..codes-1]. Each length corresponds to the + symbols 0..codes-1. The Huffman code is generated by first sorting the + symbols by length from short to long, and retaining the symbol order + for codes with equal lengths. Then the code starts with all zero bits + for the first code of the shortest length, and the codes are integer + increments for the same length, and zeros are appended as the length + increases. For the deflate format, these bits are stored backwards + from their more natural integer increment ordering, and so when the + decoding tables are built in the large loop below, the integer codes + are incremented backwards. + + This routine assumes, but does not check, that all of the entries in + lens[] are in the range 0..MAXBITS. The caller must assure this. + 1..MAXBITS is interpreted as that code length. zero means that that + symbol does not occur in this code. + + The codes are sorted by computing a count of codes for each length, + creating from that a table of starting indices for each length in the + sorted table, and then entering the symbols in order in the sorted + table. The sorted table is work[], with that space being provided by + the caller. + + The length counts are used for other purposes as well, i.e. finding + the minimum and maximum length codes, determining if there are any + codes at all, checking for a valid set of lengths, and looking ahead + at length counts to determine sub-table sizes when building the + decoding tables. + */ + + /* accumulate lengths for codes (assumes lens[] all in 0..MAXBITS) */ + for (len = 0; len <= MAXBITS; len++) { + count[len] = 0; + } + for (sym = 0; sym < codes; sym++) { + count[lens[lens_index + sym]]++; + } + + /* bound code lengths, force root to be within code lengths */ + root = bits; + for (max = MAXBITS; max >= 1; max--) { + if (count[max] !== 0) break; + } + if (root > max) { + root = max; + } + if (max === 0) { + /* no symbols to code at all */ + //table.op[opts.table_index] = 64; //here.op = (var char)64; /* invalid code marker */ + //table.bits[opts.table_index] = 1; //here.bits = (var char)1; + //table.val[opts.table_index++] = 0; //here.val = (var short)0; + table[table_index++] = (1 << 24) | (64 << 16) | 0; + + //table.op[opts.table_index] = 64; + //table.bits[opts.table_index] = 1; + //table.val[opts.table_index++] = 0; + table[table_index++] = (1 << 24) | (64 << 16) | 0; + + opts.bits = 1; + return 0; /* no symbols, but wait for decoding to report error */ + } + for (min = 1; min < max; min++) { + if (count[min] !== 0) break; + } + if (root < min) { + root = min; + } + + /* check for an over-subscribed or incomplete set of lengths */ + left = 1; + for (len = 1; len <= MAXBITS; len++) { + left <<= 1; + left -= count[len]; + if (left < 0) { + return -1; + } /* over-subscribed */ + } + if (left > 0 && (type === CODES$1 || max !== 1)) { + return -1; /* incomplete set */ + } + + /* generate offsets into symbol table for each length for sorting */ + offs[1] = 0; + for (len = 1; len < MAXBITS; len++) { + offs[len + 1] = offs[len] + count[len]; + } + + /* sort symbols by length, by symbol order within each length */ + for (sym = 0; sym < codes; sym++) { + if (lens[lens_index + sym] !== 0) { + work[offs[lens[lens_index + sym]]++] = sym; + } + } + + /* + Create and fill in decoding tables. In this loop, the table being + filled is at next and has curr index bits. The code being used is huff + with length len. That code is converted to an index by dropping drop + bits off of the bottom. For codes where len is less than drop + curr, + those top drop + curr - len bits are incremented through all values to + fill the table with replicated entries. + + root is the number of index bits for the root table. When len exceeds + root, sub-tables are created pointed to by the root entry with an index + of the low root bits of huff. This is saved in low to check for when a + new sub-table should be started. drop is zero when the root table is + being filled, and drop is root when sub-tables are being filled. + + When a new sub-table is needed, it is necessary to look ahead in the + code lengths to determine what size sub-table is needed. The length + counts are used for this, and so count[] is decremented as codes are + entered in the tables. + + used keeps track of how many table entries have been allocated from the + provided *table space. It is checked for LENS and DIST tables against + the constants ENOUGH_LENS and ENOUGH_DISTS to guard against changes in + the initial root table size constants. See the comments in inftrees.h + for more information. + + sym increments through all symbols, and the loop terminates when + all codes of length max, i.e. all codes, have been processed. This + routine permits incomplete codes, so another loop after this one fills + in the rest of the decoding tables with invalid code markers. + */ + + /* set up for code type */ + // poor man optimization - use if-else instead of switch, + // to avoid deopts in old v8 + if (type === CODES$1) { + base = extra = work; /* dummy value--not used */ + end = 19; + } else if (type === LENS$1) { + base = lbase; + base_index -= 257; + extra = lext; + extra_index -= 257; + end = 256; + } else { + /* DISTS */ + base = dbase; + extra = dext; + end = -1; + } + + /* initialize opts for loop */ + huff = 0; /* starting code */ + sym = 0; /* starting code symbol */ + len = min; /* starting code length */ + next = table_index; /* current table to fill in */ + curr = root; /* current table index bits */ + drop = 0; /* current bits to drop from code for index */ + low = -1; /* trigger new sub-table when len > root */ + used = 1 << root; /* use root table entries */ + mask = used - 1; /* mask for comparing low */ + + /* check available table space */ + if ( + (type === LENS$1 && used > ENOUGH_LENS$1) || + (type === DISTS$1 && used > ENOUGH_DISTS$1) + ) { + return 1; + } + + /* process all codes and make table entries */ + for (;;) { + /* create table entry */ + here_bits = len - drop; + if (work[sym] < end) { + here_op = 0; + here_val = work[sym]; + } else if (work[sym] > end) { + here_op = extra[extra_index + work[sym]]; + here_val = base[base_index + work[sym]]; + } else { + here_op = 32 + 64; /* end of block */ + here_val = 0; + } + + /* replicate for those indices with low len bits equal to huff */ + incr = 1 << (len - drop); + fill = 1 << curr; + min = fill; /* save offset to next table */ + do { + fill -= incr; + table[next + (huff >> drop) + fill] = (here_bits << 24) | + (here_op << 16) | here_val | 0; + } while (fill !== 0); + + /* backwards increment the len-bit code huff */ + incr = 1 << (len - 1); + while (huff & incr) { + incr >>= 1; + } + if (incr !== 0) { + huff &= incr - 1; + huff += incr; + } else { + huff = 0; + } + + /* go to next symbol, update count, len */ + sym++; + if (--count[len] === 0) { + if (len === max) break; + len = lens[lens_index + work[sym]]; + } + + /* create new sub-table if needed */ + if (len > root && (huff & mask) !== low) { + /* if first time, transition to sub-tables */ + if (drop === 0) { + drop = root; + } + + /* increment past last table */ + next += min; /* here min is 1 << curr */ + + /* determine length of next table */ + curr = len - drop; + left = 1 << curr; + while (curr + drop < max) { + left -= count[curr + drop]; + if (left <= 0) break; + curr++; + left <<= 1; + } + + /* check for enough space */ + used += 1 << curr; + if ( + (type === LENS$1 && used > ENOUGH_LENS$1) || + (type === DISTS$1 && used > ENOUGH_DISTS$1) + ) { + return 1; + } + + /* point entry in root table to sub-table */ + low = huff & mask; + /*table.op[low] = curr; + table.bits[low] = root; + table.val[low] = next - opts.table_index;*/ + table[low] = (root << 24) | (curr << 16) | (next - table_index) | 0; + } + } + + /* fill in remaining table entry if code is incomplete (guaranteed to have + at most one remaining entry, since if the code is incomplete, the + maximum code length that was allowed to get this far is one bit) */ + if (huff !== 0) { + //table.op[next + huff] = 64; /* invalid code marker */ + //table.bits[next + huff] = len - drop; + //table.val[next + huff] = 0; + table[next + huff] = ((len - drop) << 24) | (64 << 16) | 0; + } + + /* set return parameters */ + //opts.table_index += used; + opts.bits = root; + return 0; +}; + +var inftrees = inflate_table; + +// (C) 1995-2013 Jean-loup Gailly and Mark Adler +// (C) 2014-2017 Vitaly Puzrin and Andrey Tupitsin +// +// This software is provided 'as-is', without any express or implied +// warranty. In no event will the authors be held liable for any damages +// arising from the use of this software. +// +// Permission is granted to anyone to use this software for any purpose, +// including commercial applications, and to alter it and redistribute it +// freely, subject to the following restrictions: +// +// 1. The origin of this software must not be misrepresented; you must not +// claim that you wrote the original software. If you use this software +// in a product, an acknowledgment in the product documentation would be +// appreciated but is not required. +// 2. Altered source versions must be plainly marked as such, and must not be +// misrepresented as being the original software. +// 3. This notice may not be removed or altered from any source distribution. + +const CODES = 0; +const LENS = 1; +const DISTS = 2; + +/* Public constants ==========================================================*/ +/* ===========================================================================*/ + +const { + Z_FINISH: Z_FINISH$1, + Z_BLOCK, + Z_TREES, + Z_OK: Z_OK$1, + Z_STREAM_END: Z_STREAM_END$1, + Z_NEED_DICT: Z_NEED_DICT$1, + Z_STREAM_ERROR: Z_STREAM_ERROR$1, + Z_DATA_ERROR: Z_DATA_ERROR$1, + Z_MEM_ERROR: Z_MEM_ERROR$1, + Z_BUF_ERROR, + Z_DEFLATED, +} = constants$2; + +/* STATES ====================================================================*/ +/* ===========================================================================*/ + +const HEAD = 1; /* i: waiting for magic header */ +const FLAGS = 2; /* i: waiting for method and flags (gzip) */ +const TIME = 3; /* i: waiting for modification time (gzip) */ +const OS = 4; /* i: waiting for extra flags and operating system (gzip) */ +const EXLEN = 5; /* i: waiting for extra length (gzip) */ +const EXTRA = 6; /* i: waiting for extra bytes (gzip) */ +const NAME = 7; /* i: waiting for end of file name (gzip) */ +const COMMENT = 8; /* i: waiting for end of comment (gzip) */ +const HCRC = 9; /* i: waiting for header crc (gzip) */ +const DICTID = 10; /* i: waiting for dictionary check value */ +const DICT = 11; /* waiting for inflateSetDictionary() call */ +const TYPE = 12; /* i: waiting for type bits, including last-flag bit */ +const TYPEDO = 13; /* i: same, but skip check to exit inflate on new block */ +const STORED = 14; /* i: waiting for stored size (length and complement) */ +const COPY_ = 15; /* i/o: same as COPY below, but only first time in */ +const COPY = 16; /* i/o: waiting for input or output to copy stored block */ +const TABLE = 17; /* i: waiting for dynamic block table lengths */ +const LENLENS = 18; /* i: waiting for code length code lengths */ +const CODELENS = 19; /* i: waiting for length/lit and distance code lengths */ +const LEN_ = 20; /* i: same as LEN below, but only first time in */ +const LEN = 21; /* i: waiting for length/lit/eob code */ +const LENEXT = 22; /* i: waiting for length extra bits */ +const DIST = 23; /* i: waiting for distance code */ +const DISTEXT = 24; /* i: waiting for distance extra bits */ +const MATCH = 25; /* o: waiting for output space to copy string */ +const LIT = 26; /* o: waiting for output space to write literal */ +const CHECK = 27; /* i: waiting for 32-bit check value */ +const LENGTH = 28; /* i: waiting for 32-bit length (gzip) */ +const DONE = 29; /* finished check, done -- remain here until reset */ +const BAD = 30; /* got a data error -- remain here until reset */ +const MEM = 31; /* got an inflate() memory error -- remain here until reset */ +const SYNC = 32; /* looking for synchronization bytes to restart inflate() */ + +/* ===========================================================================*/ + +const ENOUGH_LENS = 852; +const ENOUGH_DISTS = 592; +//const ENOUGH = (ENOUGH_LENS+ENOUGH_DISTS); + +const MAX_WBITS = 15; +/* 32K LZ77 window */ +const DEF_WBITS = MAX_WBITS; + +const zswap32 = (q) => { + return (((q >>> 24) & 0xff) + + ((q >>> 8) & 0xff00) + + ((q & 0xff00) << 8) + + ((q & 0xff) << 24)); +}; + +function InflateState() { + this.mode = 0; /* current inflate mode */ + this.last = false; /* true if processing last block */ + this.wrap = 0; /* bit 0 true for zlib, bit 1 true for gzip */ + this.havedict = false; /* true if dictionary provided */ + this.flags = 0; /* gzip header method and flags (0 if zlib) */ + this.dmax = 0; /* zlib header max distance (INFLATE_STRICT) */ + this.check = 0; /* protected copy of check value */ + this.total = 0; /* protected copy of output count */ + // TODO: may be {} + this.head = null; /* where to save gzip header information */ + + /* sliding window */ + this.wbits = 0; /* log base 2 of requested window size */ + this.wsize = 0; /* window size or zero if not using window */ + this.whave = 0; /* valid bytes in the window */ + this.wnext = 0; /* window write index */ + this.window = null; /* allocated sliding window, if needed */ + + /* bit accumulator */ + this.hold = 0; /* input bit accumulator */ + this.bits = 0; /* number of bits in "in" */ + + /* for string and stored block copying */ + this.length = 0; /* literal or length of data to copy */ + this.offset = 0; /* distance back to copy string from */ + + /* for table and code decoding */ + this.extra = 0; /* extra bits needed */ + + /* fixed and dynamic code tables */ + this.lencode = null; /* starting table for length/literal codes */ + this.distcode = null; /* starting table for distance codes */ + this.lenbits = 0; /* index bits for lencode */ + this.distbits = 0; /* index bits for distcode */ + + /* dynamic table building */ + this.ncode = 0; /* number of code length code lengths */ + this.nlen = 0; /* number of length code lengths */ + this.ndist = 0; /* number of distance code lengths */ + this.have = 0; /* number of code lengths in lens[] */ + this.next = null; /* next available space in codes[] */ + + this.lens = new Uint16Array(320); /* temporary storage for code lengths */ + this.work = new Uint16Array(288); /* work area for code table building */ + + /* + because we don't have pointers in js, we use lencode and distcode directly + as buffers so we don't need codes + */ + //this.codes = new Int32Array(ENOUGH); /* space for code tables */ + this.lendyn = null; /* dynamic table for length/literal codes (JS specific) */ + this.distdyn = null; /* dynamic table for distance codes (JS specific) */ + this.sane = 0; /* if false, allow invalid distance too far */ + this.back = 0; /* bits back of last unprocessed length/lit */ + this.was = 0; /* initial length of match */ +} + +const inflateResetKeep = (strm) => { + if (!strm || !strm.state) return Z_STREAM_ERROR$1; + const state = strm.state; + strm.total_in = strm.total_out = state.total = 0; + strm.msg = ""; /*Z_NULL*/ + if (state.wrap) { + /* to support ill-conceived Java test suite */ + strm.adler = state.wrap & 1; + } + state.mode = HEAD; + state.last = 0; + state.havedict = 0; + state.dmax = 32768; + state.head = null /*Z_NULL*/; + state.hold = 0; + state.bits = 0; + //state.lencode = state.distcode = state.next = state.codes; + state.lencode = state.lendyn = new Int32Array(ENOUGH_LENS); + state.distcode = state.distdyn = new Int32Array(ENOUGH_DISTS); + + state.sane = 1; + state.back = -1; + //Tracev((stderr, "inflate: reset\n")); + return Z_OK$1; +}; + +const inflateReset = (strm) => { + if (!strm || !strm.state) return Z_STREAM_ERROR$1; + const state = strm.state; + state.wsize = 0; + state.whave = 0; + state.wnext = 0; + return inflateResetKeep(strm); +}; + +const inflateReset2 = (strm, windowBits) => { + let wrap; + + /* get the state */ + if (!strm || !strm.state) return Z_STREAM_ERROR$1; + const state = strm.state; + + /* extract wrap request from windowBits parameter */ + if (windowBits < 0) { + wrap = 0; + windowBits = -windowBits; + } else { + wrap = (windowBits >> 4) + 1; + if (windowBits < 48) { + windowBits &= 15; + } + } + + /* set number of window bits, free window if different */ + if (windowBits && (windowBits < 8 || windowBits > 15)) { + return Z_STREAM_ERROR$1; + } + if (state.window !== null && state.wbits !== windowBits) { + state.window = null; + } + + /* update state and reset the rest of it */ + state.wrap = wrap; + state.wbits = windowBits; + return inflateReset(strm); +}; + +const inflateInit2 = (strm, windowBits) => { + if (!strm) return Z_STREAM_ERROR$1; + //strm.msg = Z_NULL; /* in case we return an error */ + + const state = new InflateState(); + + //if (state === Z_NULL) return Z_MEM_ERROR; + //Tracev((stderr, "inflate: allocated\n")); + strm.state = state; + state.window = null /*Z_NULL*/; + const ret = inflateReset2(strm, windowBits); + if (ret !== Z_OK$1) { + strm.state = null /*Z_NULL*/; + } + return ret; +}; + +const inflateInit = (strm) => { + return inflateInit2(strm, DEF_WBITS); +}; + +/* + Return state with length and distance decoding tables and index sizes set to + fixed code decoding. Normally this returns fixed tables from inffixed.h. + If BUILDFIXED is defined, then instead this routine builds the tables the + first time it's called, and returns those tables the first time and + thereafter. This reduces the size of the code by about 2K bytes, in + exchange for a little execution time. However, BUILDFIXED should not be + used for threaded applications, since the rewriting of the tables and virgin + may not be thread-safe. + */ +let virgin = true; + +let lenfix, distfix; // We have no pointers in JS, so keep tables separate + +const fixedtables = (state) => { + /* build fixed huffman tables if first call (may not be thread safe) */ + if (virgin) { + lenfix = new Int32Array(512); + distfix = new Int32Array(32); + + /* literal/length table */ + let sym = 0; + while (sym < 144) state.lens[sym++] = 8; + while (sym < 256) state.lens[sym++] = 9; + while (sym < 280) state.lens[sym++] = 7; + while (sym < 288) state.lens[sym++] = 8; + + inftrees(LENS, state.lens, 0, 288, lenfix, 0, state.work, { bits: 9 }); + + /* distance table */ + sym = 0; + while (sym < 32) state.lens[sym++] = 5; + + inftrees(DISTS, state.lens, 0, 32, distfix, 0, state.work, { bits: 5 }); + + /* do this just once */ + virgin = false; + } + + state.lencode = lenfix; + state.lenbits = 9; + state.distcode = distfix; + state.distbits = 5; +}; + +/* + Update the window with the last wsize (normally 32K) bytes written before + returning. If window does not exist yet, create it. This is only called + when a window is already in use, or when output has been written during this + inflate call, but the end of the deflate stream has not been reached yet. + It is also called to create a window for dictionary data when a dictionary + is loaded. + + Providing output buffers larger than 32K to inflate() should provide a speed + advantage, since only the last 32K of output is copied to the sliding window + upon return from inflate(), and since all distances after the first 32K of + output will fall in the output data, making match copies simpler and faster. + The advantage may be dependent on the size of the processor's data caches. + */ +const updatewindow = (strm, src, end, copy) => { + let dist; + const state = strm.state; + + /* if it hasn't been done already, allocate space for the window */ + if (state.window === null) { + state.wsize = 1 << state.wbits; + state.wnext = 0; + state.whave = 0; + + state.window = new Uint8Array(state.wsize); + } + + /* copy state->wsize or less output bytes into the circular window */ + if (copy >= state.wsize) { + state.window.set(src.subarray(end - state.wsize, end), 0); + state.wnext = 0; + state.whave = state.wsize; + } else { + dist = state.wsize - state.wnext; + if (dist > copy) { + dist = copy; + } + //zmemcpy(state->window + state->wnext, end - copy, dist); + state.window.set(src.subarray(end - copy, end - copy + dist), state.wnext); + copy -= dist; + if (copy) { + //zmemcpy(state->window, end - copy, copy); + state.window.set(src.subarray(end - copy, end), 0); + state.wnext = copy; + state.whave = state.wsize; + } else { + state.wnext += dist; + if (state.wnext === state.wsize) state.wnext = 0; + if (state.whave < state.wsize) state.whave += dist; + } + } + return 0; +}; + +const inflate$2 = (strm, flush) => { + let state; + let input, output; // input/output buffers + let next; /* next input INDEX */ + let put; /* next output INDEX */ + let have, left; /* available input and output */ + let hold; /* bit buffer */ + let bits; /* bits in bit buffer */ + let _in, _out; /* save starting available input and output */ + let copy; /* number of stored or match bytes to copy */ + let from; /* where to copy match bytes from */ + let from_source; + let here = 0; /* current decoding table entry */ + let here_bits, here_op, here_val; // paked "here" denormalized (JS specific) + //let last; /* parent table entry */ + let last_bits, last_op, last_val; // paked "last" denormalized (JS specific) + let len; /* length to copy for repeats, bits to drop */ + let ret; /* return code */ + const hbuf = new Uint8Array(4); /* buffer for gzip header crc calculation */ + let opts; + + let n; // temporary variable for NEED_BITS + + const order = /* permutation of code lengths */ + new Uint8Array([ + 16, + 17, + 18, + 0, + 8, + 7, + 9, + 6, + 10, + 5, + 11, + 4, + 12, + 3, + 13, + 2, + 14, + 1, + 15, + ]); + + if ( + !strm || !strm.state || !strm.output || + (!strm.input && strm.avail_in !== 0) + ) { + return Z_STREAM_ERROR$1; + } + + state = strm.state; + if (state.mode === TYPE) state.mode = TYPEDO; /* skip check */ + + //--- LOAD() --- + put = strm.next_out; + output = strm.output; + left = strm.avail_out; + next = strm.next_in; + input = strm.input; + have = strm.avail_in; + hold = state.hold; + bits = state.bits; + //--- + + _in = have; + _out = left; + ret = Z_OK$1; + + inf_leave: + // goto emulation + for (;;) { + switch (state.mode) { + case HEAD: + if (state.wrap === 0) { + state.mode = TYPEDO; + break; + } + //=== NEEDBITS(16); + while (bits < 16) { + if (have === 0) break inf_leave; + have--; + hold += input[next++] << bits; + bits += 8; + } + //===// + if ((state.wrap & 2) && hold === 0x8b1f) { + /* gzip header */ + state.check = 0 /*crc32(0L, Z_NULL, 0)*/; + //=== CRC2(state.check, hold); + hbuf[0] = hold & 0xff; + hbuf[1] = (hold >>> 8) & 0xff; + state.check = crc32_1(state.check, hbuf, 2, 0); + //===// + + //=== INITBITS(); + hold = 0; + bits = 0; + //===// + state.mode = FLAGS; + break; + } + state.flags = 0; /* expect zlib header */ + if (state.head) { + state.head.done = false; + } + if ( + !(state.wrap & 1) || /* check if zlib header allowed */ + (((hold & 0xff) /*BITS(8)*/ << 8) + (hold >> 8)) % 31 + ) { + strm.msg = "incorrect header check"; + state.mode = BAD; + break; + } + if ((hold & 0x0f) /*BITS(4)*/ !== Z_DEFLATED) { + strm.msg = "unknown compression method"; + state.mode = BAD; + break; + } + //--- DROPBITS(4) ---// + hold >>>= 4; + bits -= 4; + //---// + len = (hold & 0x0f) /*BITS(4)*/ + 8; + if (state.wbits === 0) { + state.wbits = len; + } else if (len > state.wbits) { + strm.msg = "invalid window size"; + state.mode = BAD; + break; + } + + // !!! pako patch. Force use `options.windowBits` if passed. + // Required to always use max window size by default. + state.dmax = 1 << state.wbits; + //state.dmax = 1 << len; + + //Tracev((stderr, "inflate: zlib header ok\n")); + strm.adler = state.check = 1 /*adler32(0L, Z_NULL, 0)*/; + state.mode = hold & 0x200 ? DICTID : TYPE; + //=== INITBITS(); + hold = 0; + bits = 0; + //===// + break; + case FLAGS: + //=== NEEDBITS(16); */ + while (bits < 16) { + if (have === 0) break inf_leave; + have--; + hold += input[next++] << bits; + bits += 8; + } + //===// + state.flags = hold; + if ((state.flags & 0xff) !== Z_DEFLATED) { + strm.msg = "unknown compression method"; + state.mode = BAD; + break; + } + if (state.flags & 0xe000) { + strm.msg = "unknown header flags set"; + state.mode = BAD; + break; + } + if (state.head) { + state.head.text = (hold >> 8) & 1; + } + if (state.flags & 0x0200) { + //=== CRC2(state.check, hold); + hbuf[0] = hold & 0xff; + hbuf[1] = (hold >>> 8) & 0xff; + state.check = crc32_1(state.check, hbuf, 2, 0); + //===// + } + //=== INITBITS(); + hold = 0; + bits = 0; + //===// + state.mode = TIME; + /* falls through */ + case TIME: + //=== NEEDBITS(32); */ + while (bits < 32) { + if (have === 0) break inf_leave; + have--; + hold += input[next++] << bits; + bits += 8; + } + //===// + if (state.head) { + state.head.time = hold; + } + if (state.flags & 0x0200) { + //=== CRC4(state.check, hold) + hbuf[0] = hold & 0xff; + hbuf[1] = (hold >>> 8) & 0xff; + hbuf[2] = (hold >>> 16) & 0xff; + hbuf[3] = (hold >>> 24) & 0xff; + state.check = crc32_1(state.check, hbuf, 4, 0); + //=== + } + //=== INITBITS(); + hold = 0; + bits = 0; + //===// + state.mode = OS; + /* falls through */ + case OS: + //=== NEEDBITS(16); */ + while (bits < 16) { + if (have === 0) break inf_leave; + have--; + hold += input[next++] << bits; + bits += 8; + } + //===// + if (state.head) { + state.head.xflags = hold & 0xff; + state.head.os = hold >> 8; + } + if (state.flags & 0x0200) { + //=== CRC2(state.check, hold); + hbuf[0] = hold & 0xff; + hbuf[1] = (hold >>> 8) & 0xff; + state.check = crc32_1(state.check, hbuf, 2, 0); + //===// + } + //=== INITBITS(); + hold = 0; + bits = 0; + //===// + state.mode = EXLEN; + /* falls through */ + case EXLEN: + if (state.flags & 0x0400) { + //=== NEEDBITS(16); */ + while (bits < 16) { + if (have === 0) break inf_leave; + have--; + hold += input[next++] << bits; + bits += 8; + } + //===// + state.length = hold; + if (state.head) { + state.head.extra_len = hold; + } + if (state.flags & 0x0200) { + //=== CRC2(state.check, hold); + hbuf[0] = hold & 0xff; + hbuf[1] = (hold >>> 8) & 0xff; + state.check = crc32_1(state.check, hbuf, 2, 0); + //===// + } + //=== INITBITS(); + hold = 0; + bits = 0; + //===// + } else if (state.head) { + state.head.extra = null /*Z_NULL*/; + } + state.mode = EXTRA; + /* falls through */ + case EXTRA: + if (state.flags & 0x0400) { + copy = state.length; + if (copy > have) copy = have; + if (copy) { + if (state.head) { + len = state.head.extra_len - state.length; + if (!state.head.extra) { + // Use untyped array for more convenient processing later + state.head.extra = new Uint8Array(state.head.extra_len); + } + state.head.extra.set( + input.subarray( + next, + // extra field is limited to 65536 bytes + // - no need for additional size check + next + copy, + ), + /*len + copy > state.head.extra_max - len ? state.head.extra_max : copy,*/ + len, + ); + //zmemcpy(state.head.extra + len, next, + // len + copy > state.head.extra_max ? + // state.head.extra_max - len : copy); + } + if (state.flags & 0x0200) { + state.check = crc32_1(state.check, input, copy, next); + } + have -= copy; + next += copy; + state.length -= copy; + } + if (state.length) break inf_leave; + } + state.length = 0; + state.mode = NAME; + /* falls through */ + case NAME: + if (state.flags & 0x0800) { + if (have === 0) break inf_leave; + copy = 0; + do { + // TODO: 2 or 1 bytes? + len = input[next + copy++]; + /* use constant limit because in js we should not preallocate memory */ + if ( + state.head && len && + (state.length < 65536 /*state.head.name_max*/) + ) { + state.head.name += String.fromCharCode(len); + } + } while (len && copy < have); + + if (state.flags & 0x0200) { + state.check = crc32_1(state.check, input, copy, next); + } + have -= copy; + next += copy; + if (len) break inf_leave; + } else if (state.head) { + state.head.name = null; + } + state.length = 0; + state.mode = COMMENT; + /* falls through */ + case COMMENT: + if (state.flags & 0x1000) { + if (have === 0) break inf_leave; + copy = 0; + do { + len = input[next + copy++]; + /* use constant limit because in js we should not preallocate memory */ + if ( + state.head && len && + (state.length < 65536 /*state.head.comm_max*/) + ) { + state.head.comment += String.fromCharCode(len); + } + } while (len && copy < have); + if (state.flags & 0x0200) { + state.check = crc32_1(state.check, input, copy, next); + } + have -= copy; + next += copy; + if (len) break inf_leave; + } else if (state.head) { + state.head.comment = null; + } + state.mode = HCRC; + /* falls through */ + case HCRC: + if (state.flags & 0x0200) { + //=== NEEDBITS(16); */ + while (bits < 16) { + if (have === 0) break inf_leave; + have--; + hold += input[next++] << bits; + bits += 8; + } + //===// + if (hold !== (state.check & 0xffff)) { + strm.msg = "header crc mismatch"; + state.mode = BAD; + break; + } + //=== INITBITS(); + hold = 0; + bits = 0; + //===// + } + if (state.head) { + state.head.hcrc = (state.flags >> 9) & 1; + state.head.done = true; + } + strm.adler = state.check = 0; + state.mode = TYPE; + break; + case DICTID: + //=== NEEDBITS(32); */ + while (bits < 32) { + if (have === 0) break inf_leave; + have--; + hold += input[next++] << bits; + bits += 8; + } + //===// + strm.adler = state.check = zswap32(hold); + //=== INITBITS(); + hold = 0; + bits = 0; + //===// + state.mode = DICT; + /* falls through */ + case DICT: + if (state.havedict === 0) { + //--- RESTORE() --- + strm.next_out = put; + strm.avail_out = left; + strm.next_in = next; + strm.avail_in = have; + state.hold = hold; + state.bits = bits; + //--- + return Z_NEED_DICT$1; + } + strm.adler = state.check = 1 /*adler32(0L, Z_NULL, 0)*/; + state.mode = TYPE; + /* falls through */ + case TYPE: + if (flush === Z_BLOCK || flush === Z_TREES) break inf_leave; + /* falls through */ + case TYPEDO: + if (state.last) { + //--- BYTEBITS() ---// + hold >>>= bits & 7; + bits -= bits & 7; + //---// + state.mode = CHECK; + break; + } + //=== NEEDBITS(3); */ + while (bits < 3) { + if (have === 0) break inf_leave; + have--; + hold += input[next++] << bits; + bits += 8; + } + //===// + state.last = hold & 0x01 /*BITS(1)*/; + //--- DROPBITS(1) ---// + hold >>>= 1; + bits -= 1; + //---// + + switch ((hold & 0x03) /*BITS(2)*/) { + case 0: /* stored block */ + //Tracev((stderr, "inflate: stored block%s\n", + // state.last ? " (last)" : "")); + state.mode = STORED; + break; + case 1: /* fixed block */ + fixedtables(state); + //Tracev((stderr, "inflate: fixed codes block%s\n", + // state.last ? " (last)" : "")); + state.mode = LEN_; /* decode codes */ + if (flush === Z_TREES) { + //--- DROPBITS(2) ---// + hold >>>= 2; + bits -= 2; + //---// + break inf_leave; + } + break; + case 2: /* dynamic block */ + //Tracev((stderr, "inflate: dynamic codes block%s\n", + // state.last ? " (last)" : "")); + state.mode = TABLE; + break; + case 3: + strm.msg = "invalid block type"; + state.mode = BAD; + } + //--- DROPBITS(2) ---// + hold >>>= 2; + bits -= 2; + //---// + break; + case STORED: + //--- BYTEBITS() ---// /* go to byte boundary */ + hold >>>= bits & 7; + bits -= bits & 7; + //---// + //=== NEEDBITS(32); */ + while (bits < 32) { + if (have === 0) break inf_leave; + have--; + hold += input[next++] << bits; + bits += 8; + } + //===// + if ((hold & 0xffff) !== ((hold >>> 16) ^ 0xffff)) { + strm.msg = "invalid stored block lengths"; + state.mode = BAD; + break; + } + state.length = hold & 0xffff; + //Tracev((stderr, "inflate: stored length %u\n", + // state.length)); + //=== INITBITS(); + hold = 0; + bits = 0; + //===// + state.mode = COPY_; + if (flush === Z_TREES) break inf_leave; + /* falls through */ + case COPY_: + state.mode = COPY; + /* falls through */ + case COPY: + copy = state.length; + if (copy) { + if (copy > have) copy = have; + if (copy > left) copy = left; + if (copy === 0) break inf_leave; + //--- zmemcpy(put, next, copy); --- + output.set(input.subarray(next, next + copy), put); + //---// + have -= copy; + next += copy; + left -= copy; + put += copy; + state.length -= copy; + break; + } + //Tracev((stderr, "inflate: stored end\n")); + state.mode = TYPE; + break; + case TABLE: + //=== NEEDBITS(14); */ + while (bits < 14) { + if (have === 0) break inf_leave; + have--; + hold += input[next++] << bits; + bits += 8; + } + //===// + state.nlen = (hold & 0x1f) /*BITS(5)*/ + 257; + //--- DROPBITS(5) ---// + hold >>>= 5; + bits -= 5; + //---// + state.ndist = (hold & 0x1f) /*BITS(5)*/ + 1; + //--- DROPBITS(5) ---// + hold >>>= 5; + bits -= 5; + //---// + state.ncode = (hold & 0x0f) /*BITS(4)*/ + 4; + //--- DROPBITS(4) ---// + hold >>>= 4; + bits -= 4; + //---// + //#ifndef PKZIP_BUG_WORKAROUND + if (state.nlen > 286 || state.ndist > 30) { + strm.msg = "too many length or distance symbols"; + state.mode = BAD; + break; + } + //#endif + //Tracev((stderr, "inflate: table sizes ok\n")); + state.have = 0; + state.mode = LENLENS; + /* falls through */ + case LENLENS: + while (state.have < state.ncode) { + //=== NEEDBITS(3); + while (bits < 3) { + if (have === 0) break inf_leave; + have--; + hold += input[next++] << bits; + bits += 8; + } + //===// + state.lens[order[state.have++]] = hold & 0x07; //BITS(3); + //--- DROPBITS(3) ---// + hold >>>= 3; + bits -= 3; + //---// + } + while (state.have < 19) { + state.lens[order[state.have++]] = 0; + } + // We have separate tables & no pointers. 2 commented lines below not needed. + //state.next = state.codes; + //state.lencode = state.next; + // Switch to use dynamic table + state.lencode = state.lendyn; + state.lenbits = 7; + + opts = { bits: state.lenbits }; + ret = inftrees( + CODES, + state.lens, + 0, + 19, + state.lencode, + 0, + state.work, + opts, + ); + state.lenbits = opts.bits; + + if (ret) { + strm.msg = "invalid code lengths set"; + state.mode = BAD; + break; + } + //Tracev((stderr, "inflate: code lengths ok\n")); + state.have = 0; + state.mode = CODELENS; + /* falls through */ + case CODELENS: + while (state.have < state.nlen + state.ndist) { + for (;;) { + here = state + .lencode[ + hold & ((1 << state.lenbits) - 1) + ]; /*BITS(state.lenbits)*/ + here_bits = here >>> 24; + here_op = (here >>> 16) & 0xff; + here_val = here & 0xffff; + + if ((here_bits) <= bits) break; + //--- PULLBYTE() ---// + if (have === 0) break inf_leave; + have--; + hold += input[next++] << bits; + bits += 8; + //---// + } + if (here_val < 16) { + //--- DROPBITS(here.bits) ---// + hold >>>= here_bits; + bits -= here_bits; + //---// + state.lens[state.have++] = here_val; + } else { + if (here_val === 16) { + //=== NEEDBITS(here.bits + 2); + n = here_bits + 2; + while (bits < n) { + if (have === 0) break inf_leave; + have--; + hold += input[next++] << bits; + bits += 8; + } + //===// + //--- DROPBITS(here.bits) ---// + hold >>>= here_bits; + bits -= here_bits; + //---// + if (state.have === 0) { + strm.msg = "invalid bit length repeat"; + state.mode = BAD; + break; + } + len = state.lens[state.have - 1]; + copy = 3 + (hold & 0x03); //BITS(2); + //--- DROPBITS(2) ---// + hold >>>= 2; + bits -= 2; + //---// + } else if (here_val === 17) { + //=== NEEDBITS(here.bits + 3); + n = here_bits + 3; + while (bits < n) { + if (have === 0) break inf_leave; + have--; + hold += input[next++] << bits; + bits += 8; + } + //===// + //--- DROPBITS(here.bits) ---// + hold >>>= here_bits; + bits -= here_bits; + //---// + len = 0; + copy = 3 + (hold & 0x07); //BITS(3); + //--- DROPBITS(3) ---// + hold >>>= 3; + bits -= 3; + //---// + } else { + //=== NEEDBITS(here.bits + 7); + n = here_bits + 7; + while (bits < n) { + if (have === 0) break inf_leave; + have--; + hold += input[next++] << bits; + bits += 8; + } + //===// + //--- DROPBITS(here.bits) ---// + hold >>>= here_bits; + bits -= here_bits; + //---// + len = 0; + copy = 11 + (hold & 0x7f); //BITS(7); + //--- DROPBITS(7) ---// + hold >>>= 7; + bits -= 7; + //---// + } + if (state.have + copy > state.nlen + state.ndist) { + strm.msg = "invalid bit length repeat"; + state.mode = BAD; + break; + } + while (copy--) { + state.lens[state.have++] = len; + } + } + } + + /* handle error breaks in while */ + if (state.mode === BAD) break; + + /* check for end-of-block code (better have one) */ + if (state.lens[256] === 0) { + strm.msg = "invalid code -- missing end-of-block"; + state.mode = BAD; + break; + } + + /* build code tables -- note: do not change the lenbits or distbits + values here (9 and 6) without reading the comments in inftrees.h + concerning the ENOUGH constants, which depend on those values */ + state.lenbits = 9; + + opts = { bits: state.lenbits }; + ret = inftrees( + LENS, + state.lens, + 0, + state.nlen, + state.lencode, + 0, + state.work, + opts, + ); + // We have separate tables & no pointers. 2 commented lines below not needed. + // state.next_index = opts.table_index; + state.lenbits = opts.bits; + // state.lencode = state.next; + + if (ret) { + strm.msg = "invalid literal/lengths set"; + state.mode = BAD; + break; + } + + state.distbits = 6; + //state.distcode.copy(state.codes); + // Switch to use dynamic table + state.distcode = state.distdyn; + opts = { bits: state.distbits }; + ret = inftrees( + DISTS, + state.lens, + state.nlen, + state.ndist, + state.distcode, + 0, + state.work, + opts, + ); + // We have separate tables & no pointers. 2 commented lines below not needed. + // state.next_index = opts.table_index; + state.distbits = opts.bits; + // state.distcode = state.next; + + if (ret) { + strm.msg = "invalid distances set"; + state.mode = BAD; + break; + } + //Tracev((stderr, 'inflate: codes ok\n')); + state.mode = LEN_; + if (flush === Z_TREES) break inf_leave; + /* falls through */ + case LEN_: + state.mode = LEN; + /* falls through */ + case LEN: + if (have >= 6 && left >= 258) { + //--- RESTORE() --- + strm.next_out = put; + strm.avail_out = left; + strm.next_in = next; + strm.avail_in = have; + state.hold = hold; + state.bits = bits; + //--- + inffast(strm, _out); + //--- LOAD() --- + put = strm.next_out; + output = strm.output; + left = strm.avail_out; + next = strm.next_in; + input = strm.input; + have = strm.avail_in; + hold = state.hold; + bits = state.bits; + //--- + + if (state.mode === TYPE) { + state.back = -1; + } + break; + } + state.back = 0; + for (;;) { + here = state + .lencode[ + hold & ((1 << state.lenbits) - 1) + ]; /*BITS(state.lenbits)*/ + here_bits = here >>> 24; + here_op = (here >>> 16) & 0xff; + here_val = here & 0xffff; + + if (here_bits <= bits) break; + //--- PULLBYTE() ---// + if (have === 0) break inf_leave; + have--; + hold += input[next++] << bits; + bits += 8; + //---// + } + if (here_op && (here_op & 0xf0) === 0) { + last_bits = here_bits; + last_op = here_op; + last_val = here_val; + for (;;) { + here = state.lencode[ + last_val + + ((hold & + ((1 << (last_bits + last_op)) - + 1)) /*BITS(last.bits + last.op)*/ >> last_bits) + ]; + here_bits = here >>> 24; + here_op = (here >>> 16) & 0xff; + here_val = here & 0xffff; + + if ((last_bits + here_bits) <= bits) break; + //--- PULLBYTE() ---// + if (have === 0) break inf_leave; + have--; + hold += input[next++] << bits; + bits += 8; + //---// + } + //--- DROPBITS(last.bits) ---// + hold >>>= last_bits; + bits -= last_bits; + //---// + state.back += last_bits; + } + //--- DROPBITS(here.bits) ---// + hold >>>= here_bits; + bits -= here_bits; + //---// + state.back += here_bits; + state.length = here_val; + if (here_op === 0) { + //Tracevv((stderr, here.val >= 0x20 && here.val < 0x7f ? + // "inflate: literal '%c'\n" : + // "inflate: literal 0x%02x\n", here.val)); + state.mode = LIT; + break; + } + if (here_op & 32) { + //Tracevv((stderr, "inflate: end of block\n")); + state.back = -1; + state.mode = TYPE; + break; + } + if (here_op & 64) { + strm.msg = "invalid literal/length code"; + state.mode = BAD; + break; + } + state.extra = here_op & 15; + state.mode = LENEXT; + /* falls through */ + case LENEXT: + if (state.extra) { + //=== NEEDBITS(state.extra); + n = state.extra; + while (bits < n) { + if (have === 0) break inf_leave; + have--; + hold += input[next++] << bits; + bits += 8; + } + //===// + state.length += hold & ((1 << state.extra) - 1) /*BITS(state.extra)*/; + //--- DROPBITS(state.extra) ---// + hold >>>= state.extra; + bits -= state.extra; + //---// + state.back += state.extra; + } + //Tracevv((stderr, "inflate: length %u\n", state.length)); + state.was = state.length; + state.mode = DIST; + /* falls through */ + case DIST: + for (;;) { + here = state + .distcode[ + hold & ((1 << state.distbits) - 1) + ]; /*BITS(state.distbits)*/ + here_bits = here >>> 24; + here_op = (here >>> 16) & 0xff; + here_val = here & 0xffff; + + if ((here_bits) <= bits) break; + //--- PULLBYTE() ---// + if (have === 0) break inf_leave; + have--; + hold += input[next++] << bits; + bits += 8; + //---// + } + if ((here_op & 0xf0) === 0) { + last_bits = here_bits; + last_op = here_op; + last_val = here_val; + for (;;) { + here = state.distcode[ + last_val + + ((hold & + ((1 << (last_bits + last_op)) - + 1)) /*BITS(last.bits + last.op)*/ >> last_bits) + ]; + here_bits = here >>> 24; + here_op = (here >>> 16) & 0xff; + here_val = here & 0xffff; + + if ((last_bits + here_bits) <= bits) break; + //--- PULLBYTE() ---// + if (have === 0) break inf_leave; + have--; + hold += input[next++] << bits; + bits += 8; + //---// + } + //--- DROPBITS(last.bits) ---// + hold >>>= last_bits; + bits -= last_bits; + //---// + state.back += last_bits; + } + //--- DROPBITS(here.bits) ---// + hold >>>= here_bits; + bits -= here_bits; + //---// + state.back += here_bits; + if (here_op & 64) { + strm.msg = "invalid distance code"; + state.mode = BAD; + break; + } + state.offset = here_val; + state.extra = (here_op) & 15; + state.mode = DISTEXT; + /* falls through */ + case DISTEXT: + if (state.extra) { + //=== NEEDBITS(state.extra); + n = state.extra; + while (bits < n) { + if (have === 0) break inf_leave; + have--; + hold += input[next++] << bits; + bits += 8; + } + //===// + state.offset += hold & ((1 << state.extra) - 1) /*BITS(state.extra)*/; + //--- DROPBITS(state.extra) ---// + hold >>>= state.extra; + bits -= state.extra; + //---// + state.back += state.extra; + } + //#ifdef INFLATE_STRICT + if (state.offset > state.dmax) { + strm.msg = "invalid distance too far back"; + state.mode = BAD; + break; + } + //#endif + //Tracevv((stderr, "inflate: distance %u\n", state.offset)); + state.mode = MATCH; + /* falls through */ + case MATCH: + if (left === 0) break inf_leave; + copy = _out - left; + if (state.offset > copy) { + /* copy from window */ + copy = state.offset - copy; + if (copy > state.whave) { + if (state.sane) { + strm.msg = "invalid distance too far back"; + state.mode = BAD; + break; + } + // (!) This block is disabled in zlib defaults, + // don't enable it for binary compatibility + //#ifdef INFLATE_ALLOW_INVALID_DISTANCE_TOOFAR_ARRR + // Trace((stderr, "inflate.c too far\n")); + // copy -= state.whave; + // if (copy > state.length) { copy = state.length; } + // if (copy > left) { copy = left; } + // left -= copy; + // state.length -= copy; + // do { + // output[put++] = 0; + // } while (--copy); + // if (state.length === 0) { state.mode = LEN; } + // break; + //#endif + } + if (copy > state.wnext) { + copy -= state.wnext; + from = state.wsize - copy; + } else { + from = state.wnext - copy; + } + if (copy > state.length) copy = state.length; + from_source = state.window; + } else { + /* copy from output */ + from_source = output; + from = put - state.offset; + copy = state.length; + } + if (copy > left) copy = left; + left -= copy; + state.length -= copy; + do { + output[put++] = from_source[from++]; + } while (--copy); + if (state.length === 0) state.mode = LEN; + break; + case LIT: + if (left === 0) break inf_leave; + output[put++] = state.length; + left--; + state.mode = LEN; + break; + case CHECK: + if (state.wrap) { + //=== NEEDBITS(32); + while (bits < 32) { + if (have === 0) break inf_leave; + have--; + // Use '|' instead of '+' to make sure that result is signed + hold |= input[next++] << bits; + bits += 8; + } + //===// + _out -= left; + strm.total_out += _out; + state.total += _out; + if (_out) { + strm.adler = + state.check = + /*UPDATE(state.check, put - _out, _out);*/ + state.flags + ? crc32_1(state.check, output, _out, put - _out) + : adler32_1(state.check, output, _out, put - _out); + } + _out = left; + // NB: crc32 stored as signed 32-bit int, zswap32 returns signed too + if ((state.flags ? hold : zswap32(hold)) !== state.check) { + strm.msg = "incorrect data check"; + state.mode = BAD; + break; + } + //=== INITBITS(); + hold = 0; + bits = 0; + //===// + //Tracev((stderr, "inflate: check matches trailer\n")); + } + state.mode = LENGTH; + /* falls through */ + case LENGTH: + if (state.wrap && state.flags) { + //=== NEEDBITS(32); + while (bits < 32) { + if (have === 0) break inf_leave; + have--; + hold += input[next++] << bits; + bits += 8; + } + //===// + if (hold !== (state.total & 0xffffffff)) { + strm.msg = "incorrect length check"; + state.mode = BAD; + break; + } + //=== INITBITS(); + hold = 0; + bits = 0; + //===// + //Tracev((stderr, "inflate: length matches trailer\n")); + } + state.mode = DONE; + /* falls through */ + case DONE: + ret = Z_STREAM_END$1; + break inf_leave; + case BAD: + ret = Z_DATA_ERROR$1; + break inf_leave; + case MEM: + return Z_MEM_ERROR$1; + case SYNC: + /* falls through */ + default: + return Z_STREAM_ERROR$1; + } + } + + // inf_leave <- here is real place for "goto inf_leave", emulated via "break inf_leave" + + /* + Return from inflate(), updating the total counts and the check value. + If there was no progress during the inflate() call, return a buffer + error. Call updatewindow() to create and/or update the window state. + Note: a memory error from inflate() is non-recoverable. + */ + + //--- RESTORE() --- + strm.next_out = put; + strm.avail_out = left; + strm.next_in = next; + strm.avail_in = have; + state.hold = hold; + state.bits = bits; + //--- + + if ( + state.wsize || (_out !== strm.avail_out && state.mode < BAD && + (state.mode < CHECK || flush !== Z_FINISH$1)) + ) { + if (updatewindow(strm, strm.output, strm.next_out, _out - strm.avail_out)); + } + _in -= strm.avail_in; + _out -= strm.avail_out; + strm.total_in += _in; + strm.total_out += _out; + state.total += _out; + if (state.wrap && _out) { + strm.adler = + state + .check = /*UPDATE(state.check, strm.next_out - _out, _out);*/ + state.flags + ? crc32_1(state.check, output, _out, strm.next_out - _out) + : adler32_1(state.check, output, _out, strm.next_out - _out); + } + strm.data_type = state.bits + (state.last ? 64 : 0) + + (state.mode === TYPE ? 128 : 0) + + (state.mode === LEN_ || state.mode === COPY_ ? 256 : 0); + if (((_in === 0 && _out === 0) || flush === Z_FINISH$1) && ret === Z_OK$1) { + ret = Z_BUF_ERROR; + } + return ret; +}; + +const inflateEnd = (strm) => { + if (!strm || !strm.state /*|| strm->zfree == (free_func)0*/) { + return Z_STREAM_ERROR$1; + } + + let state = strm.state; + if (state.window) { + state.window = null; + } + strm.state = null; + return Z_OK$1; +}; + +const inflateGetHeader = (strm, head) => { + /* check state */ + if (!strm || !strm.state) return Z_STREAM_ERROR$1; + const state = strm.state; + if ((state.wrap & 2) === 0) return Z_STREAM_ERROR$1; + + /* save header structure */ + state.head = head; + head.done = false; + return Z_OK$1; +}; + +const inflateSetDictionary = (strm, dictionary) => { + const dictLength = dictionary.length; + + let state; + let dictid; + let ret; + + /* check state */ + if (!strm /* == Z_NULL */ || !strm.state /* == Z_NULL */) { + return Z_STREAM_ERROR$1; + } + state = strm.state; + + if (state.wrap !== 0 && state.mode !== DICT) { + return Z_STREAM_ERROR$1; + } + + /* check for correct dictionary identifier */ + if (state.mode === DICT) { + dictid = 1; /* adler32(0, null, 0)*/ + /* dictid = adler32(dictid, dictionary, dictLength); */ + dictid = adler32_1(dictid, dictionary, dictLength, 0); + if (dictid !== state.check) { + return Z_DATA_ERROR$1; + } + } + /* copy dictionary to window using updatewindow(), which will amend the + existing dictionary if appropriate */ + ret = updatewindow(strm, dictionary, dictLength, dictLength); + if (ret) { + state.mode = MEM; + return Z_MEM_ERROR$1; + } + state.havedict = 1; + // Tracev((stderr, "inflate: dictionary set\n")); + return Z_OK$1; +}; + +var inflateReset_1 = inflateReset; +var inflateReset2_1 = inflateReset2; +var inflateResetKeep_1 = inflateResetKeep; +var inflateInit_1 = inflateInit; +var inflateInit2_1 = inflateInit2; +var inflate_2$1 = inflate$2; +var inflateEnd_1 = inflateEnd; +var inflateGetHeader_1 = inflateGetHeader; +var inflateSetDictionary_1 = inflateSetDictionary; +var inflateInfo = "pako inflate (from Nodeca project)"; + +/* Not implemented +module.exports.inflateCopy = inflateCopy; +module.exports.inflateGetDictionary = inflateGetDictionary; +module.exports.inflateMark = inflateMark; +module.exports.inflatePrime = inflatePrime; +module.exports.inflateSync = inflateSync; +module.exports.inflateSyncPoint = inflateSyncPoint; +module.exports.inflateUndermine = inflateUndermine; +*/ + +var inflate_1$2 = { + inflateReset: inflateReset_1, + inflateReset2: inflateReset2_1, + inflateResetKeep: inflateResetKeep_1, + inflateInit: inflateInit_1, + inflateInit2: inflateInit2_1, + inflate: inflate_2$1, + inflateEnd: inflateEnd_1, + inflateGetHeader: inflateGetHeader_1, + inflateSetDictionary: inflateSetDictionary_1, + inflateInfo: inflateInfo, +}; + +// (C) 1995-2013 Jean-loup Gailly and Mark Adler +// (C) 2014-2017 Vitaly Puzrin and Andrey Tupitsin +// +// This software is provided 'as-is', without any express or implied +// warranty. In no event will the authors be held liable for any damages +// arising from the use of this software. +// +// Permission is granted to anyone to use this software for any purpose, +// including commercial applications, and to alter it and redistribute it +// freely, subject to the following restrictions: +// +// 1. The origin of this software must not be misrepresented; you must not +// claim that you wrote the original software. If you use this software +// in a product, an acknowledgment in the product documentation would be +// appreciated but is not required. +// 2. Altered source versions must be plainly marked as such, and must not be +// misrepresented as being the original software. +// 3. This notice may not be removed or altered from any source distribution. + +function GZheader() { + /* true if compressed data believed to be text */ + this.text = 0; + /* modification time */ + this.time = 0; + /* extra flags (not used when writing a gzip file) */ + this.xflags = 0; + /* operating system */ + this.os = 0; + /* pointer to extra field or Z_NULL if none */ + this.extra = null; + /* extra field length (valid if extra != Z_NULL) */ + this.extra_len = 0; // Actually, we don't need it in JS, + // but leave for few code modifications + + // + // Setup limits is not necessary because in js we should not preallocate memory + // for inflate use constant limit in 65536 bytes + // + + /* space at extra (only when reading header) */ + // this.extra_max = 0; + /* pointer to zero-terminated file name or Z_NULL */ + this.name = ""; + /* space at name (only when reading header) */ + // this.name_max = 0; + /* pointer to zero-terminated comment or Z_NULL */ + this.comment = ""; + /* space at comment (only when reading header) */ + // this.comm_max = 0; + /* true if there was or will be a header crc */ + this.hcrc = 0; + /* true when done reading gzip header (not used when writing a gzip file) */ + this.done = false; +} + +var gzheader = GZheader; + +const toString = Object.prototype.toString; + +/* Public constants ==========================================================*/ +/* ===========================================================================*/ + +const { + Z_NO_FLUSH, + Z_FINISH, + Z_OK, + Z_STREAM_END, + Z_NEED_DICT, + Z_STREAM_ERROR, + Z_DATA_ERROR, + Z_MEM_ERROR, +} = constants$2; + +/* ===========================================================================*/ + +/** + * class Inflate + * + * Generic JS-style wrapper for zlib calls. If you don't need + * streaming behaviour - use more simple functions: [[inflate]] + * and [[inflateRaw]]. + */ + +/* internal + * inflate.chunks -> Array + * + * Chunks of output data, if [[Inflate#onData]] not overridden. + **/ + +/** + * Inflate.result -> Uint8Array|String + * + * Uncompressed result, generated by default [[Inflate#onData]] + * and [[Inflate#onEnd]] handlers. Filled after you push last chunk + * (call [[Inflate#push]] with `Z_FINISH` / `true` param). + */ + +/** + * Inflate.err -> Number + * + * Error code after inflate finished. 0 (Z_OK) on success. + * Should be checked if broken data possible. + */ + +/** + * Inflate.msg -> String + * + * Error message, if [[Inflate.err]] != 0 + */ + +/** + * new Inflate(options) + * - options (Object): zlib inflate options. + * + * Creates new inflator instance with specified params. Throws exception + * on bad params. Supported options: + * + * - `windowBits` + * - `dictionary` + * + * [http://zlib.net/manual.html#Advanced](http://zlib.net/manual.html#Advanced) + * for more information on these. + * + * Additional options, for internal needs: + * + * - `chunkSize` - size of generated data chunks (16K by default) + * - `raw` (Boolean) - do raw inflate + * - `to` (String) - if equal to 'string', then result will be converted + * from utf8 to utf16 (javascript) string. When string output requested, + * chunk length can differ from `chunkSize`, depending on content. + * + * By default, when no options set, autodetect deflate/gzip data format via + * wrapper header. + * + * ##### Example: + * + * ```javascript + * const pako = require('pako') + * const chunk1 = new Uint8Array([1,2,3,4,5,6,7,8,9]) + * const chunk2 = new Uint8Array([10,11,12,13,14,15,16,17,18,19]); + * + * const inflate = new pako.Inflate({ level: 3}); + * + * inflate.push(chunk1, false); + * inflate.push(chunk2, true); // true -> last chunk + * + * if (inflate.err) { throw new Error(inflate.err); } + * + * console.log(inflate.result); + * ``` + */ +function Inflate$1(options) { + this.options = common.assign({ + chunkSize: 1024 * 64, + windowBits: 15, + to: "", + }, options || {}); + + const opt = this.options; + + // Force window size for `raw` data, if not set directly, + // because we have no header for autodetect. + if (opt.raw && (opt.windowBits >= 0) && (opt.windowBits < 16)) { + opt.windowBits = -opt.windowBits; + if (opt.windowBits === 0) opt.windowBits = -15; + } + + // If `windowBits` not defined (and mode not raw) - set autodetect flag for gzip/deflate + if ( + (opt.windowBits >= 0) && (opt.windowBits < 16) && + !(options && options.windowBits) + ) { + opt.windowBits += 32; + } + + // Gzip header has no info about windows size, we can do autodetect only + // for deflate. So, if window size not set, force it to max when gzip possible + if ((opt.windowBits > 15) && (opt.windowBits < 48)) { + // bit 3 (16) -> gzipped data + // bit 4 (32) -> autodetect gzip/deflate + if ((opt.windowBits & 15) === 0) { + opt.windowBits |= 15; + } + } + + this.err = 0; // error code, if happens (0 = Z_OK) + this.msg = ""; // error message + this.ended = false; // used to avoid multiple onEnd() calls + this.chunks = []; // chunks of compressed data + + this.strm = new zstream(); + this.strm.avail_out = 0; + + let status = inflate_1$2.inflateInit2( + this.strm, + opt.windowBits, + ); + + if (status !== Z_OK) { + throw new Error(messages[status]); + } + + this.header = new gzheader(); + + inflate_1$2.inflateGetHeader(this.strm, this.header); + + // Setup dictionary + if (opt.dictionary) { + // Convert data if needed + if (typeof opt.dictionary === "string") { + opt.dictionary = strings.string2buf(opt.dictionary); + } else if (toString.call(opt.dictionary) === "[object ArrayBuffer]") { + opt.dictionary = new Uint8Array(opt.dictionary); + } + if (opt.raw) { //In raw mode we need to set the dictionary early + status = inflate_1$2.inflateSetDictionary(this.strm, opt.dictionary); + if (status !== Z_OK) { + throw new Error(messages[status]); + } + } + } +} + +/** + * Inflate#push(data[, flush_mode]) -> Boolean + * - data (Uint8Array|ArrayBuffer): input data + * - flush_mode (Number|Boolean): 0..6 for corresponding Z_NO_FLUSH..Z_TREE + * flush modes. See constants. Skipped or `false` means Z_NO_FLUSH, + * `true` means Z_FINISH. + * + * Sends input data to inflate pipe, generating [[Inflate#onData]] calls with + * new output chunks. Returns `true` on success. If end of stream detected, + * [[Inflate#onEnd]] will be called. + * + * `flush_mode` is not needed for normal operation, because end of stream + * detected automatically. You may try to use it for advanced things, but + * this functionality was not tested. + * + * On fail call [[Inflate#onEnd]] with error code and return false. + * + * ##### Example + * + * ```javascript + * push(chunk, false); // push one of data chunks + * ... + * push(chunk, true); // push last chunk + * ``` + */ +Inflate$1.prototype.push = function (data, flush_mode) { + const strm = this.strm; + const chunkSize = this.options.chunkSize; + const dictionary = this.options.dictionary; + let status, _flush_mode, last_avail_out; + + if (this.ended) return false; + + if (flush_mode === ~~flush_mode) _flush_mode = flush_mode; + else _flush_mode = flush_mode === true ? Z_FINISH : Z_NO_FLUSH; + + // Convert data if needed + if (toString.call(data) === "[object ArrayBuffer]") { + strm.input = new Uint8Array(data); + } else { + strm.input = data; + } + + strm.next_in = 0; + strm.avail_in = strm.input.length; + + for (;;) { + if (strm.avail_out === 0) { + strm.output = new Uint8Array(chunkSize); + strm.next_out = 0; + strm.avail_out = chunkSize; + } + + status = inflate_1$2.inflate(strm, _flush_mode); + + if (status === Z_NEED_DICT && dictionary) { + status = inflate_1$2.inflateSetDictionary(strm, dictionary); + + if (status === Z_OK) { + status = inflate_1$2.inflate(strm, _flush_mode); + } else if (status === Z_DATA_ERROR) { + // Replace code with more verbose + status = Z_NEED_DICT; + } + } + + // Skip snyc markers if more data follows and not raw mode + while ( + strm.avail_in > 0 && + status === Z_STREAM_END && + strm.state.wrap > 0 && + data[strm.next_in] !== 0 + ) { + inflate_1$2.inflateReset(strm); + status = inflate_1$2.inflate(strm, _flush_mode); + } + + switch (status) { + case Z_STREAM_ERROR: + case Z_DATA_ERROR: + case Z_NEED_DICT: + case Z_MEM_ERROR: + this.onEnd(status); + this.ended = true; + return false; + } + + // Remember real `avail_out` value, because we may patch out buffer content + // to align utf8 strings boundaries. + last_avail_out = strm.avail_out; + + if (strm.next_out) { + if (strm.avail_out === 0 || status === Z_STREAM_END) { + if (this.options.to === "string") { + let next_out_utf8 = strings.utf8border(strm.output, strm.next_out); + + let tail = strm.next_out - next_out_utf8; + let utf8str = strings.buf2string(strm.output, next_out_utf8); + + // move tail & realign counters + strm.next_out = tail; + strm.avail_out = chunkSize - tail; + if (tail) { + strm.output.set( + strm.output.subarray(next_out_utf8, next_out_utf8 + tail), + 0, + ); + } + + this.onData(utf8str); + } else { + this.onData( + strm.output.length === strm.next_out + ? strm.output + : strm.output.subarray(0, strm.next_out), + ); + } + } + } + + // Must repeat iteration if out buffer is full + if (status === Z_OK && last_avail_out === 0) continue; + + // Finalize if end of stream reached. + if (status === Z_STREAM_END) { + status = inflate_1$2.inflateEnd(this.strm); + this.onEnd(status); + this.ended = true; + return true; + } + + if (strm.avail_in === 0) break; + } + + return true; +}; + +/** + * Inflate#onData(chunk) -> Void + * - chunk (Uint8Array|String): output data. When string output requested, + * each chunk will be string. + * + * By default, stores data blocks in `chunks[]` property and glue + * those in `onEnd`. Override this handler, if you need another behaviour. + */ +Inflate$1.prototype.onData = function (chunk) { + this.chunks.push(chunk); +}; + +/** + * Inflate#onEnd(status) -> Void + * - status (Number): inflate status. 0 (Z_OK) on success, + * other if not. + * + * Called either after you tell inflate that the input stream is + * complete (Z_FINISH). By default - join collected chunks, + * free memory and fill `results` / `err` properties. + */ +Inflate$1.prototype.onEnd = function (status) { + // On success - join + if (status === Z_OK) { + if (this.options.to === "string") { + this.result = this.chunks.join(""); + } else { + this.result = common.flattenChunks(this.chunks); + } + } + this.chunks = []; + this.err = status; + this.msg = this.strm.msg; +}; + +/** + * inflate(data[, options]) -> Uint8Array|String + * - data (Uint8Array): input data to decompress. + * - options (Object): zlib inflate options. + * + * Decompress `data` with inflate/ungzip and `options`. Autodetect + * format via wrapper header by default. That's why we don't provide + * separate `ungzip` method. + * + * Supported options are: + * + * - windowBits + * + * [http://zlib.net/manual.html#Advanced](http://zlib.net/manual.html#Advanced) + * for more information. + * + * Sugar (options): + * + * - `raw` (Boolean) - say that we work with raw stream, if you don't wish to specify + * negative windowBits implicitly. + * - `to` (String) - if equal to 'string', then result will be converted + * from utf8 to utf16 (javascript) string. When string output requested, + * chunk length can differ from `chunkSize`, depending on content. + * + * ##### Example: + * + * ```javascript + * const pako = require('pako'); + * const input = pako.deflate(new Uint8Array([1,2,3,4,5,6,7,8,9])); + * let output; + * + * try { + * output = pako.inflate(input); + * } catch (err) { + * console.log(err); + * } + * ``` + */ +function inflate$1(input, options) { + const inflator = new Inflate$1(options); + + inflator.push(input); + + // That will never happens, if you don't cheat with options :) + if (inflator.err) throw inflator.msg || messages[inflator.err]; + + return inflator.result; +} + +/** + * inflateRaw(data[, options]) -> Uint8Array|String + * - data (Uint8Array): input data to decompress. + * - options (Object): zlib inflate options. + * + * The same as [[inflate]], but creates raw data, without wrapper + * (header and adler32 crc). + */ +function inflateRaw$1(input, options) { + options = options || {}; + options.raw = true; + return inflate$1(input, options); +} + +/** + * ungzip(data[, options]) -> Uint8Array|String + * - data (Uint8Array): input data to decompress. + * - options (Object): zlib inflate options. + * + * Just shortcut to [[inflate]], because it autodetects format + * by header.content. Done for convenience. + */ + +var Inflate_1$1 = Inflate$1; +var inflate_2 = inflate$1; +var inflateRaw_1$1 = inflateRaw$1; +var ungzip$1 = inflate$1; +var constants = constants$2; + +var inflate_1$1 = { + Inflate: Inflate_1$1, + inflate: inflate_2, + inflateRaw: inflateRaw_1$1, + ungzip: ungzip$1, + constants: constants, +}; + +const { Deflate, deflate, deflateRaw, gzip } = deflate_1$1; + +const { Inflate, inflate, inflateRaw, ungzip } = inflate_1$1; + +var Deflate_1 = Deflate; +var deflate_1 = deflate; +var deflateRaw_1 = deflateRaw; +var gzip_1 = gzip; +var Inflate_1 = Inflate; +var inflate_1 = inflate; +var inflateRaw_1 = inflateRaw; +var ungzip_1 = ungzip; +var constants_1 = constants$2; + +var pako = { + Deflate: Deflate_1, + deflate: deflate_1, + deflateRaw: deflateRaw_1, + gzip: gzip_1, + Inflate: Inflate_1, + inflate: inflate_1, + inflateRaw: inflateRaw_1, + ungzip: ungzip_1, + constants: constants_1, +}; + +// NOTE(@bartlomieju): Zstream is also exported here, even though it's not exported in regular dist file +export { + constants_1 as constants, + Deflate_1 as Deflate, + deflate_1 as deflate, + deflate_1$2 as zlib_deflate, + deflateRaw_1 as deflateRaw, + gzip_1 as gzip, + Inflate_1 as Inflate, + inflate_1 as inflate, + inflate_1$2 as zlib_inflate, + inflateRaw_1 as inflateRaw, + pako as default, + ungzip_1 as ungzip, + ZStream as Zstream, +}; diff --git a/tools/bench/mod.js b/ext/node/polyfills/_process/exiting.ts similarity index 54% rename from tools/bench/mod.js rename to ext/node/polyfills/_process/exiting.ts index 9d90818a5bfba6..8cc37eef838ba2 100644 --- a/tools/bench/mod.js +++ b/ext/node/polyfills/_process/exiting.ts @@ -1,4 +1,4 @@ // Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. -export * from "./rebench.js"; -export * from "./rebootstrap.js"; +// deno-lint-ignore prefer-const +export let _exiting = false; diff --git a/ext/node/polyfills/_process/process.ts b/ext/node/polyfills/_process/process.ts new file mode 100644 index 00000000000000..24a4ae1a2b4367 --- /dev/null +++ b/ext/node/polyfills/_process/process.ts @@ -0,0 +1,133 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. +// Copyright Joyent, Inc. and Node.js contributors. All rights reserved. MIT license. + +// The following are all the process APIs that don't depend on the stream module +// They have to be split this way to prevent a circular dependency + +import { build } from "internal:runtime/js/01_build.js"; +import { nextTick as _nextTick } from "internal:deno_node/polyfills/_next_tick.ts"; +import { _exiting } from "internal:deno_node/polyfills/_process/exiting.ts"; +import * as fs from "internal:runtime/js/30_fs.js"; + +/** Returns the operating system CPU architecture for which the Deno binary was compiled */ +export function arch(): string { + if (build.arch == "x86_64") { + return "x64"; + } else if (build.arch == "aarch64") { + return "arm64"; + } else { + throw Error("unreachable"); + } +} + +/** https://nodejs.org/api/process.html#process_process_chdir_directory */ +export const chdir = fs.chdir; + +/** https://nodejs.org/api/process.html#process_process_cwd */ +export const cwd = fs.cwd; + +/** https://nodejs.org/api/process.html#process_process_nexttick_callback_args */ +export const nextTick = _nextTick; + +/** Wrapper of Deno.env.get, which doesn't throw type error when + * the env name has "=" or "\0" in it. */ +function denoEnvGet(name: string) { + const perm = + Deno.permissions.querySync?.({ name: "env", variable: name }).state ?? + "granted"; // for Deno Deploy + // Returns undefined if the env permission is unavailable + if (perm !== "granted") { + return undefined; + } + try { + return Deno.env.get(name); + } catch (e) { + if (e instanceof TypeError) { + return undefined; + } + throw e; + } +} + +const OBJECT_PROTO_PROP_NAMES = Object.getOwnPropertyNames(Object.prototype); +/** + * https://nodejs.org/api/process.html#process_process_env + * Requires env permissions + */ +export const env: InstanceType & Record = + new Proxy(Object(), { + get: (target, prop) => { + if (typeof prop === "symbol") { + return target[prop]; + } + + const envValue = denoEnvGet(prop); + + if (envValue) { + return envValue; + } + + if (OBJECT_PROTO_PROP_NAMES.includes(prop)) { + return target[prop]; + } + + return envValue; + }, + ownKeys: () => Reflect.ownKeys(Deno.env.toObject()), + getOwnPropertyDescriptor: (_target, name) => { + const value = denoEnvGet(String(name)); + if (value) { + return { + enumerable: true, + configurable: true, + value, + }; + } + }, + set(_target, prop, value) { + Deno.env.set(String(prop), String(value)); + return true; // success + }, + has: (_target, prop) => typeof denoEnvGet(String(prop)) === "string", + }); + +/** + * https://nodejs.org/api/process.html#process_process_version + * + * This value is hard coded to latest stable release of Node, as + * some packages are checking it for compatibility. Previously + * it pointed to Deno version, but that led to incompability + * with some packages. + */ +export const version = "v18.12.1"; + +/** + * https://nodejs.org/api/process.html#process_process_versions + * + * This value is hard coded to latest stable release of Node, as + * some packages are checking it for compatibility. Previously + * it contained only output of `Deno.version`, but that led to incompability + * with some packages. Value of `v8` field is still taken from `Deno.version`. + */ +export const versions = { + node: "18.12.1", + uv: "1.43.0", + zlib: "1.2.11", + brotli: "1.0.9", + ares: "1.18.1", + modules: "108", + nghttp2: "1.47.0", + napi: "8", + llhttp: "6.0.10", + openssl: "3.0.7+quic", + cldr: "41.0", + icu: "71.1", + tz: "2022b", + unicode: "14.0", + ngtcp2: "0.8.1", + nghttp3: "0.7.0", + // Will be filled when calling "__bootstrapNodeProcess()", + deno: "", + v8: "", + typescript: "", +}; diff --git a/ext/node/polyfills/_process/stdio.mjs b/ext/node/polyfills/_process/stdio.mjs new file mode 100644 index 00000000000000..4e0173dfa73a8a --- /dev/null +++ b/ext/node/polyfills/_process/stdio.mjs @@ -0,0 +1,7 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. +// Copyright Joyent, Inc. and Node.js contributors. All rights reserved. MIT license. + +// Lazily initializes the actual stdio objects. +// This trick is necessary for avoiding circular dependencies between +// stream and process modules. +export const stdio = {}; diff --git a/ext/node/polyfills/_process/streams.mjs b/ext/node/polyfills/_process/streams.mjs new file mode 100644 index 00000000000000..b27f75e2d874b4 --- /dev/null +++ b/ext/node/polyfills/_process/streams.mjs @@ -0,0 +1,238 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. +// Copyright Joyent, Inc. and Node.js contributors. All rights reserved. MIT license. + +import { Buffer } from "internal:deno_node/polyfills/buffer.ts"; +import { + clearLine, + clearScreenDown, + cursorTo, + moveCursor, +} from "internal:deno_node/polyfills/internal/readline/callbacks.mjs"; +import { Duplex, Readable, Writable } from "internal:deno_node/polyfills/stream.ts"; +import { isWindows } from "internal:deno_node/polyfills/_util/os.ts"; +import { fs as fsConstants } from "internal:deno_node/polyfills/internal_binding/constants.ts"; +import * as files from "internal:runtime/js/40_files.js"; + +// https://github.com/nodejs/node/blob/00738314828074243c9a52a228ab4c68b04259ef/lib/internal/bootstrap/switches/is_main_thread.js#L41 +export function createWritableStdioStream(writer, name) { + const stream = new Writable({ + write(buf, enc, cb) { + if (!writer) { + this.destroy( + new Error(`Deno.${name} is not available in this environment`), + ); + return; + } + writer.writeSync(buf instanceof Uint8Array ? buf : Buffer.from(buf, enc)); + cb(); + }, + destroy(err, cb) { + cb(err); + this._undestroy(); + if (!this._writableState.emitClose) { + nextTick(() => this.emit("close")); + } + }, + }); + stream.fd = writer?.rid ?? -1; + stream.destroySoon = stream.destroy; + stream._isStdio = true; + stream.once("close", () => writer?.close()); + Object.defineProperties(stream, { + columns: { + enumerable: true, + configurable: true, + get: () => + Deno.isatty?.(writer?.rid) ? Deno.consoleSize?.().columns : undefined, + }, + rows: { + enumerable: true, + configurable: true, + get: () => + Deno.isatty?.(writer?.rid) ? Deno.consoleSize?.().rows : undefined, + }, + isTTY: { + enumerable: true, + configurable: true, + get: () => Deno.isatty?.(writer?.rid), + }, + getWindowSize: { + enumerable: true, + configurable: true, + value: () => + Deno.isatty?.(writer?.rid) + ? Object.values(Deno.consoleSize?.()) + : undefined, + }, + }); + + if (Deno.isatty?.(writer?.rid)) { + // These belong on tty.WriteStream(), but the TTY streams currently have + // following problems: + // 1. Using them here introduces a circular dependency. + // 2. Creating a net.Socket() from a fd is not currently supported. + stream.cursorTo = function (x, y, callback) { + return cursorTo(this, x, y, callback); + }; + + stream.moveCursor = function (dx, dy, callback) { + return moveCursor(this, dx, dy, callback); + }; + + stream.clearLine = function (dir, callback) { + return clearLine(this, dir, callback); + }; + + stream.clearScreenDown = function (callback) { + return clearScreenDown(this, callback); + }; + } + + return stream; +} + +// TODO(PolarETech): This function should be replaced by +// `guessHandleType()` in "../internal_binding/util.ts". +// https://github.com/nodejs/node/blob/v18.12.1/src/node_util.cc#L257 +function _guessStdinType(fd) { + if (typeof fd !== "number" || fd < 0) return "UNKNOWN"; + if (Deno.isatty?.(fd)) return "TTY"; + + try { + const fileInfo = Deno.fstatSync?.(fd); + + // https://github.com/nodejs/node/blob/v18.12.1/deps/uv/src/unix/tty.c#L333 + if (!isWindows) { + switch (fileInfo.mode & fsConstants.S_IFMT) { + case fsConstants.S_IFREG: + case fsConstants.S_IFCHR: + return "FILE"; + case fsConstants.S_IFIFO: + return "PIPE"; + case fsConstants.S_IFSOCK: + // TODO(PolarETech): Need a better way to identify "TCP". + // Currently, unable to exclude UDP. + return "TCP"; + default: + return "UNKNOWN"; + } + } + + // https://github.com/nodejs/node/blob/v18.12.1/deps/uv/src/win/handle.c#L31 + if (fileInfo.isFile) { + // TODO(PolarETech): Need a better way to identify a piped stdin on Windows. + // On Windows, `Deno.fstatSync(rid).isFile` returns true even for a piped stdin. + // Therefore, a piped stdin cannot be distinguished from a file by this property. + // The mtime, atime, and birthtime of the file are "2339-01-01T00:00:00.000Z", + // so use the property as a workaround. + if (fileInfo.birthtime.valueOf() === 11644473600000) return "PIPE"; + return "FILE"; + } + } catch (e) { + // TODO(PolarETech): Need a better way to identify a character file on Windows. + // "EISDIR" error occurs when stdin is "null" on Windows, + // so use the error as a workaround. + if (isWindows && e.code === "EISDIR") return "FILE"; + } + + return "UNKNOWN"; +} + +const _read = function (size) { + const p = Buffer.alloc(size || 16 * 1024); + files.stdin?.read(p).then((length) => { + this.push(length === null ? null : p.slice(0, length)); + }, (error) => { + this.destroy(error); + }); +}; + +/** https://nodejs.org/api/process.html#process_process_stdin */ +// https://github.com/nodejs/node/blob/v18.12.1/lib/internal/bootstrap/switches/is_main_thread.js#L189 +/** Create process.stdin */ +export const initStdin = () => { + const fd = files.stdin?.rid; + let stdin; + const stdinType = _guessStdinType(fd); + + switch (stdinType) { + case "FILE": { + // Since `fs.ReadStream` cannot be imported before process initialization, + // use `Readable` instead. + // https://github.com/nodejs/node/blob/v18.12.1/lib/internal/bootstrap/switches/is_main_thread.js#L200 + // https://github.com/nodejs/node/blob/v18.12.1/lib/internal/fs/streams.js#L148 + stdin = new Readable({ + highWaterMark: 64 * 1024, + autoDestroy: false, + read: _read, + }); + break; + } + case "TTY": + case "PIPE": + case "TCP": { + // TODO(PolarETech): + // For TTY, `new Duplex()` should be replaced `new tty.ReadStream()` if possible. + // There are two problems that need to be resolved. + // 1. Using them here introduces a circular dependency. + // 2. Creating a tty.ReadStream() is not currently supported. + // https://github.com/nodejs/node/blob/v18.12.1/lib/internal/bootstrap/switches/is_main_thread.js#L194 + // https://github.com/nodejs/node/blob/v18.12.1/lib/tty.js#L47 + + // For PIPE and TCP, `new Duplex()` should be replaced `new net.Socket()` if possible. + // There are two problems that need to be resolved. + // 1. Using them here introduces a circular dependency. + // 2. Creating a net.Socket() from a fd is not currently supported. + // https://github.com/nodejs/node/blob/v18.12.1/lib/internal/bootstrap/switches/is_main_thread.js#L206 + // https://github.com/nodejs/node/blob/v18.12.1/lib/net.js#L329 + stdin = new Duplex({ + readable: stdinType === "TTY" ? undefined : true, + writable: stdinType === "TTY" ? undefined : false, + readableHighWaterMark: stdinType === "TTY" ? 0 : undefined, + allowHalfOpen: false, + emitClose: false, + autoDestroy: true, + decodeStrings: false, + read: _read, + }); + + if (stdinType !== "TTY") { + // Make sure the stdin can't be `.end()`-ed + stdin._writableState.ended = true; + } + break; + } + default: { + // Provide a dummy contentless input for e.g. non-console + // Windows applications. + stdin = new Readable({ read() {} }); + stdin.push(null); + } + } + + stdin.on("close", () => files.stdin?.close()); + stdin.fd = files.stdin?.rid ?? -1; + Object.defineProperty(stdin, "isTTY", { + enumerable: true, + configurable: true, + get() { + return Deno.isatty?.(Deno.stdin.rid); + }, + }); + stdin._isRawMode = false; + stdin.setRawMode = (enable) => { + files.stdin?.setRaw?.(enable); + stdin._isRawMode = enable; + return stdin; + }; + Object.defineProperty(stdin, "isRaw", { + enumerable: true, + configurable: true, + get() { + return stdin._isRawMode; + }, + }); + + return stdin; +}; + diff --git a/ext/node/polyfills/_readline.d.ts b/ext/node/polyfills/_readline.d.ts new file mode 100644 index 00000000000000..e3caea92bf1fa2 --- /dev/null +++ b/ext/node/polyfills/_readline.d.ts @@ -0,0 +1,655 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. +// deno-lint-ignore-file no-explicit-any + +// Forked from https://github.com/DefinitelyTyped/DefinitelyTyped/blob/cd61f5b4d3d143108569ec3f88adc0eb34b961c4/types/node/readline.d.ts + +import { + Abortable, + EventEmitter, +} from "internal:deno_node/polyfills/_events.d.ts"; +import * as promises from "internal:deno_node/polyfills/readline/promises.ts"; +import { + ReadableStream, + WritableStream, +} from "internal:deno_node/polyfills/_global.d.ts"; +import { Buffer } from "internal:deno_node/polyfills/buffer.ts"; +import type { + AsyncCompleter, + Completer, + CompleterResult, + ReadLineOptions, +} from "internal:deno_node/polyfills/_readline_shared_types.d.ts"; + +/** + * The `readline` module provides an interface for reading data from a `Readable` stream (such as `process.stdin`) one line at a time. + * + * To use the promise-based APIs: + * + * Once this code is invoked, the Node.js application will not terminate until the`readline.Interface` is closed because the interface waits for data to be + * received on the `input` stream. + * @see [source](https://github.com/nodejs/node/blob/v18.0.0/lib/readline.js) + */ +export { promises }; +export interface Key { + sequence?: string | undefined; + name?: string | undefined; + ctrl?: boolean | undefined; + meta?: boolean | undefined; + shift?: boolean | undefined; +} +/** + * Instances of the `readline.Interface` class are constructed using the`readline.createInterface()` method. Every instance is associated with a + * single `input` `Readable` stream and a single `output` `Writable` stream. + * The `output` stream is used to print prompts for user input that arrives on, + * and is read from, the `input` stream. + * @since v0.1.104 + */ +export class Interface extends EventEmitter { + readonly terminal: boolean; + /** + * The current input data being processed by node. + * + * This can be used when collecting input from a TTY stream to retrieve the + * current value that has been processed thus far, prior to the `line` event + * being emitted. Once the `line` event has been emitted, this property will + * be an empty string. + * + * Be aware that modifying the value during the instance runtime may have + * unintended consequences if `rl.cursor` is not also controlled. + * + * **If not using a TTY stream for input, use the `'line'` event.** + * + * One possible use case would be as follows: + * + * ```js + * const values = ['lorem ipsum', 'dolor sit amet']; + * const rl = readline.createInterface(process.stdin); + * const showResults = debounce(() => { + * console.log( + * '\n', + * values.filter((val) => val.startsWith(rl.line)).join(' ') + * ); + * }, 300); + * process.stdin.on('keypress', (c, k) => { + * showResults(); + * }); + * ``` + * @since v0.1.98 + */ + readonly line: string; + /** + * The cursor position relative to `rl.line`. + * + * This will track where the current cursor lands in the input string, when + * reading input from a TTY stream. The position of cursor determines the + * portion of the input string that will be modified as input is processed, + * as well as the column where the terminal caret will be rendered. + * @since v0.1.98 + */ + readonly cursor: number; + /** + * NOTE: According to the documentation: + * + * > Instances of the `readline.Interface` class are constructed using the + * > `readline.createInterface()` method. + * + * @see https://nodejs.org/dist/latest-v10.x/docs/api/readline.html#readline_class_interface + */ + protected constructor( + input: ReadableStream, + output?: WritableStream, + completer?: Completer | AsyncCompleter, + terminal?: boolean, + ); + /** + * NOTE: According to the documentation: + * + * > Instances of the `readline.Interface` class are constructed using the + * > `readline.createInterface()` method. + * + * @see https://nodejs.org/dist/latest-v10.x/docs/api/readline.html#readline_class_interface + */ + protected constructor(options: ReadLineOptions); + /** + * The `rl.getPrompt()` method returns the current prompt used by `rl.prompt()`. + * @since v15.3.0 + * @return the current prompt string + */ + getPrompt(): string; + /** + * The `rl.setPrompt()` method sets the prompt that will be written to `output`whenever `rl.prompt()` is called. + * @since v0.1.98 + */ + setPrompt(prompt: string): void; + /** + * The `rl.prompt()` method writes the `readline.Interface` instances configured`prompt` to a new line in `output` in order to provide a user with a new + * location at which to provide input. + * + * When called, `rl.prompt()` will resume the `input` stream if it has been + * paused. + * + * If the `readline.Interface` was created with `output` set to `null` or`undefined` the prompt is not written. + * @since v0.1.98 + * @param preserveCursor If `true`, prevents the cursor placement from being reset to `0`. + */ + prompt(preserveCursor?: boolean): void; + /** + * The `rl.question()` method displays the `query` by writing it to the `output`, + * waits for user input to be provided on `input`, then invokes the `callback`function passing the provided input as the first argument. + * + * When called, `rl.question()` will resume the `input` stream if it has been + * paused. + * + * If the `readline.Interface` was created with `output` set to `null` or`undefined` the `query` is not written. + * + * The `callback` function passed to `rl.question()` does not follow the typical + * pattern of accepting an `Error` object or `null` as the first argument. + * The `callback` is called with the provided answer as the only argument. + * + * Example usage: + * + * ```js + * rl.question('What is your favorite food? ', (answer) => { + * console.log(`Oh, so your favorite food is ${answer}`); + * }); + * ``` + * + * Using an `AbortController` to cancel a question. + * + * ```js + * const ac = new AbortController(); + * const signal = ac.signal; + * + * rl.question('What is your favorite food? ', { signal }, (answer) => { + * console.log(`Oh, so your favorite food is ${answer}`); + * }); + * + * signal.addEventListener('abort', () => { + * console.log('The food question timed out'); + * }, { once: true }); + * + * setTimeout(() => ac.abort(), 10000); + * ``` + * + * If this method is invoked as it's util.promisify()ed version, it returns a + * Promise that fulfills with the answer. If the question is canceled using + * an `AbortController` it will reject with an `AbortError`. + * + * ```js + * const util = require('util'); + * const question = util.promisify(rl.question).bind(rl); + * + * async function questionExample() { + * try { + * const answer = await question('What is you favorite food? '); + * console.log(`Oh, so your favorite food is ${answer}`); + * } catch (err) { + * console.error('Question rejected', err); + * } + * } + * questionExample(); + * ``` + * @since v0.3.3 + * @param query A statement or query to write to `output`, prepended to the prompt. + * @param callback A callback function that is invoked with the user's input in response to the `query`. + */ + question(query: string, callback: (answer: string) => void): void; + question( + query: string, + options: Abortable, + callback: (answer: string) => void, + ): void; + /** + * The `rl.pause()` method pauses the `input` stream, allowing it to be resumed + * later if necessary. + * + * Calling `rl.pause()` does not immediately pause other events (including`'line'`) from being emitted by the `readline.Interface` instance. + * @since v0.3.4 + */ + pause(): this; + /** + * The `rl.resume()` method resumes the `input` stream if it has been paused. + * @since v0.3.4 + */ + resume(): this; + /** + * The `rl.close()` method closes the `readline.Interface` instance and + * relinquishes control over the `input` and `output` streams. When called, + * the `'close'` event will be emitted. + * + * Calling `rl.close()` does not immediately stop other events (including `'line'`) + * from being emitted by the `readline.Interface` instance. + * @since v0.1.98 + */ + close(): void; + /** + * The `rl.write()` method will write either `data` or a key sequence identified + * by `key` to the `output`. The `key` argument is supported only if `output` is + * a `TTY` text terminal. See `TTY keybindings` for a list of key + * combinations. + * + * If `key` is specified, `data` is ignored. + * + * When called, `rl.write()` will resume the `input` stream if it has been + * paused. + * + * If the `readline.Interface` was created with `output` set to `null` or`undefined` the `data` and `key` are not written. + * + * ```js + * rl.write('Delete this!'); + * // Simulate Ctrl+U to delete the line written previously + * rl.write(null, { ctrl: true, name: 'u' }); + * ``` + * + * The `rl.write()` method will write the data to the `readline` `Interface`'s`input`_as if it were provided by the user_. + * @since v0.1.98 + */ + write(data: string | Buffer, key?: Key): void; + write(data: undefined | null | string | Buffer, key: Key): void; + /** + * Returns the real position of the cursor in relation to the input + * prompt + string. Long input (wrapping) strings, as well as multiple + * line prompts are included in the calculations. + * @since v13.5.0, v12.16.0 + */ + getCursorPos(): CursorPos; + /** + * events.EventEmitter + * 1. close + * 2. line + * 3. pause + * 4. resume + * 5. SIGCONT + * 6. SIGINT + * 7. SIGTSTP + * 8. history + */ + addListener(event: string, listener: (...args: any[]) => void): this; + addListener(event: "close", listener: () => void): this; + addListener(event: "line", listener: (input: string) => void): this; + addListener(event: "pause", listener: () => void): this; + addListener(event: "resume", listener: () => void): this; + addListener(event: "SIGCONT", listener: () => void): this; + addListener(event: "SIGINT", listener: () => void): this; + addListener(event: "SIGTSTP", listener: () => void): this; + addListener(event: "history", listener: (history: string[]) => void): this; + emit(event: string | symbol, ...args: any[]): boolean; + emit(event: "close"): boolean; + emit(event: "line", input: string): boolean; + emit(event: "pause"): boolean; + emit(event: "resume"): boolean; + emit(event: "SIGCONT"): boolean; + emit(event: "SIGINT"): boolean; + emit(event: "SIGTSTP"): boolean; + emit(event: "history", history: string[]): boolean; + on(event: string, listener: (...args: any[]) => void): this; + on(event: "close", listener: () => void): this; + on(event: "line", listener: (input: string) => void): this; + on(event: "pause", listener: () => void): this; + on(event: "resume", listener: () => void): this; + on(event: "SIGCONT", listener: () => void): this; + on(event: "SIGINT", listener: () => void): this; + on(event: "SIGTSTP", listener: () => void): this; + on(event: "history", listener: (history: string[]) => void): this; + once(event: string, listener: (...args: any[]) => void): this; + once(event: "close", listener: () => void): this; + once(event: "line", listener: (input: string) => void): this; + once(event: "pause", listener: () => void): this; + once(event: "resume", listener: () => void): this; + once(event: "SIGCONT", listener: () => void): this; + once(event: "SIGINT", listener: () => void): this; + once(event: "SIGTSTP", listener: () => void): this; + once(event: "history", listener: (history: string[]) => void): this; + prependListener(event: string, listener: (...args: any[]) => void): this; + prependListener(event: "close", listener: () => void): this; + prependListener(event: "line", listener: (input: string) => void): this; + prependListener(event: "pause", listener: () => void): this; + prependListener(event: "resume", listener: () => void): this; + prependListener(event: "SIGCONT", listener: () => void): this; + prependListener(event: "SIGINT", listener: () => void): this; + prependListener(event: "SIGTSTP", listener: () => void): this; + prependListener( + event: "history", + listener: (history: string[]) => void, + ): this; + prependOnceListener(event: string, listener: (...args: any[]) => void): this; + prependOnceListener(event: "close", listener: () => void): this; + prependOnceListener(event: "line", listener: (input: string) => void): this; + prependOnceListener(event: "pause", listener: () => void): this; + prependOnceListener(event: "resume", listener: () => void): this; + prependOnceListener(event: "SIGCONT", listener: () => void): this; + prependOnceListener(event: "SIGINT", listener: () => void): this; + prependOnceListener(event: "SIGTSTP", listener: () => void): this; + prependOnceListener( + event: "history", + listener: (history: string[]) => void, + ): this; + [Symbol.asyncIterator](): AsyncIterableIterator; +} +export type ReadLine = Interface; // type forwarded for backwards compatibility +export { AsyncCompleter, Completer, CompleterResult, ReadLineOptions }; +/** + * The `readline.createInterface()` method creates a new `readline.Interface`instance. + * + * ```js + * const readline = require('readline'); + * const rl = readline.createInterface({ + * input: process.stdin, + * output: process.stdout + * }); + * ``` + * + * Once the `readline.Interface` instance is created, the most common case is to + * listen for the `'line'` event: + * + * ```js + * rl.on('line', (line) => { + * console.log(`Received: ${line}`); + * }); + * ``` + * + * If `terminal` is `true` for this instance then the `output` stream will get + * the best compatibility if it defines an `output.columns` property and emits + * a `'resize'` event on the `output` if or when the columns ever change + * (`process.stdout` does this automatically when it is a TTY). + * + * When creating a `readline.Interface` using `stdin` as input, the program + * will not terminate until it receives `EOF` (Ctrl+D on + * Linux/macOS, Ctrl+Z followed by Return on + * Windows). + * If you want your application to exit without waiting for user input, you can `unref()` the standard input stream: + * + * ```js + * process.stdin.unref(); + * ``` + * @since v0.1.98 + */ +export function createInterface( + input: ReadableStream, + output?: WritableStream, + completer?: Completer | AsyncCompleter, + terminal?: boolean, +): Interface; +export function createInterface(options: ReadLineOptions): Interface; +/** + * The `readline.emitKeypressEvents()` method causes the given `Readable` stream to begin emitting `'keypress'` events corresponding to received input. + * + * Optionally, `interface` specifies a `readline.Interface` instance for which + * autocompletion is disabled when copy-pasted input is detected. + * + * If the `stream` is a `TTY`, then it must be in raw mode. + * + * This is automatically called by any readline instance on its `input` if the`input` is a terminal. Closing the `readline` instance does not stop + * the `input` from emitting `'keypress'` events. + * + * ```js + * readline.emitKeypressEvents(process.stdin); + * if (process.stdin.isTTY) + * process.stdin.setRawMode(true); + * ``` + * + * ## Example: Tiny CLI + * + * The following example illustrates the use of `readline.Interface` class to + * implement a small command-line interface: + * + * ```js + * const readline = require('readline'); + * const rl = readline.createInterface({ + * input: process.stdin, + * output: process.stdout, + * prompt: 'OHAI> ' + * }); + * + * rl.prompt(); + * + * rl.on('line', (line) => { + * switch (line.trim()) { + * case 'hello': + * console.log('world!'); + * break; + * default: + * console.log(`Say what? I might have heard '${line.trim()}'`); + * break; + * } + * rl.prompt(); + * }).on('close', () => { + * console.log('Have a great day!'); + * process.exit(0); + * }); + * ``` + * + * ## Example: Read file stream line-by-Line + * + * A common use case for `readline` is to consume an input file one line at a + * time. The easiest way to do so is leveraging the `fs.ReadStream` API as + * well as a `for await...of` loop: + * + * ```js + * const fs = require('fs'); + * const readline = require('readline'); + * + * async function processLineByLine() { + * const fileStream = fs.createReadStream('input.txt'); + * + * const rl = readline.createInterface({ + * input: fileStream, + * crlfDelay: Infinity + * }); + * // Note: we use the crlfDelay option to recognize all instances of CR LF + * // ('\r\n') in input.txt as a single line break. + * + * for await (const line of rl) { + * // Each line in input.txt will be successively available here as `line`. + * console.log(`Line from file: ${line}`); + * } + * } + * + * processLineByLine(); + * ``` + * + * Alternatively, one could use the `'line'` event: + * + * ```js + * const fs = require('fs'); + * const readline = require('readline'); + * + * const rl = readline.createInterface({ + * input: fs.createReadStream('sample.txt'), + * crlfDelay: Infinity + * }); + * + * rl.on('line', (line) => { + * console.log(`Line from file: ${line}`); + * }); + * ``` + * + * Currently, `for await...of` loop can be a bit slower. If `async` / `await`flow and speed are both essential, a mixed approach can be applied: + * + * ```js + * const { once } = require('events'); + * const { createReadStream } = require('fs'); + * const { createInterface } = require('readline'); + * + * (async function processLineByLine() { + * try { + * const rl = createInterface({ + * input: createReadStream('big-file.txt'), + * crlfDelay: Infinity + * }); + * + * rl.on('line', (line) => { + * // Process the line. + * }); + * + * await once(rl, 'close'); + * + * console.log('File processed.'); + * } catch (err) { + * console.error(err); + * } + * })(); + * ``` + * @since v0.7.7 + */ +export function emitKeypressEvents( + stream: ReadableStream, + readlineInterface?: Interface, +): void; +export type Direction = -1 | 0 | 1; +export interface CursorPos { + rows: number; + cols: number; +} +/** + * The `readline.clearLine()` method clears current line of given `TTY` stream + * in a specified direction identified by `dir`. + * @since v0.7.7 + * @param callback Invoked once the operation completes. + * @return `false` if `stream` wishes for the calling code to wait for the `'drain'` event to be emitted before continuing to write additional data; otherwise `true`. + */ +export function clearLine( + stream: WritableStream, + dir: Direction, + callback?: () => void, +): boolean; +/** + * The `readline.clearScreenDown()` method clears the given `TTY` stream from + * the current position of the cursor down. + * @since v0.7.7 + * @param callback Invoked once the operation completes. + * @return `false` if `stream` wishes for the calling code to wait for the `'drain'` event to be emitted before continuing to write additional data; otherwise `true`. + */ +export function clearScreenDown( + stream: WritableStream, + callback?: () => void, +): boolean; +/** + * The `readline.cursorTo()` method moves cursor to the specified position in a + * given `TTY` `stream`. + * @since v0.7.7 + * @param callback Invoked once the operation completes. + * @return `false` if `stream` wishes for the calling code to wait for the `'drain'` event to be emitted before continuing to write additional data; otherwise `true`. + */ +export function cursorTo( + stream: WritableStream, + x: number, + y?: number, + callback?: () => void, +): boolean; +/** + * The `readline.moveCursor()` method moves the cursor _relative_ to its current + * position in a given `TTY` `stream`. + * + * ## Example: Tiny CLI + * + * The following example illustrates the use of `readline.Interface` class to + * implement a small command-line interface: + * + * ```js + * const readline = require('readline'); + * const rl = readline.createInterface({ + * input: process.stdin, + * output: process.stdout, + * prompt: 'OHAI> ' + * }); + * + * rl.prompt(); + * + * rl.on('line', (line) => { + * switch (line.trim()) { + * case 'hello': + * console.log('world!'); + * break; + * default: + * console.log(`Say what? I might have heard '${line.trim()}'`); + * break; + * } + * rl.prompt(); + * }).on('close', () => { + * console.log('Have a great day!'); + * process.exit(0); + * }); + * ``` + * + * ## Example: Read file stream line-by-Line + * + * A common use case for `readline` is to consume an input file one line at a + * time. The easiest way to do so is leveraging the `fs.ReadStream` API as + * well as a `for await...of` loop: + * + * ```js + * const fs = require('fs'); + * const readline = require('readline'); + * + * async function processLineByLine() { + * const fileStream = fs.createReadStream('input.txt'); + * + * const rl = readline.createInterface({ + * input: fileStream, + * crlfDelay: Infinity + * }); + * // Note: we use the crlfDelay option to recognize all instances of CR LF + * // ('\r\n') in input.txt as a single line break. + * + * for await (const line of rl) { + * // Each line in input.txt will be successively available here as `line`. + * console.log(`Line from file: ${line}`); + * } + * } + * + * processLineByLine(); + * ``` + * + * Alternatively, one could use the `'line'` event: + * + * ```js + * const fs = require('fs'); + * const readline = require('readline'); + * + * const rl = readline.createInterface({ + * input: fs.createReadStream('sample.txt'), + * crlfDelay: Infinity + * }); + * + * rl.on('line', (line) => { + * console.log(`Line from file: ${line}`); + * }); + * ``` + * + * Currently, `for await...of` loop can be a bit slower. If `async` / `await`flow and speed are both essential, a mixed approach can be applied: + * + * ```js + * const { once } = require('events'); + * const { createReadStream } = require('fs'); + * const { createInterface } = require('readline'); + * + * (async function processLineByLine() { + * try { + * const rl = createInterface({ + * input: createReadStream('big-file.txt'), + * crlfDelay: Infinity + * }); + * + * rl.on('line', (line) => { + * // Process the line. + * }); + * + * await once(rl, 'close'); + * + * console.log('File processed.'); + * } catch (err) { + * console.error(err); + * } + * })(); + * ``` + * @since v0.7.7 + * @param callback Invoked once the operation completes. + * @return `false` if `stream` wishes for the calling code to wait for the `'drain'` event to be emitted before continuing to write additional data; otherwise `true`. + */ +export function moveCursor( + stream: WritableStream, + dx: number, + dy: number, + callback?: () => void, +): boolean; diff --git a/ext/node/polyfills/_readline.mjs b/ext/node/polyfills/_readline.mjs new file mode 100644 index 00000000000000..0665dbcf316d36 --- /dev/null +++ b/ext/node/polyfills/_readline.mjs @@ -0,0 +1,486 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. +// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. + +// deno-lint-ignore-file camelcase + +import { + clearLine, + clearScreenDown, + cursorTo, + moveCursor, +} from "internal:deno_node/polyfills/internal/readline/callbacks.mjs"; +import { emitKeypressEvents } from "internal:deno_node/polyfills/internal/readline/emitKeypressEvents.mjs"; +import promises from "internal:deno_node/polyfills/readline/promises.ts"; +import { validateAbortSignal } from "internal:deno_node/polyfills/internal/validators.mjs"; +import { promisify } from "internal:deno_node/polyfills/internal/util.mjs"; +import { AbortError } from "internal:deno_node/polyfills/internal/errors.ts"; +import process from "internal:deno_node/polyfills/process.ts"; + +import { + Interface as _Interface, + InterfaceConstructor, + kAddHistory, + kDecoder, + kDeleteLeft, + kDeleteLineLeft, + kDeleteLineRight, + kDeleteRight, + kDeleteWordLeft, + kDeleteWordRight, + kGetDisplayPos, + kHistoryNext, + kHistoryPrev, + kInsertString, + kLine, + kLine_buffer, + kMoveCursor, + kNormalWrite, + kOldPrompt, + kOnLine, + kPreviousKey, + kPrompt, + kQuestion, + kQuestionCallback, + kQuestionCancel, + kRefreshLine, + kSawKeyPress, + kSawReturnAt, + kSetRawMode, + kTabComplete, + kTabCompleter, + kTtyWrite, + kWordLeft, + kWordRight, + kWriteToOutput, +} from "internal:deno_node/polyfills/internal/readline/interface.mjs"; + +function Interface(input, output, completer, terminal) { + if (!(this instanceof Interface)) { + return new Interface(input, output, completer, terminal); + } + + if ( + input?.input && + typeof input.completer === "function" && input.completer.length !== 2 + ) { + const { completer } = input; + input.completer = (v, cb) => cb(null, completer(v)); + } else if (typeof completer === "function" && completer.length !== 2) { + const realCompleter = completer; + completer = (v, cb) => cb(null, realCompleter(v)); + } + + // NOTE(bartlomieju): in Node this is `FunctionPrototypeCall(...)`, + // but trying to do `Function.prototype.call()` somehow doesn't work here + // /shrug + InterfaceConstructor.bind( + this, + )( + input, + output, + completer, + terminal, + ); + if (process.env.TERM === "dumb") { + this._ttyWrite = _ttyWriteDumb.bind(this); + } +} + +Object.setPrototypeOf(Interface.prototype, _Interface.prototype); +Object.setPrototypeOf(Interface, _Interface); + +/** + * Displays `query` by writing it to the `output`. + * @param {string} query + * @param {{ signal?: AbortSignal; }} [options] + * @param {Function} cb + * @returns {void} + */ +Interface.prototype.question = function question(query, options, cb) { + cb = typeof options === "function" ? options : cb; + options = typeof options === "object" && options !== null ? options : {}; + + if (options.signal) { + validateAbortSignal(options.signal, "options.signal"); + if (options.signal.aborted) { + return; + } + + const onAbort = () => { + this[kQuestionCancel](); + }; + options.signal.addEventListener("abort", onAbort, { once: true }); + const cleanup = () => { + options.signal.removeEventListener(onAbort); + }; + cb = typeof cb === "function" + ? (answer) => { + cleanup(); + return cb(answer); + } + : cleanup; + } + + if (typeof cb === "function") { + this[kQuestion](query, cb); + } +}; +Interface.prototype.question[promisify.custom] = function question( + query, + options, +) { + options = typeof options === "object" && options !== null ? options : {}; + + if (options.signal && options.signal.aborted) { + return Promise.reject( + new AbortError(undefined, { cause: options.signal.reason }), + ); + } + + return new Promise((resolve, reject) => { + let cb = resolve; + + if (options.signal) { + const onAbort = () => { + reject(new AbortError(undefined, { cause: options.signal.reason })); + }; + options.signal.addEventListener("abort", onAbort, { once: true }); + cb = (answer) => { + options.signal.removeEventListener("abort", onAbort); + resolve(answer); + }; + } + + this.question(query, options, cb); + }); +}; + +/** + * Creates a new `readline.Interface` instance. + * @param {Readable | { + * input: Readable; + * output: Writable; + * completer?: Function; + * terminal?: boolean; + * history?: string[]; + * historySize?: number; + * removeHistoryDuplicates?: boolean; + * prompt?: string; + * crlfDelay?: number; + * escapeCodeTimeout?: number; + * tabSize?: number; + * signal?: AbortSignal; + * }} input + * @param {Writable} [output] + * @param {Function} [completer] + * @param {boolean} [terminal] + * @returns {Interface} + */ +function createInterface(input, output, completer, terminal) { + return new Interface(input, output, completer, terminal); +} + +Object.defineProperties(Interface.prototype, { + // Redirect internal prototype methods to the underscore notation for backward + // compatibility. + [kSetRawMode]: { + get() { + return this._setRawMode; + }, + }, + [kOnLine]: { + get() { + return this._onLine; + }, + }, + [kWriteToOutput]: { + get() { + return this._writeToOutput; + }, + }, + [kAddHistory]: { + get() { + return this._addHistory; + }, + }, + [kRefreshLine]: { + get() { + return this._refreshLine; + }, + }, + [kNormalWrite]: { + get() { + return this._normalWrite; + }, + }, + [kInsertString]: { + get() { + return this._insertString; + }, + }, + [kTabComplete]: { + get() { + return this._tabComplete; + }, + }, + [kWordLeft]: { + get() { + return this._wordLeft; + }, + }, + [kWordRight]: { + get() { + return this._wordRight; + }, + }, + [kDeleteLeft]: { + get() { + return this._deleteLeft; + }, + }, + [kDeleteRight]: { + get() { + return this._deleteRight; + }, + }, + [kDeleteWordLeft]: { + get() { + return this._deleteWordLeft; + }, + }, + [kDeleteWordRight]: { + get() { + return this._deleteWordRight; + }, + }, + [kDeleteLineLeft]: { + get() { + return this._deleteLineLeft; + }, + }, + [kDeleteLineRight]: { + get() { + return this._deleteLineRight; + }, + }, + [kLine]: { + get() { + return this._line; + }, + }, + [kHistoryNext]: { + get() { + return this._historyNext; + }, + }, + [kHistoryPrev]: { + get() { + return this._historyPrev; + }, + }, + [kGetDisplayPos]: { + get() { + return this._getDisplayPos; + }, + }, + [kMoveCursor]: { + get() { + return this._moveCursor; + }, + }, + [kTtyWrite]: { + get() { + return this._ttyWrite; + }, + }, + + // Defining proxies for the internal instance properties for backward + // compatibility. + _decoder: { + get() { + return this[kDecoder]; + }, + set(value) { + this[kDecoder] = value; + }, + }, + _line_buffer: { + get() { + return this[kLine_buffer]; + }, + set(value) { + this[kLine_buffer] = value; + }, + }, + _oldPrompt: { + get() { + return this[kOldPrompt]; + }, + set(value) { + this[kOldPrompt] = value; + }, + }, + _previousKey: { + get() { + return this[kPreviousKey]; + }, + set(value) { + this[kPreviousKey] = value; + }, + }, + _prompt: { + get() { + return this[kPrompt]; + }, + set(value) { + this[kPrompt] = value; + }, + }, + _questionCallback: { + get() { + return this[kQuestionCallback]; + }, + set(value) { + this[kQuestionCallback] = value; + }, + }, + _sawKeyPress: { + get() { + return this[kSawKeyPress]; + }, + set(value) { + this[kSawKeyPress] = value; + }, + }, + _sawReturnAt: { + get() { + return this[kSawReturnAt]; + }, + set(value) { + this[kSawReturnAt] = value; + }, + }, +}); + +// Make internal methods public for backward compatibility. +Interface.prototype._setRawMode = _Interface.prototype[kSetRawMode]; +Interface.prototype._onLine = _Interface.prototype[kOnLine]; +Interface.prototype._writeToOutput = _Interface.prototype[kWriteToOutput]; +Interface.prototype._addHistory = _Interface.prototype[kAddHistory]; +Interface.prototype._refreshLine = _Interface.prototype[kRefreshLine]; +Interface.prototype._normalWrite = _Interface.prototype[kNormalWrite]; +Interface.prototype._insertString = _Interface.prototype[kInsertString]; +Interface.prototype._tabComplete = function (lastKeypressWasTab) { + // Overriding parent method because `this.completer` in the legacy + // implementation takes a callback instead of being an async function. + this.pause(); + const string = this.line.slice(0, this.cursor); + this.completer(string, (err, value) => { + this.resume(); + + if (err) { + // TODO(bartlomieju): inspect is not ported yet + // this._writeToOutput(`Tab completion error: ${inspect(err)}`); + this._writeToOutput(`Tab completion error: ${err}`); + return; + } + + this[kTabCompleter](lastKeypressWasTab, value); + }); +}; +Interface.prototype._wordLeft = _Interface.prototype[kWordLeft]; +Interface.prototype._wordRight = _Interface.prototype[kWordRight]; +Interface.prototype._deleteLeft = _Interface.prototype[kDeleteLeft]; +Interface.prototype._deleteRight = _Interface.prototype[kDeleteRight]; +Interface.prototype._deleteWordLeft = _Interface.prototype[kDeleteWordLeft]; +Interface.prototype._deleteWordRight = _Interface.prototype[kDeleteWordRight]; +Interface.prototype._deleteLineLeft = _Interface.prototype[kDeleteLineLeft]; +Interface.prototype._deleteLineRight = _Interface.prototype[kDeleteLineRight]; +Interface.prototype._line = _Interface.prototype[kLine]; +Interface.prototype._historyNext = _Interface.prototype[kHistoryNext]; +Interface.prototype._historyPrev = _Interface.prototype[kHistoryPrev]; +Interface.prototype._getDisplayPos = _Interface.prototype[kGetDisplayPos]; +Interface.prototype._getCursorPos = _Interface.prototype.getCursorPos; +Interface.prototype._moveCursor = _Interface.prototype[kMoveCursor]; +Interface.prototype._ttyWrite = _Interface.prototype[kTtyWrite]; + +function _ttyWriteDumb(s, key) { + key = key || {}; + + if (key.name === "escape") return; + + if (this[kSawReturnAt] && key.name !== "enter") { + this[kSawReturnAt] = 0; + } + + if (key.ctrl) { + if (key.name === "c") { + if (this.listenerCount("SIGINT") > 0) { + this.emit("SIGINT"); + } else { + // This readline instance is finished + this.close(); + } + + return; + } else if (key.name === "d") { + this.close(); + return; + } + } + + switch (key.name) { + case "return": // Carriage return, i.e. \r + this[kSawReturnAt] = Date.now(); + this._line(); + break; + + case "enter": + // When key interval > crlfDelay + if ( + this[kSawReturnAt] === 0 || + Date.now() - this[kSawReturnAt] > this.crlfDelay + ) { + this._line(); + } + this[kSawReturnAt] = 0; + break; + + default: + if (typeof s === "string" && s) { + this.line += s; + this.cursor += s.length; + this._writeToOutput(s); + } + } +} + +export { + clearLine, + clearScreenDown, + createInterface, + cursorTo, + emitKeypressEvents, + Interface, + moveCursor, + promises, +}; diff --git a/ext/node/polyfills/_readline_shared_types.d.ts b/ext/node/polyfills/_readline_shared_types.d.ts new file mode 100644 index 00000000000000..274483916404e2 --- /dev/null +++ b/ext/node/polyfills/_readline_shared_types.d.ts @@ -0,0 +1,42 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. + +// Part of https://github.com/DefinitelyTyped/DefinitelyTyped/blob/cd61f5b4d3d143108569ec3f88adc0eb34b961c4/types/node/readline.d.ts + +// This .d.ts file is provided to avoid circular dependencies. + +import type { + ReadableStream, + WritableStream, +} from "internal:deno_node/polyfills/_global.d.ts"; + +export type Completer = (line: string) => CompleterResult; +export type AsyncCompleter = ( + line: string, + callback: (err?: null | Error, result?: CompleterResult) => void, +) => void; +export type CompleterResult = [string[], string]; +export interface ReadLineOptions { + input: ReadableStream; + output?: WritableStream | undefined; + completer?: Completer | AsyncCompleter | undefined; + terminal?: boolean | undefined; + /** + * Initial list of history lines. This option makes sense + * only if `terminal` is set to `true` by the user or by an internal `output` + * check, otherwise the history caching mechanism is not initialized at all. + * @default [] + */ + history?: string[] | undefined; + historySize?: number | undefined; + prompt?: string | undefined; + crlfDelay?: number | undefined; + /** + * If `true`, when a new input line added + * to the history list duplicates an older one, this removes the older line + * from the list. + * @default false + */ + removeHistoryDuplicates?: boolean | undefined; + escapeCodeTimeout?: number | undefined; + tabSize?: number | undefined; +} diff --git a/ext/node/polyfills/_stream.d.ts b/ext/node/polyfills/_stream.d.ts new file mode 100644 index 00000000000000..33ebda23c2a559 --- /dev/null +++ b/ext/node/polyfills/_stream.d.ts @@ -0,0 +1,1491 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. +// deno-lint-ignore-file no-explicit-any + +// Forked from https://github.com/DefinitelyTyped/DefinitelyTyped/blob/4f538975138678878fed5b2555c0672aa578ab7d/types/node/stream.d.ts + +import { Buffer } from "internal:deno_node/polyfills/buffer.ts"; +import { + Abortable, + EventEmitter, +} from "internal:deno_node/polyfills/_events.d.ts"; +import { + Buffered, + BufferEncoding, + ErrnoException, + ReadableStream, + ReadWriteStream, + WritableStream, +} from "internal:deno_node/polyfills/_global.d.ts"; + +export class Stream extends EventEmitter { + pipe( + destination: T, + options?: { + end?: boolean | undefined; + }, + ): T; + constructor(opts?: ReadableOptions); +} + +interface StreamOptions extends Abortable { + emitClose?: boolean | undefined; + highWaterMark?: number | undefined; + objectMode?: boolean | undefined; + construct?(this: T, callback: (error?: Error | null) => void): void; + destroy?( + this: T, + error: Error | null, + callback: (error: Error | null) => void, + ): void; + autoDestroy?: boolean | undefined; +} +export interface ReadableOptions extends StreamOptions { + encoding?: BufferEncoding | undefined; + read?(this: Readable, size: number): void; +} +/** + * @since v0.9.4 + */ +export class Readable extends Stream implements ReadableStream { + /** + * A utility method for creating Readable Streams out of iterators. + */ + static from( + iterable: Iterable | AsyncIterable, + options?: ReadableOptions, + ): Readable; + + /** + * A utility method for creating `Readable` from a `ReadableStream`. + * @since v17.0.0 + * @experimental + */ + static fromWeb( + readableStream: globalThis.ReadableStream, + options?: Pick< + ReadableOptions, + "encoding" | "highWaterMark" | "objectMode" | "signal" + >, + ): Readable; + + /** + * Returns whether the stream has been read from or cancelled. + * @since v16.8.0 + */ + static isDisturbed(stream: Readable | ReadableStream): boolean; + /** + * Returns whether the stream was destroyed or errored before emitting `'end'`. + * @since v16.8.0 + * @experimental + */ + readonly readableAborted: boolean; + /** + * Is `true` if it is safe to call `readable.read()`, which means + * the stream has not been destroyed or emitted `'error'` or `'end'`. + * @since v11.4.0 + */ + readable: boolean; + /** + * Returns whether `'data'` has been emitted. + * @since v16.7.0 + * @experimental + */ + readonly readableDidRead: boolean; + /** + * Getter for the property `encoding` of a given `Readable` stream. The `encoding`property can be set using the `readable.setEncoding()` method. + * @since v12.7.0 + */ + readonly readableEncoding: BufferEncoding | null; + /** + * Becomes `true` when `'end'` event is emitted. + * @since v12.9.0 + */ + readonly readableEnded: boolean; + /** + * This property reflects the current state of a `Readable` stream as described + * in the `Three states` section. + * @since v9.4.0 + */ + readonly readableFlowing: boolean | null; + /** + * Returns the value of `highWaterMark` passed when creating this `Readable`. + * @since v9.3.0 + */ + readonly readableHighWaterMark: number; + /** + * This property contains the number of bytes (or objects) in the queue + * ready to be read. The value provides introspection data regarding + * the status of the `highWaterMark`. + * @since v9.4.0 + */ + readonly readableLength: number; + /** + * Getter for the property `objectMode` of a given `Readable` stream. + * @since v12.3.0 + */ + readonly readableObjectMode: boolean; + /** + * Is `true` after `readable.destroy()` has been called. + * @since v8.0.0 + */ + destroyed: boolean; + constructor(opts?: ReadableOptions); + _construct?(callback: (error?: Error | null) => void): void; + _read(size: number): void; + _undestroy(): void; + /** + * The `readable.read()` method pulls some data out of the internal buffer and + * returns it. If no data available to be read, `null` is returned. By default, + * the data will be returned as a `Buffer` object unless an encoding has been + * specified using the `readable.setEncoding()` method or the stream is operating + * in object mode. + * + * The optional `size` argument specifies a specific number of bytes to read. If`size` bytes are not available to be read, `null` will be returned _unless_the stream has ended, in which + * case all of the data remaining in the internal + * buffer will be returned. + * + * If the `size` argument is not specified, all of the data contained in the + * internal buffer will be returned. + * + * The `size` argument must be less than or equal to 1 GiB. + * + * The `readable.read()` method should only be called on `Readable` streams + * operating in paused mode. In flowing mode, `readable.read()` is called + * automatically until the internal buffer is fully drained. + * + * ```js + * const readable = getReadableStreamSomehow(); + * + * // 'readable' may be triggered multiple times as data is buffered in + * readable.on('readable', () => { + * let chunk; + * console.log('Stream is readable (new data received in buffer)'); + * // Use a loop to make sure we read all currently available data + * while (null !== (chunk = readable.read())) { + * console.log(`Read ${chunk.length} bytes of data...`); + * } + * }); + * + * // 'end' will be triggered once when there is no more data available + * readable.on('end', () => { + * console.log('Reached end of stream.'); + * }); + * ``` + * + * Each call to `readable.read()` returns a chunk of data, or `null`. The chunks + * are not concatenated. A `while` loop is necessary to consume all data + * currently in the buffer. When reading a large file `.read()` may return `null`, + * having consumed all buffered content so far, but there is still more data to + * come not yet buffered. In this case a new `'readable'` event will be emitted + * when there is more data in the buffer. Finally the `'end'` event will be + * emitted when there is no more data to come. + * + * Therefore to read a file's whole contents from a `readable`, it is necessary + * to collect chunks across multiple `'readable'` events: + * + * ```js + * const chunks = []; + * + * readable.on('readable', () => { + * let chunk; + * while (null !== (chunk = readable.read())) { + * chunks.push(chunk); + * } + * }); + * + * readable.on('end', () => { + * const content = chunks.join(''); + * }); + * ``` + * + * A `Readable` stream in object mode will always return a single item from + * a call to `readable.read(size)`, regardless of the value of the`size` argument. + * + * If the `readable.read()` method returns a chunk of data, a `'data'` event will + * also be emitted. + * + * Calling {@link read} after the `'end'` event has + * been emitted will return `null`. No runtime error will be raised. + * @since v0.9.4 + * @param size Optional argument to specify how much data to read. + */ + read(size?: number): any; + /** + * The `readable.setEncoding()` method sets the character encoding for + * data read from the `Readable` stream. + * + * By default, no encoding is assigned and stream data will be returned as`Buffer` objects. Setting an encoding causes the stream data + * to be returned as strings of the specified encoding rather than as `Buffer`objects. For instance, calling `readable.setEncoding('utf8')` will cause the + * output data to be interpreted as UTF-8 data, and passed as strings. Calling`readable.setEncoding('hex')` will cause the data to be encoded in hexadecimal + * string format. + * + * The `Readable` stream will properly handle multi-byte characters delivered + * through the stream that would otherwise become improperly decoded if simply + * pulled from the stream as `Buffer` objects. + * + * ```js + * const readable = getReadableStreamSomehow(); + * readable.setEncoding('utf8'); + * readable.on('data', (chunk) => { + * assert.equal(typeof chunk, 'string'); + * console.log('Got %d characters of string data:', chunk.length); + * }); + * ``` + * @since v0.9.4 + * @param encoding The encoding to use. + */ + setEncoding(encoding: BufferEncoding): this; + /** + * The `readable.pause()` method will cause a stream in flowing mode to stop + * emitting `'data'` events, switching out of flowing mode. Any data that + * becomes available will remain in the internal buffer. + * + * ```js + * const readable = getReadableStreamSomehow(); + * readable.on('data', (chunk) => { + * console.log(`Received ${chunk.length} bytes of data.`); + * readable.pause(); + * console.log('There will be no additional data for 1 second.'); + * setTimeout(() => { + * console.log('Now data will start flowing again.'); + * readable.resume(); + * }, 1000); + * }); + * ``` + * + * The `readable.pause()` method has no effect if there is a `'readable'`event listener. + * @since v0.9.4 + */ + pause(): this; + /** + * The `readable.resume()` method causes an explicitly paused `Readable` stream to + * resume emitting `'data'` events, switching the stream into flowing mode. + * + * The `readable.resume()` method can be used to fully consume the data from a + * stream without actually processing any of that data: + * + * ```js + * getReadableStreamSomehow() + * .resume() + * .on('end', () => { + * console.log('Reached the end, but did not read anything.'); + * }); + * ``` + * + * The `readable.resume()` method has no effect if there is a `'readable'`event listener. + * @since v0.9.4 + */ + resume(): this; + /** + * The `readable.isPaused()` method returns the current operating state of the`Readable`. This is used primarily by the mechanism that underlies the`readable.pipe()` method. In most + * typical cases, there will be no reason to + * use this method directly. + * + * ```js + * const readable = new stream.Readable(); + * + * readable.isPaused(); // === false + * readable.pause(); + * readable.isPaused(); // === true + * readable.resume(); + * readable.isPaused(); // === false + * ``` + * @since v0.11.14 + */ + isPaused(): boolean; + /** + * The `readable.unpipe()` method detaches a `Writable` stream previously attached + * using the {@link pipe} method. + * + * If the `destination` is not specified, then _all_ pipes are detached. + * + * If the `destination` is specified, but no pipe is set up for it, then + * the method does nothing. + * + * ```js + * const fs = require('fs'); + * const readable = getReadableStreamSomehow(); + * const writable = fs.createWriteStream('file.txt'); + * // All the data from readable goes into 'file.txt', + * // but only for the first second. + * readable.pipe(writable); + * setTimeout(() => { + * console.log('Stop writing to file.txt.'); + * readable.unpipe(writable); + * console.log('Manually close the file stream.'); + * writable.end(); + * }, 1000); + * ``` + * @since v0.9.4 + * @param destination Optional specific stream to unpipe + */ + unpipe(destination?: WritableStream): this; + /** + * Passing `chunk` as `null` signals the end of the stream (EOF) and behaves the + * same as `readable.push(null)`, after which no more data can be written. The EOF + * signal is put at the end of the buffer and any buffered data will still be + * flushed. + * + * The `readable.unshift()` method pushes a chunk of data back into the internal + * buffer. This is useful in certain situations where a stream is being consumed by + * code that needs to "un-consume" some amount of data that it has optimistically + * pulled out of the source, so that the data can be passed on to some other party. + * + * The `stream.unshift(chunk)` method cannot be called after the `'end'` event + * has been emitted or a runtime error will be thrown. + * + * Developers using `stream.unshift()` often should consider switching to + * use of a `Transform` stream instead. See the `API for stream implementers` section for more information. + * + * ```js + * // Pull off a header delimited by \n\n. + * // Use unshift() if we get too much. + * // Call the callback with (error, header, stream). + * const { StringDecoder } = require('string_decoder'); + * function parseHeader(stream, callback) { + * stream.on('error', callback); + * stream.on('readable', onReadable); + * const decoder = new StringDecoder('utf8'); + * let header = ''; + * function onReadable() { + * let chunk; + * while (null !== (chunk = stream.read())) { + * const str = decoder.write(chunk); + * if (str.match(/\n\n/)) { + * // Found the header boundary. + * const split = str.split(/\n\n/); + * header += split.shift(); + * const remaining = split.join('\n\n'); + * const buf = Buffer.from(remaining, 'utf8'); + * stream.removeListener('error', callback); + * // Remove the 'readable' listener before unshifting. + * stream.removeListener('readable', onReadable); + * if (buf.length) + * stream.unshift(buf); + * // Now the body of the message can be read from the stream. + * callback(null, header, stream); + * } else { + * // Still reading the header. + * header += str; + * } + * } + * } + * } + * ``` + * + * Unlike {@link push}, `stream.unshift(chunk)` will not + * end the reading process by resetting the internal reading state of the stream. + * This can cause unexpected results if `readable.unshift()` is called during a + * read (i.e. from within a {@link _read} implementation on a + * custom stream). Following the call to `readable.unshift()` with an immediate {@link push} will reset the reading state appropriately, + * however it is best to simply avoid calling `readable.unshift()` while in the + * process of performing a read. + * @since v0.9.11 + * @param chunk Chunk of data to unshift onto the read queue. For streams not operating in object mode, `chunk` must be a string, `Buffer`, `Uint8Array` or `null`. For object mode + * streams, `chunk` may be any JavaScript value. + * @param encoding Encoding of string chunks. Must be a valid `Buffer` encoding, such as `'utf8'` or `'ascii'`. + */ + unshift(chunk: any, encoding?: BufferEncoding): void; + /** + * Prior to Node.js 0.10, streams did not implement the entire `stream` module API + * as it is currently defined. (See `Compatibility` for more information.) + * + * When using an older Node.js library that emits `'data'` events and has a {@link pause} method that is advisory only, the`readable.wrap()` method can be used to create a `Readable` + * stream that uses + * the old stream as its data source. + * + * It will rarely be necessary to use `readable.wrap()` but the method has been + * provided as a convenience for interacting with older Node.js applications and + * libraries. + * + * ```js + * const { OldReader } = require('./old-api-module.js'); + * const { Readable } = require('stream'); + * const oreader = new OldReader(); + * const myReader = new Readable().wrap(oreader); + * + * myReader.on('readable', () => { + * myReader.read(); // etc. + * }); + * ``` + * @since v0.9.4 + * @param stream An "old style" readable stream + */ + wrap(stream: ReadableStream): this; + push(chunk: any, encoding?: BufferEncoding): boolean; + _destroy( + error: Error | null, + callback: (error?: Error | null) => void, + ): void; + /** + * Destroy the stream. Optionally emit an `'error'` event, and emit a `'close'`event (unless `emitClose` is set to `false`). After this call, the readable + * stream will release any internal resources and subsequent calls to `push()`will be ignored. + * + * Once `destroy()` has been called any further calls will be a no-op and no + * further errors except from `_destroy()` may be emitted as `'error'`. + * + * Implementors should not override this method, but instead implement `readable._destroy()`. + * @since v8.0.0 + * @param error Error which will be passed as payload in `'error'` event + */ + destroy(error?: Error): void; + /** + * Event emitter + * The defined events on documents including: + * 1. close + * 2. data + * 3. end + * 4. error + * 5. pause + * 6. readable + * 7. resume + */ + addListener(event: "close", listener: () => void): this; + addListener(event: "data", listener: (chunk: any) => void): this; + addListener(event: "end", listener: () => void): this; + addListener(event: "error", listener: (err: Error) => void): this; + addListener(event: "pause", listener: () => void): this; + addListener(event: "readable", listener: () => void): this; + addListener(event: "resume", listener: () => void): this; + addListener( + event: string | symbol, + listener: (...args: any[]) => void, + ): this; + emit(event: "close"): boolean; + emit(event: "data", chunk: any): boolean; + emit(event: "end"): boolean; + emit(event: "error", err: Error): boolean; + emit(event: "pause"): boolean; + emit(event: "readable"): boolean; + emit(event: "resume"): boolean; + emit(event: string | symbol, ...args: any[]): boolean; + on(event: "close", listener: () => void): this; + on(event: "data", listener: (chunk: any) => void): this; + on(event: "end", listener: () => void): this; + on(event: "error", listener: (err: Error) => void): this; + on(event: "pause", listener: () => void): this; + on(event: "readable", listener: () => void): this; + on(event: "resume", listener: () => void): this; + on(event: string | symbol, listener: (...args: any[]) => void): this; + once(event: "close", listener: () => void): this; + once(event: "data", listener: (chunk: any) => void): this; + once(event: "end", listener: () => void): this; + once(event: "error", listener: (err: Error) => void): this; + once(event: "pause", listener: () => void): this; + once(event: "readable", listener: () => void): this; + once(event: "resume", listener: () => void): this; + once(event: string | symbol, listener: (...args: any[]) => void): this; + prependListener(event: "close", listener: () => void): this; + prependListener(event: "data", listener: (chunk: any) => void): this; + prependListener(event: "end", listener: () => void): this; + prependListener(event: "error", listener: (err: Error) => void): this; + prependListener(event: "pause", listener: () => void): this; + prependListener(event: "readable", listener: () => void): this; + prependListener(event: "resume", listener: () => void): this; + prependListener( + event: string | symbol, + listener: (...args: any[]) => void, + ): this; + prependOnceListener(event: "close", listener: () => void): this; + prependOnceListener(event: "data", listener: (chunk: any) => void): this; + prependOnceListener(event: "end", listener: () => void): this; + prependOnceListener(event: "error", listener: (err: Error) => void): this; + prependOnceListener(event: "pause", listener: () => void): this; + prependOnceListener(event: "readable", listener: () => void): this; + prependOnceListener(event: "resume", listener: () => void): this; + prependOnceListener( + event: string | symbol, + listener: (...args: any[]) => void, + ): this; + removeListener(event: "close", listener: () => void): this; + removeListener(event: "data", listener: (chunk: any) => void): this; + removeListener(event: "end", listener: () => void): this; + removeListener(event: "error", listener: (err: Error) => void): this; + removeListener(event: "pause", listener: () => void): this; + removeListener(event: "readable", listener: () => void): this; + removeListener(event: "resume", listener: () => void): this; + removeListener( + event: string | symbol, + listener: (...args: any[]) => void, + ): this; + [Symbol.asyncIterator](): AsyncIterableIterator; +} +export interface WritableOptions extends StreamOptions { + decodeStrings?: boolean | undefined; + defaultEncoding?: BufferEncoding | undefined; + write?( + this: Writable, + chunk: any, + encoding: BufferEncoding, + callback: (error?: Error | null) => void, + ): void; + writev?( + this: Writable, + chunks: Array<{ + chunk: any; + encoding: BufferEncoding; + }>, + callback: (error?: Error | null) => void, + ): void; + final?(this: Writable, callback: (error?: Error | null) => void): void; +} +/** + * @since v0.9.4 + */ +export class Writable extends Stream implements WritableStream { + /** + * A utility method for creating `Writable` from a `WritableStream`. + * @since v17.0.0 + * @experimental + */ + static fromWeb( + writableStream: globalThis.WritableStream, + options?: Pick< + WritableOptions, + "decodeStrings" | "highWaterMark" | "objectMode" | "signal" + >, + ): Writable; + + /** + * Is `true` if it is safe to call `writable.write()`, which means + * the stream has not been destroyed, errored or ended. + * @since v11.4.0 + */ + readonly writable: boolean; + readonly writableBuffer?: Buffered[]; + /** + * Is `true` after `writable.end()` has been called. This property + * does not indicate whether the data has been flushed, for this use `writable.writableFinished` instead. + * @since v12.9.0 + */ + readonly writableEnded: boolean; + /** + * Is set to `true` immediately before the `'finish'` event is emitted. + * @since v12.6.0 + */ + readonly writableFinished: boolean; + /** + * Return the value of `highWaterMark` passed when creating this `Writable`. + * @since v9.3.0 + */ + readonly writableHighWaterMark: number; + /** + * This property contains the number of bytes (or objects) in the queue + * ready to be written. The value provides introspection data regarding + * the status of the `highWaterMark`. + * @since v9.4.0 + */ + readonly writableLength: number; + /** + * Getter for the property `objectMode` of a given `Writable` stream. + * @since v12.3.0 + */ + readonly writableObjectMode: boolean; + /** + * Number of times `writable.uncork()` needs to be + * called in order to fully uncork the stream. + * @since v13.2.0, v12.16.0 + */ + readonly writableCorked: number; + /** + * Is `true` after `writable.destroy()` has been called. + * @since v8.0.0 + */ + destroyed: boolean; + /** + * Is true after 'close' has been emitted. + * @since v8.0.0 + */ + readonly closed: boolean; + constructor(opts?: WritableOptions); + _write( + chunk: any, + encoding: BufferEncoding, + callback: (error?: Error | null) => void, + ): void; + _writev?( + chunks: Array<{ + chunk: any; + encoding: BufferEncoding; + }>, + callback: (error?: Error | null) => void, + ): void; + _construct?(callback: (error?: Error | null) => void): void; + _destroy( + error: Error | null, + callback: (error?: Error | null) => void, + ): void; + _final(callback: (error?: Error | null) => void): void; + /** + * The `writable.write()` method writes some data to the stream, and calls the + * supplied `callback` once the data has been fully handled. If an error + * occurs, the `callback` will be called with the error as its + * first argument. The `callback` is called asynchronously and before `'error'` is + * emitted. + * + * The return value is `true` if the internal buffer is less than the`highWaterMark` configured when the stream was created after admitting `chunk`. + * If `false` is returned, further attempts to write data to the stream should + * stop until the `'drain'` event is emitted. + * + * While a stream is not draining, calls to `write()` will buffer `chunk`, and + * return false. Once all currently buffered chunks are drained (accepted for + * delivery by the operating system), the `'drain'` event will be emitted. + * It is recommended that once `write()` returns false, no more chunks be written + * until the `'drain'` event is emitted. While calling `write()` on a stream that + * is not draining is allowed, Node.js will buffer all written chunks until + * maximum memory usage occurs, at which point it will abort unconditionally. + * Even before it aborts, high memory usage will cause poor garbage collector + * performance and high RSS (which is not typically released back to the system, + * even after the memory is no longer required). Since TCP sockets may never + * drain if the remote peer does not read the data, writing a socket that is + * not draining may lead to a remotely exploitable vulnerability. + * + * Writing data while the stream is not draining is particularly + * problematic for a `Transform`, because the `Transform` streams are paused + * by default until they are piped or a `'data'` or `'readable'` event handler + * is added. + * + * If the data to be written can be generated or fetched on demand, it is + * recommended to encapsulate the logic into a `Readable` and use {@link pipe}. However, if calling `write()` is preferred, it is + * possible to respect backpressure and avoid memory issues using the `'drain'` event: + * + * ```js + * function write(data, cb) { + * if (!stream.write(data)) { + * stream.once('drain', cb); + * } else { + * process.nextTick(cb); + * } + * } + * + * // Wait for cb to be called before doing any other write. + * write('hello', () => { + * console.log('Write completed, do more writes now.'); + * }); + * ``` + * + * A `Writable` stream in object mode will always ignore the `encoding` argument. + * @since v0.9.4 + * @param chunk Optional data to write. For streams not operating in object mode, `chunk` must be a string, `Buffer` or `Uint8Array`. For object mode streams, `chunk` may be any + * JavaScript value other than `null`. + * @param [encoding='utf8'] The encoding, if `chunk` is a string. + * @param callback Callback for when this chunk of data is flushed. + * @return `false` if the stream wishes for the calling code to wait for the `'drain'` event to be emitted before continuing to write additional data; otherwise `true`. + */ + write( + chunk: any, + callback?: (error: Error | null | undefined) => void, + ): boolean; + write( + chunk: any, + encoding: BufferEncoding, + callback?: (error: Error | null | undefined) => void, + ): boolean; + /** + * The `writable.setDefaultEncoding()` method sets the default `encoding` for a `Writable` stream. + * @since v0.11.15 + * @param encoding The new default encoding + */ + setDefaultEncoding(encoding: BufferEncoding): this; + /** + * Calling the `writable.end()` method signals that no more data will be written + * to the `Writable`. The optional `chunk` and `encoding` arguments allow one + * final additional chunk of data to be written immediately before closing the + * stream. + * + * Calling the {@link write} method after calling {@link end} will raise an error. + * + * ```js + * // Write 'hello, ' and then end with 'world!'. + * const fs = require('fs'); + * const file = fs.createWriteStream('example.txt'); + * file.write('hello, '); + * file.end('world!'); + * // Writing more now is not allowed! + * ``` + * @since v0.9.4 + * @param chunk Optional data to write. For streams not operating in object mode, `chunk` must be a string, `Buffer` or `Uint8Array`. For object mode streams, `chunk` may be any + * JavaScript value other than `null`. + * @param encoding The encoding if `chunk` is a string + * @param callback Callback for when the stream is finished. + */ + end(cb?: () => void): void; + end(chunk: any, cb?: () => void): void; + end(chunk: any, encoding: BufferEncoding, cb?: () => void): void; + /** + * The `writable.cork()` method forces all written data to be buffered in memory. + * The buffered data will be flushed when either the {@link uncork} or {@link end} methods are called. + * + * The primary intent of `writable.cork()` is to accommodate a situation in which + * several small chunks are written to the stream in rapid succession. Instead of + * immediately forwarding them to the underlying destination, `writable.cork()`buffers all the chunks until `writable.uncork()` is called, which will pass them + * all to `writable._writev()`, if present. This prevents a head-of-line blocking + * situation where data is being buffered while waiting for the first small chunk + * to be processed. However, use of `writable.cork()` without implementing`writable._writev()` may have an adverse effect on throughput. + * + * See also: `writable.uncork()`, `writable._writev()`. + * @since v0.11.2 + */ + cork(): void; + /** + * The `writable.uncork()` method flushes all data buffered since {@link cork} was called. + * + * When using `writable.cork()` and `writable.uncork()` to manage the buffering + * of writes to a stream, it is recommended that calls to `writable.uncork()` be + * deferred using `process.nextTick()`. Doing so allows batching of all`writable.write()` calls that occur within a given Node.js event loop phase. + * + * ```js + * stream.cork(); + * stream.write('some '); + * stream.write('data '); + * process.nextTick(() => stream.uncork()); + * ``` + * + * If the `writable.cork()` method is called multiple times on a stream, the + * same number of calls to `writable.uncork()` must be called to flush the buffered + * data. + * + * ```js + * stream.cork(); + * stream.write('some '); + * stream.cork(); + * stream.write('data '); + * process.nextTick(() => { + * stream.uncork(); + * // The data will not be flushed until uncork() is called a second time. + * stream.uncork(); + * }); + * ``` + * + * See also: `writable.cork()`. + * @since v0.11.2 + */ + uncork(): void; + /** + * Destroy the stream. Optionally emit an `'error'` event, and emit a `'close'`event (unless `emitClose` is set to `false`). After this call, the writable + * stream has ended and subsequent calls to `write()` or `end()` will result in + * an `ERR_STREAM_DESTROYED` error. + * This is a destructive and immediate way to destroy a stream. Previous calls to`write()` may not have drained, and may trigger an `ERR_STREAM_DESTROYED` error. + * Use `end()` instead of destroy if data should flush before close, or wait for + * the `'drain'` event before destroying the stream. + * + * Once `destroy()` has been called any further calls will be a no-op and no + * further errors except from `_destroy()` may be emitted as `'error'`. + * + * Implementors should not override this method, + * but instead implement `writable._destroy()`. + * @since v8.0.0 + * @param error Optional, an error to emit with `'error'` event. + */ + destroy(error?: Error): void; + /** + * Event emitter + * The defined events on documents including: + * 1. close + * 2. drain + * 3. error + * 4. finish + * 5. pipe + * 6. unpipe + */ + addListener(event: "close", listener: () => void): this; + addListener(event: "drain", listener: () => void): this; + addListener(event: "error", listener: (err: Error) => void): this; + addListener(event: "finish", listener: () => void): this; + addListener(event: "pipe", listener: (src: Readable) => void): this; + addListener(event: "unpipe", listener: (src: Readable) => void): this; + addListener( + event: string | symbol, + listener: (...args: any[]) => void, + ): this; + emit(event: "close"): boolean; + emit(event: "drain"): boolean; + emit(event: "error", err: Error): boolean; + emit(event: "finish"): boolean; + emit(event: "pipe", src: Readable): boolean; + emit(event: "unpipe", src: Readable): boolean; + emit(event: string | symbol, ...args: any[]): boolean; + on(event: "close", listener: () => void): this; + on(event: "drain", listener: () => void): this; + on(event: "error", listener: (err: Error) => void): this; + on(event: "finish", listener: () => void): this; + on(event: "pipe", listener: (src: Readable) => void): this; + on(event: "unpipe", listener: (src: Readable) => void): this; + on(event: string | symbol, listener: (...args: any[]) => void): this; + once(event: "close", listener: () => void): this; + once(event: "drain", listener: () => void): this; + once(event: "error", listener: (err: Error) => void): this; + once(event: "finish", listener: () => void): this; + once(event: "pipe", listener: (src: Readable) => void): this; + once(event: "unpipe", listener: (src: Readable) => void): this; + once(event: string | symbol, listener: (...args: any[]) => void): this; + prependListener(event: "close", listener: () => void): this; + prependListener(event: "drain", listener: () => void): this; + prependListener(event: "error", listener: (err: Error) => void): this; + prependListener(event: "finish", listener: () => void): this; + prependListener(event: "pipe", listener: (src: Readable) => void): this; + prependListener(event: "unpipe", listener: (src: Readable) => void): this; + prependListener( + event: string | symbol, + listener: (...args: any[]) => void, + ): this; + prependOnceListener(event: "close", listener: () => void): this; + prependOnceListener(event: "drain", listener: () => void): this; + prependOnceListener(event: "error", listener: (err: Error) => void): this; + prependOnceListener(event: "finish", listener: () => void): this; + prependOnceListener(event: "pipe", listener: (src: Readable) => void): this; + prependOnceListener( + event: "unpipe", + listener: (src: Readable) => void, + ): this; + prependOnceListener( + event: string | symbol, + listener: (...args: any[]) => void, + ): this; + removeListener(event: "close", listener: () => void): this; + removeListener(event: "drain", listener: () => void): this; + removeListener(event: "error", listener: (err: Error) => void): this; + removeListener(event: "finish", listener: () => void): this; + removeListener(event: "pipe", listener: (src: Readable) => void): this; + removeListener(event: "unpipe", listener: (src: Readable) => void): this; + removeListener( + event: string | symbol, + listener: (...args: any[]) => void, + ): this; +} +export interface DuplexOptions extends ReadableOptions, WritableOptions { + allowHalfOpen?: boolean | undefined; + readableObjectMode?: boolean | undefined; + writableObjectMode?: boolean | undefined; + readableHighWaterMark?: number | undefined; + writableHighWaterMark?: number | undefined; + writableCorked?: number | undefined; + construct?(this: Duplex, callback: (error?: Error | null) => void): void; + read?(this: Duplex, size: number): void; + write?( + this: Duplex, + chunk: any, + encoding: BufferEncoding, + callback: (error?: Error | null) => void, + ): void; + writev?( + this: Duplex, + chunks: Array<{ + chunk: any; + encoding: BufferEncoding; + }>, + callback: (error?: Error | null) => void, + ): void; + final?(this: Duplex, callback: (error?: Error | null) => void): void; + destroy?( + this: Duplex, + error: Error | null, + callback: (error: Error | null) => void, + ): void; +} +/** + * Duplex streams are streams that implement both the `Readable` and `Writable` interfaces. + * + * Examples of `Duplex` streams include: + * + * * `TCP sockets` + * * `zlib streams` + * * `crypto streams` + * @since v0.9.4 + */ +export class Duplex extends Readable implements Writable { + readonly writable: boolean; + readonly writableBuffer?: Buffered[]; + readonly writableEnded: boolean; + readonly writableFinished: boolean; + readonly writableHighWaterMark: number; + readonly writableLength: number; + readonly writableObjectMode: boolean; + readonly writableCorked: number; + readonly closed: boolean; + /** + * If `false` then the stream will automatically end the writable side when the + * readable side ends. Set initially by the `allowHalfOpen` constructor option, + * which defaults to `false`. + * + * This can be changed manually to change the half-open behavior of an existing`Duplex` stream instance, but must be changed before the `'end'` event is + * emitted. + * @since v0.9.4 + */ + allowHalfOpen: boolean; + constructor(opts?: DuplexOptions); + /** + * A utility method for creating duplex streams. + * + * - `Stream` converts writable stream into writable `Duplex` and readable stream + * to `Duplex`. + * - `Blob` converts into readable `Duplex`. + * - `string` converts into readable `Duplex`. + * - `ArrayBuffer` converts into readable `Duplex`. + * - `AsyncIterable` converts into a readable `Duplex`. Cannot yield `null`. + * - `AsyncGeneratorFunction` converts into a readable/writable transform + * `Duplex`. Must take a source `AsyncIterable` as first parameter. Cannot yield + * `null`. + * - `AsyncFunction` converts into a writable `Duplex`. Must return + * either `null` or `undefined` + * - `Object ({ writable, readable })` converts `readable` and + * `writable` into `Stream` and then combines them into `Duplex` where the + * `Duplex` will write to the `writable` and read from the `readable`. + * - `Promise` converts into readable `Duplex`. Value `null` is ignored. + * + * @since v16.8.0 + */ + static from( + src: + | Stream + | Blob + | ArrayBuffer + | string + | Iterable + | AsyncIterable + | AsyncGeneratorFunction + | Promise + // deno-lint-ignore ban-types + | Object, + ): Duplex; + _write( + chunk: any, + encoding: BufferEncoding, + callback: (error?: Error | null) => void, + ): void; + _writev?( + chunks: Array<{ + chunk: any; + encoding: BufferEncoding; + }>, + callback: (error?: Error | null) => void, + ): void; + _destroy( + error: Error | null, + callback: (error: Error | null) => void, + ): void; + _final(callback: (error?: Error | null) => void): void; + write( + chunk: any, + encoding?: BufferEncoding, + cb?: (error: Error | null | undefined) => void, + ): boolean; + write(chunk: any, cb?: (error: Error | null | undefined) => void): boolean; + setDefaultEncoding(encoding: BufferEncoding): this; + end(cb?: () => void): void; + end(chunk: any, cb?: () => void): void; + end(chunk: any, encoding?: BufferEncoding, cb?: () => void): void; + cork(): void; + uncork(): void; +} +type TransformCallback = (error?: Error | null, data?: any) => void; +export interface TransformOptions extends DuplexOptions { + construct?(this: Transform, callback: (error?: Error | null) => void): void; + read?(this: Transform, size: number): void; + write?( + this: Transform, + chunk: any, + encoding: BufferEncoding, + callback: (error?: Error | null) => void, + ): void; + writev?( + this: Transform, + chunks: Array<{ + chunk: any; + encoding: BufferEncoding; + }>, + callback: (error?: Error | null) => void, + ): void; + final?(this: Transform, callback: (error?: Error | null) => void): void; + destroy?( + this: Transform, + error: Error | null, + callback: (error: Error | null) => void, + ): void; + transform?( + this: Transform, + chunk: any, + encoding: BufferEncoding, + callback: TransformCallback, + ): void; + flush?(this: Transform, callback: TransformCallback): void; +} +/** + * Transform streams are `Duplex` streams where the output is in some way + * related to the input. Like all `Duplex` streams, `Transform` streams + * implement both the `Readable` and `Writable` interfaces. + * + * Examples of `Transform` streams include: + * + * * `zlib streams` + * * `crypto streams` + * @since v0.9.4 + */ +export class Transform extends Duplex { + constructor(opts?: TransformOptions); + _transform( + chunk: any, + encoding: BufferEncoding, + callback: TransformCallback, + ): void; + _flush(callback: TransformCallback): void; +} +/** + * The `stream.PassThrough` class is a trivial implementation of a `Transform` stream that simply passes the input bytes across to the output. Its purpose is + * primarily for examples and testing, but there are some use cases where `stream.PassThrough` is useful as a building block for novel sorts of streams. + */ +export class PassThrough extends Transform {} +/** + * Attaches an AbortSignal to a readable or writeable stream. This lets code + * control stream destruction using an `AbortController`. + * + * Calling `abort` on the `AbortController` corresponding to the passed`AbortSignal` will behave the same way as calling `.destroy(new AbortError())`on the stream. + * + * ```js + * const fs = require('fs'); + * + * const controller = new AbortController(); + * const read = addAbortSignal( + * controller.signal, + * fs.createReadStream(('object.json')) + * ); + * // Later, abort the operation closing the stream + * controller.abort(); + * ``` + * + * Or using an `AbortSignal` with a readable stream as an async iterable: + * + * ```js + * const controller = new AbortController(); + * setTimeout(() => controller.abort(), 10_000); // set a timeout + * const stream = addAbortSignal( + * controller.signal, + * fs.createReadStream(('object.json')) + * ); + * (async () => { + * try { + * for await (const chunk of stream) { + * await process(chunk); + * } + * } catch (e) { + * if (e.name === 'AbortError') { + * // The operation was cancelled + * } else { + * throw e; + * } + * } + * })(); + * ``` + * @since v15.4.0 + * @param signal A signal representing possible cancellation + * @param stream a stream to attach a signal to + */ +export function addAbortSignal( + signal: AbortSignal, + stream: T, +): T; +interface FinishedOptions extends Abortable { + error?: boolean | undefined; + readable?: boolean | undefined; + writable?: boolean | undefined; +} +/** + * A function to get notified when a stream is no longer readable, writable + * or has experienced an error or a premature close event. + * + * ```js + * const { finished } = require('stream'); + * + * const rs = fs.createReadStream('archive.tar'); + * + * finished(rs, (err) => { + * if (err) { + * console.error('Stream failed.', err); + * } else { + * console.log('Stream is done reading.'); + * } + * }); + * + * rs.resume(); // Drain the stream. + * ``` + * + * Especially useful in error handling scenarios where a stream is destroyed + * prematurely (like an aborted HTTP request), and will not emit `'end'`or `'finish'`. + * + * The `finished` API provides promise version: + * + * ```js + * const { finished } = require('stream/promises'); + * + * const rs = fs.createReadStream('archive.tar'); + * + * async function run() { + * await finished(rs); + * console.log('Stream is done reading.'); + * } + * + * run().catch(console.error); + * rs.resume(); // Drain the stream. + * ``` + * + * `stream.finished()` leaves dangling event listeners (in particular`'error'`, `'end'`, `'finish'` and `'close'`) after `callback` has been + * invoked. The reason for this is so that unexpected `'error'` events (due to + * incorrect stream implementations) do not cause unexpected crashes. + * If this is unwanted behavior then the returned cleanup function needs to be + * invoked in the callback: + * + * ```js + * const cleanup = finished(rs, (err) => { + * cleanup(); + * // ... + * }); + * ``` + * @since v10.0.0 + * @param stream A readable and/or writable stream. + * @param callback A callback function that takes an optional error argument. + * @return A cleanup function which removes all registered listeners. + */ +export function finished( + stream: + | ReadableStream + | WritableStream + | ReadWriteStream, + options: FinishedOptions, + callback: (err?: ErrnoException | null) => void, +): () => void; +export function finished( + stream: + | ReadableStream + | WritableStream + | ReadWriteStream, + callback: (err?: ErrnoException | null) => void, +): () => void; +export namespace finished { + function __promisify__( + stream: + | ReadableStream + | WritableStream + | ReadWriteStream, + options?: FinishedOptions, + ): Promise; +} +type PipelineSourceFunction = () => Iterable | AsyncIterable; +type PipelineSource = + | Iterable + | AsyncIterable + | ReadableStream + | PipelineSourceFunction; +type PipelineTransform, U> = + | ReadWriteStream + | (( + source: S extends + (...args: any[]) => Iterable | AsyncIterable + ? AsyncIterable + : S, + ) => AsyncIterable); +type PipelineTransformSource = + | PipelineSource + | PipelineTransform; +type PipelineDestinationIterableFunction = ( + source: AsyncIterable, +) => AsyncIterable; +type PipelineDestinationPromiseFunction = ( + source: AsyncIterable, +) => Promise

; +type PipelineDestination, P> = S extends + PipelineTransformSource ? + | WritableStream + | PipelineDestinationIterableFunction + | PipelineDestinationPromiseFunction + : never; +type PipelineCallback> = S extends + PipelineDestinationPromiseFunction + ? (err: ErrnoException | null, value: P) => void + : (err: ErrnoException | null) => void; +type PipelinePromise> = S extends + PipelineDestinationPromiseFunction ? Promise

+ : Promise; +interface PipelineOptions { + signal: AbortSignal; +} +/** + * A module method to pipe between streams and generators forwarding errors and + * properly cleaning up and provide a callback when the pipeline is complete. + * + * ```js + * const { pipeline } = require('stream'); + * const fs = require('fs'); + * const zlib = require('zlib'); + * + * // Use the pipeline API to easily pipe a series of streams + * // together and get notified when the pipeline is fully done. + * + * // A pipeline to gzip a potentially huge tar file efficiently: + * + * pipeline( + * fs.createReadStream('archive.tar'), + * zlib.createGzip(), + * fs.createWriteStream('archive.tar.gz'), + * (err) => { + * if (err) { + * console.error('Pipeline failed.', err); + * } else { + * console.log('Pipeline succeeded.'); + * } + * } + * ); + * ``` + * + * The `pipeline` API provides a promise version, which can also + * receive an options argument as the last parameter with a`signal` `AbortSignal` property. When the signal is aborted,`destroy` will be called on the underlying pipeline, with + * an`AbortError`. + * + * ```js + * const { pipeline } = require('stream/promises'); + * + * async function run() { + * await pipeline( + * fs.createReadStream('archive.tar'), + * zlib.createGzip(), + * fs.createWriteStream('archive.tar.gz') + * ); + * console.log('Pipeline succeeded.'); + * } + * + * run().catch(console.error); + * ``` + * + * To use an `AbortSignal`, pass it inside an options object, + * as the last argument: + * + * ```js + * const { pipeline } = require('stream/promises'); + * + * async function run() { + * const ac = new AbortController(); + * const signal = ac.signal; + * + * setTimeout(() => ac.abort(), 1); + * await pipeline( + * fs.createReadStream('archive.tar'), + * zlib.createGzip(), + * fs.createWriteStream('archive.tar.gz'), + * { signal }, + * ); + * } + * + * run().catch(console.error); // AbortError + * ``` + * + * The `pipeline` API also supports async generators: + * + * ```js + * const { pipeline } = require('stream/promises'); + * const fs = require('fs'); + * + * async function run() { + * await pipeline( + * fs.createReadStream('lowercase.txt'), + * async function* (source, signal) { + * source.setEncoding('utf8'); // Work with strings rather than `Buffer`s. + * for await (const chunk of source) { + * yield await processChunk(chunk, { signal }); + * } + * }, + * fs.createWriteStream('uppercase.txt') + * ); + * console.log('Pipeline succeeded.'); + * } + * + * run().catch(console.error); + * ``` + * + * Remember to handle the `signal` argument passed into the async generator. + * Especially in the case where the async generator is the source for the + * pipeline (i.e. first argument) or the pipeline will never complete. + * + * ```js + * const { pipeline } = require('stream/promises'); + * const fs = require('fs'); + * + * async function run() { + * await pipeline( + * async function * (signal) { + * await someLongRunningfn({ signal }); + * yield 'asd'; + * }, + * fs.createWriteStream('uppercase.txt') + * ); + * console.log('Pipeline succeeded.'); + * } + * + * run().catch(console.error); + * ``` + * + * `stream.pipeline()` will call `stream.destroy(err)` on all streams except: + * + * * `Readable` streams which have emitted `'end'` or `'close'`. + * * `Writable` streams which have emitted `'finish'` or `'close'`. + * + * `stream.pipeline()` leaves dangling event listeners on the streams + * after the `callback` has been invoked. In the case of reuse of streams after + * failure, this can cause event listener leaks and swallowed errors. + * @since v10.0.0 + * @param callback Called when the pipeline is fully done. + */ +export function pipeline< + A extends PipelineSource, + B extends PipelineDestination, +>( + source: A, + destination: B, + callback?: PipelineCallback, +): B extends WritableStream ? B : WritableStream; +export function pipeline< + A extends PipelineSource, + T1 extends PipelineTransform, + B extends PipelineDestination, +>( + source: A, + transform1: T1, + destination: B, + callback?: PipelineCallback, +): B extends WritableStream ? B : WritableStream; +export function pipeline< + A extends PipelineSource, + T1 extends PipelineTransform, + T2 extends PipelineTransform, + B extends PipelineDestination, +>( + source: A, + transform1: T1, + transform2: T2, + destination: B, + callback?: PipelineCallback, +): B extends WritableStream ? B : WritableStream; +export function pipeline< + A extends PipelineSource, + T1 extends PipelineTransform, + T2 extends PipelineTransform, + T3 extends PipelineTransform, + B extends PipelineDestination, +>( + source: A, + transform1: T1, + transform2: T2, + transform3: T3, + destination: B, + callback?: PipelineCallback, +): B extends WritableStream ? B : WritableStream; +export function pipeline< + A extends PipelineSource, + T1 extends PipelineTransform, + T2 extends PipelineTransform, + T3 extends PipelineTransform, + T4 extends PipelineTransform, + B extends PipelineDestination, +>( + source: A, + transform1: T1, + transform2: T2, + transform3: T3, + transform4: T4, + destination: B, + callback?: PipelineCallback, +): B extends WritableStream ? B : WritableStream; +export function pipeline( + streams: ReadonlyArray< + ReadableStream | WritableStream | ReadWriteStream + >, + callback?: (err: ErrnoException | null) => void, +): WritableStream; +export function pipeline( + stream1: ReadableStream, + stream2: ReadWriteStream | WritableStream, + ...streams: Array< + | ReadWriteStream + | WritableStream + | ((err: ErrnoException | null) => void) + > +): WritableStream; +export namespace pipeline { + function __promisify__< + A extends PipelineSource, + B extends PipelineDestination, + >(source: A, destination: B, options?: PipelineOptions): PipelinePromise; + function __promisify__< + A extends PipelineSource, + T1 extends PipelineTransform, + B extends PipelineDestination, + >( + source: A, + transform1: T1, + destination: B, + options?: PipelineOptions, + ): PipelinePromise; + function __promisify__< + A extends PipelineSource, + T1 extends PipelineTransform, + T2 extends PipelineTransform, + B extends PipelineDestination, + >( + source: A, + transform1: T1, + transform2: T2, + destination: B, + options?: PipelineOptions, + ): PipelinePromise; + function __promisify__< + A extends PipelineSource, + T1 extends PipelineTransform, + T2 extends PipelineTransform, + T3 extends PipelineTransform, + B extends PipelineDestination, + >( + source: A, + transform1: T1, + transform2: T2, + transform3: T3, + destination: B, + options?: PipelineOptions, + ): PipelinePromise; + function __promisify__< + A extends PipelineSource, + T1 extends PipelineTransform, + T2 extends PipelineTransform, + T3 extends PipelineTransform, + T4 extends PipelineTransform, + B extends PipelineDestination, + >( + source: A, + transform1: T1, + transform2: T2, + transform3: T3, + transform4: T4, + destination: B, + options?: PipelineOptions, + ): PipelinePromise; + function __promisify__( + streams: ReadonlyArray< + ReadableStream | WritableStream | ReadWriteStream + >, + options?: PipelineOptions, + ): Promise; + function __promisify__( + stream1: ReadableStream, + stream2: ReadWriteStream | WritableStream, + ...streams: Array< + ReadWriteStream | WritableStream | PipelineOptions + > + ): Promise; +} +interface Pipe { + close(): void; + hasRef(): boolean; + ref(): void; + unref(): void; +} + +// These have to be at the bottom of the file to work correctly, for some reason +export function _uint8ArrayToBuffer(chunk: Uint8Array): Buffer; +export function _isUint8Array(value: unknown): value is Uint8Array; diff --git a/ext/node/polyfills/_stream.mjs b/ext/node/polyfills/_stream.mjs new file mode 100644 index 00000000000000..d0749fbe05ff72 --- /dev/null +++ b/ext/node/polyfills/_stream.mjs @@ -0,0 +1,748 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// deno-fmt-ignore-file +// deno-lint-ignore-file +import { nextTick } from "internal:deno_node/polyfills/_next_tick.ts"; +import { stdio } from "internal:deno_node/polyfills/_process/stdio.mjs"; +import { AbortController } from "internal:deno_web/03_abort_signal.js"; +import { Blob } from "internal:deno_web/09_file.js"; + +/* esm.sh - esbuild bundle(readable-stream@4.2.0) es2022 production */ +const __process$ = { nextTick, stdio };import __buffer$ from "internal:deno_node/polyfills/buffer.ts";import __string_decoder$ from "internal:deno_node/polyfills/string_decoder.ts";import __events$ from "internal:deno_node/polyfills/events.ts";var pi=Object.create;var Bt=Object.defineProperty;var wi=Object.getOwnPropertyDescriptor;var yi=Object.getOwnPropertyNames;var gi=Object.getPrototypeOf,Si=Object.prototype.hasOwnProperty;var E=(e=>typeof require<"u"?require:typeof Proxy<"u"?new Proxy(e,{get:(t,n)=>(typeof require<"u"?require:t)[n]}):e)(function(e){if(typeof require<"u")return require.apply(this,arguments);throw new Error('Dynamic require of "'+e+'" is not supported')});var g=(e,t)=>()=>(t||e((t={exports:{}}).exports,t),t.exports);var Ei=(e,t,n,r)=>{if(t&&typeof t=="object"||typeof t=="function")for(let i of yi(t))!Si.call(e,i)&&i!==n&&Bt(e,i,{get:()=>t[i],enumerable:!(r=wi(t,i))||r.enumerable});return e};var Ri=(e,t,n)=>(n=e!=null?pi(gi(e)):{},Ei(t||!e||!e.__esModule?Bt(n,"default",{value:e,enumerable:!0}):n,e));var m=g((Yf,Gt)=>{"use strict";Gt.exports={ArrayIsArray(e){return Array.isArray(e)},ArrayPrototypeIncludes(e,t){return e.includes(t)},ArrayPrototypeIndexOf(e,t){return e.indexOf(t)},ArrayPrototypeJoin(e,t){return e.join(t)},ArrayPrototypeMap(e,t){return e.map(t)},ArrayPrototypePop(e,t){return e.pop(t)},ArrayPrototypePush(e,t){return e.push(t)},ArrayPrototypeSlice(e,t,n){return e.slice(t,n)},Error,FunctionPrototypeCall(e,t,...n){return e.call(t,...n)},FunctionPrototypeSymbolHasInstance(e,t){return Function.prototype[Symbol.hasInstance].call(e,t)},MathFloor:Math.floor,Number,NumberIsInteger:Number.isInteger,NumberIsNaN:Number.isNaN,NumberMAX_SAFE_INTEGER:Number.MAX_SAFE_INTEGER,NumberMIN_SAFE_INTEGER:Number.MIN_SAFE_INTEGER,NumberParseInt:Number.parseInt,ObjectDefineProperties(e,t){return Object.defineProperties(e,t)},ObjectDefineProperty(e,t,n){return Object.defineProperty(e,t,n)},ObjectGetOwnPropertyDescriptor(e,t){return Object.getOwnPropertyDescriptor(e,t)},ObjectKeys(e){return Object.keys(e)},ObjectSetPrototypeOf(e,t){return Object.setPrototypeOf(e,t)},Promise,PromisePrototypeCatch(e,t){return e.catch(t)},PromisePrototypeThen(e,t,n){return e.then(t,n)},PromiseReject(e){return Promise.reject(e)},ReflectApply:Reflect.apply,RegExpPrototypeTest(e,t){return e.test(t)},SafeSet:Set,String,StringPrototypeSlice(e,t,n){return e.slice(t,n)},StringPrototypeToLowerCase(e){return e.toLowerCase()},StringPrototypeToUpperCase(e){return e.toUpperCase()},StringPrototypeTrim(e){return e.trim()},Symbol,SymbolAsyncIterator:Symbol.asyncIterator,SymbolHasInstance:Symbol.hasInstance,SymbolIterator:Symbol.iterator,TypedArrayPrototypeSet(e,t,n){return e.set(t,n)},Uint8Array}});var j=g((Kf,Je)=>{"use strict";var Ai=__buffer$,mi=Object.getPrototypeOf(async function(){}).constructor,Ht=Blob||Ai.Blob,Ti=typeof Ht<"u"?function(t){return t instanceof Ht}:function(t){return!1},Xe=class extends Error{constructor(t){if(!Array.isArray(t))throw new TypeError(`Expected input to be an Array, got ${typeof t}`);let n="";for(let r=0;r{e=r,t=i}),resolve:e,reject:t}},promisify(e){return new Promise((t,n)=>{e((r,...i)=>r?n(r):t(...i))})},debuglog(){return function(){}},format(e,...t){return e.replace(/%([sdifj])/g,function(...[n,r]){let i=t.shift();return r==="f"?i.toFixed(6):r==="j"?JSON.stringify(i):r==="s"&&typeof i=="object"?`${i.constructor!==Object?i.constructor.name:""} {}`.trim():i.toString()})},inspect(e){switch(typeof e){case"string":if(e.includes("'"))if(e.includes('"')){if(!e.includes("`")&&!e.includes("${"))return`\`${e}\``}else return`"${e}"`;return`'${e}'`;case"number":return isNaN(e)?"NaN":Object.is(e,-0)?String(e):e;case"bigint":return`${String(e)}n`;case"boolean":case"undefined":return String(e);case"object":return"{}"}},types:{isAsyncFunction(e){return e instanceof mi},isArrayBufferView(e){return ArrayBuffer.isView(e)}},isBlob:Ti};Je.exports.promisify.custom=Symbol.for("nodejs.util.promisify.custom")});var O=g((zf,Kt)=>{"use strict";var{format:Ii,inspect:Re,AggregateError:Mi}=j(),Ni=globalThis.AggregateError||Mi,Di=Symbol("kIsNodeError"),Oi=["string","function","number","object","Function","Object","boolean","bigint","symbol"],qi=/^([A-Z][a-z0-9]*)+$/,xi="__node_internal_",Ae={};function X(e,t){if(!e)throw new Ae.ERR_INTERNAL_ASSERTION(t)}function Vt(e){let t="",n=e.length,r=e[0]==="-"?1:0;for(;n>=r+4;n-=3)t=`_${e.slice(n-3,n)}${t}`;return`${e.slice(0,n)}${t}`}function Li(e,t,n){if(typeof t=="function")return X(t.length<=n.length,`Code: ${e}; The provided arguments length (${n.length}) does not match the required ones (${t.length}).`),t(...n);let r=(t.match(/%[dfijoOs]/g)||[]).length;return X(r===n.length,`Code: ${e}; The provided arguments length (${n.length}) does not match the required ones (${r}).`),n.length===0?t:Ii(t,...n)}function N(e,t,n){n||(n=Error);class r extends n{constructor(...o){super(Li(e,t,o))}toString(){return`${this.name} [${e}]: ${this.message}`}}Object.defineProperties(r.prototype,{name:{value:n.name,writable:!0,enumerable:!1,configurable:!0},toString:{value(){return`${this.name} [${e}]: ${this.message}`},writable:!0,enumerable:!1,configurable:!0}}),r.prototype.code=e,r.prototype[Di]=!0,Ae[e]=r}function Yt(e){let t=xi+e.name;return Object.defineProperty(e,"name",{value:t}),e}function Pi(e,t){if(e&&t&&e!==t){if(Array.isArray(t.errors))return t.errors.push(e),t;let n=new Ni([t,e],t.message);return n.code=t.code,n}return e||t}var Qe=class extends Error{constructor(t="The operation was aborted",n=void 0){if(n!==void 0&&typeof n!="object")throw new Ae.ERR_INVALID_ARG_TYPE("options","Object",n);super(t,n),this.code="ABORT_ERR",this.name="AbortError"}};N("ERR_ASSERTION","%s",Error);N("ERR_INVALID_ARG_TYPE",(e,t,n)=>{X(typeof e=="string","'name' must be a string"),Array.isArray(t)||(t=[t]);let r="The ";e.endsWith(" argument")?r+=`${e} `:r+=`"${e}" ${e.includes(".")?"property":"argument"} `,r+="must be ";let i=[],o=[],l=[];for(let f of t)X(typeof f=="string","All expected entries have to be of type string"),Oi.includes(f)?i.push(f.toLowerCase()):qi.test(f)?o.push(f):(X(f!=="object",'The value "object" should be written as "Object"'),l.push(f));if(o.length>0){let f=i.indexOf("object");f!==-1&&(i.splice(i,f,1),o.push("Object"))}if(i.length>0){switch(i.length){case 1:r+=`of type ${i[0]}`;break;case 2:r+=`one of type ${i[0]} or ${i[1]}`;break;default:{let f=i.pop();r+=`one of type ${i.join(", ")}, or ${f}`}}(o.length>0||l.length>0)&&(r+=" or ")}if(o.length>0){switch(o.length){case 1:r+=`an instance of ${o[0]}`;break;case 2:r+=`an instance of ${o[0]} or ${o[1]}`;break;default:{let f=o.pop();r+=`an instance of ${o.join(", ")}, or ${f}`}}l.length>0&&(r+=" or ")}switch(l.length){case 0:break;case 1:l[0].toLowerCase()!==l[0]&&(r+="an "),r+=`${l[0]}`;break;case 2:r+=`one of ${l[0]} or ${l[1]}`;break;default:{let f=l.pop();r+=`one of ${l.join(", ")}, or ${f}`}}if(n==null)r+=`. Received ${n}`;else if(typeof n=="function"&&n.name)r+=`. Received function ${n.name}`;else if(typeof n=="object"){var u;(u=n.constructor)!==null&&u!==void 0&&u.name?r+=`. Received an instance of ${n.constructor.name}`:r+=`. Received ${Re(n,{depth:-1})}`}else{let f=Re(n,{colors:!1});f.length>25&&(f=`${f.slice(0,25)}...`),r+=`. Received type ${typeof n} (${f})`}return r},TypeError);N("ERR_INVALID_ARG_VALUE",(e,t,n="is invalid")=>{let r=Re(t);return r.length>128&&(r=r.slice(0,128)+"..."),`The ${e.includes(".")?"property":"argument"} '${e}' ${n}. Received ${r}`},TypeError);N("ERR_INVALID_RETURN_VALUE",(e,t,n)=>{var r;let i=n!=null&&(r=n.constructor)!==null&&r!==void 0&&r.name?`instance of ${n.constructor.name}`:`type ${typeof n}`;return`Expected ${e} to be returned from the "${t}" function but got ${i}.`},TypeError);N("ERR_MISSING_ARGS",(...e)=>{X(e.length>0,"At least one arg needs to be specified");let t,n=e.length;switch(e=(Array.isArray(e)?e:[e]).map(r=>`"${r}"`).join(" or "),n){case 1:t+=`The ${e[0]} argument`;break;case 2:t+=`The ${e[0]} and ${e[1]} arguments`;break;default:{let r=e.pop();t+=`The ${e.join(", ")}, and ${r} arguments`}break}return`${t} must be specified`},TypeError);N("ERR_OUT_OF_RANGE",(e,t,n)=>{X(t,'Missing "range" argument');let r;return Number.isInteger(n)&&Math.abs(n)>2**32?r=Vt(String(n)):typeof n=="bigint"?(r=String(n),(n>2n**32n||n<-(2n**32n))&&(r=Vt(r)),r+="n"):r=Re(n),`The value of "${e}" is out of range. It must be ${t}. Received ${r}`},RangeError);N("ERR_MULTIPLE_CALLBACK","Callback called multiple times",Error);N("ERR_METHOD_NOT_IMPLEMENTED","The %s method is not implemented",Error);N("ERR_STREAM_ALREADY_FINISHED","Cannot call %s after a stream was finished",Error);N("ERR_STREAM_CANNOT_PIPE","Cannot pipe, not readable",Error);N("ERR_STREAM_DESTROYED","Cannot call %s after a stream was destroyed",Error);N("ERR_STREAM_NULL_VALUES","May not write null values to stream",TypeError);N("ERR_STREAM_PREMATURE_CLOSE","Premature close",Error);N("ERR_STREAM_PUSH_AFTER_EOF","stream.push() after EOF",Error);N("ERR_STREAM_UNSHIFT_AFTER_END_EVENT","stream.unshift() after end event",Error);N("ERR_STREAM_WRITE_AFTER_END","write after end",Error);N("ERR_UNKNOWN_ENCODING","Unknown encoding: %s",TypeError);Kt.exports={AbortError:Qe,aggregateTwoErrors:Yt(Pi),hideStackFrames:Yt,codes:Ae}});var _e=g((Xf,nn)=>{"use strict";var{ArrayIsArray:Jt,ArrayPrototypeIncludes:Qt,ArrayPrototypeJoin:Zt,ArrayPrototypeMap:ki,NumberIsInteger:et,NumberIsNaN:Wi,NumberMAX_SAFE_INTEGER:Ci,NumberMIN_SAFE_INTEGER:ji,NumberParseInt:$i,ObjectPrototypeHasOwnProperty:vi,RegExpPrototypeExec:Fi,String:Ui,StringPrototypeToUpperCase:Bi,StringPrototypeTrim:Gi}=m(),{hideStackFrames:k,codes:{ERR_SOCKET_BAD_PORT:Hi,ERR_INVALID_ARG_TYPE:q,ERR_INVALID_ARG_VALUE:me,ERR_OUT_OF_RANGE:J,ERR_UNKNOWN_SIGNAL:zt}}=O(),{normalizeEncoding:Vi}=j(),{isAsyncFunction:Yi,isArrayBufferView:Ki}=j().types,Xt={};function zi(e){return e===(e|0)}function Xi(e){return e===e>>>0}var Ji=/^[0-7]+$/,Qi="must be a 32-bit unsigned integer or an octal string";function Zi(e,t,n){if(typeof e>"u"&&(e=n),typeof e=="string"){if(Fi(Ji,e)===null)throw new me(t,e,Qi);e=$i(e,8)}return en(e,t),e}var eo=k((e,t,n=ji,r=Ci)=>{if(typeof e!="number")throw new q(t,"number",e);if(!et(e))throw new J(t,"an integer",e);if(er)throw new J(t,`>= ${n} && <= ${r}`,e)}),to=k((e,t,n=-2147483648,r=2147483647)=>{if(typeof e!="number")throw new q(t,"number",e);if(!et(e))throw new J(t,"an integer",e);if(er)throw new J(t,`>= ${n} && <= ${r}`,e)}),en=k((e,t,n=!1)=>{if(typeof e!="number")throw new q(t,"number",e);if(!et(e))throw new J(t,"an integer",e);let r=n?1:0,i=4294967295;if(ei)throw new J(t,`>= ${r} && <= ${i}`,e)});function tn(e,t){if(typeof e!="string")throw new q(t,"string",e)}function no(e,t,n=void 0,r){if(typeof e!="number")throw new q(t,"number",e);if(n!=null&&er||(n!=null||r!=null)&&Wi(e))throw new J(t,`${n!=null?`>= ${n}`:""}${n!=null&&r!=null?" && ":""}${r!=null?`<= ${r}`:""}`,e)}var ro=k((e,t,n)=>{if(!Qt(n,e)){let r=Zt(ki(n,o=>typeof o=="string"?`'${o}'`:Ui(o)),", "),i="must be one of: "+r;throw new me(t,e,i)}});function io(e,t){if(typeof e!="boolean")throw new q(t,"boolean",e)}function Ze(e,t,n){return e==null||!vi(e,t)?n:e[t]}var oo=k((e,t,n=null)=>{let r=Ze(n,"allowArray",!1),i=Ze(n,"allowFunction",!1);if(!Ze(n,"nullable",!1)&&e===null||!r&&Jt(e)||typeof e!="object"&&(!i||typeof e!="function"))throw new q(t,"Object",e)}),lo=k((e,t,n=0)=>{if(!Jt(e))throw new q(t,"Array",e);if(e.length{if(!Ki(e))throw new q(t,["Buffer","TypedArray","DataView"],e)});function uo(e,t){let n=Vi(t),r=e.length;if(n==="hex"&&r%2!==0)throw new me("encoding",t,`is invalid for data of length ${r}`)}function so(e,t="Port",n=!0){if(typeof e!="number"&&typeof e!="string"||typeof e=="string"&&Gi(e).length===0||+e!==+e>>>0||e>65535||e===0&&!n)throw new Hi(t,e,n);return e|0}var co=k((e,t)=>{if(e!==void 0&&(e===null||typeof e!="object"||!("aborted"in e)))throw new q(t,"AbortSignal",e)}),ho=k((e,t)=>{if(typeof e!="function")throw new q(t,"Function",e)}),bo=k((e,t)=>{if(typeof e!="function"||Yi(e))throw new q(t,"Function",e)}),_o=k((e,t)=>{if(e!==void 0)throw new q(t,"undefined",e)});function po(e,t,n){if(!Qt(n,e))throw new q(t,`('${Zt(n,"|")}')`,e)}nn.exports={isInt32:zi,isUint32:Xi,parseFileMode:Zi,validateArray:lo,validateBoolean:io,validateBuffer:fo,validateEncoding:uo,validateFunction:ho,validateInt32:to,validateInteger:eo,validateNumber:no,validateObject:oo,validateOneOf:ro,validatePlainFunction:bo,validatePort:so,validateSignalName:ao,validateString:tn,validateUint32:en,validateUndefined:_o,validateUnion:po,validateAbortSignal:co}});var V=g((Jf,_n)=>{"use strict";var{Symbol:Te,SymbolAsyncIterator:rn,SymbolIterator:on}=m(),ln=Te("kDestroyed"),an=Te("kIsErrored"),tt=Te("kIsReadable"),fn=Te("kIsDisturbed");function Ie(e,t=!1){var n;return!!(e&&typeof e.pipe=="function"&&typeof e.on=="function"&&(!t||typeof e.pause=="function"&&typeof e.resume=="function")&&(!e._writableState||((n=e._readableState)===null||n===void 0?void 0:n.readable)!==!1)&&(!e._writableState||e._readableState))}function Me(e){var t;return!!(e&&typeof e.write=="function"&&typeof e.on=="function"&&(!e._readableState||((t=e._writableState)===null||t===void 0?void 0:t.writable)!==!1))}function wo(e){return!!(e&&typeof e.pipe=="function"&&e._readableState&&typeof e.on=="function"&&typeof e.write=="function")}function Q(e){return e&&(e._readableState||e._writableState||typeof e.write=="function"&&typeof e.on=="function"||typeof e.pipe=="function"&&typeof e.on=="function")}function yo(e,t){return e==null?!1:t===!0?typeof e[rn]=="function":t===!1?typeof e[on]=="function":typeof e[rn]=="function"||typeof e[on]=="function"}function Ne(e){if(!Q(e))return null;let t=e._writableState,n=e._readableState,r=t||n;return!!(e.destroyed||e[ln]||r!=null&&r.destroyed)}function un(e){if(!Me(e))return null;if(e.writableEnded===!0)return!0;let t=e._writableState;return t!=null&&t.errored?!1:typeof t?.ended!="boolean"?null:t.ended}function go(e,t){if(!Me(e))return null;if(e.writableFinished===!0)return!0;let n=e._writableState;return n!=null&&n.errored?!1:typeof n?.finished!="boolean"?null:!!(n.finished||t===!1&&n.ended===!0&&n.length===0)}function So(e){if(!Ie(e))return null;if(e.readableEnded===!0)return!0;let t=e._readableState;return!t||t.errored?!1:typeof t?.ended!="boolean"?null:t.ended}function sn(e,t){if(!Ie(e))return null;let n=e._readableState;return n!=null&&n.errored?!1:typeof n?.endEmitted!="boolean"?null:!!(n.endEmitted||t===!1&&n.ended===!0&&n.length===0)}function dn(e){return e&&e[tt]!=null?e[tt]:typeof e?.readable!="boolean"?null:Ne(e)?!1:Ie(e)&&e.readable&&!sn(e)}function cn(e){return typeof e?.writable!="boolean"?null:Ne(e)?!1:Me(e)&&e.writable&&!un(e)}function Eo(e,t){return Q(e)?Ne(e)?!0:!(t?.readable!==!1&&dn(e)||t?.writable!==!1&&cn(e)):null}function Ro(e){var t,n;return Q(e)?e.writableErrored?e.writableErrored:(t=(n=e._writableState)===null||n===void 0?void 0:n.errored)!==null&&t!==void 0?t:null:null}function Ao(e){var t,n;return Q(e)?e.readableErrored?e.readableErrored:(t=(n=e._readableState)===null||n===void 0?void 0:n.errored)!==null&&t!==void 0?t:null:null}function mo(e){if(!Q(e))return null;if(typeof e.closed=="boolean")return e.closed;let t=e._writableState,n=e._readableState;return typeof t?.closed=="boolean"||typeof n?.closed=="boolean"?t?.closed||n?.closed:typeof e._closed=="boolean"&&hn(e)?e._closed:null}function hn(e){return typeof e._closed=="boolean"&&typeof e._defaultKeepAlive=="boolean"&&typeof e._removedConnection=="boolean"&&typeof e._removedContLen=="boolean"}function bn(e){return typeof e._sent100=="boolean"&&hn(e)}function To(e){var t;return typeof e._consuming=="boolean"&&typeof e._dumped=="boolean"&&((t=e.req)===null||t===void 0?void 0:t.upgradeOrConnect)===void 0}function Io(e){if(!Q(e))return null;let t=e._writableState,n=e._readableState,r=t||n;return!r&&bn(e)||!!(r&&r.autoDestroy&&r.emitClose&&r.closed===!1)}function Mo(e){var t;return!!(e&&((t=e[fn])!==null&&t!==void 0?t:e.readableDidRead||e.readableAborted))}function No(e){var t,n,r,i,o,l,u,f,a,c;return!!(e&&((t=(n=(r=(i=(o=(l=e[an])!==null&&l!==void 0?l:e.readableErrored)!==null&&o!==void 0?o:e.writableErrored)!==null&&i!==void 0?i:(u=e._readableState)===null||u===void 0?void 0:u.errorEmitted)!==null&&r!==void 0?r:(f=e._writableState)===null||f===void 0?void 0:f.errorEmitted)!==null&&n!==void 0?n:(a=e._readableState)===null||a===void 0?void 0:a.errored)!==null&&t!==void 0?t:(c=e._writableState)===null||c===void 0?void 0:c.errored))}_n.exports={kDestroyed:ln,isDisturbed:Mo,kIsDisturbed:fn,isErrored:No,kIsErrored:an,isReadable:dn,kIsReadable:tt,isClosed:mo,isDestroyed:Ne,isDuplexNodeStream:wo,isFinished:Eo,isIterable:yo,isReadableNodeStream:Ie,isReadableEnded:So,isReadableFinished:sn,isReadableErrored:Ao,isNodeStream:Q,isWritable:cn,isWritableNodeStream:Me,isWritableEnded:un,isWritableFinished:go,isWritableErrored:Ro,isServerRequest:To,isServerResponse:bn,willEmitClose:Io}});var Y=g((Qf,rt)=>{var oe=__process$,{AbortError:Do,codes:Oo}=O(),{ERR_INVALID_ARG_TYPE:qo,ERR_STREAM_PREMATURE_CLOSE:pn}=Oo,{kEmptyObject:wn,once:yn}=j(),{validateAbortSignal:xo,validateFunction:Lo,validateObject:Po}=_e(),{Promise:ko}=m(),{isClosed:Wo,isReadable:gn,isReadableNodeStream:nt,isReadableFinished:Sn,isReadableErrored:Co,isWritable:En,isWritableNodeStream:Rn,isWritableFinished:An,isWritableErrored:jo,isNodeStream:$o,willEmitClose:vo}=V();function Fo(e){return e.setHeader&&typeof e.abort=="function"}var Uo=()=>{};function mn(e,t,n){var r,i;arguments.length===2?(n=t,t=wn):t==null?t=wn:Po(t,"options"),Lo(n,"callback"),xo(t.signal,"options.signal"),n=yn(n);let o=(r=t.readable)!==null&&r!==void 0?r:nt(e),l=(i=t.writable)!==null&&i!==void 0?i:Rn(e);if(!$o(e))throw new qo("stream","Stream",e);let u=e._writableState,f=e._readableState,a=()=>{e.writable||b()},c=vo(e)&&nt(e)===o&&Rn(e)===l,s=An(e,!1),b=()=>{s=!0,e.destroyed&&(c=!1),!(c&&(!e.readable||o))&&(!o||d)&&n.call(e)},d=Sn(e,!1),h=()=>{d=!0,e.destroyed&&(c=!1),!(c&&(!e.writable||l))&&(!l||s)&&n.call(e)},D=M=>{n.call(e,M)},L=Wo(e),_=()=>{L=!0;let M=jo(e)||Co(e);if(M&&typeof M!="boolean")return n.call(e,M);if(o&&!d&&nt(e,!0)&&!Sn(e,!1))return n.call(e,new pn);if(l&&!s&&!An(e,!1))return n.call(e,new pn);n.call(e)},p=()=>{e.req.on("finish",b)};Fo(e)?(e.on("complete",b),c||e.on("abort",_),e.req?p():e.on("request",p)):l&&!u&&(e.on("end",a),e.on("close",a)),!c&&typeof e.aborted=="boolean"&&e.on("aborted",_),e.on("end",h),e.on("finish",b),t.error!==!1&&e.on("error",D),e.on("close",_),L?oe.nextTick(_):u!=null&&u.errorEmitted||f!=null&&f.errorEmitted?c||oe.nextTick(_):(!o&&(!c||gn(e))&&(s||En(e)===!1)||!l&&(!c||En(e))&&(d||gn(e)===!1)||f&&e.req&&e.aborted)&&oe.nextTick(_);let I=()=>{n=Uo,e.removeListener("aborted",_),e.removeListener("complete",b),e.removeListener("abort",_),e.removeListener("request",p),e.req&&e.req.removeListener("finish",b),e.removeListener("end",a),e.removeListener("close",a),e.removeListener("finish",b),e.removeListener("end",h),e.removeListener("error",D),e.removeListener("close",_)};if(t.signal&&!L){let M=()=>{let F=n;I(),F.call(e,new Do(void 0,{cause:t.signal.reason}))};if(t.signal.aborted)oe.nextTick(M);else{let F=n;n=yn((...re)=>{t.signal.removeEventListener("abort",M),F.apply(e,re)}),t.signal.addEventListener("abort",M)}}return I}function Bo(e,t){return new ko((n,r)=>{mn(e,t,i=>{i?r(i):n()})})}rt.exports=mn;rt.exports.finished=Bo});var xn=g((Zf,lt)=>{"use strict";var Nn=AbortController,{codes:{ERR_INVALID_ARG_TYPE:pe,ERR_MISSING_ARGS:Go,ERR_OUT_OF_RANGE:Ho},AbortError:$}=O(),{validateAbortSignal:le,validateInteger:Vo,validateObject:ae}=_e(),Yo=m().Symbol("kWeak"),{finished:Ko}=Y(),{ArrayPrototypePush:zo,MathFloor:Xo,Number:Jo,NumberIsNaN:Qo,Promise:Tn,PromiseReject:In,PromisePrototypeThen:Zo,Symbol:Dn}=m(),De=Dn("kEmpty"),Mn=Dn("kEof");function Oe(e,t){if(typeof e!="function")throw new pe("fn",["Function","AsyncFunction"],e);t!=null&&ae(t,"options"),t?.signal!=null&&le(t.signal,"options.signal");let n=1;return t?.concurrency!=null&&(n=Xo(t.concurrency)),Vo(n,"concurrency",1),async function*(){var i,o;let l=new Nn,u=this,f=[],a=l.signal,c={signal:a},s=()=>l.abort();t!=null&&(i=t.signal)!==null&&i!==void 0&&i.aborted&&s(),t==null||(o=t.signal)===null||o===void 0||o.addEventListener("abort",s);let b,d,h=!1;function D(){h=!0}async function L(){try{for await(let I of u){var _;if(h)return;if(a.aborted)throw new $;try{I=e(I,c)}catch(M){I=In(M)}I!==De&&(typeof((_=I)===null||_===void 0?void 0:_.catch)=="function"&&I.catch(D),f.push(I),b&&(b(),b=null),!h&&f.length&&f.length>=n&&await new Tn(M=>{d=M}))}f.push(Mn)}catch(I){let M=In(I);Zo(M,void 0,D),f.push(M)}finally{var p;h=!0,b&&(b(),b=null),t==null||(p=t.signal)===null||p===void 0||p.removeEventListener("abort",s)}}L();try{for(;;){for(;f.length>0;){let _=await f[0];if(_===Mn)return;if(a.aborted)throw new $;_!==De&&(yield _),f.shift(),d&&(d(),d=null)}await new Tn(_=>{b=_})}}finally{l.abort(),h=!0,d&&(d(),d=null)}}.call(this)}function el(e=void 0){return e!=null&&ae(e,"options"),e?.signal!=null&&le(e.signal,"options.signal"),async function*(){let n=0;for await(let i of this){var r;if(e!=null&&(r=e.signal)!==null&&r!==void 0&&r.aborted)throw new $({cause:e.signal.reason});yield[n++,i]}}.call(this)}async function On(e,t=void 0){for await(let n of ot.call(this,e,t))return!0;return!1}async function tl(e,t=void 0){if(typeof e!="function")throw new pe("fn",["Function","AsyncFunction"],e);return!await On.call(this,async(...n)=>!await e(...n),t)}async function nl(e,t){for await(let n of ot.call(this,e,t))return n}async function rl(e,t){if(typeof e!="function")throw new pe("fn",["Function","AsyncFunction"],e);async function n(r,i){return await e(r,i),De}for await(let r of Oe.call(this,n,t));}function ot(e,t){if(typeof e!="function")throw new pe("fn",["Function","AsyncFunction"],e);async function n(r,i){return await e(r,i)?r:De}return Oe.call(this,n,t)}var it=class extends Go{constructor(){super("reduce"),this.message="Reduce of an empty stream requires an initial value"}};async function il(e,t,n){var r;if(typeof e!="function")throw new pe("reducer",["Function","AsyncFunction"],e);n!=null&&ae(n,"options"),n?.signal!=null&&le(n.signal,"options.signal");let i=arguments.length>1;if(n!=null&&(r=n.signal)!==null&&r!==void 0&&r.aborted){let a=new $(void 0,{cause:n.signal.reason});throw this.once("error",()=>{}),await Ko(this.destroy(a)),a}let o=new Nn,l=o.signal;if(n!=null&&n.signal){let a={once:!0,[Yo]:this};n.signal.addEventListener("abort",()=>o.abort(),a)}let u=!1;try{for await(let a of this){var f;if(u=!0,n!=null&&(f=n.signal)!==null&&f!==void 0&&f.aborted)throw new $;i?t=await e(t,a,{signal:l}):(t=a,i=!0)}if(!u&&!i)throw new it}finally{o.abort()}return t}async function ol(e){e!=null&&ae(e,"options"),e?.signal!=null&&le(e.signal,"options.signal");let t=[];for await(let r of this){var n;if(e!=null&&(n=e.signal)!==null&&n!==void 0&&n.aborted)throw new $(void 0,{cause:e.signal.reason});zo(t,r)}return t}function ll(e,t){let n=Oe.call(this,e,t);return async function*(){for await(let i of n)yield*i}.call(this)}function qn(e){if(e=Jo(e),Qo(e))return 0;if(e<0)throw new Ho("number",">= 0",e);return e}function al(e,t=void 0){return t!=null&&ae(t,"options"),t?.signal!=null&&le(t.signal,"options.signal"),e=qn(e),async function*(){var r;if(t!=null&&(r=t.signal)!==null&&r!==void 0&&r.aborted)throw new $;for await(let o of this){var i;if(t!=null&&(i=t.signal)!==null&&i!==void 0&&i.aborted)throw new $;e--<=0&&(yield o)}}.call(this)}function fl(e,t=void 0){return t!=null&&ae(t,"options"),t?.signal!=null&&le(t.signal,"options.signal"),e=qn(e),async function*(){var r;if(t!=null&&(r=t.signal)!==null&&r!==void 0&&r.aborted)throw new $;for await(let o of this){var i;if(t!=null&&(i=t.signal)!==null&&i!==void 0&&i.aborted)throw new $;if(e-- >0)yield o;else return}}.call(this)}lt.exports.streamReturningOperators={asIndexedPairs:el,drop:al,filter:ot,flatMap:ll,map:Oe,take:fl};lt.exports.promiseReturningOperators={every:tl,forEach:rl,reduce:il,toArray:ol,some:On,find:nl}});var Z=g((eu,vn)=>{"use strict";var K=__process$,{aggregateTwoErrors:ul,codes:{ERR_MULTIPLE_CALLBACK:sl},AbortError:dl}=O(),{Symbol:kn}=m(),{kDestroyed:cl,isDestroyed:hl,isFinished:bl,isServerRequest:_l}=V(),Wn=kn("kDestroy"),at=kn("kConstruct");function Cn(e,t,n){e&&(e.stack,t&&!t.errored&&(t.errored=e),n&&!n.errored&&(n.errored=e))}function pl(e,t){let n=this._readableState,r=this._writableState,i=r||n;return r&&r.destroyed||n&&n.destroyed?(typeof t=="function"&&t(),this):(Cn(e,r,n),r&&(r.destroyed=!0),n&&(n.destroyed=!0),i.constructed?Ln(this,e,t):this.once(Wn,function(o){Ln(this,ul(o,e),t)}),this)}function Ln(e,t,n){let r=!1;function i(o){if(r)return;r=!0;let l=e._readableState,u=e._writableState;Cn(o,u,l),u&&(u.closed=!0),l&&(l.closed=!0),typeof n=="function"&&n(o),o?K.nextTick(wl,e,o):K.nextTick(jn,e)}try{e._destroy(t||null,i)}catch(o){i(o)}}function wl(e,t){ft(e,t),jn(e)}function jn(e){let t=e._readableState,n=e._writableState;n&&(n.closeEmitted=!0),t&&(t.closeEmitted=!0),(n&&n.emitClose||t&&t.emitClose)&&e.emit("close")}function ft(e,t){let n=e._readableState,r=e._writableState;r&&r.errorEmitted||n&&n.errorEmitted||(r&&(r.errorEmitted=!0),n&&(n.errorEmitted=!0),e.emit("error",t))}function yl(){let e=this._readableState,t=this._writableState;e&&(e.constructed=!0,e.closed=!1,e.closeEmitted=!1,e.destroyed=!1,e.errored=null,e.errorEmitted=!1,e.reading=!1,e.ended=e.readable===!1,e.endEmitted=e.readable===!1),t&&(t.constructed=!0,t.destroyed=!1,t.closed=!1,t.closeEmitted=!1,t.errored=null,t.errorEmitted=!1,t.finalCalled=!1,t.prefinished=!1,t.ended=t.writable===!1,t.ending=t.writable===!1,t.finished=t.writable===!1)}function ut(e,t,n){let r=e._readableState,i=e._writableState;if(i&&i.destroyed||r&&r.destroyed)return this;r&&r.autoDestroy||i&&i.autoDestroy?e.destroy(t):t&&(t.stack,i&&!i.errored&&(i.errored=t),r&&!r.errored&&(r.errored=t),n?K.nextTick(ft,e,t):ft(e,t))}function gl(e,t){if(typeof e._construct!="function")return;let n=e._readableState,r=e._writableState;n&&(n.constructed=!1),r&&(r.constructed=!1),e.once(at,t),!(e.listenerCount(at)>1)&&K.nextTick(Sl,e)}function Sl(e){let t=!1;function n(r){if(t){ut(e,r??new sl);return}t=!0;let i=e._readableState,o=e._writableState,l=o||i;i&&(i.constructed=!0),o&&(o.constructed=!0),l.destroyed?e.emit(Wn,r):r?ut(e,r,!0):K.nextTick(El,e)}try{e._construct(n)}catch(r){n(r)}}function El(e){e.emit(at)}function Pn(e){return e&&e.setHeader&&typeof e.abort=="function"}function $n(e){e.emit("close")}function Rl(e,t){e.emit("error",t),K.nextTick($n,e)}function Al(e,t){!e||hl(e)||(!t&&!bl(e)&&(t=new dl),_l(e)?(e.socket=null,e.destroy(t)):Pn(e)?e.abort():Pn(e.req)?e.req.abort():typeof e.destroy=="function"?e.destroy(t):typeof e.close=="function"?e.close():t?K.nextTick(Rl,e,t):K.nextTick($n,e),e.destroyed||(e[cl]=!0))}vn.exports={construct:gl,destroyer:Al,destroy:pl,undestroy:yl,errorOrDestroy:ut}});var Le=g((tu,Un)=>{"use strict";var{ArrayIsArray:ml,ObjectSetPrototypeOf:Fn}=m(),{EventEmitter:qe}=__events$;function xe(e){qe.call(this,e)}Fn(xe.prototype,qe.prototype);Fn(xe,qe);xe.prototype.pipe=function(e,t){let n=this;function r(c){e.writable&&e.write(c)===!1&&n.pause&&n.pause()}n.on("data",r);function i(){n.readable&&n.resume&&n.resume()}e.on("drain",i),!e._isStdio&&(!t||t.end!==!1)&&(n.on("end",l),n.on("close",u));let o=!1;function l(){o||(o=!0,e.end())}function u(){o||(o=!0,typeof e.destroy=="function"&&e.destroy())}function f(c){a(),qe.listenerCount(this,"error")===0&&this.emit("error",c)}st(n,"error",f),st(e,"error",f);function a(){n.removeListener("data",r),e.removeListener("drain",i),n.removeListener("end",l),n.removeListener("close",u),n.removeListener("error",f),e.removeListener("error",f),n.removeListener("end",a),n.removeListener("close",a),e.removeListener("close",a)}return n.on("end",a),n.on("close",a),e.on("close",a),e.emit("pipe",n),e};function st(e,t,n){if(typeof e.prependListener=="function")return e.prependListener(t,n);!e._events||!e._events[t]?e.on(t,n):ml(e._events[t])?e._events[t].unshift(n):e._events[t]=[n,e._events[t]]}Un.exports={Stream:xe,prependListener:st}});var ke=g((nu,Pe)=>{"use strict";var{AbortError:Tl,codes:Il}=O(),Ml=Y(),{ERR_INVALID_ARG_TYPE:Bn}=Il,Nl=(e,t)=>{if(typeof e!="object"||!("aborted"in e))throw new Bn(t,"AbortSignal",e)};function Dl(e){return!!(e&&typeof e.pipe=="function")}Pe.exports.addAbortSignal=function(t,n){if(Nl(t,"signal"),!Dl(n))throw new Bn("stream","stream.Stream",n);return Pe.exports.addAbortSignalNoValidate(t,n)};Pe.exports.addAbortSignalNoValidate=function(e,t){if(typeof e!="object"||!("aborted"in e))return t;let n=()=>{t.destroy(new Tl(void 0,{cause:e.reason}))};return e.aborted?n():(e.addEventListener("abort",n),Ml(t,()=>e.removeEventListener("abort",n))),t}});var Vn=g((iu,Hn)=>{"use strict";var{StringPrototypeSlice:Gn,SymbolIterator:Ol,TypedArrayPrototypeSet:We,Uint8Array:ql}=m(),{Buffer:dt}=__buffer$,{inspect:xl}=j();Hn.exports=class{constructor(){this.head=null,this.tail=null,this.length=0}push(t){let n={data:t,next:null};this.length>0?this.tail.next=n:this.head=n,this.tail=n,++this.length}unshift(t){let n={data:t,next:this.head};this.length===0&&(this.tail=n),this.head=n,++this.length}shift(){if(this.length===0)return;let t=this.head.data;return this.length===1?this.head=this.tail=null:this.head=this.head.next,--this.length,t}clear(){this.head=this.tail=null,this.length=0}join(t){if(this.length===0)return"";let n=this.head,r=""+n.data;for(;(n=n.next)!==null;)r+=t+n.data;return r}concat(t){if(this.length===0)return dt.alloc(0);let n=dt.allocUnsafe(t>>>0),r=this.head,i=0;for(;r;)We(n,r.data,i),i+=r.data.length,r=r.next;return n}consume(t,n){let r=this.head.data;if(to.length)n+=o,t-=o.length;else{t===o.length?(n+=o,++i,r.next?this.head=r.next:this.head=this.tail=null):(n+=Gn(o,0,t),this.head=r,r.data=Gn(o,t));break}++i}while((r=r.next)!==null);return this.length-=i,n}_getBuffer(t){let n=dt.allocUnsafe(t),r=t,i=this.head,o=0;do{let l=i.data;if(t>l.length)We(n,l,r-t),t-=l.length;else{t===l.length?(We(n,l,r-t),++o,i.next?this.head=i.next:this.head=this.tail=null):(We(n,new ql(l.buffer,l.byteOffset,t),r-t),this.head=i,i.data=l.slice(t));break}++o}while((i=i.next)!==null);return this.length-=o,n}[Symbol.for("nodejs.util.inspect.custom")](t,n){return xl(this,{...n,depth:0,customInspect:!1})}}});var Ce=g((ou,Kn)=>{"use strict";var{MathFloor:Ll,NumberIsInteger:Pl}=m(),{ERR_INVALID_ARG_VALUE:kl}=O().codes;function Wl(e,t,n){return e.highWaterMark!=null?e.highWaterMark:t?e[n]:null}function Yn(e){return e?16:16*1024}function Cl(e,t,n,r){let i=Wl(t,r,n);if(i!=null){if(!Pl(i)||i<0){let o=r?`options.${n}`:"options.highWaterMark";throw new kl(o,i)}return Ll(i)}return Yn(e.objectMode)}Kn.exports={getHighWaterMark:Cl,getDefaultHighWaterMark:Yn}});var ct=g((lu,Qn)=>{"use strict";var zn=__process$,{PromisePrototypeThen:jl,SymbolAsyncIterator:Xn,SymbolIterator:Jn}=m(),{Buffer:$l}=__buffer$,{ERR_INVALID_ARG_TYPE:vl,ERR_STREAM_NULL_VALUES:Fl}=O().codes;function Ul(e,t,n){let r;if(typeof t=="string"||t instanceof $l)return new e({objectMode:!0,...n,read(){this.push(t),this.push(null)}});let i;if(t&&t[Xn])i=!0,r=t[Xn]();else if(t&&t[Jn])i=!1,r=t[Jn]();else throw new vl("iterable",["Iterable"],t);let o=new e({objectMode:!0,highWaterMark:1,...n}),l=!1;o._read=function(){l||(l=!0,f())},o._destroy=function(a,c){jl(u(a),()=>zn.nextTick(c,a),s=>zn.nextTick(c,s||a))};async function u(a){let c=a!=null,s=typeof r.throw=="function";if(c&&s){let{value:b,done:d}=await r.throw(a);if(await b,d)return}if(typeof r.return=="function"){let{value:b}=await r.return();await b}}async function f(){for(;;){try{let{value:a,done:c}=i?await r.next():r.next();if(c)o.push(null);else{let s=a&&typeof a.then=="function"?await a:a;if(s===null)throw l=!1,new Fl;if(o.push(s))continue;l=!1}}catch(a){o.destroy(a)}break}}return o}Qn.exports=Ul});var we=g((au,dr)=>{var W=__process$,{ArrayPrototypeIndexOf:Bl,NumberIsInteger:Gl,NumberIsNaN:Hl,NumberParseInt:Vl,ObjectDefineProperties:tr,ObjectKeys:Yl,ObjectSetPrototypeOf:nr,Promise:Kl,SafeSet:zl,SymbolAsyncIterator:Xl,Symbol:Jl}=m();dr.exports=w;w.ReadableState=yt;var{EventEmitter:Ql}=__events$,{Stream:z,prependListener:Zl}=Le(),{Buffer:ht}=__buffer$,{addAbortSignal:ea}=ke(),ta=Y(),y=j().debuglog("stream",e=>{y=e}),na=Vn(),ue=Z(),{getHighWaterMark:ra,getDefaultHighWaterMark:ia}=Ce(),{aggregateTwoErrors:Zn,codes:{ERR_INVALID_ARG_TYPE:oa,ERR_METHOD_NOT_IMPLEMENTED:la,ERR_OUT_OF_RANGE:aa,ERR_STREAM_PUSH_AFTER_EOF:fa,ERR_STREAM_UNSHIFT_AFTER_END_EVENT:ua}}=O(),{validateObject:sa}=_e(),ee=Jl("kPaused"),{StringDecoder:rr}=__string_decoder$,da=ct();nr(w.prototype,z.prototype);nr(w,z);var bt=()=>{},{errorOrDestroy:fe}=ue;function yt(e,t,n){typeof n!="boolean"&&(n=t instanceof v()),this.objectMode=!!(e&&e.objectMode),n&&(this.objectMode=this.objectMode||!!(e&&e.readableObjectMode)),this.highWaterMark=e?ra(this,e,"readableHighWaterMark",n):ia(!1),this.buffer=new na,this.length=0,this.pipes=[],this.flowing=null,this.ended=!1,this.endEmitted=!1,this.reading=!1,this.constructed=!0,this.sync=!0,this.needReadable=!1,this.emittedReadable=!1,this.readableListening=!1,this.resumeScheduled=!1,this[ee]=null,this.errorEmitted=!1,this.emitClose=!e||e.emitClose!==!1,this.autoDestroy=!e||e.autoDestroy!==!1,this.destroyed=!1,this.errored=null,this.closed=!1,this.closeEmitted=!1,this.defaultEncoding=e&&e.defaultEncoding||"utf8",this.awaitDrainWriters=null,this.multiAwaitDrain=!1,this.readingMore=!1,this.dataEmitted=!1,this.decoder=null,this.encoding=null,e&&e.encoding&&(this.decoder=new rr(e.encoding),this.encoding=e.encoding)}function w(e){if(!(this instanceof w))return new w(e);let t=this instanceof v();this._readableState=new yt(e,this,t),e&&(typeof e.read=="function"&&(this._read=e.read),typeof e.destroy=="function"&&(this._destroy=e.destroy),typeof e.construct=="function"&&(this._construct=e.construct),e.signal&&!t&&ea(e.signal,this)),z.call(this,e),ue.construct(this,()=>{this._readableState.needReadable&&je(this,this._readableState)})}w.prototype.destroy=ue.destroy;w.prototype._undestroy=ue.undestroy;w.prototype._destroy=function(e,t){t(e)};w.prototype[Ql.captureRejectionSymbol]=function(e){this.destroy(e)};w.prototype.push=function(e,t){return ir(this,e,t,!1)};w.prototype.unshift=function(e,t){return ir(this,e,t,!0)};function ir(e,t,n,r){y("readableAddChunk",t);let i=e._readableState,o;if(i.objectMode||(typeof t=="string"?(n=n||i.defaultEncoding,i.encoding!==n&&(r&&i.encoding?t=ht.from(t,n).toString(i.encoding):(t=ht.from(t,n),n=""))):t instanceof ht?n="":z._isUint8Array(t)?(t=z._uint8ArrayToBuffer(t),n=""):t!=null&&(o=new oa("chunk",["string","Buffer","Uint8Array"],t))),o)fe(e,o);else if(t===null)i.reading=!1,ba(e,i);else if(i.objectMode||t&&t.length>0)if(r)if(i.endEmitted)fe(e,new ua);else{if(i.destroyed||i.errored)return!1;_t(e,i,t,!0)}else if(i.ended)fe(e,new fa);else{if(i.destroyed||i.errored)return!1;i.reading=!1,i.decoder&&!n?(t=i.decoder.write(t),i.objectMode||t.length!==0?_t(e,i,t,!1):je(e,i)):_t(e,i,t,!1)}else r||(i.reading=!1,je(e,i));return!i.ended&&(i.length0?(t.multiAwaitDrain?t.awaitDrainWriters.clear():t.awaitDrainWriters=null,t.dataEmitted=!0,e.emit("data",n)):(t.length+=t.objectMode?1:n.length,r?t.buffer.unshift(n):t.buffer.push(n),t.needReadable&&$e(e)),je(e,t)}w.prototype.isPaused=function(){let e=this._readableState;return e[ee]===!0||e.flowing===!1};w.prototype.setEncoding=function(e){let t=new rr(e);this._readableState.decoder=t,this._readableState.encoding=this._readableState.decoder.encoding;let n=this._readableState.buffer,r="";for(let i of n)r+=t.write(i);return n.clear(),r!==""&&n.push(r),this._readableState.length=r.length,this};var ca=1073741824;function ha(e){if(e>ca)throw new aa("size","<= 1GiB",e);return e--,e|=e>>>1,e|=e>>>2,e|=e>>>4,e|=e>>>8,e|=e>>>16,e++,e}function er(e,t){return e<=0||t.length===0&&t.ended?0:t.objectMode?1:Hl(e)?t.flowing&&t.length?t.buffer.first().length:t.length:e<=t.length?e:t.ended?t.length:0}w.prototype.read=function(e){y("read",e),e===void 0?e=NaN:Gl(e)||(e=Vl(e,10));let t=this._readableState,n=e;if(e>t.highWaterMark&&(t.highWaterMark=ha(e)),e!==0&&(t.emittedReadable=!1),e===0&&t.needReadable&&((t.highWaterMark!==0?t.length>=t.highWaterMark:t.length>0)||t.ended))return y("read: emitReadable",t.length,t.ended),t.length===0&&t.ended?pt(this):$e(this),null;if(e=er(e,t),e===0&&t.ended)return t.length===0&&pt(this),null;let r=t.needReadable;if(y("need readable",r),(t.length===0||t.length-e0?i=ur(e,t):i=null,i===null?(t.needReadable=t.length<=t.highWaterMark,e=0):(t.length-=e,t.multiAwaitDrain?t.awaitDrainWriters.clear():t.awaitDrainWriters=null),t.length===0&&(t.ended||(t.needReadable=!0),n!==e&&t.ended&&pt(this)),i!==null&&!t.errorEmitted&&!t.closeEmitted&&(t.dataEmitted=!0,this.emit("data",i)),i};function ba(e,t){if(y("onEofChunk"),!t.ended){if(t.decoder){let n=t.decoder.end();n&&n.length&&(t.buffer.push(n),t.length+=t.objectMode?1:n.length)}t.ended=!0,t.sync?$e(e):(t.needReadable=!1,t.emittedReadable=!0,or(e))}}function $e(e){let t=e._readableState;y("emitReadable",t.needReadable,t.emittedReadable),t.needReadable=!1,t.emittedReadable||(y("emitReadable",t.flowing),t.emittedReadable=!0,W.nextTick(or,e))}function or(e){let t=e._readableState;y("emitReadable_",t.destroyed,t.length,t.ended),!t.destroyed&&!t.errored&&(t.length||t.ended)&&(e.emit("readable"),t.emittedReadable=!1),t.needReadable=!t.flowing&&!t.ended&&t.length<=t.highWaterMark,ar(e)}function je(e,t){!t.readingMore&&t.constructed&&(t.readingMore=!0,W.nextTick(_a,e,t))}function _a(e,t){for(;!t.reading&&!t.ended&&(t.length1&&r.pipes.includes(e)&&(y("false write response, pause",r.awaitDrainWriters.size),r.awaitDrainWriters.add(e)),n.pause()),f||(f=pa(n,e),e.on("drain",f))}n.on("data",b);function b(_){y("ondata");let p=e.write(_);y("dest.write",p),p===!1&&s()}function d(_){if(y("onerror",_),L(),e.removeListener("error",d),e.listenerCount("error")===0){let p=e._writableState||e._readableState;p&&!p.errorEmitted?fe(e,_):e.emit("error",_)}}Zl(e,"error",d);function h(){e.removeListener("finish",D),L()}e.once("close",h);function D(){y("onfinish"),e.removeListener("close",h),L()}e.once("finish",D);function L(){y("unpipe"),n.unpipe(e)}return e.emit("pipe",n),e.writableNeedDrain===!0?r.flowing&&s():r.flowing||(y("pipe resume"),n.resume()),e};function pa(e,t){return function(){let r=e._readableState;r.awaitDrainWriters===t?(y("pipeOnDrain",1),r.awaitDrainWriters=null):r.multiAwaitDrain&&(y("pipeOnDrain",r.awaitDrainWriters.size),r.awaitDrainWriters.delete(t)),(!r.awaitDrainWriters||r.awaitDrainWriters.size===0)&&e.listenerCount("data")&&e.resume()}}w.prototype.unpipe=function(e){let t=this._readableState,n={hasUnpiped:!1};if(t.pipes.length===0)return this;if(!e){let i=t.pipes;t.pipes=[],this.pause();for(let o=0;o0,r.flowing!==!1&&this.resume()):e==="readable"&&!r.endEmitted&&!r.readableListening&&(r.readableListening=r.needReadable=!0,r.flowing=!1,r.emittedReadable=!1,y("on readable",r.length,r.reading),r.length?$e(this):r.reading||W.nextTick(wa,this)),n};w.prototype.addListener=w.prototype.on;w.prototype.removeListener=function(e,t){let n=z.prototype.removeListener.call(this,e,t);return e==="readable"&&W.nextTick(lr,this),n};w.prototype.off=w.prototype.removeListener;w.prototype.removeAllListeners=function(e){let t=z.prototype.removeAllListeners.apply(this,arguments);return(e==="readable"||e===void 0)&&W.nextTick(lr,this),t};function lr(e){let t=e._readableState;t.readableListening=e.listenerCount("readable")>0,t.resumeScheduled&&t[ee]===!1?t.flowing=!0:e.listenerCount("data")>0?e.resume():t.readableListening||(t.flowing=null)}function wa(e){y("readable nexttick read 0"),e.read(0)}w.prototype.resume=function(){let e=this._readableState;return e.flowing||(y("resume"),e.flowing=!e.readableListening,ya(this,e)),e[ee]=!1,this};function ya(e,t){t.resumeScheduled||(t.resumeScheduled=!0,W.nextTick(ga,e,t))}function ga(e,t){y("resume",t.reading),t.reading||e.read(0),t.resumeScheduled=!1,e.emit("resume"),ar(e),t.flowing&&!t.reading&&e.read(0)}w.prototype.pause=function(){return y("call pause flowing=%j",this._readableState.flowing),this._readableState.flowing!==!1&&(y("pause"),this._readableState.flowing=!1,this.emit("pause")),this._readableState[ee]=!0,this};function ar(e){let t=e._readableState;for(y("flow",t.flowing);t.flowing&&e.read()!==null;);}w.prototype.wrap=function(e){let t=!1;e.on("data",r=>{!this.push(r)&&e.pause&&(t=!0,e.pause())}),e.on("end",()=>{this.push(null)}),e.on("error",r=>{fe(this,r)}),e.on("close",()=>{this.destroy()}),e.on("destroy",()=>{this.destroy()}),this._read=()=>{t&&e.resume&&(t=!1,e.resume())};let n=Yl(e);for(let r=1;r{i=l?Zn(i,l):null,n(),n=bt});try{for(;;){let l=e.destroyed?null:e.read();if(l!==null)yield l;else{if(i)throw i;if(i===null)return;await new Kl(r)}}}catch(l){throw i=Zn(i,l),i}finally{(i||t?.destroyOnReturn!==!1)&&(i===void 0||e._readableState.autoDestroy)?ue.destroyer(e,null):(e.off("readable",r),o())}}tr(w.prototype,{readable:{__proto__:null,get(){let e=this._readableState;return!!e&&e.readable!==!1&&!e.destroyed&&!e.errorEmitted&&!e.endEmitted},set(e){this._readableState&&(this._readableState.readable=!!e)}},readableDidRead:{__proto__:null,enumerable:!1,get:function(){return this._readableState.dataEmitted}},readableAborted:{__proto__:null,enumerable:!1,get:function(){return!!(this._readableState.readable!==!1&&(this._readableState.destroyed||this._readableState.errored)&&!this._readableState.endEmitted)}},readableHighWaterMark:{__proto__:null,enumerable:!1,get:function(){return this._readableState.highWaterMark}},readableBuffer:{__proto__:null,enumerable:!1,get:function(){return this._readableState&&this._readableState.buffer}},readableFlowing:{__proto__:null,enumerable:!1,get:function(){return this._readableState.flowing},set:function(e){this._readableState&&(this._readableState.flowing=e)}},readableLength:{__proto__:null,enumerable:!1,get(){return this._readableState.length}},readableObjectMode:{__proto__:null,enumerable:!1,get(){return this._readableState?this._readableState.objectMode:!1}},readableEncoding:{__proto__:null,enumerable:!1,get(){return this._readableState?this._readableState.encoding:null}},errored:{__proto__:null,enumerable:!1,get(){return this._readableState?this._readableState.errored:null}},closed:{__proto__:null,get(){return this._readableState?this._readableState.closed:!1}},destroyed:{__proto__:null,enumerable:!1,get(){return this._readableState?this._readableState.destroyed:!1},set(e){!this._readableState||(this._readableState.destroyed=e)}},readableEnded:{__proto__:null,enumerable:!1,get(){return this._readableState?this._readableState.endEmitted:!1}}});tr(yt.prototype,{pipesCount:{__proto__:null,get(){return this.pipes.length}},paused:{__proto__:null,get(){return this[ee]!==!1},set(e){this[ee]=!!e}}});w._fromList=ur;function ur(e,t){if(t.length===0)return null;let n;return t.objectMode?n=t.buffer.shift():!e||e>=t.length?(t.decoder?n=t.buffer.join(""):t.buffer.length===1?n=t.buffer.first():n=t.buffer.concat(t.length),t.buffer.clear()):n=t.buffer.consume(e,t.decoder),n}function pt(e){let t=e._readableState;y("endReadable",t.endEmitted),t.endEmitted||(t.ended=!0,W.nextTick(Ea,t,e))}function Ea(e,t){if(y("endReadableNT",e.endEmitted,e.length),!e.errored&&!e.closeEmitted&&!e.endEmitted&&e.length===0){if(e.endEmitted=!0,t.emit("end"),t.writable&&t.allowHalfOpen===!1)W.nextTick(Ra,t);else if(e.autoDestroy){let n=t._writableState;(!n||n.autoDestroy&&(n.finished||n.writable===!1))&&t.destroy()}}}function Ra(e){e.writable&&!e.writableEnded&&!e.destroyed&&e.end()}w.from=function(e,t){return da(w,e,t)};var wt;function sr(){return wt===void 0&&(wt={}),wt}w.fromWeb=function(e,t){return sr().newStreamReadableFromReadableStream(e,t)};w.toWeb=function(e,t){return sr().newReadableStreamFromStreamReadable(e,t)};w.wrap=function(e,t){var n,r;return new w({objectMode:(n=(r=e.readableObjectMode)!==null&&r!==void 0?r:e.objectMode)!==null&&n!==void 0?n:!0,...t,destroy(i,o){ue.destroyer(e,i),o(i)}}).wrap(e)}});var Tt=g((fu,Ar)=>{var te=__process$,{ArrayPrototypeSlice:br,Error:Aa,FunctionPrototypeSymbolHasInstance:_r,ObjectDefineProperty:pr,ObjectDefineProperties:ma,ObjectSetPrototypeOf:wr,StringPrototypeToLowerCase:Ta,Symbol:Ia,SymbolHasInstance:Ma}=m();Ar.exports=S;S.WritableState=Se;var{EventEmitter:Na}=__events$,ye=Le().Stream,{Buffer:ve}=__buffer$,Be=Z(),{addAbortSignal:Da}=ke(),{getHighWaterMark:Oa,getDefaultHighWaterMark:qa}=Ce(),{ERR_INVALID_ARG_TYPE:xa,ERR_METHOD_NOT_IMPLEMENTED:La,ERR_MULTIPLE_CALLBACK:yr,ERR_STREAM_CANNOT_PIPE:Pa,ERR_STREAM_DESTROYED:ge,ERR_STREAM_ALREADY_FINISHED:ka,ERR_STREAM_NULL_VALUES:Wa,ERR_STREAM_WRITE_AFTER_END:Ca,ERR_UNKNOWN_ENCODING:gr}=O().codes,{errorOrDestroy:se}=Be;wr(S.prototype,ye.prototype);wr(S,ye);function Et(){}var de=Ia("kOnFinished");function Se(e,t,n){typeof n!="boolean"&&(n=t instanceof v()),this.objectMode=!!(e&&e.objectMode),n&&(this.objectMode=this.objectMode||!!(e&&e.writableObjectMode)),this.highWaterMark=e?Oa(this,e,"writableHighWaterMark",n):qa(!1),this.finalCalled=!1,this.needDrain=!1,this.ending=!1,this.ended=!1,this.finished=!1,this.destroyed=!1;let r=!!(e&&e.decodeStrings===!1);this.decodeStrings=!r,this.defaultEncoding=e&&e.defaultEncoding||"utf8",this.length=0,this.writing=!1,this.corked=0,this.sync=!0,this.bufferProcessing=!1,this.onwrite=$a.bind(void 0,t),this.writecb=null,this.writelen=0,this.afterWriteTickInfo=null,Ue(this),this.pendingcb=0,this.constructed=!0,this.prefinished=!1,this.errorEmitted=!1,this.emitClose=!e||e.emitClose!==!1,this.autoDestroy=!e||e.autoDestroy!==!1,this.errored=null,this.closed=!1,this.closeEmitted=!1,this[de]=[]}function Ue(e){e.buffered=[],e.bufferedIndex=0,e.allBuffers=!0,e.allNoop=!0}Se.prototype.getBuffer=function(){return br(this.buffered,this.bufferedIndex)};pr(Se.prototype,"bufferedRequestCount",{__proto__:null,get(){return this.buffered.length-this.bufferedIndex}});function S(e){let t=this instanceof v();if(!t&&!_r(S,this))return new S(e);this._writableState=new Se(e,this,t),e&&(typeof e.write=="function"&&(this._write=e.write),typeof e.writev=="function"&&(this._writev=e.writev),typeof e.destroy=="function"&&(this._destroy=e.destroy),typeof e.final=="function"&&(this._final=e.final),typeof e.construct=="function"&&(this._construct=e.construct),e.signal&&Da(e.signal,this)),ye.call(this,e),Be.construct(this,()=>{let n=this._writableState;n.writing||At(this,n),mt(this,n)})}pr(S,Ma,{__proto__:null,value:function(e){return _r(this,e)?!0:this!==S?!1:e&&e._writableState instanceof Se}});S.prototype.pipe=function(){se(this,new Pa)};function Sr(e,t,n,r){let i=e._writableState;if(typeof n=="function")r=n,n=i.defaultEncoding;else{if(!n)n=i.defaultEncoding;else if(n!=="buffer"&&!ve.isEncoding(n))throw new gr(n);typeof r!="function"&&(r=Et)}if(t===null)throw new Wa;if(!i.objectMode)if(typeof t=="string")i.decodeStrings!==!1&&(t=ve.from(t,n),n="buffer");else if(t instanceof ve)n="buffer";else if(ye._isUint8Array(t))t=ye._uint8ArrayToBuffer(t),n="buffer";else throw new xa("chunk",["string","Buffer","Uint8Array"],t);let o;return i.ending?o=new Ca:i.destroyed&&(o=new ge("write")),o?(te.nextTick(r,o),se(e,o,!0),o):(i.pendingcb++,ja(e,i,t,n,r))}S.prototype.write=function(e,t,n){return Sr(this,e,t,n)===!0};S.prototype.cork=function(){this._writableState.corked++};S.prototype.uncork=function(){let e=this._writableState;e.corked&&(e.corked--,e.writing||At(this,e))};S.prototype.setDefaultEncoding=function(t){if(typeof t=="string"&&(t=Ta(t)),!ve.isEncoding(t))throw new gr(t);return this._writableState.defaultEncoding=t,this};function ja(e,t,n,r,i){let o=t.objectMode?1:n.length;t.length+=o;let l=t.lengthn.bufferedIndex&&At(e,n),r?n.afterWriteTickInfo!==null&&n.afterWriteTickInfo.cb===i?n.afterWriteTickInfo.count++:(n.afterWriteTickInfo={count:1,cb:i,stream:e,state:n},te.nextTick(va,n.afterWriteTickInfo)):Er(e,n,1,i))}function va({stream:e,state:t,count:n,cb:r}){return t.afterWriteTickInfo=null,Er(e,t,n,r)}function Er(e,t,n,r){for(!t.ending&&!e.destroyed&&t.length===0&&t.needDrain&&(t.needDrain=!1,e.emit("drain"));n-- >0;)t.pendingcb--,r();t.destroyed&&Rt(t),mt(e,t)}function Rt(e){if(e.writing)return;for(let i=e.bufferedIndex;i1&&e._writev){t.pendingcb-=o-1;let u=t.allNoop?Et:a=>{for(let c=l;c256?(n.splice(0,l),t.bufferedIndex=0):t.bufferedIndex=l}t.bufferProcessing=!1}S.prototype._write=function(e,t,n){if(this._writev)this._writev([{chunk:e,encoding:t}],n);else throw new La("_write()")};S.prototype._writev=null;S.prototype.end=function(e,t,n){let r=this._writableState;typeof e=="function"?(n=e,e=null,t=null):typeof t=="function"&&(n=t,t=null);let i;if(e!=null){let o=Sr(this,e,t);o instanceof Aa&&(i=o)}return r.corked&&(r.corked=1,this.uncork()),i||(!r.errored&&!r.ending?(r.ending=!0,mt(this,r,!0),r.ended=!0):r.finished?i=new ka("end"):r.destroyed&&(i=new ge("end"))),typeof n=="function"&&(i||r.finished?te.nextTick(n,i):r[de].push(n)),this};function Fe(e){return e.ending&&!e.destroyed&&e.constructed&&e.length===0&&!e.errored&&e.buffered.length===0&&!e.finished&&!e.writing&&!e.errorEmitted&&!e.closeEmitted}function Fa(e,t){let n=!1;function r(i){if(n){se(e,i??yr());return}if(n=!0,t.pendingcb--,i){let o=t[de].splice(0);for(let l=0;l{Fe(i)?St(r,i):i.pendingcb--},e,t)):Fe(t)&&(t.pendingcb++,St(e,t))))}function St(e,t){t.pendingcb--,t.finished=!0;let n=t[de].splice(0);for(let r=0;r{var It=__process$,Ga=__buffer$,{isReadable:Ha,isWritable:Va,isIterable:mr,isNodeStream:Ya,isReadableNodeStream:Tr,isWritableNodeStream:Ir,isDuplexNodeStream:Ka}=V(),Mr=Y(),{AbortError:Lr,codes:{ERR_INVALID_ARG_TYPE:za,ERR_INVALID_RETURN_VALUE:Nr}}=O(),{destroyer:ce}=Z(),Xa=v(),Ja=we(),{createDeferredPromise:Dr}=j(),Or=ct(),qr=Blob||Ga.Blob,Qa=typeof qr<"u"?function(t){return t instanceof qr}:function(t){return!1},Za=AbortController,{FunctionPrototypeCall:xr}=m(),ne=class extends Xa{constructor(t){super(t),t?.readable===!1&&(this._readableState.readable=!1,this._readableState.ended=!0,this._readableState.endEmitted=!0),t?.writable===!1&&(this._writableState.writable=!1,this._writableState.ending=!0,this._writableState.ended=!0,this._writableState.finished=!0)}};Pr.exports=function e(t,n){if(Ka(t))return t;if(Tr(t))return Ge({readable:t});if(Ir(t))return Ge({writable:t});if(Ya(t))return Ge({writable:!1,readable:!1});if(typeof t=="function"){let{value:i,write:o,final:l,destroy:u}=ef(t);if(mr(i))return Or(ne,i,{objectMode:!0,write:o,final:l,destroy:u});let f=i?.then;if(typeof f=="function"){let a,c=xr(f,i,s=>{if(s!=null)throw new Nr("nully","body",s)},s=>{ce(a,s)});return a=new ne({objectMode:!0,readable:!1,write:o,final(s){l(async()=>{try{await c,It.nextTick(s,null)}catch(b){It.nextTick(s,b)}})},destroy:u})}throw new Nr("Iterable, AsyncIterable or AsyncFunction",n,i)}if(Qa(t))return e(t.arrayBuffer());if(mr(t))return Or(ne,t,{objectMode:!0,writable:!1});if(typeof t?.writable=="object"||typeof t?.readable=="object"){let i=t!=null&&t.readable?Tr(t?.readable)?t?.readable:e(t.readable):void 0,o=t!=null&&t.writable?Ir(t?.writable)?t?.writable:e(t.writable):void 0;return Ge({readable:i,writable:o})}let r=t?.then;if(typeof r=="function"){let i;return xr(r,t,o=>{o!=null&&i.push(o),i.push(null)},o=>{ce(i,o)}),i=new ne({objectMode:!0,writable:!1,read(){}})}throw new za(n,["Blob","ReadableStream","WritableStream","Stream","Iterable","AsyncIterable","Function","{ readable, writable } pair","Promise"],t)};function ef(e){let{promise:t,resolve:n}=Dr(),r=new Za,i=r.signal;return{value:e(async function*(){for(;;){let l=t;t=null;let{chunk:u,done:f,cb:a}=await l;if(It.nextTick(a),f)return;if(i.aborted)throw new Lr(void 0,{cause:i.reason});({promise:t,resolve:n}=Dr()),yield u}}(),{signal:i}),write(l,u,f){let a=n;n=null,a({chunk:l,done:!1,cb:f})},final(l){let u=n;n=null,u({done:!0,cb:l})},destroy(l,u){r.abort(),u(l)}}}function Ge(e){let t=e.readable&&typeof e.readable.read!="function"?Ja.wrap(e.readable):e.readable,n=e.writable,r=!!Ha(t),i=!!Va(n),o,l,u,f,a;function c(s){let b=f;f=null,b?b(s):s?a.destroy(s):!r&&!i&&a.destroy()}return a=new ne({readableObjectMode:!!(t!=null&&t.readableObjectMode),writableObjectMode:!!(n!=null&&n.writableObjectMode),readable:r,writable:i}),i&&(Mr(n,s=>{i=!1,s&&ce(t,s),c(s)}),a._write=function(s,b,d){n.write(s,b)?d():o=d},a._final=function(s){n.end(),l=s},n.on("drain",function(){if(o){let s=o;o=null,s()}}),n.on("finish",function(){if(l){let s=l;l=null,s()}})),r&&(Mr(t,s=>{r=!1,s&&ce(t,s),c(s)}),t.on("readable",function(){if(u){let s=u;u=null,s()}}),t.on("end",function(){a.push(null)}),a._read=function(){for(;;){let s=t.read();if(s===null){u=a._read;return}if(!a.push(s))return}}),a._destroy=function(s,b){!s&&f!==null&&(s=new Lr),u=null,o=null,l=null,f===null?b(s):(f=b,ce(n,s),ce(t,s))},a}});var v=g((su,jr)=>{"use strict";var{ObjectDefineProperties:tf,ObjectGetOwnPropertyDescriptor:B,ObjectKeys:nf,ObjectSetPrototypeOf:Wr}=m();jr.exports=C;var Dt=we(),x=Tt();Wr(C.prototype,Dt.prototype);Wr(C,Dt);{let e=nf(x.prototype);for(let t=0;t{"use strict";var{ObjectSetPrototypeOf:$r,Symbol:rf}=m();vr.exports=G;var{ERR_METHOD_NOT_IMPLEMENTED:of}=O().codes,qt=v(),{getHighWaterMark:lf}=Ce();$r(G.prototype,qt.prototype);$r(G,qt);var Ee=rf("kCallback");function G(e){if(!(this instanceof G))return new G(e);let t=e?lf(this,e,"readableHighWaterMark",!0):null;t===0&&(e={...e,highWaterMark:null,readableHighWaterMark:t,writableHighWaterMark:e.writableHighWaterMark||0}),qt.call(this,e),this._readableState.sync=!1,this[Ee]=null,e&&(typeof e.transform=="function"&&(this._transform=e.transform),typeof e.flush=="function"&&(this._flush=e.flush)),this.on("prefinish",af)}function Ot(e){typeof this._flush=="function"&&!this.destroyed?this._flush((t,n)=>{if(t){e?e(t):this.destroy(t);return}n!=null&&this.push(n),this.push(null),e&&e()}):(this.push(null),e&&e())}function af(){this._final!==Ot&&Ot.call(this)}G.prototype._final=Ot;G.prototype._transform=function(e,t,n){throw new of("_transform()")};G.prototype._write=function(e,t,n){let r=this._readableState,i=this._writableState,o=r.length;this._transform(e,t,(l,u)=>{if(l){n(l);return}u!=null&&this.push(u),i.ended||o===r.length||r.length{"use strict";var{ObjectSetPrototypeOf:Fr}=m();Ur.exports=he;var Lt=xt();Fr(he.prototype,Lt.prototype);Fr(he,Lt);function he(e){if(!(this instanceof he))return new he(e);Lt.call(this,e)}he.prototype._transform=function(e,t,n){n(null,e)}});var Ye=g((hu,zr)=>{var He=__process$,{ArrayIsArray:ff,Promise:uf,SymbolAsyncIterator:sf}=m(),Ve=Y(),{once:df}=j(),cf=Z(),Br=v(),{aggregateTwoErrors:hf,codes:{ERR_INVALID_ARG_TYPE:Yr,ERR_INVALID_RETURN_VALUE:kt,ERR_MISSING_ARGS:bf,ERR_STREAM_DESTROYED:_f,ERR_STREAM_PREMATURE_CLOSE:pf},AbortError:wf}=O(),{validateFunction:yf,validateAbortSignal:gf}=_e(),{isIterable:be,isReadable:Wt,isReadableNodeStream:$t,isNodeStream:Gr}=V(),Sf=AbortController,Ct,jt;function Hr(e,t,n){let r=!1;e.on("close",()=>{r=!0});let i=Ve(e,{readable:t,writable:n},o=>{r=!o});return{destroy:o=>{r||(r=!0,cf.destroyer(e,o||new _f("pipe")))},cleanup:i}}function Ef(e){return yf(e[e.length-1],"streams[stream.length - 1]"),e.pop()}function Rf(e){if(be(e))return e;if($t(e))return Af(e);throw new Yr("val",["Readable","Iterable","AsyncIterable"],e)}async function*Af(e){jt||(jt=we()),yield*jt.prototype[sf].call(e)}async function Vr(e,t,n,{end:r}){let i,o=null,l=a=>{if(a&&(i=a),o){let c=o;o=null,c()}},u=()=>new uf((a,c)=>{i?c(i):o=()=>{i?c(i):a()}});t.on("drain",l);let f=Ve(t,{readable:!1},l);try{t.writableNeedDrain&&await u();for await(let a of e)t.write(a)||await u();r&&t.end(),await u(),n()}catch(a){n(i!==a?hf(i,a):a)}finally{f(),t.off("drain",l)}}function mf(...e){return Kr(e,df(Ef(e)))}function Kr(e,t,n){if(e.length===1&&ff(e[0])&&(e=e[0]),e.length<2)throw new bf("streams");let r=new Sf,i=r.signal,o=n?.signal,l=[];gf(o,"options.signal");function u(){d(new wf)}o?.addEventListener("abort",u);let f,a,c=[],s=0;function b(_){d(_,--s===0)}function d(_,p){if(_&&(!f||f.code==="ERR_STREAM_PREMATURE_CLOSE")&&(f=_),!(!f&&!p)){for(;c.length;)c.shift()(f);o?.removeEventListener("abort",u),r.abort(),p&&(f||l.forEach(I=>I()),He.nextTick(t,f,a))}}let h;for(let _=0;_0,F=I||n?.end!==!1,re=_===e.length-1;if(Gr(p)){let P=function(U){U&&U.name!=="AbortError"&&U.code!=="ERR_STREAM_PREMATURE_CLOSE"&&b(U)};var L=P;if(F){let{destroy:U,cleanup:ze}=Hr(p,I,M);c.push(U),Wt(p)&&re&&l.push(ze)}p.on("error",P),Wt(p)&&re&&l.push(()=>{p.removeListener("error",P)})}if(_===0)if(typeof p=="function"){if(h=p({signal:i}),!be(h))throw new kt("Iterable, AsyncIterable or Stream","source",h)}else be(p)||$t(p)?h=p:h=Br.from(p);else if(typeof p=="function")if(h=Rf(h),h=p(h,{signal:i}),I){if(!be(h,!0))throw new kt("AsyncIterable",`transform[${_-1}]`,h)}else{var D;Ct||(Ct=Pt());let P=new Ct({objectMode:!0}),U=(D=h)===null||D===void 0?void 0:D.then;if(typeof U=="function")s++,U.call(h,ie=>{a=ie,ie!=null&&P.write(ie),F&&P.end(),He.nextTick(b)},ie=>{P.destroy(ie),He.nextTick(b,ie)});else if(be(h,!0))s++,Vr(h,P,b,{end:F});else throw new kt("AsyncIterable or Promise","destination",h);h=P;let{destroy:ze,cleanup:_i}=Hr(h,!1,!0);c.push(ze),re&&l.push(_i)}else if(Gr(p)){if($t(h)){s+=2;let P=Tf(h,p,b,{end:F});Wt(p)&&re&&l.push(P)}else if(be(h))s++,Vr(h,p,b,{end:F});else throw new Yr("val",["Readable","Iterable","AsyncIterable"],h);h=p}else h=Br.from(p)}return(i!=null&&i.aborted||o!=null&&o.aborted)&&He.nextTick(u),h}function Tf(e,t,n,{end:r}){let i=!1;return t.on("close",()=>{i||n(new pf)}),e.pipe(t,{end:r}),r?e.once("end",()=>{i=!0,t.end()}):n(),Ve(e,{readable:!0,writable:!1},o=>{let l=e._readableState;o&&o.code==="ERR_STREAM_PREMATURE_CLOSE"&&l&&l.ended&&!l.errored&&!l.errorEmitted?e.once("end",n).once("error",n):n(o)}),Ve(t,{readable:!1,writable:!0},n)}zr.exports={pipelineImpl:Kr,pipeline:mf}});var ei=g((bu,Zr)=>{"use strict";var{pipeline:If}=Ye(),Ke=v(),{destroyer:Mf}=Z(),{isNodeStream:Nf,isReadable:Xr,isWritable:Jr}=V(),{AbortError:Df,codes:{ERR_INVALID_ARG_VALUE:Qr,ERR_MISSING_ARGS:Of}}=O();Zr.exports=function(...t){if(t.length===0)throw new Of("streams");if(t.length===1)return Ke.from(t[0]);let n=[...t];if(typeof t[0]=="function"&&(t[0]=Ke.from(t[0])),typeof t[t.length-1]=="function"){let d=t.length-1;t[d]=Ke.from(t[d])}for(let d=0;d0&&!Jr(t[d]))throw new Qr(`streams[${d}]`,n[d],"must be writable")}let r,i,o,l,u;function f(d){let h=l;l=null,h?h(d):d?u.destroy(d):!b&&!s&&u.destroy()}let a=t[0],c=If(t,f),s=!!Jr(a),b=!!Xr(c);return u=new Ke({writableObjectMode:!!(a!=null&&a.writableObjectMode),readableObjectMode:!!(c!=null&&c.writableObjectMode),writable:s,readable:b}),s&&(u._write=function(d,h,D){a.write(d,h)?D():r=D},u._final=function(d){a.end(),i=d},a.on("drain",function(){if(r){let d=r;r=null,d()}}),c.on("finish",function(){if(i){let d=i;i=null,d()}})),b&&(c.on("readable",function(){if(o){let d=o;o=null,d()}}),c.on("end",function(){u.push(null)}),u._read=function(){for(;;){let d=c.read();if(d===null){o=u._read;return}if(!u.push(d))return}}),u._destroy=function(d,h){!d&&l!==null&&(d=new Df),o=null,r=null,i=null,l===null?h(d):(l=h,Mf(c,d))},u}});var vt=g((_u,ti)=>{"use strict";var{ArrayPrototypePop:qf,Promise:xf}=m(),{isIterable:Lf,isNodeStream:Pf}=V(),{pipelineImpl:kf}=Ye(),{finished:Wf}=Y();function Cf(...e){return new xf((t,n)=>{let r,i,o=e[e.length-1];if(o&&typeof o=="object"&&!Pf(o)&&!Lf(o)){let l=qf(e);r=l.signal,i=l.end}kf(e,(l,u)=>{l?n(l):t(u)},{signal:r,end:i})})}ti.exports={finished:Wf,pipeline:Cf}});var di=g((pu,si)=>{var{Buffer:jf}=__buffer$,{ObjectDefineProperty:H,ObjectKeys:ii,ReflectApply:oi}=m(),{promisify:{custom:li}}=j(),{streamReturningOperators:ni,promiseReturningOperators:ri}=xn(),{codes:{ERR_ILLEGAL_CONSTRUCTOR:ai}}=O(),$f=ei(),{pipeline:fi}=Ye(),{destroyer:vf}=Z(),ui=Y(),Ft=vt(),Ut=V(),R=si.exports=Le().Stream;R.isDisturbed=Ut.isDisturbed;R.isErrored=Ut.isErrored;R.isReadable=Ut.isReadable;R.Readable=we();for(let e of ii(ni)){let n=function(...r){if(new.target)throw ai();return R.Readable.from(oi(t,this,r))};Uf=n;let t=ni[e];H(n,"name",{__proto__:null,value:t.name}),H(n,"length",{__proto__:null,value:t.length}),H(R.Readable.prototype,e,{__proto__:null,value:n,enumerable:!1,configurable:!0,writable:!0})}var Uf;for(let e of ii(ri)){let n=function(...i){if(new.target)throw ai();return oi(t,this,i)};Uf=n;let t=ri[e];H(n,"name",{__proto__:null,value:t.name}),H(n,"length",{__proto__:null,value:t.length}),H(R.Readable.prototype,e,{__proto__:null,value:n,enumerable:!1,configurable:!0,writable:!0})}var Uf;R.Writable=Tt();R.Duplex=v();R.Transform=xt();R.PassThrough=Pt();R.pipeline=fi;var{addAbortSignal:Ff}=ke();R.addAbortSignal=Ff;R.finished=ui;R.destroy=vf;R.compose=$f;H(R,"promises",{__proto__:null,configurable:!0,enumerable:!0,get(){return Ft}});H(fi,li,{__proto__:null,enumerable:!0,get(){return Ft.pipeline}});H(ui,li,{__proto__:null,enumerable:!0,get(){return Ft.finished}});R.Stream=R;R._isUint8Array=function(t){return t instanceof Uint8Array};R._uint8ArrayToBuffer=function(t){return jf.from(t.buffer,t.byteOffset,t.byteLength)}});var ci=g((wu,A)=>{"use strict";var T=di(),Bf=vt(),Gf=T.Readable.destroy;A.exports=T.Readable;A.exports._uint8ArrayToBuffer=T._uint8ArrayToBuffer;A.exports._isUint8Array=T._isUint8Array;A.exports.isDisturbed=T.isDisturbed;A.exports.isErrored=T.isErrored;A.exports.isReadable=T.isReadable;A.exports.Readable=T.Readable;A.exports.Writable=T.Writable;A.exports.Duplex=T.Duplex;A.exports.Transform=T.Transform;A.exports.PassThrough=T.PassThrough;A.exports.addAbortSignal=T.addAbortSignal;A.exports.finished=T.finished;A.exports.destroy=T.destroy;A.exports.destroy=Gf;A.exports.pipeline=T.pipeline;A.exports.compose=T.compose;Object.defineProperty(T,"promises",{configurable:!0,enumerable:!0,get(){return Bf}});A.exports.Stream=T.Stream;A.exports.default=A.exports});var bi=Ri(ci()),{_uint8ArrayToBuffer:yu,_isUint8Array:gu,isDisturbed:Su,isErrored:Eu,isReadable:Ru,Readable:Au,Writable:mu,Duplex:Tu,Transform:Iu,PassThrough:Mu,addAbortSignal:Nu,finished:Du,destroy:Ou,pipeline:qu,compose:xu,Stream:Lu}=bi,{default:hi,...Hf}=bi,Pu=hi!==void 0?hi:Hf;export{Tu as Duplex,Mu as PassThrough,Au as Readable,Lu as Stream,Iu as Transform,mu as Writable,gu as _isUint8Array,yu as _uint8ArrayToBuffer,Nu as addAbortSignal,xu as compose,Pu as default,Ou as destroy,Du as finished,Su as isDisturbed,Eu as isErrored,Ru as isReadable,qu as pipeline}; +/* End esm.sh bundle */ + +// The following code implements Readable.fromWeb(), Writable.fromWeb(), and +// Duplex.fromWeb(). These functions are not properly implemented in the +// readable-stream module yet. This can be removed once the following upstream +// issue is resolved: https://github.com/nodejs/readable-stream/issues/482 + +import { + AbortError, + ERR_INVALID_ARG_TYPE, + ERR_INVALID_ARG_VALUE, + ERR_STREAM_PREMATURE_CLOSE, +} from "internal:deno_node/polyfills/internal/errors.ts"; +import { destroy } from "internal:deno_node/polyfills/internal/streams/destroy.mjs"; +import finished from "internal:deno_node/polyfills/internal/streams/end-of-stream.mjs"; +import { + isDestroyed, + isReadable, + isReadableEnded, + isWritable, + isWritableEnded, +} from "internal:deno_node/polyfills/internal/streams/utils.mjs"; +import { createDeferredPromise, kEmptyObject } from "internal:deno_node/polyfills/internal/util.mjs"; +import { validateBoolean, validateObject } from "internal:deno_node/polyfills/internal/validators.mjs"; + +const process = __process$; +const { Buffer } = __buffer$; +const Readable = Au; +const Writable = mu; +const Duplex = Tu; + +function isReadableStream(object) { + return object instanceof ReadableStream; +} + +function isWritableStream(object) { + return object instanceof WritableStream; +} + +Readable.fromWeb = function ( + readableStream, + options = kEmptyObject, +) { + if (!isReadableStream(readableStream)) { + throw new ERR_INVALID_ARG_TYPE( + "readableStream", + "ReadableStream", + readableStream, + ); + } + + validateObject(options, "options"); + const { + highWaterMark, + encoding, + objectMode = false, + signal, + } = options; + + if (encoding !== undefined && !Buffer.isEncoding(encoding)) { + throw new ERR_INVALID_ARG_VALUE(encoding, "options.encoding"); + } + validateBoolean(objectMode, "options.objectMode"); + + const reader = readableStream.getReader(); + let closed = false; + + const readable = new Readable({ + objectMode, + highWaterMark, + encoding, + signal, + + read() { + reader.read().then( + (chunk) => { + if (chunk.done) { + readable.push(null); + } else { + readable.push(chunk.value); + } + }, + (error) => destroy.call(readable, error), + ); + }, + + destroy(error, callback) { + function done() { + try { + callback(error); + } catch (error) { + // In a next tick because this is happening within + // a promise context, and if there are any errors + // thrown we don't want those to cause an unhandled + // rejection. Let's just escape the promise and + // handle it separately. + process.nextTick(() => { + throw error; + }); + } + } + + if (!closed) { + reader.cancel(error).then(done, done); + return; + } + + done(); + }, + }); + + reader.closed.then( + () => { + closed = true; + if (!isReadableEnded(readable)) { + readable.push(null); + } + }, + (error) => { + closed = true; + destroy.call(readable, error); + }, + ); + + return readable; +}; + +Writable.fromWeb = function ( + writableStream, + options = kEmptyObject, +) { + if (!isWritableStream(writableStream)) { + throw new ERR_INVALID_ARG_TYPE( + "writableStream", + "WritableStream", + writableStream, + ); + } + + validateObject(options, "options"); + const { + highWaterMark, + decodeStrings = true, + objectMode = false, + signal, + } = options; + + validateBoolean(objectMode, "options.objectMode"); + validateBoolean(decodeStrings, "options.decodeStrings"); + + const writer = writableStream.getWriter(); + let closed = false; + + const writable = new Writable({ + highWaterMark, + objectMode, + decodeStrings, + signal, + + writev(chunks, callback) { + function done(error) { + error = error.filter((e) => e); + try { + callback(error.length === 0 ? undefined : error); + } catch (error) { + // In a next tick because this is happening within + // a promise context, and if there are any errors + // thrown we don't want those to cause an unhandled + // rejection. Let's just escape the promise and + // handle it separately. + process.nextTick(() => destroy.call(writable, error)); + } + } + + writer.ready.then( + () => + Promise.all( + chunks.map((data) => writer.write(data.chunk)), + ).then(done, done), + done, + ); + }, + + write(chunk, encoding, callback) { + if (typeof chunk === "string" && decodeStrings && !objectMode) { + chunk = Buffer.from(chunk, encoding); + chunk = new Uint8Array( + chunk.buffer, + chunk.byteOffset, + chunk.byteLength, + ); + } + + function done(error) { + try { + callback(error); + } catch (error) { + destroy(this, duplex, error); + } + } + + writer.ready.then( + () => writer.write(chunk).then(done, done), + done, + ); + }, + + destroy(error, callback) { + function done() { + try { + callback(error); + } catch (error) { + // In a next tick because this is happening within + // a promise context, and if there are any errors + // thrown we don't want those to cause an unhandled + // rejection. Let's just escape the promise and + // handle it separately. + process.nextTick(() => { + throw error; + }); + } + } + + if (!closed) { + if (error != null) { + writer.abort(error).then(done, done); + } else { + writer.close().then(done, done); + } + return; + } + + done(); + }, + + final(callback) { + function done(error) { + try { + callback(error); + } catch (error) { + // In a next tick because this is happening within + // a promise context, and if there are any errors + // thrown we don't want those to cause an unhandled + // rejection. Let's just escape the promise and + // handle it separately. + process.nextTick(() => destroy.call(writable, error)); + } + } + + if (!closed) { + writer.close().then(done, done); + } + }, + }); + + writer.closed.then( + () => { + closed = true; + if (!isWritableEnded(writable)) { + destroy.call(writable, new ERR_STREAM_PREMATURE_CLOSE()); + } + }, + (error) => { + closed = true; + destroy.call(writable, error); + }, + ); + + return writable; +}; + +Duplex.fromWeb = function (pair, options = kEmptyObject) { + validateObject(pair, "pair"); + const { + readable: readableStream, + writable: writableStream, + } = pair; + + if (!isReadableStream(readableStream)) { + throw new ERR_INVALID_ARG_TYPE( + "pair.readable", + "ReadableStream", + readableStream, + ); + } + if (!isWritableStream(writableStream)) { + throw new ERR_INVALID_ARG_TYPE( + "pair.writable", + "WritableStream", + writableStream, + ); + } + + validateObject(options, "options"); + const { + allowHalfOpen = false, + objectMode = false, + encoding, + decodeStrings = true, + highWaterMark, + signal, + } = options; + + validateBoolean(objectMode, "options.objectMode"); + if (encoding !== undefined && !Buffer.isEncoding(encoding)) { + throw new ERR_INVALID_ARG_VALUE(encoding, "options.encoding"); + } + + const writer = writableStream.getWriter(); + const reader = readableStream.getReader(); + let writableClosed = false; + let readableClosed = false; + + const duplex = new Duplex({ + allowHalfOpen, + highWaterMark, + objectMode, + encoding, + decodeStrings, + signal, + + writev(chunks, callback) { + function done(error) { + error = error.filter((e) => e); + try { + callback(error.length === 0 ? undefined : error); + } catch (error) { + // In a next tick because this is happening within + // a promise context, and if there are any errors + // thrown we don't want those to cause an unhandled + // rejection. Let's just escape the promise and + // handle it separately. + process.nextTick(() => destroy(duplex, error)); + } + } + + writer.ready.then( + () => + Promise.all( + chunks.map((data) => writer.write(data.chunk)), + ).then(done, done), + done, + ); + }, + + write(chunk, encoding, callback) { + if (typeof chunk === "string" && decodeStrings && !objectMode) { + chunk = Buffer.from(chunk, encoding); + chunk = new Uint8Array( + chunk.buffer, + chunk.byteOffset, + chunk.byteLength, + ); + } + + function done(error) { + try { + callback(error); + } catch (error) { + destroy(duplex, error); + } + } + + writer.ready.then( + () => writer.write(chunk).then(done, done), + done, + ); + }, + + final(callback) { + function done(error) { + try { + callback(error); + } catch (error) { + // In a next tick because this is happening within + // a promise context, and if there are any errors + // thrown we don't want those to cause an unhandled + // rejection. Let's just escape the promise and + // handle it separately. + process.nextTick(() => destroy(duplex, error)); + } + } + + if (!writableClosed) { + writer.close().then(done, done); + } + }, + + read() { + reader.read().then( + (chunk) => { + if (chunk.done) { + duplex.push(null); + } else { + duplex.push(chunk.value); + } + }, + (error) => destroy(duplex, error), + ); + }, + + destroy(error, callback) { + function done() { + try { + callback(error); + } catch (error) { + // In a next tick because this is happening within + // a promise context, and if there are any errors + // thrown we don't want those to cause an unhandled + // rejection. Let's just escape the promise and + // handle it separately. + process.nextTick(() => { + throw error; + }); + } + } + + async function closeWriter() { + if (!writableClosed) { + await writer.abort(error); + } + } + + async function closeReader() { + if (!readableClosed) { + await reader.cancel(error); + } + } + + if (!writableClosed || !readableClosed) { + Promise.all([ + closeWriter(), + closeReader(), + ]).then(done, done); + return; + } + + done(); + }, + }); + + writer.closed.then( + () => { + writableClosed = true; + if (!isWritableEnded(duplex)) { + destroy(duplex, new ERR_STREAM_PREMATURE_CLOSE()); + } + }, + (error) => { + writableClosed = true; + readableClosed = true; + destroy(duplex, error); + }, + ); + + reader.closed.then( + () => { + readableClosed = true; + if (!isReadableEnded(duplex)) { + duplex.push(null); + } + }, + (error) => { + writableClosed = true; + readableClosed = true; + destroy(duplex, error); + }, + ); + + return duplex; +}; + +// readable-stream attaches these to Readable, but Node.js core does not. +// Delete them here to better match Node.js core. These can be removed once +// https://github.com/nodejs/readable-stream/issues/485 is resolved. +delete Readable.Duplex; +delete Readable.PassThrough; +delete Readable.Readable; +delete Readable.Stream; +delete Readable.Transform; +delete Readable.Writable; +delete Readable._isUint8Array; +delete Readable._uint8ArrayToBuffer; +delete Readable.addAbortSignal; +delete Readable.compose; +delete Readable.destroy; +delete Readable.finished; +delete Readable.isDisturbed; +delete Readable.isErrored; +delete Readable.isReadable; +delete Readable.pipeline; + +// The following code implements Readable.toWeb(), Writable.toWeb(), and +// Duplex.toWeb(). These functions are not properly implemented in the +// readable-stream module yet. This can be removed once the following upstream +// issue is resolved: https://github.com/nodejs/readable-stream/issues/482 +function newReadableStreamFromStreamReadable( + streamReadable, + options = kEmptyObject, +) { + // Not using the internal/streams/utils isReadableNodeStream utility + // here because it will return false if streamReadable is a Duplex + // whose readable option is false. For a Duplex that is not readable, + // we want it to pass this check but return a closed ReadableStream. + if (typeof streamReadable?._readableState !== "object") { + throw new ERR_INVALID_ARG_TYPE( + "streamReadable", + "stream.Readable", + streamReadable, + ); + } + + if (isDestroyed(streamReadable) || !isReadable(streamReadable)) { + const readable = new ReadableStream(); + readable.cancel(); + return readable; + } + + const objectMode = streamReadable.readableObjectMode; + const highWaterMark = streamReadable.readableHighWaterMark; + + const evaluateStrategyOrFallback = (strategy) => { + // If there is a strategy available, use it + if (strategy) { + return strategy; + } + + if (objectMode) { + // When running in objectMode explicitly but no strategy, we just fall + // back to CountQueuingStrategy + return new CountQueuingStrategy({ highWaterMark }); + } + + // When not running in objectMode explicitly, we just fall + // back to a minimal strategy that just specifies the highWaterMark + // and no size algorithm. Using a ByteLengthQueuingStrategy here + // is unnecessary. + return { highWaterMark }; + }; + + const strategy = evaluateStrategyOrFallback(options?.strategy); + + let controller; + + function onData(chunk) { + // Copy the Buffer to detach it from the pool. + if (Buffer.isBuffer(chunk) && !objectMode) { + chunk = new Uint8Array(chunk); + } + controller.enqueue(chunk); + if (controller.desiredSize <= 0) { + streamReadable.pause(); + } + } + + streamReadable.pause(); + + const cleanup = finished(streamReadable, (error) => { + if (error?.code === "ERR_STREAM_PREMATURE_CLOSE") { + const err = new AbortError(undefined, { cause: error }); + error = err; + } + + cleanup(); + // This is a protection against non-standard, legacy streams + // that happen to emit an error event again after finished is called. + streamReadable.on("error", () => {}); + if (error) { + return controller.error(error); + } + controller.close(); + }); + + streamReadable.on("data", onData); + + return new ReadableStream({ + start(c) { + controller = c; + }, + + pull() { + streamReadable.resume(); + }, + + cancel(reason) { + destroy(streamReadable, reason); + }, + }, strategy); +} + +function newWritableStreamFromStreamWritable(streamWritable) { + // Not using the internal/streams/utils isWritableNodeStream utility + // here because it will return false if streamWritable is a Duplex + // whose writable option is false. For a Duplex that is not writable, + // we want it to pass this check but return a closed WritableStream. + if (typeof streamWritable?._writableState !== "object") { + throw new ERR_INVALID_ARG_TYPE( + "streamWritable", + "stream.Writable", + streamWritable, + ); + } + + if (isDestroyed(streamWritable) || !isWritable(streamWritable)) { + const writable = new WritableStream(); + writable.close(); + return writable; + } + + const highWaterMark = streamWritable.writableHighWaterMark; + const strategy = streamWritable.writableObjectMode + ? new CountQueuingStrategy({ highWaterMark }) + : { highWaterMark }; + + let controller; + let backpressurePromise; + let closed; + + function onDrain() { + if (backpressurePromise !== undefined) { + backpressurePromise.resolve(); + } + } + + const cleanup = finished(streamWritable, (error) => { + if (error?.code === "ERR_STREAM_PREMATURE_CLOSE") { + const err = new AbortError(undefined, { cause: error }); + error = err; + } + + cleanup(); + // This is a protection against non-standard, legacy streams + // that happen to emit an error event again after finished is called. + streamWritable.on("error", () => {}); + if (error != null) { + if (backpressurePromise !== undefined) { + backpressurePromise.reject(error); + } + // If closed is not undefined, the error is happening + // after the WritableStream close has already started. + // We need to reject it here. + if (closed !== undefined) { + closed.reject(error); + closed = undefined; + } + controller.error(error); + controller = undefined; + return; + } + + if (closed !== undefined) { + closed.resolve(); + closed = undefined; + return; + } + controller.error(new AbortError()); + controller = undefined; + }); + + streamWritable.on("drain", onDrain); + + return new WritableStream({ + start(c) { + controller = c; + }, + + async write(chunk) { + if (streamWritable.writableNeedDrain || !streamWritable.write(chunk)) { + backpressurePromise = createDeferredPromise(); + return backpressurePromise.promise.finally(() => { + backpressurePromise = undefined; + }); + } + }, + + abort(reason) { + destroy(streamWritable, reason); + }, + + close() { + if (closed === undefined && !isWritableEnded(streamWritable)) { + closed = createDeferredPromise(); + streamWritable.end(); + return closed.promise; + } + + controller = undefined; + return Promise.resolve(); + }, + }, strategy); +} + +function newReadableWritablePairFromDuplex(duplex) { + // Not using the internal/streams/utils isWritableNodeStream and + // isReadableNodestream utilities here because they will return false + // if the duplex was created with writable or readable options set to + // false. Instead, we'll check the readable and writable state after + // and return closed WritableStream or closed ReadableStream as + // necessary. + if ( + typeof duplex?._writableState !== "object" || + typeof duplex?._readableState !== "object" + ) { + throw new ERR_INVALID_ARG_TYPE("duplex", "stream.Duplex", duplex); + } + + if (isDestroyed(duplex)) { + const writable = new WritableStream(); + const readable = new ReadableStream(); + writable.close(); + readable.cancel(); + return { readable, writable }; + } + + const writable = isWritable(duplex) + ? newWritableStreamFromStreamWritable(duplex) + : new WritableStream(); + + if (!isWritable(duplex)) { + writable.close(); + } + + const readable = isReadable(duplex) + ? newReadableStreamFromStreamReadable(duplex) + : new ReadableStream(); + + if (!isReadable(duplex)) { + readable.cancel(); + } + + return { writable, readable }; +} + +Readable.toWeb = newReadableStreamFromStreamReadable; +Writable.toWeb = newWritableStreamFromStreamWritable; +Duplex.toWeb = newReadableWritablePairFromDuplex; diff --git a/ext/node/polyfills/_tls_common.ts b/ext/node/polyfills/_tls_common.ts new file mode 100644 index 00000000000000..c032f5680f6060 --- /dev/null +++ b/ext/node/polyfills/_tls_common.ts @@ -0,0 +1,15 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// deno-lint-ignore-file no-explicit-any + +export function createSecureContext(options: any) { + return { + ca: options?.ca, + cert: options?.cert, + key: options?.key, + }; +} + +export default { + createSecureContext, +}; diff --git a/ext/node/polyfills/_tls_wrap.ts b/ext/node/polyfills/_tls_wrap.ts new file mode 100644 index 00000000000000..2cec7b129abcec --- /dev/null +++ b/ext/node/polyfills/_tls_wrap.ts @@ -0,0 +1,443 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// deno-lint-ignore-file no-explicit-any + +import { + ObjectAssign, + StringPrototypeReplace, +} from "internal:deno_node/polyfills/internal/primordials.mjs"; +import assert from "internal:deno_node/polyfills/internal/assert.mjs"; +import * as net from "internal:deno_node/polyfills/net.ts"; +import { createSecureContext } from "internal:deno_node/polyfills/_tls_common.ts"; +import { kStreamBaseField } from "internal:deno_node/polyfills/internal_binding/stream_wrap.ts"; +import { connResetException } from "internal:deno_node/polyfills/internal/errors.ts"; +import { emitWarning } from "internal:deno_node/polyfills/process.ts"; +import { debuglog } from "internal:deno_node/polyfills/internal/util/debuglog.ts"; +import { + constants as TCPConstants, + TCP, +} from "internal:deno_node/polyfills/internal_binding/tcp_wrap.ts"; +import { + constants as PipeConstants, + Pipe, +} from "internal:deno_node/polyfills/internal_binding/pipe_wrap.ts"; +import { EventEmitter } from "internal:deno_node/polyfills/events.ts"; +import { kEmptyObject } from "internal:deno_node/polyfills/internal/util.mjs"; +import { nextTick } from "internal:deno_node/polyfills/_next_tick.ts"; + +const kConnectOptions = Symbol("connect-options"); +const kIsVerified = Symbol("verified"); +const kPendingSession = Symbol("pendingSession"); +const kRes = Symbol("res"); + +let debug = debuglog("tls", (fn) => { + debug = fn; +}); + +function onConnectEnd(this: any) { + // NOTE: This logic is shared with _http_client.js + if (!this._hadError) { + const options = this[kConnectOptions]; + this._hadError = true; + const error: any = connResetException( + "Client network socket disconnected " + + "before secure TLS connection was " + + "established", + ); + error.path = options.path; + error.host = options.host; + error.port = options.port; + error.localAddress = options.localAddress; + this.destroy(error); + } +} + +export class TLSSocket extends net.Socket { + _tlsOptions: any; + _secureEstablished: boolean; + _securePending: boolean; + _newSessionPending: boolean; + _controlReleased: boolean; + secureConnecting: boolean; + _SNICallback: any; + servername: string | null; + alpnProtocol: any; + authorized: boolean; + authorizationError: any; + [kRes]: any; + [kIsVerified]: boolean; + [kPendingSession]: any; + [kConnectOptions]: any; + ssl: any; + _start: any; + constructor(socket: any, opts: any = kEmptyObject) { + const tlsOptions = { ...opts }; + + let hostname = tlsOptions?.secureContext?.servername; + hostname = opts.host; + tlsOptions.hostname = hostname; + + const _cert = tlsOptions?.secureContext?.cert; + const _key = tlsOptions?.secureContext?.key; + + let caCerts = tlsOptions?.secureContext?.ca; + if (typeof caCerts === "string") caCerts = [caCerts]; + tlsOptions.caCerts = caCerts; + + super({ + handle: _wrapHandle(tlsOptions, socket), + ...opts, + manualStart: true, // This prevents premature reading from TLS handle + }); + if (socket) { + this._parent = socket; + } + this._tlsOptions = tlsOptions; + this._secureEstablished = false; + this._securePending = false; + this._newSessionPending = false; + this._controlReleased = false; + this.secureConnecting = true; + this._SNICallback = null; + this.servername = null; + this.alpnProtocol = null; + this.authorized = false; + this.authorizationError = null; + this[kRes] = null; + this[kIsVerified] = false; + this[kPendingSession] = null; + + this.ssl = new class { + verifyError() { + return null; // Never fails, rejectUnauthorized is always true in Deno. + } + }(); + + // deno-lint-ignore no-this-alias + const tlssock = this; + + /** Wraps the given socket and adds the tls capability to the underlying + * handle */ + function _wrapHandle(tlsOptions: any, wrap: net.Socket | undefined) { + let handle: any; + + if (wrap) { + handle = wrap._handle; + } + + const options = tlsOptions; + if (!handle) { + handle = options.pipe + ? new Pipe(PipeConstants.SOCKET) + : new TCP(TCPConstants.SOCKET); + } + + // Patches `afterConnect` hook to replace TCP conn with TLS conn + const afterConnect = handle.afterConnect; + handle.afterConnect = async (req: any, status: number) => { + try { + const conn = await Deno.startTls(handle[kStreamBaseField], options); + tlssock.emit("secure"); + tlssock.removeListener("end", onConnectEnd); + handle[kStreamBaseField] = conn; + } catch { + // TODO(kt3k): Handle this + } + return afterConnect.call(handle, req, status); + }; + + (handle as any).verifyError = function () { + return null; // Never fails, rejectUnauthorized is always true in Deno. + }; + // Pretends `handle` is `tls_wrap.wrap(handle, ...)` to make some npm modules happy + // An example usage of `_parentWrap` in npm module: + // https://github.com/szmarczak/http2-wrapper/blob/51eeaf59ff9344fb192b092241bfda8506983620/source/utils/js-stream-socket.js#L6 + handle._parent = handle; + handle._parentWrap = wrap; + + return handle; + } + } + + _tlsError(err: Error) { + this.emit("_tlsError", err); + if (this._controlReleased) { + return err; + } + return null; + } + + _releaseControl() { + if (this._controlReleased) { + return false; + } + this._controlReleased = true; + this.removeListener("error", this._tlsError); + return true; + } + + getEphemeralKeyInfo() { + return {}; + } + + isSessionReused() { + return false; + } + + setSession(_session: any) { + // TODO(kt3k): implement this + } + + setServername(_servername: any) { + // TODO(kt3k): implement this + } + + getPeerCertificate(_detailed: boolean) { + // TODO(kt3k): implement this + return { + subject: "localhost", + subjectaltname: "IP Address:127.0.0.1, IP Address:::1", + }; + } +} + +function normalizeConnectArgs(listArgs: any) { + const args = net._normalizeArgs(listArgs); + const options = args[0]; + const cb = args[1]; + + // If args[0] was options, then normalize dealt with it. + // If args[0] is port, or args[0], args[1] is host, port, we need to + // find the options and merge them in, normalize's options has only + // the host/port/path args that it knows about, not the tls options. + // This means that options.host overrides a host arg. + if (listArgs[1] !== null && typeof listArgs[1] === "object") { + ObjectAssign(options, listArgs[1]); + } else if (listArgs[2] !== null && typeof listArgs[2] === "object") { + ObjectAssign(options, listArgs[2]); + } + + return cb ? [options, cb] : [options]; +} + +let ipServernameWarned = false; + +export function Server(options: any, listener: any) { + return new ServerImpl(options, listener); +} + +export class ServerImpl extends EventEmitter { + listener?: Deno.TlsListener; + #closed = false; + constructor(public options: any, listener: any) { + super(); + if (listener) { + this.on("secureConnection", listener); + } + } + + listen(port: any, callback: any): this { + const key = this.options.key?.toString(); + const cert = this.options.cert?.toString(); + // TODO(kt3k): The default host should be "localhost" + const hostname = this.options.host ?? "0.0.0.0"; + + this.listener = Deno.listenTls({ port, hostname, cert, key }); + + callback?.call(this); + this.#listen(this.listener); + return this; + } + + async #listen(listener: Deno.TlsListener) { + while (!this.#closed) { + try { + // Creates TCP handle and socket directly from Deno.TlsConn. + // This works as TLS socket. We don't use TLSSocket class for doing + // this because Deno.startTls only supports client side tcp connection. + const handle = new TCP(TCPConstants.SOCKET, await listener.accept()); + const socket = new net.Socket({ handle }); + this.emit("secureConnection", socket); + } catch (e) { + if (e instanceof Deno.errors.BadResource) { + this.#closed = true; + } + // swallow + } + } + } + + close(cb?: (err?: Error) => void): this { + if (this.listener) { + this.listener.close(); + } + cb?.(); + nextTick(() => { + this.emit("close"); + }); + return this; + } + + address() { + const addr = this.listener!.addr as Deno.NetAddr; + return { + port: addr.port, + address: addr.hostname, + }; + } +} + +Server.prototype = ServerImpl.prototype; + +export function createServer(options: any, listener: any) { + return new ServerImpl(options, listener); +} + +function onConnectSecure(this: TLSSocket) { + this.authorized = true; + this.secureConnecting = false; + debug("client emit secureConnect. authorized:", this.authorized); + this.emit("secureConnect"); + + this.removeListener("end", onConnectEnd); +} + +export function connect(...args: any[]) { + args = normalizeConnectArgs(args); + let options = args[0]; + const cb = args[1]; + const allowUnauthorized = getAllowUnauthorized(); + + options = { + rejectUnauthorized: !allowUnauthorized, + ciphers: DEFAULT_CIPHERS, + checkServerIdentity, + minDHSize: 1024, + ...options, + }; + + if (!options.keepAlive) { + options.singleUse = true; + } + + assert(typeof options.checkServerIdentity === "function"); + assert( + typeof options.minDHSize === "number", + "options.minDHSize is not a number: " + options.minDHSize, + ); + assert( + options.minDHSize > 0, + "options.minDHSize is not a positive number: " + + options.minDHSize, + ); + + const context = options.secureContext || createSecureContext(options); + + const tlssock = new TLSSocket(options.socket, { + allowHalfOpen: options.allowHalfOpen, + pipe: !!options.path, + secureContext: context, + isServer: false, + requestCert: true, + rejectUnauthorized: options.rejectUnauthorized !== false, + session: options.session, + ALPNProtocols: options.ALPNProtocols, + requestOCSP: options.requestOCSP, + enableTrace: options.enableTrace, + pskCallback: options.pskCallback, + highWaterMark: options.highWaterMark, + onread: options.onread, + signal: options.signal, + ...options, // Caveat emptor: Node does not do this. + }); + + // rejectUnauthorized property can be explicitly defined as `undefined` + // causing the assignment to default value (`true`) fail. Before assigning + // it to the tlssock connection options, explicitly check if it is false + // and update rejectUnauthorized property. The property gets used by TLSSocket + // connection handler to allow or reject connection if unauthorized + options.rejectUnauthorized = options.rejectUnauthorized !== false; + + tlssock[kConnectOptions] = options; + + if (cb) { + tlssock.once("secureConnect", cb); + } + + if (!options.socket) { + // If user provided the socket, it's their responsibility to manage its + // connectivity. If we created one internally, we connect it. + if (options.timeout) { + tlssock.setTimeout(options.timeout); + } + + tlssock.connect(options, tlssock._start); + } + + tlssock._releaseControl(); + + if (options.session) { + tlssock.setSession(options.session); + } + + if (options.servername) { + if (!ipServernameWarned && net.isIP(options.servername)) { + emitWarning( + "Setting the TLS ServerName to an IP address is not permitted by " + + "RFC 6066. This will be ignored in a future version.", + "DeprecationWarning", + "DEP0123", + ); + ipServernameWarned = true; + } + tlssock.setServername(options.servername); + } + + if (options.socket) { + tlssock._start(); + } + + tlssock.on("secure", onConnectSecure); + tlssock.prependListener("end", onConnectEnd); + + return tlssock; +} + +function getAllowUnauthorized() { + return false; +} + +// TODO(kt3k): Implement this when Deno provides APIs for getting peer +// certificates. +export function checkServerIdentity(_hostname: string, _cert: any) { +} + +function unfqdn(host: string): string { + return StringPrototypeReplace(host, /[.]$/, ""); +} + +// Order matters. Mirrors ALL_CIPHER_SUITES from rustls/src/suites.rs but +// using openssl cipher names instead. Mutable in Node but not (yet) in Deno. +export const DEFAULT_CIPHERS = [ + // TLSv1.3 suites + "AES256-GCM-SHA384", + "AES128-GCM-SHA256", + "TLS_CHACHA20_POLY1305_SHA256", + // TLSv1.2 suites + "ECDHE-ECDSA-AES256-GCM-SHA384", + "ECDHE-ECDSA-AES128-GCM-SHA256", + "ECDHE-ECDSA-CHACHA20-POLY1305", + "ECDHE-RSA-AES256-GCM-SHA384", + "ECDHE-RSA-AES128-GCM-SHA256", + "ECDHE-RSA-CHACHA20-POLY1305", +].join(":"); + +export default { + TLSSocket, + connect, + createServer, + checkServerIdentity, + DEFAULT_CIPHERS, + Server, + unfqdn, +}; diff --git a/ext/node/polyfills/_util/_util_callbackify.ts b/ext/node/polyfills/_util/_util_callbackify.ts new file mode 100644 index 00000000000000..fe83a227d0872b --- /dev/null +++ b/ext/node/polyfills/_util/_util_callbackify.ts @@ -0,0 +1,129 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. +// +// Adapted from Node.js. Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. + +// These are simplified versions of the "real" errors in Node. + +import { nextTick } from "internal:deno_node/polyfills/_next_tick.ts"; + +class NodeFalsyValueRejectionError extends Error { + public reason: unknown; + public code = "ERR_FALSY_VALUE_REJECTION"; + constructor(reason: unknown) { + super("Promise was rejected with falsy value"); + this.reason = reason; + } +} +class NodeInvalidArgTypeError extends TypeError { + public code = "ERR_INVALID_ARG_TYPE"; + constructor(argumentName: string) { + super(`The ${argumentName} argument must be of type function.`); + } +} + +type Callback = + | ((err: Error) => void) + | ((err: null, result: ResultT) => void); + +function callbackify( + fn: () => PromiseLike, +): (callback: Callback) => void; +function callbackify( + fn: (arg: ArgT) => PromiseLike, +): (arg: ArgT, callback: Callback) => void; +function callbackify( + fn: (arg1: Arg1T, arg2: Arg2T) => PromiseLike, +): (arg1: Arg1T, arg2: Arg2T, callback: Callback) => void; +function callbackify( + fn: (arg1: Arg1T, arg2: Arg2T, arg3: Arg3T) => PromiseLike, +): (arg1: Arg1T, arg2: Arg2T, arg3: Arg3T, callback: Callback) => void; +function callbackify( + fn: ( + arg1: Arg1T, + arg2: Arg2T, + arg3: Arg3T, + arg4: Arg4T, + ) => PromiseLike, +): ( + arg1: Arg1T, + arg2: Arg2T, + arg3: Arg3T, + arg4: Arg4T, + callback: Callback, +) => void; +function callbackify( + fn: ( + arg1: Arg1T, + arg2: Arg2T, + arg3: Arg3T, + arg4: Arg4T, + arg5: Arg5T, + ) => PromiseLike, +): ( + arg1: Arg1T, + arg2: Arg2T, + arg3: Arg3T, + arg4: Arg4T, + arg5: Arg5T, + callback: Callback, +) => void; + +function callbackify( + original: (...args: unknown[]) => PromiseLike, +): (...args: unknown[]) => void { + if (typeof original !== "function") { + throw new NodeInvalidArgTypeError('"original"'); + } + + const callbackified = function (this: unknown, ...args: unknown[]) { + const maybeCb = args.pop(); + if (typeof maybeCb !== "function") { + throw new NodeInvalidArgTypeError("last"); + } + const cb = (...args: unknown[]) => { + maybeCb.apply(this, args); + }; + original.apply(this, args).then( + (ret: unknown) => { + nextTick(cb.bind(this, null, ret)); + }, + (rej: unknown) => { + rej = rej || new NodeFalsyValueRejectionError(rej); + nextTick(cb.bind(this, rej)); + }, + ); + }; + + const descriptors = Object.getOwnPropertyDescriptors(original); + // It is possible to manipulate a functions `length` or `name` property. This + // guards against the manipulation. + if (typeof descriptors.length.value === "number") { + descriptors.length.value++; + } + if (typeof descriptors.name.value === "string") { + descriptors.name.value += "Callbackified"; + } + Object.defineProperties(callbackified, descriptors); + return callbackified; +} + +export { callbackify }; diff --git a/ext/node/polyfills/_util/asserts.ts b/ext/node/polyfills/_util/asserts.ts new file mode 100644 index 00000000000000..7760c8639738da --- /dev/null +++ b/ext/node/polyfills/_util/asserts.ts @@ -0,0 +1,21 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. + +/** Assertion error class for node compat layer's internal code. */ +export class NodeCompatAssertionError extends Error { + constructor(message: string) { + super(message); + this.name = "NodeCompatAssertionError"; + } +} + +/** Make an assertion, if not `true`, then throw. */ +export function assert(expr: unknown, msg = ""): asserts expr { + if (!expr) { + throw new NodeCompatAssertionError(msg); + } +} + +/** Use this to assert unreachable code. */ +export function unreachable(): never { + throw new NodeCompatAssertionError("unreachable"); +} diff --git a/ext/node/polyfills/_util/async.ts b/ext/node/polyfills/_util/async.ts new file mode 100644 index 00000000000000..b508dbfedc9c79 --- /dev/null +++ b/ext/node/polyfills/_util/async.ts @@ -0,0 +1,55 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. +// This module is vendored from std/async/deferred.ts and std/async/delay.ts +// (with some modifications) + +export interface Deferred extends Promise { + readonly state: "pending" | "fulfilled" | "rejected"; + resolve(value?: T | PromiseLike): void; + // deno-lint-ignore no-explicit-any + reject(reason?: any): void; +} + +/** Creates a Promise with the `reject` and `resolve` functions */ +export function deferred(): Deferred { + let methods; + let state = "pending"; + const promise = new Promise((resolve, reject) => { + methods = { + async resolve(value: T | PromiseLike) { + await value; + state = "fulfilled"; + resolve(value); + }, + // deno-lint-ignore no-explicit-any + reject(reason?: any) { + state = "rejected"; + reject(reason); + }, + }; + }); + Object.defineProperty(promise, "state", { get: () => state }); + return Object.assign(promise, methods) as Deferred; +} + +/** Resolve a Promise after a given amount of milliseconds. */ +export function delay( + ms: number, + options: { signal?: AbortSignal } = {}, +): Promise { + const { signal } = options; + if (signal?.aborted) { + return Promise.reject(new DOMException("Delay was aborted.", "AbortError")); + } + return new Promise((resolve, reject) => { + const abort = () => { + clearTimeout(i); + reject(new DOMException("Delay was aborted.", "AbortError")); + }; + const done = () => { + signal?.removeEventListener("abort", abort); + resolve(); + }; + const i = setTimeout(done, ms); + signal?.addEventListener("abort", abort, { once: true }); + }); +} diff --git a/ext/node/polyfills/_util/os.ts b/ext/node/polyfills/_util/os.ts new file mode 100644 index 00000000000000..a3cb396bda8da2 --- /dev/null +++ b/ext/node/polyfills/_util/os.ts @@ -0,0 +1,10 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. + +const { ops } = globalThis.__bootstrap.core; + +export type OSType = "windows" | "linux" | "darwin" | "freebsd"; + +export const osType: OSType = ops.op_node_build_os(); + +export const isWindows = osType === "windows"; +export const isLinux = osType === "linux"; diff --git a/ext/node/polyfills/_util/std_asserts.ts b/ext/node/polyfills/_util/std_asserts.ts new file mode 100644 index 00000000000000..8c4c80078dd7db --- /dev/null +++ b/ext/node/polyfills/_util/std_asserts.ts @@ -0,0 +1,293 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. +// vendored from std/testing/asserts.ts + +import { red } from "internal:deno_node/polyfills/_util/std_fmt_colors.ts"; +import { + buildMessage, + diff, + diffstr, +} from "internal:deno_node/polyfills/_util/std_testing_diff.ts"; + +/** Converts the input into a string. Objects, Sets and Maps are sorted so as to + * make tests less flaky */ +export function format(v: unknown): string { + // deno-lint-ignore no-explicit-any + const { Deno } = globalThis as any; + return typeof Deno?.inspect === "function" + ? Deno.inspect(v, { + depth: Infinity, + sorted: true, + trailingComma: true, + compact: false, + iterableLimit: Infinity, + // getters should be true in assertEquals. + getters: true, + }) + : `"${String(v).replace(/(?=["\\])/g, "\\")}"`; +} + +const CAN_NOT_DISPLAY = "[Cannot display]"; + +export class AssertionError extends Error { + override name = "AssertionError"; + constructor(message: string) { + super(message); + } +} + +function isKeyedCollection(x: unknown): x is Set { + return [Symbol.iterator, "size"].every((k) => k in (x as Set)); +} + +/** Deep equality comparison used in assertions */ +export function equal(c: unknown, d: unknown): boolean { + const seen = new Map(); + return (function compare(a: unknown, b: unknown): boolean { + // Have to render RegExp & Date for string comparison + // unless it's mistreated as object + if ( + a && + b && + ((a instanceof RegExp && b instanceof RegExp) || + (a instanceof URL && b instanceof URL)) + ) { + return String(a) === String(b); + } + if (a instanceof Date && b instanceof Date) { + const aTime = a.getTime(); + const bTime = b.getTime(); + // Check for NaN equality manually since NaN is not + // equal to itself. + if (Number.isNaN(aTime) && Number.isNaN(bTime)) { + return true; + } + return aTime === bTime; + } + if (typeof a === "number" && typeof b === "number") { + return Number.isNaN(a) && Number.isNaN(b) || a === b; + } + if (Object.is(a, b)) { + return true; + } + if (a && typeof a === "object" && b && typeof b === "object") { + if (a && b && !constructorsEqual(a, b)) { + return false; + } + if (a instanceof WeakMap || b instanceof WeakMap) { + if (!(a instanceof WeakMap && b instanceof WeakMap)) return false; + throw new TypeError("cannot compare WeakMap instances"); + } + if (a instanceof WeakSet || b instanceof WeakSet) { + if (!(a instanceof WeakSet && b instanceof WeakSet)) return false; + throw new TypeError("cannot compare WeakSet instances"); + } + if (seen.get(a) === b) { + return true; + } + if (Object.keys(a || {}).length !== Object.keys(b || {}).length) { + return false; + } + seen.set(a, b); + if (isKeyedCollection(a) && isKeyedCollection(b)) { + if (a.size !== b.size) { + return false; + } + + let unmatchedEntries = a.size; + + for (const [aKey, aValue] of a.entries()) { + for (const [bKey, bValue] of b.entries()) { + /* Given that Map keys can be references, we need + * to ensure that they are also deeply equal */ + if ( + (aKey === aValue && bKey === bValue && compare(aKey, bKey)) || + (compare(aKey, bKey) && compare(aValue, bValue)) + ) { + unmatchedEntries--; + break; + } + } + } + + return unmatchedEntries === 0; + } + const merged = { ...a, ...b }; + for ( + const key of [ + ...Object.getOwnPropertyNames(merged), + ...Object.getOwnPropertySymbols(merged), + ] + ) { + type Key = keyof typeof merged; + if (!compare(a && a[key as Key], b && b[key as Key])) { + return false; + } + if (((key in a) && (!(key in b))) || ((key in b) && (!(key in a)))) { + return false; + } + } + if (a instanceof WeakRef || b instanceof WeakRef) { + if (!(a instanceof WeakRef && b instanceof WeakRef)) return false; + return compare(a.deref(), b.deref()); + } + return true; + } + return false; + })(c, d); +} + +// deno-lint-ignore ban-types +function constructorsEqual(a: object, b: object) { + return a.constructor === b.constructor || + a.constructor === Object && !b.constructor || + !a.constructor && b.constructor === Object; +} + +/** Make an assertion, error will be thrown if `expr` does not have truthy value. */ +export function assert(expr: unknown, msg = ""): asserts expr { + if (!expr) { + throw new AssertionError(msg); + } +} + +/** Make an assertion that `actual` and `expected` are equal, deeply. If not + * deeply equal, then throw. */ +export function assertEquals(actual: T, expected: T, msg?: string) { + if (equal(actual, expected)) { + return; + } + let message = ""; + const actualString = format(actual); + const expectedString = format(expected); + try { + const stringDiff = (typeof actual === "string") && + (typeof expected === "string"); + const diffResult = stringDiff + ? diffstr(actual as string, expected as string) + : diff(actualString.split("\n"), expectedString.split("\n")); + const diffMsg = buildMessage(diffResult, { stringDiff }).join("\n"); + message = `Values are not equal:\n${diffMsg}`; + } catch { + message = `\n${red(red(CAN_NOT_DISPLAY))} + \n\n`; + } + if (msg) { + message = msg; + } + throw new AssertionError(message); +} + +/** Make an assertion that `actual` and `expected` are not equal, deeply. + * If not then throw. */ +export function assertNotEquals(actual: T, expected: T, msg?: string) { + if (!equal(actual, expected)) { + return; + } + let actualString: string; + let expectedString: string; + try { + actualString = String(actual); + } catch { + actualString = "[Cannot display]"; + } + try { + expectedString = String(expected); + } catch { + expectedString = "[Cannot display]"; + } + if (!msg) { + msg = `actual: ${actualString} expected not to be: ${expectedString}`; + } + throw new AssertionError(msg); +} + +/** Make an assertion that `actual` and `expected` are strictly equal. If + * not then throw. */ +export function assertStrictEquals( + actual: unknown, + expected: T, + msg?: string, +): asserts actual is T { + if (Object.is(actual, expected)) { + return; + } + + let message: string; + + if (msg) { + message = msg; + } else { + const actualString = format(actual); + const expectedString = format(expected); + + if (actualString === expectedString) { + const withOffset = actualString + .split("\n") + .map((l) => ` ${l}`) + .join("\n"); + message = + `Values have the same structure but are not reference-equal:\n\n${ + red(withOffset) + }\n`; + } else { + try { + const stringDiff = (typeof actual === "string") && + (typeof expected === "string"); + const diffResult = stringDiff + ? diffstr(actual as string, expected as string) + : diff(actualString.split("\n"), expectedString.split("\n")); + const diffMsg = buildMessage(diffResult, { stringDiff }).join("\n"); + message = `Values are not strictly equal:\n${diffMsg}`; + } catch { + message = `\n${CAN_NOT_DISPLAY} + \n\n`; + } + } + } + + throw new AssertionError(message); +} + +/** Make an assertion that `actual` and `expected` are not strictly equal. + * If the values are strictly equal then throw. */ +export function assertNotStrictEquals( + actual: T, + expected: T, + msg?: string, +) { + if (!Object.is(actual, expected)) { + return; + } + + throw new AssertionError( + msg ?? `Expected "actual" to be strictly unequal to: ${format(actual)}\n`, + ); +} + +/** Make an assertion that `actual` match RegExp `expected`. If not + * then throw. */ +export function assertMatch( + actual: string, + expected: RegExp, + msg?: string, +) { + if (!expected.test(actual)) { + if (!msg) { + msg = `actual: "${actual}" expected to match: "${expected}"`; + } + throw new AssertionError(msg); + } +} + +/** Make an assertion that `actual` not match RegExp `expected`. If match + * then throw. */ +export function assertNotMatch( + actual: string, + expected: RegExp, + msg?: string, +) { + if (expected.test(actual)) { + if (!msg) { + msg = `actual: "${actual}" expected to not match: "${expected}"`; + } + throw new AssertionError(msg); + } +} diff --git a/ext/node/polyfills/_util/std_fmt_colors.ts b/ext/node/polyfills/_util/std_fmt_colors.ts new file mode 100644 index 00000000000000..d54e2c83d25188 --- /dev/null +++ b/ext/node/polyfills/_util/std_fmt_colors.ts @@ -0,0 +1,519 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. +// This file is vendored from std/fmt/colors.ts + +// TODO(kt3k): Initialize this at the start of runtime +// based on Deno.noColor +const noColor = false; + +interface Code { + open: string; + close: string; + regexp: RegExp; +} + +/** RGB 8-bits per channel. Each in range `0->255` or `0x00->0xff` */ +interface Rgb { + r: number; + g: number; + b: number; +} + +let enabled = !noColor; + +/** + * Set changing text color to enabled or disabled + * @param value + */ +export function setColorEnabled(value: boolean) { + if (noColor) { + return; + } + + enabled = value; +} + +/** Get whether text color change is enabled or disabled. */ +export function getColorEnabled(): boolean { + return enabled; +} + +/** + * Builds color code + * @param open + * @param close + */ +function code(open: number[], close: number): Code { + return { + open: `\x1b[${open.join(";")}m`, + close: `\x1b[${close}m`, + regexp: new RegExp(`\\x1b\\[${close}m`, "g"), + }; +} + +/** + * Applies color and background based on color code and its associated text + * @param str text to apply color settings to + * @param code color code to apply + */ +function run(str: string, code: Code): string { + return enabled + ? `${code.open}${str.replace(code.regexp, code.open)}${code.close}` + : str; +} + +/** + * Reset the text modified + * @param str text to reset + */ +export function reset(str: string): string { + return run(str, code([0], 0)); +} + +/** + * Make the text bold. + * @param str text to make bold + */ +export function bold(str: string): string { + return run(str, code([1], 22)); +} + +/** + * The text emits only a small amount of light. + * @param str text to dim + */ +export function dim(str: string): string { + return run(str, code([2], 22)); +} + +/** + * Make the text italic. + * @param str text to make italic + */ +export function italic(str: string): string { + return run(str, code([3], 23)); +} + +/** + * Make the text underline. + * @param str text to underline + */ +export function underline(str: string): string { + return run(str, code([4], 24)); +} + +/** + * Invert background color and text color. + * @param str text to invert its color + */ +export function inverse(str: string): string { + return run(str, code([7], 27)); +} + +/** + * Make the text hidden. + * @param str text to hide + */ +export function hidden(str: string): string { + return run(str, code([8], 28)); +} + +/** + * Put horizontal line through the center of the text. + * @param str text to strike through + */ +export function strikethrough(str: string): string { + return run(str, code([9], 29)); +} + +/** + * Set text color to black. + * @param str text to make black + */ +export function black(str: string): string { + return run(str, code([30], 39)); +} + +/** + * Set text color to red. + * @param str text to make red + */ +export function red(str: string): string { + return run(str, code([31], 39)); +} + +/** + * Set text color to green. + * @param str text to make green + */ +export function green(str: string): string { + return run(str, code([32], 39)); +} + +/** + * Set text color to yellow. + * @param str text to make yellow + */ +export function yellow(str: string): string { + return run(str, code([33], 39)); +} + +/** + * Set text color to blue. + * @param str text to make blue + */ +export function blue(str: string): string { + return run(str, code([34], 39)); +} + +/** + * Set text color to magenta. + * @param str text to make magenta + */ +export function magenta(str: string): string { + return run(str, code([35], 39)); +} + +/** + * Set text color to cyan. + * @param str text to make cyan + */ +export function cyan(str: string): string { + return run(str, code([36], 39)); +} + +/** + * Set text color to white. + * @param str text to make white + */ +export function white(str: string): string { + return run(str, code([37], 39)); +} + +/** + * Set text color to gray. + * @param str text to make gray + */ +export function gray(str: string): string { + return brightBlack(str); +} + +/** + * Set text color to bright black. + * @param str text to make bright-black + */ +export function brightBlack(str: string): string { + return run(str, code([90], 39)); +} + +/** + * Set text color to bright red. + * @param str text to make bright-red + */ +export function brightRed(str: string): string { + return run(str, code([91], 39)); +} + +/** + * Set text color to bright green. + * @param str text to make bright-green + */ +export function brightGreen(str: string): string { + return run(str, code([92], 39)); +} + +/** + * Set text color to bright yellow. + * @param str text to make bright-yellow + */ +export function brightYellow(str: string): string { + return run(str, code([93], 39)); +} + +/** + * Set text color to bright blue. + * @param str text to make bright-blue + */ +export function brightBlue(str: string): string { + return run(str, code([94], 39)); +} + +/** + * Set text color to bright magenta. + * @param str text to make bright-magenta + */ +export function brightMagenta(str: string): string { + return run(str, code([95], 39)); +} + +/** + * Set text color to bright cyan. + * @param str text to make bright-cyan + */ +export function brightCyan(str: string): string { + return run(str, code([96], 39)); +} + +/** + * Set text color to bright white. + * @param str text to make bright-white + */ +export function brightWhite(str: string): string { + return run(str, code([97], 39)); +} + +/** + * Set background color to black. + * @param str text to make its background black + */ +export function bgBlack(str: string): string { + return run(str, code([40], 49)); +} + +/** + * Set background color to red. + * @param str text to make its background red + */ +export function bgRed(str: string): string { + return run(str, code([41], 49)); +} + +/** + * Set background color to green. + * @param str text to make its background green + */ +export function bgGreen(str: string): string { + return run(str, code([42], 49)); +} + +/** + * Set background color to yellow. + * @param str text to make its background yellow + */ +export function bgYellow(str: string): string { + return run(str, code([43], 49)); +} + +/** + * Set background color to blue. + * @param str text to make its background blue + */ +export function bgBlue(str: string): string { + return run(str, code([44], 49)); +} + +/** + * Set background color to magenta. + * @param str text to make its background magenta + */ +export function bgMagenta(str: string): string { + return run(str, code([45], 49)); +} + +/** + * Set background color to cyan. + * @param str text to make its background cyan + */ +export function bgCyan(str: string): string { + return run(str, code([46], 49)); +} + +/** + * Set background color to white. + * @param str text to make its background white + */ +export function bgWhite(str: string): string { + return run(str, code([47], 49)); +} + +/** + * Set background color to bright black. + * @param str text to make its background bright-black + */ +export function bgBrightBlack(str: string): string { + return run(str, code([100], 49)); +} + +/** + * Set background color to bright red. + * @param str text to make its background bright-red + */ +export function bgBrightRed(str: string): string { + return run(str, code([101], 49)); +} + +/** + * Set background color to bright green. + * @param str text to make its background bright-green + */ +export function bgBrightGreen(str: string): string { + return run(str, code([102], 49)); +} + +/** + * Set background color to bright yellow. + * @param str text to make its background bright-yellow + */ +export function bgBrightYellow(str: string): string { + return run(str, code([103], 49)); +} + +/** + * Set background color to bright blue. + * @param str text to make its background bright-blue + */ +export function bgBrightBlue(str: string): string { + return run(str, code([104], 49)); +} + +/** + * Set background color to bright magenta. + * @param str text to make its background bright-magenta + */ +export function bgBrightMagenta(str: string): string { + return run(str, code([105], 49)); +} + +/** + * Set background color to bright cyan. + * @param str text to make its background bright-cyan + */ +export function bgBrightCyan(str: string): string { + return run(str, code([106], 49)); +} + +/** + * Set background color to bright white. + * @param str text to make its background bright-white + */ +export function bgBrightWhite(str: string): string { + return run(str, code([107], 49)); +} + +/* Special Color Sequences */ + +/** + * Clam and truncate color codes + * @param n + * @param max number to truncate to + * @param min number to truncate from + */ +function clampAndTruncate(n: number, max = 255, min = 0): number { + return Math.trunc(Math.max(Math.min(n, max), min)); +} + +/** + * Set text color using paletted 8bit colors. + * https://en.wikipedia.org/wiki/ANSI_escape_code#8-bit + * @param str text color to apply paletted 8bit colors to + * @param color code + */ +export function rgb8(str: string, color: number): string { + return run(str, code([38, 5, clampAndTruncate(color)], 39)); +} + +/** + * Set background color using paletted 8bit colors. + * https://en.wikipedia.org/wiki/ANSI_escape_code#8-bit + * @param str text color to apply paletted 8bit background colors to + * @param color code + */ +export function bgRgb8(str: string, color: number): string { + return run(str, code([48, 5, clampAndTruncate(color)], 49)); +} + +/** + * Set text color using 24bit rgb. + * `color` can be a number in range `0x000000` to `0xffffff` or + * an `Rgb`. + * + * To produce the color magenta: + * + * ```ts + * import { rgb24 } from "https://deno.land/std@$STD_VERSION/fmt/colors.ts"; + * rgb24("foo", 0xff00ff); + * rgb24("foo", {r: 255, g: 0, b: 255}); + * ``` + * @param str text color to apply 24bit rgb to + * @param color code + */ +export function rgb24(str: string, color: number | Rgb): string { + if (typeof color === "number") { + return run( + str, + code( + [38, 2, (color >> 16) & 0xff, (color >> 8) & 0xff, color & 0xff], + 39, + ), + ); + } + return run( + str, + code( + [ + 38, + 2, + clampAndTruncate(color.r), + clampAndTruncate(color.g), + clampAndTruncate(color.b), + ], + 39, + ), + ); +} + +/** + * Set background color using 24bit rgb. + * `color` can be a number in range `0x000000` to `0xffffff` or + * an `Rgb`. + * + * To produce the color magenta: + * + * ```ts + * import { bgRgb24 } from "https://deno.land/std@$STD_VERSION/fmt/colors.ts"; + * bgRgb24("foo", 0xff00ff); + * bgRgb24("foo", {r: 255, g: 0, b: 255}); + * ``` + * @param str text color to apply 24bit rgb to + * @param color code + */ +export function bgRgb24(str: string, color: number | Rgb): string { + if (typeof color === "number") { + return run( + str, + code( + [48, 2, (color >> 16) & 0xff, (color >> 8) & 0xff, color & 0xff], + 49, + ), + ); + } + return run( + str, + code( + [ + 48, + 2, + clampAndTruncate(color.r), + clampAndTruncate(color.g), + clampAndTruncate(color.b), + ], + 49, + ), + ); +} + +// https://github.com/chalk/ansi-regex/blob/02fa893d619d3da85411acc8fd4e2eea0e95a9d9/index.js +const ANSI_PATTERN = new RegExp( + [ + "[\\u001B\\u009B][[\\]()#;?]*(?:(?:(?:(?:;[-a-zA-Z\\d\\/#&.:=?%@~_]+)*|[a-zA-Z\\d]+(?:;[-a-zA-Z\\d\\/#&.:=?%@~_]*)*)?\\u0007)", + "(?:(?:\\d{1,4}(?:;\\d{0,4})*)?[\\dA-PR-TZcf-nq-uy=><~]))", + ].join("|"), + "g", +); + +/** + * Remove ANSI escape codes from the string. + * @param string to remove ANSI escape codes from + */ +export function stripColor(string: string): string { + return string.replace(ANSI_PATTERN, ""); +} diff --git a/ext/node/polyfills/_util/std_testing_diff.ts b/ext/node/polyfills/_util/std_testing_diff.ts new file mode 100644 index 00000000000000..766b5efdc4047f --- /dev/null +++ b/ext/node/polyfills/_util/std_testing_diff.ts @@ -0,0 +1,440 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. +// This file was vendored from std/testing/_diff.ts + +import { + bgGreen, + bgRed, + bold, + gray, + green, + red, + white, +} from "internal:deno_node/polyfills/_util/std_fmt_colors.ts"; + +interface FarthestPoint { + y: number; + id: number; +} + +export enum DiffType { + removed = "removed", + common = "common", + added = "added", +} + +export interface DiffResult { + type: DiffType; + value: T; + details?: Array>; +} + +const REMOVED = 1; +const COMMON = 2; +const ADDED = 3; + +function createCommon(A: T[], B: T[], reverse?: boolean): T[] { + const common = []; + if (A.length === 0 || B.length === 0) return []; + for (let i = 0; i < Math.min(A.length, B.length); i += 1) { + if ( + A[reverse ? A.length - i - 1 : i] === B[reverse ? B.length - i - 1 : i] + ) { + common.push(A[reverse ? A.length - i - 1 : i]); + } else { + return common; + } + } + return common; +} + +/** + * Renders the differences between the actual and expected values + * @param A Actual value + * @param B Expected value + */ +export function diff(A: T[], B: T[]): Array> { + const prefixCommon = createCommon(A, B); + const suffixCommon = createCommon( + A.slice(prefixCommon.length), + B.slice(prefixCommon.length), + true, + ).reverse(); + A = suffixCommon.length + ? A.slice(prefixCommon.length, -suffixCommon.length) + : A.slice(prefixCommon.length); + B = suffixCommon.length + ? B.slice(prefixCommon.length, -suffixCommon.length) + : B.slice(prefixCommon.length); + const swapped = B.length > A.length; + [A, B] = swapped ? [B, A] : [A, B]; + const M = A.length; + const N = B.length; + if (!M && !N && !suffixCommon.length && !prefixCommon.length) return []; + if (!N) { + return [ + ...prefixCommon.map( + (c): DiffResult => ({ type: DiffType.common, value: c }), + ), + ...A.map( + (a): DiffResult => ({ + type: swapped ? DiffType.added : DiffType.removed, + value: a, + }), + ), + ...suffixCommon.map( + (c): DiffResult => ({ type: DiffType.common, value: c }), + ), + ]; + } + const offset = N; + const delta = M - N; + const size = M + N + 1; + const fp: FarthestPoint[] = Array.from( + { length: size }, + () => ({ y: -1, id: -1 }), + ); + /** + * INFO: + * This buffer is used to save memory and improve performance. + * The first half is used to save route and last half is used to save diff + * type. + * This is because, when I kept new uint8array area to save type,performance + * worsened. + */ + const routes = new Uint32Array((M * N + size + 1) * 2); + const diffTypesPtrOffset = routes.length / 2; + let ptr = 0; + let p = -1; + + function backTrace( + A: T[], + B: T[], + current: FarthestPoint, + swapped: boolean, + ): Array<{ + type: DiffType; + value: T; + }> { + const M = A.length; + const N = B.length; + const result = []; + let a = M - 1; + let b = N - 1; + let j = routes[current.id]; + let type = routes[current.id + diffTypesPtrOffset]; + while (true) { + if (!j && !type) break; + const prev = j; + if (type === REMOVED) { + result.unshift({ + type: swapped ? DiffType.removed : DiffType.added, + value: B[b], + }); + b -= 1; + } else if (type === ADDED) { + result.unshift({ + type: swapped ? DiffType.added : DiffType.removed, + value: A[a], + }); + a -= 1; + } else { + result.unshift({ type: DiffType.common, value: A[a] }); + a -= 1; + b -= 1; + } + j = routes[prev]; + type = routes[prev + diffTypesPtrOffset]; + } + return result; + } + + function createFP( + slide: FarthestPoint, + down: FarthestPoint, + k: number, + M: number, + ): FarthestPoint { + if (slide && slide.y === -1 && down && down.y === -1) { + return { y: 0, id: 0 }; + } + if ( + (down && down.y === -1) || + k === M || + (slide && slide.y) > (down && down.y) + 1 + ) { + const prev = slide.id; + ptr++; + routes[ptr] = prev; + routes[ptr + diffTypesPtrOffset] = ADDED; + return { y: slide.y, id: ptr }; + } else { + const prev = down.id; + ptr++; + routes[ptr] = prev; + routes[ptr + diffTypesPtrOffset] = REMOVED; + return { y: down.y + 1, id: ptr }; + } + } + + function snake( + k: number, + slide: FarthestPoint, + down: FarthestPoint, + _offset: number, + A: T[], + B: T[], + ): FarthestPoint { + const M = A.length; + const N = B.length; + if (k < -N || M < k) return { y: -1, id: -1 }; + const fp = createFP(slide, down, k, M); + while (fp.y + k < M && fp.y < N && A[fp.y + k] === B[fp.y]) { + const prev = fp.id; + ptr++; + fp.id = ptr; + fp.y += 1; + routes[ptr] = prev; + routes[ptr + diffTypesPtrOffset] = COMMON; + } + return fp; + } + + while (fp[delta + offset].y < N) { + p = p + 1; + for (let k = -p; k < delta; ++k) { + fp[k + offset] = snake( + k, + fp[k - 1 + offset], + fp[k + 1 + offset], + offset, + A, + B, + ); + } + for (let k = delta + p; k > delta; --k) { + fp[k + offset] = snake( + k, + fp[k - 1 + offset], + fp[k + 1 + offset], + offset, + A, + B, + ); + } + fp[delta + offset] = snake( + delta, + fp[delta - 1 + offset], + fp[delta + 1 + offset], + offset, + A, + B, + ); + } + return [ + ...prefixCommon.map( + (c): DiffResult => ({ type: DiffType.common, value: c }), + ), + ...backTrace(A, B, fp[delta + offset], swapped), + ...suffixCommon.map( + (c): DiffResult => ({ type: DiffType.common, value: c }), + ), + ]; +} + +/** + * Renders the differences between the actual and expected strings + * Partially inspired from https://github.com/kpdecker/jsdiff + * @param A Actual string + * @param B Expected string + */ +export function diffstr(A: string, B: string) { + function unescape(string: string): string { + // unescape invisible characters. + // ref: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String#escape_sequences + return string + .replaceAll("\b", "\\b") + .replaceAll("\f", "\\f") + .replaceAll("\t", "\\t") + .replaceAll("\v", "\\v") + .replaceAll( // does not remove line breaks + /\r\n|\r|\n/g, + (str) => str === "\r" ? "\\r" : str === "\n" ? "\\n\n" : "\\r\\n\r\n", + ); + } + + function tokenize(string: string, { wordDiff = false } = {}): string[] { + if (wordDiff) { + // Split string on whitespace symbols + const tokens = string.split(/([^\S\r\n]+|[()[\]{}'"\r\n]|\b)/); + // Extended Latin character set + const words = + /^[a-zA-Z\u{C0}-\u{FF}\u{D8}-\u{F6}\u{F8}-\u{2C6}\u{2C8}-\u{2D7}\u{2DE}-\u{2FF}\u{1E00}-\u{1EFF}]+$/u; + + // Join boundary splits that we do not consider to be boundaries and merge empty strings surrounded by word chars + for (let i = 0; i < tokens.length - 1; i++) { + if ( + !tokens[i + 1] && tokens[i + 2] && words.test(tokens[i]) && + words.test(tokens[i + 2]) + ) { + tokens[i] += tokens[i + 2]; + tokens.splice(i + 1, 2); + i--; + } + } + return tokens.filter((token) => token); + } else { + // Split string on new lines symbols + const tokens = [], lines = string.split(/(\n|\r\n)/); + + // Ignore final empty token when text ends with a newline + if (!lines[lines.length - 1]) { + lines.pop(); + } + + // Merge the content and line separators into single tokens + for (let i = 0; i < lines.length; i++) { + if (i % 2) { + tokens[tokens.length - 1] += lines[i]; + } else { + tokens.push(lines[i]); + } + } + return tokens; + } + } + + // Create details by filtering relevant word-diff for current line + // and merge "space-diff" if surrounded by word-diff for cleaner displays + function createDetails( + line: DiffResult, + tokens: Array>, + ) { + return tokens.filter(({ type }) => + type === line.type || type === DiffType.common + ).map((result, i, t) => { + if ( + (result.type === DiffType.common) && (t[i - 1]) && + (t[i - 1]?.type === t[i + 1]?.type) && /\s+/.test(result.value) + ) { + return { + ...result, + type: t[i - 1].type, + }; + } + return result; + }); + } + + // Compute multi-line diff + const diffResult = diff( + tokenize(`${unescape(A)}\n`), + tokenize(`${unescape(B)}\n`), + ); + + const added = [], removed = []; + for (const result of diffResult) { + if (result.type === DiffType.added) { + added.push(result); + } + if (result.type === DiffType.removed) { + removed.push(result); + } + } + + // Compute word-diff + const aLines = added.length < removed.length ? added : removed; + const bLines = aLines === removed ? added : removed; + for (const a of aLines) { + let tokens = [] as Array>, + b: undefined | DiffResult; + // Search another diff line with at least one common token + while (bLines.length) { + b = bLines.shift(); + tokens = diff( + tokenize(a.value, { wordDiff: true }), + tokenize(b?.value ?? "", { wordDiff: true }), + ); + if ( + tokens.some(({ type, value }) => + type === DiffType.common && value.trim().length + ) + ) { + break; + } + } + // Register word-diff details + a.details = createDetails(a, tokens); + if (b) { + b.details = createDetails(b, tokens); + } + } + + return diffResult; +} + +/** + * Colors the output of assertion diffs + * @param diffType Difference type, either added or removed + */ +function createColor( + diffType: DiffType, + { background = false } = {}, +): (s: string) => string { + // TODO(@littledivy): Remove this when we can detect + // true color terminals. + // https://github.com/denoland/deno_std/issues/2575 + background = false; + switch (diffType) { + case DiffType.added: + return (s: string): string => + background ? bgGreen(white(s)) : green(bold(s)); + case DiffType.removed: + return (s: string): string => background ? bgRed(white(s)) : red(bold(s)); + default: + return white; + } +} + +/** + * Prefixes `+` or `-` in diff output + * @param diffType Difference type, either added or removed + */ +function createSign(diffType: DiffType): string { + switch (diffType) { + case DiffType.added: + return "+ "; + case DiffType.removed: + return "- "; + default: + return " "; + } +} + +export function buildMessage( + diffResult: ReadonlyArray>, + { stringDiff = false } = {}, +): string[] { + const messages: string[] = [], diffMessages: string[] = []; + messages.push(""); + messages.push(""); + messages.push( + ` ${gray(bold("[Diff]"))} ${red(bold("Actual"))} / ${ + green(bold("Expected")) + }`, + ); + messages.push(""); + messages.push(""); + diffResult.forEach((result: DiffResult) => { + const c = createColor(result.type); + const line = result.details?.map((detail) => + detail.type !== DiffType.common + ? createColor(detail.type, { background: true })(detail.value) + : detail.value + ).join("") ?? result.value; + diffMessages.push(c(`${createSign(result.type)}${line}`)); + }); + messages.push(...(stringDiff ? [diffMessages.join("")] : diffMessages)); + messages.push(""); + + return messages; +} diff --git a/ext/node/polyfills/_utils.ts b/ext/node/polyfills/_utils.ts new file mode 100644 index 00000000000000..85398ead998bdf --- /dev/null +++ b/ext/node/polyfills/_utils.ts @@ -0,0 +1,210 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. + +import { + TextDecoder, + TextEncoder, +} from "internal:deno_web/08_text_encoding.js"; +import { errorMap } from "internal:deno_node/polyfills/internal_binding/uv.ts"; +import { codes } from "internal:deno_node/polyfills/internal/error_codes.ts"; + +export type BinaryEncodings = "binary"; + +export type TextEncodings = + | "ascii" + | "utf8" + | "utf-8" + | "utf16le" + | "ucs2" + | "ucs-2" + | "base64" + | "latin1" + | "hex"; + +export type Encodings = BinaryEncodings | TextEncodings; + +export function notImplemented(msg: string): never { + const message = msg ? `Not implemented: ${msg}` : "Not implemented"; + throw new Error(message); +} + +export function warnNotImplemented(msg?: string) { + const message = msg + ? `Warning: Not implemented: ${msg}` + : "Warning: Not implemented"; + console.warn(message); +} + +export type _TextDecoder = typeof TextDecoder.prototype; +export const _TextDecoder = TextDecoder; + +export type _TextEncoder = typeof TextEncoder.prototype; +export const _TextEncoder = TextEncoder; + +// API helpers + +export type MaybeNull = T | null; +export type MaybeDefined = T | undefined; +export type MaybeEmpty = T | null | undefined; + +export function intoCallbackAPI( + // deno-lint-ignore no-explicit-any + func: (...args: any[]) => Promise, + cb: MaybeEmpty<(err: MaybeNull, value?: MaybeEmpty) => void>, + // deno-lint-ignore no-explicit-any + ...args: any[] +) { + func(...args).then( + (value) => cb && cb(null, value), + (err) => cb && cb(err), + ); +} + +export function intoCallbackAPIWithIntercept( + // deno-lint-ignore no-explicit-any + func: (...args: any[]) => Promise, + interceptor: (v: T1) => T2, + cb: MaybeEmpty<(err: MaybeNull, value?: MaybeEmpty) => void>, + // deno-lint-ignore no-explicit-any + ...args: any[] +) { + func(...args).then( + (value) => cb && cb(null, interceptor(value)), + (err) => cb && cb(err), + ); +} + +export function spliceOne(list: string[], index: number) { + for (; index + 1 < list.length; index++) list[index] = list[index + 1]; + list.pop(); +} + +// Taken from: https://github.com/nodejs/node/blob/ba684805b6c0eded76e5cd89ee00328ac7a59365/lib/internal/util.js#L125 +// Return undefined if there is no match. +// Move the "slow cases" to a separate function to make sure this function gets +// inlined properly. That prioritizes the common case. +export function normalizeEncoding( + enc: string | null, +): TextEncodings | undefined { + if (enc == null || enc === "utf8" || enc === "utf-8") return "utf8"; + return slowCases(enc); +} + +// https://github.com/nodejs/node/blob/ba684805b6c0eded76e5cd89ee00328ac7a59365/lib/internal/util.js#L130 +function slowCases(enc: string): TextEncodings | undefined { + switch (enc.length) { + case 4: + if (enc === "UTF8") return "utf8"; + if (enc === "ucs2" || enc === "UCS2") return "utf16le"; + enc = `${enc}`.toLowerCase(); + if (enc === "utf8") return "utf8"; + if (enc === "ucs2") return "utf16le"; + break; + case 3: + if (enc === "hex" || enc === "HEX" || `${enc}`.toLowerCase() === "hex") { + return "hex"; + } + break; + case 5: + if (enc === "ascii") return "ascii"; + if (enc === "ucs-2") return "utf16le"; + if (enc === "UTF-8") return "utf8"; + if (enc === "ASCII") return "ascii"; + if (enc === "UCS-2") return "utf16le"; + enc = `${enc}`.toLowerCase(); + if (enc === "utf-8") return "utf8"; + if (enc === "ascii") return "ascii"; + if (enc === "ucs-2") return "utf16le"; + break; + case 6: + if (enc === "base64") return "base64"; + if (enc === "latin1" || enc === "binary") return "latin1"; + if (enc === "BASE64") return "base64"; + if (enc === "LATIN1" || enc === "BINARY") return "latin1"; + enc = `${enc}`.toLowerCase(); + if (enc === "base64") return "base64"; + if (enc === "latin1" || enc === "binary") return "latin1"; + break; + case 7: + if ( + enc === "utf16le" || + enc === "UTF16LE" || + `${enc}`.toLowerCase() === "utf16le" + ) { + return "utf16le"; + } + break; + case 8: + if ( + enc === "utf-16le" || + enc === "UTF-16LE" || + `${enc}`.toLowerCase() === "utf-16le" + ) { + return "utf16le"; + } + break; + default: + if (enc === "") return "utf8"; + } +} + +export function validateIntegerRange( + value: number, + name: string, + min = -2147483648, + max = 2147483647, +) { + // The defaults for min and max correspond to the limits of 32-bit integers. + if (!Number.isInteger(value)) { + throw new Error(`${name} must be 'an integer' but was ${value}`); + } + + if (value < min || value > max) { + throw new Error( + `${name} must be >= ${min} && <= ${max}. Value was ${value}`, + ); + } +} + +type OptionalSpread = T extends undefined ? [] + : [T]; + +export function once( + callback: (...args: OptionalSpread) => void, +) { + let called = false; + return function (this: unknown, ...args: OptionalSpread) { + if (called) return; + called = true; + callback.apply(this, args); + }; +} + +export function makeMethodsEnumerable(klass: { new (): unknown }) { + const proto = klass.prototype; + for (const key of Object.getOwnPropertyNames(proto)) { + const value = proto[key]; + if (typeof value === "function") { + const desc = Reflect.getOwnPropertyDescriptor(proto, key); + if (desc) { + desc.enumerable = true; + Object.defineProperty(proto, key, desc); + } + } + } +} + +const NumberIsSafeInteger = Number.isSafeInteger; + +/** + * Returns a system error name from an error code number. + * @param code error code number + */ +export function getSystemErrorName(code: number): string | undefined { + if (typeof code !== "number") { + throw new codes.ERR_INVALID_ARG_TYPE("err", "number", code); + } + if (code >= 0 || !NumberIsSafeInteger(code)) { + throw new codes.ERR_OUT_OF_RANGE("err", "a negative integer", code); + } + return errorMap.get(code)?.[0]; +} diff --git a/ext/node/polyfills/_zlib.mjs b/ext/node/polyfills/_zlib.mjs new file mode 100644 index 00000000000000..0b1cb2d5f22bc9 --- /dev/null +++ b/ext/node/polyfills/_zlib.mjs @@ -0,0 +1,628 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. +// Copyright (c) 2014-2015 Devon Govett +// Forked from https://github.com/browserify/browserify-zlib + +// deno-lint-ignore-file + +import { Buffer, kMaxLength } from "internal:deno_node/polyfills/buffer.ts"; +import { Transform } from "internal:deno_node/polyfills/stream.ts"; +import * as binding from "internal:deno_node/polyfills/_zlib_binding.mjs"; +import util from "internal:deno_node/polyfills/util.ts"; +import { ok as assert } from "internal:deno_node/polyfills/assert.ts"; +import { zlib as zlibConstants } from "internal:deno_node/polyfills/internal_binding/constants.ts"; +import { nextTick } from "internal:deno_node/polyfills/_next_tick.ts"; + +var kRangeErrorMessage = "Cannot create final Buffer. It would be larger " + + "than 0x" + kMaxLength.toString(16) + " bytes"; + +// translation table for return codes. +export const codes = Object.freeze({ + Z_OK: binding.Z_OK, + Z_STREAM_END: binding.Z_STREAM_END, + Z_NEED_DICT: binding.Z_NEED_DICT, + Z_ERRNO: binding.Z_ERRNO, + Z_STREAM_ERROR: binding.Z_STREAM_ERROR, + Z_DATA_ERROR: binding.Z_DATA_ERROR, + Z_MEM_ERROR: binding.Z_MEM_ERROR, + Z_BUF_ERROR: binding.Z_BUF_ERROR, + Z_VERSION_ERROR: binding.Z_VERSION_ERROR, + [binding.Z_OK]: "Z_OK", + [binding.Z_STREAM_END]: "Z_STREAM_END", + [binding.Z_NEED_DICT]: "Z_NEED_DICT", + [binding.Z_ERRNO]: "Z_ERRNO", + [binding.Z_STREAM_ERROR]: "Z_STREAM_ERROR", + [binding.Z_DATA_ERROR]: "Z_DATA_ERROR", + [binding.Z_MEM_ERROR]: "Z_MEM_ERROR", + [binding.Z_BUF_ERROR]: "Z_BUF_ERROR", + [binding.Z_VERSION_ERROR]: "Z_VERSION_ERROR", +}); + +export const createDeflate = function (o) { + return new Deflate(o); +}; + +export const createInflate = function (o) { + return new Inflate(o); +}; + +export const createDeflateRaw = function (o) { + return new DeflateRaw(o); +}; + +export const createInflateRaw = function (o) { + return new InflateRaw(o); +}; + +export const createGzip = function (o) { + return new Gzip(o); +}; + +export const createGunzip = function (o) { + return new Gunzip(o); +}; + +export const createUnzip = function (o) { + return new Unzip(o); +}; + +// Convenience methods. +// compress/decompress a string or buffer in one step. +export const deflate = function (buffer, opts, callback) { + if (typeof opts === "function") { + callback = opts; + opts = {}; + } + return zlibBuffer(new Deflate(opts), buffer, callback); +}; + +export const deflateSync = function (buffer, opts) { + return zlibBufferSync(new Deflate(opts), buffer); +}; + +export const gzip = function (buffer, opts, callback) { + if (typeof opts === "function") { + callback = opts; + opts = {}; + } + return zlibBuffer(new Gzip(opts), buffer, callback); +}; + +export const gzipSync = function (buffer, opts) { + return zlibBufferSync(new Gzip(opts), buffer); +}; + +export const deflateRaw = function (buffer, opts, callback) { + if (typeof opts === "function") { + callback = opts; + opts = {}; + } + return zlibBuffer(new DeflateRaw(opts), buffer, callback); +}; + +export const deflateRawSync = function (buffer, opts) { + return zlibBufferSync(new DeflateRaw(opts), buffer); +}; + +export const unzip = function (buffer, opts, callback) { + if (typeof opts === "function") { + callback = opts; + opts = {}; + } + return zlibBuffer(new Unzip(opts), buffer, callback); +}; + +export const unzipSync = function (buffer, opts) { + return zlibBufferSync(new Unzip(opts), buffer); +}; + +export const inflate = function (buffer, opts, callback) { + if (typeof opts === "function") { + callback = opts; + opts = {}; + } + return zlibBuffer(new Inflate(opts), buffer, callback); +}; + +export const inflateSync = function (buffer, opts) { + return zlibBufferSync(new Inflate(opts), buffer); +}; + +export const gunzip = function (buffer, opts, callback) { + if (typeof opts === "function") { + callback = opts; + opts = {}; + } + return zlibBuffer(new Gunzip(opts), buffer, callback); +}; + +export const gunzipSync = function (buffer, opts) { + return zlibBufferSync(new Gunzip(opts), buffer); +}; + +export const inflateRaw = function (buffer, opts, callback) { + if (typeof opts === "function") { + callback = opts; + opts = {}; + } + return zlibBuffer(new InflateRaw(opts), buffer, callback); +}; + +export const inflateRawSync = function (buffer, opts) { + return zlibBufferSync(new InflateRaw(opts), buffer); +}; + +function zlibBuffer(engine, buffer, callback) { + var buffers = []; + var nread = 0; + + engine.on("error", onError); + engine.on("end", onEnd); + + engine.end(buffer); + flow(); + + function flow() { + var chunk; + while (null !== (chunk = engine.read())) { + buffers.push(chunk); + nread += chunk.length; + } + engine.once("readable", flow); + } + + function onError(err) { + engine.removeListener("end", onEnd); + engine.removeListener("readable", flow); + callback(err); + } + + function onEnd() { + var buf; + var err = null; + + if (nread >= kMaxLength) { + err = new RangeError(kRangeErrorMessage); + } else { + buf = Buffer.concat(buffers, nread); + } + + buffers = []; + engine.close(); + callback(err, buf); + } +} + +function zlibBufferSync(engine, buffer) { + if (typeof buffer === "string") buffer = Buffer.from(buffer); + + if (!Buffer.isBuffer(buffer)) throw new TypeError("Not a string or buffer"); + + var flushFlag = engine._finishFlushFlag; + + return engine._processChunk(buffer, flushFlag); +} + +// generic zlib +// minimal 2-byte header +function Deflate(opts) { + if (!(this instanceof Deflate)) return new Deflate(opts); + Zlib.call(this, opts, binding.DEFLATE); +} + +function Inflate(opts) { + if (!(this instanceof Inflate)) return new Inflate(opts); + Zlib.call(this, opts, binding.INFLATE); +} + +// gzip - bigger header, same deflate compression +function Gzip(opts) { + if (!(this instanceof Gzip)) return new Gzip(opts); + Zlib.call(this, opts, binding.GZIP); +} + +function Gunzip(opts) { + if (!(this instanceof Gunzip)) return new Gunzip(opts); + Zlib.call(this, opts, binding.GUNZIP); +} + +// raw - no header +function DeflateRaw(opts) { + if (!(this instanceof DeflateRaw)) return new DeflateRaw(opts); + Zlib.call(this, opts, binding.DEFLATERAW); +} + +function InflateRaw(opts) { + if (!(this instanceof InflateRaw)) return new InflateRaw(opts); + Zlib.call(this, opts, binding.INFLATERAW); +} + +// auto-detect header. +function Unzip(opts) { + if (!(this instanceof Unzip)) return new Unzip(opts); + Zlib.call(this, opts, binding.UNZIP); +} + +function isValidFlushFlag(flag) { + return flag === binding.Z_NO_FLUSH || flag === binding.Z_PARTIAL_FLUSH || + flag === binding.Z_SYNC_FLUSH || flag === binding.Z_FULL_FLUSH || + flag === binding.Z_FINISH || flag === binding.Z_BLOCK; +} + +// the Zlib class they all inherit from +// This thing manages the queue of requests, and returns +// true or false if there is anything in the queue when +// you call the .write() method. + +function Zlib(opts, mode) { + var _this = this; + + this._opts = opts = opts || {}; + this._chunkSize = opts.chunkSize || zlibConstants.Z_DEFAULT_CHUNK; + + Transform.call(this, opts); + + if (opts.flush && !isValidFlushFlag(opts.flush)) { + throw new Error("Invalid flush flag: " + opts.flush); + } + if (opts.finishFlush && !isValidFlushFlag(opts.finishFlush)) { + throw new Error("Invalid flush flag: " + opts.finishFlush); + } + + this._flushFlag = opts.flush || binding.Z_NO_FLUSH; + this._finishFlushFlag = typeof opts.finishFlush !== "undefined" + ? opts.finishFlush + : binding.Z_FINISH; + + if (opts.chunkSize) { + if ( + opts.chunkSize < zlibConstants.Z_MIN_CHUNK || + opts.chunkSize > zlibConstants.Z_MAX_CHUNK + ) { + throw new Error("Invalid chunk size: " + opts.chunkSize); + } + } + + if (opts.windowBits) { + if ( + opts.windowBits < zlibConstants.Z_MIN_WINDOWBITS || + opts.windowBits > zlibConstants.Z_MAX_WINDOWBITS + ) { + throw new Error("Invalid windowBits: " + opts.windowBits); + } + } + + if (opts.level) { + if ( + opts.level < zlibConstants.Z_MIN_LEVEL || + opts.level > zlibConstants.Z_MAX_LEVEL + ) { + throw new Error("Invalid compression level: " + opts.level); + } + } + + if (opts.memLevel) { + if ( + opts.memLevel < zlibConstants.Z_MIN_MEMLEVEL || + opts.memLevel > zlibConstants.Z_MAX_MEMLEVEL + ) { + throw new Error("Invalid memLevel: " + opts.memLevel); + } + } + + if (opts.strategy) { + if ( + opts.strategy != zlibConstants.Z_FILTERED && + opts.strategy != zlibConstants.Z_HUFFMAN_ONLY && + opts.strategy != zlibConstants.Z_RLE && + opts.strategy != zlibConstants.Z_FIXED && + opts.strategy != zlibConstants.Z_DEFAULT_STRATEGY + ) { + throw new Error("Invalid strategy: " + opts.strategy); + } + } + + if (opts.dictionary) { + if (!Buffer.isBuffer(opts.dictionary)) { + throw new Error("Invalid dictionary: it should be a Buffer instance"); + } + } + + this._handle = new binding.Zlib(mode); + + var self = this; + this._hadError = false; + this._handle.onerror = function (message, errno) { + // there is no way to cleanly recover. + // continuing only obscures problems. + _close(self); + self._hadError = true; + + var error = new Error(message); + error.errno = errno; + error.code = codes[errno]; + self.emit("error", error); + }; + + var level = zlibConstants.Z_DEFAULT_COMPRESSION; + if (typeof opts.level === "number") level = opts.level; + + var strategy = zlibConstants.Z_DEFAULT_STRATEGY; + if (typeof opts.strategy === "number") strategy = opts.strategy; + + this._handle.init( + opts.windowBits || zlibConstants.Z_DEFAULT_WINDOWBITS, + level, + opts.memLevel || zlibConstants.Z_DEFAULT_MEMLEVEL, + strategy, + opts.dictionary, + ); + + this._buffer = Buffer.allocUnsafe(this._chunkSize); + this._offset = 0; + this._level = level; + this._strategy = strategy; + + this.once("end", this.close); + + Object.defineProperty(this, "_closed", { + get: function () { + return !_this._handle; + }, + configurable: true, + enumerable: true, + }); +} + +util.inherits(Zlib, Transform); + +Zlib.prototype.params = function (level, strategy, callback) { + if (level < zlibConstants.Z_MIN_LEVEL || level > zlibConstants.Z_MAX_LEVEL) { + throw new RangeError("Invalid compression level: " + level); + } + if ( + strategy != zlibConstants.Z_FILTERED && + strategy != zlibConstants.Z_HUFFMAN_ONLY && + strategy != zlibConstants.Z_RLE && + strategy != zlibConstants.Z_FIXED && + strategy != zlibConstants.Z_DEFAULT_STRATEGY + ) { + throw new TypeError("Invalid strategy: " + strategy); + } + + if (this._level !== level || this._strategy !== strategy) { + var self = this; + this.flush(binding.Z_SYNC_FLUSH, function () { + assert(self._handle, "zlib binding closed"); + self._handle.params(level, strategy); + if (!self._hadError) { + self._level = level; + self._strategy = strategy; + if (callback) callback(); + } + }); + } else { + nextTick(callback); + } +}; + +Zlib.prototype.reset = function () { + assert(this._handle, "zlib binding closed"); + return this._handle.reset(); +}; + +// This is the _flush function called by the transform class, +// internally, when the last chunk has been written. +Zlib.prototype._flush = function (callback) { + this._transform(Buffer.alloc(0), "", callback); +}; + +Zlib.prototype.flush = function (kind, callback) { + var _this2 = this; + + var ws = this._writableState; + + if (typeof kind === "function" || kind === undefined && !callback) { + callback = kind; + kind = binding.Z_FULL_FLUSH; + } + + if (ws.ended) { + if (callback) nextTick(callback); + } else if (ws.ending) { + if (callback) this.once("end", callback); + } else if (ws.needDrain) { + if (callback) { + this.once("drain", function () { + return _this2.flush(kind, callback); + }); + } + } else { + this._flushFlag = kind; + this.write(Buffer.alloc(0), "", callback); + } +}; + +Zlib.prototype.close = function (callback) { + _close(this, callback); + nextTick(emitCloseNT, this); +}; + +function _close(engine, callback) { + if (callback) nextTick(callback); + + // Caller may invoke .close after a zlib error (which will null _handle). + if (!engine._handle) return; + + engine._handle.close(); + engine._handle = null; +} + +function emitCloseNT(self) { + self.emit("close"); +} + +Zlib.prototype._transform = function (chunk, encoding, cb) { + var flushFlag; + var ws = this._writableState; + var ending = ws.ending || ws.ended; + var last = ending && (!chunk || ws.length === chunk.length); + + if (chunk !== null && !Buffer.isBuffer(chunk)) { + return cb(new Error("invalid input")); + } + + if (!this._handle) return cb(new Error("zlib binding closed")); + + // If it's the last chunk, or a final flush, we use the Z_FINISH flush flag + // (or whatever flag was provided using opts.finishFlush). + // If it's explicitly flushing at some other time, then we use + // Z_FULL_FLUSH. Otherwise, use Z_NO_FLUSH for maximum compression + // goodness. + if (last) flushFlag = this._finishFlushFlag; + else { + flushFlag = this._flushFlag; + // once we've flushed the last of the queue, stop flushing and + // go back to the normal behavior. + if (chunk.length >= ws.length) { + this._flushFlag = this._opts.flush || binding.Z_NO_FLUSH; + } + } + + this._processChunk(chunk, flushFlag, cb); +}; + +Zlib.prototype._processChunk = function (chunk, flushFlag, cb) { + var availInBefore = chunk && chunk.length; + var availOutBefore = this._chunkSize - this._offset; + var inOff = 0; + + var self = this; + + var async = typeof cb === "function"; + + if (!async) { + var buffers = []; + var nread = 0; + + var error; + this.on("error", function (er) { + error = er; + }); + + assert(this._handle, "zlib binding closed"); + do { + var res = this._handle.writeSync( + flushFlag, + chunk, // in + inOff, // in_off + availInBefore, // in_len + this._buffer, // out + this._offset, //out_off + availOutBefore, + ); // out_len + } while (!this._hadError && callback(res[0], res[1])); + + if (this._hadError) { + throw error; + } + + if (nread >= kMaxLength) { + _close(this); + throw new RangeError(kRangeErrorMessage); + } + + var buf = Buffer.concat(buffers, nread); + _close(this); + + return buf; + } + + assert(this._handle, "zlib binding closed"); + var req = this._handle.write( + flushFlag, + chunk, // in + inOff, // in_off + availInBefore, // in_len + this._buffer, // out + this._offset, //out_off + availOutBefore, + ); // out_len + + req.buffer = chunk; + req.callback = callback; + + function callback(availInAfter, availOutAfter) { + // When the callback is used in an async write, the callback's + // context is the `req` object that was created. The req object + // is === this._handle, and that's why it's important to null + // out the values after they are done being used. `this._handle` + // can stay in memory longer than the callback and buffer are needed. + if (this) { + this.buffer = null; + this.callback = null; + } + + if (self._hadError) return; + + var have = availOutBefore - availOutAfter; + assert(have >= 0, "have should not go down"); + + if (have > 0) { + var out = self._buffer.slice(self._offset, self._offset + have); + self._offset += have; + // serve some output to the consumer. + if (async) { + self.push(out); + } else { + buffers.push(out); + nread += out.length; + } + } + + // exhausted the output buffer, or used all the input create a new one. + if (availOutAfter === 0 || self._offset >= self._chunkSize) { + availOutBefore = self._chunkSize; + self._offset = 0; + self._buffer = Buffer.allocUnsafe(self._chunkSize); + } + + if (availOutAfter === 0) { + // Not actually done. Need to reprocess. + // Also, update the availInBefore to the availInAfter value, + // so that if we have to hit it a third (fourth, etc.) time, + // it'll have the correct byte counts. + inOff += availInBefore - availInAfter; + availInBefore = availInAfter; + + if (!async) return true; + + var newReq = self._handle.write( + flushFlag, + chunk, + inOff, + availInBefore, + self._buffer, + self._offset, + self._chunkSize, + ); + newReq.callback = callback; // this same function + newReq.buffer = chunk; + return; + } + + if (!async) return false; + + // finished with the chunk. + cb(); + } +}; + +util.inherits(Deflate, Zlib); +util.inherits(Inflate, Zlib); +util.inherits(Gzip, Zlib); +util.inherits(Gunzip, Zlib); +util.inherits(DeflateRaw, Zlib); +util.inherits(InflateRaw, Zlib); +util.inherits(Unzip, Zlib); + +export { Deflate, DeflateRaw, Gunzip, Gzip, Inflate, InflateRaw, Unzip }; diff --git a/ext/node/polyfills/_zlib_binding.mjs b/ext/node/polyfills/_zlib_binding.mjs new file mode 100644 index 00000000000000..0286fefd59618a --- /dev/null +++ b/ext/node/polyfills/_zlib_binding.mjs @@ -0,0 +1,511 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. +// Copyright (c) 2014-2015 Devon Govett +// Forked from https://github.com/browserify/browserify-zlib + +// deno-lint-ignore-file + +import assert from "internal:deno_node/polyfills/assert.ts"; +import { constants, zlib_deflate, zlib_inflate, Zstream } from "internal:deno_node/polyfills/_pako.mjs"; +import { nextTick } from "internal:deno_node/polyfills/_next_tick.ts"; + +export const Z_NO_FLUSH = constants.Z_NO_FLUSH; +export const Z_PARTIAL_FLUSH = constants.Z_PARTIAL_FLUSH; +export const Z_SYNC_FLUSH = constants.Z_SYNC_FLUSH; +export const Z_FULL_FLUSH = constants.Z_FULL_FLUSH; +export const Z_FINISH = constants.Z_FINISH; +export const Z_BLOCK = constants.Z_BLOCK; +export const Z_TREES = constants.Z_TREES; +export const Z_OK = constants.Z_OK; +export const Z_STREAM_END = constants.Z_STREAM_END; +export const Z_NEED_DICT = constants.Z_NEED_DICT; +export const Z_ERRNO = constants.Z_ERRNO; +export const Z_STREAM_ERROR = constants.Z_STREAM_ERROR; +export const Z_DATA_ERROR = constants.Z_DATA_ERROR; +export const Z_MEM_ERROR = constants.Z_MEM_ERROR; +export const Z_BUF_ERROR = constants.Z_BUF_ERROR; +export const Z_VERSION_ERROR = constants.Z_VERSION_ERROR; +export const Z_NO_COMPRESSION = constants.Z_NO_COMPRESSION; +export const Z_BEST_SPEED = constants.Z_BEST_SPEED; +export const Z_BEST_COMPRESSION = constants.Z_BEST_COMPRESSION; +export const Z_DEFAULT_COMPRESSION = constants.Z_DEFAULT_COMPRESSION; +export const Z_FILTERED = constants.Z_FILTERED; +export const Z_HUFFMAN_ONLY = constants.Z_HUFFMAN_ONLYZ_FILTERED; +export const Z_RLE = constants.Z_RLE; +export const Z_FIXED = constants.Z_FIXED; +export const Z_DEFAULT_STRATEGY = constants.Z_DEFAULT_STRATEGY; +export const Z_BINARY = constants.Z_BINARY; +export const Z_TEXT = constants.Z_TEXT; +export const Z_UNKNOWN = constants.Z_UNKNOWN; +export const Z_DEFLATED = constants.Z_DEFLATED; + +// zlib modes +export const NONE = 0; +export const DEFLATE = 1; +export const INFLATE = 2; +export const GZIP = 3; +export const GUNZIP = 4; +export const DEFLATERAW = 5; +export const INFLATERAW = 6; +export const UNZIP = 7; + +var GZIP_HEADER_ID1 = 0x1f; +var GZIP_HEADER_ID2 = 0x8b; + +/** + * Emulate Node's zlib C++ layer for use by the JS layer in index.js + */ +function Zlib(mode) { + if (typeof mode !== "number" || mode < DEFLATE || mode > UNZIP) { + throw new TypeError("Bad argument"); + } + + this.dictionary = null; + this.err = 0; + this.flush = 0; + this.init_done = false; + this.level = 0; + this.memLevel = 0; + this.mode = mode; + this.strategy = 0; + this.windowBits = 0; + this.write_in_progress = false; + this.pending_close = false; + this.gzip_id_bytes_read = 0; +} + +Zlib.prototype.close = function () { + if (this.write_in_progress) { + this.pending_close = true; + return; + } + + this.pending_close = false; + + assert(this.init_done, "close before init"); + assert(this.mode <= UNZIP); + + if (this.mode === DEFLATE || this.mode === GZIP || this.mode === DEFLATERAW) { + zlib_deflate.deflateEnd(this.strm); + } else if ( + this.mode === INFLATE || this.mode === GUNZIP || this.mode === INFLATERAW || + this.mode === UNZIP + ) { + zlib_inflate.inflateEnd(this.strm); + } + + this.mode = NONE; + + this.dictionary = null; +}; + +Zlib.prototype.write = function ( + flush, + input, + in_off, + in_len, + out, + out_off, + out_len, +) { + return this._write(true, flush, input, in_off, in_len, out, out_off, out_len); +}; + +Zlib.prototype.writeSync = function ( + flush, + input, + in_off, + in_len, + out, + out_off, + out_len, +) { + return this._write( + false, + flush, + input, + in_off, + in_len, + out, + out_off, + out_len, + ); +}; + +Zlib.prototype._write = function ( + async, + flush, + input, + in_off, + in_len, + out, + out_off, + out_len, +) { + assert.equal(arguments.length, 8); + + assert(this.init_done, "write before init"); + assert(this.mode !== NONE, "already finalized"); + assert.equal(false, this.write_in_progress, "write already in progress"); + assert.equal(false, this.pending_close, "close is pending"); + + this.write_in_progress = true; + + assert.equal(false, flush === undefined, "must provide flush value"); + + this.write_in_progress = true; + + if ( + flush !== Z_NO_FLUSH && flush !== Z_PARTIAL_FLUSH && + flush !== Z_SYNC_FLUSH && flush !== Z_FULL_FLUSH && flush !== Z_FINISH && + flush !== Z_BLOCK + ) { + throw new Error("Invalid flush value"); + } + + if (input == null) { + input = Buffer.alloc(0); + in_len = 0; + in_off = 0; + } + + this.strm.avail_in = in_len; + this.strm.input = input; + this.strm.next_in = in_off; + this.strm.avail_out = out_len; + this.strm.output = out; + this.strm.next_out = out_off; + this.flush = flush; + + if (!async) { + // sync version + this._process(); + + if (this._checkError()) { + return this._afterSync(); + } + return; + } + + // async version + var self = this; + nextTick(function () { + self._process(); + self._after(); + }); + + return this; +}; + +Zlib.prototype._afterSync = function () { + var avail_out = this.strm.avail_out; + var avail_in = this.strm.avail_in; + + this.write_in_progress = false; + + return [avail_in, avail_out]; +}; + +Zlib.prototype._process = function () { + var next_expected_header_byte = null; + + // If the avail_out is left at 0, then it means that it ran out + // of room. If there was avail_out left over, then it means + // that all of the input was consumed. + switch (this.mode) { + case DEFLATE: + case GZIP: + case DEFLATERAW: + this.err = zlib_deflate.deflate(this.strm, this.flush); + break; + case UNZIP: + if (this.strm.avail_in > 0) { + next_expected_header_byte = this.strm.next_in; + } + + switch (this.gzip_id_bytes_read) { + case 0: + if (next_expected_header_byte === null) { + break; + } + + if (this.strm.input[next_expected_header_byte] === GZIP_HEADER_ID1) { + this.gzip_id_bytes_read = 1; + next_expected_header_byte++; + + if (this.strm.avail_in === 1) { + // The only available byte was already read. + break; + } + } else { + this.mode = INFLATE; + break; + } + + // fallthrough + + case 1: + if (next_expected_header_byte === null) { + break; + } + + if (this.strm.input[next_expected_header_byte] === GZIP_HEADER_ID2) { + this.gzip_id_bytes_read = 2; + this.mode = GUNZIP; + } else { + // There is no actual difference between INFLATE and INFLATERAW + // (after initialization). + this.mode = INFLATE; + } + + break; + default: + throw new Error("invalid number of gzip magic number bytes read"); + } + + // fallthrough + + case INFLATE: + case GUNZIP: + case INFLATERAW: + this.err = zlib_inflate.inflate(this.strm, this.flush); + + // If data was encoded with dictionary + if (this.err === Z_NEED_DICT && this.dictionary) { + // Load it + this.err = zlib_inflate.inflateSetDictionary( + this.strm, + this.dictionary, + ); + if (this.err === Z_OK) { + // And try to decode again + this.err = zlib_inflate.inflate(this.strm, this.flush); + } else if (this.err === Z_DATA_ERROR) { + // Both inflateSetDictionary() and inflate() return Z_DATA_ERROR. + // Make it possible for After() to tell a bad dictionary from bad + // input. + this.err = Z_NEED_DICT; + } + } + while ( + this.strm.avail_in > 0 && this.mode === GUNZIP && + this.err === Z_STREAM_END && this.strm.next_in[0] !== 0x00 + ) { + // Bytes remain in input buffer. Perhaps this is another compressed + // member in the same archive, or just trailing garbage. + // Trailing zero bytes are okay, though, since they are frequently + // used for padding. + + this.reset(); + this.err = zlib_inflate.inflate(this.strm, this.flush); + } + break; + default: + throw new Error("Unknown mode " + this.mode); + } +}; + +Zlib.prototype._checkError = function () { + // Acceptable error states depend on the type of zlib stream. + switch (this.err) { + case Z_OK: + case Z_BUF_ERROR: + if (this.strm.avail_out !== 0 && this.flush === Z_FINISH) { + this._error("unexpected end of file"); + return false; + } + break; + case Z_STREAM_END: + // normal statuses, not fatal + break; + case Z_NEED_DICT: + if (this.dictionary == null) { + this._error("Missing dictionary"); + } else { + this._error("Bad dictionary"); + } + return false; + default: + // something else. + this._error("Zlib error"); + return false; + } + + return true; +}; + +Zlib.prototype._after = function () { + if (!this._checkError()) { + return; + } + + var avail_out = this.strm.avail_out; + var avail_in = this.strm.avail_in; + + this.write_in_progress = false; + + // call the write() cb + this.callback(avail_in, avail_out); + + if (this.pending_close) { + this.close(); + } +}; + +Zlib.prototype._error = function (message) { + if (this.strm.msg) { + message = this.strm.msg; + } + this.onerror(message, this.err); + + // no hope of rescue. + this.write_in_progress = false; + if (this.pending_close) { + this.close(); + } +}; + +Zlib.prototype.init = function ( + windowBits, + level, + memLevel, + strategy, + dictionary, +) { + assert( + arguments.length === 4 || arguments.length === 5, + "init(windowBits, level, memLevel, strategy, [dictionary])", + ); + + assert(windowBits >= 8 && windowBits <= 15, "invalid windowBits"); + assert(level >= -1 && level <= 9, "invalid compression level"); + + assert(memLevel >= 1 && memLevel <= 9, "invalid memlevel"); + + assert( + strategy === Z_FILTERED || strategy === Z_HUFFMAN_ONLY || + strategy === Z_RLE || strategy === Z_FIXED || + strategy === Z_DEFAULT_STRATEGY, + "invalid strategy", + ); + + this._init(level, windowBits, memLevel, strategy, dictionary); + this._setDictionary(); +}; + +Zlib.prototype.params = function () { + throw new Error("deflateParams Not supported"); +}; + +Zlib.prototype.reset = function () { + this._reset(); + this._setDictionary(); +}; + +Zlib.prototype._init = function ( + level, + windowBits, + memLevel, + strategy, + dictionary, +) { + this.level = level; + this.windowBits = windowBits; + this.memLevel = memLevel; + this.strategy = strategy; + + this.flush = Z_NO_FLUSH; + + this.err = Z_OK; + + if (this.mode === GZIP || this.mode === GUNZIP) { + this.windowBits += 16; + } + + if (this.mode === UNZIP) { + this.windowBits += 32; + } + + if (this.mode === DEFLATERAW || this.mode === INFLATERAW) { + this.windowBits = -1 * this.windowBits; + } + + this.strm = new Zstream(); + + switch (this.mode) { + case DEFLATE: + case GZIP: + case DEFLATERAW: + this.err = zlib_deflate.deflateInit2( + this.strm, + this.level, + Z_DEFLATED, + this.windowBits, + this.memLevel, + this.strategy, + ); + break; + case INFLATE: + case GUNZIP: + case INFLATERAW: + case UNZIP: + this.err = zlib_inflate.inflateInit2(this.strm, this.windowBits); + break; + default: + throw new Error("Unknown mode " + this.mode); + } + + if (this.err !== Z_OK) { + this._error("Init error"); + } + + this.dictionary = dictionary; + + this.write_in_progress = false; + this.init_done = true; +}; + +Zlib.prototype._setDictionary = function () { + if (this.dictionary == null) { + return; + } + + this.err = Z_OK; + + switch (this.mode) { + case DEFLATE: + case DEFLATERAW: + this.err = zlib_deflate.deflateSetDictionary(this.strm, this.dictionary); + break; + default: + break; + } + + if (this.err !== Z_OK) { + this._error("Failed to set dictionary"); + } +}; + +Zlib.prototype._reset = function () { + this.err = Z_OK; + + switch (this.mode) { + case DEFLATE: + case DEFLATERAW: + case GZIP: + this.err = zlib_deflate.deflateReset(this.strm); + break; + case INFLATE: + case INFLATERAW: + case GUNZIP: + this.err = zlib_inflate.inflateReset(this.strm); + break; + default: + break; + } + + if (this.err !== Z_OK) { + this._error("Failed to reset stream"); + } +}; + +export { Zlib }; diff --git a/ext/node/polyfills/assert.ts b/ext/node/polyfills/assert.ts new file mode 100644 index 00000000000000..2d5e86e587773c --- /dev/null +++ b/ext/node/polyfills/assert.ts @@ -0,0 +1,940 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. +// deno-lint-ignore-file ban-types +import { + AssertionError, + AssertionErrorConstructorOptions, +} from "internal:deno_node/polyfills/assertion_error.ts"; +import * as asserts from "internal:deno_node/polyfills/_util/std_asserts.ts"; +import { inspect } from "internal:deno_node/polyfills/util.ts"; +import { + ERR_AMBIGUOUS_ARGUMENT, + ERR_INVALID_ARG_TYPE, + ERR_INVALID_ARG_VALUE, + ERR_INVALID_RETURN_VALUE, + ERR_MISSING_ARGS, +} from "internal:deno_node/polyfills/internal/errors.ts"; +import { isDeepEqual } from "internal:deno_node/polyfills/internal/util/comparisons.ts"; + +function innerFail(obj: { + actual?: unknown; + expected?: unknown; + message?: string | Error; + operator?: string; +}) { + if (obj.message instanceof Error) { + throw obj.message; + } + + throw new AssertionError({ + actual: obj.actual, + expected: obj.expected, + message: obj.message, + operator: obj.operator, + }); +} + +interface ExtendedAssertionErrorConstructorOptions + extends AssertionErrorConstructorOptions { + generatedMessage?: boolean; +} + +// TODO(uki00a): This function is a workaround for setting the `generatedMessage` property flexibly. +function createAssertionError( + options: ExtendedAssertionErrorConstructorOptions, +): AssertionError { + const error = new AssertionError(options); + if (options.generatedMessage) { + error.generatedMessage = true; + } + return error; +} + +/** Converts the std assertion error to node.js assertion error */ +function toNode( + fn: () => void, + opts?: { + actual: unknown; + expected: unknown; + message?: string | Error; + operator?: string; + }, +) { + const { operator, message, actual, expected } = opts || {}; + try { + fn(); + } catch (e) { + if (e instanceof asserts.AssertionError) { + if (typeof message === "string") { + throw new AssertionError({ + operator, + message, + actual, + expected, + }); + } else if (message instanceof Error) { + throw message; + } else { + throw new AssertionError({ + operator, + message: e.message, + actual, + expected, + }); + } + } + throw e; + } +} + +function assert(actual: unknown, message?: string | Error): asserts actual { + if (arguments.length === 0) { + throw new AssertionError({ + message: "No value argument passed to `assert.ok()`", + }); + } + toNode( + () => asserts.assert(actual), + { message, actual, expected: true }, + ); +} +const ok = assert; + +function throws( + fn: () => void, + error?: RegExp | Function | Error, + message?: string, +) { + // Check arg types + if (typeof fn !== "function") { + throw new ERR_INVALID_ARG_TYPE("fn", "function", fn); + } + if ( + typeof error === "object" && error !== null && + Object.getPrototypeOf(error) === Object.prototype && + Object.keys(error).length === 0 + ) { + // error is an empty object + throw new ERR_INVALID_ARG_VALUE( + "error", + error, + "may not be an empty object", + ); + } + if (typeof message === "string") { + if ( + !(error instanceof RegExp) && typeof error !== "function" && + !(error instanceof Error) && typeof error !== "object" + ) { + throw new ERR_INVALID_ARG_TYPE("error", [ + "Function", + "Error", + "RegExp", + "Object", + ], error); + } + } else { + if ( + typeof error !== "undefined" && typeof error !== "string" && + !(error instanceof RegExp) && typeof error !== "function" && + !(error instanceof Error) && typeof error !== "object" + ) { + throw new ERR_INVALID_ARG_TYPE("error", [ + "Function", + "Error", + "RegExp", + "Object", + ], error); + } + } + + // Checks test function + try { + fn(); + } catch (e) { + if ( + validateThrownError(e, error, message, { + operator: throws, + }) + ) { + return; + } + } + if (message) { + let msg = `Missing expected exception: ${message}`; + if (typeof error === "function" && error?.name) { + msg = `Missing expected exception (${error.name}): ${message}`; + } + throw new AssertionError({ + message: msg, + operator: "throws", + actual: undefined, + expected: error, + }); + } else if (typeof error === "string") { + // Use case of throws(fn, message) + throw new AssertionError({ + message: `Missing expected exception: ${error}`, + operator: "throws", + actual: undefined, + expected: undefined, + }); + } else if (typeof error === "function" && error?.prototype !== undefined) { + throw new AssertionError({ + message: `Missing expected exception (${error.name}).`, + operator: "throws", + actual: undefined, + expected: error, + }); + } else { + throw new AssertionError({ + message: "Missing expected exception.", + operator: "throws", + actual: undefined, + expected: error, + }); + } +} + +function doesNotThrow( + fn: () => void, + message?: string, +): void; +function doesNotThrow( + fn: () => void, + error?: Function, + message?: string | Error, +): void; +function doesNotThrow( + fn: () => void, + error?: RegExp, + message?: string, +): void; +function doesNotThrow( + fn: () => void, + expected?: Function | RegExp | string, + message?: string | Error, +) { + // Check arg type + if (typeof fn !== "function") { + throw new ERR_INVALID_ARG_TYPE("fn", "function", fn); + } else if ( + !(expected instanceof RegExp) && typeof expected !== "function" && + typeof expected !== "string" && typeof expected !== "undefined" + ) { + throw new ERR_INVALID_ARG_TYPE("expected", ["Function", "RegExp"], fn); + } + + // Checks test function + try { + fn(); + } catch (e) { + gotUnwantedException(e, expected, message, doesNotThrow); + } +} + +function equal( + actual: unknown, + expected: unknown, + message?: string | Error, +) { + if (arguments.length < 2) { + throw new ERR_MISSING_ARGS("actual", "expected"); + } + + if (actual == expected) { + return; + } + + if (Number.isNaN(actual) && Number.isNaN(expected)) { + return; + } + + if (typeof message === "string") { + throw new AssertionError({ + message, + }); + } else if (message instanceof Error) { + throw message; + } + + toNode( + () => asserts.assertStrictEquals(actual, expected), + { + message: message || `${actual} == ${expected}`, + operator: "==", + actual, + expected, + }, + ); +} +function notEqual( + actual: unknown, + expected: unknown, + message?: string | Error, +) { + if (arguments.length < 2) { + throw new ERR_MISSING_ARGS("actual", "expected"); + } + + if (Number.isNaN(actual) && Number.isNaN(expected)) { + throw new AssertionError({ + message: `${actual} != ${expected}`, + operator: "!=", + actual, + expected, + }); + } + if (actual != expected) { + return; + } + + if (typeof message === "string") { + throw new AssertionError({ + message, + }); + } else if (message instanceof Error) { + throw message; + } + + toNode( + () => asserts.assertNotStrictEquals(actual, expected), + { + message: message || `${actual} != ${expected}`, + operator: "!=", + actual, + expected, + }, + ); +} +function strictEqual( + actual: unknown, + expected: unknown, + message?: string | Error, +) { + if (arguments.length < 2) { + throw new ERR_MISSING_ARGS("actual", "expected"); + } + + toNode( + () => asserts.assertStrictEquals(actual, expected), + { message, operator: "strictEqual", actual, expected }, + ); +} +function notStrictEqual( + actual: unknown, + expected: unknown, + message?: string | Error, +) { + if (arguments.length < 2) { + throw new ERR_MISSING_ARGS("actual", "expected"); + } + + toNode( + () => asserts.assertNotStrictEquals(actual, expected), + { message, actual, expected, operator: "notStrictEqual" }, + ); +} + +function deepEqual( + actual: unknown, + expected: unknown, + message?: string | Error, +) { + if (arguments.length < 2) { + throw new ERR_MISSING_ARGS("actual", "expected"); + } + + if (!isDeepEqual(actual, expected)) { + innerFail({ actual, expected, message, operator: "deepEqual" }); + } +} +function notDeepEqual( + actual: unknown, + expected: unknown, + message?: string | Error, +) { + if (arguments.length < 2) { + throw new ERR_MISSING_ARGS("actual", "expected"); + } + + if (isDeepEqual(actual, expected)) { + innerFail({ actual, expected, message, operator: "notDeepEqual" }); + } +} +function deepStrictEqual( + actual: unknown, + expected: unknown, + message?: string | Error, +) { + if (arguments.length < 2) { + throw new ERR_MISSING_ARGS("actual", "expected"); + } + + toNode( + () => asserts.assertEquals(actual, expected), + { message, actual, expected, operator: "deepStrictEqual" }, + ); +} +function notDeepStrictEqual( + actual: unknown, + expected: unknown, + message?: string | Error, +) { + if (arguments.length < 2) { + throw new ERR_MISSING_ARGS("actual", "expected"); + } + + toNode( + () => asserts.assertNotEquals(actual, expected), + { message, actual, expected, operator: "deepNotStrictEqual" }, + ); +} + +function fail(message?: string | Error): never { + if (typeof message === "string" || message == null) { + throw createAssertionError({ + message: message ?? "Failed", + operator: "fail", + generatedMessage: message == null, + }); + } else { + throw message; + } +} +function match(actual: string, regexp: RegExp, message?: string | Error) { + if (arguments.length < 2) { + throw new ERR_MISSING_ARGS("actual", "regexp"); + } + if (!(regexp instanceof RegExp)) { + throw new ERR_INVALID_ARG_TYPE("regexp", "RegExp", regexp); + } + + toNode( + () => asserts.assertMatch(actual, regexp), + { message, actual, expected: regexp, operator: "match" }, + ); +} + +function doesNotMatch( + string: string, + regexp: RegExp, + message?: string | Error, +) { + if (arguments.length < 2) { + throw new ERR_MISSING_ARGS("string", "regexp"); + } + if (!(regexp instanceof RegExp)) { + throw new ERR_INVALID_ARG_TYPE("regexp", "RegExp", regexp); + } + if (typeof string !== "string") { + if (message instanceof Error) { + throw message; + } + throw new AssertionError({ + message: message || + `The "string" argument must be of type string. Received type ${typeof string} (${ + inspect(string) + })`, + actual: string, + expected: regexp, + operator: "doesNotMatch", + }); + } + + toNode( + () => asserts.assertNotMatch(string, regexp), + { message, actual: string, expected: regexp, operator: "doesNotMatch" }, + ); +} + +function strict(actual: unknown, message?: string | Error): asserts actual { + if (arguments.length === 0) { + throw new AssertionError({ + message: "No value argument passed to `assert.ok()`", + }); + } + assert(actual, message); +} + +function rejects( + // deno-lint-ignore no-explicit-any + asyncFn: Promise | (() => Promise), + error?: RegExp | Function | Error, +): Promise; + +function rejects( + // deno-lint-ignore no-explicit-any + asyncFn: Promise | (() => Promise), + message?: string, +): Promise; + +// Intentionally avoid using async/await because test-assert-async.js requires it +function rejects( + // deno-lint-ignore no-explicit-any + asyncFn: Promise | (() => Promise), + error?: RegExp | Function | Error | string, + message?: string, +) { + let promise: Promise; + if (typeof asyncFn === "function") { + try { + promise = asyncFn(); + } catch (err) { + // If `asyncFn` throws an error synchronously, this function returns a rejected promise. + return Promise.reject(err); + } + + if (!isValidThenable(promise)) { + return Promise.reject( + new ERR_INVALID_RETURN_VALUE( + "instance of Promise", + "promiseFn", + promise, + ), + ); + } + } else if (!isValidThenable(asyncFn)) { + return Promise.reject( + new ERR_INVALID_ARG_TYPE("promiseFn", ["function", "Promise"], asyncFn), + ); + } else { + promise = asyncFn; + } + + function onFulfilled() { + let message = "Missing expected rejection"; + if (typeof error === "string") { + message += `: ${error}`; + } else if (typeof error === "function" && error.prototype !== undefined) { + message += ` (${error.name}).`; + } else { + message += "."; + } + return Promise.reject(createAssertionError({ + message, + operator: "rejects", + generatedMessage: true, + })); + } + + // deno-lint-ignore camelcase + function rejects_onRejected(e: Error) { // TODO(uki00a): In order to `test-assert-async.js` pass, intentionally adds `rejects_` as a prefix. + if ( + validateThrownError(e, error, message, { + operator: rejects, + validationFunctionName: "validate", + }) + ) { + return; + } + } + + return promise.then(onFulfilled, rejects_onRejected); +} + +function doesNotReject( + // deno-lint-ignore no-explicit-any + asyncFn: Promise | (() => Promise), + error?: RegExp | Function, +): Promise; + +function doesNotReject( + // deno-lint-ignore no-explicit-any + asyncFn: Promise | (() => Promise), + message?: string, +): Promise; + +// Intentionally avoid using async/await because test-assert-async.js requires it +function doesNotReject( + // deno-lint-ignore no-explicit-any + asyncFn: Promise | (() => Promise), + error?: RegExp | Function | string, + message?: string, +) { + // deno-lint-ignore no-explicit-any + let promise: Promise; + if (typeof asyncFn === "function") { + try { + const value = asyncFn(); + if (!isValidThenable(value)) { + return Promise.reject( + new ERR_INVALID_RETURN_VALUE( + "instance of Promise", + "promiseFn", + value, + ), + ); + } + promise = value; + } catch (e) { + // If `asyncFn` throws an error synchronously, this function returns a rejected promise. + return Promise.reject(e); + } + } else if (!isValidThenable(asyncFn)) { + return Promise.reject( + new ERR_INVALID_ARG_TYPE("promiseFn", ["function", "Promise"], asyncFn), + ); + } else { + promise = asyncFn; + } + + return promise.then( + () => {}, + (e) => gotUnwantedException(e, error, message, doesNotReject), + ); +} + +function gotUnwantedException( + // deno-lint-ignore no-explicit-any + e: any, + expected: RegExp | Function | string | null | undefined, + message: string | Error | null | undefined, + operator: Function, +): never { + if (typeof expected === "string") { + // The use case of doesNotThrow(fn, message); + throw new AssertionError({ + message: + `Got unwanted exception: ${expected}\nActual message: "${e.message}"`, + operator: operator.name, + }); + } else if ( + typeof expected === "function" && expected.prototype !== undefined + ) { + // The use case of doesNotThrow(fn, Error, message); + if (e instanceof expected) { + let msg = `Got unwanted exception: ${e.constructor?.name}`; + if (message) { + msg += ` ${String(message)}`; + } + throw new AssertionError({ + message: msg, + operator: operator.name, + }); + } else if (expected.prototype instanceof Error) { + throw e; + } else { + const result = expected(e); + if (result === true) { + let msg = `Got unwanted rejection.\nActual message: "${e.message}"`; + if (message) { + msg += ` ${String(message)}`; + } + throw new AssertionError({ + message: msg, + operator: operator.name, + }); + } + } + throw e; + } else { + if (message) { + throw new AssertionError({ + message: `Got unwanted exception: ${message}\nActual message: "${ + e ? e.message : String(e) + }"`, + operator: operator.name, + }); + } + throw new AssertionError({ + message: `Got unwanted exception.\nActual message: "${ + e ? e.message : String(e) + }"`, + operator: operator.name, + }); + } +} + +/** + * Throws `value` if the value is not `null` or `undefined`. + * + * @param err + */ +// deno-lint-ignore no-explicit-any +function ifError(err: any) { + if (err !== null && err !== undefined) { + let message = "ifError got unwanted exception: "; + + if (typeof err === "object" && typeof err.message === "string") { + if (err.message.length === 0 && err.constructor) { + message += err.constructor.name; + } else { + message += err.message; + } + } else { + message += inspect(err); + } + + const newErr = new AssertionError({ + actual: err, + expected: null, + operator: "ifError", + message, + stackStartFn: ifError, + }); + + // Make sure we actually have a stack trace! + const origStack = err.stack; + + if (typeof origStack === "string") { + // This will remove any duplicated frames from the error frames taken + // from within `ifError` and add the original error frames to the newly + // created ones. + const tmp2 = origStack.split("\n"); + tmp2.shift(); + + // Filter all frames existing in err.stack. + let tmp1 = newErr!.stack?.split("\n"); + + for (const errFrame of tmp2) { + // Find the first occurrence of the frame. + const pos = tmp1?.indexOf(errFrame); + + if (pos !== -1) { + // Only keep new frames. + tmp1 = tmp1?.slice(0, pos); + + break; + } + } + + newErr.stack = `${tmp1?.join("\n")}\n${tmp2.join("\n")}`; + } + + throw newErr; + } +} + +interface ValidateThrownErrorOptions { + operator: Function; + validationFunctionName?: string; +} + +function validateThrownError( + // deno-lint-ignore no-explicit-any + e: any, + error: RegExp | Function | Error | string | null | undefined, + message: string | undefined | null, + options: ValidateThrownErrorOptions, +): boolean { + if (typeof error === "string") { + if (message != null) { + throw new ERR_INVALID_ARG_TYPE( + "error", + ["Object", "Error", "Function", "RegExp"], + error, + ); + } else if (typeof e === "object" && e !== null) { + if (e.message === error) { + throw new ERR_AMBIGUOUS_ARGUMENT( + "error/message", + `The error message "${e.message}" is identical to the message.`, + ); + } + } else if (e === error) { + throw new ERR_AMBIGUOUS_ARGUMENT( + "error/message", + `The error "${e}" is identical to the message.`, + ); + } + message = error; + error = undefined; + } + if ( + error instanceof Function && error.prototype !== undefined && + error.prototype instanceof Error + ) { + // error is a constructor + if (e instanceof error) { + return true; + } + throw createAssertionError({ + message: + `The error is expected to be an instance of "${error.name}". Received "${e?.constructor?.name}"\n\nError message:\n\n${e?.message}`, + actual: e, + expected: error, + operator: options.operator.name, + generatedMessage: true, + }); + } + if (error instanceof Function) { + const received = error(e); + if (received === true) { + return true; + } + throw createAssertionError({ + message: `The ${ + options.validationFunctionName + ? `"${options.validationFunctionName}" validation` + : "validation" + } function is expected to return "true". Received ${ + inspect(received) + }\n\nCaught error:\n\n${e}`, + actual: e, + expected: error, + operator: options.operator.name, + generatedMessage: true, + }); + } + if (error instanceof RegExp) { + if (error.test(String(e))) { + return true; + } + throw createAssertionError({ + message: + `The input did not match the regular expression ${error.toString()}. Input:\n\n'${ + String(e) + }'\n`, + actual: e, + expected: error, + operator: options.operator.name, + generatedMessage: true, + }); + } + if (typeof error === "object" && error !== null) { + const keys = Object.keys(error); + if (error instanceof Error) { + keys.push("name", "message"); + } + for (const k of keys) { + if (e == null) { + throw createAssertionError({ + message: message || "object is expected to thrown, but got null", + actual: e, + expected: error, + operator: options.operator.name, + generatedMessage: message == null, + }); + } + + if (typeof e === "string") { + throw createAssertionError({ + message: message || + `object is expected to thrown, but got string: ${e}`, + actual: e, + expected: error, + operator: options.operator.name, + generatedMessage: message == null, + }); + } + if (typeof e === "number") { + throw createAssertionError({ + message: message || + `object is expected to thrown, but got number: ${e}`, + actual: e, + expected: error, + operator: options.operator.name, + generatedMessage: message == null, + }); + } + if (!(k in e)) { + throw createAssertionError({ + message: message || `A key in the expected object is missing: ${k}`, + actual: e, + expected: error, + operator: options.operator.name, + generatedMessage: message == null, + }); + } + const actual = e[k]; + // deno-lint-ignore no-explicit-any + const expected = (error as any)[k]; + if (typeof actual === "string" && expected instanceof RegExp) { + match(actual, expected); + } else { + deepStrictEqual(actual, expected); + } + } + return true; + } + if (typeof error === "undefined") { + return true; + } + throw createAssertionError({ + message: `Invalid expectation: ${error}`, + operator: options.operator.name, + generatedMessage: true, + }); +} + +// deno-lint-ignore no-explicit-any +function isValidThenable(maybeThennable: any): boolean { + if (!maybeThennable) { + return false; + } + + if (maybeThennable instanceof Promise) { + return true; + } + + const isThenable = typeof maybeThennable.then === "function" && + typeof maybeThennable.catch === "function"; + + return isThenable && typeof maybeThennable !== "function"; +} + +Object.assign(strict, { + AssertionError, + deepEqual: deepStrictEqual, + deepStrictEqual, + doesNotMatch, + doesNotReject, + doesNotThrow, + equal: strictEqual, + fail, + ifError, + match, + notDeepEqual: notDeepStrictEqual, + notDeepStrictEqual, + notEqual: notStrictEqual, + notStrictEqual, + ok, + rejects, + strict, + strictEqual, + throws, +}); + +export default Object.assign(assert, { + AssertionError, + deepEqual, + deepStrictEqual, + doesNotMatch, + doesNotReject, + doesNotThrow, + equal, + fail, + ifError, + match, + notDeepEqual, + notDeepStrictEqual, + notEqual, + notStrictEqual, + ok, + rejects, + strict, + strictEqual, + throws, +}); + +export { + AssertionError, + deepEqual, + deepStrictEqual, + doesNotMatch, + doesNotReject, + doesNotThrow, + equal, + fail, + ifError, + match, + notDeepEqual, + notDeepStrictEqual, + notEqual, + notStrictEqual, + ok, + rejects, + strict, + strictEqual, + throws, +}; diff --git a/ext/node/polyfills/assert/strict.ts b/ext/node/polyfills/assert/strict.ts new file mode 100644 index 00000000000000..eab0f4e78df000 --- /dev/null +++ b/ext/node/polyfills/assert/strict.ts @@ -0,0 +1,26 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. +import { strict } from "internal:deno_node/polyfills/assert.ts"; + +export { + AssertionError, + deepEqual, + deepStrictEqual, + doesNotMatch, + doesNotReject, + doesNotThrow, + equal, + fail, + ifError, + match, + notDeepEqual, + notDeepStrictEqual, + notEqual, + notStrictEqual, + ok, + rejects, + strictEqual, + throws, +} from "internal:deno_node/polyfills/assert.ts"; + +export { strict }; +export default strict; diff --git a/ext/node/polyfills/assertion_error.ts b/ext/node/polyfills/assertion_error.ts new file mode 100644 index 00000000000000..f2e221f2024dd8 --- /dev/null +++ b/ext/node/polyfills/assertion_error.ts @@ -0,0 +1,579 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. + +// Adapted from Node.js. Copyright Joyent, Inc. and other Node contributors. + +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: + +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. + +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. + +import { inspect } from "internal:deno_node/polyfills/util.ts"; +import { stripColor as removeColors } from "internal:deno_node/polyfills/_util/std_fmt_colors.ts"; + +function getConsoleWidth(): number { + try { + return Deno.consoleSize().columns; + } catch { + return 80; + } +} + +// TODO(schwarzkopfb): we should implement Node's concept of "primordials" +// Ref: https://github.com/denoland/deno/issues/6040#issuecomment-637305828 +const MathMax = Math.max; +const { Error } = globalThis; +const { + create: ObjectCreate, + defineProperty: ObjectDefineProperty, + getPrototypeOf: ObjectGetPrototypeOf, + getOwnPropertyDescriptor: ObjectGetOwnPropertyDescriptor, + keys: ObjectKeys, +} = Object; + +import { ERR_INVALID_ARG_TYPE } from "internal:deno_node/polyfills/internal/errors.ts"; + +let blue = ""; +let green = ""; +let red = ""; +let defaultColor = ""; + +const kReadableOperator: { [key: string]: string } = { + deepStrictEqual: "Expected values to be strictly deep-equal:", + strictEqual: "Expected values to be strictly equal:", + strictEqualObject: 'Expected "actual" to be reference-equal to "expected":', + deepEqual: "Expected values to be loosely deep-equal:", + notDeepStrictEqual: 'Expected "actual" not to be strictly deep-equal to:', + notStrictEqual: 'Expected "actual" to be strictly unequal to:', + notStrictEqualObject: + 'Expected "actual" not to be reference-equal to "expected":', + notDeepEqual: 'Expected "actual" not to be loosely deep-equal to:', + notIdentical: "Values have same structure but are not reference-equal:", + notDeepEqualUnequal: "Expected values not to be loosely deep-equal:", +}; + +// Comparing short primitives should just show === / !== instead of using the +// diff. +const kMaxShortLength = 12; + +export function copyError(source: Error): Error { + const keys = ObjectKeys(source); + const target = ObjectCreate(ObjectGetPrototypeOf(source)); + for (const key of keys) { + const desc = ObjectGetOwnPropertyDescriptor(source, key); + + if (desc !== undefined) { + ObjectDefineProperty(target, key, desc); + } + } + ObjectDefineProperty(target, "message", { value: source.message }); + return target; +} + +export function inspectValue(val: unknown): string { + // The util.inspect default values could be changed. This makes sure the + // error messages contain the necessary information nevertheless. + return inspect( + val, + { + compact: true, + customInspect: false, + depth: 1000, + maxArrayLength: Infinity, + // Assert compares only enumerable properties (with a few exceptions). + showHidden: false, + // Assert does not detect proxies currently. + showProxy: false, + sorted: true, + // Inspect getters as we also check them when comparing entries. + getters: true, + }, + ); +} + +export function createErrDiff( + actual: unknown, + expected: unknown, + operator: string, +): string { + let other = ""; + let res = ""; + let end = ""; + let skipped = false; + const actualInspected = inspectValue(actual); + const actualLines = actualInspected.split("\n"); + const expectedLines = inspectValue(expected).split("\n"); + + let i = 0; + let indicator = ""; + + // In case both values are objects or functions explicitly mark them as not + // reference equal for the `strictEqual` operator. + if ( + operator === "strictEqual" && + ((typeof actual === "object" && actual !== null && + typeof expected === "object" && expected !== null) || + (typeof actual === "function" && typeof expected === "function")) + ) { + operator = "strictEqualObject"; + } + + // If "actual" and "expected" fit on a single line and they are not strictly + // equal, check further special handling. + if ( + actualLines.length === 1 && expectedLines.length === 1 && + actualLines[0] !== expectedLines[0] + ) { + // Check for the visible length using the `removeColors()` function, if + // appropriate. + const c = inspect.defaultOptions.colors; + const actualRaw = c ? removeColors(actualLines[0]) : actualLines[0]; + const expectedRaw = c ? removeColors(expectedLines[0]) : expectedLines[0]; + const inputLength = actualRaw.length + expectedRaw.length; + // If the character length of "actual" and "expected" together is less than + // kMaxShortLength and if neither is an object and at least one of them is + // not `zero`, use the strict equal comparison to visualize the output. + if (inputLength <= kMaxShortLength) { + if ( + (typeof actual !== "object" || actual === null) && + (typeof expected !== "object" || expected === null) && + (actual !== 0 || expected !== 0) + ) { // -0 === +0 + return `${kReadableOperator[operator]}\n\n` + + `${actualLines[0]} !== ${expectedLines[0]}\n`; + } + } else if (operator !== "strictEqualObject") { + // If the stderr is a tty and the input length is lower than the current + // columns per line, add a mismatch indicator below the output. If it is + // not a tty, use a default value of 80 characters. + const maxLength = Deno.isatty(Deno.stderr.rid) ? getConsoleWidth() : 80; + if (inputLength < maxLength) { + while (actualRaw[i] === expectedRaw[i]) { + i++; + } + // Ignore the first characters. + if (i > 2) { + // Add position indicator for the first mismatch in case it is a + // single line and the input length is less than the column length. + indicator = `\n ${" ".repeat(i)}^`; + i = 0; + } + } + } + } + + // Remove all ending lines that match (this optimizes the output for + // readability by reducing the number of total changed lines). + let a = actualLines[actualLines.length - 1]; + let b = expectedLines[expectedLines.length - 1]; + while (a === b) { + if (i++ < 3) { + end = `\n ${a}${end}`; + } else { + other = a; + } + actualLines.pop(); + expectedLines.pop(); + if (actualLines.length === 0 || expectedLines.length === 0) { + break; + } + a = actualLines[actualLines.length - 1]; + b = expectedLines[expectedLines.length - 1]; + } + + const maxLines = MathMax(actualLines.length, expectedLines.length); + // Strict equal with identical objects that are not identical by reference. + // E.g., assert.deepStrictEqual({ a: Symbol() }, { a: Symbol() }) + if (maxLines === 0) { + // We have to get the result again. The lines were all removed before. + const actualLines = actualInspected.split("\n"); + + // Only remove lines in case it makes sense to collapse those. + if (actualLines.length > 50) { + actualLines[46] = `${blue}...${defaultColor}`; + while (actualLines.length > 47) { + actualLines.pop(); + } + } + + return `${kReadableOperator.notIdentical}\n\n${actualLines.join("\n")}\n`; + } + + // There were at least five identical lines at the end. Mark a couple of + // skipped. + if (i >= 5) { + end = `\n${blue}...${defaultColor}${end}`; + skipped = true; + } + if (other !== "") { + end = `\n ${other}${end}`; + other = ""; + } + + let printedLines = 0; + let identical = 0; + const msg = kReadableOperator[operator] + + `\n${green}+ actual${defaultColor} ${red}- expected${defaultColor}`; + const skippedMsg = ` ${blue}...${defaultColor} Lines skipped`; + + let lines = actualLines; + let plusMinus = `${green}+${defaultColor}`; + let maxLength = expectedLines.length; + if (actualLines.length < maxLines) { + lines = expectedLines; + plusMinus = `${red}-${defaultColor}`; + maxLength = actualLines.length; + } + + for (i = 0; i < maxLines; i++) { + if (maxLength < i + 1) { + // If more than two former lines are identical, print them. Collapse them + // in case more than five lines were identical. + if (identical > 2) { + if (identical > 3) { + if (identical > 4) { + if (identical === 5) { + res += `\n ${lines[i - 3]}`; + printedLines++; + } else { + res += `\n${blue}...${defaultColor}`; + skipped = true; + } + } + res += `\n ${lines[i - 2]}`; + printedLines++; + } + res += `\n ${lines[i - 1]}`; + printedLines++; + } + // No identical lines before. + identical = 0; + // Add the expected line to the cache. + if (lines === actualLines) { + res += `\n${plusMinus} ${lines[i]}`; + } else { + other += `\n${plusMinus} ${lines[i]}`; + } + printedLines++; + // Only extra actual lines exist + // Lines diverge + } else { + const expectedLine = expectedLines[i]; + let actualLine = actualLines[i]; + // If the lines diverge, specifically check for lines that only diverge by + // a trailing comma. In that case it is actually identical and we should + // mark it as such. + let divergingLines = actualLine !== expectedLine && + (!actualLine.endsWith(",") || + actualLine.slice(0, -1) !== expectedLine); + // If the expected line has a trailing comma but is otherwise identical, + // add a comma at the end of the actual line. Otherwise the output could + // look weird as in: + // + // [ + // 1 // No comma at the end! + // + 2 + // ] + // + if ( + divergingLines && + expectedLine.endsWith(",") && + expectedLine.slice(0, -1) === actualLine + ) { + divergingLines = false; + actualLine += ","; + } + if (divergingLines) { + // If more than two former lines are identical, print them. Collapse + // them in case more than five lines were identical. + if (identical > 2) { + if (identical > 3) { + if (identical > 4) { + if (identical === 5) { + res += `\n ${actualLines[i - 3]}`; + printedLines++; + } else { + res += `\n${blue}...${defaultColor}`; + skipped = true; + } + } + res += `\n ${actualLines[i - 2]}`; + printedLines++; + } + res += `\n ${actualLines[i - 1]}`; + printedLines++; + } + // No identical lines before. + identical = 0; + // Add the actual line to the result and cache the expected diverging + // line so consecutive diverging lines show up as +++--- and not +-+-+-. + res += `\n${green}+${defaultColor} ${actualLine}`; + other += `\n${red}-${defaultColor} ${expectedLine}`; + printedLines += 2; + // Lines are identical + } else { + // Add all cached information to the result before adding other things + // and reset the cache. + res += other; + other = ""; + identical++; + // The very first identical line since the last diverging line is be + // added to the result. + if (identical <= 2) { + res += `\n ${actualLine}`; + printedLines++; + } + } + } + // Inspected object to big (Show ~50 rows max) + if (printedLines > 50 && i < maxLines - 2) { + return `${msg}${skippedMsg}\n${res}\n${blue}...${defaultColor}${other}\n` + + `${blue}...${defaultColor}`; + } + } + + return `${msg}${skipped ? skippedMsg : ""}\n${res}${other}${end}${indicator}`; +} + +export interface AssertionErrorDetailsDescriptor { + message: string; + actual: unknown; + expected: unknown; + operator: string; + stack: Error; +} + +export interface AssertionErrorConstructorOptions { + message?: string; + actual?: unknown; + expected?: unknown; + operator?: string; + details?: AssertionErrorDetailsDescriptor[]; + // deno-lint-ignore ban-types + stackStartFn?: Function; + // Compatibility with older versions. + // deno-lint-ignore ban-types + stackStartFunction?: Function; +} + +interface ErrorWithStackTraceLimit extends ErrorConstructor { + stackTraceLimit: number; +} + +export class AssertionError extends Error { + [key: string]: unknown; + + // deno-lint-ignore constructor-super + constructor(options: AssertionErrorConstructorOptions) { + if (typeof options !== "object" || options === null) { + throw new ERR_INVALID_ARG_TYPE("options", "Object", options); + } + const { + message, + operator, + stackStartFn, + details, + // Compatibility with older versions. + stackStartFunction, + } = options; + let { + actual, + expected, + } = options; + + // TODO(schwarzkopfb): `stackTraceLimit` should be added to `ErrorConstructor` in + // cli/dts/lib.deno.shared_globals.d.ts + const limit = (Error as ErrorWithStackTraceLimit).stackTraceLimit; + (Error as ErrorWithStackTraceLimit).stackTraceLimit = 0; + + if (message != null) { + super(String(message)); + } else { + if (Deno.isatty(Deno.stderr.rid)) { + // Reset on each call to make sure we handle dynamically set environment + // variables correct. + if (Deno.noColor) { + blue = ""; + green = ""; + defaultColor = ""; + red = ""; + } else { + blue = "\u001b[34m"; + green = "\u001b[32m"; + defaultColor = "\u001b[39m"; + red = "\u001b[31m"; + } + } + // Prevent the error stack from being visible by duplicating the error + // in a very close way to the original in case both sides are actually + // instances of Error. + if ( + typeof actual === "object" && actual !== null && + typeof expected === "object" && expected !== null && + "stack" in actual && actual instanceof Error && + "stack" in expected && expected instanceof Error + ) { + actual = copyError(actual); + expected = copyError(expected); + } + + if (operator === "deepStrictEqual" || operator === "strictEqual") { + super(createErrDiff(actual, expected, operator)); + } else if ( + operator === "notDeepStrictEqual" || + operator === "notStrictEqual" + ) { + // In case the objects are equal but the operator requires unequal, show + // the first object and say A equals B + let base = kReadableOperator[operator]; + const res = inspectValue(actual).split("\n"); + + // In case "actual" is an object or a function, it should not be + // reference equal. + if ( + operator === "notStrictEqual" && + ((typeof actual === "object" && actual !== null) || + typeof actual === "function") + ) { + base = kReadableOperator.notStrictEqualObject; + } + + // Only remove lines in case it makes sense to collapse those. + if (res.length > 50) { + res[46] = `${blue}...${defaultColor}`; + while (res.length > 47) { + res.pop(); + } + } + + // Only print a single input. + if (res.length === 1) { + super(`${base}${res[0].length > 5 ? "\n\n" : " "}${res[0]}`); + } else { + super(`${base}\n\n${res.join("\n")}\n`); + } + } else { + let res = inspectValue(actual); + let other = inspectValue(expected); + const knownOperator = kReadableOperator[operator ?? ""]; + if (operator === "notDeepEqual" && res === other) { + res = `${knownOperator}\n\n${res}`; + if (res.length > 1024) { + res = `${res.slice(0, 1021)}...`; + } + super(res); + } else { + if (res.length > 512) { + res = `${res.slice(0, 509)}...`; + } + if (other.length > 512) { + other = `${other.slice(0, 509)}...`; + } + if (operator === "deepEqual") { + res = `${knownOperator}\n\n${res}\n\nshould loosely deep-equal\n\n`; + } else { + const newOp = kReadableOperator[`${operator}Unequal`]; + if (newOp) { + res = `${newOp}\n\n${res}\n\nshould not loosely deep-equal\n\n`; + } else { + other = ` ${operator} ${other}`; + } + } + super(`${res}${other}`); + } + } + } + + (Error as ErrorWithStackTraceLimit).stackTraceLimit = limit; + + this.generatedMessage = !message; + ObjectDefineProperty(this, "name", { + __proto__: null, + value: "AssertionError [ERR_ASSERTION]", + enumerable: false, + writable: true, + configurable: true, + // deno-lint-ignore no-explicit-any + } as any); + this.code = "ERR_ASSERTION"; + + if (details) { + this.actual = undefined; + this.expected = undefined; + this.operator = undefined; + + for (let i = 0; i < details.length; i++) { + this["message " + i] = details[i].message; + this["actual " + i] = details[i].actual; + this["expected " + i] = details[i].expected; + this["operator " + i] = details[i].operator; + this["stack trace " + i] = details[i].stack; + } + } else { + this.actual = actual; + this.expected = expected; + this.operator = operator; + } + + // @ts-ignore this function is not available in lib.dom.d.ts + Error.captureStackTrace(this, stackStartFn || stackStartFunction); + // Create error message including the error code in the name. + this.stack; + // Reset the name. + this.name = "AssertionError"; + } + + override toString() { + return `${this.name} [${this.code}]: ${this.message}`; + } + + [inspect.custom](_recurseTimes: number, ctx: Record) { + // Long strings should not be fully inspected. + const tmpActual = this.actual; + const tmpExpected = this.expected; + + for (const name of ["actual", "expected"]) { + if (typeof this[name] === "string") { + const value = this[name] as string; + const lines = value.split("\n"); + if (lines.length > 10) { + lines.length = 10; + this[name] = `${lines.join("\n")}\n...`; + } else if (value.length > 512) { + this[name] = `${value.slice(512)}...`; + } + } + } + + // This limits the `actual` and `expected` property default inspection to + // the minimum depth. Otherwise those values would be too verbose compared + // to the actual error message which contains a combined view of these two + // input values. + const result = inspect(this, { + ...ctx, + customInspect: false, + depth: 0, + }); + + // Reset the properties after inspection. + this.actual = tmpActual; + this.expected = tmpExpected; + + return result; + } +} + +export default AssertionError; diff --git a/ext/node/polyfills/async_hooks.ts b/ext/node/polyfills/async_hooks.ts new file mode 100644 index 00000000000000..295f01ec855452 --- /dev/null +++ b/ext/node/polyfills/async_hooks.ts @@ -0,0 +1,331 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. +// Copyright Joyent and Node contributors. All rights reserved. MIT license. + +// This implementation is inspired by "workerd" AsyncLocalStorage implementation: +// https://github.com/cloudflare/workerd/blob/77fd0ed6ddba184414f0216508fc62b06e716cab/src/workerd/api/node/async-hooks.c++#L9 + +import { validateFunction } from "internal:deno_node/polyfills/internal/validators.mjs"; +import { core } from "internal:deno_node/polyfills/_core.ts"; + +function assert(cond: boolean) { + if (!cond) throw new Error("Assertion failed"); +} +const asyncContextStack: AsyncContextFrame[] = []; + +function pushAsyncFrame(frame: AsyncContextFrame) { + asyncContextStack.push(frame); +} + +function popAsyncFrame() { + assert(asyncContextStack.length > 0); + asyncContextStack.pop(); +} + +let rootAsyncFrame: AsyncContextFrame | undefined = undefined; +let promiseHooksSet = false; + +const asyncContext = Symbol("asyncContext"); +function isRejected(promise: Promise) { + const [state] = core.getPromiseDetails(promise); + return state == 2; +} + +function setPromiseHooks() { + if (promiseHooksSet) { + return; + } + promiseHooksSet = true; + + const init = (promise: Promise) => { + const currentFrame = AsyncContextFrame.current(); + if (!currentFrame.isRoot()) { + assert(AsyncContextFrame.tryGetContext(promise) == null); + AsyncContextFrame.attachContext(promise); + } + }; + const before = (promise: Promise) => { + const maybeFrame = AsyncContextFrame.tryGetContext(promise); + if (maybeFrame) { + pushAsyncFrame(maybeFrame); + } else { + pushAsyncFrame(AsyncContextFrame.getRootAsyncContext()); + } + }; + const after = (promise: Promise) => { + popAsyncFrame(); + if (!isRejected(promise)) { + // @ts-ignore promise async context + delete promise[asyncContext]; + } + }; + const resolve = (promise: Promise) => { + const currentFrame = AsyncContextFrame.current(); + if ( + !currentFrame.isRoot() && isRejected(promise) && + AsyncContextFrame.tryGetContext(promise) == null + ) { + AsyncContextFrame.attachContext(promise); + } + }; + + core.setPromiseHooks(init, before, after, resolve); +} + +class AsyncContextFrame { + storage: StorageEntry[]; + constructor( + maybeParent?: AsyncContextFrame | null, + maybeStorageEntry?: StorageEntry | null, + isRoot = false, + ) { + this.storage = []; + + setPromiseHooks(); + + const propagate = (parent: AsyncContextFrame) => { + parent.storage = parent.storage.filter((entry) => !entry.key.isDead()); + parent.storage.forEach((entry) => this.storage.push(entry)); + + if (maybeStorageEntry) { + const existingEntry = this.storage.find((entry) => + entry.key === maybeStorageEntry.key + ); + if (existingEntry) { + existingEntry.value = maybeStorageEntry.value; + } else { + this.storage.push(maybeStorageEntry); + } + } + }; + + if (!isRoot) { + if (maybeParent) { + propagate(maybeParent); + } else { + propagate(AsyncContextFrame.current()); + } + } + } + + static tryGetContext(promise: Promise) { + // @ts-ignore promise async context + return promise[asyncContext]; + } + + static attachContext(promise: Promise) { + assert(!(asyncContext in promise)); + // @ts-ignore promise async context + promise[asyncContext] = AsyncContextFrame.current(); + } + + static getRootAsyncContext() { + if (typeof rootAsyncFrame !== "undefined") { + return rootAsyncFrame; + } + + rootAsyncFrame = new AsyncContextFrame(null, null, true); + return rootAsyncFrame; + } + + static current() { + if (asyncContextStack.length === 0) { + return AsyncContextFrame.getRootAsyncContext(); + } + + return asyncContextStack[asyncContextStack.length - 1]; + } + + static create( + maybeParent?: AsyncContextFrame | null, + maybeStorageEntry?: StorageEntry | null, + ) { + return new AsyncContextFrame(maybeParent, maybeStorageEntry); + } + + static wrap( + fn: () => unknown, + maybeFrame: AsyncContextFrame | undefined, + // deno-lint-ignore no-explicit-any + thisArg: any, + ) { + // deno-lint-ignore no-explicit-any + return (...args: any) => { + const frame = maybeFrame || AsyncContextFrame.current(); + Scope.enter(frame); + try { + return fn.apply(thisArg, args); + } finally { + Scope.exit(); + } + }; + } + + get(key: StorageKey) { + assert(!key.isDead()); + this.storage = this.storage.filter((entry) => !entry.key.isDead()); + const entry = this.storage.find((entry) => entry.key === key); + if (entry) { + return entry.value; + } + return undefined; + } + + isRoot() { + return AsyncContextFrame.getRootAsyncContext() == this; + } +} + +export class AsyncResource { + frame: AsyncContextFrame; + type: string; + constructor(type: string) { + this.type = type; + this.frame = AsyncContextFrame.current(); + } + + runInAsyncScope( + fn: (...args: unknown[]) => unknown, + thisArg: unknown, + ...args: unknown[] + ) { + Scope.enter(this.frame); + + try { + return fn.apply(thisArg, args); + } finally { + Scope.exit(); + } + } + + bind(fn: (...args: unknown[]) => unknown, thisArg = this) { + validateFunction(fn, "fn"); + const frame = AsyncContextFrame.current(); + const bound = AsyncContextFrame.wrap(fn, frame, thisArg); + + Object.defineProperties(bound, { + "length": { + configurable: true, + enumerable: false, + value: fn.length, + writable: false, + }, + "asyncResource": { + configurable: true, + enumerable: true, + value: this, + writable: true, + }, + }); + return bound; + } + + static bind( + fn: (...args: unknown[]) => unknown, + type?: string, + thisArg?: AsyncResource, + ) { + type = type || fn.name; + return (new AsyncResource(type || "AsyncResource")).bind(fn, thisArg); + } +} + +class Scope { + static enter(maybeFrame?: AsyncContextFrame) { + if (maybeFrame) { + pushAsyncFrame(maybeFrame); + } else { + pushAsyncFrame(AsyncContextFrame.getRootAsyncContext()); + } + } + + static exit() { + popAsyncFrame(); + } +} + +class StorageEntry { + key: StorageKey; + value: unknown; + constructor(key: StorageKey, value: unknown) { + this.key = key; + this.value = value; + } +} + +class StorageKey { + #dead = false; + + reset() { + this.#dead = true; + } + + isDead() { + return this.#dead; + } +} + +const fnReg = new FinalizationRegistry((key: StorageKey) => { + key.reset(); +}); + +export class AsyncLocalStorage { + #key; + + constructor() { + this.#key = new StorageKey(); + fnReg.register(this, this.#key); + } + + // deno-lint-ignore no-explicit-any + run(store: any, callback: any, ...args: any[]): any { + const frame = AsyncContextFrame.create( + null, + new StorageEntry(this.#key, store), + ); + Scope.enter(frame); + let res; + try { + res = callback(...args); + } finally { + Scope.exit(); + } + return res; + } + + // deno-lint-ignore no-explicit-any + exit(callback: (...args: unknown[]) => any, ...args: any[]): any { + return this.run(undefined, callback, args); + } + + // deno-lint-ignore no-explicit-any + getStore(): any { + const currentFrame = AsyncContextFrame.current(); + return currentFrame.get(this.#key); + } +} + +export function executionAsyncId() { + return 1; +} + +class AsyncHook { + enable() { + } + + disable() { + } +} + +export function createHook() { + return new AsyncHook(); +} + +// Placing all exports down here because the exported classes won't export +// otherwise. +export default { + // Embedder API + AsyncResource, + executionAsyncId, + createHook, + AsyncLocalStorage, +}; diff --git a/ext/node/polyfills/buffer.ts b/ext/node/polyfills/buffer.ts new file mode 100644 index 00000000000000..8a4426c97a6f6b --- /dev/null +++ b/ext/node/polyfills/buffer.ts @@ -0,0 +1,13 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. +// @deno-types="./internal/buffer.d.ts" +export { + atob, + Blob, + btoa, + Buffer, + constants, + default, + kMaxLength, + kStringMaxLength, + SlowBuffer, +} from "internal:deno_node/polyfills/internal/buffer.mjs"; diff --git a/ext/node/polyfills/child_process.ts b/ext/node/polyfills/child_process.ts new file mode 100644 index 00000000000000..cc0e17ffbdb706 --- /dev/null +++ b/ext/node/polyfills/child_process.ts @@ -0,0 +1,832 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. + +// This module implements 'child_process' module of Node.JS API. +// ref: https://nodejs.org/api/child_process.html +import { core } from "internal:deno_node/polyfills/_core.ts"; +import { + ChildProcess, + ChildProcessOptions, + normalizeSpawnArguments, + type SpawnOptions, + spawnSync as _spawnSync, + type SpawnSyncOptions, + type SpawnSyncResult, + stdioStringToArray, +} from "internal:deno_node/polyfills/internal/child_process.ts"; +import { + validateAbortSignal, + validateFunction, + validateObject, + validateString, +} from "internal:deno_node/polyfills/internal/validators.mjs"; +import { + ERR_CHILD_PROCESS_IPC_REQUIRED, + ERR_CHILD_PROCESS_STDIO_MAXBUFFER, + ERR_INVALID_ARG_TYPE, + ERR_INVALID_ARG_VALUE, + ERR_OUT_OF_RANGE, + genericNodeError, +} from "internal:deno_node/polyfills/internal/errors.ts"; +import { + ArrayIsArray, + ArrayPrototypeJoin, + ArrayPrototypePush, + ArrayPrototypeSlice, + ObjectAssign, + StringPrototypeSlice, +} from "internal:deno_node/polyfills/internal/primordials.mjs"; +import { + getSystemErrorName, + promisify, +} from "internal:deno_node/polyfills/util.ts"; +import { createDeferredPromise } from "internal:deno_node/polyfills/internal/util.mjs"; +import process from "internal:deno_node/polyfills/process.ts"; +import { Buffer } from "internal:deno_node/polyfills/buffer.ts"; +import { + convertToValidSignal, + kEmptyObject, +} from "internal:deno_node/polyfills/internal/util.mjs"; + +const MAX_BUFFER = 1024 * 1024; + +type ForkOptions = ChildProcessOptions; + +/** + * Spawns a new Node.js process + fork. + * @param modulePath + * @param args + * @param option + * @returns + */ +export function fork( + modulePath: string, + _args?: string[], + _options?: ForkOptions, +) { + validateString(modulePath, "modulePath"); + + // Get options and args arguments. + let execArgv; + let options: SpawnOptions & { + execArgv?: string; + execPath?: string; + silent?: boolean; + } = {}; + let args: string[] = []; + let pos = 1; + if (pos < arguments.length && Array.isArray(arguments[pos])) { + args = arguments[pos++]; + } + + if (pos < arguments.length && arguments[pos] == null) { + pos++; + } + + if (pos < arguments.length && arguments[pos] != null) { + if (typeof arguments[pos] !== "object") { + throw new ERR_INVALID_ARG_VALUE(`arguments[${pos}]`, arguments[pos]); + } + + options = { ...arguments[pos++] }; + } + + // Prepare arguments for fork: + execArgv = options.execArgv || process.execArgv; + + if (execArgv === process.execArgv && process._eval != null) { + const index = execArgv.lastIndexOf(process._eval); + if (index > 0) { + // Remove the -e switch to avoid fork bombing ourselves. + execArgv = execArgv.slice(0); + execArgv.splice(index - 1, 2); + } + } + + // TODO(bartlomieju): this is incomplete, currently only handling a single + // V8 flag to get Prisma integration running, we should fill this out with + // more + const v8Flags: string[] = []; + if (Array.isArray(execArgv)) { + for (let index = 0; index < execArgv.length; index++) { + const flag = execArgv[index]; + if (flag.startsWith("--max-old-space-size")) { + execArgv.splice(index, 1); + v8Flags.push(flag); + } + } + } + const stringifiedV8Flags: string[] = []; + if (v8Flags.length > 0) { + stringifiedV8Flags.push("--v8-flags=" + v8Flags.join(",")); + } + args = [ + "run", + "--unstable", // TODO(kt3k): Remove when npm: is stable + "--node-modules-dir", + "-A", + ...stringifiedV8Flags, + ...execArgv, + modulePath, + ...args, + ]; + + if (typeof options.stdio === "string") { + options.stdio = stdioStringToArray(options.stdio, "ipc"); + } else if (!Array.isArray(options.stdio)) { + // Use a separate fd=3 for the IPC channel. Inherit stdin, stdout, + // and stderr from the parent if silent isn't set. + options.stdio = stdioStringToArray( + options.silent ? "pipe" : "inherit", + "ipc", + ); + } else if (!options.stdio.includes("ipc")) { + throw new ERR_CHILD_PROCESS_IPC_REQUIRED("options.stdio"); + } + + options.execPath = options.execPath || Deno.execPath(); + options.shell = false; + + Object.assign(options.env ??= {}, { + DENO_DONT_USE_INTERNAL_NODE_COMPAT_STATE: core.ops + .op_npm_process_state(), + }); + + return spawn(options.execPath, args, options); +} + +export function spawn(command: string): ChildProcess; +export function spawn(command: string, options: SpawnOptions): ChildProcess; +export function spawn(command: string, args: string[]): ChildProcess; +export function spawn( + command: string, + args: string[], + options: SpawnOptions, +): ChildProcess; +/** + * Spawns a child process using `command`. + */ +export function spawn( + command: string, + argsOrOptions?: string[] | SpawnOptions, + maybeOptions?: SpawnOptions, +): ChildProcess { + const args = Array.isArray(argsOrOptions) ? argsOrOptions : []; + let options = !Array.isArray(argsOrOptions) && argsOrOptions != null + ? argsOrOptions + : maybeOptions as SpawnOptions; + + options = normalizeSpawnArguments(command, args, options); + + validateAbortSignal(options?.signal, "options.signal"); + return new ChildProcess(command, args, options); +} + +function validateTimeout(timeout?: number) { + if (timeout != null && !(Number.isInteger(timeout) && timeout >= 0)) { + throw new ERR_OUT_OF_RANGE("timeout", "an unsigned integer", timeout); + } +} + +function validateMaxBuffer(maxBuffer?: number) { + if ( + maxBuffer != null && + !(typeof maxBuffer === "number" && maxBuffer >= 0) + ) { + throw new ERR_OUT_OF_RANGE( + "options.maxBuffer", + "a positive number", + maxBuffer, + ); + } +} + +function sanitizeKillSignal(killSignal?: string | number) { + if (typeof killSignal === "string" || typeof killSignal === "number") { + return convertToValidSignal(killSignal); + } else if (killSignal != null) { + throw new ERR_INVALID_ARG_TYPE( + "options.killSignal", + ["string", "number"], + killSignal, + ); + } +} + +export function spawnSync( + command: string, + argsOrOptions?: string[] | SpawnSyncOptions, + maybeOptions?: SpawnSyncOptions, +): SpawnSyncResult { + const args = Array.isArray(argsOrOptions) ? argsOrOptions : []; + let options = !Array.isArray(argsOrOptions) && argsOrOptions + ? argsOrOptions + : maybeOptions as SpawnSyncOptions; + + options = { + maxBuffer: MAX_BUFFER, + ...normalizeSpawnArguments(command, args, options), + }; + + // Validate the timeout, if present. + validateTimeout(options.timeout); + + // Validate maxBuffer, if present. + validateMaxBuffer(options.maxBuffer); + + // Validate and translate the kill signal, if present. + sanitizeKillSignal(options.killSignal); + + return _spawnSync(command, args, options); +} + +interface ExecOptions extends + Pick< + ChildProcessOptions, + | "env" + | "signal" + | "uid" + | "gid" + | "windowsHide" + > { + cwd?: string | URL; + encoding?: string; + /** + * Shell to execute the command with. + */ + shell?: string; + timeout?: number; + /** + * Largest amount of data in bytes allowed on stdout or stderr. If exceeded, the child process is terminated and any output is truncated. + */ + maxBuffer?: number; + killSignal?: string | number; +} +type ExecException = ChildProcessError; +type ExecCallback = ( + error: ExecException | null, + stdout?: string | Buffer, + stderr?: string | Buffer, +) => void; +type ExecSyncOptions = SpawnSyncOptions; +type ExecFileSyncOptions = SpawnSyncOptions; +function normalizeExecArgs( + command: string, + optionsOrCallback?: ExecOptions | ExecSyncOptions | ExecCallback, + maybeCallback?: ExecCallback, +) { + let callback: ExecFileCallback | undefined = maybeCallback; + + if (typeof optionsOrCallback === "function") { + callback = optionsOrCallback; + optionsOrCallback = undefined; + } + + // Make a shallow copy so we don't clobber the user's options object. + const options: ExecOptions | ExecSyncOptions = { ...optionsOrCallback }; + options.shell = typeof options.shell === "string" ? options.shell : true; + + return { + file: command, + options: options!, + callback: callback!, + }; +} + +/** + * Spawns a shell executing the given command. + */ +export function exec(command: string): ChildProcess; +export function exec(command: string, options: ExecOptions): ChildProcess; +export function exec(command: string, callback: ExecCallback): ChildProcess; +export function exec( + command: string, + options: ExecOptions, + callback: ExecCallback, +): ChildProcess; +export function exec( + command: string, + optionsOrCallback?: ExecOptions | ExecCallback, + maybeCallback?: ExecCallback, +): ChildProcess { + const opts = normalizeExecArgs(command, optionsOrCallback, maybeCallback); + return execFile(opts.file, opts.options as ExecFileOptions, opts.callback); +} + +interface PromiseWithChild extends Promise { + child: ChildProcess; +} +type ExecOutputForPromisify = { + stdout?: string | Buffer; + stderr?: string | Buffer; +}; +type ExecExceptionForPromisify = ExecException & ExecOutputForPromisify; + +const customPromiseExecFunction = (orig: typeof exec) => { + return (...args: [command: string, options: ExecOptions]) => { + const { promise, resolve, reject } = createDeferredPromise() as unknown as { + promise: PromiseWithChild; + resolve?: (value: ExecOutputForPromisify) => void; + reject?: (reason?: ExecExceptionForPromisify) => void; + }; + + promise.child = orig(...args, (err, stdout, stderr) => { + if (err !== null) { + const _err: ExecExceptionForPromisify = err; + _err.stdout = stdout; + _err.stderr = stderr; + reject && reject(_err); + } else { + resolve && resolve({ stdout, stderr }); + } + }); + + return promise; + }; +}; + +Object.defineProperty(exec, promisify.custom, { + enumerable: false, + value: customPromiseExecFunction(exec), +}); + +interface ExecFileOptions extends ChildProcessOptions { + encoding?: string; + timeout?: number; + maxBuffer?: number; + killSignal?: string | number; +} +interface ChildProcessError extends Error { + code?: string | number; + killed?: boolean; + signal?: AbortSignal; + cmd?: string; +} +class ExecFileError extends Error implements ChildProcessError { + code?: string | number; + + constructor(message: string) { + super(message); + this.code = "UNKNOWN"; + } +} +type ExecFileCallback = ( + error: ChildProcessError | null, + stdout?: string | Buffer, + stderr?: string | Buffer, +) => void; +export function execFile(file: string): ChildProcess; +export function execFile( + file: string, + callback: ExecFileCallback, +): ChildProcess; +export function execFile(file: string, args: string[]): ChildProcess; +export function execFile( + file: string, + args: string[], + callback: ExecFileCallback, +): ChildProcess; +export function execFile(file: string, options: ExecFileOptions): ChildProcess; +export function execFile( + file: string, + options: ExecFileOptions, + callback: ExecFileCallback, +): ChildProcess; +export function execFile( + file: string, + args: string[], + options: ExecFileOptions, + callback: ExecFileCallback, +): ChildProcess; +export function execFile( + file: string, + argsOrOptionsOrCallback?: string[] | ExecFileOptions | ExecFileCallback, + optionsOrCallback?: ExecFileOptions | ExecFileCallback, + maybeCallback?: ExecFileCallback, +): ChildProcess { + let args: string[] = []; + let options: ExecFileOptions = {}; + let callback: ExecFileCallback | undefined; + + if (Array.isArray(argsOrOptionsOrCallback)) { + args = argsOrOptionsOrCallback; + } else if (argsOrOptionsOrCallback instanceof Function) { + callback = argsOrOptionsOrCallback; + } else if (argsOrOptionsOrCallback) { + options = argsOrOptionsOrCallback; + } + if (optionsOrCallback instanceof Function) { + callback = optionsOrCallback; + } else if (optionsOrCallback) { + options = optionsOrCallback; + callback = maybeCallback; + } + + const execOptions = { + encoding: "utf8", + timeout: 0, + maxBuffer: MAX_BUFFER, + killSignal: "SIGTERM", + shell: false, + ...options, + }; + if (!Number.isInteger(execOptions.timeout) || execOptions.timeout < 0) { + // In Node source, the first argument to error constructor is "timeout" instead of "options.timeout". + // timeout is indeed a member of options object. + throw new ERR_OUT_OF_RANGE( + "timeout", + "an unsigned integer", + execOptions.timeout, + ); + } + if (execOptions.maxBuffer < 0) { + throw new ERR_OUT_OF_RANGE( + "options.maxBuffer", + "a positive number", + execOptions.maxBuffer, + ); + } + const spawnOptions: SpawnOptions = { + cwd: execOptions.cwd, + env: execOptions.env, + gid: execOptions.gid, + shell: execOptions.shell, + signal: execOptions.signal, + uid: execOptions.uid, + windowsHide: !!execOptions.windowsHide, + windowsVerbatimArguments: !!execOptions.windowsVerbatimArguments, + }; + + const child = spawn(file, args, spawnOptions); + + let encoding: string | null; + const _stdout: (string | Uint8Array)[] = []; + const _stderr: (string | Uint8Array)[] = []; + if ( + execOptions.encoding !== "buffer" && Buffer.isEncoding(execOptions.encoding) + ) { + encoding = execOptions.encoding; + } else { + encoding = null; + } + let stdoutLen = 0; + let stderrLen = 0; + let killed = false; + let exited = false; + let timeoutId: number | null; + + let ex: ChildProcessError | null = null; + + let cmd = file; + + function exithandler(code = 0, signal?: AbortSignal) { + if (exited) return; + exited = true; + + if (timeoutId) { + clearTimeout(timeoutId); + timeoutId = null; + } + + if (!callback) return; + + // merge chunks + let stdout; + let stderr; + if ( + encoding || + ( + child.stdout && + child.stdout.readableEncoding + ) + ) { + stdout = _stdout.join(""); + } else { + stdout = Buffer.concat(_stdout as Buffer[]); + } + if ( + encoding || + ( + child.stderr && + child.stderr.readableEncoding + ) + ) { + stderr = _stderr.join(""); + } else { + stderr = Buffer.concat(_stderr as Buffer[]); + } + + if (!ex && code === 0 && signal === null) { + callback(null, stdout, stderr); + return; + } + + if (args?.length) { + cmd += ` ${args.join(" ")}`; + } + + if (!ex) { + ex = new ExecFileError( + "Command failed: " + cmd + "\n" + stderr, + ); + ex.code = code < 0 ? getSystemErrorName(code) : code; + ex.killed = child.killed || killed; + ex.signal = signal; + } + + ex.cmd = cmd; + callback(ex, stdout, stderr); + } + + function errorhandler(e: ExecFileError) { + ex = e; + + if (child.stdout) { + child.stdout.destroy(); + } + + if (child.stderr) { + child.stderr.destroy(); + } + + exithandler(); + } + + function kill() { + if (child.stdout) { + child.stdout.destroy(); + } + + if (child.stderr) { + child.stderr.destroy(); + } + + killed = true; + try { + child.kill(execOptions.killSignal); + } catch (e) { + if (e) { + ex = e as ChildProcessError; + } + exithandler(); + } + } + + if (execOptions.timeout > 0) { + timeoutId = setTimeout(function delayedKill() { + kill(); + timeoutId = null; + }, execOptions.timeout); + } + + if (child.stdout) { + if (encoding) { + child.stdout.setEncoding(encoding); + } + + child.stdout.on("data", function onChildStdout(chunk: string | Buffer) { + // Do not need to count the length + if (execOptions.maxBuffer === Infinity) { + ArrayPrototypePush(_stdout, chunk); + return; + } + + const encoding = child.stdout?.readableEncoding; + const length = encoding + ? Buffer.byteLength(chunk, encoding) + : chunk.length; + const slice = encoding + ? StringPrototypeSlice + : (buf: string | Buffer, ...args: number[]) => buf.slice(...args); + stdoutLen += length; + + if (stdoutLen > execOptions.maxBuffer) { + const truncatedLen = execOptions.maxBuffer - (stdoutLen - length); + ArrayPrototypePush(_stdout, slice(chunk, 0, truncatedLen)); + + ex = new ERR_CHILD_PROCESS_STDIO_MAXBUFFER("stdout"); + kill(); + } else { + ArrayPrototypePush(_stdout, chunk); + } + }); + } + + if (child.stderr) { + if (encoding) { + child.stderr.setEncoding(encoding); + } + + child.stderr.on("data", function onChildStderr(chunk: string | Buffer) { + // Do not need to count the length + if (execOptions.maxBuffer === Infinity) { + ArrayPrototypePush(_stderr, chunk); + return; + } + + const encoding = child.stderr?.readableEncoding; + const length = encoding + ? Buffer.byteLength(chunk, encoding) + : chunk.length; + const slice = encoding + ? StringPrototypeSlice + : (buf: string | Buffer, ...args: number[]) => buf.slice(...args); + stderrLen += length; + + if (stderrLen > execOptions.maxBuffer) { + const truncatedLen = execOptions.maxBuffer - (stderrLen - length); + ArrayPrototypePush(_stderr, slice(chunk, 0, truncatedLen)); + + ex = new ERR_CHILD_PROCESS_STDIO_MAXBUFFER("stderr"); + kill(); + } else { + ArrayPrototypePush(_stderr, chunk); + } + }); + } + + child.addListener("close", exithandler); + child.addListener("error", errorhandler); + + return child; +} + +type ExecFileExceptionForPromisify = ExecFileError & ExecOutputForPromisify; + +const customPromiseExecFileFunction = ( + orig: ( + file: string, + argsOrOptionsOrCallback?: string[] | ExecFileOptions | ExecFileCallback, + optionsOrCallback?: ExecFileOptions | ExecFileCallback, + maybeCallback?: ExecFileCallback, + ) => ChildProcess, +) => { + return ( + ...args: [ + file: string, + argsOrOptions?: string[] | ExecFileOptions, + options?: ExecFileOptions, + ] + ) => { + const { promise, resolve, reject } = createDeferredPromise() as unknown as { + promise: PromiseWithChild; + resolve?: (value: ExecOutputForPromisify) => void; + reject?: (reason?: ExecFileExceptionForPromisify) => void; + }; + + promise.child = orig(...args, (err, stdout, stderr) => { + if (err !== null) { + const _err: ExecFileExceptionForPromisify = err; + _err.stdout = stdout; + _err.stderr = stderr; + reject && reject(_err); + } else { + resolve && resolve({ stdout, stderr }); + } + }); + + return promise; + }; +}; + +Object.defineProperty(execFile, promisify.custom, { + enumerable: false, + value: customPromiseExecFileFunction(execFile), +}); + +function checkExecSyncError( + ret: SpawnSyncResult, + args: string[], + cmd?: string, +) { + let err; + if (ret.error) { + err = ret.error; + ObjectAssign(err, ret); + } else if (ret.status !== 0) { + let msg = "Command failed: "; + msg += cmd || ArrayPrototypeJoin(args, " "); + if (ret.stderr && ret.stderr.length > 0) { + msg += `\n${ret.stderr.toString()}`; + } + err = genericNodeError(msg, ret); + } + return err; +} + +export function execSync(command: string, options: ExecSyncOptions) { + const opts = normalizeExecArgs(command, options); + const inheritStderr = !(opts.options as ExecSyncOptions).stdio; + + const ret = spawnSync(opts.file, opts.options as SpawnSyncOptions); + + if (inheritStderr && ret.stderr) { + process.stderr.write(ret.stderr); + } + + const err = checkExecSyncError(ret, [], command); + + if (err) { + throw err; + } + + return ret.stdout; +} + +function normalizeExecFileArgs( + file: string, + args?: string[] | null | ExecFileSyncOptions | ExecFileCallback, + options?: ExecFileSyncOptions | null | ExecFileCallback, + callback?: ExecFileCallback, +): { + file: string; + args: string[]; + options: ExecFileSyncOptions; + callback?: ExecFileCallback; +} { + if (ArrayIsArray(args)) { + args = ArrayPrototypeSlice(args); + } else if (args != null && typeof args === "object") { + callback = options as ExecFileCallback; + options = args as ExecFileSyncOptions; + args = null; + } else if (typeof args === "function") { + callback = args; + options = null; + args = null; + } + + if (args == null) { + args = []; + } + + if (typeof options === "function") { + callback = options as ExecFileCallback; + } else if (options != null) { + validateObject(options, "options"); + } + + if (options == null) { + options = kEmptyObject; + } + + args = args as string[]; + options = options as ExecFileSyncOptions; + + if (callback != null) { + validateFunction(callback, "callback"); + } + + // Validate argv0, if present. + if (options.argv0 != null) { + validateString(options.argv0, "options.argv0"); + } + + return { file, args, options, callback }; +} + +export function execFileSync(file: string): string | Buffer; +export function execFileSync(file: string, args: string[]): string | Buffer; +export function execFileSync( + file: string, + options: ExecFileSyncOptions, +): string | Buffer; +export function execFileSync( + file: string, + args: string[], + options: ExecFileSyncOptions, +): string | Buffer; +export function execFileSync( + file: string, + args?: string[] | ExecFileSyncOptions, + options?: ExecFileSyncOptions, +): string | Buffer { + ({ file, args, options } = normalizeExecFileArgs(file, args, options)); + + const inheritStderr = !options.stdio; + const ret = spawnSync(file, args, options); + + if (inheritStderr && ret.stderr) { + process.stderr.write(ret.stderr); + } + + const errArgs: string[] = [options.argv0 || file, ...(args as string[])]; + const err = checkExecSyncError(ret, errArgs); + + if (err) { + throw err; + } + + return ret.stdout as string | Buffer; +} + +export default { + fork, + spawn, + exec, + execFile, + execFileSync, + execSync, + ChildProcess, + spawnSync, +}; +export { ChildProcess }; diff --git a/ext/node/polyfills/cluster.ts b/ext/node/polyfills/cluster.ts new file mode 100644 index 00000000000000..70d70384fa67a7 --- /dev/null +++ b/ext/node/polyfills/cluster.ts @@ -0,0 +1,69 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. +// Copyright Joyent and Node contributors. All rights reserved. MIT license. + +import { notImplemented } from "internal:deno_node/polyfills/_utils.ts"; + +/** A Worker object contains all public information and method about a worker. + * In the primary it can be obtained using cluster.workers. In a worker it can + * be obtained using cluster.worker. + */ +export class Worker { + constructor() { + notImplemented("cluster.Worker.prototype.constructor"); + } +} +/** Calls .disconnect() on each worker in cluster.workers. */ +export function disconnected() { + notImplemented("cluster.disconnected"); +} +/** Spawn a new worker process. */ +export function fork() { + notImplemented("cluster.fork"); +} +/** True if the process is a primary. This is determined by + * the process.env.NODE_UNIQUE_ID. If process.env.NODE_UNIQUE_ID is undefined, + * then isPrimary is true. */ +export const isPrimary = undefined; +/** True if the process is not a primary (it is the negation of + * cluster.isPrimary). */ +export const isWorker = undefined; +/** Deprecated alias for cluster.isPrimary. details. */ +export const isMaster = isPrimary; +/** The scheduling policy, either cluster.SCHED_RR for round-robin or + * cluster.SCHED_NONE to leave it to the operating system. This is a global + * setting and effectively frozen once either the first worker is spawned, or + * .setupPrimary() is called, whichever comes first. */ +export const schedulingPolicy = undefined; +/** The settings object */ +export const settings = undefined; +/** Deprecated alias for .setupPrimary(). */ +export function setupMaster() { + notImplemented("cluster.setupMaster"); +} +/** setupPrimary is used to change the default 'fork' behavior. Once called, + * the settings will be present in cluster.settings. */ +export function setupPrimary() { + notImplemented("cluster.setupPrimary"); +} +/** A reference to the current worker object. Not available in the primary + * process. */ +export const worker = undefined; +/** A hash that stores the active worker objects, keyed by id field. Makes it + * easy to loop through all the workers. It is only available in the primary + * process. */ +export const workers = undefined; + +export default { + Worker, + disconnected, + fork, + isPrimary, + isWorker, + isMaster, + schedulingPolicy, + settings, + setupMaster, + setupPrimary, + worker, + workers, +}; diff --git a/ext/node/polyfills/console.ts b/ext/node/polyfills/console.ts new file mode 100644 index 00000000000000..f811f1a86cc955 --- /dev/null +++ b/ext/node/polyfills/console.ts @@ -0,0 +1,37 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. + +import { Console } from "internal:deno_node/polyfills/internal/console/constructor.mjs"; +import { windowOrWorkerGlobalScope } from "internal:runtime/js/98_global_scope.js"; +// Don't rely on global `console` because during bootstrapping, it is pointing +// to native `console` object provided by V8. +const console = windowOrWorkerGlobalScope.console.value; + +export default Object.assign({}, console, { Console }); + +export { Console }; +export const { + assert, + clear, + count, + countReset, + debug, + dir, + dirxml, + error, + group, + groupCollapsed, + groupEnd, + info, + log, + profile, + profileEnd, + table, + time, + timeEnd, + timeLog, + timeStamp, + trace, + warn, +} = console; +// deno-lint-ignore no-explicit-any +export const indentLevel = (console as any)?.indentLevel; diff --git a/ext/node/polyfills/constants.ts b/ext/node/polyfills/constants.ts new file mode 100644 index 00000000000000..f9342240608bcf --- /dev/null +++ b/ext/node/polyfills/constants.ts @@ -0,0 +1,182 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. + +// Based on: https://github.com/nodejs/node/blob/0646eda/lib/constants.js + +import { constants as fsConstants } from "internal:deno_node/polyfills/fs.ts"; +import { constants as osConstants } from "internal:deno_node/polyfills/os.ts"; + +export default { + ...fsConstants, + ...osConstants.dlopen, + ...osConstants.errno, + ...osConstants.signals, + ...osConstants.priority, +}; + +export const { + F_OK, + R_OK, + W_OK, + X_OK, + O_RDONLY, + O_WRONLY, + O_RDWR, + O_NOCTTY, + O_TRUNC, + O_APPEND, + O_DIRECTORY, + O_NOFOLLOW, + O_SYNC, + O_DSYNC, + O_SYMLINK, + O_NONBLOCK, + O_CREAT, + O_EXCL, + S_IRUSR, + S_IWUSR, + S_IXUSR, + S_IRGRP, + S_IWGRP, + S_IXGRP, + S_IROTH, + S_IWOTH, + S_IXOTH, + COPYFILE_EXCL, + COPYFILE_FICLONE, + COPYFILE_FICLONE_FORCE, + UV_FS_COPYFILE_EXCL, + UV_FS_COPYFILE_FICLONE, + UV_FS_COPYFILE_FICLONE_FORCE, +} = fsConstants; +export const { + RTLD_DEEPBIND, + RTLD_GLOBAL, + RTLD_LAZY, + RTLD_LOCAL, + RTLD_NOW, +} = osConstants.dlopen; +export const { + E2BIG, + EACCES, + EADDRINUSE, + EADDRNOTAVAIL, + EAFNOSUPPORT, + EAGAIN, + EALREADY, + EBADF, + EBADMSG, + EBUSY, + ECANCELED, + ECHILD, + ECONNABORTED, + ECONNREFUSED, + ECONNRESET, + EDEADLK, + EDESTADDRREQ, + EDOM, + EDQUOT, + EEXIST, + EFAULT, + EFBIG, + EHOSTUNREACH, + EIDRM, + EILSEQ, + EINPROGRESS, + EINTR, + EINVAL, + EIO, + EISCONN, + EISDIR, + ELOOP, + EMFILE, + EMLINK, + EMSGSIZE, + EMULTIHOP, + ENAMETOOLONG, + ENETDOWN, + ENETRESET, + ENETUNREACH, + ENFILE, + ENOBUFS, + ENODATA, + ENODEV, + ENOENT, + ENOEXEC, + ENOLCK, + ENOLINK, + ENOMEM, + ENOMSG, + ENOPROTOOPT, + ENOSPC, + ENOSR, + ENOSTR, + ENOSYS, + ENOTCONN, + ENOTDIR, + ENOTEMPTY, + ENOTSOCK, + ENOTSUP, + ENOTTY, + ENXIO, + EOPNOTSUPP, + EOVERFLOW, + EPERM, + EPIPE, + EPROTO, + EPROTONOSUPPORT, + EPROTOTYPE, + ERANGE, + EROFS, + ESPIPE, + ESRCH, + ESTALE, + ETIME, + ETIMEDOUT, + ETXTBSY, + EWOULDBLOCK, + EXDEV, +} = osConstants.errno; +export const { + PRIORITY_ABOVE_NORMAL, + PRIORITY_BELOW_NORMAL, + PRIORITY_HIGH, + PRIORITY_HIGHEST, + PRIORITY_LOW, + PRIORITY_NORMAL, +} = osConstants.priority; +export const { + SIGABRT, + SIGALRM, + SIGBUS, + SIGCHLD, + SIGCONT, + SIGFPE, + SIGHUP, + SIGILL, + SIGINT, + SIGIO, + SIGIOT, + SIGKILL, + SIGPIPE, + SIGPOLL, + SIGPROF, + SIGPWR, + SIGQUIT, + SIGSEGV, + SIGSTKFLT, + SIGSTOP, + SIGSYS, + SIGTERM, + SIGTRAP, + SIGTSTP, + SIGTTIN, + SIGTTOU, + SIGUNUSED, + SIGURG, + SIGUSR1, + SIGUSR2, + SIGVTALRM, + SIGWINCH, + SIGXCPU, + SIGXFSZ, +} = osConstants.signals; diff --git a/ext/node/polyfills/crypto.ts b/ext/node/polyfills/crypto.ts new file mode 100644 index 00000000000000..8c179e916c73bb --- /dev/null +++ b/ext/node/polyfills/crypto.ts @@ -0,0 +1,513 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. +// Copyright Joyent, Inc. and Node.js contributors. All rights reserved. MIT license. + +import { ERR_CRYPTO_FIPS_FORCED } from "internal:deno_node/polyfills/internal/errors.ts"; +import { crypto as constants } from "internal:deno_node/polyfills/internal_binding/constants.ts"; +import { getOptionValue } from "internal:deno_node/polyfills/internal/options.ts"; +import { + getFipsCrypto, + setFipsCrypto, + timingSafeEqual, +} from "internal:deno_node/polyfills/internal_binding/crypto.ts"; +import { + checkPrime, + checkPrimeSync, + generatePrime, + generatePrimeSync, + randomBytes, + randomFill, + randomFillSync, + randomInt, + randomUUID, +} from "internal:deno_node/polyfills/internal/crypto/random.ts"; +import type { + CheckPrimeOptions, + GeneratePrimeOptions, + GeneratePrimeOptionsArrayBuffer, + GeneratePrimeOptionsBigInt, + LargeNumberLike, +} from "internal:deno_node/polyfills/internal/crypto/random.ts"; +import { + pbkdf2, + pbkdf2Sync, +} from "internal:deno_node/polyfills/internal/crypto/pbkdf2.ts"; +import type { + Algorithms, + NormalizedAlgorithms, +} from "internal:deno_node/polyfills/internal/crypto/pbkdf2.ts"; +import { + scrypt, + scryptSync, +} from "internal:deno_node/polyfills/internal/crypto/scrypt.ts"; +import { + hkdf, + hkdfSync, +} from "internal:deno_node/polyfills/internal/crypto/hkdf.ts"; +import { + generateKey, + generateKeyPair, + generateKeyPairSync, + generateKeySync, +} from "internal:deno_node/polyfills/internal/crypto/keygen.ts"; +import type { + BasePrivateKeyEncodingOptions, + DSAKeyPairKeyObjectOptions, + DSAKeyPairOptions, + ECKeyPairKeyObjectOptions, + ECKeyPairOptions, + ED25519KeyPairKeyObjectOptions, + ED25519KeyPairOptions, + ED448KeyPairKeyObjectOptions, + ED448KeyPairOptions, + KeyPairKeyObjectResult, + KeyPairSyncResult, + RSAKeyPairKeyObjectOptions, + RSAKeyPairOptions, + RSAPSSKeyPairKeyObjectOptions, + RSAPSSKeyPairOptions, + X25519KeyPairKeyObjectOptions, + X25519KeyPairOptions, + X448KeyPairKeyObjectOptions, + X448KeyPairOptions, +} from "internal:deno_node/polyfills/internal/crypto/keygen.ts"; +import { + createPrivateKey, + createPublicKey, + createSecretKey, + KeyObject, +} from "internal:deno_node/polyfills/internal/crypto/keys.ts"; +import type { + AsymmetricKeyDetails, + JsonWebKeyInput, + JwkKeyExportOptions, + KeyExportOptions, + KeyObjectType, +} from "internal:deno_node/polyfills/internal/crypto/keys.ts"; +import { + DiffieHellman, + diffieHellman, + DiffieHellmanGroup, + ECDH, +} from "internal:deno_node/polyfills/internal/crypto/diffiehellman.ts"; +import { + Cipheriv, + Decipheriv, + getCipherInfo, + privateDecrypt, + privateEncrypt, + publicDecrypt, + publicEncrypt, +} from "internal:deno_node/polyfills/internal/crypto/cipher.ts"; +import type { + Cipher, + CipherCCM, + CipherCCMOptions, + CipherCCMTypes, + CipherGCM, + CipherGCMOptions, + CipherGCMTypes, + CipherKey, + CipherOCB, + CipherOCBOptions, + CipherOCBTypes, + Decipher, + DecipherCCM, + DecipherGCM, + DecipherOCB, +} from "internal:deno_node/polyfills/internal/crypto/cipher.ts"; +import type { + BinaryLike, + BinaryToTextEncoding, + CharacterEncoding, + ECDHKeyFormat, + Encoding, + HASH_DATA, + KeyFormat, + KeyType, + LegacyCharacterEncoding, + PrivateKeyInput, + PublicKeyInput, +} from "internal:deno_node/polyfills/internal/crypto/types.ts"; +import { + Sign, + signOneShot, + Verify, + verifyOneShot, +} from "internal:deno_node/polyfills/internal/crypto/sig.ts"; +import type { + DSAEncoding, + KeyLike, + SigningOptions, + SignKeyObjectInput, + SignPrivateKeyInput, + VerifyKeyObjectInput, + VerifyPublicKeyInput, +} from "internal:deno_node/polyfills/internal/crypto/sig.ts"; +import { + createHash, + Hash, + Hmac, +} from "internal:deno_node/polyfills/internal/crypto/hash.ts"; +import { X509Certificate } from "internal:deno_node/polyfills/internal/crypto/x509.ts"; +import type { + PeerCertificate, + X509CheckOptions, +} from "internal:deno_node/polyfills/internal/crypto/x509.ts"; +import { + getCiphers, + getCurves, + getHashes, + secureHeapUsed, + setEngine, +} from "internal:deno_node/polyfills/internal/crypto/util.ts"; +import type { SecureHeapUsage } from "internal:deno_node/polyfills/internal/crypto/util.ts"; +import Certificate from "internal:deno_node/polyfills/internal/crypto/certificate.ts"; +import type { + TransformOptions, + WritableOptions, +} from "internal:deno_node/polyfills/_stream.d.ts"; +import { crypto as webcrypto } from "internal:deno_crypto/00_crypto.js"; + +const fipsForced = getOptionValue("--force-fips"); + +function createCipheriv( + algorithm: CipherCCMTypes, + key: CipherKey, + iv: BinaryLike, + options: CipherCCMOptions, +): CipherCCM; +function createCipheriv( + algorithm: CipherOCBTypes, + key: CipherKey, + iv: BinaryLike, + options: CipherOCBOptions, +): CipherOCB; +function createCipheriv( + algorithm: CipherGCMTypes, + key: CipherKey, + iv: BinaryLike, + options?: CipherGCMOptions, +): CipherGCM; +function createCipheriv( + algorithm: string, + key: CipherKey, + iv: BinaryLike | null, + options?: TransformOptions, +): Cipher; +function createCipheriv( + cipher: string, + key: CipherKey, + iv: BinaryLike | null, + options?: TransformOptions, +): Cipher { + return new Cipheriv(cipher, key, iv, options); +} + +function createDecipheriv( + algorithm: CipherCCMTypes, + key: CipherKey, + iv: BinaryLike, + options: CipherCCMOptions, +): DecipherCCM; +function createDecipheriv( + algorithm: CipherOCBTypes, + key: CipherKey, + iv: BinaryLike, + options: CipherOCBOptions, +): DecipherOCB; +function createDecipheriv( + algorithm: CipherGCMTypes, + key: CipherKey, + iv: BinaryLike, + options?: CipherGCMOptions, +): DecipherGCM; +function createDecipheriv( + algorithm: string, + key: CipherKey, + iv: BinaryLike | null, + options?: TransformOptions, +): Decipher { + return new Decipheriv(algorithm, key, iv, options); +} + +function createDiffieHellman( + primeLength: number, + generator?: number | ArrayBufferView, +): DiffieHellman; +function createDiffieHellman(prime: ArrayBufferView): DiffieHellman; +function createDiffieHellman( + prime: string, + primeEncoding: BinaryToTextEncoding, +): DiffieHellman; +function createDiffieHellman( + prime: string, + primeEncoding: BinaryToTextEncoding, + generator: number | ArrayBufferView, +): DiffieHellman; +function createDiffieHellman( + prime: string, + primeEncoding: BinaryToTextEncoding, + generator: string, + generatorEncoding: BinaryToTextEncoding, +): DiffieHellman; +function createDiffieHellman( + sizeOrKey: number | string | ArrayBufferView, + keyEncoding?: number | ArrayBufferView | BinaryToTextEncoding, + generator?: number | ArrayBufferView | string, + generatorEncoding?: BinaryToTextEncoding, +): DiffieHellman { + return new DiffieHellman( + sizeOrKey, + keyEncoding, + generator, + generatorEncoding, + ); +} + +function createDiffieHellmanGroup(name: string): DiffieHellmanGroup { + return new DiffieHellmanGroup(name); +} + +function createECDH(curve: string): ECDH { + return new ECDH(curve); +} + +function createHmac( + hmac: string, + key: string | ArrayBuffer | KeyObject, + options?: TransformOptions, +) { + return Hmac(hmac, key, options); +} + +function createSign(algorithm: string, options?: WritableOptions): Sign { + return new Sign(algorithm, options); +} + +function createVerify(algorithm: string, options?: WritableOptions): Verify { + return new Verify(algorithm, options); +} + +function setFipsForced(val: boolean) { + if (val) { + return; + } + + throw new ERR_CRYPTO_FIPS_FORCED(); +} + +function getFipsForced() { + return 1; +} + +Object.defineProperty(constants, "defaultCipherList", { + value: getOptionValue("--tls-cipher-list"), +}); + +const getDiffieHellman = createDiffieHellmanGroup; + +const getFips = fipsForced ? getFipsForced : getFipsCrypto; +const setFips = fipsForced ? setFipsForced : setFipsCrypto; + +const sign = signOneShot; +const verify = verifyOneShot; + +export default { + Certificate, + checkPrime, + checkPrimeSync, + Cipheriv, + constants, + createCipheriv, + createDecipheriv, + createDiffieHellman, + createDiffieHellmanGroup, + createECDH, + createHash, + createHmac, + createPrivateKey, + createPublicKey, + createSecretKey, + createSign, + createVerify, + Decipheriv, + DiffieHellman, + diffieHellman, + DiffieHellmanGroup, + ECDH, + generateKey, + generateKeyPair, + generateKeyPairSync, + generateKeySync, + generatePrime, + generatePrimeSync, + getCipherInfo, + getCiphers, + getCurves, + getDiffieHellman, + getFips, + getHashes, + Hash, + hkdf, + hkdfSync, + Hmac, + KeyObject, + pbkdf2, + pbkdf2Sync, + privateDecrypt, + privateEncrypt, + publicDecrypt, + publicEncrypt, + randomBytes, + randomFill, + randomFillSync, + randomInt, + randomUUID, + scrypt, + scryptSync, + secureHeapUsed, + setEngine, + setFips, + Sign, + sign, + timingSafeEqual, + Verify, + verify, + webcrypto, + X509Certificate, +}; + +export type { + Algorithms, + AsymmetricKeyDetails, + BasePrivateKeyEncodingOptions, + BinaryLike, + BinaryToTextEncoding, + CharacterEncoding, + CheckPrimeOptions, + Cipher, + CipherCCM, + CipherCCMOptions, + CipherCCMTypes, + CipherGCM, + CipherGCMOptions, + CipherGCMTypes, + CipherKey, + CipherOCB, + CipherOCBOptions, + CipherOCBTypes, + Decipher, + DecipherCCM, + DecipherGCM, + DecipherOCB, + DSAEncoding, + DSAKeyPairKeyObjectOptions, + DSAKeyPairOptions, + ECDHKeyFormat, + ECKeyPairKeyObjectOptions, + ECKeyPairOptions, + ED25519KeyPairKeyObjectOptions, + ED25519KeyPairOptions, + ED448KeyPairKeyObjectOptions, + ED448KeyPairOptions, + Encoding, + GeneratePrimeOptions, + GeneratePrimeOptionsArrayBuffer, + GeneratePrimeOptionsBigInt, + HASH_DATA, + JsonWebKeyInput, + JwkKeyExportOptions, + KeyExportOptions, + KeyFormat, + KeyLike, + KeyObjectType, + KeyPairKeyObjectResult, + KeyPairSyncResult, + KeyType, + LargeNumberLike, + LegacyCharacterEncoding, + NormalizedAlgorithms, + PeerCertificate, + PrivateKeyInput, + PublicKeyInput, + RSAKeyPairKeyObjectOptions, + RSAKeyPairOptions, + RSAPSSKeyPairKeyObjectOptions, + RSAPSSKeyPairOptions, + SecureHeapUsage, + SigningOptions, + SignKeyObjectInput, + SignPrivateKeyInput, + VerifyKeyObjectInput, + VerifyPublicKeyInput, + X25519KeyPairKeyObjectOptions, + X25519KeyPairOptions, + X448KeyPairKeyObjectOptions, + X448KeyPairOptions, + X509CheckOptions, +}; + +export { + Certificate, + checkPrime, + checkPrimeSync, + Cipheriv, + constants, + createCipheriv, + createDecipheriv, + createDiffieHellman, + createDiffieHellmanGroup, + createECDH, + createHash, + createHmac, + createPrivateKey, + createPublicKey, + createSecretKey, + createSign, + createVerify, + Decipheriv, + DiffieHellman, + diffieHellman, + DiffieHellmanGroup, + ECDH, + generateKey, + generateKeyPair, + generateKeyPairSync, + generateKeySync, + generatePrime, + generatePrimeSync, + getCipherInfo, + getCiphers, + getCurves, + getDiffieHellman, + getFips, + getHashes, + Hash, + hkdf, + hkdfSync, + Hmac, + KeyObject, + pbkdf2, + pbkdf2Sync, + privateDecrypt, + privateEncrypt, + publicDecrypt, + publicEncrypt, + randomBytes, + randomFill, + randomFillSync, + randomInt, + randomUUID, + scrypt, + scryptSync, + secureHeapUsed, + setEngine, + setFips, + Sign, + sign, + timingSafeEqual, + Verify, + verify, + webcrypto, + X509Certificate, +}; diff --git a/ext/node/polyfills/dgram.ts b/ext/node/polyfills/dgram.ts new file mode 100644 index 00000000000000..222421eb0024e6 --- /dev/null +++ b/ext/node/polyfills/dgram.ts @@ -0,0 +1,1558 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. +// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. + +import { Buffer } from "internal:deno_node/polyfills/buffer.ts"; +import { EventEmitter } from "internal:deno_node/polyfills/events.ts"; +import { lookup as defaultLookup } from "internal:deno_node/polyfills/dns.ts"; +import type { + ErrnoException, + NodeSystemErrorCtx, +} from "internal:deno_node/polyfills/internal/errors.ts"; +import { + ERR_BUFFER_OUT_OF_BOUNDS, + ERR_INVALID_ARG_TYPE, + ERR_INVALID_FD_TYPE, + ERR_MISSING_ARGS, + ERR_SOCKET_ALREADY_BOUND, + ERR_SOCKET_BAD_BUFFER_SIZE, + ERR_SOCKET_BUFFER_SIZE, + ERR_SOCKET_DGRAM_IS_CONNECTED, + ERR_SOCKET_DGRAM_NOT_CONNECTED, + ERR_SOCKET_DGRAM_NOT_RUNNING, + errnoException, + exceptionWithHostPort, +} from "internal:deno_node/polyfills/internal/errors.ts"; +import type { Abortable } from "internal:deno_node/polyfills/_events.d.ts"; +import { + kStateSymbol, + newHandle, +} from "internal:deno_node/polyfills/internal/dgram.ts"; +import type { SocketType } from "internal:deno_node/polyfills/internal/dgram.ts"; +import { + asyncIdSymbol, + defaultTriggerAsyncIdScope, + ownerSymbol, +} from "internal:deno_node/polyfills/internal/async_hooks.ts"; +import { + SendWrap, + UDP, +} from "internal:deno_node/polyfills/internal_binding/udp_wrap.ts"; +import { + isInt32, + validateAbortSignal, + validateNumber, + validatePort, + validateString, +} from "internal:deno_node/polyfills/internal/validators.mjs"; +import { guessHandleType } from "internal:deno_node/polyfills/internal_binding/util.ts"; +import { os } from "internal:deno_node/polyfills/internal_binding/constants.ts"; +import { nextTick } from "internal:deno_node/polyfills/process.ts"; +import { channel } from "internal:deno_node/polyfills/diagnostics_channel.ts"; +import { isArrayBufferView } from "internal:deno_node/polyfills/internal/util/types.ts"; + +const { UV_UDP_REUSEADDR, UV_UDP_IPV6ONLY } = os; + +const udpSocketChannel = channel("udp.socket"); + +const BIND_STATE_UNBOUND = 0; +const BIND_STATE_BINDING = 1; +const BIND_STATE_BOUND = 2; + +const CONNECT_STATE_DISCONNECTED = 0; +const CONNECT_STATE_CONNECTING = 1; +const CONNECT_STATE_CONNECTED = 2; + +const RECV_BUFFER = true; +const SEND_BUFFER = false; + +export interface AddressInfo { + address: string; + family: number; + port: number; +} + +export type MessageType = string | Uint8Array | Buffer | DataView; + +export type RemoteInfo = { + address: string; + family: "IPv4" | "IPv6"; + port: number; + size?: number; +}; + +export interface BindOptions { + port?: number; + address?: string; + exclusive?: boolean; + fd?: number; +} + +export interface SocketOptions extends Abortable { + type: SocketType; + reuseAddr?: boolean; + /** + * @default false + */ + ipv6Only?: boolean; + recvBufferSize?: number; + sendBufferSize?: number; + lookup?: typeof defaultLookup; +} + +interface SocketInternalState { + handle: UDP | null; + receiving: boolean; + bindState: + | typeof BIND_STATE_UNBOUND + | typeof BIND_STATE_BINDING + | typeof BIND_STATE_BOUND; + connectState: + | typeof CONNECT_STATE_DISCONNECTED + | typeof CONNECT_STATE_CONNECTING + | typeof CONNECT_STATE_CONNECTED; + queue?: Array<() => void>; + reuseAddr?: boolean; + ipv6Only?: boolean; + recvBufferSize?: number; + sendBufferSize?: number; +} + +const isSocketOptions = ( + socketOption: unknown, +): socketOption is SocketOptions => + socketOption !== null && typeof socketOption === "object"; + +const isUdpHandle = (handle: unknown): handle is UDP => + handle !== null && + typeof handle === "object" && + typeof (handle as UDP).recvStart === "function"; + +const isBindOptions = (options: unknown): options is BindOptions => + options !== null && typeof options === "object"; + +/** + * Encapsulates the datagram functionality. + * + * New instances of `dgram.Socket` are created using `createSocket`. + * The `new` keyword is not to be used to create `dgram.Socket` instances. + */ +export class Socket extends EventEmitter { + [asyncIdSymbol]!: number; + [kStateSymbol]!: SocketInternalState; + + type!: SocketType; + + constructor( + type: SocketType | SocketOptions, + listener?: (msg: Buffer, rinfo: RemoteInfo) => void, + ) { + super(); + + let lookup; + let recvBufferSize; + let sendBufferSize; + + let options: SocketOptions | undefined; + + if (isSocketOptions(type)) { + options = type; + type = options.type; + lookup = options.lookup; + recvBufferSize = options.recvBufferSize; + sendBufferSize = options.sendBufferSize; + } + + const handle = newHandle(type, lookup); + handle[ownerSymbol] = this; + + this[asyncIdSymbol] = handle.getAsyncId(); + this.type = type; + + if (typeof listener === "function") { + this.on("message", listener); + } + + this[kStateSymbol] = { + handle, + receiving: false, + bindState: BIND_STATE_UNBOUND, + connectState: CONNECT_STATE_DISCONNECTED, + queue: undefined, + reuseAddr: options && options.reuseAddr, // Use UV_UDP_REUSEADDR if true. + ipv6Only: options && options.ipv6Only, + recvBufferSize, + sendBufferSize, + }; + + if (options?.signal !== undefined) { + const { signal } = options; + + validateAbortSignal(signal, "options.signal"); + + const onAborted = () => { + this.close(); + }; + + if (signal.aborted) { + onAborted(); + } else { + signal.addEventListener("abort", onAborted); + + this.once( + "close", + () => signal.removeEventListener("abort", onAborted), + ); + } + } + + if (udpSocketChannel.hasSubscribers) { + udpSocketChannel.publish({ + socket: this, + }); + } + } + + /** + * Tells the kernel to join a multicast group at the given `multicastAddress` + * and `multicastInterface` using the `IP_ADD_MEMBERSHIP` socket option. If + * the`multicastInterface` argument is not specified, the operating system + * will choose one interface and will add membership to it. To add membership + * to every available interface, call `addMembership` multiple times, once + * per interface. + * + * When called on an unbound socket, this method will implicitly bind to a + * random port, listening on all interfaces. + * + * When sharing a UDP socket across multiple `cluster` workers, the + * `socket.addMembership()` function must be called only once or an + * `EADDRINUSE` error will occur: + * + * ```js + * import cluster from "internal:deno_node/polyfills/cluster"; + * import dgram from "internal:deno_node/polyfills/dgram"; + * + * if (cluster.isPrimary) { + * cluster.fork(); // Works ok. + * cluster.fork(); // Fails with EADDRINUSE. + * } else { + * const s = dgram.createSocket('udp4'); + * s.bind(1234, () => { + * s.addMembership('224.0.0.114'); + * }); + * } + * ``` + */ + addMembership(multicastAddress: string, interfaceAddress?: string) { + healthCheck(this); + + if (!multicastAddress) { + throw new ERR_MISSING_ARGS("multicastAddress"); + } + + const { handle } = this[kStateSymbol]; + const err = handle!.addMembership(multicastAddress, interfaceAddress); + + if (err) { + throw errnoException(err, "addMembership"); + } + } + + /** + * Tells the kernel to join a source-specific multicast channel at the given + * `sourceAddress` and `groupAddress`, using the `multicastInterface` with + * the `IP_ADD_SOURCE_MEMBERSHIP` socket option. If the `multicastInterface` + * argument is not specified, the operating system will choose one interface + * and will add membership to it. To add membership to every available + * interface, call `socket.addSourceSpecificMembership()` multiple times, + * once per interface. + * + * When called on an unbound socket, this method will implicitly bind to a + * random port, listening on all interfaces. + */ + addSourceSpecificMembership( + sourceAddress: string, + groupAddress: string, + interfaceAddress?: string, + ) { + healthCheck(this); + + validateString(sourceAddress, "sourceAddress"); + validateString(groupAddress, "groupAddress"); + + const err = this[kStateSymbol].handle!.addSourceSpecificMembership( + sourceAddress, + groupAddress, + interfaceAddress, + ); + + if (err) { + throw errnoException(err, "addSourceSpecificMembership"); + } + } + + /** + * Returns an object containing the address information for a socket. + * For UDP sockets, this object will contain `address`, `family` and `port`properties. + * + * This method throws `EBADF` if called on an unbound socket. + */ + address(): AddressInfo { + healthCheck(this); + + const out = {}; + const err = this[kStateSymbol].handle!.getsockname(out); + + if (err) { + throw errnoException(err, "getsockname"); + } + + return out as AddressInfo; + } + + /** + * For UDP sockets, causes the `dgram.Socket` to listen for datagram + * messages on a named `port` and optional `address`. If `port` is not + * specified or is `0`, the operating system will attempt to bind to a + * random port. If `address` is not specified, the operating system will + * attempt to listen on all addresses. Once binding is complete, a + * `'listening'` event is emitted and the optional `callback` function is + * called. + * + * Specifying both a `'listening'` event listener and passing a `callback` to + * the `socket.bind()` method is not harmful but not very useful. + * + * A bound datagram socket keeps the Node.js process running to receive + * datagram messages. + * + * If binding fails, an `'error'` event is generated. In rare case (e.g. + * attempting to bind with a closed socket), an `Error` may be thrown. + * + * Example of a UDP server listening on port 41234: + * + * ```js + * import dgram from "internal:deno_node/polyfills/dgram"; + * + * const server = dgram.createSocket('udp4'); + * + * server.on('error', (err) => { + * console.log(`server error:\n${err.stack}`); + * server.close(); + * }); + * + * server.on('message', (msg, rinfo) => { + * console.log(`server got: ${msg} from ${rinfo.address}:${rinfo.port}`); + * }); + * + * server.on('listening', () => { + * const address = server.address(); + * console.log(`server listening ${address.address}:${address.port}`); + * }); + * + * server.bind(41234); + * // Prints: server listening 0.0.0.0:41234 + * ``` + * + * @param callback with no parameters. Called when binding is complete. + */ + bind(port?: number, address?: string, callback?: () => void): this; + bind(port: number, callback?: () => void): this; + bind(callback: () => void): this; + bind(options: BindOptions, callback?: () => void): this; + bind(port_?: unknown, address_?: unknown /* callback */): this { + let port = typeof port_ === "function" ? null : port_; + + healthCheck(this); + + const state = this[kStateSymbol]; + + if (state.bindState !== BIND_STATE_UNBOUND) { + throw new ERR_SOCKET_ALREADY_BOUND(); + } + + state.bindState = BIND_STATE_BINDING; + + const cb = arguments.length && arguments[arguments.length - 1]; + + if (typeof cb === "function") { + // deno-lint-ignore no-inner-declarations + function removeListeners(this: Socket) { + this.removeListener("error", removeListeners); + this.removeListener("listening", onListening); + } + + // deno-lint-ignore no-inner-declarations + function onListening(this: Socket) { + removeListeners.call(this); + cb.call(this); + } + + this.on("error", removeListeners); + this.on("listening", onListening); + } + + if (isUdpHandle(port)) { + replaceHandle(this, port); + startListening(this); + + return this; + } + + // Open an existing fd instead of creating a new one. + if (isBindOptions(port) && isInt32(port.fd!) && port.fd! > 0) { + const fd = port.fd!; + const state = this[kStateSymbol]; + + // TODO(cmorten): here we deviate somewhat from the Node implementation which + // makes use of the https://nodejs.org/api/cluster.html module to run servers + // across a "cluster" of Node processes to take advantage of multi-core + // systems. + // + // Though Deno has has a Worker capability from which we could simulate this, + // for now we assert that we are _always_ on the primary process. + + const type = guessHandleType(fd); + + if (type !== "UDP") { + throw new ERR_INVALID_FD_TYPE(type); + } + + const err = state.handle!.open(fd); + + if (err) { + throw errnoException(err, "open"); + } + + startListening(this); + + return this; + } + + let address: string; + + if (isBindOptions(port)) { + address = port.address || ""; + port = port.port; + } else { + address = typeof address_ === "function" ? "" : (address_ as string); + } + + // Defaulting address for bind to all interfaces + if (!address) { + if (this.type === "udp4") { + address = "0.0.0.0"; + } else { + address = "::"; + } + } + + // Resolve address first + state.handle!.lookup(address, (lookupError, ip) => { + if (lookupError) { + state.bindState = BIND_STATE_UNBOUND; + this.emit("error", lookupError); + + return; + } + + let flags: number | undefined = 0; + + if (state.reuseAddr) { + flags |= UV_UDP_REUSEADDR; + } + if (state.ipv6Only) { + flags |= UV_UDP_IPV6ONLY!; + } + + // TODO(cmorten): here we deviate somewhat from the Node implementation which + // makes use of the https://nodejs.org/api/cluster.html module to run servers + // across a "cluster" of Node processes to take advantage of multi-core + // systems. + // + // Though Deno has has a Worker capability from which we could simulate this, + // for now we assert that we are _always_ on the primary process. + + if (!state.handle) { + return; // Handle has been closed in the mean time + } + + const err = state.handle.bind(ip, port as number || 0, flags); + + if (err) { + const ex = exceptionWithHostPort(err, "bind", ip, port as number); + state.bindState = BIND_STATE_UNBOUND; + this.emit("error", ex); + + // Todo(@bartlomieju): close? + return; + } + + startListening(this); + }); + + return this; + } + + /** + * Close the underlying socket and stop listening for data on it. If a + * callback is provided, it is added as a listener for the `'close'` event. + * + * @param callback Called when the socket has been closed. + */ + close(callback?: () => void): this { + const state = this[kStateSymbol]; + const queue = state.queue; + + if (typeof callback === "function") { + this.on("close", callback); + } + + if (queue !== undefined) { + queue.push(this.close.bind(this)); + + return this; + } + + healthCheck(this); + stopReceiving(this); + + state.handle!.close(); + state.handle = null; + + defaultTriggerAsyncIdScope( + this[asyncIdSymbol], + nextTick, + socketCloseNT, + this, + ); + + return this; + } + + /** + * Associates the `dgram.Socket` to a remote address and port. Every + * message sent by this handle is automatically sent to that destination. + * Also, the socket will only receive messages from that remote peer. + * Trying to call `connect()` on an already connected socket will result + * in an `ERR_SOCKET_DGRAM_IS_CONNECTED` exception. If `address` is not + * provided, `'127.0.0.1'` (for `udp4` sockets) or `'::1'` (for `udp6` sockets) + * will be used by default. Once the connection is complete, a `'connect'` event + * is emitted and the optional `callback` function is called. In case of failure, + * the `callback` is called or, failing this, an `'error'` event is emitted. + * + * @param callback Called when the connection is completed or on error. + */ + connect( + port: number, + address?: string, + callback?: (err?: ErrnoException) => void, + ): void; + connect(port: number, callback: (err?: ErrnoException) => void): void; + connect(port: number, address?: unknown, callback?: unknown) { + port = validatePort(port, "Port", false); + + if (typeof address === "function") { + callback = address; + address = ""; + } else if (address === undefined) { + address = ""; + } + + validateString(address, "address"); + + const state = this[kStateSymbol]; + + if (state.connectState !== CONNECT_STATE_DISCONNECTED) { + throw new ERR_SOCKET_DGRAM_IS_CONNECTED(); + } + + state.connectState = CONNECT_STATE_CONNECTING; + + if (state.bindState === BIND_STATE_UNBOUND) { + this.bind({ port: 0, exclusive: true }); + } + + if (state.bindState !== BIND_STATE_BOUND) { + enqueue( + this, + _connect.bind( + this, + port, + address as string, + callback as (err?: ErrnoException) => void, + ), + ); + + return; + } + + Reflect.apply(_connect, this, [port, address, callback]); + } + + /** + * A synchronous function that disassociates a connected `dgram.Socket` from + * its remote address. Trying to call `disconnect()` on an unbound or already + * disconnected socket will result in an `ERR_SOCKET_DGRAM_NOT_CONNECTED` + * exception. + */ + disconnect() { + const state = this[kStateSymbol]; + + if (state.connectState !== CONNECT_STATE_CONNECTED) { + throw new ERR_SOCKET_DGRAM_NOT_CONNECTED(); + } + + const err = state.handle!.disconnect(); + + if (err) { + throw errnoException(err, "connect"); + } else { + state.connectState = CONNECT_STATE_DISCONNECTED; + } + } + + /** + * Instructs the kernel to leave a multicast group at `multicastAddress` + * using the `IP_DROP_MEMBERSHIP` socket option. This method is automatically + * called by the kernel when the socket is closed or the process terminates, + * so most apps will never have reason to call this. + * + * If `multicastInterface` is not specified, the operating system will + * attempt to drop membership on all valid interfaces. + */ + dropMembership(multicastAddress: string, interfaceAddress?: string) { + healthCheck(this); + + if (!multicastAddress) { + throw new ERR_MISSING_ARGS("multicastAddress"); + } + + const err = this[kStateSymbol].handle!.dropMembership( + multicastAddress, + interfaceAddress, + ); + + if (err) { + throw errnoException(err, "dropMembership"); + } + } + + /** + * Instructs the kernel to leave a source-specific multicast channel at the + * given `sourceAddress` and `groupAddress` using the + * `IP_DROP_SOURCE_MEMBERSHIP` socket option. This method is automatically + * called by the kernel when the socket is closed or the process terminates, + * so most apps will never have reason to call this. + * + * If `multicastInterface` is not specified, the operating system will + * attempt to drop membership on all valid interfaces. + */ + dropSourceSpecificMembership( + sourceAddress: string, + groupAddress: string, + interfaceAddress?: string, + ) { + healthCheck(this); + + validateString(sourceAddress, "sourceAddress"); + validateString(groupAddress, "groupAddress"); + + const err = this[kStateSymbol].handle!.dropSourceSpecificMembership( + sourceAddress, + groupAddress, + interfaceAddress, + ); + + if (err) { + throw errnoException(err, "dropSourceSpecificMembership"); + } + } + + /** + * This method throws `ERR_SOCKET_BUFFER_SIZE` if called on an unbound + * socket. + * + * @return the `SO_RCVBUF` socket receive buffer size in bytes. + */ + getRecvBufferSize(): number { + return bufferSize(this, 0, RECV_BUFFER); + } + + /** + * This method throws `ERR_SOCKET_BUFFER_SIZE` if called on an unbound + * socket. + * + * @return the `SO_SNDBUF` socket send buffer size in bytes. + */ + getSendBufferSize(): number { + return bufferSize(this, 0, SEND_BUFFER); + } + + /** + * By default, binding a socket will cause it to block the Node.js process + * from exiting as long as the socket is open. The `socket.unref()` method + * can be used to exclude the socket from the reference counting that keeps + * the Node.js process active. The `socket.ref()` method adds the socket back + * to the reference counting and restores the default behavior. + * + * Calling `socket.ref()` multiples times will have no additional effect. + * + * The `socket.ref()` method returns a reference to the socket so calls can + * be chained. + */ + ref(): this { + const handle = this[kStateSymbol].handle; + + if (handle) { + handle.ref(); + } + + return this; + } + + /** + * Returns an object containing the `address`, `family`, and `port` of the + * remote endpoint. This method throws an `ERR_SOCKET_DGRAM_NOT_CONNECTED` + * exception if the socket is not connected. + */ + remoteAddress(): AddressInfo { + healthCheck(this); + + const state = this[kStateSymbol]; + + if (state.connectState !== CONNECT_STATE_CONNECTED) { + throw new ERR_SOCKET_DGRAM_NOT_CONNECTED(); + } + + const out = {}; + const err = state.handle!.getpeername(out); + + if (err) { + throw errnoException(err, "getpeername"); + } + + return out as AddressInfo; + } + + /** + * Broadcasts a datagram on the socket. + * For connectionless sockets, the destination `port` and `address` must be + * specified. Connected sockets, on the other hand, will use their associated + * remote endpoint, so the `port` and `address` arguments must not be set. + * + * The `msg` argument contains the message to be sent. + * Depending on its type, different behavior can apply. If `msg` is a + * `Buffer`, any `TypedArray` or a `DataView`, + * the `offset` and `length` specify the offset within the `Buffer` where the + * message begins and the number of bytes in the message, respectively. + * If `msg` is a `String`, then it is automatically converted to a `Buffer` + * with `'utf8'` encoding. With messages that contain multi-byte characters, + * `offset` and `length` will be calculated with respect to `byte length` and + * not the character position. If `msg` is an array, `offset` and `length` + * must not be specified. + * + * The `address` argument is a string. If the value of `address` is a host + * name, DNS will be used to resolve the address of the host. If `address` + * is not provided or otherwise nullish, `'127.0.0.1'` (for `udp4` sockets) + * or `'::1'`(for `udp6` sockets) will be used by default. + * + * If the socket has not been previously bound with a call to `bind`, the + * socket is assigned a random port number and is bound to the "all + * interfaces" address (`'0.0.0.0'` for `udp4` sockets, `'::0'` for `udp6` + * sockets.) + * + * An optional `callback` function may be specified to as a way of + * reporting DNS errors or for determining when it is safe to reuse the `buf` + * object. DNS lookups delay the time to send for at least one tick of the + * Node.js event loop. + * + * The only way to know for sure that the datagram has been sent is by using + * a `callback`. If an error occurs and a `callback` is given, the error will + * be passed as the first argument to the `callback`. If a `callback` is not + * given, the error is emitted as an `'error'` event on the `socket` object. + * + * Offset and length are optional but both _must_ be set if either are used. + * They are supported only when the first argument is a `Buffer`, a + * `TypedArray`, or a `DataView`. + * + * This method throws `ERR_SOCKET_BAD_PORT` if called on an unbound socket. + * + * Example of sending a UDP packet to a port on `localhost`; + * + * ```js + * import dgram from "internal:deno_node/polyfills/dgram"; + * import { Buffer } from "internal:deno_node/polyfills/buffer"; + * + * const message = Buffer.from('Some bytes'); + * const client = dgram.createSocket('udp4'); + * client.send(message, 41234, 'localhost', (err) => { + * client.close(); + * }); + * ``` + * + * Example of sending a UDP packet composed of multiple buffers to a port on + * `127.0.0.1`; + * + * ```js + * import dgram from "internal:deno_node/polyfills/dgram"; + * import { Buffer } from "internal:deno_node/polyfills/buffer"; + * + * const buf1 = Buffer.from('Some '); + * const buf2 = Buffer.from('bytes'); + * const client = dgram.createSocket('udp4'); + * client.send([buf1, buf2], 41234, (err) => { + * client.close(); + * }); + * ``` + * + * Sending multiple buffers might be faster or slower depending on the + * application and operating system. Run benchmarks to + * determine the optimal strategy on a case-by-case basis. Generally + * speaking, however, sending multiple buffers is faster. + * + * Example of sending a UDP packet using a socket connected to a port on + * `localhost`: + * + * ```js + * import dgram from "internal:deno_node/polyfills/dgram"; + * import { Buffer } from "internal:deno_node/polyfills/buffer"; + * + * const message = Buffer.from('Some bytes'); + * const client = dgram.createSocket('udp4'); + * client.connect(41234, 'localhost', (err) => { + * client.send(message, (err) => { + * client.close(); + * }); + * }); + * ``` + * + * @param msg Message to be sent. + * @param offset Offset in the buffer where the message starts. + * @param length Number of bytes in the message. + * @param port Destination port. + * @param address Destination host name or IP address. + * @param callback Called when the message has been sent. + */ + send( + msg: MessageType | ReadonlyArray, + port?: number, + address?: string, + callback?: (error: ErrnoException | null, bytes?: number) => void, + ): void; + send( + msg: MessageType | ReadonlyArray, + port?: number, + callback?: (error: ErrnoException | null, bytes?: number) => void, + ): void; + send( + msg: MessageType | ReadonlyArray, + callback?: (error: ErrnoException | null, bytes?: number) => void, + ): void; + send( + msg: MessageType, + offset: number, + length: number, + port?: number, + address?: string, + callback?: (error: ErrnoException | null, bytes?: number) => void, + ): void; + send( + msg: MessageType, + offset: number, + length: number, + port?: number, + callback?: (error: ErrnoException | null, bytes?: number) => void, + ): void; + send( + msg: MessageType, + offset: number, + length: number, + callback?: (error: ErrnoException | null, bytes?: number) => void, + ): void; + send( + buffer: unknown, + offset?: unknown, + length?: unknown, + port?: unknown, + address?: unknown, + callback?: unknown, + ) { + let list: MessageType[] | null; + + const state = this[kStateSymbol]; + const connected = state.connectState === CONNECT_STATE_CONNECTED; + + if (!connected) { + if (address || (port && typeof port !== "function")) { + buffer = sliceBuffer( + buffer as MessageType, + offset as number, + length as number, + ); + } else { + callback = port; + port = offset; + address = length; + } + } else { + if (typeof length === "number") { + buffer = sliceBuffer(buffer as MessageType, offset as number, length); + + if (typeof port === "function") { + callback = port; + port = null; + } + } else { + callback = offset; + } + + if (port || address) { + throw new ERR_SOCKET_DGRAM_IS_CONNECTED(); + } + } + + if (!Array.isArray(buffer)) { + if (typeof buffer === "string") { + list = [Buffer.from(buffer)]; + } else if (!isArrayBufferView(buffer)) { + throw new ERR_INVALID_ARG_TYPE( + "buffer", + ["Buffer", "TypedArray", "DataView", "string"], + buffer, + ); + } else { + list = [buffer as MessageType]; + } + } else if (!(list = fixBufferList(buffer))) { + throw new ERR_INVALID_ARG_TYPE( + "buffer list arguments", + ["Buffer", "TypedArray", "DataView", "string"], + buffer, + ); + } + + if (!connected) { + port = validatePort(port, "Port", false); + } + + // Normalize callback so it's either a function or undefined but not anything + // else. + if (typeof callback !== "function") { + callback = undefined; + } + + if (typeof address === "function") { + callback = address; + address = undefined; + } else if (address && typeof address !== "string") { + throw new ERR_INVALID_ARG_TYPE("address", ["string", "falsy"], address); + } + + healthCheck(this); + + if (state.bindState === BIND_STATE_UNBOUND) { + this.bind({ port: 0, exclusive: true }); + } + + if (list.length === 0) { + list.push(Buffer.alloc(0)); + } + + // If the socket hasn't been bound yet, push the outbound packet onto the + // send queue and send after binding is complete. + if (state.bindState !== BIND_STATE_BOUND) { + // @ts-ignore mapping unknowns back onto themselves doesn't type nicely + enqueue(this, this.send.bind(this, list, port, address, callback)); + + return; + } + + const afterDns = (ex: ErrnoException | null, ip: string) => { + defaultTriggerAsyncIdScope( + this[asyncIdSymbol], + doSend, + ex, + this, + ip, + list, + address, + port, + callback, + ); + }; + + if (!connected) { + state.handle!.lookup(address as string, afterDns); + } else { + afterDns(null, ""); + } + } + + /** + * Sets or clears the `SO_BROADCAST` socket option. When set to `true`, UDP + * packets may be sent to a local interface's broadcast address. + * + * This method throws `EBADF` if called on an unbound socket. + */ + setBroadcast(arg: boolean) { + const err = this[kStateSymbol].handle!.setBroadcast(arg ? 1 : 0); + + if (err) { + throw errnoException(err, "setBroadcast"); + } + } + + /** + * _All references to scope in this section are referring to [IPv6 Zone Indices](https://en.wikipedia.org/wiki/IPv6_address#Scoped_literal_IPv6_addresses), which are defined by [RFC + * 4007](https://tools.ietf.org/html/rfc4007). In string form, an IP_ + * _with a scope index is written as `'IP%scope'` where scope is an interface name_ + * _or interface number._ + * + * Sets the default outgoing multicast interface of the socket to a chosen + * interface or back to system interface selection. The `multicastInterface` must + * be a valid string representation of an IP from the socket's family. + * + * For IPv4 sockets, this should be the IP configured for the desired physical + * interface. All packets sent to multicast on the socket will be sent on the + * interface determined by the most recent successful use of this call. + * + * For IPv6 sockets, `multicastInterface` should include a scope to indicate the + * interface as in the examples that follow. In IPv6, individual `send` calls can + * also use explicit scope in addresses, so only packets sent to a multicast + * address without specifying an explicit scope are affected by the most recent + * successful use of this call. + * + * This method throws `EBADF` if called on an unbound socket. + * + * #### Example: IPv6 outgoing multicast interface + * + * On most systems, where scope format uses the interface name: + * + * ```js + * const socket = dgram.createSocket('udp6'); + * + * socket.bind(1234, () => { + * socket.setMulticastInterface('::%eth1'); + * }); + * ``` + * + * On Windows, where scope format uses an interface number: + * + * ```js + * const socket = dgram.createSocket('udp6'); + * + * socket.bind(1234, () => { + * socket.setMulticastInterface('::%2'); + * }); + * ``` + * + * #### Example: IPv4 outgoing multicast interface + * + * All systems use an IP of the host on the desired physical interface: + * + * ```js + * const socket = dgram.createSocket('udp4'); + * + * socket.bind(1234, () => { + * socket.setMulticastInterface('10.0.0.2'); + * }); + * ``` + */ + setMulticastInterface(interfaceAddress: string) { + healthCheck(this); + validateString(interfaceAddress, "interfaceAddress"); + + const err = this[kStateSymbol].handle!.setMulticastInterface( + interfaceAddress, + ); + + if (err) { + throw errnoException(err, "setMulticastInterface"); + } + } + + /** + * Sets or clears the `IP_MULTICAST_LOOP` socket option. When set to `true`, + * multicast packets will also be received on the local interface. + * + * This method throws `EBADF` if called on an unbound socket. + */ + setMulticastLoopback(arg: boolean): typeof arg { + const err = this[kStateSymbol].handle!.setMulticastLoopback(arg ? 1 : 0); + + if (err) { + throw errnoException(err, "setMulticastLoopback"); + } + + return arg; // 0.4 compatibility + } + + /** + * Sets the `IP_MULTICAST_TTL` socket option. While TTL generally stands for + * "Time to Live", in this context it specifies the number of IP hops that a + * packet is allowed to travel through, specifically for multicast traffic. Each + * router or gateway that forwards a packet decrements the TTL. If the TTL is + * decremented to 0 by a router, it will not be forwarded. + * + * The `ttl` argument may be between 0 and 255\. The default on most systems is `1`. + * + * This method throws `EBADF` if called on an unbound socket. + */ + setMulticastTTL(ttl: number): typeof ttl { + validateNumber(ttl, "ttl"); + + const err = this[kStateSymbol].handle!.setMulticastTTL(ttl); + + if (err) { + throw errnoException(err, "setMulticastTTL"); + } + + return ttl; + } + + /** + * Sets the `SO_RCVBUF` socket option. Sets the maximum socket receive buffer + * in bytes. + * + * This method throws `ERR_SOCKET_BUFFER_SIZE` if called on an unbound socket. + */ + setRecvBufferSize(size: number) { + bufferSize(this, size, RECV_BUFFER); + } + + /** + * Sets the `SO_SNDBUF` socket option. Sets the maximum socket send buffer + * in bytes. + * + * This method throws `ERR_SOCKET_BUFFER_SIZE` if called on an unbound socket. + */ + setSendBufferSize(size: number) { + bufferSize(this, size, SEND_BUFFER); + } + + /** + * Sets the `IP_TTL` socket option. While TTL generally stands for "Time to Live", + * in this context it specifies the number of IP hops that a packet is allowed to + * travel through. Each router or gateway that forwards a packet decrements the + * TTL. If the TTL is decremented to 0 by a router, it will not be forwarded. + * Changing TTL values is typically done for network probes or when multicasting. + * + * The `ttl` argument may be between between 1 and 255\. The default on most systems + * is 64. + * + * This method throws `EBADF` if called on an unbound socket. + */ + setTTL(ttl: number): typeof ttl { + validateNumber(ttl, "ttl"); + + const err = this[kStateSymbol].handle!.setTTL(ttl); + + if (err) { + throw errnoException(err, "setTTL"); + } + + return ttl; + } + + /** + * By default, binding a socket will cause it to block the Node.js process from + * exiting as long as the socket is open. The `socket.unref()` method can be used + * to exclude the socket from the reference counting that keeps the Node.js + * process active, allowing the process to exit even if the socket is still + * listening. + * + * Calling `socket.unref()` multiple times will have no addition effect. + * + * The `socket.unref()` method returns a reference to the socket so calls can be + * chained. + */ + unref(): this { + const handle = this[kStateSymbol].handle; + + if (handle) { + handle.unref(); + } + + return this; + } +} + +/** + * Creates a `dgram.Socket` object. Once the socket is created, calling + * `socket.bind()` will instruct the socket to begin listening for datagram + * messages. When `address` and `port` are not passed to `socket.bind()` the + * method will bind the socket to the "all interfaces" address on a random port + * (it does the right thing for both `udp4` and `udp6` sockets). The bound + * address and port can be retrieved using `socket.address().address` and + * `socket.address().port`. + * + * If the `signal` option is enabled, calling `.abort()` on the corresponding + * `AbortController` is similar to calling `.close()` on the socket: + * + * ```js + * const controller = new AbortController(); + * const { signal } = controller; + * const server = dgram.createSocket({ type: 'udp4', signal }); + * server.on('message', (msg, rinfo) => { + * console.log(`server got: ${msg} from ${rinfo.address}:${rinfo.port}`); + * }); + * // Later, when you want to close the server. + * controller.abort(); + * ``` + * + * @param options + * @param callback Attached as a listener for `'message'` events. Optional. + */ +export function createSocket( + type: SocketType, + listener?: (msg: Buffer, rinfo: RemoteInfo) => void, +): Socket; +export function createSocket( + type: SocketOptions, + listener?: (msg: Buffer, rinfo: RemoteInfo) => void, +): Socket; +export function createSocket( + type: SocketType | SocketOptions, + listener?: (msg: Buffer, rinfo: RemoteInfo) => void, +): Socket { + return new Socket(type, listener); +} + +function startListening(socket: Socket) { + const state = socket[kStateSymbol]; + + state.handle!.onmessage = onMessage; + // Todo(@bartlomieju): handle errors + state.handle!.recvStart(); + state.receiving = true; + state.bindState = BIND_STATE_BOUND; + + if (state.recvBufferSize) { + bufferSize(socket, state.recvBufferSize, RECV_BUFFER); + } + + if (state.sendBufferSize) { + bufferSize(socket, state.sendBufferSize, SEND_BUFFER); + } + + socket.emit("listening"); +} + +function replaceHandle(self: Socket, newHandle: UDP) { + const state = self[kStateSymbol]; + const oldHandle = state.handle!; + + // Set up the handle that we got from primary. + newHandle.lookup = oldHandle.lookup; + newHandle.bind = oldHandle.bind; + newHandle.send = oldHandle.send; + newHandle[ownerSymbol] = self; + + // Replace the existing handle by the handle we got from primary. + oldHandle.close(); + state.handle = newHandle; +} + +function bufferSize(self: Socket, size: number, buffer: boolean): number { + if (size >>> 0 !== size) { + throw new ERR_SOCKET_BAD_BUFFER_SIZE(); + } + + const ctx = {}; + const ret = self[kStateSymbol].handle!.bufferSize(size, buffer, ctx); + + if (ret === undefined) { + throw new ERR_SOCKET_BUFFER_SIZE(ctx as NodeSystemErrorCtx); + } + + return ret; +} + +function socketCloseNT(self: Socket) { + self.emit("close"); +} + +function healthCheck(socket: Socket) { + if (!socket[kStateSymbol].handle) { + // Error message from dgram_legacy.js. + throw new ERR_SOCKET_DGRAM_NOT_RUNNING(); + } +} + +function stopReceiving(socket: Socket) { + const state = socket[kStateSymbol]; + + if (!state.receiving) { + return; + } + + state.handle!.recvStop(); + state.receiving = false; +} + +function onMessage( + nread: number, + handle: UDP, + buf?: Buffer, + rinfo?: RemoteInfo, +) { + const self = handle[ownerSymbol] as Socket; + + if (nread < 0) { + self.emit("error", errnoException(nread, "recvmsg")); + + return; + } + + rinfo!.size = buf!.length; // compatibility + + self.emit("message", buf, rinfo); +} + +function sliceBuffer(buffer: MessageType, offset: number, length: number) { + if (typeof buffer === "string") { + buffer = Buffer.from(buffer); + } else if (!isArrayBufferView(buffer)) { + throw new ERR_INVALID_ARG_TYPE( + "buffer", + ["Buffer", "TypedArray", "DataView", "string"], + buffer, + ); + } + + offset = offset >>> 0; + length = length >>> 0; + + if (offset > buffer.byteLength) { + throw new ERR_BUFFER_OUT_OF_BOUNDS("offset"); + } + + if (offset + length > buffer.byteLength) { + throw new ERR_BUFFER_OUT_OF_BOUNDS("length"); + } + + return Buffer.from(buffer.buffer, buffer.byteOffset + offset, length); +} + +function fixBufferList( + list: ReadonlyArray, +): Array | null { + const newList = new Array(list.length); + + for (let i = 0, l = list.length; i < l; i++) { + const buf = list[i]; + + if (typeof buf === "string") { + newList[i] = Buffer.from(buf); + } else if (!isArrayBufferView(buf)) { + return null; + } else { + newList[i] = Buffer.from(buf.buffer, buf.byteOffset, buf.byteLength); + } + } + + return newList; +} + +function enqueue(self: Socket, toEnqueue: () => void) { + const state = self[kStateSymbol]; + + // If the send queue hasn't been initialized yet, do it, and install an + // event handler that flushes the send queue after binding is done. + if (state.queue === undefined) { + state.queue = []; + + self.once(EventEmitter.errorMonitor, onListenError); + self.once("listening", onListenSuccess); + } + + state.queue.push(toEnqueue); +} + +function onListenSuccess(this: Socket) { + this.removeListener(EventEmitter.errorMonitor, onListenError); + clearQueue.call(this); +} + +function onListenError(this: Socket) { + this.removeListener("listening", onListenSuccess); + this[kStateSymbol].queue = undefined; +} + +function clearQueue(this: Socket) { + const state = this[kStateSymbol]; + const queue = state.queue; + state.queue = undefined; + + // Flush the send queue. + for (const queueEntry of queue!) { + queueEntry(); + } +} + +function _connect( + this: Socket, + port: number, + address: string, + callback: (err?: ErrnoException) => void, +) { + const state = this[kStateSymbol]; + + if (callback) { + this.once("connect", callback); + } + + const afterDns = (ex: ErrnoException | null, ip: string) => { + defaultTriggerAsyncIdScope( + this[asyncIdSymbol], + doConnect, + ex, + this, + ip, + address, + port, + callback, + ); + }; + + state.handle!.lookup(address, afterDns); +} + +function doConnect( + ex: ErrnoException | null, + self: Socket, + ip: string, + address: string, + port: number, + callback: (err?: ErrnoException) => void, +) { + const state = self[kStateSymbol]; + + if (!state.handle) { + return; + } + + if (!ex) { + const err = state.handle.connect(ip, port); + + if (err) { + ex = exceptionWithHostPort(err, "connect", address, port); + } + } + + if (ex) { + state.connectState = CONNECT_STATE_DISCONNECTED; + + return nextTick(() => { + if (callback) { + self.removeListener("connect", callback); + + callback(ex!); + } else { + self.emit("error", ex); + } + }); + } + + state.connectState = CONNECT_STATE_CONNECTED; + + nextTick(() => self.emit("connect")); +} + +function doSend( + ex: ErrnoException | null, + self: Socket, + ip: string, + list: MessageType[], + address: string, + port: number, + callback?: (error: ErrnoException | null, bytes?: number) => void, +) { + const state = self[kStateSymbol]; + + if (ex) { + if (typeof callback === "function") { + nextTick(callback, ex); + + return; + } + + nextTick(() => self.emit("error", ex)); + + return; + } else if (!state.handle) { + return; + } + + const req = new SendWrap(); + req.list = list; // Keep reference alive. + req.address = address; + req.port = port; + + if (callback) { + req.callback = callback; + req.oncomplete = afterSend; + } + + let err; + + if (port) { + err = state.handle.send(req, list, list.length, port, ip, !!callback); + } else { + err = state.handle.send(req, list, list.length, !!callback); + } + + if (err >= 1) { + // Synchronous finish. The return code is msg_length + 1 so that we can + // distinguish between synchronous success and asynchronous success. + if (callback) { + nextTick(callback, null, err - 1); + } + + return; + } + + if (err && callback) { + // Don't emit as error, dgram_legacy.js compatibility + const ex = exceptionWithHostPort(err, "send", address, port); + + nextTick(callback, ex); + } +} + +function afterSend(this: SendWrap, err: number | null, sent?: number) { + let ex: ErrnoException | null; + + if (err) { + ex = exceptionWithHostPort(err, "send", this.address, this.port); + } else { + ex = null; + } + + this.callback(ex, sent); +} + +export type { SocketType }; + +export default { + createSocket, + Socket, +}; diff --git a/ext/node/polyfills/diagnostics_channel.ts b/ext/node/polyfills/diagnostics_channel.ts new file mode 100644 index 00000000000000..c0918e4e2410a5 --- /dev/null +++ b/ext/node/polyfills/diagnostics_channel.ts @@ -0,0 +1,89 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. +import { ERR_INVALID_ARG_TYPE } from "internal:deno_node/polyfills/internal/errors.ts"; +import { validateFunction } from "internal:deno_node/polyfills/internal/validators.mjs"; +import { nextTick } from "internal:deno_node/polyfills/process.ts"; + +type Subscriber = (message: unknown, name?: string) => void; + +export class Channel { + _subscribers: Subscriber[]; + name: string; + constructor(name: string) { + this._subscribers = []; + this.name = name; + } + + publish(message: unknown) { + for (const subscriber of this._subscribers) { + try { + subscriber(message, this.name); + } catch (err) { + nextTick(() => { + throw err; + }); + } + } + } + + subscribe(subscription: Subscriber) { + validateFunction(subscription, "subscription"); + + this._subscribers.push(subscription); + } + + unsubscribe(subscription: Subscriber) { + if (!this._subscribers.includes(subscription)) { + return false; + } + + this._subscribers.splice(this._subscribers.indexOf(subscription), 1); + + return true; + } + + get hasSubscribers() { + return this._subscribers.length > 0; + } +} + +const channels: Record = {}; + +export function channel(name: string) { + if (typeof name !== "string" && typeof name !== "symbol") { + throw new ERR_INVALID_ARG_TYPE("channel", ["string", "symbol"], name); + } + + if (!Object.hasOwn(channels, name)) { + channels[name] = new Channel(name); + } + + return channels[name]; +} + +export function hasSubscribers(name: string) { + if (!Object.hasOwn(channels, name)) { + return false; + } + + return channels[name].hasSubscribers; +} + +export function subscribe(name: string, subscription: Subscriber) { + const c = channel(name); + + return c.subscribe(subscription); +} + +export function unsubscribe(name: string, subscription: Subscriber) { + const c = channel(name); + + return c.unsubscribe(subscription); +} + +export default { + channel, + hasSubscribers, + subscribe, + unsubscribe, + Channel, +}; diff --git a/ext/node/polyfills/dns.ts b/ext/node/polyfills/dns.ts new file mode 100644 index 00000000000000..1626517f13dadc --- /dev/null +++ b/ext/node/polyfills/dns.ts @@ -0,0 +1,1012 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. +// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. + +import { nextTick } from "internal:deno_node/polyfills/_next_tick.ts"; +import { customPromisifyArgs } from "internal:deno_node/polyfills/internal/util.mjs"; +import { + validateBoolean, + validateFunction, + validateNumber, + validateOneOf, + validateString, +} from "internal:deno_node/polyfills/internal/validators.mjs"; +import { isIP } from "internal:deno_node/polyfills/internal/net.ts"; +import { + emitInvalidHostnameWarning, + getDefaultResolver, + getDefaultVerbatim, + isFamily, + isLookupCallback, + isLookupOptions, + isResolveCallback, + Resolver as CallbackResolver, + setDefaultResolver, + setDefaultResultOrder, + validateHints, +} from "internal:deno_node/polyfills/internal/dns/utils.ts"; +import type { + AnyAaaaRecord, + AnyARecord, + AnyCnameRecord, + AnyMxRecord, + AnyNaptrRecord, + AnyNsRecord, + AnyPtrRecord, + AnyRecord, + AnySoaRecord, + AnySrvRecord, + AnyTxtRecord, + CaaRecord, + LookupAddress, + LookupAllOptions, + LookupOneOptions, + LookupOptions, + MxRecord, + NaptrRecord, + Records, + RecordWithTtl, + ResolveCallback, + ResolveOptions, + ResolverOptions, + ResolveWithTtlOptions, + SoaRecord, + SrvRecord, +} from "internal:deno_node/polyfills/internal/dns/utils.ts"; +import promisesBase from "internal:deno_node/polyfills/internal/dns/promises.ts"; +import type { ErrnoException } from "internal:deno_node/polyfills/internal/errors.ts"; +import { + dnsException, + ERR_INVALID_ARG_TYPE, + ERR_INVALID_ARG_VALUE, +} from "internal:deno_node/polyfills/internal/errors.ts"; +import { + AI_ADDRCONFIG as ADDRCONFIG, + AI_ALL as ALL, + AI_V4MAPPED as V4MAPPED, +} from "internal:deno_node/polyfills/internal_binding/ares.ts"; +import { + ChannelWrapQuery, + getaddrinfo, + GetAddrInfoReqWrap, + QueryReqWrap, +} from "internal:deno_node/polyfills/internal_binding/cares_wrap.ts"; +import { toASCII } from "internal:deno_node/polyfills/punycode.ts"; +import { notImplemented } from "internal:deno_node/polyfills/_utils.ts"; + +function onlookup( + this: GetAddrInfoReqWrap, + err: number | null, + addresses: string[], +) { + if (err) { + return this.callback(dnsException(err, "getaddrinfo", this.hostname)); + } + + this.callback(null, addresses[0], this.family || isIP(addresses[0])); +} + +function onlookupall( + this: GetAddrInfoReqWrap, + err: number | null, + addresses: string[], +) { + if (err) { + return this.callback(dnsException(err, "getaddrinfo", this.hostname)); + } + + const family = this.family; + const parsedAddresses = []; + + for (let i = 0; i < addresses.length; i++) { + const addr = addresses[i]; + parsedAddresses[i] = { + address: addr, + family: family || isIP(addr), + }; + } + + this.callback(null, parsedAddresses); +} + +type LookupCallback = ( + err: ErrnoException | null, + addressOrAddresses?: string | LookupAddress[] | null, + family?: number, +) => void; + +const validFamilies = [0, 4, 6]; + +// Easy DNS A/AAAA look up +// lookup(hostname, [options,] callback) +export function lookup( + hostname: string, + family: number, + callback: ( + err: ErrnoException | null, + address: string, + family: number, + ) => void, +): GetAddrInfoReqWrap | Record; +export function lookup( + hostname: string, + options: LookupOneOptions, + callback: ( + err: ErrnoException | null, + address: string, + family: number, + ) => void, +): GetAddrInfoReqWrap | Record; +export function lookup( + hostname: string, + options: LookupAllOptions, + callback: (err: ErrnoException | null, addresses: LookupAddress[]) => void, +): GetAddrInfoReqWrap | Record; +export function lookup( + hostname: string, + options: LookupOptions, + callback: ( + err: ErrnoException | null, + address: string | LookupAddress[], + family: number, + ) => void, +): GetAddrInfoReqWrap | Record; +export function lookup( + hostname: string, + callback: ( + err: ErrnoException | null, + address: string, + family: number, + ) => void, +): GetAddrInfoReqWrap | Record; +export function lookup( + hostname: string, + options: unknown, + callback?: unknown, +): GetAddrInfoReqWrap | Record { + let hints = 0; + let family = 0; + let all = false; + let verbatim = getDefaultVerbatim(); + + // Parse arguments + if (hostname) { + validateString(hostname, "hostname"); + } + + if (isLookupCallback(options)) { + callback = options; + family = 0; + } else if (isFamily(options)) { + validateFunction(callback, "callback"); + + validateOneOf(options, "family", validFamilies); + family = options; + } else if (!isLookupOptions(options)) { + validateFunction(arguments.length === 2 ? options : callback, "callback"); + + throw new ERR_INVALID_ARG_TYPE("options", ["integer", "object"], options); + } else { + validateFunction(callback, "callback"); + + if (options?.hints != null) { + validateNumber(options.hints, "options.hints"); + hints = options.hints >>> 0; + validateHints(hints); + } + + if (options?.family != null) { + validateOneOf(options.family, "options.family", validFamilies); + family = options.family; + } + + if (options?.all != null) { + validateBoolean(options.all, "options.all"); + all = options.all; + } + + if (options?.verbatim != null) { + validateBoolean(options.verbatim, "options.verbatim"); + verbatim = options.verbatim; + } + } + + if (!hostname) { + emitInvalidHostnameWarning(hostname); + + if (all) { + nextTick(callback as LookupCallback, null, []); + } else { + nextTick(callback as LookupCallback, null, null, family === 6 ? 6 : 4); + } + + return {}; + } + + const matchedFamily = isIP(hostname); + + if (matchedFamily) { + if (all) { + nextTick(callback as LookupCallback, null, [ + { address: hostname, family: matchedFamily }, + ]); + } else { + nextTick(callback as LookupCallback, null, hostname, matchedFamily); + } + + return {}; + } + + const req = new GetAddrInfoReqWrap(); + req.callback = callback as LookupCallback; + req.family = family; + req.hostname = hostname; + req.oncomplete = all ? onlookupall : onlookup; + + const err = getaddrinfo(req, toASCII(hostname), family, hints, verbatim); + + if (err) { + nextTick( + callback as LookupCallback, + dnsException(err, "getaddrinfo", hostname), + ); + + return {}; + } + + return req; +} + +Object.defineProperty(lookup, customPromisifyArgs, { + value: ["address", "family"], + enumerable: false, +}); + +function onresolve( + this: QueryReqWrap, + err: number, + records: Records, + ttls?: number[], +) { + if (err) { + this.callback(dnsException(err, this.bindingName, this.hostname)); + + return; + } + + const parsedRecords = ttls && this.ttl + ? (records as string[]).map((address: string, index: number) => ({ + address, + ttl: ttls[index], + })) + : records; + + this.callback(null, parsedRecords); +} + +function resolver(bindingName: keyof ChannelWrapQuery) { + function query( + this: Resolver, + name: string, + options: unknown, + callback?: unknown, + ): QueryReqWrap { + if (isResolveCallback(options)) { + callback = options; + options = {}; + } + + validateString(name, "name"); + validateFunction(callback, "callback"); + + const req = new QueryReqWrap(); + req.bindingName = bindingName; + req.callback = callback as ResolveCallback; + req.hostname = name; + req.oncomplete = onresolve; + + if (options && (options as ResolveOptions).ttl) { + notImplemented("dns.resolve* with ttl option"); + } + + req.ttl = !!(options && (options as ResolveOptions).ttl); + + const err = this._handle[bindingName](req, toASCII(name)); + + if (err) { + throw dnsException(err, bindingName, name); + } + + return req; + } + + Object.defineProperty(query, "name", { value: bindingName }); + + return query; +} + +const resolveMap = Object.create(null); + +export class Resolver extends CallbackResolver { + constructor(options?: ResolverOptions) { + super(options); + } + + // deno-lint-ignore no-explicit-any + [resolveMethod: string]: any; +} + +Resolver.prototype.resolveAny = resolveMap.ANY = resolver("queryAny"); +Resolver.prototype.resolve4 = resolveMap.A = resolver("queryA"); +Resolver.prototype.resolve6 = resolveMap.AAAA = resolver("queryAaaa"); +Resolver.prototype.resolveCaa = resolveMap.CAA = resolver("queryCaa"); +Resolver.prototype.resolveCname = resolveMap.CNAME = resolver("queryCname"); +Resolver.prototype.resolveMx = resolveMap.MX = resolver("queryMx"); +Resolver.prototype.resolveNs = resolveMap.NS = resolver("queryNs"); +Resolver.prototype.resolveTxt = resolveMap.TXT = resolver("queryTxt"); +Resolver.prototype.resolveSrv = resolveMap.SRV = resolver("querySrv"); +Resolver.prototype.resolvePtr = resolveMap.PTR = resolver("queryPtr"); +Resolver.prototype.resolveNaptr = resolveMap.NAPTR = resolver("queryNaptr"); +Resolver.prototype.resolveSoa = resolveMap.SOA = resolver("querySoa"); +Resolver.prototype.reverse = resolver("getHostByAddr"); +Resolver.prototype.resolve = _resolve; + +function _resolve( + this: Resolver, + hostname: string, + rrtype: unknown, + callback?: unknown, +): QueryReqWrap { + let resolver: Resolver; + + if (typeof hostname !== "string") { + throw new ERR_INVALID_ARG_TYPE("name", "string", hostname); + } + + if (typeof rrtype === "string") { + resolver = resolveMap[rrtype]; + } else if (typeof rrtype === "function") { + resolver = resolveMap.A; + callback = rrtype; + } else { + throw new ERR_INVALID_ARG_TYPE("rrtype", "string", rrtype); + } + + if (typeof resolver === "function") { + return Reflect.apply(resolver, this, [hostname, callback]); + } + + throw new ERR_INVALID_ARG_VALUE("rrtype", rrtype); +} + +/** + * Sets the IP address and port of servers to be used when performing DNS + * resolution. The `servers` argument is an array of [RFC 5952](https://tools.ietf.org/html/rfc5952#section-6) formatted + * addresses. If the port is the IANA default DNS port (53) it can be omitted. + * + * ```js + * dns.setServers([ + * '4.4.4.4', + * '[2001:4860:4860::8888]', + * '4.4.4.4:1053', + * '[2001:4860:4860::8888]:1053', + * ]); + * ``` + * + * An error will be thrown if an invalid address is provided. + * + * The `dns.setServers()` method must not be called while a DNS query is in + * progress. + * + * The `setServers` method affects only `resolve`,`dns.resolve*()` and `reverse` (and specifically _not_ `lookup`). + * + * This method works much like [resolve.conf](https://man7.org/linux/man-pages/man5/resolv.conf.5.html). + * That is, if attempting to resolve with the first server provided results in a + * `NOTFOUND` error, the `resolve()` method will _not_ attempt to resolve with + * subsequent servers provided. Fallback DNS servers will only be used if the + * earlier ones time out or result in some other error. + * + * @param servers array of `RFC 5952` formatted addresses + */ +export function setServers(servers: ReadonlyArray) { + const resolver = new Resolver(); + + resolver.setServers(servers); + setDefaultResolver(resolver); +} + +// The Node implementation uses `bindDefaultResolver` to set the follow methods +// on `module.exports` bound to the current `defaultResolver`. We don't have +// the same ability in ESM but can simulate this (at some cost) by explicitly +// exporting these methods which dynamically bind to the default resolver when +// called. + +/** + * Returns an array of IP address strings, formatted according to [RFC 5952](https://tools.ietf.org/html/rfc5952#section-6), + * that are currently configured for DNS resolution. A string will include a port + * section if a custom port is used. + * + * ```js + * [ + * '4.4.4.4', + * '2001:4860:4860::8888', + * '4.4.4.4:1053', + * '[2001:4860:4860::8888]:1053', + * ] + * ``` + */ +export function getServers(): string[] { + return Resolver.prototype.getServers.bind(getDefaultResolver())(); +} + +/** + * Uses the DNS protocol to resolve all records (also known as `ANY` or `*` query). + * The `ret` argument passed to the `callback` function will be an array containing + * various types of records. Each object has a property `type` that indicates the + * type of the current record. And depending on the `type`, additional properties + * will be present on the object. + * + * Here is an example of the `ret` object passed to the callback: + * + * ```js + * [ { type: 'A', address: '127.0.0.1', ttl: 299 }, + * { type: 'CNAME', value: 'example.com' }, + * { type: 'MX', exchange: 'alt4.aspmx.l.example.com', priority: 50 }, + * { type: 'NS', value: 'ns1.example.com' }, + * { type: 'TXT', entries: [ 'v=spf1 include:_spf.example.com ~all' ] }, + * { type: 'SOA', + * nsname: 'ns1.example.com', + * hostmaster: 'admin.example.com', + * serial: 156696742, + * refresh: 900, + * retry: 900, + * expire: 1800, + * minttl: 60 } ] + * ``` + * + * DNS server operators may choose not to respond to `ANY` queries. It may be + * better to call individual methods like `resolve4`, `resolveMx`, and so on. + * For more details, see [RFC 8482](https://tools.ietf.org/html/rfc8482). + */ +export function resolveAny( + hostname: string, + callback: (err: ErrnoException | null, addresses: AnyRecord[]) => void, +): QueryReqWrap; +export function resolveAny(...args: unknown[]): QueryReqWrap { + return Resolver.prototype.resolveAny.bind(getDefaultResolver() as Resolver)( + ...args, + ); +} + +/** + * Uses the DNS protocol to resolve a IPv4 addresses (`A` records) for the + * `hostname`. The `addresses` argument passed to the `callback` function will + * contain an array of IPv4 addresses (e.g. `['74.125.79.104', '74.125.79.105','74.125.79.106']`). + * + * @param hostname Host name to resolve. + */ +export function resolve4( + hostname: string, + callback: (err: ErrnoException | null, addresses: string[]) => void, +): void; +export function resolve4( + hostname: string, + options: ResolveWithTtlOptions, + callback: (err: ErrnoException | null, addresses: RecordWithTtl[]) => void, +): void; +export function resolve4( + hostname: string, + options: ResolveOptions, + callback: ( + err: ErrnoException | null, + addresses: string[] | RecordWithTtl[], + ) => void, +): void; +export function resolve4( + hostname: string, + options: unknown, + callback?: unknown, +) { + return Resolver.prototype.resolve4.bind(getDefaultResolver() as Resolver)( + hostname, + options, + callback, + ); +} + +/** + * Uses the DNS protocol to resolve a IPv6 addresses (`AAAA` records) for the + * `hostname`. The `addresses` argument passed to the `callback` function + * will contain an array of IPv6 addresses. + * + * @param hostname Host name to resolve. + */ +export function resolve6( + hostname: string, + callback: (err: ErrnoException | null, addresses: string[]) => void, +): void; +export function resolve6( + hostname: string, + options: ResolveWithTtlOptions, + callback: (err: ErrnoException | null, addresses: RecordWithTtl[]) => void, +): void; +export function resolve6( + hostname: string, + options: ResolveOptions, + callback: ( + err: ErrnoException | null, + addresses: string[] | RecordWithTtl[], + ) => void, +): void; +export function resolve6( + hostname: string, + options: unknown, + callback?: unknown, +) { + return Resolver.prototype.resolve6.bind(getDefaultResolver() as Resolver)( + hostname, + options, + callback, + ); +} + +/** + * Uses the DNS protocol to resolve `CAA` records for the `hostname`. The + * `addresses` argument passed to the `callback` function will contain an array + * of certification authority authorization records available for the + * `hostname` (e.g. `[{critical: 0, iodef: 'mailto:pki@example.com'}, {critical: 128, issue: 'pki.example.com'}]`). + */ +export function resolveCaa( + hostname: string, + callback: (err: ErrnoException | null, records: CaaRecord[]) => void, +): QueryReqWrap; +export function resolveCaa(...args: unknown[]): QueryReqWrap { + return Resolver.prototype.resolveCaa.bind(getDefaultResolver() as Resolver)( + ...args, + ); +} + +/** + * Uses the DNS protocol to resolve `CNAME` records for the `hostname`. The + * `addresses` argument passed to the `callback` function will contain an array + * of canonical name records available for the `hostname`(e.g. `['bar.example.com']`). + */ +export function resolveCname( + hostname: string, + callback: (err: ErrnoException | null, addresses: string[]) => void, +): QueryReqWrap; +export function resolveCname(...args: unknown[]): QueryReqWrap { + return Resolver.prototype.resolveCname.bind(getDefaultResolver() as Resolver)( + ...args, + ); +} + +/** + * Uses the DNS protocol to resolve mail exchange records (`MX` records) for the + * `hostname`. The `addresses` argument passed to the `callback` function will + * contain an array of objects containing both a `priority` and `exchange` + * property (e.g. `[{priority: 10, exchange: 'mx.example.com'}, ...]`). + */ +export function resolveMx( + hostname: string, + callback: (err: ErrnoException | null, addresses: MxRecord[]) => void, +): QueryReqWrap; +export function resolveMx(...args: unknown[]): QueryReqWrap { + return Resolver.prototype.resolveMx.bind(getDefaultResolver() as Resolver)( + ...args, + ); +} + +/** + * Uses the DNS protocol to resolve name server records (`NS` records) for the + * `hostname`. The `addresses` argument passed to the `callback` function will + * contain an array of name server records available for `hostname` + * (e.g. `['ns1.example.com', 'ns2.example.com']`). + */ +export function resolveNs( + hostname: string, + callback: (err: ErrnoException | null, addresses: string[]) => void, +): QueryReqWrap; +export function resolveNs(...args: unknown[]): QueryReqWrap { + return Resolver.prototype.resolveNs.bind(getDefaultResolver() as Resolver)( + ...args, + ); +} + +/** + * Uses the DNS protocol to resolve text queries (`TXT` records) for the + * `hostname`. The `records` argument passed to the `callback` function is a + * two-dimensional array of the text records available for `hostname` + * (e.g.`[ ['v=spf1 ip4:0.0.0.0 ', '~all' ] ]`). Each sub-array contains TXT + * chunks of one record. Depending on the use case, these could be either + * joined together or treated separately. + */ +export function resolveTxt( + hostname: string, + callback: (err: ErrnoException | null, addresses: string[][]) => void, +): QueryReqWrap; +export function resolveTxt(...args: unknown[]): QueryReqWrap { + return Resolver.prototype.resolveTxt.bind(getDefaultResolver() as Resolver)( + ...args, + ); +} + +/** + * Uses the DNS protocol to resolve service records (`SRV` records) for the + * `hostname`. The `addresses` argument passed to the `callback` function will + * be an array of objects with the following properties: + * + * - `priority` + * - `weight` + * - `port` + * - `name` + * + * ```js + * { + * priority: 10, + * weight: 5, + * port: 21223, + * name: 'service.example.com' + * } + * ``` + */ +export function resolveSrv( + hostname: string, + callback: (err: ErrnoException | null, addresses: SrvRecord[]) => void, +): QueryReqWrap; +export function resolveSrv(...args: unknown[]): QueryReqWrap { + return Resolver.prototype.resolveSrv.bind(getDefaultResolver() as Resolver)( + ...args, + ); +} + +/** + * Uses the DNS protocol to resolve pointer records (`PTR` records) for the + * `hostname`. The `addresses` argument passed to the `callback` function will + * be an array of strings containing the reply records. + */ +export function resolvePtr( + hostname: string, + callback: (err: ErrnoException | null, addresses: string[]) => void, +): QueryReqWrap; +export function resolvePtr(...args: unknown[]): QueryReqWrap { + return Resolver.prototype.resolvePtr.bind(getDefaultResolver() as Resolver)( + ...args, + ); +} + +/** + * Uses the DNS protocol to resolve regular expression based records (`NAPTR` + * records) for the `hostname`. The `addresses` argument passed to the + * `callback` function will contain an array of objects with the following + * properties: + * + * - `flags` + * - `service` + * - `regexp` + * - `replacement` + * - `order` + * - `preference` + * + * ```js + * { + * flags: 's', + * service: 'SIP+D2U', + * regexp: '', + * replacement: '_sip._udp.example.com', + * order: 30, + * preference: 100 + * } + * ``` + */ +export function resolveNaptr( + hostname: string, + callback: (err: ErrnoException | null, addresses: NaptrRecord[]) => void, +): QueryReqWrap; +export function resolveNaptr(...args: unknown[]): QueryReqWrap { + return Resolver.prototype.resolveNaptr.bind(getDefaultResolver() as Resolver)( + ...args, + ); +} + +/** + * Uses the DNS protocol to resolve a start of authority record (`SOA` record) for + * the `hostname`. The `address` argument passed to the `callback` function will + * be an object with the following properties: + * + * - `nsname` + * - `hostmaster` + * - `serial` + * - `refresh` + * - `retry` + * - `expire` + * - `minttl` + * + * ```js + * { + * nsname: 'ns.example.com', + * hostmaster: 'root.example.com', + * serial: 2013101809, + * refresh: 10000, + * retry: 2400, + * expire: 604800, + * minttl: 3600 + * } + * ``` + */ +export function resolveSoa( + hostname: string, + callback: (err: ErrnoException | null, address: SoaRecord) => void, +): QueryReqWrap; +export function resolveSoa(...args: unknown[]): QueryReqWrap { + return Resolver.prototype.resolveSoa.bind(getDefaultResolver() as Resolver)( + ...args, + ); +} + +/** + * Performs a reverse DNS query that resolves an IPv4 or IPv6 address to an + * array of host names. + * + * On error, `err` is an `Error` object, where `err.code` is + * one of the `DNS error codes`. + */ +export function reverse( + ip: string, + callback: (err: ErrnoException | null, hostnames: string[]) => void, +): QueryReqWrap; +export function reverse(...args: unknown[]): QueryReqWrap { + return Resolver.prototype.reverse.bind(getDefaultResolver() as Resolver)( + ...args, + ); +} + +/** + * Uses the DNS protocol to resolve a host name (e.g. `'nodejs.org'`) into an array + * of the resource records. The `callback` function has arguments`(err, records)`.] + * When successful, `records` will be an array of resource + * records. The type and structure of individual results varies based on `rrtype`. + * + * On error, `err` is an `Error` object, where `err.code` is one of the DNS error codes. + * + * @param hostname Host name to resolve. + * @param [rrtype='A'] Resource record type. + */ +export function resolve( + hostname: string, + callback: (err: ErrnoException | null, addresses: string[]) => void, +): QueryReqWrap; +export function resolve( + hostname: string, + rrtype: "A", + callback: (err: ErrnoException | null, addresses: string[]) => void, +): QueryReqWrap; +export function resolve( + hostname: string, + rrtype: "AAAA", + callback: (err: ErrnoException | null, addresses: string[]) => void, +): QueryReqWrap; +export function resolve( + hostname: string, + rrtype: "ANY", + callback: (err: ErrnoException | null, addresses: AnyRecord[]) => void, +): QueryReqWrap; +export function resolve( + hostname: string, + rrtype: "CNAME", + callback: (err: ErrnoException | null, addresses: string[]) => void, +): QueryReqWrap; +export function resolve( + hostname: string, + rrtype: "MX", + callback: (err: ErrnoException | null, addresses: MxRecord[]) => void, +): QueryReqWrap; +export function resolve( + hostname: string, + rrtype: "NAPTR", + callback: (err: ErrnoException | null, addresses: NaptrRecord[]) => void, +): QueryReqWrap; +export function resolve( + hostname: string, + rrtype: "NS", + callback: (err: ErrnoException | null, addresses: string[]) => void, +): QueryReqWrap; +export function resolve( + hostname: string, + rrtype: "PTR", + callback: (err: ErrnoException | null, addresses: string[]) => void, +): QueryReqWrap; +export function resolve( + hostname: string, + rrtype: "SOA", + callback: (err: ErrnoException | null, addresses: SoaRecord) => void, +): QueryReqWrap; +export function resolve( + hostname: string, + rrtype: "SRV", + callback: (err: ErrnoException | null, addresses: SrvRecord[]) => void, +): QueryReqWrap; +export function resolve( + hostname: string, + rrtype: "TXT", + callback: (err: ErrnoException | null, addresses: string[][]) => void, +): QueryReqWrap; +export function resolve( + hostname: string, + rrtype: string, + callback: ( + err: ErrnoException | null, + addresses: + | string[] + | MxRecord[] + | NaptrRecord[] + | SoaRecord + | SrvRecord[] + | string[][] + | AnyRecord[], + ) => void, +): QueryReqWrap; +export function resolve(hostname: string, rrtype: unknown, callback?: unknown) { + return Resolver.prototype.resolve.bind(getDefaultResolver() as Resolver)( + hostname, + rrtype, + callback, + ); +} + +// ERROR CODES +export const NODATA = "ENODATA"; +export const FORMERR = "EFORMERR"; +export const SERVFAIL = "ESERVFAIL"; +export const NOTFOUND = "ENOTFOUND"; +export const NOTIMP = "ENOTIMP"; +export const REFUSED = "EREFUSED"; +export const BADQUERY = "EBADQUERY"; +export const BADNAME = "EBADNAME"; +export const BADFAMILY = "EBADFAMILY"; +export const BADRESP = "EBADRESP"; +export const CONNREFUSED = "ECONNREFUSED"; +export const TIMEOUT = "ETIMEOUT"; +export const EOF = "EOF"; +export const FILE = "EFILE"; +export const NOMEM = "ENOMEM"; +export const DESTRUCTION = "EDESTRUCTION"; +export const BADSTR = "EBADSTR"; +export const BADFLAGS = "EBADFLAGS"; +export const NONAME = "ENONAME"; +export const BADHINTS = "EBADHINTS"; +export const NOTINITIALIZED = "ENOTINITIALIZED"; +export const LOADIPHLPAPI = "ELOADIPHLPAPI"; +export const ADDRGETNETWORKPARAMS = "EADDRGETNETWORKPARAMS"; +export const CANCELLED = "ECANCELLED"; + +const promises = { + ...promisesBase, + setDefaultResultOrder, + setServers, + + // ERROR CODES + NODATA, + FORMERR, + SERVFAIL, + NOTFOUND, + NOTIMP, + REFUSED, + BADQUERY, + BADNAME, + BADFAMILY, + BADRESP, + CONNREFUSED, + TIMEOUT, + EOF, + FILE, + NOMEM, + DESTRUCTION, + BADSTR, + BADFLAGS, + NONAME, + BADHINTS, + NOTINITIALIZED, + LOADIPHLPAPI, + ADDRGETNETWORKPARAMS, + CANCELLED, +}; + +export { ADDRCONFIG, ALL, promises, setDefaultResultOrder, V4MAPPED }; + +export type { + AnyAaaaRecord, + AnyARecord, + AnyCnameRecord, + AnyMxRecord, + AnyNaptrRecord, + AnyNsRecord, + AnyPtrRecord, + AnyRecord, + AnySoaRecord, + AnySrvRecord, + AnyTxtRecord, + CaaRecord, + LookupAddress, + LookupAllOptions, + LookupOneOptions, + LookupOptions, + MxRecord, + NaptrRecord, + Records, + RecordWithTtl, + ResolveCallback, + ResolveOptions, + ResolverOptions, + ResolveWithTtlOptions, + SoaRecord, + SrvRecord, +}; + +export default { + ADDRCONFIG, + ALL, + V4MAPPED, + lookup, + getServers, + resolveAny, + resolve4, + resolve6, + resolveCaa, + resolveCname, + resolveMx, + resolveNs, + resolveTxt, + resolveSrv, + resolvePtr, + resolveNaptr, + resolveSoa, + resolve, + Resolver, + reverse, + setServers, + setDefaultResultOrder, + promises, + NODATA, + FORMERR, + SERVFAIL, + NOTFOUND, + NOTIMP, + REFUSED, + BADQUERY, + BADNAME, + BADFAMILY, + BADRESP, + CONNREFUSED, + TIMEOUT, + EOF, + FILE, + NOMEM, + DESTRUCTION, + BADSTR, + BADFLAGS, + NONAME, + BADHINTS, + NOTINITIALIZED, + LOADIPHLPAPI, + ADDRGETNETWORKPARAMS, + CANCELLED, +}; diff --git a/ext/node/polyfills/dns/promises.ts b/ext/node/polyfills/dns/promises.ts new file mode 100644 index 00000000000000..0de6a488bff14c --- /dev/null +++ b/ext/node/polyfills/dns/promises.ts @@ -0,0 +1,70 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. +// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. +import { promises } from "internal:deno_node/polyfills/dns.ts"; +export const { + getServers, + lookup, + resolve, + resolve4, + resolve6, + resolveAny, + resolveCaa, + resolveCname, + resolveMx, + resolveNaptr, + resolveNs, + resolvePtr, + Resolver, + resolveSoa, + resolveSrv, + resolveTxt, + reverse, + setDefaultResultOrder, + setServers, + + // ERROR CODES + NODATA, + FORMERR, + SERVFAIL, + NOTFOUND, + NOTIMP, + REFUSED, + BADQUERY, + BADNAME, + BADFAMILY, + BADRESP, + CONNREFUSED, + TIMEOUT, + EOF, + FILE, + NOMEM, + DESTRUCTION, + BADSTR, + BADFLAGS, + NONAME, + BADHINTS, + NOTINITIALIZED, + LOADIPHLPAPI, + ADDRGETNETWORKPARAMS, + CANCELLED, +} = promises; +export default promises; diff --git a/ext/node/polyfills/domain.ts b/ext/node/polyfills/domain.ts new file mode 100644 index 00000000000000..ea0aed728d6f47 --- /dev/null +++ b/ext/node/polyfills/domain.ts @@ -0,0 +1,17 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. +// Copyright Joyent and Node contributors. All rights reserved. MIT license. + +import { notImplemented } from "internal:deno_node/polyfills/_utils.ts"; + +export function create() { + notImplemented("domain.create"); +} +export class Domain { + constructor() { + notImplemented("domain.Domain.prototype.constructor"); + } +} +export default { + create, + Domain, +}; diff --git a/ext/node/polyfills/events.ts b/ext/node/polyfills/events.ts new file mode 100644 index 00000000000000..5b43faa101b113 --- /dev/null +++ b/ext/node/polyfills/events.ts @@ -0,0 +1,14 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. +// @deno-types="./_events.d.ts" +export { + captureRejectionSymbol, + default, + defaultMaxListeners, + errorMonitor, + EventEmitter, + getEventListeners, + listenerCount, + on, + once, + setMaxListeners, +} from "internal:deno_node/polyfills/_events.mjs"; diff --git a/ext/node/polyfills/fs.ts b/ext/node/polyfills/fs.ts new file mode 100644 index 00000000000000..b67d2facdf865c --- /dev/null +++ b/ext/node/polyfills/fs.ts @@ -0,0 +1,431 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. +import { + access, + accessPromise, + accessSync, +} from "internal:deno_node/polyfills/_fs/_fs_access.ts"; +import { + appendFile, + appendFilePromise, + appendFileSync, +} from "internal:deno_node/polyfills/_fs/_fs_appendFile.ts"; +import { + chmod, + chmodPromise, + chmodSync, +} from "internal:deno_node/polyfills/_fs/_fs_chmod.ts"; +import { + chown, + chownPromise, + chownSync, +} from "internal:deno_node/polyfills/_fs/_fs_chown.ts"; +import { + close, + closeSync, +} from "internal:deno_node/polyfills/_fs/_fs_close.ts"; +import * as constants from "internal:deno_node/polyfills/_fs/_fs_constants.ts"; +import { + copyFile, + copyFilePromise, + copyFileSync, +} from "internal:deno_node/polyfills/_fs/_fs_copy.ts"; +import Dir from "internal:deno_node/polyfills/_fs/_fs_dir.ts"; +import Dirent from "internal:deno_node/polyfills/_fs/_fs_dirent.ts"; +import { + exists, + existsSync, +} from "internal:deno_node/polyfills/_fs/_fs_exists.ts"; +import { + fdatasync, + fdatasyncSync, +} from "internal:deno_node/polyfills/_fs/_fs_fdatasync.ts"; +import { + fstat, + fstatSync, +} from "internal:deno_node/polyfills/_fs/_fs_fstat.ts"; +import { + fsync, + fsyncSync, +} from "internal:deno_node/polyfills/_fs/_fs_fsync.ts"; +import { + ftruncate, + ftruncateSync, +} from "internal:deno_node/polyfills/_fs/_fs_ftruncate.ts"; +import { + futimes, + futimesSync, +} from "internal:deno_node/polyfills/_fs/_fs_futimes.ts"; +import { + link, + linkPromise, + linkSync, +} from "internal:deno_node/polyfills/_fs/_fs_link.ts"; +import { + lstat, + lstatPromise, + lstatSync, +} from "internal:deno_node/polyfills/_fs/_fs_lstat.ts"; +import { + mkdir, + mkdirPromise, + mkdirSync, +} from "internal:deno_node/polyfills/_fs/_fs_mkdir.ts"; +import { + mkdtemp, + mkdtempPromise, + mkdtempSync, +} from "internal:deno_node/polyfills/_fs/_fs_mkdtemp.ts"; +import { + open, + openPromise, + openSync, +} from "internal:deno_node/polyfills/_fs/_fs_open.ts"; +import { + opendir, + opendirPromise, + opendirSync, +} from "internal:deno_node/polyfills/_fs/_fs_opendir.ts"; +import { read, readSync } from "internal:deno_node/polyfills/_fs/_fs_read.ts"; +import { + readdir, + readdirPromise, + readdirSync, +} from "internal:deno_node/polyfills/_fs/_fs_readdir.ts"; +import { + readFile, + readFilePromise, + readFileSync, +} from "internal:deno_node/polyfills/_fs/_fs_readFile.ts"; +import { + readlink, + readlinkPromise, + readlinkSync, +} from "internal:deno_node/polyfills/_fs/_fs_readlink.ts"; +import { + realpath, + realpathPromise, + realpathSync, +} from "internal:deno_node/polyfills/_fs/_fs_realpath.ts"; +import { + rename, + renamePromise, + renameSync, +} from "internal:deno_node/polyfills/_fs/_fs_rename.ts"; +import { + rmdir, + rmdirPromise, + rmdirSync, +} from "internal:deno_node/polyfills/_fs/_fs_rmdir.ts"; +import { + rm, + rmPromise, + rmSync, +} from "internal:deno_node/polyfills/_fs/_fs_rm.ts"; +import { + stat, + statPromise, + statSync, +} from "internal:deno_node/polyfills/_fs/_fs_stat.ts"; +import { + symlink, + symlinkPromise, + symlinkSync, +} from "internal:deno_node/polyfills/_fs/_fs_symlink.ts"; +import { + truncate, + truncatePromise, + truncateSync, +} from "internal:deno_node/polyfills/_fs/_fs_truncate.ts"; +import { + unlink, + unlinkPromise, + unlinkSync, +} from "internal:deno_node/polyfills/_fs/_fs_unlink.ts"; +import { + utimes, + utimesPromise, + utimesSync, +} from "internal:deno_node/polyfills/_fs/_fs_utimes.ts"; +import { + unwatchFile, + watch, + watchFile, + watchPromise, +} from "internal:deno_node/polyfills/_fs/_fs_watch.ts"; +// @deno-types="./_fs/_fs_write.d.ts" +import { + write, + writeSync, +} from "internal:deno_node/polyfills/_fs/_fs_write.mjs"; +// @deno-types="./_fs/_fs_writev.d.ts" +import { + writev, + writevSync, +} from "internal:deno_node/polyfills/_fs/_fs_writev.mjs"; +import { + writeFile, + writeFilePromise, + writeFileSync, +} from "internal:deno_node/polyfills/_fs/_fs_writeFile.ts"; +import { Stats } from "internal:deno_node/polyfills/internal/fs/utils.mjs"; +// @deno-types="./internal/fs/streams.d.ts" +import { + createReadStream, + createWriteStream, + ReadStream, + WriteStream, +} from "internal:deno_node/polyfills/internal/fs/streams.mjs"; + +const { + F_OK, + R_OK, + W_OK, + X_OK, + O_RDONLY, + O_WRONLY, + O_RDWR, + O_NOCTTY, + O_TRUNC, + O_APPEND, + O_DIRECTORY, + O_NOFOLLOW, + O_SYNC, + O_DSYNC, + O_SYMLINK, + O_NONBLOCK, + O_CREAT, + O_EXCL, +} = constants; + +const promises = { + access: accessPromise, + copyFile: copyFilePromise, + open: openPromise, + opendir: opendirPromise, + rename: renamePromise, + truncate: truncatePromise, + rm: rmPromise, + rmdir: rmdirPromise, + mkdir: mkdirPromise, + readdir: readdirPromise, + readlink: readlinkPromise, + symlink: symlinkPromise, + lstat: lstatPromise, + stat: statPromise, + link: linkPromise, + unlink: unlinkPromise, + chmod: chmodPromise, + // lchmod: promisify(lchmod), + // lchown: promisify(lchown), + chown: chownPromise, + utimes: utimesPromise, + // lutimes = promisify(lutimes), + realpath: realpathPromise, + mkdtemp: mkdtempPromise, + writeFile: writeFilePromise, + appendFile: appendFilePromise, + readFile: readFilePromise, + watch: watchPromise, +}; + +export default { + access, + accessSync, + appendFile, + appendFileSync, + chmod, + chmodSync, + chown, + chownSync, + close, + closeSync, + constants, + copyFile, + copyFileSync, + createReadStream, + createWriteStream, + Dir, + Dirent, + exists, + existsSync, + F_OK, + fdatasync, + fdatasyncSync, + fstat, + fstatSync, + fsync, + fsyncSync, + ftruncate, + ftruncateSync, + futimes, + futimesSync, + link, + linkSync, + lstat, + lstatSync, + mkdir, + mkdirSync, + mkdtemp, + mkdtempSync, + O_APPEND, + O_CREAT, + O_DIRECTORY, + O_DSYNC, + O_EXCL, + O_NOCTTY, + O_NOFOLLOW, + O_NONBLOCK, + O_RDONLY, + O_RDWR, + O_SYMLINK, + O_SYNC, + O_TRUNC, + O_WRONLY, + open, + openSync, + opendir, + opendirSync, + read, + readSync, + promises, + R_OK, + readdir, + readdirSync, + readFile, + readFileSync, + readlink, + readlinkSync, + ReadStream, + realpath, + realpathSync, + rename, + renameSync, + rmdir, + rmdirSync, + rm, + rmSync, + stat, + Stats, + statSync, + symlink, + symlinkSync, + truncate, + truncateSync, + unlink, + unlinkSync, + unwatchFile, + utimes, + utimesSync, + W_OK, + watch, + watchFile, + write, + writeFile, + writev, + writevSync, + writeFileSync, + WriteStream, + writeSync, + X_OK, +}; + +export { + access, + accessSync, + appendFile, + appendFileSync, + chmod, + chmodSync, + chown, + chownSync, + close, + closeSync, + constants, + copyFile, + copyFileSync, + createReadStream, + createWriteStream, + Dir, + Dirent, + exists, + existsSync, + F_OK, + fdatasync, + fdatasyncSync, + fstat, + fstatSync, + fsync, + fsyncSync, + ftruncate, + ftruncateSync, + futimes, + futimesSync, + link, + linkSync, + lstat, + lstatSync, + mkdir, + mkdirSync, + mkdtemp, + mkdtempSync, + O_APPEND, + O_CREAT, + O_DIRECTORY, + O_DSYNC, + O_EXCL, + O_NOCTTY, + O_NOFOLLOW, + O_NONBLOCK, + O_RDONLY, + O_RDWR, + O_SYMLINK, + O_SYNC, + O_TRUNC, + O_WRONLY, + open, + opendir, + opendirSync, + openSync, + promises, + R_OK, + read, + readdir, + readdirSync, + readFile, + readFileSync, + readlink, + readlinkSync, + ReadStream, + readSync, + realpath, + realpathSync, + rename, + renameSync, + rm, + rmdir, + rmdirSync, + rmSync, + stat, + Stats, + statSync, + symlink, + symlinkSync, + truncate, + truncateSync, + unlink, + unlinkSync, + unwatchFile, + utimes, + utimesSync, + W_OK, + watch, + watchFile, + write, + writeFile, + writeFileSync, + WriteStream, + writeSync, + writev, + writevSync, + X_OK, +}; diff --git a/ext/node/polyfills/fs/promises.ts b/ext/node/polyfills/fs/promises.ts new file mode 100644 index 00000000000000..2f4687661d4531 --- /dev/null +++ b/ext/node/polyfills/fs/promises.ts @@ -0,0 +1,33 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. +import { promises as fsPromises } from "internal:deno_node/polyfills/fs.ts"; + +export const access = fsPromises.access; +export const copyFile = fsPromises.copyFile; +export const open = fsPromises.open; +export const opendir = fsPromises.opendir; +export const rename = fsPromises.rename; +export const truncate = fsPromises.truncate; +export const rm = fsPromises.rm; +export const rmdir = fsPromises.rmdir; +export const mkdir = fsPromises.mkdir; +export const readdir = fsPromises.readdir; +export const readlink = fsPromises.readlink; +export const symlink = fsPromises.symlink; +export const lstat = fsPromises.lstat; +export const stat = fsPromises.stat; +export const link = fsPromises.link; +export const unlink = fsPromises.unlink; +export const chmod = fsPromises.chmod; +// export const lchmod = fs.lchmod; +// export const lchown = fs.lchown; +export const chown = fsPromises.chown; +export const utimes = fsPromises.utimes; +// export const lutimes = fs.lutimes; +export const realpath = fsPromises.realpath; +export const mkdtemp = fsPromises.mkdtemp; +export const writeFile = fsPromises.writeFile; +export const appendFile = fsPromises.appendFile; +export const readFile = fsPromises.readFile; +export const watch = fsPromises.watch; + +export default fsPromises; diff --git a/ext/node/polyfills/global.ts b/ext/node/polyfills/global.ts new file mode 100644 index 00000000000000..f9cb9186f0b5b4 --- /dev/null +++ b/ext/node/polyfills/global.ts @@ -0,0 +1,91 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. +// deno-lint-ignore-file no-var +import processModule from "internal:deno_node/polyfills/process.ts"; +import { Buffer as bufferModule } from "internal:deno_node/polyfills/buffer.ts"; +import { + clearInterval, + clearTimeout, + setInterval, + setTimeout, +} from "internal:deno_node/polyfills/timers.ts"; +import timers from "internal:deno_node/polyfills/timers.ts"; + +type GlobalType = { + process: typeof processModule; + Buffer: typeof bufferModule; + setImmediate: typeof timers.setImmediate; + clearImmediate: typeof timers.clearImmediate; + setTimeout: typeof timers.setTimeout; + clearTimeout: typeof timers.clearTimeout; + setInterval: typeof timers.setInterval; + clearInterval: typeof timers.clearInterval; +}; + +declare global { + interface Window { + global: GlobalType; + } + + interface globalThis { + global: GlobalType; + } + + var global: GlobalType; + var process: typeof processModule; + var Buffer: typeof bufferModule; + type Buffer = bufferModule; + var setImmediate: typeof timers.setImmediate; + var clearImmediate: typeof timers.clearImmediate; +} + +Object.defineProperty(globalThis, "global", { + value: new Proxy(globalThis, { + get(target, prop, receiver) { + switch (prop) { + case "setInterval": + return setInterval; + case "setTimeout": + return setTimeout; + case "clearInterval": + return clearInterval; + case "clearTimeout": + return clearTimeout; + default: + return Reflect.get(target, prop, receiver); + } + }, + }), + writable: false, + enumerable: false, + configurable: true, +}); + +Object.defineProperty(globalThis, "process", { + value: processModule, + enumerable: false, + writable: true, + configurable: true, +}); + +Object.defineProperty(globalThis, "Buffer", { + value: bufferModule, + enumerable: false, + writable: true, + configurable: true, +}); + +Object.defineProperty(globalThis, "setImmediate", { + value: timers.setImmediate, + enumerable: true, + writable: true, + configurable: true, +}); + +Object.defineProperty(globalThis, "clearImmediate", { + value: timers.clearImmediate, + enumerable: true, + writable: true, + configurable: true, +}); + +export {}; diff --git a/ext/node/polyfills/http.ts b/ext/node/polyfills/http.ts new file mode 100644 index 00000000000000..4bfc1e1d3f27dc --- /dev/null +++ b/ext/node/polyfills/http.ts @@ -0,0 +1,940 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. + +import { TextEncoder } from "internal:deno_web/08_text_encoding.js"; +import { + type Deferred, + deferred, +} from "internal:deno_node/polyfills/_util/async.ts"; +import { + _normalizeArgs, + ListenOptions, + Socket, +} from "internal:deno_node/polyfills/net.ts"; +import { Buffer } from "internal:deno_node/polyfills/buffer.ts"; +import { ERR_SERVER_NOT_RUNNING } from "internal:deno_node/polyfills/internal/errors.ts"; +import { EventEmitter } from "internal:deno_node/polyfills/events.ts"; +import { nextTick } from "internal:deno_node/polyfills/_next_tick.ts"; +import { validatePort } from "internal:deno_node/polyfills/internal/validators.mjs"; +import { + Readable as NodeReadable, + Writable as NodeWritable, +} from "internal:deno_node/polyfills/stream.ts"; +import { OutgoingMessage } from "internal:deno_node/polyfills/_http_outgoing.ts"; +import { Agent } from "internal:deno_node/polyfills/_http_agent.mjs"; +import { chunkExpression as RE_TE_CHUNKED } from "internal:deno_node/polyfills/_http_common.ts"; +import { urlToHttpOptions } from "internal:deno_node/polyfills/internal/url.ts"; +import { + constants, + TCP, +} from "internal:deno_node/polyfills/internal_binding/tcp_wrap.ts"; + +enum STATUS_CODES { + /** RFC 7231, 6.2.1 */ + Continue = 100, + /** RFC 7231, 6.2.2 */ + SwitchingProtocols = 101, + /** RFC 2518, 10.1 */ + Processing = 102, + /** RFC 8297 **/ + EarlyHints = 103, + + /** RFC 7231, 6.3.1 */ + OK = 200, + /** RFC 7231, 6.3.2 */ + Created = 201, + /** RFC 7231, 6.3.3 */ + Accepted = 202, + /** RFC 7231, 6.3.4 */ + NonAuthoritativeInfo = 203, + /** RFC 7231, 6.3.5 */ + NoContent = 204, + /** RFC 7231, 6.3.6 */ + ResetContent = 205, + /** RFC 7233, 4.1 */ + PartialContent = 206, + /** RFC 4918, 11.1 */ + MultiStatus = 207, + /** RFC 5842, 7.1 */ + AlreadyReported = 208, + /** RFC 3229, 10.4.1 */ + IMUsed = 226, + + /** RFC 7231, 6.4.1 */ + MultipleChoices = 300, + /** RFC 7231, 6.4.2 */ + MovedPermanently = 301, + /** RFC 7231, 6.4.3 */ + Found = 302, + /** RFC 7231, 6.4.4 */ + SeeOther = 303, + /** RFC 7232, 4.1 */ + NotModified = 304, + /** RFC 7231, 6.4.5 */ + UseProxy = 305, + /** RFC 7231, 6.4.7 */ + TemporaryRedirect = 307, + /** RFC 7538, 3 */ + PermanentRedirect = 308, + + /** RFC 7231, 6.5.1 */ + BadRequest = 400, + /** RFC 7235, 3.1 */ + Unauthorized = 401, + /** RFC 7231, 6.5.2 */ + PaymentRequired = 402, + /** RFC 7231, 6.5.3 */ + Forbidden = 403, + /** RFC 7231, 6.5.4 */ + NotFound = 404, + /** RFC 7231, 6.5.5 */ + MethodNotAllowed = 405, + /** RFC 7231, 6.5.6 */ + NotAcceptable = 406, + /** RFC 7235, 3.2 */ + ProxyAuthRequired = 407, + /** RFC 7231, 6.5.7 */ + RequestTimeout = 408, + /** RFC 7231, 6.5.8 */ + Conflict = 409, + /** RFC 7231, 6.5.9 */ + Gone = 410, + /** RFC 7231, 6.5.10 */ + LengthRequired = 411, + /** RFC 7232, 4.2 */ + PreconditionFailed = 412, + /** RFC 7231, 6.5.11 */ + RequestEntityTooLarge = 413, + /** RFC 7231, 6.5.12 */ + RequestURITooLong = 414, + /** RFC 7231, 6.5.13 */ + UnsupportedMediaType = 415, + /** RFC 7233, 4.4 */ + RequestedRangeNotSatisfiable = 416, + /** RFC 7231, 6.5.14 */ + ExpectationFailed = 417, + /** RFC 7168, 2.3.3 */ + Teapot = 418, + /** RFC 7540, 9.1.2 */ + MisdirectedRequest = 421, + /** RFC 4918, 11.2 */ + UnprocessableEntity = 422, + /** RFC 4918, 11.3 */ + Locked = 423, + /** RFC 4918, 11.4 */ + FailedDependency = 424, + /** RFC 8470, 5.2 */ + TooEarly = 425, + /** RFC 7231, 6.5.15 */ + UpgradeRequired = 426, + /** RFC 6585, 3 */ + PreconditionRequired = 428, + /** RFC 6585, 4 */ + TooManyRequests = 429, + /** RFC 6585, 5 */ + RequestHeaderFieldsTooLarge = 431, + /** RFC 7725, 3 */ + UnavailableForLegalReasons = 451, + + /** RFC 7231, 6.6.1 */ + InternalServerError = 500, + /** RFC 7231, 6.6.2 */ + NotImplemented = 501, + /** RFC 7231, 6.6.3 */ + BadGateway = 502, + /** RFC 7231, 6.6.4 */ + ServiceUnavailable = 503, + /** RFC 7231, 6.6.5 */ + GatewayTimeout = 504, + /** RFC 7231, 6.6.6 */ + HTTPVersionNotSupported = 505, + /** RFC 2295, 8.1 */ + VariantAlsoNegotiates = 506, + /** RFC 4918, 11.5 */ + InsufficientStorage = 507, + /** RFC 5842, 7.2 */ + LoopDetected = 508, + /** RFC 2774, 7 */ + NotExtended = 510, + /** RFC 6585, 6 */ + NetworkAuthenticationRequired = 511, +} + +const METHODS = [ + "ACL", + "BIND", + "CHECKOUT", + "CONNECT", + "COPY", + "DELETE", + "GET", + "HEAD", + "LINK", + "LOCK", + "M-SEARCH", + "MERGE", + "MKACTIVITY", + "MKCALENDAR", + "MKCOL", + "MOVE", + "NOTIFY", + "OPTIONS", + "PATCH", + "POST", + "PROPFIND", + "PROPPATCH", + "PURGE", + "PUT", + "REBIND", + "REPORT", + "SEARCH", + "SOURCE", + "SUBSCRIBE", + "TRACE", + "UNBIND", + "UNLINK", + "UNLOCK", + "UNSUBSCRIBE", +]; + +type Chunk = string | Buffer | Uint8Array; + +// @ts-ignore Deno[Deno.internal] is used on purpose here +const DenoServe = Deno[Deno.internal]?.nodeUnstable?.serve || Deno.serve; +// @ts-ignore Deno[Deno.internal] is used on purpose here +const DenoUpgradeHttpRaw = Deno[Deno.internal]?.nodeUnstable?.upgradeHttpRaw || + Deno.upgradeHttpRaw; + +const ENCODER = new TextEncoder(); + +export interface RequestOptions { + agent?: Agent; + auth?: string; + createConnection?: () => unknown; + defaultPort?: number; + family?: number; + headers?: Record; + hints?: number; + host?: string; + hostname?: string; + insecureHTTPParser?: boolean; + localAddress?: string; + localPort?: number; + lookup?: () => void; + maxHeaderSize?: number; + method?: string; + path?: string; + port?: number; + protocol?: string; + setHost?: boolean; + socketPath?: string; + timeout?: number; + signal?: AbortSignal; + href?: string; +} + +// TODO(@bartlomieju): Implement ClientRequest methods (e.g. setHeader()) +/** ClientRequest represents the http(s) request from the client */ +class ClientRequest extends NodeWritable { + defaultProtocol = "http:"; + body: null | ReadableStream = null; + controller: ReadableStreamDefaultController | null = null; + constructor( + public opts: RequestOptions, + public cb?: (res: IncomingMessageForClient) => void, + ) { + super(); + } + + // deno-lint-ignore no-explicit-any + override _write(chunk: any, _enc: string, cb: () => void) { + if (this.controller) { + this.controller.enqueue(chunk); + cb(); + return; + } + + this.body = new ReadableStream({ + start: (controller) => { + this.controller = controller; + controller.enqueue(chunk); + cb(); + }, + }); + } + + override async _final() { + if (this.controller) { + this.controller.close(); + } + + const body = await this._createBody(this.body, this.opts); + const client = await this._createCustomClient(); + const opts = { + body, + method: this.opts.method, + client, + headers: this.opts.headers, + }; + const mayResponse = fetch(this._createUrlStrFromOptions(this.opts), opts) + .catch((e) => { + if (e.message.includes("connection closed before message completed")) { + // Node.js seems ignoring this error + } else { + this.emit("error", e); + } + return undefined; + }); + const res = new IncomingMessageForClient( + await mayResponse, + this._createSocket(), + ); + this.emit("response", res); + if (client) { + res.on("end", () => { + client.close(); + }); + } + this.cb?.(res); + } + + abort() { + this.destroy(); + } + + async _createBody( + body: ReadableStream | null, + opts: RequestOptions, + ): Promise { + if (!body) return null; + if (!opts.headers) return body; + + const headers = Object.fromEntries( + Object.entries(opts.headers).map(([k, v]) => [k.toLowerCase(), v]), + ); + + if ( + !RE_TE_CHUNKED.test(headers["transfer-encoding"]) && + !Number.isNaN(Number.parseInt(headers["content-length"], 10)) + ) { + const bufferList: Buffer[] = []; + for await (const chunk of body) { + bufferList.push(chunk); + } + return Buffer.concat(bufferList); + } + + return body; + } + + _createCustomClient(): Promise { + return Promise.resolve(undefined); + } + + _createSocket(): Socket { + // Note: Creates a dummy socket for the compatibility + // Sometimes the libraries check some properties of socket + // e.g. if (!response.socket.authorized) { ... } + return new Socket({}); + } + + _createUrlStrFromOptions(opts: RequestOptions): string { + if (opts.href) { + return opts.href; + } + const protocol = opts.protocol ?? this.defaultProtocol; + const auth = opts.auth; + const host = opts.host ?? opts.hostname ?? "localhost"; + const defaultPort = opts.agent?.defaultPort; + const port = opts.port ?? defaultPort ?? 80; + let path = opts.path ?? "/"; + if (!path.startsWith("/")) { + path = "/" + path; + } + return `${protocol}//${auth ? `${auth}@` : ""}${host}${ + port === 80 ? "" : `:${port}` + }${path}`; + } + + setTimeout() { + console.log("not implemented: ClientRequest.setTimeout"); + } +} + +/** IncomingMessage for http(s) client */ +export class IncomingMessageForClient extends NodeReadable { + reader: ReadableStreamDefaultReader | undefined; + #statusMessage = ""; + constructor(public response: Response | undefined, public socket: Socket) { + super(); + this.reader = response?.body?.getReader(); + } + + override async _read(_size: number) { + if (this.reader === undefined) { + this.push(null); + return; + } + try { + const res = await this.reader.read(); + if (res.done) { + this.push(null); + return; + } + this.push(res.value); + } catch (e) { + // deno-lint-ignore no-explicit-any + this.destroy(e as any); + } + } + + get headers() { + if (this.response) { + return Object.fromEntries(this.response.headers.entries()); + } + return {}; + } + + get trailers() { + return {}; + } + + get statusCode() { + return this.response?.status || 0; + } + + get statusMessage() { + return this.#statusMessage || this.response?.statusText || ""; + } + + set statusMessage(v: string) { + this.#statusMessage = v; + } +} + +export class ServerResponse extends NodeWritable { + statusCode?: number = undefined; + statusMessage?: string = undefined; + #headers = new Headers({}); + #readable: ReadableStream; + override writable = true; + // used by `npm:on-finished` + finished = false; + headersSent = false; + #firstChunk: Chunk | null = null; + // Used if --unstable flag IS NOT present + #reqEvent?: Deno.RequestEvent; + // Used if --unstable flag IS present + #resolve?: (value: Response | PromiseLike) => void; + #isFlashRequest: boolean; + + static #enqueue(controller: ReadableStreamDefaultController, chunk: Chunk) { + // TODO(kt3k): This is a workaround for denoland/deno#17194 + // This if-block should be removed when the above issue is resolved. + if (chunk.length === 0) { + return; + } + if (typeof chunk === "string") { + controller.enqueue(ENCODER.encode(chunk)); + } else { + controller.enqueue(chunk); + } + } + + /** Returns true if the response body should be null with the given + * http status code */ + static #bodyShouldBeNull(status: number) { + return status === 101 || status === 204 || status === 205 || status === 304; + } + + constructor( + reqEvent: undefined | Deno.RequestEvent, + resolve: undefined | ((value: Response | PromiseLike) => void), + ) { + let controller: ReadableByteStreamController; + const readable = new ReadableStream({ + start(c) { + controller = c as ReadableByteStreamController; + }, + }); + super({ + autoDestroy: true, + defaultEncoding: "utf-8", + emitClose: true, + write: (chunk, _encoding, cb) => { + if (!this.headersSent) { + if (this.#firstChunk === null) { + this.#firstChunk = chunk; + return cb(); + } else { + ServerResponse.#enqueue(controller, this.#firstChunk); + this.#firstChunk = null; + this.respond(false); + } + } + ServerResponse.#enqueue(controller, chunk); + return cb(); + }, + final: (cb) => { + if (this.#firstChunk) { + this.respond(true, this.#firstChunk); + } else if (!this.headersSent) { + this.respond(true); + } + controller.close(); + return cb(); + }, + destroy: (err, cb) => { + if (err) { + controller.error(err); + } + return cb(null); + }, + }); + this.#readable = readable; + this.#resolve = resolve; + this.#reqEvent = reqEvent; + this.#isFlashRequest = typeof resolve !== "undefined"; + } + + setHeader(name: string, value: string) { + this.#headers.set(name, value); + return this; + } + + getHeader(name: string) { + return this.#headers.get(name); + } + removeHeader(name: string) { + return this.#headers.delete(name); + } + getHeaderNames() { + return Array.from(this.#headers.keys()); + } + hasHeader(name: string) { + return this.#headers.has(name); + } + + writeHead(status: number, headers: Record) { + this.statusCode = status; + for (const k in headers) { + if (Object.hasOwn(headers, k)) { + this.#headers.set(k, headers[k]); + } + } + return this; + } + + #ensureHeaders(singleChunk?: Chunk) { + if (this.statusCode === undefined) { + this.statusCode = 200; + this.statusMessage = "OK"; + } + // Only taken if --unstable IS NOT present + if ( + !this.#isFlashRequest && typeof singleChunk === "string" && + !this.hasHeader("content-type") + ) { + this.setHeader("content-type", "text/plain;charset=UTF-8"); + } + } + + respond(final: boolean, singleChunk?: Chunk) { + this.headersSent = true; + this.#ensureHeaders(singleChunk); + let body = singleChunk ?? (final ? null : this.#readable); + if (ServerResponse.#bodyShouldBeNull(this.statusCode!)) { + body = null; + } + if (this.#isFlashRequest) { + this.#resolve!( + new Response(body, { + headers: this.#headers, + status: this.statusCode, + statusText: this.statusMessage, + }), + ); + } else { + this.#reqEvent!.respondWith( + new Response(body, { + headers: this.#headers, + status: this.statusCode, + statusText: this.statusMessage, + }), + ).catch(() => { + // ignore this error + }); + } + } + + // deno-lint-ignore no-explicit-any + override end(chunk?: any, encoding?: any, cb?: any): this { + this.finished = true; + if (this.#isFlashRequest) { + // Flash sets both of these headers. + this.#headers.delete("transfer-encoding"); + this.#headers.delete("content-length"); + } else if (!chunk && this.#headers.has("transfer-encoding")) { + // FIXME(bnoordhuis) Node sends a zero length chunked body instead, i.e., + // the trailing "0\r\n", but respondWith() just hangs when I try that. + this.#headers.set("content-length", "0"); + this.#headers.delete("transfer-encoding"); + } + + // @ts-expect-error The signature for cb is stricter than the one implemented here + return super.end(chunk, encoding, cb); + } +} + +// TODO(@AaronO): optimize +export class IncomingMessageForServer extends NodeReadable { + #req: Request; + url: string; + method: string; + + constructor(req: Request) { + // Check if no body (GET/HEAD/OPTIONS/...) + const reader = req.body?.getReader(); + super({ + autoDestroy: true, + emitClose: true, + objectMode: false, + read: async function (_size) { + if (!reader) { + return this.push(null); + } + + try { + const { value } = await reader!.read(); + this.push(value !== undefined ? Buffer.from(value) : null); + } catch (err) { + this.destroy(err as Error); + } + }, + destroy: (err, cb) => { + reader?.cancel().finally(() => cb(err)); + }, + }); + // TODO(@bartlomieju): consider more robust path extraction, e.g: + // url: (new URL(request.url).pathname), + this.url = req.url?.slice(req.url.indexOf("/", 8)); + this.method = req.method; + this.#req = req; + } + + get aborted() { + return false; + } + + get httpVersion() { + return "1.1"; + } + + get headers() { + return Object.fromEntries(this.#req.headers.entries()); + } + + get upgrade(): boolean { + return Boolean( + this.#req.headers.get("connection")?.toLowerCase().includes("upgrade") && + this.#req.headers.get("upgrade"), + ); + } +} + +type ServerHandler = ( + req: IncomingMessageForServer, + res: ServerResponse, +) => void; + +export function Server(handler?: ServerHandler): ServerImpl { + return new ServerImpl(handler); +} + +class ServerImpl extends EventEmitter { + #isFlashServer: boolean; + + #httpConnections: Set = new Set(); + #listener?: Deno.Listener; + + #addr?: Deno.NetAddr; + #hasClosed = false; + #ac?: AbortController; + #servePromise?: Deferred; + listening = false; + + constructor(handler?: ServerHandler) { + super(); + // @ts-ignore Might be undefined without `--unstable` flag + this.#isFlashServer = typeof DenoServe == "function"; + if (this.#isFlashServer) { + this.#servePromise = deferred(); + this.#servePromise.then(() => this.emit("close")); + } + if (handler !== undefined) { + this.on("request", handler); + } + } + + listen(...args: unknown[]): this { + // TODO(bnoordhuis) Delegate to net.Server#listen(). + const normalized = _normalizeArgs(args); + const options = normalized[0] as Partial; + const cb = normalized[1]; + + if (cb !== null) { + // @ts-ignore change EventEmitter's sig to use CallableFunction + this.once("listening", cb); + } + + let port = 0; + if (typeof options.port === "number" || typeof options.port === "string") { + validatePort(options.port, "options.port"); + port = options.port | 0; + } + + // TODO(bnoordhuis) Node prefers [::] when host is omitted, + // we on the other hand default to 0.0.0.0. + if (this.#isFlashServer) { + const hostname = options.host ?? "0.0.0.0"; + this.#addr = { + hostname, + port, + } as Deno.NetAddr; + this.listening = true; + nextTick(() => this.#serve()); + } else { + this.listening = true; + const hostname = options.host ?? ""; + this.#listener = Deno.listen({ port, hostname }); + nextTick(() => this.#listenLoop()); + } + + return this; + } + + async #listenLoop() { + const go = async (httpConn: Deno.HttpConn) => { + try { + for (;;) { + let reqEvent = null; + try { + // Note: httpConn.nextRequest() calls httpConn.close() on error. + reqEvent = await httpConn.nextRequest(); + } catch { + // Connection closed. + // TODO(bnoordhuis) Emit "clientError" event on the http.Server + // instance? Node emits it when request parsing fails and expects + // the listener to send a raw 4xx HTTP response on the underlying + // net.Socket but we don't have one to pass to the listener. + } + if (reqEvent === null) { + break; + } + const req = new IncomingMessageForServer(reqEvent.request); + const res = new ServerResponse(reqEvent, undefined); + this.emit("request", req, res); + } + } finally { + this.#httpConnections.delete(httpConn); + } + }; + + const listener = this.#listener; + + if (listener !== undefined) { + this.emit("listening"); + + for await (const conn of listener) { + let httpConn: Deno.HttpConn; + try { + httpConn = Deno.serveHttp(conn); + } catch { + continue; /// Connection closed. + } + + this.#httpConnections.add(httpConn); + go(httpConn); + } + } + } + + #serve() { + const ac = new AbortController(); + const handler = (request: Request) => { + const req = new IncomingMessageForServer(request); + if (req.upgrade && this.listenerCount("upgrade") > 0) { + const [conn, head] = DenoUpgradeHttpRaw(request) as [ + Deno.Conn, + Uint8Array, + ]; + const socket = new Socket({ + handle: new TCP(constants.SERVER, conn), + }); + this.emit("upgrade", req, socket, Buffer.from(head)); + } else { + return new Promise((resolve): void => { + const res = new ServerResponse(undefined, resolve); + this.emit("request", req, res); + }); + } + }; + + if (this.#hasClosed) { + return; + } + this.#ac = ac; + DenoServe( + { + handler: handler as Deno.ServeHandler, + ...this.#addr, + signal: ac.signal, + // @ts-ignore Might be any without `--unstable` flag + onListen: ({ port }) => { + this.#addr!.port = port; + this.emit("listening"); + }, + }, + ).then(() => this.#servePromise!.resolve()); + } + + setTimeout() { + console.error("Not implemented: Server.setTimeout()"); + } + + close(cb?: (err?: Error) => void): this { + const listening = this.listening; + this.listening = false; + + this.#hasClosed = true; + if (typeof cb === "function") { + if (listening) { + this.once("close", cb); + } else { + this.once("close", function close() { + cb(new ERR_SERVER_NOT_RUNNING()); + }); + } + } + + if (this.#isFlashServer) { + if (listening && this.#ac) { + this.#ac.abort(); + this.#ac = undefined; + } else { + this.#servePromise!.resolve(); + } + } else { + nextTick(() => this.emit("close")); + + if (listening) { + this.#listener!.close(); + this.#listener = undefined; + + for (const httpConn of this.#httpConnections) { + try { + httpConn.close(); + } catch { + // Already closed. + } + } + + this.#httpConnections.clear(); + } + } + + return this; + } + + address() { + let addr; + if (this.#isFlashServer) { + addr = this.#addr!; + } else { + addr = this.#listener!.addr as Deno.NetAddr; + } + return { + port: addr.port, + address: addr.hostname, + }; + } +} + +Server.prototype = ServerImpl.prototype; + +export function createServer(handler?: ServerHandler) { + return Server(handler); +} + +/** Makes an HTTP request. */ +export function request( + url: string | URL, + cb?: (res: IncomingMessageForClient) => void, +): ClientRequest; +export function request( + opts: RequestOptions, + cb?: (res: IncomingMessageForClient) => void, +): ClientRequest; +export function request( + url: string | URL, + opts: RequestOptions, + cb?: (res: IncomingMessageForClient) => void, +): ClientRequest; +// deno-lint-ignore no-explicit-any +export function request(...args: any[]) { + let options = {}; + if (typeof args[0] === "string") { + options = urlToHttpOptions(new URL(args.shift())); + } else if (args[0] instanceof URL) { + options = urlToHttpOptions(args.shift()); + } + if (args[0] && typeof args[0] !== "function") { + Object.assign(options, args.shift()); + } + args.unshift(options); + return new ClientRequest(args[0], args[1]); +} + +/** Makes a `GET` HTTP request. */ +export function get( + url: string | URL, + cb?: (res: IncomingMessageForClient) => void, +): ClientRequest; +export function get( + opts: RequestOptions, + cb?: (res: IncomingMessageForClient) => void, +): ClientRequest; +export function get( + url: string | URL, + opts: RequestOptions, + cb?: (res: IncomingMessageForClient) => void, +): ClientRequest; +// deno-lint-ignore no-explicit-any +export function get(...args: any[]) { + const req = request(args[0], args[1], args[2]); + req.end(); + return req; +} + +export { + Agent, + ClientRequest, + IncomingMessageForServer as IncomingMessage, + METHODS, + OutgoingMessage, + STATUS_CODES, +}; +export default { + Agent, + ClientRequest, + STATUS_CODES, + METHODS, + createServer, + Server, + IncomingMessage: IncomingMessageForServer, + IncomingMessageForClient, + IncomingMessageForServer, + OutgoingMessage, + ServerResponse, + request, + get, +}; diff --git a/ext/node/polyfills/http2.ts b/ext/node/polyfills/http2.ts new file mode 100644 index 00000000000000..e5eb1725a1f701 --- /dev/null +++ b/ext/node/polyfills/http2.ts @@ -0,0 +1,83 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. +// Copyright Joyent and Node contributors. All rights reserved. MIT license. + +import { notImplemented } from "internal:deno_node/polyfills/_utils.ts"; + +export class Http2Session { + constructor() { + notImplemented("Http2Session.prototype.constructor"); + } +} +export class ServerHttp2Session { + constructor() { + notImplemented("ServerHttp2Session"); + } +} +export class ClientHttp2Session { + constructor() { + notImplemented("ClientHttp2Session"); + } +} +export class Http2Stream { + constructor() { + notImplemented("Http2Stream"); + } +} +export class ClientHttp2Stream { + constructor() { + notImplemented("ClientHttp2Stream"); + } +} +export class ServerHttp2Stream { + constructor() { + notImplemented("ServerHttp2Stream"); + } +} +export class Http2Server { + constructor() { + notImplemented("Http2Server"); + } +} +export class Http2SecureServer { + constructor() { + notImplemented("Http2SecureServer"); + } +} +export function createServer() {} +export function createSecureServer() {} +export function connect() {} +export const constants = {}; +export function getDefaultSettings() {} +export function getPackedSettings() {} +export function getUnpackedSettings() {} +export const sensitiveHeaders = Symbol("nodejs.http2.sensitiveHeaders"); +export class Http2ServerRequest { + constructor() { + notImplemented("Http2ServerRequest"); + } +} +export class Http2ServerResponse { + constructor() { + notImplemented("Http2ServerResponse"); + } +} +export default { + Http2Session, + ServerHttp2Session, + ClientHttp2Session, + Http2Stream, + ClientHttp2Stream, + ServerHttp2Stream, + Http2Server, + Http2SecureServer, + createServer, + createSecureServer, + connect, + constants, + getDefaultSettings, + getPackedSettings, + getUnpackedSettings, + sensitiveHeaders, + Http2ServerRequest, + Http2ServerResponse, +}; diff --git a/ext/node/polyfills/https.ts b/ext/node/polyfills/https.ts new file mode 100644 index 00000000000000..da77b46e241c43 --- /dev/null +++ b/ext/node/polyfills/https.ts @@ -0,0 +1,128 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. +// Copyright Joyent and Node contributors. All rights reserved. MIT license. + +import { notImplemented } from "internal:deno_node/polyfills/_utils.ts"; +import { urlToHttpOptions } from "internal:deno_node/polyfills/internal/url.ts"; +import { + Agent as HttpAgent, + ClientRequest, + IncomingMessageForClient as IncomingMessage, + type RequestOptions, +} from "internal:deno_node/polyfills/http.ts"; +import type { Socket } from "internal:deno_node/polyfills/net.ts"; + +export class Agent extends HttpAgent { +} + +export class Server { + constructor() { + notImplemented("https.Server.prototype.constructor"); + } +} +export function createServer() { + notImplemented("https.createServer"); +} + +interface HttpsRequestOptions extends RequestOptions { + _: unknown; +} + +// Store additional root CAs. +// undefined means NODE_EXTRA_CA_CERTS is not checked yet. +// null means there's no additional root CAs. +let caCerts: string[] | undefined | null; + +/** Makes a request to an https server. */ +export function get( + url: string | URL, + cb?: (res: IncomingMessage) => void, +): HttpsClientRequest; +export function get( + opts: HttpsRequestOptions, + cb?: (res: IncomingMessage) => void, +): HttpsClientRequest; +export function get( + url: string | URL, + opts: HttpsRequestOptions, + cb?: (res: IncomingMessage) => void, +): HttpsClientRequest; +// deno-lint-ignore no-explicit-any +export function get(...args: any[]) { + const req = request(args[0], args[1], args[2]); + req.end(); + return req; +} + +export const globalAgent = undefined; +/** HttpsClientRequest class loosely follows http.ClientRequest class API. */ +class HttpsClientRequest extends ClientRequest { + override defaultProtocol = "https:"; + override async _createCustomClient(): Promise< + Deno.HttpClient | undefined + > { + if (caCerts === null) { + return undefined; + } + if (caCerts !== undefined) { + return Deno.createHttpClient({ caCerts }); + } + const status = await Deno.permissions.query({ + name: "env", + variable: "NODE_EXTRA_CA_CERTS", + }); + if (status.state !== "granted") { + caCerts = null; + return undefined; + } + const certFilename = Deno.env.get("NODE_EXTRA_CA_CERTS"); + if (!certFilename) { + caCerts = null; + return undefined; + } + const caCert = await Deno.readTextFile(certFilename); + caCerts = [caCert]; + return Deno.createHttpClient({ caCerts }); + } + + override _createSocket(): Socket { + // deno-lint-ignore no-explicit-any + return { authorized: true } as any; + } +} + +/** Makes a request to an https server. */ +export function request( + url: string | URL, + cb?: (res: IncomingMessage) => void, +): HttpsClientRequest; +export function request( + opts: HttpsRequestOptions, + cb?: (res: IncomingMessage) => void, +): HttpsClientRequest; +export function request( + url: string | URL, + opts: HttpsRequestOptions, + cb?: (res: IncomingMessage) => void, +): HttpsClientRequest; +// deno-lint-ignore no-explicit-any +export function request(...args: any[]) { + let options = {}; + if (typeof args[0] === "string") { + options = urlToHttpOptions(new URL(args.shift())); + } else if (args[0] instanceof URL) { + options = urlToHttpOptions(args.shift()); + } + if (args[0] && typeof args[0] !== "function") { + Object.assign(options, args.shift()); + } + args.unshift(options); + return new HttpsClientRequest(args[0], args[1]); +} +export default { + Agent, + Server, + createServer, + get, + globalAgent, + request, +}; diff --git a/ext/node/polyfills/inspector.ts b/ext/node/polyfills/inspector.ts new file mode 100644 index 00000000000000..e946d7605ec882 --- /dev/null +++ b/ext/node/polyfills/inspector.ts @@ -0,0 +1,91 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. +// Copyright Joyent and Node contributors. All rights reserved. MIT license. + +import { EventEmitter } from "internal:deno_node/polyfills/events.ts"; +import { notImplemented } from "internal:deno_node/polyfills/_utils.ts"; + +const connectionSymbol = Symbol("connectionProperty"); +const messageCallbacksSymbol = Symbol("messageCallbacks"); +const nextIdSymbol = Symbol("nextId"); +const onMessageSymbol = Symbol("onMessage"); + +class Session extends EventEmitter { + [connectionSymbol]: null; + [nextIdSymbol]: number; + [messageCallbacksSymbol]: Map void>; + + constructor() { + super(); + notImplemented("inspector.Session.prototype.constructor"); + } + + /** Connects the session to the inspector back-end. */ + connect() { + notImplemented("inspector.Session.prototype.connect"); + } + + /** Connects the session to the main thread + * inspector back-end. */ + connectToMainThread() { + notImplemented("inspector.Session.prototype.connectToMainThread"); + } + + [onMessageSymbol](_message: string) { + notImplemented("inspector.Session.prototype[Symbol('onMessage')]"); + } + + /** Posts a message to the inspector back-end. */ + post( + _method: string, + _params?: Record, + _callback?: (...args: unknown[]) => void, + ) { + notImplemented("inspector.Session.prototype.post"); + } + + /** Immediately closes the session, all pending + * message callbacks will be called with an + * error. + */ + disconnect() { + notImplemented("inspector.Session.prototype.disconnect"); + } +} + +/** Activates inspector on host and port. + * See https://nodejs.org/api/inspector.html#inspectoropenport-host-wait */ +function open(_port?: number, _host?: string, _wait?: boolean) { + notImplemented("inspector.Session.prototype.open"); +} + +/** Deactivate the inspector. Blocks until there are no active connections. + * See https://nodejs.org/api/inspector.html#inspectorclose */ +function close() { + notImplemented("inspector.Session.prototype.close"); +} + +/** Return the URL of the active inspector, or undefined if there is none. + * See https://nodejs.org/api/inspector.html#inspectorurl */ +function url() { + // TODO(kt3k): returns undefined for now, which means the inspector is not activated. + return undefined; +} + +/** Blocks until a client (existing or connected later) has sent Runtime.runIfWaitingForDebugger command. + * See https://nodejs.org/api/inspector.html#inspectorwaitfordebugger */ +function waitForDebugger() { + notImplemented("inspector.wairForDebugger"); +} + +const console = globalThis.console; + +export { close, console, open, Session, url, waitForDebugger }; + +export default { + close, + console, + open, + Session, + url, + waitForDebugger, +}; diff --git a/ext/node/polyfills/internal/assert.mjs b/ext/node/polyfills/internal/assert.mjs new file mode 100644 index 00000000000000..fcdb32a056d027 --- /dev/null +++ b/ext/node/polyfills/internal/assert.mjs @@ -0,0 +1,16 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. +import { ERR_INTERNAL_ASSERTION } from "internal:deno_node/polyfills/internal/errors.ts"; + +function assert(value, message) { + if (!value) { + throw new ERR_INTERNAL_ASSERTION(message); + } +} + +function fail(message) { + throw new ERR_INTERNAL_ASSERTION(message); +} + +assert.fail = fail; + +export default assert; diff --git a/ext/node/polyfills/internal/async_hooks.ts b/ext/node/polyfills/internal/async_hooks.ts new file mode 100644 index 00000000000000..8bf513e468b77b --- /dev/null +++ b/ext/node/polyfills/internal/async_hooks.ts @@ -0,0 +1,420 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. +// Copyright Joyent and Node contributors. All rights reserved. MIT license. + +// deno-lint-ignore camelcase +import * as async_wrap from "internal:deno_node/polyfills/internal_binding/async_wrap.ts"; +import { ERR_ASYNC_CALLBACK } from "internal:deno_node/polyfills/internal/errors.ts"; +export { + asyncIdSymbol, + ownerSymbol, +} from "internal:deno_node/polyfills/internal_binding/symbols.ts"; + +interface ActiveHooks { + array: AsyncHook[]; + // deno-lint-ignore camelcase + call_depth: number; + // deno-lint-ignore camelcase + tmp_array: AsyncHook[] | null; + // deno-lint-ignore camelcase + tmp_fields: number[] | null; +} + +// Properties in active_hooks are used to keep track of the set of hooks being +// executed in case another hook is enabled/disabled. The new set of hooks is +// then restored once the active set of hooks is finished executing. +// deno-lint-ignore camelcase +const active_hooks: ActiveHooks = { + // Array of all AsyncHooks that will be iterated whenever an async event + // fires. Using var instead of (preferably const) in order to assign + // active_hooks.tmp_array if a hook is enabled/disabled during hook + // execution. + array: [], + // Use a counter to track nested calls of async hook callbacks and make sure + // the active_hooks.array isn't altered mid execution. + // deno-lint-ignore camelcase + call_depth: 0, + // Use to temporarily store and updated active_hooks.array if the user + // enables or disables a hook while hooks are being processed. If a hook is + // enabled() or disabled() during hook execution then the current set of + // active hooks is duplicated and set equal to active_hooks.tmp_array. Any + // subsequent changes are on the duplicated array. When all hooks have + // completed executing active_hooks.tmp_array is assigned to + // active_hooks.array. + // deno-lint-ignore camelcase + tmp_array: null, + // Keep track of the field counts held in active_hooks.tmp_array. Because the + // async_hook_fields can't be reassigned, store each uint32 in an array that + // is written back to async_hook_fields when active_hooks.array is restored. + // deno-lint-ignore camelcase + tmp_fields: null, +}; + +export const registerDestroyHook = async_wrap.registerDestroyHook; +const { + // deno-lint-ignore camelcase + async_hook_fields, + // deno-lint-ignore camelcase + asyncIdFields: async_id_fields, + newAsyncId, + constants, +} = async_wrap; +export { newAsyncId }; +const { + kInit, + kBefore, + kAfter, + kDestroy, + kPromiseResolve, + kTotals, + kCheck, + kDefaultTriggerAsyncId, + kStackLength, +} = constants; + +// deno-lint-ignore camelcase +const resource_symbol = Symbol("resource"); +// deno-lint-ignore camelcase +export const async_id_symbol = Symbol("trigger_async_id"); +// deno-lint-ignore camelcase +export const trigger_async_id_symbol = Symbol("trigger_async_id"); +// deno-lint-ignore camelcase +export const init_symbol = Symbol("init"); +// deno-lint-ignore camelcase +export const before_symbol = Symbol("before"); +// deno-lint-ignore camelcase +export const after_symbol = Symbol("after"); +// deno-lint-ignore camelcase +export const destroy_symbol = Symbol("destroy"); +// deno-lint-ignore camelcase +export const promise_resolve_symbol = Symbol("promiseResolve"); + +export const symbols = { + // deno-lint-ignore camelcase + async_id_symbol, + // deno-lint-ignore camelcase + trigger_async_id_symbol, + // deno-lint-ignore camelcase + init_symbol, + // deno-lint-ignore camelcase + before_symbol, + // deno-lint-ignore camelcase + after_symbol, + // deno-lint-ignore camelcase + destroy_symbol, + // deno-lint-ignore camelcase + promise_resolve_symbol, +}; + +// deno-lint-ignore no-explicit-any +function lookupPublicResource(resource: any) { + if (typeof resource !== "object" || resource === null) return resource; + // TODO(addaleax): Merge this with owner_symbol and use it across all + // AsyncWrap instances. + const publicResource = resource[resource_symbol]; + if (publicResource !== undefined) { + return publicResource; + } + return resource; +} + +// Used by C++ to call all init() callbacks. Because some state can be setup +// from C++ there's no need to perform all the same operations as in +// emitInitScript. +function emitInitNative( + asyncId: number, + // deno-lint-ignore no-explicit-any + type: any, + triggerAsyncId: number, + // deno-lint-ignore no-explicit-any + resource: any, +) { + active_hooks.call_depth += 1; + resource = lookupPublicResource(resource); + // Use a single try/catch for all hooks to avoid setting up one per iteration. + try { + for (let i = 0; i < active_hooks.array.length; i++) { + if (typeof active_hooks.array[i][init_symbol] === "function") { + active_hooks.array[i][init_symbol]( + asyncId, + type, + triggerAsyncId, + resource, + ); + } + } + } catch (e) { + throw e; + } finally { + active_hooks.call_depth -= 1; + } + + // Hooks can only be restored if there have been no recursive hook calls. + // Also the active hooks do not need to be restored if enable()/disable() + // weren't called during hook execution, in which case active_hooks.tmp_array + // will be null. + if (active_hooks.call_depth === 0 && active_hooks.tmp_array !== null) { + restoreActiveHooks(); + } +} + +function getHookArrays(): [AsyncHook[], number[] | Uint32Array] { + if (active_hooks.call_depth === 0) { + return [active_hooks.array, async_hook_fields]; + } + // If this hook is being enabled while in the middle of processing the array + // of currently active hooks then duplicate the current set of active hooks + // and store this there. This shouldn't fire until the next time hooks are + // processed. + if (active_hooks.tmp_array === null) { + storeActiveHooks(); + } + return [active_hooks.tmp_array!, active_hooks.tmp_fields!]; +} + +function storeActiveHooks() { + active_hooks.tmp_array = active_hooks.array.slice(); + // Don't want to make the assumption that kInit to kDestroy are indexes 0 to + // 4. So do this the long way. + active_hooks.tmp_fields = []; + copyHooks(active_hooks.tmp_fields, async_hook_fields); +} + +function copyHooks( + destination: number[] | Uint32Array, + source: number[] | Uint32Array, +) { + destination[kInit] = source[kInit]; + destination[kBefore] = source[kBefore]; + destination[kAfter] = source[kAfter]; + destination[kDestroy] = source[kDestroy]; + destination[kPromiseResolve] = source[kPromiseResolve]; +} + +// Then restore the correct hooks array in case any hooks were added/removed +// during hook callback execution. +function restoreActiveHooks() { + active_hooks.array = active_hooks.tmp_array!; + copyHooks(async_hook_fields, active_hooks.tmp_fields!); + + active_hooks.tmp_array = null; + active_hooks.tmp_fields = null; +} + +// deno-lint-ignore no-unused-vars +let wantPromiseHook = false; +function enableHooks() { + async_hook_fields[kCheck] += 1; + + // TODO(kt3k): Uncomment this + // setCallbackTrampoline(callbackTrampoline); +} + +function disableHooks() { + async_hook_fields[kCheck] -= 1; + + wantPromiseHook = false; + + // TODO(kt3k): Uncomment the below + // setCallbackTrampoline(); + + // Delay the call to `disablePromiseHook()` because we might currently be + // between the `before` and `after` calls of a Promise. + // TODO(kt3k): Uncomment the below + // enqueueMicrotask(disablePromiseHookIfNecessary); +} + +// Return the triggerAsyncId meant for the constructor calling it. It's up to +// the user to safeguard this call and make sure it's zero'd out when the +// constructor is complete. +export function getDefaultTriggerAsyncId() { + const defaultTriggerAsyncId = + async_id_fields[async_wrap.UidFields.kDefaultTriggerAsyncId]; + // If defaultTriggerAsyncId isn't set, use the executionAsyncId + if (defaultTriggerAsyncId < 0) { + return async_id_fields[async_wrap.UidFields.kExecutionAsyncId]; + } + return defaultTriggerAsyncId; +} + +export function defaultTriggerAsyncIdScope( + triggerAsyncId: number | undefined, + // deno-lint-ignore no-explicit-any + block: (...arg: any[]) => void, + ...args: unknown[] +) { + if (triggerAsyncId === undefined) { + return block.apply(null, args); + } + // CHECK(NumberIsSafeInteger(triggerAsyncId)) + // CHECK(triggerAsyncId > 0) + const oldDefaultTriggerAsyncId = async_id_fields[kDefaultTriggerAsyncId]; + async_id_fields[kDefaultTriggerAsyncId] = triggerAsyncId; + + try { + return block.apply(null, args); + } finally { + async_id_fields[kDefaultTriggerAsyncId] = oldDefaultTriggerAsyncId; + } +} + +function hasHooks(key: number) { + return async_hook_fields[key] > 0; +} + +export function enabledHooksExist() { + return hasHooks(kCheck); +} + +export function initHooksExist() { + return hasHooks(kInit); +} + +export function afterHooksExist() { + return hasHooks(kAfter); +} + +export function destroyHooksExist() { + return hasHooks(kDestroy); +} + +export function promiseResolveHooksExist() { + return hasHooks(kPromiseResolve); +} + +function emitInitScript( + asyncId: number, + // deno-lint-ignore no-explicit-any + type: any, + triggerAsyncId: number, + // deno-lint-ignore no-explicit-any + resource: any, +) { + // Short circuit all checks for the common case. Which is that no hooks have + // been set. Do this to remove performance impact for embedders (and core). + if (!hasHooks(kInit)) { + return; + } + + if (triggerAsyncId === null) { + triggerAsyncId = getDefaultTriggerAsyncId(); + } + + emitInitNative(asyncId, type, triggerAsyncId, resource); +} +export { emitInitScript as emitInit }; + +export function hasAsyncIdStack() { + return hasHooks(kStackLength); +} + +export { constants }; + +type Fn = (...args: unknown[]) => unknown; + +export class AsyncHook { + [init_symbol]: Fn; + [before_symbol]: Fn; + [after_symbol]: Fn; + [destroy_symbol]: Fn; + [promise_resolve_symbol]: Fn; + + constructor({ + init, + before, + after, + destroy, + promiseResolve, + }: { + init: Fn; + before: Fn; + after: Fn; + destroy: Fn; + promiseResolve: Fn; + }) { + if (init !== undefined && typeof init !== "function") { + throw new ERR_ASYNC_CALLBACK("hook.init"); + } + if (before !== undefined && typeof before !== "function") { + throw new ERR_ASYNC_CALLBACK("hook.before"); + } + if (after !== undefined && typeof after !== "function") { + throw new ERR_ASYNC_CALLBACK("hook.after"); + } + if (destroy !== undefined && typeof destroy !== "function") { + throw new ERR_ASYNC_CALLBACK("hook.destroy"); + } + if (promiseResolve !== undefined && typeof promiseResolve !== "function") { + throw new ERR_ASYNC_CALLBACK("hook.promiseResolve"); + } + + this[init_symbol] = init; + this[before_symbol] = before; + this[after_symbol] = after; + this[destroy_symbol] = destroy; + this[promise_resolve_symbol] = promiseResolve; + } + + enable() { + // The set of callbacks for a hook should be the same regardless of whether + // enable()/disable() are run during their execution. The following + // references are reassigned to the tmp arrays if a hook is currently being + // processed. + // deno-lint-ignore camelcase + const { 0: hooks_array, 1: hook_fields } = getHookArrays(); + + // Each hook is only allowed to be added once. + if (hooks_array.includes(this)) { + return this; + } + + // deno-lint-ignore camelcase + const prev_kTotals = hook_fields[kTotals]; + + // createHook() has already enforced that the callbacks are all functions, + // so here simply increment the count of whether each callbacks exists or + // not. + hook_fields[kTotals] = hook_fields[kInit] += +!!this[init_symbol]; + hook_fields[kTotals] += hook_fields[kBefore] += +!!this[before_symbol]; + hook_fields[kTotals] += hook_fields[kAfter] += +!!this[after_symbol]; + hook_fields[kTotals] += hook_fields[kDestroy] += +!!this[destroy_symbol]; + hook_fields[kTotals] += hook_fields[kPromiseResolve] += + +!!this[promise_resolve_symbol]; + hooks_array.push(this); + + if (prev_kTotals === 0 && hook_fields[kTotals] > 0) { + enableHooks(); + } + + // TODO(kt3k): Uncomment the below + // updatePromiseHookMode(); + + return this; + } + + disable() { + // deno-lint-ignore camelcase + const { 0: hooks_array, 1: hook_fields } = getHookArrays(); + + const index = hooks_array.indexOf(this); + if (index === -1) { + return this; + } + + // deno-lint-ignore camelcase + const prev_kTotals = hook_fields[kTotals]; + + hook_fields[kTotals] = hook_fields[kInit] -= +!!this[init_symbol]; + hook_fields[kTotals] += hook_fields[kBefore] -= +!!this[before_symbol]; + hook_fields[kTotals] += hook_fields[kAfter] -= +!!this[after_symbol]; + hook_fields[kTotals] += hook_fields[kDestroy] -= +!!this[destroy_symbol]; + hook_fields[kTotals] += hook_fields[kPromiseResolve] -= + +!!this[promise_resolve_symbol]; + hooks_array.splice(index, 1); + + if (prev_kTotals > 0 && hook_fields[kTotals] === 0) { + disableHooks(); + } + + return this; + } +} diff --git a/ext/node/polyfills/internal/blob.mjs b/ext/node/polyfills/internal/blob.mjs new file mode 100644 index 00000000000000..1b685fad40bc76 --- /dev/null +++ b/ext/node/polyfills/internal/blob.mjs @@ -0,0 +1,7 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. +// Node's implementation checks for a symbol they put in the blob prototype +// Since the implementation of Blob is Deno's, the only option is to check the +// objects constructor +export function isBlob(object) { + return object instanceof Blob; +} diff --git a/ext/node/polyfills/internal/buffer.d.ts b/ext/node/polyfills/internal/buffer.d.ts new file mode 100644 index 00000000000000..638674467a1700 --- /dev/null +++ b/ext/node/polyfills/internal/buffer.d.ts @@ -0,0 +1,2074 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. +// Copyright DefinitelyTyped contributors. All rights reserved. MIT license. + +/** + * One of many allowed encodings for the buffer content + * - ascii + * - base64 + * - base64url + * - binary + * - hex + * - latin1 + * - ucs-2 + * - ucs2 + * - utf-8 + * - utf16le + * - utf8 + */ +type Encoding = unknown; + +type WithImplicitCoercion = + | T + | { + valueOf(): T; + }; + +/** + * `Buffer` objects are used to represent a fixed-length sequence of bytes. Many + * Node.js APIs support `Buffer`s. + * + * The `Buffer` class is a subclass of JavaScript's [`Uint8Array`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Uint8Array) class and + * extends it with methods that cover additional use cases. Node.js APIs accept + * plain [`Uint8Array`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Uint8Array) s wherever `Buffer`s are supported as well. + * + * While the `Buffer` class is available within the global scope, it is still + * recommended to explicitly reference it via an import or require statement. + * + * ```js + * import { Buffer } from "internal:deno_node/polyfills/internal/buffer"; + * + * // Creates a zero-filled Buffer of length 10. + * const buf1 = Buffer.alloc(10); + * + * // Creates a Buffer of length 10, + * // filled with bytes which all have the value `1`. + * const buf2 = Buffer.alloc(10, 1); + * + * // Creates an uninitialized buffer of length 10. + * // This is faster than calling Buffer.alloc() but the returned + * // Buffer instance might contain old data that needs to be + * // overwritten using fill(), write(), or other functions that fill the Buffer's + * // contents. + * const buf3 = Buffer.allocUnsafe(10); + * + * // Creates a Buffer containing the bytes [1, 2, 3]. + * const buf4 = Buffer.from([1, 2, 3]); + * + * // Creates a Buffer containing the bytes [1, 1, 1, 1] – the entries + * // are all truncated using `(value & 255)` to fit into the range 0–255. + * const buf5 = Buffer.from([257, 257.5, -255, '1']); + * + * // Creates a Buffer containing the UTF-8-encoded bytes for the string 'tést': + * // [0x74, 0xc3, 0xa9, 0x73, 0x74] (in hexadecimal notation) + * // [116, 195, 169, 115, 116] (in decimal notation) + * const buf6 = Buffer.from('tést'); + * + * // Creates a Buffer containing the Latin-1 bytes [0x74, 0xe9, 0x73, 0x74]. + * const buf7 = Buffer.from('tést', 'latin1'); + * ``` + * @see [source](https://github.com/nodejs/node/blob/v16.9.0/lib/buffer.js) + */ +export class Buffer extends Uint8Array { + /** + * Allocates a new buffer containing the given {str}. + * + * @param str String to store in buffer. + * @param encoding encoding to use, optional. Default is 'utf8' + * @deprecated since v10.0.0 - Use `Buffer.from(string[, encoding])` instead. + */ + constructor(str: string, encoding?: Encoding); + /** + * Allocates a new buffer of {size} octets. + * + * @param size count of octets to allocate. + * @deprecated since v10.0.0 - Use `Buffer.alloc()` instead (also see `Buffer.allocUnsafe()`). + */ + constructor(size: number); + /** + * Allocates a new buffer containing the given {array} of octets. + * + * @param array The octets to store. + * @deprecated since v10.0.0 - Use `Buffer.from(array)` instead. + */ + constructor(array: Uint8Array); + /** + * Produces a Buffer backed by the same allocated memory as + * the given {ArrayBuffer}/{SharedArrayBuffer}. + * + * @param arrayBuffer The ArrayBuffer with which to share memory. + * @deprecated since v10.0.0 - Use `Buffer.from(arrayBuffer[, byteOffset[, length]])` instead. + */ + constructor(arrayBuffer: ArrayBuffer | SharedArrayBuffer); + /** + * Allocates a new buffer containing the given {array} of octets. + * + * @param array The octets to store. + * @deprecated since v10.0.0 - Use `Buffer.from(array)` instead. + */ + constructor(array: ReadonlyArray); + /** + * Copies the passed {buffer} data onto a new {Buffer} instance. + * + * @param buffer The buffer to copy. + * @deprecated since v10.0.0 - Use `Buffer.from(buffer)` instead. + */ + constructor(buffer: Buffer); + /** + * Allocates a new `Buffer` using an `array` of bytes in the range `0` – `255`. + * Array entries outside that range will be truncated to fit into it. + * + * ```js + * import { Buffer } from "internal:deno_node/polyfills/internal/buffer"; + * + * // Creates a new Buffer containing the UTF-8 bytes of the string 'buffer'. + * const buf = Buffer.from([0x62, 0x75, 0x66, 0x66, 0x65, 0x72]); + * ``` + * + * A `TypeError` will be thrown if `array` is not an `Array` or another type + * appropriate for `Buffer.from()` variants. + * + * `Buffer.from(array)` and `Buffer.from(string)` may also use the internal`Buffer` pool like `Buffer.allocUnsafe()` does. + * @since v5.10.0 + */ + static from( + arrayBuffer: WithImplicitCoercion, + byteOffset?: number, + length?: number, + ): Buffer; + /** + * Creates a new Buffer using the passed {data} + * @param data data to create a new Buffer + */ + static from(data: Uint8Array | ReadonlyArray): Buffer; + static from( + data: WithImplicitCoercion | string>, + ): Buffer; + /** + * Creates a new Buffer containing the given JavaScript string {str}. + * If provided, the {encoding} parameter identifies the character encoding. + * If not provided, {encoding} defaults to 'utf8'. + */ + static from( + str: + | WithImplicitCoercion + | { + [Symbol.toPrimitive](hint: "string"): string; + }, + encoding?: Encoding, + ): Buffer; + /** + * Creates a new Buffer using the passed {data} + * @param values to create a new Buffer + */ + static of(...items: number[]): Buffer; + /** + * Returns `true` if `obj` is a `Buffer`, `false` otherwise. + * + * ```js + * import { Buffer } from "internal:deno_node/polyfills/internal/buffer"; + * + * Buffer.isBuffer(Buffer.alloc(10)); // true + * Buffer.isBuffer(Buffer.from('foo')); // true + * Buffer.isBuffer('a string'); // false + * Buffer.isBuffer([]); // false + * Buffer.isBuffer(new Uint8Array(1024)); // false + * ``` + * @since v0.1.101 + */ + static isBuffer(obj: unknown): obj is Buffer; + /** + * Returns `true` if `encoding` is the name of a supported character encoding, + * or `false` otherwise. + * + * ```js + * import { Buffer } from "internal:deno_node/polyfills/internal/buffer"; + * + * console.log(Buffer.isEncoding('utf8')); + * // Prints: true + * + * console.log(Buffer.isEncoding('hex')); + * // Prints: true + * + * console.log(Buffer.isEncoding('utf/8')); + * // Prints: false + * + * console.log(Buffer.isEncoding('')); + * // Prints: false + * ``` + * @since v0.9.1 + * @param encoding A character encoding name to check. + */ + static isEncoding(encoding: string): boolean; + /** + * Returns the byte length of a string when encoded using `encoding`. + * This is not the same as [`String.prototype.length`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/length), which does not account + * for the encoding that is used to convert the string into bytes. + * + * For `'base64'`, `'base64url'`, and `'hex'`, this function assumes valid input. + * For strings that contain non-base64/hex-encoded data (e.g. whitespace), the + * return value might be greater than the length of a `Buffer` created from the + * string. + * + * ```js + * import { Buffer } from "internal:deno_node/polyfills/internal/buffer"; + * + * const str = '\u00bd + \u00bc = \u00be'; + * + * console.log(`${str}: ${str.length} characters, ` + + * `${Buffer.byteLength(str, 'utf8')} bytes`); + * // Prints: ½ + ¼ = ¾: 9 characters, 12 bytes + * ``` + * + * When `string` is a + * `Buffer`/[`DataView`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/DataView)/[`TypedArray`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/- + * Reference/Global_Objects/TypedArray)/[`ArrayBuffer`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/ArrayBuffer)/[`SharedArrayBuffer`](https://develop- + * er.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/SharedArrayBuffer), the byte length as reported by `.byteLength`is returned. + * @since v0.1.90 + * @param string A value to calculate the length of. + * @param [encoding='utf8'] If `string` is a string, this is its encoding. + * @return The number of bytes contained within `string`. + */ + static byteLength( + string: + | string + | ArrayBufferView + | ArrayBuffer + | SharedArrayBuffer, + encoding?: Encoding, + ): number; + /** + * Returns a new `Buffer` which is the result of concatenating all the `Buffer`instances in the `list` together. + * + * If the list has no items, or if the `totalLength` is 0, then a new zero-length`Buffer` is returned. + * + * If `totalLength` is not provided, it is calculated from the `Buffer` instances + * in `list` by adding their lengths. + * + * If `totalLength` is provided, it is coerced to an unsigned integer. If the + * combined length of the `Buffer`s in `list` exceeds `totalLength`, the result is + * truncated to `totalLength`. + * + * ```js + * import { Buffer } from "internal:deno_node/polyfills/internal/buffer"; + * + * // Create a single `Buffer` from a list of three `Buffer` instances. + * + * const buf1 = Buffer.alloc(10); + * const buf2 = Buffer.alloc(14); + * const buf3 = Buffer.alloc(18); + * const totalLength = buf1.length + buf2.length + buf3.length; + * + * console.log(totalLength); + * // Prints: 42 + * + * const bufA = Buffer.concat([buf1, buf2, buf3], totalLength); + * + * console.log(bufA); + * // Prints: + * console.log(bufA.length); + * // Prints: 42 + * ``` + * + * `Buffer.concat()` may also use the internal `Buffer` pool like `Buffer.allocUnsafe()` does. + * @since v0.7.11 + * @param list List of `Buffer` or {@link Uint8Array} instances to concatenate. + * @param totalLength Total length of the `Buffer` instances in `list` when concatenated. + */ + static concat( + list: ReadonlyArray, + totalLength?: number, + ): Buffer; + /** + * Compares `buf1` to `buf2`, typically for the purpose of sorting arrays of`Buffer` instances. This is equivalent to calling `buf1.compare(buf2)`. + * + * ```js + * import { Buffer } from "internal:deno_node/polyfills/internal/buffer"; + * + * const buf1 = Buffer.from('1234'); + * const buf2 = Buffer.from('0123'); + * const arr = [buf1, buf2]; + * + * console.log(arr.sort(Buffer.compare)); + * // Prints: [ , ] + * // (This result is equal to: [buf2, buf1].) + * ``` + * @since v0.11.13 + * @return Either `-1`, `0`, or `1`, depending on the result of the comparison. See `compare` for details. + */ + static compare(buf1: Uint8Array, buf2: Uint8Array): number; + /** + * Allocates a new `Buffer` of `size` bytes. If `fill` is `undefined`, the`Buffer` will be zero-filled. + * + * ```js + * import { Buffer } from "internal:deno_node/polyfills/internal/buffer"; + * + * const buf = Buffer.alloc(5); + * + * console.log(buf); + * // Prints: + * ``` + * + * If `size` is larger than {@link constants.MAX_LENGTH} or smaller than 0, `ERR_INVALID_ARG_VALUE` is thrown. + * + * If `fill` is specified, the allocated `Buffer` will be initialized by calling `buf.fill(fill)`. + * + * ```js + * import { Buffer } from "internal:deno_node/polyfills/internal/buffer"; + * + * const buf = Buffer.alloc(5, 'a'); + * + * console.log(buf); + * // Prints: + * ``` + * + * If both `fill` and `encoding` are specified, the allocated `Buffer` will be + * initialized by calling `buf.fill(fill, encoding)`. + * + * ```js + * import { Buffer } from "internal:deno_node/polyfills/internal/buffer"; + * + * const buf = Buffer.alloc(11, 'aGVsbG8gd29ybGQ=', 'base64'); + * + * console.log(buf); + * // Prints: + * ``` + * + * Calling `Buffer.alloc()` can be measurably slower than the alternative `Buffer.allocUnsafe()` but ensures that the newly created `Buffer` instance + * contents will never contain sensitive data from previous allocations, including + * data that might not have been allocated for `Buffer`s. + * + * A `TypeError` will be thrown if `size` is not a number. + * @since v5.10.0 + * @param size The desired length of the new `Buffer`. + * @param [fill=0] A value to pre-fill the new `Buffer` with. + * @param [encoding='utf8'] If `fill` is a string, this is its encoding. + */ + static alloc( + size: number, + fill?: string | Uint8Array | number, + encoding?: Encoding, + ): Buffer; + /** + * Allocates a new `Buffer` of `size` bytes. If `size` is larger than {@link constants.MAX_LENGTH} or smaller than 0, `ERR_INVALID_ARG_VALUE` is thrown. + * + * The underlying memory for `Buffer` instances created in this way is _not_ + * _initialized_. The contents of the newly created `Buffer` are unknown and_may contain sensitive data_. Use `Buffer.alloc()` instead to initialize`Buffer` instances with zeroes. + * + * ```js + * import { Buffer } from "internal:deno_node/polyfills/internal/buffer"; + * + * const buf = Buffer.allocUnsafe(10); + * + * console.log(buf); + * // Prints (contents may vary): + * + * buf.fill(0); + * + * console.log(buf); + * // Prints: + * ``` + * + * A `TypeError` will be thrown if `size` is not a number. + * + * The `Buffer` module pre-allocates an internal `Buffer` instance of + * size `Buffer.poolSize` that is used as a pool for the fast allocation of new`Buffer` instances created using `Buffer.allocUnsafe()`,`Buffer.from(array)`, `Buffer.concat()`, and the + * deprecated`new Buffer(size)` constructor only when `size` is less than or equal + * to `Buffer.poolSize >> 1` (floor of `Buffer.poolSize` divided by two). + * + * Use of this pre-allocated internal memory pool is a key difference between + * calling `Buffer.alloc(size, fill)` vs. `Buffer.allocUnsafe(size).fill(fill)`. + * Specifically, `Buffer.alloc(size, fill)` will _never_ use the internal `Buffer`pool, while `Buffer.allocUnsafe(size).fill(fill)`_will_ use the internal`Buffer` pool if `size` is less + * than or equal to half `Buffer.poolSize`. The + * difference is subtle but can be important when an application requires the + * additional performance that `Buffer.allocUnsafe()` provides. + * @since v5.10.0 + * @param size The desired length of the new `Buffer`. + */ + static allocUnsafe(size: number): Buffer; + /** + * Allocates a new `Buffer` of `size` bytes. If `size` is larger than {@link constants.MAX_LENGTH} or smaller than 0, `ERR_INVALID_ARG_VALUE` is thrown. A zero-length `Buffer` is created + * if `size` is 0. + * + * The underlying memory for `Buffer` instances created in this way is _not_ + * _initialized_. The contents of the newly created `Buffer` are unknown and_may contain sensitive data_. Use `buf.fill(0)` to initialize + * such `Buffer` instances with zeroes. + * + * When using `Buffer.allocUnsafe()` to allocate new `Buffer` instances, + * allocations under 4 KB are sliced from a single pre-allocated `Buffer`. This + * allows applications to avoid the garbage collection overhead of creating many + * individually allocated `Buffer` instances. This approach improves both + * performance and memory usage by eliminating the need to track and clean up as + * many individual `ArrayBuffer` objects. + * + * However, in the case where a developer may need to retain a small chunk of + * memory from a pool for an indeterminate amount of time, it may be appropriate + * to create an un-pooled `Buffer` instance using `Buffer.allocUnsafeSlow()` and + * then copying out the relevant bits. + * + * ```js + * import { Buffer } from "internal:deno_node/polyfills/internal/buffer"; + * + * // Need to keep around a few small chunks of memory. + * const store = []; + * + * socket.on('readable', () => { + * let data; + * while (null !== (data = readable.read())) { + * // Allocate for retained data. + * const sb = Buffer.allocUnsafeSlow(10); + * + * // Copy the data into the new allocation. + * data.copy(sb, 0, 0, 10); + * + * store.push(sb); + * } + * }); + * ``` + * + * A `TypeError` will be thrown if `size` is not a number. + * @since v5.12.0 + * @param size The desired length of the new `Buffer`. + */ + static allocUnsafeSlow(size: number): Buffer; + // /** + // * This is the size (in bytes) of pre-allocated internal `Buffer` instances used + // * for pooling. This value may be modified. + // * @since v0.11.3 + // */ + // static poolSize: number; + + /** + * Writes `string` to `buf` at `offset` according to the character encoding in`encoding`. The `length` parameter is the number of bytes to write. If `buf` did + * not contain enough space to fit the entire string, only part of `string` will be + * written. However, partially encoded characters will not be written. + * + * ```js + * import { Buffer } from "internal:deno_node/polyfills/internal/buffer"; + * + * const buf = Buffer.alloc(256); + * + * const len = buf.write('\u00bd + \u00bc = \u00be', 0); + * + * console.log(`${len} bytes: ${buf.toString('utf8', 0, len)}`); + * // Prints: 12 bytes: ½ + ¼ = ¾ + * + * const buffer = Buffer.alloc(10); + * + * const length = buffer.write('abcd', 8); + * + * console.log(`${length} bytes: ${buffer.toString('utf8', 8, 10)}`); + * // Prints: 2 bytes : ab + * ``` + * @since v0.1.90 + * @param string String to write to `buf`. + * @param [offset=0] Number of bytes to skip before starting to write `string`. + * @param [length=buf.length - offset] Maximum number of bytes to write (written bytes will not exceed `buf.length - offset`). + * @param [encoding='utf8'] The character encoding of `string`. + * @return Number of bytes written. + */ + write(string: string, encoding?: Encoding): number; + write(string: string, offset: number, encoding?: Encoding): number; + write( + string: string, + offset: number, + length: number, + encoding?: Encoding, + ): number; + /** + * Decodes `buf` to a string according to the specified character encoding in`encoding`. `start` and `end` may be passed to decode only a subset of `buf`. + * + * If `encoding` is `'utf8'` and a byte sequence in the input is not valid UTF-8, + * then each invalid byte is replaced with the replacement character `U+FFFD`. + * + * The maximum length of a string instance (in UTF-16 code units) is available + * as {@link constants.MAX_STRING_LENGTH}. + * + * ```js + * import { Buffer } from "internal:deno_node/polyfills/internal/buffer"; + * + * const buf1 = Buffer.allocUnsafe(26); + * + * for (let i = 0; i < 26; i++) { + * // 97 is the decimal ASCII value for 'a'. + * buf1[i] = i + 97; + * } + * + * console.log(buf1.toString('utf8')); + * // Prints: abcdefghijklmnopqrstuvwxyz + * console.log(buf1.toString('utf8', 0, 5)); + * // Prints: abcde + * + * const buf2 = Buffer.from('tést'); + * + * console.log(buf2.toString('hex')); + * // Prints: 74c3a97374 + * console.log(buf2.toString('utf8', 0, 3)); + * // Prints: té + * console.log(buf2.toString(undefined, 0, 3)); + * // Prints: té + * ``` + * @since v0.1.90 + * @param [encoding='utf8'] The character encoding to use. + * @param [start=0] The byte offset to start decoding at. + * @param [end=buf.length] The byte offset to stop decoding at (not inclusive). + */ + toString(encoding?: Encoding, start?: number, end?: number): string; + /** + * Returns a JSON representation of `buf`. [`JSON.stringify()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON/stringify) implicitly calls + * this function when stringifying a `Buffer` instance. + * + * `Buffer.from()` accepts objects in the format returned from this method. + * In particular, `Buffer.from(buf.toJSON())` works like `Buffer.from(buf)`. + * + * ```js + * import { Buffer } from "internal:deno_node/polyfills/internal/buffer"; + * + * const buf = Buffer.from([0x1, 0x2, 0x3, 0x4, 0x5]); + * const json = JSON.stringify(buf); + * + * console.log(json); + * // Prints: {"type":"Buffer","data":[1,2,3,4,5]} + * + * const copy = JSON.parse(json, (key, value) => { + * return value && value.type === 'Buffer' ? + * Buffer.from(value) : + * value; + * }); + * + * console.log(copy); + * // Prints: + * ``` + * @since v0.9.2 + */ + toJSON(): { + type: "Buffer"; + data: number[]; + }; + /** + * Returns `true` if both `buf` and `otherBuffer` have exactly the same bytes,`false` otherwise. Equivalent to `buf.compare(otherBuffer) === 0`. + * + * ```js + * import { Buffer } from "internal:deno_node/polyfills/internal/buffer"; + * + * const buf1 = Buffer.from('ABC'); + * const buf2 = Buffer.from('414243', 'hex'); + * const buf3 = Buffer.from('ABCD'); + * + * console.log(buf1.equals(buf2)); + * // Prints: true + * console.log(buf1.equals(buf3)); + * // Prints: false + * ``` + * @since v0.11.13 + * @param otherBuffer A `Buffer` or {@link Uint8Array} with which to compare `buf`. + */ + equals(otherBuffer: Uint8Array): boolean; + /** + * Compares `buf` with `target` and returns a number indicating whether `buf`comes before, after, or is the same as `target` in sort order. + * Comparison is based on the actual sequence of bytes in each `Buffer`. + * + * * `0` is returned if `target` is the same as `buf` + * * `1` is returned if `target` should come _before_`buf` when sorted. + * * `-1` is returned if `target` should come _after_`buf` when sorted. + * + * ```js + * import { Buffer } from "internal:deno_node/polyfills/internal/buffer"; + * + * const buf1 = Buffer.from('ABC'); + * const buf2 = Buffer.from('BCD'); + * const buf3 = Buffer.from('ABCD'); + * + * console.log(buf1.compare(buf1)); + * // Prints: 0 + * console.log(buf1.compare(buf2)); + * // Prints: -1 + * console.log(buf1.compare(buf3)); + * // Prints: -1 + * console.log(buf2.compare(buf1)); + * // Prints: 1 + * console.log(buf2.compare(buf3)); + * // Prints: 1 + * console.log([buf1, buf2, buf3].sort(Buffer.compare)); + * // Prints: [ , , ] + * // (This result is equal to: [buf1, buf3, buf2].) + * ``` + * + * The optional `targetStart`, `targetEnd`, `sourceStart`, and `sourceEnd`arguments can be used to limit the comparison to specific ranges within `target`and `buf` respectively. + * + * ```js + * import { Buffer } from "internal:deno_node/polyfills/internal/buffer"; + * + * const buf1 = Buffer.from([1, 2, 3, 4, 5, 6, 7, 8, 9]); + * const buf2 = Buffer.from([5, 6, 7, 8, 9, 1, 2, 3, 4]); + * + * console.log(buf1.compare(buf2, 5, 9, 0, 4)); + * // Prints: 0 + * console.log(buf1.compare(buf2, 0, 6, 4)); + * // Prints: -1 + * console.log(buf1.compare(buf2, 5, 6, 5)); + * // Prints: 1 + * ``` + * + * `ERR_OUT_OF_RANGE` is thrown if `targetStart < 0`, `sourceStart < 0`,`targetEnd > target.byteLength`, or `sourceEnd > source.byteLength`. + * @since v0.11.13 + * @param target A `Buffer` or {@link Uint8Array} with which to compare `buf`. + * @param [targetStart=0] The offset within `target` at which to begin comparison. + * @param [targetEnd=target.length] The offset within `target` at which to end comparison (not inclusive). + * @param [sourceStart=0] The offset within `buf` at which to begin comparison. + * @param [sourceEnd=buf.length] The offset within `buf` at which to end comparison (not inclusive). + */ + compare( + target: Uint8Array, + targetStart?: number, + targetEnd?: number, + sourceStart?: number, + sourceEnd?: number, + ): number; + /** + * Copies data from a region of `buf` to a region in `target`, even if the `target`memory region overlaps with `buf`. + * + * [`TypedArray.prototype.set()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/TypedArray/set) performs the same operation, and is available + * for all TypedArrays, including Node.js `Buffer`s, although it takes + * different function arguments. + * + * ```js + * import { Buffer } from "internal:deno_node/polyfills/internal/buffer"; + * + * // Create two `Buffer` instances. + * const buf1 = Buffer.allocUnsafe(26); + * const buf2 = Buffer.allocUnsafe(26).fill('!'); + * + * for (let i = 0; i < 26; i++) { + * // 97 is the decimal ASCII value for 'a'. + * buf1[i] = i + 97; + * } + * + * // Copy `buf1` bytes 16 through 19 into `buf2` starting at byte 8 of `buf2`. + * buf1.copy(buf2, 8, 16, 20); + * // This is equivalent to: + * // buf2.set(buf1.subarray(16, 20), 8); + * + * console.log(buf2.toString('ascii', 0, 25)); + * // Prints: !!!!!!!!qrst!!!!!!!!!!!!! + * ``` + * + * ```js + * import { Buffer } from "internal:deno_node/polyfills/internal/buffer"; + * + * // Create a `Buffer` and copy data from one region to an overlapping region + * // within the same `Buffer`. + * + * const buf = Buffer.allocUnsafe(26); + * + * for (let i = 0; i < 26; i++) { + * // 97 is the decimal ASCII value for 'a'. + * buf[i] = i + 97; + * } + * + * buf.copy(buf, 0, 4, 10); + * + * console.log(buf.toString()); + * // Prints: efghijghijklmnopqrstuvwxyz + * ``` + * @since v0.1.90 + * @param target A `Buffer` or {@link Uint8Array} to copy into. + * @param [targetStart=0] The offset within `target` at which to begin writing. + * @param [sourceStart=0] The offset within `buf` from which to begin copying. + * @param [sourceEnd=buf.length] The offset within `buf` at which to stop copying (not inclusive). + * @return The number of bytes copied. + */ + copy( + target: Uint8Array, + targetStart?: number, + sourceStart?: number, + sourceEnd?: number, + ): number; + /** + * Returns a new `Buffer` that references the same memory as the original, but + * offset and cropped by the `start` and `end` indices. + * + * This is the same behavior as `buf.subarray()`. + * + * This method is not compatible with the `Uint8Array.prototype.slice()`, + * which is a superclass of `Buffer`. To copy the slice, use`Uint8Array.prototype.slice()`. + * + * ```js + * import { Buffer } from "internal:deno_node/polyfills/internal/buffer"; + * + * const buf = Buffer.from('buffer'); + * + * const copiedBuf = Uint8Array.prototype.slice.call(buf); + * copiedBuf[0]++; + * console.log(copiedBuf.toString()); + * // Prints: cuffer + * + * console.log(buf.toString()); + * // Prints: buffer + * ``` + * @since v0.3.0 + * @param [start=0] Where the new `Buffer` will start. + * @param [end=buf.length] Where the new `Buffer` will end (not inclusive). + */ + slice(start?: number, end?: number): Buffer; + /** + * Returns a new `Buffer` that references the same memory as the original, but + * offset and cropped by the `start` and `end` indices. + * + * Specifying `end` greater than `buf.length` will return the same result as + * that of `end` equal to `buf.length`. + * + * This method is inherited from [`TypedArray.prototype.subarray()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/TypedArray/subarray). + * + * Modifying the new `Buffer` slice will modify the memory in the original `Buffer`because the allocated memory of the two objects overlap. + * + * ```js + * import { Buffer } from "internal:deno_node/polyfills/internal/buffer"; + * + * // Create a `Buffer` with the ASCII alphabet, take a slice, and modify one byte + * // from the original `Buffer`. + * + * const buf1 = Buffer.allocUnsafe(26); + * + * for (let i = 0; i < 26; i++) { + * // 97 is the decimal ASCII value for 'a'. + * buf1[i] = i + 97; + * } + * + * const buf2 = buf1.subarray(0, 3); + * + * console.log(buf2.toString('ascii', 0, buf2.length)); + * // Prints: abc + * + * buf1[0] = 33; + * + * console.log(buf2.toString('ascii', 0, buf2.length)); + * // Prints: !bc + * ``` + * + * Specifying negative indexes causes the slice to be generated relative to the + * end of `buf` rather than the beginning. + * + * ```js + * import { Buffer } from "internal:deno_node/polyfills/internal/buffer"; + * + * const buf = Buffer.from('buffer'); + * + * console.log(buf.subarray(-6, -1).toString()); + * // Prints: buffe + * // (Equivalent to buf.subarray(0, 5).) + * + * console.log(buf.subarray(-6, -2).toString()); + * // Prints: buff + * // (Equivalent to buf.subarray(0, 4).) + * + * console.log(buf.subarray(-5, -2).toString()); + * // Prints: uff + * // (Equivalent to buf.subarray(1, 4).) + * ``` + * @since v3.0.0 + * @param [start=0] Where the new `Buffer` will start. + * @param [end=buf.length] Where the new `Buffer` will end (not inclusive). + */ + subarray(start?: number, end?: number): Buffer; + /** + * Writes `value` to `buf` at the specified `offset` as big-endian. + * + * `value` is interpreted and written as a two's complement signed integer. + * + * ```js + * import { Buffer } from "internal:deno_node/polyfills/internal/buffer"; + * + * const buf = Buffer.allocUnsafe(8); + * + * buf.writeBigInt64BE(0x0102030405060708n, 0); + * + * console.log(buf); + * // Prints: + * ``` + * @since v12.0.0, v10.20.0 + * @param value Number to be written to `buf`. + * @param [offset=0] Number of bytes to skip before starting to write. Must satisfy: `0 <= offset <= buf.length - 8`. + * @return `offset` plus the number of bytes written. + */ + writeBigInt64BE(value: bigint, offset?: number): number; + /** + * Writes `value` to `buf` at the specified `offset` as little-endian. + * + * `value` is interpreted and written as a two's complement signed integer. + * + * ```js + * import { Buffer } from "internal:deno_node/polyfills/internal/buffer"; + * + * const buf = Buffer.allocUnsafe(8); + * + * buf.writeBigInt64LE(0x0102030405060708n, 0); + * + * console.log(buf); + * // Prints: + * ``` + * @since v12.0.0, v10.20.0 + * @param value Number to be written to `buf`. + * @param [offset=0] Number of bytes to skip before starting to write. Must satisfy: `0 <= offset <= buf.length - 8`. + * @return `offset` plus the number of bytes written. + */ + writeBigInt64LE(value: bigint, offset?: number): number; + /** + * Writes `value` to `buf` at the specified `offset` as big-endian. + * + * This function is also available under the `writeBigUint64BE` alias. + * + * ```js + * import { Buffer } from "internal:deno_node/polyfills/internal/buffer"; + * + * const buf = Buffer.allocUnsafe(8); + * + * buf.writeBigUInt64BE(0xdecafafecacefaden, 0); + * + * console.log(buf); + * // Prints: + * ``` + * @since v12.0.0, v10.20.0 + * @param value Number to be written to `buf`. + * @param [offset=0] Number of bytes to skip before starting to write. Must satisfy: `0 <= offset <= buf.length - 8`. + * @return `offset` plus the number of bytes written. + */ + writeBigUInt64BE(value: bigint, offset?: number): number; + /** + * Writes `value` to `buf` at the specified `offset` as little-endian + * + * ```js + * import { Buffer } from "internal:deno_node/polyfills/internal/buffer"; + * + * const buf = Buffer.allocUnsafe(8); + * + * buf.writeBigUInt64LE(0xdecafafecacefaden, 0); + * + * console.log(buf); + * // Prints: + * ``` + * + * This function is also available under the `writeBigUint64LE` alias. + * @since v12.0.0, v10.20.0 + * @param value Number to be written to `buf`. + * @param [offset=0] Number of bytes to skip before starting to write. Must satisfy: `0 <= offset <= buf.length - 8`. + * @return `offset` plus the number of bytes written. + */ + writeBigUInt64LE(value: bigint, offset?: number): number; + /** + * Writes `byteLength` bytes of `value` to `buf` at the specified `offset`as little-endian. Supports up to 48 bits of accuracy. Behavior is undefined + * when `value` is anything other than an unsigned integer. + * + * This function is also available under the `writeUintLE` alias. + * + * ```js + * import { Buffer } from "internal:deno_node/polyfills/internal/buffer"; + * + * const buf = Buffer.allocUnsafe(6); + * + * buf.writeUIntLE(0x1234567890ab, 0, 6); + * + * console.log(buf); + * // Prints: + * ``` + * @since v0.5.5 + * @param value Number to be written to `buf`. + * @param offset Number of bytes to skip before starting to write. Must satisfy `0 <= offset <= buf.length - byteLength`. + * @param byteLength Number of bytes to write. Must satisfy `0 < byteLength <= 6`. + * @return `offset` plus the number of bytes written. + */ + writeUIntLE(value: number, offset: number, byteLength: number): number; + /** + * Writes `byteLength` bytes of `value` to `buf` at the specified `offset`as big-endian. Supports up to 48 bits of accuracy. Behavior is undefined + * when `value` is anything other than an unsigned integer. + * + * This function is also available under the `writeUintBE` alias. + * + * ```js + * import { Buffer } from "internal:deno_node/polyfills/internal/buffer"; + * + * const buf = Buffer.allocUnsafe(6); + * + * buf.writeUIntBE(0x1234567890ab, 0, 6); + * + * console.log(buf); + * // Prints: + * ``` + * @since v0.5.5 + * @param value Number to be written to `buf`. + * @param offset Number of bytes to skip before starting to write. Must satisfy `0 <= offset <= buf.length - byteLength`. + * @param byteLength Number of bytes to write. Must satisfy `0 < byteLength <= 6`. + * @return `offset` plus the number of bytes written. + */ + writeUIntBE(value: number, offset: number, byteLength: number): number; + /** + * Writes `byteLength` bytes of `value` to `buf` at the specified `offset`as little-endian. Supports up to 48 bits of accuracy. Behavior is undefined + * when `value` is anything other than a signed integer. + * + * ```js + * import { Buffer } from "internal:deno_node/polyfills/internal/buffer"; + * + * const buf = Buffer.allocUnsafe(6); + * + * buf.writeIntLE(0x1234567890ab, 0, 6); + * + * console.log(buf); + * // Prints: + * ``` + * @since v0.11.15 + * @param value Number to be written to `buf`. + * @param offset Number of bytes to skip before starting to write. Must satisfy `0 <= offset <= buf.length - byteLength`. + * @param byteLength Number of bytes to write. Must satisfy `0 < byteLength <= 6`. + * @return `offset` plus the number of bytes written. + */ + writeIntLE(value: number, offset: number, byteLength: number): number; + /** + * Writes `byteLength` bytes of `value` to `buf` at the specified `offset`as big-endian. Supports up to 48 bits of accuracy. Behavior is undefined when`value` is anything other than a + * signed integer. + * + * ```js + * import { Buffer } from "internal:deno_node/polyfills/internal/buffer"; + * + * const buf = Buffer.allocUnsafe(6); + * + * buf.writeIntBE(0x1234567890ab, 0, 6); + * + * console.log(buf); + * // Prints: + * ``` + * @since v0.11.15 + * @param value Number to be written to `buf`. + * @param offset Number of bytes to skip before starting to write. Must satisfy `0 <= offset <= buf.length - byteLength`. + * @param byteLength Number of bytes to write. Must satisfy `0 < byteLength <= 6`. + * @return `offset` plus the number of bytes written. + */ + writeIntBE(value: number, offset: number, byteLength: number): number; + /** + * Reads an unsigned, big-endian 64-bit integer from `buf` at the specified`offset`. + * + * This function is also available under the `readBigUint64BE` alias. + * + * ```js + * import { Buffer } from "internal:deno_node/polyfills/internal/buffer"; + * + * const buf = Buffer.from([0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff]); + * + * console.log(buf.readBigUInt64BE(0)); + * // Prints: 4294967295n + * ``` + * @since v12.0.0, v10.20.0 + * @param [offset=0] Number of bytes to skip before starting to read. Must satisfy: `0 <= offset <= buf.length - 8`. + */ + readBigUInt64BE(offset?: number): bigint; + /** + * Reads an unsigned, little-endian 64-bit integer from `buf` at the specified`offset`. + * + * This function is also available under the `readBigUint64LE` alias. + * + * ```js + * import { Buffer } from "internal:deno_node/polyfills/internal/buffer"; + * + * const buf = Buffer.from([0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff]); + * + * console.log(buf.readBigUInt64LE(0)); + * // Prints: 18446744069414584320n + * ``` + * @since v12.0.0, v10.20.0 + * @param [offset=0] Number of bytes to skip before starting to read. Must satisfy: `0 <= offset <= buf.length - 8`. + */ + readBigUInt64LE(offset?: number): bigint; + /** + * Reads a signed, big-endian 64-bit integer from `buf` at the specified `offset`. + * + * Integers read from a `Buffer` are interpreted as two's complement signed + * values. + * @since v12.0.0, v10.20.0 + * @param [offset=0] Number of bytes to skip before starting to read. Must satisfy: `0 <= offset <= buf.length - 8`. + */ + readBigInt64BE(offset?: number): bigint; + /** + * Reads a signed, little-endian 64-bit integer from `buf` at the specified`offset`. + * + * Integers read from a `Buffer` are interpreted as two's complement signed + * values. + * @since v12.0.0, v10.20.0 + * @param [offset=0] Number of bytes to skip before starting to read. Must satisfy: `0 <= offset <= buf.length - 8`. + */ + readBigInt64LE(offset?: number): bigint; + /** + * Reads `byteLength` number of bytes from `buf` at the specified `offset`and interprets the result as an unsigned, little-endian integer supporting + * up to 48 bits of accuracy. + * + * This function is also available under the `readUintLE` alias. + * + * ```js + * import { Buffer } from "internal:deno_node/polyfills/internal/buffer"; + * + * const buf = Buffer.from([0x12, 0x34, 0x56, 0x78, 0x90, 0xab]); + * + * console.log(buf.readUIntLE(0, 6).toString(16)); + * // Prints: ab9078563412 + * ``` + * @since v0.11.15 + * @param offset Number of bytes to skip before starting to read. Must satisfy `0 <= offset <= buf.length - byteLength`. + * @param byteLength Number of bytes to read. Must satisfy `0 < byteLength <= 6`. + */ + readUIntLE(offset: number, byteLength: number): number; + /** + * Reads `byteLength` number of bytes from `buf` at the specified `offset`and interprets the result as an unsigned big-endian integer supporting + * up to 48 bits of accuracy. + * + * This function is also available under the `readUintBE` alias. + * + * ```js + * import { Buffer } from "internal:deno_node/polyfills/internal/buffer"; + * + * const buf = Buffer.from([0x12, 0x34, 0x56, 0x78, 0x90, 0xab]); + * + * console.log(buf.readUIntBE(0, 6).toString(16)); + * // Prints: 1234567890ab + * console.log(buf.readUIntBE(1, 6).toString(16)); + * // Throws ERR_OUT_OF_RANGE. + * ``` + * @since v0.11.15 + * @param offset Number of bytes to skip before starting to read. Must satisfy `0 <= offset <= buf.length - byteLength`. + * @param byteLength Number of bytes to read. Must satisfy `0 < byteLength <= 6`. + */ + readUIntBE(offset: number, byteLength: number): number; + /** + * Reads `byteLength` number of bytes from `buf` at the specified `offset`and interprets the result as a little-endian, two's complement signed value + * supporting up to 48 bits of accuracy. + * + * ```js + * import { Buffer } from "internal:deno_node/polyfills/internal/buffer"; + * + * const buf = Buffer.from([0x12, 0x34, 0x56, 0x78, 0x90, 0xab]); + * + * console.log(buf.readIntLE(0, 6).toString(16)); + * // Prints: -546f87a9cbee + * ``` + * @since v0.11.15 + * @param offset Number of bytes to skip before starting to read. Must satisfy `0 <= offset <= buf.length - byteLength`. + * @param byteLength Number of bytes to read. Must satisfy `0 < byteLength <= 6`. + */ + readIntLE(offset: number, byteLength: number): number; + /** + * Reads `byteLength` number of bytes from `buf` at the specified `offset`and interprets the result as a big-endian, two's complement signed value + * supporting up to 48 bits of accuracy. + * + * ```js + * import { Buffer } from "internal:deno_node/polyfills/internal/buffer"; + * + * const buf = Buffer.from([0x12, 0x34, 0x56, 0x78, 0x90, 0xab]); + * + * console.log(buf.readIntBE(0, 6).toString(16)); + * // Prints: 1234567890ab + * console.log(buf.readIntBE(1, 6).toString(16)); + * // Throws ERR_OUT_OF_RANGE. + * console.log(buf.readIntBE(1, 0).toString(16)); + * // Throws ERR_OUT_OF_RANGE. + * ``` + * @since v0.11.15 + * @param offset Number of bytes to skip before starting to read. Must satisfy `0 <= offset <= buf.length - byteLength`. + * @param byteLength Number of bytes to read. Must satisfy `0 < byteLength <= 6`. + */ + readIntBE(offset: number, byteLength: number): number; + /** + * Reads an unsigned 8-bit integer from `buf` at the specified `offset`. + * + * This function is also available under the `readUint8` alias. + * + * ```js + * import { Buffer } from "internal:deno_node/polyfills/internal/buffer"; + * + * const buf = Buffer.from([1, -2]); + * + * console.log(buf.readUInt8(0)); + * // Prints: 1 + * console.log(buf.readUInt8(1)); + * // Prints: 254 + * console.log(buf.readUInt8(2)); + * // Throws ERR_OUT_OF_RANGE. + * ``` + * @since v0.5.0 + * @param [offset=0] Number of bytes to skip before starting to read. Must satisfy `0 <= offset <= buf.length - 1`. + */ + readUInt8(offset?: number): number; + /** + * Reads an unsigned, little-endian 16-bit integer from `buf` at the specified`offset`. + * + * This function is also available under the `readUint16LE` alias. + * + * ```js + * import { Buffer } from "internal:deno_node/polyfills/internal/buffer"; + * + * const buf = Buffer.from([0x12, 0x34, 0x56]); + * + * console.log(buf.readUInt16LE(0).toString(16)); + * // Prints: 3412 + * console.log(buf.readUInt16LE(1).toString(16)); + * // Prints: 5634 + * console.log(buf.readUInt16LE(2).toString(16)); + * // Throws ERR_OUT_OF_RANGE. + * ``` + * @since v0.5.5 + * @param [offset=0] Number of bytes to skip before starting to read. Must satisfy `0 <= offset <= buf.length - 2`. + */ + readUInt16LE(offset?: number): number; + /** + * Reads an unsigned, big-endian 16-bit integer from `buf` at the specified`offset`. + * + * This function is also available under the `readUint16BE` alias. + * + * ```js + * import { Buffer } from "internal:deno_node/polyfills/internal/buffer"; + * + * const buf = Buffer.from([0x12, 0x34, 0x56]); + * + * console.log(buf.readUInt16BE(0).toString(16)); + * // Prints: 1234 + * console.log(buf.readUInt16BE(1).toString(16)); + * // Prints: 3456 + * ``` + * @since v0.5.5 + * @param [offset=0] Number of bytes to skip before starting to read. Must satisfy `0 <= offset <= buf.length - 2`. + */ + readUInt16BE(offset?: number): number; + /** + * Reads an unsigned, little-endian 32-bit integer from `buf` at the specified`offset`. + * + * This function is also available under the `readUint32LE` alias. + * + * ```js + * import { Buffer } from "internal:deno_node/polyfills/internal/buffer"; + * + * const buf = Buffer.from([0x12, 0x34, 0x56, 0x78]); + * + * console.log(buf.readUInt32LE(0).toString(16)); + * // Prints: 78563412 + * console.log(buf.readUInt32LE(1).toString(16)); + * // Throws ERR_OUT_OF_RANGE. + * ``` + * @since v0.5.5 + * @param [offset=0] Number of bytes to skip before starting to read. Must satisfy `0 <= offset <= buf.length - 4`. + */ + readUInt32LE(offset?: number): number; + /** + * Reads an unsigned, big-endian 32-bit integer from `buf` at the specified`offset`. + * + * This function is also available under the `readUint32BE` alias. + * + * ```js + * import { Buffer } from "internal:deno_node/polyfills/internal/buffer"; + * + * const buf = Buffer.from([0x12, 0x34, 0x56, 0x78]); + * + * console.log(buf.readUInt32BE(0).toString(16)); + * // Prints: 12345678 + * ``` + * @since v0.5.5 + * @param [offset=0] Number of bytes to skip before starting to read. Must satisfy `0 <= offset <= buf.length - 4`. + */ + readUInt32BE(offset?: number): number; + /** + * Reads a signed 8-bit integer from `buf` at the specified `offset`. + * + * Integers read from a `Buffer` are interpreted as two's complement signed values. + * + * ```js + * import { Buffer } from "internal:deno_node/polyfills/internal/buffer"; + * + * const buf = Buffer.from([-1, 5]); + * + * console.log(buf.readInt8(0)); + * // Prints: -1 + * console.log(buf.readInt8(1)); + * // Prints: 5 + * console.log(buf.readInt8(2)); + * // Throws ERR_OUT_OF_RANGE. + * ``` + * @since v0.5.0 + * @param [offset=0] Number of bytes to skip before starting to read. Must satisfy `0 <= offset <= buf.length - 1`. + */ + readInt8(offset?: number): number; + /** + * Reads a signed, little-endian 16-bit integer from `buf` at the specified`offset`. + * + * Integers read from a `Buffer` are interpreted as two's complement signed values. + * + * ```js + * import { Buffer } from "internal:deno_node/polyfills/internal/buffer"; + * + * const buf = Buffer.from([0, 5]); + * + * console.log(buf.readInt16LE(0)); + * // Prints: 1280 + * console.log(buf.readInt16LE(1)); + * // Throws ERR_OUT_OF_RANGE. + * ``` + * @since v0.5.5 + * @param [offset=0] Number of bytes to skip before starting to read. Must satisfy `0 <= offset <= buf.length - 2`. + */ + readInt16LE(offset?: number): number; + /** + * Reads a signed, big-endian 16-bit integer from `buf` at the specified `offset`. + * + * Integers read from a `Buffer` are interpreted as two's complement signed values. + * + * ```js + * import { Buffer } from "internal:deno_node/polyfills/internal/buffer"; + * + * const buf = Buffer.from([0, 5]); + * + * console.log(buf.readInt16BE(0)); + * // Prints: 5 + * ``` + * @since v0.5.5 + * @param [offset=0] Number of bytes to skip before starting to read. Must satisfy `0 <= offset <= buf.length - 2`. + */ + readInt16BE(offset?: number): number; + /** + * Reads a signed, little-endian 32-bit integer from `buf` at the specified`offset`. + * + * Integers read from a `Buffer` are interpreted as two's complement signed values. + * + * ```js + * import { Buffer } from "internal:deno_node/polyfills/internal/buffer"; + * + * const buf = Buffer.from([0, 0, 0, 5]); + * + * console.log(buf.readInt32LE(0)); + * // Prints: 83886080 + * console.log(buf.readInt32LE(1)); + * // Throws ERR_OUT_OF_RANGE. + * ``` + * @since v0.5.5 + * @param [offset=0] Number of bytes to skip before starting to read. Must satisfy `0 <= offset <= buf.length - 4`. + */ + readInt32LE(offset?: number): number; + /** + * Reads a signed, big-endian 32-bit integer from `buf` at the specified `offset`. + * + * Integers read from a `Buffer` are interpreted as two's complement signed values. + * + * ```js + * import { Buffer } from "internal:deno_node/polyfills/internal/buffer"; + * + * const buf = Buffer.from([0, 0, 0, 5]); + * + * console.log(buf.readInt32BE(0)); + * // Prints: 5 + * ``` + * @since v0.5.5 + * @param [offset=0] Number of bytes to skip before starting to read. Must satisfy `0 <= offset <= buf.length - 4`. + */ + readInt32BE(offset?: number): number; + /** + * Reads a 32-bit, little-endian float from `buf` at the specified `offset`. + * + * ```js + * import { Buffer } from "internal:deno_node/polyfills/internal/buffer"; + * + * const buf = Buffer.from([1, 2, 3, 4]); + * + * console.log(buf.readFloatLE(0)); + * // Prints: 1.539989614439558e-36 + * console.log(buf.readFloatLE(1)); + * // Throws ERR_OUT_OF_RANGE. + * ``` + * @since v0.11.15 + * @param [offset=0] Number of bytes to skip before starting to read. Must satisfy `0 <= offset <= buf.length - 4`. + */ + readFloatLE(offset?: number): number; + /** + * Reads a 32-bit, big-endian float from `buf` at the specified `offset`. + * + * ```js + * import { Buffer } from "internal:deno_node/polyfills/internal/buffer"; + * + * const buf = Buffer.from([1, 2, 3, 4]); + * + * console.log(buf.readFloatBE(0)); + * // Prints: 2.387939260590663e-38 + * ``` + * @since v0.11.15 + * @param [offset=0] Number of bytes to skip before starting to read. Must satisfy `0 <= offset <= buf.length - 4`. + */ + readFloatBE(offset?: number): number; + /** + * Reads a 64-bit, little-endian double from `buf` at the specified `offset`. + * + * ```js + * import { Buffer } from "internal:deno_node/polyfills/internal/buffer"; + * + * const buf = Buffer.from([1, 2, 3, 4, 5, 6, 7, 8]); + * + * console.log(buf.readDoubleLE(0)); + * // Prints: 5.447603722011605e-270 + * console.log(buf.readDoubleLE(1)); + * // Throws ERR_OUT_OF_RANGE. + * ``` + * @since v0.11.15 + * @param [offset=0] Number of bytes to skip before starting to read. Must satisfy `0 <= offset <= buf.length - 8`. + */ + readDoubleLE(offset?: number): number; + /** + * Reads a 64-bit, big-endian double from `buf` at the specified `offset`. + * + * ```js + * import { Buffer } from "internal:deno_node/polyfills/internal/buffer"; + * + * const buf = Buffer.from([1, 2, 3, 4, 5, 6, 7, 8]); + * + * console.log(buf.readDoubleBE(0)); + * // Prints: 8.20788039913184e-304 + * ``` + * @since v0.11.15 + * @param [offset=0] Number of bytes to skip before starting to read. Must satisfy `0 <= offset <= buf.length - 8`. + */ + readDoubleBE(offset?: number): number; + reverse(): this; + /** + * Interprets `buf` as an array of unsigned 16-bit integers and swaps the + * byte order _in-place_. Throws `ERR_INVALID_BUFFER_SIZE` if `buf.length` is not a multiple of 2. + * + * ```js + * import { Buffer } from "internal:deno_node/polyfills/internal/buffer"; + * + * const buf1 = Buffer.from([0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8]); + * + * console.log(buf1); + * // Prints: + * + * buf1.swap16(); + * + * console.log(buf1); + * // Prints: + * + * const buf2 = Buffer.from([0x1, 0x2, 0x3]); + * + * buf2.swap16(); + * // Throws ERR_INVALID_BUFFER_SIZE. + * ``` + * + * One convenient use of `buf.swap16()` is to perform a fast in-place conversion + * between UTF-16 little-endian and UTF-16 big-endian: + * + * ```js + * import { Buffer } from "internal:deno_node/polyfills/internal/buffer"; + * + * const buf = Buffer.from('This is little-endian UTF-16', 'utf16le'); + * buf.swap16(); // Convert to big-endian UTF-16 text. + * ``` + * @since v5.10.0 + * @return A reference to `buf`. + */ + swap16(): Buffer; + /** + * Interprets `buf` as an array of unsigned 32-bit integers and swaps the + * byte order _in-place_. Throws `ERR_INVALID_BUFFER_SIZE` if `buf.length` is not a multiple of 4. + * + * ```js + * import { Buffer } from "internal:deno_node/polyfills/internal/buffer"; + * + * const buf1 = Buffer.from([0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8]); + * + * console.log(buf1); + * // Prints: + * + * buf1.swap32(); + * + * console.log(buf1); + * // Prints: + * + * const buf2 = Buffer.from([0x1, 0x2, 0x3]); + * + * buf2.swap32(); + * // Throws ERR_INVALID_BUFFER_SIZE. + * ``` + * @since v5.10.0 + * @return A reference to `buf`. + */ + swap32(): Buffer; + /** + * Interprets `buf` as an array of 64-bit numbers and swaps byte order _in-place_. + * Throws `ERR_INVALID_BUFFER_SIZE` if `buf.length` is not a multiple of 8. + * + * ```js + * import { Buffer } from "internal:deno_node/polyfills/internal/buffer"; + * + * const buf1 = Buffer.from([0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8]); + * + * console.log(buf1); + * // Prints: + * + * buf1.swap64(); + * + * console.log(buf1); + * // Prints: + * + * const buf2 = Buffer.from([0x1, 0x2, 0x3]); + * + * buf2.swap64(); + * // Throws ERR_INVALID_BUFFER_SIZE. + * ``` + * @since v6.3.0 + * @return A reference to `buf`. + */ + swap64(): Buffer; + /** + * Writes `value` to `buf` at the specified `offset`. `value` must be a + * valid unsigned 8-bit integer. Behavior is undefined when `value` is anything + * other than an unsigned 8-bit integer. + * + * This function is also available under the `writeUint8` alias. + * + * ```js + * import { Buffer } from "internal:deno_node/polyfills/internal/buffer"; + * + * const buf = Buffer.allocUnsafe(4); + * + * buf.writeUInt8(0x3, 0); + * buf.writeUInt8(0x4, 1); + * buf.writeUInt8(0x23, 2); + * buf.writeUInt8(0x42, 3); + * + * console.log(buf); + * // Prints: + * ``` + * @since v0.5.0 + * @param value Number to be written to `buf`. + * @param [offset=0] Number of bytes to skip before starting to write. Must satisfy `0 <= offset <= buf.length - 1`. + * @return `offset` plus the number of bytes written. + */ + writeUInt8(value: number, offset?: number): number; + /** + * Writes `value` to `buf` at the specified `offset` as little-endian. The `value`must be a valid unsigned 16-bit integer. Behavior is undefined when `value` is + * anything other than an unsigned 16-bit integer. + * + * This function is also available under the `writeUint16LE` alias. + * + * ```js + * import { Buffer } from "internal:deno_node/polyfills/internal/buffer"; + * + * const buf = Buffer.allocUnsafe(4); + * + * buf.writeUInt16LE(0xdead, 0); + * buf.writeUInt16LE(0xbeef, 2); + * + * console.log(buf); + * // Prints: + * ``` + * @since v0.5.5 + * @param value Number to be written to `buf`. + * @param [offset=0] Number of bytes to skip before starting to write. Must satisfy `0 <= offset <= buf.length - 2`. + * @return `offset` plus the number of bytes written. + */ + writeUInt16LE(value: number, offset?: number): number; + /** + * Writes `value` to `buf` at the specified `offset` as big-endian. The `value`must be a valid unsigned 16-bit integer. Behavior is undefined when `value`is anything other than an + * unsigned 16-bit integer. + * + * This function is also available under the `writeUint16BE` alias. + * + * ```js + * import { Buffer } from "internal:deno_node/polyfills/internal/buffer"; + * + * const buf = Buffer.allocUnsafe(4); + * + * buf.writeUInt16BE(0xdead, 0); + * buf.writeUInt16BE(0xbeef, 2); + * + * console.log(buf); + * // Prints: + * ``` + * @since v0.5.5 + * @param value Number to be written to `buf`. + * @param [offset=0] Number of bytes to skip before starting to write. Must satisfy `0 <= offset <= buf.length - 2`. + * @return `offset` plus the number of bytes written. + */ + writeUInt16BE(value: number, offset?: number): number; + /** + * Writes `value` to `buf` at the specified `offset` as little-endian. The `value`must be a valid unsigned 32-bit integer. Behavior is undefined when `value` is + * anything other than an unsigned 32-bit integer. + * + * This function is also available under the `writeUint32LE` alias. + * + * ```js + * import { Buffer } from "internal:deno_node/polyfills/internal/buffer"; + * + * const buf = Buffer.allocUnsafe(4); + * + * buf.writeUInt32LE(0xfeedface, 0); + * + * console.log(buf); + * // Prints: + * ``` + * @since v0.5.5 + * @param value Number to be written to `buf`. + * @param [offset=0] Number of bytes to skip before starting to write. Must satisfy `0 <= offset <= buf.length - 4`. + * @return `offset` plus the number of bytes written. + */ + writeUInt32LE(value: number, offset?: number): number; + /** + * Writes `value` to `buf` at the specified `offset` as big-endian. The `value`must be a valid unsigned 32-bit integer. Behavior is undefined when `value`is anything other than an + * unsigned 32-bit integer. + * + * This function is also available under the `writeUint32BE` alias. + * + * ```js + * import { Buffer } from "internal:deno_node/polyfills/internal/buffer"; + * + * const buf = Buffer.allocUnsafe(4); + * + * buf.writeUInt32BE(0xfeedface, 0); + * + * console.log(buf); + * // Prints: + * ``` + * @since v0.5.5 + * @param value Number to be written to `buf`. + * @param [offset=0] Number of bytes to skip before starting to write. Must satisfy `0 <= offset <= buf.length - 4`. + * @return `offset` plus the number of bytes written. + */ + writeUInt32BE(value: number, offset?: number): number; + /** + * Writes `value` to `buf` at the specified `offset`. `value` must be a valid + * signed 8-bit integer. Behavior is undefined when `value` is anything other than + * a signed 8-bit integer. + * + * `value` is interpreted and written as a two's complement signed integer. + * + * ```js + * import { Buffer } from "internal:deno_node/polyfills/internal/buffer"; + * + * const buf = Buffer.allocUnsafe(2); + * + * buf.writeInt8(2, 0); + * buf.writeInt8(-2, 1); + * + * console.log(buf); + * // Prints: + * ``` + * @since v0.5.0 + * @param value Number to be written to `buf`. + * @param [offset=0] Number of bytes to skip before starting to write. Must satisfy `0 <= offset <= buf.length - 1`. + * @return `offset` plus the number of bytes written. + */ + writeInt8(value: number, offset?: number): number; + /** + * Writes `value` to `buf` at the specified `offset` as little-endian. The `value`must be a valid signed 16-bit integer. Behavior is undefined when `value` is + * anything other than a signed 16-bit integer. + * + * The `value` is interpreted and written as a two's complement signed integer. + * + * ```js + * import { Buffer } from "internal:deno_node/polyfills/internal/buffer"; + * + * const buf = Buffer.allocUnsafe(2); + * + * buf.writeInt16LE(0x0304, 0); + * + * console.log(buf); + * // Prints: + * ``` + * @since v0.5.5 + * @param value Number to be written to `buf`. + * @param [offset=0] Number of bytes to skip before starting to write. Must satisfy `0 <= offset <= buf.length - 2`. + * @return `offset` plus the number of bytes written. + */ + writeInt16LE(value: number, offset?: number): number; + /** + * Writes `value` to `buf` at the specified `offset` as big-endian. The `value`must be a valid signed 16-bit integer. Behavior is undefined when `value` is + * anything other than a signed 16-bit integer. + * + * The `value` is interpreted and written as a two's complement signed integer. + * + * ```js + * import { Buffer } from "internal:deno_node/polyfills/internal/buffer"; + * + * const buf = Buffer.allocUnsafe(2); + * + * buf.writeInt16BE(0x0102, 0); + * + * console.log(buf); + * // Prints: + * ``` + * @since v0.5.5 + * @param value Number to be written to `buf`. + * @param [offset=0] Number of bytes to skip before starting to write. Must satisfy `0 <= offset <= buf.length - 2`. + * @return `offset` plus the number of bytes written. + */ + writeInt16BE(value: number, offset?: number): number; + /** + * Writes `value` to `buf` at the specified `offset` as little-endian. The `value`must be a valid signed 32-bit integer. Behavior is undefined when `value` is + * anything other than a signed 32-bit integer. + * + * The `value` is interpreted and written as a two's complement signed integer. + * + * ```js + * import { Buffer } from "internal:deno_node/polyfills/internal/buffer"; + * + * const buf = Buffer.allocUnsafe(4); + * + * buf.writeInt32LE(0x05060708, 0); + * + * console.log(buf); + * // Prints: + * ``` + * @since v0.5.5 + * @param value Number to be written to `buf`. + * @param [offset=0] Number of bytes to skip before starting to write. Must satisfy `0 <= offset <= buf.length - 4`. + * @return `offset` plus the number of bytes written. + */ + writeInt32LE(value: number, offset?: number): number; + /** + * Writes `value` to `buf` at the specified `offset` as big-endian. The `value`must be a valid signed 32-bit integer. Behavior is undefined when `value` is + * anything other than a signed 32-bit integer. + * + * The `value` is interpreted and written as a two's complement signed integer. + * + * ```js + * import { Buffer } from "internal:deno_node/polyfills/internal/buffer"; + * + * const buf = Buffer.allocUnsafe(4); + * + * buf.writeInt32BE(0x01020304, 0); + * + * console.log(buf); + * // Prints: + * ``` + * @since v0.5.5 + * @param value Number to be written to `buf`. + * @param [offset=0] Number of bytes to skip before starting to write. Must satisfy `0 <= offset <= buf.length - 4`. + * @return `offset` plus the number of bytes written. + */ + writeInt32BE(value: number, offset?: number): number; + /** + * Writes `value` to `buf` at the specified `offset` as little-endian. Behavior is + * undefined when `value` is anything other than a JavaScript number. + * + * ```js + * import { Buffer } from "internal:deno_node/polyfills/internal/buffer"; + * + * const buf = Buffer.allocUnsafe(4); + * + * buf.writeFloatLE(0xcafebabe, 0); + * + * console.log(buf); + * // Prints: + * ``` + * @since v0.11.15 + * @param value Number to be written to `buf`. + * @param [offset=0] Number of bytes to skip before starting to write. Must satisfy `0 <= offset <= buf.length - 4`. + * @return `offset` plus the number of bytes written. + */ + writeFloatLE(value: number, offset?: number): number; + /** + * Writes `value` to `buf` at the specified `offset` as big-endian. Behavior is + * undefined when `value` is anything other than a JavaScript number. + * + * ```js + * import { Buffer } from "internal:deno_node/polyfills/internal/buffer"; + * + * const buf = Buffer.allocUnsafe(4); + * + * buf.writeFloatBE(0xcafebabe, 0); + * + * console.log(buf); + * // Prints: + * ``` + * @since v0.11.15 + * @param value Number to be written to `buf`. + * @param [offset=0] Number of bytes to skip before starting to write. Must satisfy `0 <= offset <= buf.length - 4`. + * @return `offset` plus the number of bytes written. + */ + writeFloatBE(value: number, offset?: number): number; + /** + * Writes `value` to `buf` at the specified `offset` as little-endian. The `value`must be a JavaScript number. Behavior is undefined when `value` is anything + * other than a JavaScript number. + * + * ```js + * import { Buffer } from "internal:deno_node/polyfills/internal/buffer"; + * + * const buf = Buffer.allocUnsafe(8); + * + * buf.writeDoubleLE(123.456, 0); + * + * console.log(buf); + * // Prints: + * ``` + * @since v0.11.15 + * @param value Number to be written to `buf`. + * @param [offset=0] Number of bytes to skip before starting to write. Must satisfy `0 <= offset <= buf.length - 8`. + * @return `offset` plus the number of bytes written. + */ + writeDoubleLE(value: number, offset?: number): number; + /** + * Writes `value` to `buf` at the specified `offset` as big-endian. The `value`must be a JavaScript number. Behavior is undefined when `value` is anything + * other than a JavaScript number. + * + * ```js + * import { Buffer } from "internal:deno_node/polyfills/internal/buffer"; + * + * const buf = Buffer.allocUnsafe(8); + * + * buf.writeDoubleBE(123.456, 0); + * + * console.log(buf); + * // Prints: + * ``` + * @since v0.11.15 + * @param value Number to be written to `buf`. + * @param [offset=0] Number of bytes to skip before starting to write. Must satisfy `0 <= offset <= buf.length - 8`. + * @return `offset` plus the number of bytes written. + */ + writeDoubleBE(value: number, offset?: number): number; + /** + * Fills `buf` with the specified `value`. If the `offset` and `end` are not given, + * the entire `buf` will be filled: + * + * ```js + * import { Buffer } from "internal:deno_node/polyfills/internal/buffer"; + * + * // Fill a `Buffer` with the ASCII character 'h'. + * + * const b = Buffer.allocUnsafe(50).fill('h'); + * + * console.log(b.toString()); + * // Prints: hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh + * ``` + * + * `value` is coerced to a `uint32` value if it is not a string, `Buffer`, or + * integer. If the resulting integer is greater than `255` (decimal), `buf` will be + * filled with `value & 255`. + * + * If the final write of a `fill()` operation falls on a multi-byte character, + * then only the bytes of that character that fit into `buf` are written: + * + * ```js + * import { Buffer } from "internal:deno_node/polyfills/internal/buffer"; + * + * // Fill a `Buffer` with character that takes up two bytes in UTF-8. + * + * console.log(Buffer.allocUnsafe(5).fill('\u0222')); + * // Prints: + * ``` + * + * If `value` contains invalid characters, it is truncated; if no valid + * fill data remains, an exception is thrown: + * + * ```js + * import { Buffer } from "internal:deno_node/polyfills/internal/buffer"; + * + * const buf = Buffer.allocUnsafe(5); + * + * console.log(buf.fill('a')); + * // Prints: + * console.log(buf.fill('aazz', 'hex')); + * // Prints: + * console.log(buf.fill('zz', 'hex')); + * // Throws an exception. + * ``` + * @since v0.5.0 + * @param value The value with which to fill `buf`. + * @param [offset=0] Number of bytes to skip before starting to fill `buf`. + * @param [end=buf.length] Where to stop filling `buf` (not inclusive). + * @param [encoding='utf8'] The encoding for `value` if `value` is a string. + * @return A reference to `buf`. + */ + fill( + value: string | Uint8Array | number, + offset?: number, + end?: number, + encoding?: Encoding, + ): this; + /** + * If `value` is: + * + * * a string, `value` is interpreted according to the character encoding in`encoding`. + * * a `Buffer` or [`Uint8Array`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Uint8Array), `value` will be used in its entirety. + * To compare a partial `Buffer`, use `buf.slice()`. + * * a number, `value` will be interpreted as an unsigned 8-bit integer + * value between `0` and `255`. + * + * ```js + * import { Buffer } from "internal:deno_node/polyfills/internal/buffer"; + * + * const buf = Buffer.from('this is a buffer'); + * + * console.log(buf.indexOf('this')); + * // Prints: 0 + * console.log(buf.indexOf('is')); + * // Prints: 2 + * console.log(buf.indexOf(Buffer.from('a buffer'))); + * // Prints: 8 + * console.log(buf.indexOf(97)); + * // Prints: 8 (97 is the decimal ASCII value for 'a') + * console.log(buf.indexOf(Buffer.from('a buffer example'))); + * // Prints: -1 + * console.log(buf.indexOf(Buffer.from('a buffer example').slice(0, 8))); + * // Prints: 8 + * + * const utf16Buffer = Buffer.from('\u039a\u0391\u03a3\u03a3\u0395', 'utf16le'); + * + * console.log(utf16Buffer.indexOf('\u03a3', 0, 'utf16le')); + * // Prints: 4 + * console.log(utf16Buffer.indexOf('\u03a3', -4, 'utf16le')); + * // Prints: 6 + * ``` + * + * If `value` is not a string, number, or `Buffer`, this method will throw a`TypeError`. If `value` is a number, it will be coerced to a valid byte value, + * an integer between 0 and 255. + * + * If `byteOffset` is not a number, it will be coerced to a number. If the result + * of coercion is `NaN` or `0`, then the entire buffer will be searched. This + * behavior matches [`String.prototype.indexOf()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/indexOf). + * + * ```js + * import { Buffer } from "internal:deno_node/polyfills/internal/buffer"; + * + * const b = Buffer.from('abcdef'); + * + * // Passing a value that's a number, but not a valid byte. + * // Prints: 2, equivalent to searching for 99 or 'c'. + * console.log(b.indexOf(99.9)); + * console.log(b.indexOf(256 + 99)); + * + * // Passing a byteOffset that coerces to NaN or 0. + * // Prints: 1, searching the whole buffer. + * console.log(b.indexOf('b', undefined)); + * console.log(b.indexOf('b', {})); + * console.log(b.indexOf('b', null)); + * console.log(b.indexOf('b', [])); + * ``` + * + * If `value` is an empty string or empty `Buffer` and `byteOffset` is less + * than `buf.length`, `byteOffset` will be returned. If `value` is empty and`byteOffset` is at least `buf.length`, `buf.length` will be returned. + * @since v1.5.0 + * @param value What to search for. + * @param [byteOffset=0] Where to begin searching in `buf`. If negative, then offset is calculated from the end of `buf`. + * @param [encoding='utf8'] If `value` is a string, this is the encoding used to determine the binary representation of the string that will be searched for in `buf`. + * @return The index of the first occurrence of `value` in `buf`, or `-1` if `buf` does not contain `value`. + */ + indexOf( + value: string | number | Uint8Array, + byteOffset?: number, + encoding?: Encoding, + ): number; + /** + * Identical to `buf.indexOf()`, except the last occurrence of `value` is found + * rather than the first occurrence. + * + * ```js + * import { Buffer } from "internal:deno_node/polyfills/internal/buffer"; + * + * const buf = Buffer.from('this buffer is a buffer'); + * + * console.log(buf.lastIndexOf('this')); + * // Prints: 0 + * console.log(buf.lastIndexOf('buffer')); + * // Prints: 17 + * console.log(buf.lastIndexOf(Buffer.from('buffer'))); + * // Prints: 17 + * console.log(buf.lastIndexOf(97)); + * // Prints: 15 (97 is the decimal ASCII value for 'a') + * console.log(buf.lastIndexOf(Buffer.from('yolo'))); + * // Prints: -1 + * console.log(buf.lastIndexOf('buffer', 5)); + * // Prints: 5 + * console.log(buf.lastIndexOf('buffer', 4)); + * // Prints: -1 + * + * const utf16Buffer = Buffer.from('\u039a\u0391\u03a3\u03a3\u0395', 'utf16le'); + * + * console.log(utf16Buffer.lastIndexOf('\u03a3', undefined, 'utf16le')); + * // Prints: 6 + * console.log(utf16Buffer.lastIndexOf('\u03a3', -5, 'utf16le')); + * // Prints: 4 + * ``` + * + * If `value` is not a string, number, or `Buffer`, this method will throw a`TypeError`. If `value` is a number, it will be coerced to a valid byte value, + * an integer between 0 and 255. + * + * If `byteOffset` is not a number, it will be coerced to a number. Any arguments + * that coerce to `NaN`, like `{}` or `undefined`, will search the whole buffer. + * This behavior matches [`String.prototype.lastIndexOf()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/lastIndexOf). + * + * ```js + * import { Buffer } from "internal:deno_node/polyfills/internal/buffer"; + * + * const b = Buffer.from('abcdef'); + * + * // Passing a value that's a number, but not a valid byte. + * // Prints: 2, equivalent to searching for 99 or 'c'. + * console.log(b.lastIndexOf(99.9)); + * console.log(b.lastIndexOf(256 + 99)); + * + * // Passing a byteOffset that coerces to NaN. + * // Prints: 1, searching the whole buffer. + * console.log(b.lastIndexOf('b', undefined)); + * console.log(b.lastIndexOf('b', {})); + * + * // Passing a byteOffset that coerces to 0. + * // Prints: -1, equivalent to passing 0. + * console.log(b.lastIndexOf('b', null)); + * console.log(b.lastIndexOf('b', [])); + * ``` + * + * If `value` is an empty string or empty `Buffer`, `byteOffset` will be returned. + * @since v6.0.0 + * @param value What to search for. + * @param [byteOffset=buf.length - 1] Where to begin searching in `buf`. If negative, then offset is calculated from the end of `buf`. + * @param [encoding='utf8'] If `value` is a string, this is the encoding used to determine the binary representation of the string that will be searched for in `buf`. + * @return The index of the last occurrence of `value` in `buf`, or `-1` if `buf` does not contain `value`. + */ + lastIndexOf( + value: string | number | Uint8Array, + byteOffset?: number, + encoding?: Encoding, + ): number; + /** + * Creates and returns an [iterator](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Iteration_protocols) of `[index, byte]` pairs from the contents + * of `buf`. + * + * ```js + * import { Buffer } from "internal:deno_node/polyfills/internal/buffer"; + * + * // Log the entire contents of a `Buffer`. + * + * const buf = Buffer.from('buffer'); + * + * for (const pair of buf.entries()) { + * console.log(pair); + * } + * // Prints: + * // [0, 98] + * // [1, 117] + * // [2, 102] + * // [3, 102] + * // [4, 101] + * // [5, 114] + * ``` + * @since v1.1.0 + */ + entries(): IterableIterator<[number, number]>; + /** + * Equivalent to `buf.indexOf() !== -1`. + * + * ```js + * import { Buffer } from "internal:deno_node/polyfills/internal/buffer"; + * + * const buf = Buffer.from('this is a buffer'); + * + * console.log(buf.includes('this')); + * // Prints: true + * console.log(buf.includes('is')); + * // Prints: true + * console.log(buf.includes(Buffer.from('a buffer'))); + * // Prints: true + * console.log(buf.includes(97)); + * // Prints: true (97 is the decimal ASCII value for 'a') + * console.log(buf.includes(Buffer.from('a buffer example'))); + * // Prints: false + * console.log(buf.includes(Buffer.from('a buffer example').slice(0, 8))); + * // Prints: true + * console.log(buf.includes('this', 4)); + * // Prints: false + * ``` + * @since v5.3.0 + * @param value What to search for. + * @param [byteOffset=0] Where to begin searching in `buf`. If negative, then offset is calculated from the end of `buf`. + * @param [encoding='utf8'] If `value` is a string, this is its encoding. + * @return `true` if `value` was found in `buf`, `false` otherwise. + */ + includes( + value: string | number | Buffer, + byteOffset?: number, + encoding?: Encoding, + ): boolean; + /** + * Creates and returns an [iterator](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Iteration_protocols) of `buf` keys (indices). + * + * ```js + * import { Buffer } from "internal:deno_node/polyfills/internal/buffer"; + * + * const buf = Buffer.from('buffer'); + * + * for (const key of buf.keys()) { + * console.log(key); + * } + * // Prints: + * // 0 + * // 1 + * // 2 + * // 3 + * // 4 + * // 5 + * ``` + * @since v1.1.0 + */ + keys(): IterableIterator; + /** + * Creates and returns an [iterator](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Iteration_protocols) for `buf` values (bytes). This function is + * called automatically when a `Buffer` is used in a `for..of` statement. + * + * ```js + * import { Buffer } from "internal:deno_node/polyfills/internal/buffer"; + * + * const buf = Buffer.from('buffer'); + * + * for (const value of buf.values()) { + * console.log(value); + * } + * // Prints: + * // 98 + * // 117 + * // 102 + * // 102 + * // 101 + * // 114 + * + * for (const value of buf) { + * console.log(value); + * } + * // Prints: + * // 98 + * // 117 + * // 102 + * // 102 + * // 101 + * // 114 + * ``` + * @since v1.1.0 + */ + values(): IterableIterator; +} + +export const SlowBuffer: { + /** @deprecated since v6.0.0, use `Buffer.allocUnsafeSlow()` */ + new (size: number): Buffer; + prototype: Buffer; +}; + +export const atob: typeof globalThis.atob; +export const Blob: Blob; +export const btoa: typeof globalThis.btoa; +export const constants: { + MAX_LENGTH: number; + MAX_STRING_LENGTH: number; +}; +export const kMaxLength: number; +export const kStringMaxLength: number; + +declare const exports: { + atob: typeof atob; + Blob: Blob; + btoa: typeof btoa; + Buffer: Buffer; + constants: typeof constants; + kMaxLength: typeof kMaxLength; + kStringMaxLength: typeof kStringMaxLength; + SlowBuffer: typeof SlowBuffer; +}; + +export default exports; diff --git a/ext/node/polyfills/internal/buffer.mjs b/ext/node/polyfills/internal/buffer.mjs new file mode 100644 index 00000000000000..1600462cfe6942 --- /dev/null +++ b/ext/node/polyfills/internal/buffer.mjs @@ -0,0 +1,2607 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Copyright Feross Aboukhadijeh, and other contributors. All rights reserved. MIT license. + +import { TextDecoder, TextEncoder } from "internal:deno_web/08_text_encoding.js"; +import { codes } from "internal:deno_node/polyfills/internal/error_codes.ts"; +import { encodings } from "internal:deno_node/polyfills/internal_binding/string_decoder.ts"; +import { indexOfBuffer, indexOfNumber } from "internal:deno_node/polyfills/internal_binding/buffer.ts"; +import { + asciiToBytes, + base64ToBytes, + base64UrlToBytes, + bytesToAscii, + bytesToUtf16le, + hexToBytes, + utf16leToBytes, +} from "internal:deno_node/polyfills/internal_binding/_utils.ts"; +import { isAnyArrayBuffer, isArrayBufferView } from "internal:deno_node/polyfills/internal/util/types.ts"; +import { normalizeEncoding } from "internal:deno_node/polyfills/internal/util.mjs"; +import { validateBuffer } from "internal:deno_node/polyfills/internal/validators.mjs"; +import { isUint8Array } from "internal:deno_node/polyfills/internal/util/types.ts"; +import { forgivingBase64Encode, forgivingBase64UrlEncode } from "internal:deno_web/00_infra.js"; +import { atob, btoa } from "internal:deno_web/05_base64.js"; +import { Blob } from "internal:deno_web/09_file.js"; + +export { atob, btoa, Blob }; + +const utf8Encoder = new TextEncoder(); + +// Temporary buffers to convert numbers. +const float32Array = new Float32Array(1); +const uInt8Float32Array = new Uint8Array(float32Array.buffer); +const float64Array = new Float64Array(1); +const uInt8Float64Array = new Uint8Array(float64Array.buffer); + +// Check endianness. +float32Array[0] = -1; // 0xBF800000 +// Either it is [0, 0, 128, 191] or [191, 128, 0, 0]. It is not possible to +// check this with `os.endianness()` because that is determined at compile time. +export const bigEndian = uInt8Float32Array[3] === 0; + +export const kMaxLength = 2147483647; +export const kStringMaxLength = 536870888; +const MAX_UINT32 = 2 ** 32; + +const customInspectSymbol = + typeof Symbol === "function" && typeof Symbol["for"] === "function" + ? Symbol["for"]("nodejs.util.inspect.custom") + : null; + +const INSPECT_MAX_BYTES = 50; + +export const constants = { + MAX_LENGTH: kMaxLength, + MAX_STRING_LENGTH: kStringMaxLength, +}; + +Object.defineProperty(Buffer.prototype, "parent", { + enumerable: true, + get: function () { + if (!Buffer.isBuffer(this)) { + return void 0; + } + return this.buffer; + }, +}); + +Object.defineProperty(Buffer.prototype, "offset", { + enumerable: true, + get: function () { + if (!Buffer.isBuffer(this)) { + return void 0; + } + return this.byteOffset; + }, +}); + +function createBuffer(length) { + if (length > kMaxLength) { + throw new RangeError( + 'The value "' + length + '" is invalid for option "size"', + ); + } + const buf = new Uint8Array(length); + Object.setPrototypeOf(buf, Buffer.prototype); + return buf; +} + +export function Buffer(arg, encodingOrOffset, length) { + if (typeof arg === "number") { + if (typeof encodingOrOffset === "string") { + throw new codes.ERR_INVALID_ARG_TYPE( + "string", + "string", + arg, + ); + } + return _allocUnsafe(arg); + } + return _from(arg, encodingOrOffset, length); +} + +Buffer.poolSize = 8192; + +function _from(value, encodingOrOffset, length) { + if (typeof value === "string") { + return fromString(value, encodingOrOffset); + } + + if (typeof value === "object" && value !== null) { + if (isAnyArrayBuffer(value)) { + return fromArrayBuffer(value, encodingOrOffset, length); + } + + const valueOf = value.valueOf && value.valueOf(); + if ( + valueOf != null && + valueOf !== value && + (typeof valueOf === "string" || typeof valueOf === "object") + ) { + return _from(valueOf, encodingOrOffset, length); + } + + const b = fromObject(value); + if (b) { + return b; + } + + if (typeof value[Symbol.toPrimitive] === "function") { + const primitive = value[Symbol.toPrimitive]("string"); + if (typeof primitive === "string") { + return fromString(primitive, encodingOrOffset); + } + } + } + + throw new codes.ERR_INVALID_ARG_TYPE( + "first argument", + ["string", "Buffer", "ArrayBuffer", "Array", "Array-like Object"], + value, + ); +} + +Buffer.from = function from(value, encodingOrOffset, length) { + return _from(value, encodingOrOffset, length); +}; + +Object.setPrototypeOf(Buffer.prototype, Uint8Array.prototype); + +Object.setPrototypeOf(Buffer, Uint8Array); + +function assertSize(size) { + validateNumber(size, "size"); + if (!(size >= 0 && size <= kMaxLength)) { + throw new codes.ERR_INVALID_ARG_VALUE.RangeError("size", size); + } +} + +function _alloc(size, fill, encoding) { + assertSize(size); + + const buffer = createBuffer(size); + if (fill !== undefined) { + if (encoding !== undefined && typeof encoding !== "string") { + throw new codes.ERR_INVALID_ARG_TYPE( + "encoding", + "string", + encoding, + ); + } + return buffer.fill(fill, encoding); + } + return buffer; +} + +Buffer.alloc = function alloc(size, fill, encoding) { + return _alloc(size, fill, encoding); +}; + +function _allocUnsafe(size) { + assertSize(size); + return createBuffer(size < 0 ? 0 : checked(size) | 0); +} + +Buffer.allocUnsafe = function allocUnsafe(size) { + return _allocUnsafe(size); +}; + +Buffer.allocUnsafeSlow = function allocUnsafeSlow(size) { + return _allocUnsafe(size); +}; + +function fromString(string, encoding) { + if (typeof encoding !== "string" || encoding === "") { + encoding = "utf8"; + } + if (!Buffer.isEncoding(encoding)) { + throw new codes.ERR_UNKNOWN_ENCODING(encoding); + } + const length = byteLength(string, encoding) | 0; + let buf = createBuffer(length); + const actual = buf.write(string, encoding); + if (actual !== length) { + buf = buf.slice(0, actual); + } + return buf; +} + +function fromArrayLike(array) { + const length = array.length < 0 ? 0 : checked(array.length) | 0; + const buf = createBuffer(length); + for (let i = 0; i < length; i += 1) { + buf[i] = array[i] & 255; + } + return buf; +} + +function fromObject(obj) { + if (obj.length !== undefined || isAnyArrayBuffer(obj.buffer)) { + if (typeof obj.length !== "number") { + return createBuffer(0); + } + return fromArrayLike(obj); + } + + if (obj.type === "Buffer" && Array.isArray(obj.data)) { + return fromArrayLike(obj.data); + } +} + +function checked(length) { + if (length >= kMaxLength) { + throw new RangeError( + "Attempt to allocate Buffer larger than maximum size: 0x" + + kMaxLength.toString(16) + " bytes", + ); + } + return length | 0; +} + +export function SlowBuffer(length) { + assertSize(length); + return Buffer.alloc(+length); +} + +Object.setPrototypeOf(SlowBuffer.prototype, Uint8Array.prototype); + +Object.setPrototypeOf(SlowBuffer, Uint8Array); + +Buffer.isBuffer = function isBuffer(b) { + return b != null && b._isBuffer === true && b !== Buffer.prototype; +}; + +Buffer.compare = function compare(a, b) { + if (isInstance(a, Uint8Array)) { + a = Buffer.from(a, a.offset, a.byteLength); + } + if (isInstance(b, Uint8Array)) { + b = Buffer.from(b, b.offset, b.byteLength); + } + if (!Buffer.isBuffer(a) || !Buffer.isBuffer(b)) { + throw new TypeError( + 'The "buf1", "buf2" arguments must be one of type Buffer or Uint8Array', + ); + } + if (a === b) { + return 0; + } + let x = a.length; + let y = b.length; + for (let i = 0, len = Math.min(x, y); i < len; ++i) { + if (a[i] !== b[i]) { + x = a[i]; + y = b[i]; + break; + } + } + if (x < y) { + return -1; + } + if (y < x) { + return 1; + } + return 0; +}; + +Buffer.isEncoding = function isEncoding(encoding) { + return typeof encoding === "string" && encoding.length !== 0 && + normalizeEncoding(encoding) !== undefined; +}; + +Buffer.concat = function concat(list, length) { + if (!Array.isArray(list)) { + throw new codes.ERR_INVALID_ARG_TYPE("list", "Array", list); + } + + if (list.length === 0) { + return Buffer.alloc(0); + } + + if (length === undefined) { + length = 0; + for (let i = 0; i < list.length; i++) { + if (list[i].length) { + length += list[i].length; + } + } + } else { + validateOffset(length, "length"); + } + + const buffer = Buffer.allocUnsafe(length); + let pos = 0; + for (let i = 0; i < list.length; i++) { + const buf = list[i]; + if (!isUint8Array(buf)) { + // TODO(BridgeAR): This should not be of type ERR_INVALID_ARG_TYPE. + // Instead, find the proper error code for this. + throw new codes.ERR_INVALID_ARG_TYPE( + `list[${i}]`, + ["Buffer", "Uint8Array"], + list[i], + ); + } + pos += _copyActual(buf, buffer, pos, 0, buf.length); + } + + // Note: `length` is always equal to `buffer.length` at this point + if (pos < length) { + // Zero-fill the remaining bytes if the specified `length` was more than + // the actual total length, i.e. if we have some remaining allocated bytes + // there were not initialized. + buffer.fill(0, pos, length); + } + + return buffer; +}; + +function byteLength(string, encoding) { + if (typeof string !== "string") { + if (isArrayBufferView(string) || isAnyArrayBuffer(string)) { + return string.byteLength; + } + + throw new codes.ERR_INVALID_ARG_TYPE( + "string", + ["string", "Buffer", "ArrayBuffer"], + string, + ); + } + + const len = string.length; + const mustMatch = arguments.length > 2 && arguments[2] === true; + if (!mustMatch && len === 0) { + return 0; + } + + if (!encoding) { + return (mustMatch ? -1 : byteLengthUtf8(string)); + } + + const ops = getEncodingOps(encoding); + if (ops === undefined) { + return (mustMatch ? -1 : byteLengthUtf8(string)); + } + return ops.byteLength(string); +} + +Buffer.byteLength = byteLength; + +Buffer.prototype._isBuffer = true; + +function swap(b, n, m) { + const i = b[n]; + b[n] = b[m]; + b[m] = i; +} + +Buffer.prototype.swap16 = function swap16() { + const len = this.length; + if (len % 2 !== 0) { + throw new RangeError("Buffer size must be a multiple of 16-bits"); + } + for (let i = 0; i < len; i += 2) { + swap(this, i, i + 1); + } + return this; +}; + +Buffer.prototype.swap32 = function swap32() { + const len = this.length; + if (len % 4 !== 0) { + throw new RangeError("Buffer size must be a multiple of 32-bits"); + } + for (let i = 0; i < len; i += 4) { + swap(this, i, i + 3); + swap(this, i + 1, i + 2); + } + return this; +}; + +Buffer.prototype.swap64 = function swap64() { + const len = this.length; + if (len % 8 !== 0) { + throw new RangeError("Buffer size must be a multiple of 64-bits"); + } + for (let i = 0; i < len; i += 8) { + swap(this, i, i + 7); + swap(this, i + 1, i + 6); + swap(this, i + 2, i + 5); + swap(this, i + 3, i + 4); + } + return this; +}; + +Buffer.prototype.toString = function toString(encoding, start, end) { + if (arguments.length === 0) { + return this.utf8Slice(0, this.length); + } + + const len = this.length; + + if (start <= 0) { + start = 0; + } else if (start >= len) { + return ""; + } else { + start |= 0; + } + + if (end === undefined || end > len) { + end = len; + } else { + end |= 0; + } + + if (end <= start) { + return ""; + } + + if (encoding === undefined) { + return this.utf8Slice(start, end); + } + + const ops = getEncodingOps(encoding); + if (ops === undefined) { + throw new codes.ERR_UNKNOWN_ENCODING(encoding); + } + + return ops.slice(this, start, end); +}; + +Buffer.prototype.toLocaleString = Buffer.prototype.toString; + +Buffer.prototype.equals = function equals(b) { + if (!isUint8Array(b)) { + throw new codes.ERR_INVALID_ARG_TYPE( + "otherBuffer", + ["Buffer", "Uint8Array"], + b, + ); + } + if (this === b) { + return true; + } + return Buffer.compare(this, b) === 0; +}; + +Buffer.prototype.inspect = function inspect() { + let str = ""; + const max = INSPECT_MAX_BYTES; + str = this.toString("hex", 0, max).replace(/(.{2})/g, "$1 ").trim(); + if (this.length > max) { + str += " ... "; + } + return ""; +}; + +if (customInspectSymbol) { + Buffer.prototype[customInspectSymbol] = Buffer.prototype.inspect; +} + +Buffer.prototype.compare = function compare( + target, + start, + end, + thisStart, + thisEnd, +) { + if (isInstance(target, Uint8Array)) { + target = Buffer.from(target, target.offset, target.byteLength); + } + if (!Buffer.isBuffer(target)) { + throw new codes.ERR_INVALID_ARG_TYPE( + "target", + ["Buffer", "Uint8Array"], + target, + ); + } + + if (start === undefined) { + start = 0; + } else { + validateOffset(start, "targetStart", 0, kMaxLength); + } + + if (end === undefined) { + end = target.length; + } else { + validateOffset(end, "targetEnd", 0, target.length); + } + + if (thisStart === undefined) { + thisStart = 0; + } else { + validateOffset(start, "sourceStart", 0, kMaxLength); + } + + if (thisEnd === undefined) { + thisEnd = this.length; + } else { + validateOffset(end, "sourceEnd", 0, this.length); + } + + if ( + start < 0 || end > target.length || thisStart < 0 || + thisEnd > this.length + ) { + throw new codes.ERR_OUT_OF_RANGE("out of range index", "range"); + } + + if (thisStart >= thisEnd && start >= end) { + return 0; + } + if (thisStart >= thisEnd) { + return -1; + } + if (start >= end) { + return 1; + } + start >>>= 0; + end >>>= 0; + thisStart >>>= 0; + thisEnd >>>= 0; + if (this === target) { + return 0; + } + let x = thisEnd - thisStart; + let y = end - start; + const len = Math.min(x, y); + const thisCopy = this.slice(thisStart, thisEnd); + const targetCopy = target.slice(start, end); + for (let i = 0; i < len; ++i) { + if (thisCopy[i] !== targetCopy[i]) { + x = thisCopy[i]; + y = targetCopy[i]; + break; + } + } + if (x < y) { + return -1; + } + if (y < x) { + return 1; + } + return 0; +}; + +function bidirectionalIndexOf(buffer, val, byteOffset, encoding, dir) { + validateBuffer(buffer); + + if (typeof byteOffset === "string") { + encoding = byteOffset; + byteOffset = undefined; + } else if (byteOffset > 0x7fffffff) { + byteOffset = 0x7fffffff; + } else if (byteOffset < -0x80000000) { + byteOffset = -0x80000000; + } + byteOffset = +byteOffset; + if (Number.isNaN(byteOffset)) { + byteOffset = dir ? 0 : (buffer.length || buffer.byteLength); + } + dir = !!dir; + + if (typeof val === "number") { + return indexOfNumber(buffer, val >>> 0, byteOffset, dir); + } + + let ops; + if (encoding === undefined) { + ops = encodingOps.utf8; + } else { + ops = getEncodingOps(encoding); + } + + if (typeof val === "string") { + if (ops === undefined) { + throw new codes.ERR_UNKNOWN_ENCODING(encoding); + } + return ops.indexOf(buffer, val, byteOffset, dir); + } + + if (isUint8Array(val)) { + const encodingVal = ops === undefined ? encodingsMap.utf8 : ops.encodingVal; + return indexOfBuffer(buffer, val, byteOffset, encodingVal, dir); + } + + throw new codes.ERR_INVALID_ARG_TYPE( + "value", + ["number", "string", "Buffer", "Uint8Array"], + val, + ); +} + +Buffer.prototype.includes = function includes(val, byteOffset, encoding) { + return this.indexOf(val, byteOffset, encoding) !== -1; +}; + +Buffer.prototype.indexOf = function indexOf(val, byteOffset, encoding) { + return bidirectionalIndexOf(this, val, byteOffset, encoding, true); +}; + +Buffer.prototype.lastIndexOf = function lastIndexOf( + val, + byteOffset, + encoding, +) { + return bidirectionalIndexOf(this, val, byteOffset, encoding, false); +}; + +Buffer.prototype.asciiSlice = function asciiSlice(offset, length) { + if (offset === 0 && length === this.length) { + return bytesToAscii(this); + } else { + return bytesToAscii(this.slice(offset, length)); + } +}; + +Buffer.prototype.asciiWrite = function asciiWrite(string, offset, length) { + return blitBuffer(asciiToBytes(string), this, offset, length); +}; + +Buffer.prototype.base64Slice = function base64Slice( + offset, + length, +) { + if (offset === 0 && length === this.length) { + return forgivingBase64Encode(this); + } else { + return forgivingBase64Encode(this.slice(offset, length)); + } +}; + +Buffer.prototype.base64Write = function base64Write( + string, + offset, + length, +) { + return blitBuffer(base64ToBytes(string), this, offset, length); +}; + +Buffer.prototype.base64urlSlice = function base64urlSlice( + offset, + length, +) { + if (offset === 0 && length === this.length) { + return forgivingBase64UrlEncode(this); + } else { + return forgivingBase64UrlEncode(this.slice(offset, length)); + } +}; + +Buffer.prototype.base64urlWrite = function base64urlWrite( + string, + offset, + length, +) { + return blitBuffer(base64UrlToBytes(string), this, offset, length); +}; + +Buffer.prototype.hexWrite = function hexWrite(string, offset, length) { + return blitBuffer( + hexToBytes(string, this.length - offset), + this, + offset, + length, + ); +}; + +Buffer.prototype.hexSlice = function hexSlice(string, offset, length) { + return _hexSlice(this, string, offset, length); +}; + +Buffer.prototype.latin1Slice = function latin1Slice( + string, + offset, + length, +) { + return _latin1Slice(this, string, offset, length); +}; + +Buffer.prototype.latin1Write = function latin1Write( + string, + offset, + length, +) { + return blitBuffer(asciiToBytes(string), this, offset, length); +}; + +Buffer.prototype.ucs2Slice = function ucs2Slice(offset, length) { + if (offset === 0 && length === this.length) { + return bytesToUtf16le(this); + } else { + return bytesToUtf16le(this.slice(offset, length)); + } +}; + +Buffer.prototype.ucs2Write = function ucs2Write(string, offset, length) { + return blitBuffer( + utf16leToBytes(string, this.length - offset), + this, + offset, + length, + ); +}; + +Buffer.prototype.utf8Slice = function utf8Slice(string, offset, length) { + return _utf8Slice(this, string, offset, length); +}; + +Buffer.prototype.utf8Write = function utf8Write(string, offset, length) { + return blitBuffer( + utf8ToBytes(string, this.length - offset), + this, + offset, + length, + ); +}; + +Buffer.prototype.write = function write(string, offset, length, encoding) { + // Buffer#write(string); + if (offset === undefined) { + return this.utf8Write(string, 0, this.length); + } + // Buffer#write(string, encoding) + if (length === undefined && typeof offset === "string") { + encoding = offset; + length = this.length; + offset = 0; + + // Buffer#write(string, offset[, length][, encoding]) + } else { + validateOffset(offset, "offset", 0, this.length); + + const remaining = this.length - offset; + + if (length === undefined) { + length = remaining; + } else if (typeof length === "string") { + encoding = length; + length = remaining; + } else { + validateOffset(length, "length", 0, this.length); + if (length > remaining) { + length = remaining; + } + } + } + + if (!encoding) { + return this.utf8Write(string, offset, length); + } + + const ops = getEncodingOps(encoding); + if (ops === undefined) { + throw new codes.ERR_UNKNOWN_ENCODING(encoding); + } + return ops.write(this, string, offset, length); +}; + +Buffer.prototype.toJSON = function toJSON() { + return { + type: "Buffer", + data: Array.prototype.slice.call(this._arr || this, 0), + }; +}; +function fromArrayBuffer(obj, byteOffset, length) { + // Convert byteOffset to integer + if (byteOffset === undefined) { + byteOffset = 0; + } else { + byteOffset = +byteOffset; + if (Number.isNaN(byteOffset)) { + byteOffset = 0; + } + } + + const maxLength = obj.byteLength - byteOffset; + + if (maxLength < 0) { + throw new codes.ERR_BUFFER_OUT_OF_BOUNDS("offset"); + } + + if (length === undefined) { + length = maxLength; + } else { + // Convert length to non-negative integer. + length = +length; + if (length > 0) { + if (length > maxLength) { + throw new codes.ERR_BUFFER_OUT_OF_BOUNDS("length"); + } + } else { + length = 0; + } + } + + const buffer = new Uint8Array(obj, byteOffset, length); + Object.setPrototypeOf(buffer, Buffer.prototype); + return buffer; +} + +function _base64Slice(buf, start, end) { + if (start === 0 && end === buf.length) { + return forgivingBase64Encode(buf); + } else { + return forgivingBase64Encode(buf.slice(start, end)); + } +} + +const decoder = new TextDecoder(); + +function _utf8Slice(buf, start, end) { + return decoder.decode(buf.slice(start, end)); +} + +function _latin1Slice(buf, start, end) { + let ret = ""; + end = Math.min(buf.length, end); + for (let i = start; i < end; ++i) { + ret += String.fromCharCode(buf[i]); + } + return ret; +} + +function _hexSlice(buf, start, end) { + const len = buf.length; + if (!start || start < 0) { + start = 0; + } + if (!end || end < 0 || end > len) { + end = len; + } + let out = ""; + for (let i = start; i < end; ++i) { + out += hexSliceLookupTable[buf[i]]; + } + return out; +} + +Buffer.prototype.slice = function slice(start, end) { + const len = this.length; + start = ~~start; + end = end === void 0 ? len : ~~end; + if (start < 0) { + start += len; + if (start < 0) { + start = 0; + } + } else if (start > len) { + start = len; + } + if (end < 0) { + end += len; + if (end < 0) { + end = 0; + } + } else if (end > len) { + end = len; + } + if (end < start) { + end = start; + } + const newBuf = this.subarray(start, end); + Object.setPrototypeOf(newBuf, Buffer.prototype); + return newBuf; +}; + +Buffer.prototype.readUintLE = Buffer.prototype.readUIntLE = function readUIntLE( + offset, + byteLength, +) { + if (offset === undefined) { + throw new codes.ERR_INVALID_ARG_TYPE("offset", "number", offset); + } + if (byteLength === 6) { + return readUInt48LE(this, offset); + } + if (byteLength === 5) { + return readUInt40LE(this, offset); + } + if (byteLength === 3) { + return readUInt24LE(this, offset); + } + if (byteLength === 4) { + return this.readUInt32LE(offset); + } + if (byteLength === 2) { + return this.readUInt16LE(offset); + } + if (byteLength === 1) { + return this.readUInt8(offset); + } + + boundsError(byteLength, 6, "byteLength"); +}; + +Buffer.prototype.readUintBE = Buffer.prototype.readUIntBE = function readUIntBE( + offset, + byteLength, +) { + if (offset === undefined) { + throw new codes.ERR_INVALID_ARG_TYPE("offset", "number", offset); + } + if (byteLength === 6) { + return readUInt48BE(this, offset); + } + if (byteLength === 5) { + return readUInt40BE(this, offset); + } + if (byteLength === 3) { + return readUInt24BE(this, offset); + } + if (byteLength === 4) { + return this.readUInt32BE(offset); + } + if (byteLength === 2) { + return this.readUInt16BE(offset); + } + if (byteLength === 1) { + return this.readUInt8(offset); + } + + boundsError(byteLength, 6, "byteLength"); +}; + +Buffer.prototype.readUint8 = Buffer.prototype.readUInt8 = function readUInt8( + offset = 0, +) { + validateNumber(offset, "offset"); + const val = this[offset]; + if (val === undefined) { + boundsError(offset, this.length - 1); + } + + return val; +}; + +Buffer.prototype.readUint16BE = Buffer.prototype.readUInt16BE = readUInt16BE; + +Buffer.prototype.readUint16LE = + Buffer.prototype.readUInt16LE = + function readUInt16LE(offset = 0) { + validateNumber(offset, "offset"); + const first = this[offset]; + const last = this[offset + 1]; + if (first === undefined || last === undefined) { + boundsError(offset, this.length - 2); + } + + return first + last * 2 ** 8; + }; + +Buffer.prototype.readUint32LE = + Buffer.prototype.readUInt32LE = + function readUInt32LE(offset = 0) { + validateNumber(offset, "offset"); + const first = this[offset]; + const last = this[offset + 3]; + if (first === undefined || last === undefined) { + boundsError(offset, this.length - 4); + } + + return first + + this[++offset] * 2 ** 8 + + this[++offset] * 2 ** 16 + + last * 2 ** 24; + }; + +Buffer.prototype.readUint32BE = Buffer.prototype.readUInt32BE = readUInt32BE; + +Buffer.prototype.readBigUint64LE = + Buffer.prototype.readBigUInt64LE = + defineBigIntMethod( + function readBigUInt64LE(offset) { + offset = offset >>> 0; + validateNumber(offset, "offset"); + const first = this[offset]; + const last = this[offset + 7]; + if (first === void 0 || last === void 0) { + boundsError(offset, this.length - 8); + } + const lo = first + this[++offset] * 2 ** 8 + + this[++offset] * 2 ** 16 + + this[++offset] * 2 ** 24; + const hi = this[++offset] + this[++offset] * 2 ** 8 + + this[++offset] * 2 ** 16 + last * 2 ** 24; + return BigInt(lo) + (BigInt(hi) << BigInt(32)); + }, + ); + +Buffer.prototype.readBigUint64BE = + Buffer.prototype.readBigUInt64BE = + defineBigIntMethod( + function readBigUInt64BE(offset) { + offset = offset >>> 0; + validateNumber(offset, "offset"); + const first = this[offset]; + const last = this[offset + 7]; + if (first === void 0 || last === void 0) { + boundsError(offset, this.length - 8); + } + const hi = first * 2 ** 24 + this[++offset] * 2 ** 16 + + this[++offset] * 2 ** 8 + this[++offset]; + const lo = this[++offset] * 2 ** 24 + this[++offset] * 2 ** 16 + + this[++offset] * 2 ** 8 + last; + return (BigInt(hi) << BigInt(32)) + BigInt(lo); + }, + ); + +Buffer.prototype.readIntLE = function readIntLE( + offset, + byteLength, +) { + if (offset === undefined) { + throw new codes.ERR_INVALID_ARG_TYPE("offset", "number", offset); + } + if (byteLength === 6) { + return readInt48LE(this, offset); + } + if (byteLength === 5) { + return readInt40LE(this, offset); + } + if (byteLength === 3) { + return readInt24LE(this, offset); + } + if (byteLength === 4) { + return this.readInt32LE(offset); + } + if (byteLength === 2) { + return this.readInt16LE(offset); + } + if (byteLength === 1) { + return this.readInt8(offset); + } + + boundsError(byteLength, 6, "byteLength"); +}; + +Buffer.prototype.readIntBE = function readIntBE(offset, byteLength) { + if (offset === undefined) { + throw new codes.ERR_INVALID_ARG_TYPE("offset", "number", offset); + } + if (byteLength === 6) { + return readInt48BE(this, offset); + } + if (byteLength === 5) { + return readInt40BE(this, offset); + } + if (byteLength === 3) { + return readInt24BE(this, offset); + } + if (byteLength === 4) { + return this.readInt32BE(offset); + } + if (byteLength === 2) { + return this.readInt16BE(offset); + } + if (byteLength === 1) { + return this.readInt8(offset); + } + + boundsError(byteLength, 6, "byteLength"); +}; + +Buffer.prototype.readInt8 = function readInt8(offset = 0) { + validateNumber(offset, "offset"); + const val = this[offset]; + if (val === undefined) { + boundsError(offset, this.length - 1); + } + + return val | (val & 2 ** 7) * 0x1fffffe; +}; + +Buffer.prototype.readInt16LE = function readInt16LE(offset = 0) { + validateNumber(offset, "offset"); + const first = this[offset]; + const last = this[offset + 1]; + if (first === undefined || last === undefined) { + boundsError(offset, this.length - 2); + } + + const val = first + last * 2 ** 8; + return val | (val & 2 ** 15) * 0x1fffe; +}; + +Buffer.prototype.readInt16BE = function readInt16BE(offset = 0) { + validateNumber(offset, "offset"); + const first = this[offset]; + const last = this[offset + 1]; + if (first === undefined || last === undefined) { + boundsError(offset, this.length - 2); + } + + const val = first * 2 ** 8 + last; + return val | (val & 2 ** 15) * 0x1fffe; +}; + +Buffer.prototype.readInt32LE = function readInt32LE(offset = 0) { + validateNumber(offset, "offset"); + const first = this[offset]; + const last = this[offset + 3]; + if (first === undefined || last === undefined) { + boundsError(offset, this.length - 4); + } + + return first + + this[++offset] * 2 ** 8 + + this[++offset] * 2 ** 16 + + (last << 24); // Overflow +}; + +Buffer.prototype.readInt32BE = function readInt32BE(offset = 0) { + validateNumber(offset, "offset"); + const first = this[offset]; + const last = this[offset + 3]; + if (first === undefined || last === undefined) { + boundsError(offset, this.length - 4); + } + + return (first << 24) + // Overflow + this[++offset] * 2 ** 16 + + this[++offset] * 2 ** 8 + + last; +}; + +Buffer.prototype.readBigInt64LE = defineBigIntMethod( + function readBigInt64LE(offset) { + offset = offset >>> 0; + validateNumber(offset, "offset"); + const first = this[offset]; + const last = this[offset + 7]; + if (first === void 0 || last === void 0) { + boundsError(offset, this.length - 8); + } + const val = this[offset + 4] + this[offset + 5] * 2 ** 8 + + this[offset + 6] * 2 ** 16 + (last << 24); + return (BigInt(val) << BigInt(32)) + + BigInt( + first + this[++offset] * 2 ** 8 + this[++offset] * 2 ** 16 + + this[++offset] * 2 ** 24, + ); + }, +); + +Buffer.prototype.readBigInt64BE = defineBigIntMethod( + function readBigInt64BE(offset) { + offset = offset >>> 0; + validateNumber(offset, "offset"); + const first = this[offset]; + const last = this[offset + 7]; + if (first === void 0 || last === void 0) { + boundsError(offset, this.length - 8); + } + const val = (first << 24) + this[++offset] * 2 ** 16 + + this[++offset] * 2 ** 8 + this[++offset]; + return (BigInt(val) << BigInt(32)) + + BigInt( + this[++offset] * 2 ** 24 + this[++offset] * 2 ** 16 + + this[++offset] * 2 ** 8 + last, + ); + }, +); + +Buffer.prototype.readFloatLE = function readFloatLE(offset) { + return bigEndian + ? readFloatBackwards(this, offset) + : readFloatForwards(this, offset); +}; + +Buffer.prototype.readFloatBE = function readFloatBE(offset) { + return bigEndian + ? readFloatForwards(this, offset) + : readFloatBackwards(this, offset); +}; + +Buffer.prototype.readDoubleLE = function readDoubleLE(offset) { + return bigEndian + ? readDoubleBackwards(this, offset) + : readDoubleForwards(this, offset); +}; + +Buffer.prototype.readDoubleBE = function readDoubleBE(offset) { + return bigEndian + ? readDoubleForwards(this, offset) + : readDoubleBackwards(this, offset); +}; + +Buffer.prototype.writeUintLE = + Buffer.prototype.writeUIntLE = + function writeUIntLE(value, offset, byteLength) { + if (byteLength === 6) { + return writeU_Int48LE(this, value, offset, 0, 0xffffffffffff); + } + if (byteLength === 5) { + return writeU_Int40LE(this, value, offset, 0, 0xffffffffff); + } + if (byteLength === 3) { + return writeU_Int24LE(this, value, offset, 0, 0xffffff); + } + if (byteLength === 4) { + return writeU_Int32LE(this, value, offset, 0, 0xffffffff); + } + if (byteLength === 2) { + return writeU_Int16LE(this, value, offset, 0, 0xffff); + } + if (byteLength === 1) { + return writeU_Int8(this, value, offset, 0, 0xff); + } + + boundsError(byteLength, 6, "byteLength"); + }; + +Buffer.prototype.writeUintBE = + Buffer.prototype.writeUIntBE = + function writeUIntBE(value, offset, byteLength) { + if (byteLength === 6) { + return writeU_Int48BE(this, value, offset, 0, 0xffffffffffff); + } + if (byteLength === 5) { + return writeU_Int40BE(this, value, offset, 0, 0xffffffffff); + } + if (byteLength === 3) { + return writeU_Int24BE(this, value, offset, 0, 0xffffff); + } + if (byteLength === 4) { + return writeU_Int32BE(this, value, offset, 0, 0xffffffff); + } + if (byteLength === 2) { + return writeU_Int16BE(this, value, offset, 0, 0xffff); + } + if (byteLength === 1) { + return writeU_Int8(this, value, offset, 0, 0xff); + } + + boundsError(byteLength, 6, "byteLength"); + }; + +Buffer.prototype.writeUint8 = Buffer.prototype.writeUInt8 = function writeUInt8( + value, + offset = 0, +) { + return writeU_Int8(this, value, offset, 0, 0xff); +}; + +Buffer.prototype.writeUint16LE = + Buffer.prototype.writeUInt16LE = + function writeUInt16LE(value, offset = 0) { + return writeU_Int16LE(this, value, offset, 0, 0xffff); + }; + +Buffer.prototype.writeUint16BE = + Buffer.prototype.writeUInt16BE = + function writeUInt16BE(value, offset = 0) { + return writeU_Int16BE(this, value, offset, 0, 0xffff); + }; + +Buffer.prototype.writeUint32LE = + Buffer.prototype.writeUInt32LE = + function writeUInt32LE(value, offset = 0) { + return _writeUInt32LE(this, value, offset, 0, 0xffffffff); + }; + +Buffer.prototype.writeUint32BE = + Buffer.prototype.writeUInt32BE = + function writeUInt32BE(value, offset = 0) { + return _writeUInt32BE(this, value, offset, 0, 0xffffffff); + }; + +function wrtBigUInt64LE(buf, value, offset, min, max) { + checkIntBI(value, min, max, buf, offset, 7); + let lo = Number(value & BigInt(4294967295)); + buf[offset++] = lo; + lo = lo >> 8; + buf[offset++] = lo; + lo = lo >> 8; + buf[offset++] = lo; + lo = lo >> 8; + buf[offset++] = lo; + let hi = Number(value >> BigInt(32) & BigInt(4294967295)); + buf[offset++] = hi; + hi = hi >> 8; + buf[offset++] = hi; + hi = hi >> 8; + buf[offset++] = hi; + hi = hi >> 8; + buf[offset++] = hi; + return offset; +} + +function wrtBigUInt64BE(buf, value, offset, min, max) { + checkIntBI(value, min, max, buf, offset, 7); + let lo = Number(value & BigInt(4294967295)); + buf[offset + 7] = lo; + lo = lo >> 8; + buf[offset + 6] = lo; + lo = lo >> 8; + buf[offset + 5] = lo; + lo = lo >> 8; + buf[offset + 4] = lo; + let hi = Number(value >> BigInt(32) & BigInt(4294967295)); + buf[offset + 3] = hi; + hi = hi >> 8; + buf[offset + 2] = hi; + hi = hi >> 8; + buf[offset + 1] = hi; + hi = hi >> 8; + buf[offset] = hi; + return offset + 8; +} + +Buffer.prototype.writeBigUint64LE = + Buffer.prototype.writeBigUInt64LE = + defineBigIntMethod( + function writeBigUInt64LE(value, offset = 0) { + return wrtBigUInt64LE( + this, + value, + offset, + BigInt(0), + BigInt("0xffffffffffffffff"), + ); + }, + ); + +Buffer.prototype.writeBigUint64BE = + Buffer.prototype.writeBigUInt64BE = + defineBigIntMethod( + function writeBigUInt64BE(value, offset = 0) { + return wrtBigUInt64BE( + this, + value, + offset, + BigInt(0), + BigInt("0xffffffffffffffff"), + ); + }, + ); + +Buffer.prototype.writeIntLE = function writeIntLE( + value, + offset, + byteLength, +) { + if (byteLength === 6) { + return writeU_Int48LE( + this, + value, + offset, + -0x800000000000, + 0x7fffffffffff, + ); + } + if (byteLength === 5) { + return writeU_Int40LE(this, value, offset, -0x8000000000, 0x7fffffffff); + } + if (byteLength === 3) { + return writeU_Int24LE(this, value, offset, -0x800000, 0x7fffff); + } + if (byteLength === 4) { + return writeU_Int32LE(this, value, offset, -0x80000000, 0x7fffffff); + } + if (byteLength === 2) { + return writeU_Int16LE(this, value, offset, -0x8000, 0x7fff); + } + if (byteLength === 1) { + return writeU_Int8(this, value, offset, -0x80, 0x7f); + } + + boundsError(byteLength, 6, "byteLength"); +}; + +Buffer.prototype.writeIntBE = function writeIntBE( + value, + offset, + byteLength, +) { + if (byteLength === 6) { + return writeU_Int48BE( + this, + value, + offset, + -0x800000000000, + 0x7fffffffffff, + ); + } + if (byteLength === 5) { + return writeU_Int40BE(this, value, offset, -0x8000000000, 0x7fffffffff); + } + if (byteLength === 3) { + return writeU_Int24BE(this, value, offset, -0x800000, 0x7fffff); + } + if (byteLength === 4) { + return writeU_Int32BE(this, value, offset, -0x80000000, 0x7fffffff); + } + if (byteLength === 2) { + return writeU_Int16BE(this, value, offset, -0x8000, 0x7fff); + } + if (byteLength === 1) { + return writeU_Int8(this, value, offset, -0x80, 0x7f); + } + + boundsError(byteLength, 6, "byteLength"); +}; + +Buffer.prototype.writeInt8 = function writeInt8(value, offset = 0) { + return writeU_Int8(this, value, offset, -0x80, 0x7f); +}; + +Buffer.prototype.writeInt16LE = function writeInt16LE(value, offset = 0) { + return writeU_Int16LE(this, value, offset, -0x8000, 0x7fff); +}; + +Buffer.prototype.writeInt16BE = function writeInt16BE( + value, + offset = 0, +) { + return writeU_Int16BE(this, value, offset, -0x8000, 0x7fff); +}; + +Buffer.prototype.writeInt32LE = function writeInt32LE(value, offset = 0) { + return writeU_Int32LE(this, value, offset, -0x80000000, 0x7fffffff); +}; + +Buffer.prototype.writeInt32BE = function writeInt32BE(value, offset = 0) { + return writeU_Int32BE(this, value, offset, -0x80000000, 0x7fffffff); +}; + +Buffer.prototype.writeBigInt64LE = defineBigIntMethod( + function writeBigInt64LE(value, offset = 0) { + return wrtBigUInt64LE( + this, + value, + offset, + -BigInt("0x8000000000000000"), + BigInt("0x7fffffffffffffff"), + ); + }, +); + +Buffer.prototype.writeBigInt64BE = defineBigIntMethod( + function writeBigInt64BE(value, offset = 0) { + return wrtBigUInt64BE( + this, + value, + offset, + -BigInt("0x8000000000000000"), + BigInt("0x7fffffffffffffff"), + ); + }, +); + +Buffer.prototype.writeFloatLE = function writeFloatLE( + value, + offset, +) { + return bigEndian + ? writeFloatBackwards(this, value, offset) + : writeFloatForwards(this, value, offset); +}; + +Buffer.prototype.writeFloatBE = function writeFloatBE( + value, + offset, +) { + return bigEndian + ? writeFloatForwards(this, value, offset) + : writeFloatBackwards(this, value, offset); +}; + +Buffer.prototype.writeDoubleLE = function writeDoubleLE( + value, + offset, +) { + return bigEndian + ? writeDoubleBackwards(this, value, offset) + : writeDoubleForwards(this, value, offset); +}; + +Buffer.prototype.writeDoubleBE = function writeDoubleBE( + value, + offset, +) { + return bigEndian + ? writeDoubleForwards(this, value, offset) + : writeDoubleBackwards(this, value, offset); +}; + +Buffer.prototype.copy = function copy( + target, + targetStart, + sourceStart, + sourceEnd, +) { + if (!isUint8Array(this)) { + throw new codes.ERR_INVALID_ARG_TYPE( + "source", + ["Buffer", "Uint8Array"], + this, + ); + } + + if (!isUint8Array(target)) { + throw new codes.ERR_INVALID_ARG_TYPE( + "target", + ["Buffer", "Uint8Array"], + target, + ); + } + + if (targetStart === undefined) { + targetStart = 0; + } else { + targetStart = toInteger(targetStart, 0); + if (targetStart < 0) { + throw new codes.ERR_OUT_OF_RANGE("targetStart", ">= 0", targetStart); + } + } + + if (sourceStart === undefined) { + sourceStart = 0; + } else { + sourceStart = toInteger(sourceStart, 0); + if (sourceStart < 0) { + throw new codes.ERR_OUT_OF_RANGE("sourceStart", ">= 0", sourceStart); + } + if (sourceStart >= MAX_UINT32) { + throw new codes.ERR_OUT_OF_RANGE( + "sourceStart", + `< ${MAX_UINT32}`, + sourceStart, + ); + } + } + + if (sourceEnd === undefined) { + sourceEnd = this.length; + } else { + sourceEnd = toInteger(sourceEnd, 0); + if (sourceEnd < 0) { + throw new codes.ERR_OUT_OF_RANGE("sourceEnd", ">= 0", sourceEnd); + } + if (sourceEnd >= MAX_UINT32) { + throw new codes.ERR_OUT_OF_RANGE( + "sourceEnd", + `< ${MAX_UINT32}`, + sourceEnd, + ); + } + } + + if (targetStart >= target.length) { + return 0; + } + + if (sourceEnd > 0 && sourceEnd < sourceStart) { + sourceEnd = sourceStart; + } + if (sourceEnd === sourceStart) { + return 0; + } + if (target.length === 0 || this.length === 0) { + return 0; + } + + if (sourceEnd > this.length) { + sourceEnd = this.length; + } + + if (target.length - targetStart < sourceEnd - sourceStart) { + sourceEnd = target.length - targetStart + sourceStart; + } + + const len = sourceEnd - sourceStart; + if ( + this === target && typeof Uint8Array.prototype.copyWithin === "function" + ) { + this.copyWithin(targetStart, sourceStart, sourceEnd); + } else { + Uint8Array.prototype.set.call( + target, + this.subarray(sourceStart, sourceEnd), + targetStart, + ); + } + return len; +}; + +Buffer.prototype.fill = function fill(val, start, end, encoding) { + if (typeof val === "string") { + if (typeof start === "string") { + encoding = start; + start = 0; + end = this.length; + } else if (typeof end === "string") { + encoding = end; + end = this.length; + } + if (encoding !== void 0 && typeof encoding !== "string") { + throw new TypeError("encoding must be a string"); + } + if (typeof encoding === "string" && !Buffer.isEncoding(encoding)) { + throw new TypeError("Unknown encoding: " + encoding); + } + if (val.length === 1) { + const code = val.charCodeAt(0); + if (encoding === "utf8" && code < 128 || encoding === "latin1") { + val = code; + } + } + } else if (typeof val === "number") { + val = val & 255; + } else if (typeof val === "boolean") { + val = Number(val); + } + if (start < 0 || this.length < start || this.length < end) { + throw new RangeError("Out of range index"); + } + if (end <= start) { + return this; + } + start = start >>> 0; + end = end === void 0 ? this.length : end >>> 0; + if (!val) { + val = 0; + } + let i; + if (typeof val === "number") { + for (i = start; i < end; ++i) { + this[i] = val; + } + } else { + const bytes = Buffer.isBuffer(val) ? val : Buffer.from(val, encoding); + const len = bytes.length; + if (len === 0) { + throw new codes.ERR_INVALID_ARG_VALUE( + "value", + val, + ); + } + for (i = 0; i < end - start; ++i) { + this[i + start] = bytes[i % len]; + } + } + return this; +}; + +function checkBounds(buf, offset, byteLength2) { + validateNumber(offset, "offset"); + if (buf[offset] === void 0 || buf[offset + byteLength2] === void 0) { + boundsError(offset, buf.length - (byteLength2 + 1)); + } +} + +function checkIntBI(value, min, max, buf, offset, byteLength2) { + if (value > max || value < min) { + const n = typeof min === "bigint" ? "n" : ""; + let range; + if (byteLength2 > 3) { + if (min === 0 || min === BigInt(0)) { + range = `>= 0${n} and < 2${n} ** ${(byteLength2 + 1) * 8}${n}`; + } else { + range = `>= -(2${n} ** ${(byteLength2 + 1) * 8 - 1}${n}) and < 2 ** ${ + (byteLength2 + 1) * 8 - 1 + }${n}`; + } + } else { + range = `>= ${min}${n} and <= ${max}${n}`; + } + throw new codes.ERR_OUT_OF_RANGE("value", range, value); + } + checkBounds(buf, offset, byteLength2); +} + +function utf8ToBytes(string, units) { + units = units || Infinity; + let codePoint; + const length = string.length; + let leadSurrogate = null; + const bytes = []; + for (let i = 0; i < length; ++i) { + codePoint = string.charCodeAt(i); + if (codePoint > 55295 && codePoint < 57344) { + if (!leadSurrogate) { + if (codePoint > 56319) { + if ((units -= 3) > -1) { + bytes.push(239, 191, 189); + } + continue; + } else if (i + 1 === length) { + if ((units -= 3) > -1) { + bytes.push(239, 191, 189); + } + continue; + } + leadSurrogate = codePoint; + continue; + } + if (codePoint < 56320) { + if ((units -= 3) > -1) { + bytes.push(239, 191, 189); + } + leadSurrogate = codePoint; + continue; + } + codePoint = (leadSurrogate - 55296 << 10 | codePoint - 56320) + 65536; + } else if (leadSurrogate) { + if ((units -= 3) > -1) { + bytes.push(239, 191, 189); + } + } + leadSurrogate = null; + if (codePoint < 128) { + if ((units -= 1) < 0) { + break; + } + bytes.push(codePoint); + } else if (codePoint < 2048) { + if ((units -= 2) < 0) { + break; + } + bytes.push(codePoint >> 6 | 192, codePoint & 63 | 128); + } else if (codePoint < 65536) { + if ((units -= 3) < 0) { + break; + } + bytes.push( + codePoint >> 12 | 224, + codePoint >> 6 & 63 | 128, + codePoint & 63 | 128, + ); + } else if (codePoint < 1114112) { + if ((units -= 4) < 0) { + break; + } + bytes.push( + codePoint >> 18 | 240, + codePoint >> 12 & 63 | 128, + codePoint >> 6 & 63 | 128, + codePoint & 63 | 128, + ); + } else { + throw new Error("Invalid code point"); + } + } + return bytes; +} + +function blitBuffer(src, dst, offset, byteLength) { + let i; + const length = byteLength === undefined ? src.length : byteLength; + for (i = 0; i < length; ++i) { + if (i + offset >= dst.length || i >= src.length) { + break; + } + dst[i + offset] = src[i]; + } + return i; +} + +function isInstance(obj, type) { + return obj instanceof type || + obj != null && obj.constructor != null && + obj.constructor.name != null && obj.constructor.name === type.name; +} + +const hexSliceLookupTable = function () { + const alphabet = "0123456789abcdef"; + const table = new Array(256); + for (let i = 0; i < 16; ++i) { + const i16 = i * 16; + for (let j = 0; j < 16; ++j) { + table[i16 + j] = alphabet[i] + alphabet[j]; + } + } + return table; +}(); + +function defineBigIntMethod(fn) { + return typeof BigInt === "undefined" ? BufferBigIntNotDefined : fn; +} + +function BufferBigIntNotDefined() { + throw new Error("BigInt not supported"); +} + +export function readUInt48LE(buf, offset = 0) { + validateNumber(offset, "offset"); + const first = buf[offset]; + const last = buf[offset + 5]; + if (first === undefined || last === undefined) { + boundsError(offset, buf.length - 6); + } + + return first + + buf[++offset] * 2 ** 8 + + buf[++offset] * 2 ** 16 + + buf[++offset] * 2 ** 24 + + (buf[++offset] + last * 2 ** 8) * 2 ** 32; +} + +export function readUInt40LE(buf, offset = 0) { + validateNumber(offset, "offset"); + const first = buf[offset]; + const last = buf[offset + 4]; + if (first === undefined || last === undefined) { + boundsError(offset, buf.length - 5); + } + + return first + + buf[++offset] * 2 ** 8 + + buf[++offset] * 2 ** 16 + + buf[++offset] * 2 ** 24 + + last * 2 ** 32; +} + +export function readUInt24LE(buf, offset = 0) { + validateNumber(offset, "offset"); + const first = buf[offset]; + const last = buf[offset + 2]; + if (first === undefined || last === undefined) { + boundsError(offset, buf.length - 3); + } + + return first + buf[++offset] * 2 ** 8 + last * 2 ** 16; +} + +export function readUInt48BE(buf, offset = 0) { + validateNumber(offset, "offset"); + const first = buf[offset]; + const last = buf[offset + 5]; + if (first === undefined || last === undefined) { + boundsError(offset, buf.length - 6); + } + + return (first * 2 ** 8 + buf[++offset]) * 2 ** 32 + + buf[++offset] * 2 ** 24 + + buf[++offset] * 2 ** 16 + + buf[++offset] * 2 ** 8 + + last; +} + +export function readUInt40BE(buf, offset = 0) { + validateNumber(offset, "offset"); + const first = buf[offset]; + const last = buf[offset + 4]; + if (first === undefined || last === undefined) { + boundsError(offset, buf.length - 5); + } + + return first * 2 ** 32 + + buf[++offset] * 2 ** 24 + + buf[++offset] * 2 ** 16 + + buf[++offset] * 2 ** 8 + + last; +} + +export function readUInt24BE(buf, offset = 0) { + validateNumber(offset, "offset"); + const first = buf[offset]; + const last = buf[offset + 2]; + if (first === undefined || last === undefined) { + boundsError(offset, buf.length - 3); + } + + return first * 2 ** 16 + buf[++offset] * 2 ** 8 + last; +} + +export function readUInt16BE(offset = 0) { + validateNumber(offset, "offset"); + const first = this[offset]; + const last = this[offset + 1]; + if (first === undefined || last === undefined) { + boundsError(offset, this.length - 2); + } + + return first * 2 ** 8 + last; +} + +export function readUInt32BE(offset = 0) { + validateNumber(offset, "offset"); + const first = this[offset]; + const last = this[offset + 3]; + if (first === undefined || last === undefined) { + boundsError(offset, this.length - 4); + } + + return first * 2 ** 24 + + this[++offset] * 2 ** 16 + + this[++offset] * 2 ** 8 + + last; +} + +export function readDoubleBackwards(buffer, offset = 0) { + validateNumber(offset, "offset"); + const first = buffer[offset]; + const last = buffer[offset + 7]; + if (first === undefined || last === undefined) { + boundsError(offset, buffer.length - 8); + } + + uInt8Float64Array[7] = first; + uInt8Float64Array[6] = buffer[++offset]; + uInt8Float64Array[5] = buffer[++offset]; + uInt8Float64Array[4] = buffer[++offset]; + uInt8Float64Array[3] = buffer[++offset]; + uInt8Float64Array[2] = buffer[++offset]; + uInt8Float64Array[1] = buffer[++offset]; + uInt8Float64Array[0] = last; + return float64Array[0]; +} + +export function readDoubleForwards(buffer, offset = 0) { + validateNumber(offset, "offset"); + const first = buffer[offset]; + const last = buffer[offset + 7]; + if (first === undefined || last === undefined) { + boundsError(offset, buffer.length - 8); + } + + uInt8Float64Array[0] = first; + uInt8Float64Array[1] = buffer[++offset]; + uInt8Float64Array[2] = buffer[++offset]; + uInt8Float64Array[3] = buffer[++offset]; + uInt8Float64Array[4] = buffer[++offset]; + uInt8Float64Array[5] = buffer[++offset]; + uInt8Float64Array[6] = buffer[++offset]; + uInt8Float64Array[7] = last; + return float64Array[0]; +} + +export function writeDoubleForwards(buffer, val, offset = 0) { + val = +val; + checkBounds(buffer, offset, 7); + + float64Array[0] = val; + buffer[offset++] = uInt8Float64Array[0]; + buffer[offset++] = uInt8Float64Array[1]; + buffer[offset++] = uInt8Float64Array[2]; + buffer[offset++] = uInt8Float64Array[3]; + buffer[offset++] = uInt8Float64Array[4]; + buffer[offset++] = uInt8Float64Array[5]; + buffer[offset++] = uInt8Float64Array[6]; + buffer[offset++] = uInt8Float64Array[7]; + return offset; +} + +export function writeDoubleBackwards(buffer, val, offset = 0) { + val = +val; + checkBounds(buffer, offset, 7); + + float64Array[0] = val; + buffer[offset++] = uInt8Float64Array[7]; + buffer[offset++] = uInt8Float64Array[6]; + buffer[offset++] = uInt8Float64Array[5]; + buffer[offset++] = uInt8Float64Array[4]; + buffer[offset++] = uInt8Float64Array[3]; + buffer[offset++] = uInt8Float64Array[2]; + buffer[offset++] = uInt8Float64Array[1]; + buffer[offset++] = uInt8Float64Array[0]; + return offset; +} + +export function readFloatBackwards(buffer, offset = 0) { + validateNumber(offset, "offset"); + const first = buffer[offset]; + const last = buffer[offset + 3]; + if (first === undefined || last === undefined) { + boundsError(offset, buffer.length - 4); + } + + uInt8Float32Array[3] = first; + uInt8Float32Array[2] = buffer[++offset]; + uInt8Float32Array[1] = buffer[++offset]; + uInt8Float32Array[0] = last; + return float32Array[0]; +} + +export function readFloatForwards(buffer, offset = 0) { + validateNumber(offset, "offset"); + const first = buffer[offset]; + const last = buffer[offset + 3]; + if (first === undefined || last === undefined) { + boundsError(offset, buffer.length - 4); + } + + uInt8Float32Array[0] = first; + uInt8Float32Array[1] = buffer[++offset]; + uInt8Float32Array[2] = buffer[++offset]; + uInt8Float32Array[3] = last; + return float32Array[0]; +} + +export function writeFloatForwards(buffer, val, offset = 0) { + val = +val; + checkBounds(buffer, offset, 3); + + float32Array[0] = val; + buffer[offset++] = uInt8Float32Array[0]; + buffer[offset++] = uInt8Float32Array[1]; + buffer[offset++] = uInt8Float32Array[2]; + buffer[offset++] = uInt8Float32Array[3]; + return offset; +} + +export function writeFloatBackwards(buffer, val, offset = 0) { + val = +val; + checkBounds(buffer, offset, 3); + + float32Array[0] = val; + buffer[offset++] = uInt8Float32Array[3]; + buffer[offset++] = uInt8Float32Array[2]; + buffer[offset++] = uInt8Float32Array[1]; + buffer[offset++] = uInt8Float32Array[0]; + return offset; +} + +export function readInt24LE(buf, offset = 0) { + validateNumber(offset, "offset"); + const first = buf[offset]; + const last = buf[offset + 2]; + if (first === undefined || last === undefined) { + boundsError(offset, buf.length - 3); + } + + const val = first + buf[++offset] * 2 ** 8 + last * 2 ** 16; + return val | (val & 2 ** 23) * 0x1fe; +} + +export function readInt40LE(buf, offset = 0) { + validateNumber(offset, "offset"); + const first = buf[offset]; + const last = buf[offset + 4]; + if (first === undefined || last === undefined) { + boundsError(offset, buf.length - 5); + } + + return (last | (last & 2 ** 7) * 0x1fffffe) * 2 ** 32 + + first + + buf[++offset] * 2 ** 8 + + buf[++offset] * 2 ** 16 + + buf[++offset] * 2 ** 24; +} + +export function readInt48LE(buf, offset = 0) { + validateNumber(offset, "offset"); + const first = buf[offset]; + const last = buf[offset + 5]; + if (first === undefined || last === undefined) { + boundsError(offset, buf.length - 6); + } + + const val = buf[offset + 4] + last * 2 ** 8; + return (val | (val & 2 ** 15) * 0x1fffe) * 2 ** 32 + + first + + buf[++offset] * 2 ** 8 + + buf[++offset] * 2 ** 16 + + buf[++offset] * 2 ** 24; +} + +export function readInt24BE(buf, offset = 0) { + validateNumber(offset, "offset"); + const first = buf[offset]; + const last = buf[offset + 2]; + if (first === undefined || last === undefined) { + boundsError(offset, buf.length - 3); + } + + const val = first * 2 ** 16 + buf[++offset] * 2 ** 8 + last; + return val | (val & 2 ** 23) * 0x1fe; +} + +export function readInt48BE(buf, offset = 0) { + validateNumber(offset, "offset"); + const first = buf[offset]; + const last = buf[offset + 5]; + if (first === undefined || last === undefined) { + boundsError(offset, buf.length - 6); + } + + const val = buf[++offset] + first * 2 ** 8; + return (val | (val & 2 ** 15) * 0x1fffe) * 2 ** 32 + + buf[++offset] * 2 ** 24 + + buf[++offset] * 2 ** 16 + + buf[++offset] * 2 ** 8 + + last; +} + +export function readInt40BE(buf, offset = 0) { + validateNumber(offset, "offset"); + const first = buf[offset]; + const last = buf[offset + 4]; + if (first === undefined || last === undefined) { + boundsError(offset, buf.length - 5); + } + + return (first | (first & 2 ** 7) * 0x1fffffe) * 2 ** 32 + + buf[++offset] * 2 ** 24 + + buf[++offset] * 2 ** 16 + + buf[++offset] * 2 ** 8 + + last; +} + +export function byteLengthUtf8(str) { + return utf8Encoder.encode(str).length; +} + +function base64ByteLength(str, bytes) { + // Handle padding + if (str.charCodeAt(bytes - 1) === 0x3D) { + bytes--; + } + if (bytes > 1 && str.charCodeAt(bytes - 1) === 0x3D) { + bytes--; + } + + // Base64 ratio: 3/4 + return (bytes * 3) >>> 2; +} + +export const encodingsMap = Object.create(null); +for (let i = 0; i < encodings.length; ++i) { + encodingsMap[encodings[i]] = i; +} + +export const encodingOps = { + ascii: { + byteLength: (string) => string.length, + encoding: "ascii", + encodingVal: encodingsMap.ascii, + indexOf: (buf, val, byteOffset, dir) => + indexOfBuffer( + buf, + asciiToBytes(val), + byteOffset, + encodingsMap.ascii, + dir, + ), + slice: (buf, start, end) => buf.asciiSlice(start, end), + write: (buf, string, offset, len) => buf.asciiWrite(string, offset, len), + }, + base64: { + byteLength: (string) => base64ByteLength(string, string.length), + encoding: "base64", + encodingVal: encodingsMap.base64, + indexOf: (buf, val, byteOffset, dir) => + indexOfBuffer( + buf, + base64ToBytes(val), + byteOffset, + encodingsMap.base64, + dir, + ), + slice: (buf, start, end) => buf.base64Slice(start, end), + write: (buf, string, offset, len) => buf.base64Write(string, offset, len), + }, + base64url: { + byteLength: (string) => base64ByteLength(string, string.length), + encoding: "base64url", + encodingVal: encodingsMap.base64url, + indexOf: (buf, val, byteOffset, dir) => + indexOfBuffer( + buf, + base64UrlToBytes(val), + byteOffset, + encodingsMap.base64url, + dir, + ), + slice: (buf, start, end) => buf.base64urlSlice(start, end), + write: (buf, string, offset, len) => + buf.base64urlWrite(string, offset, len), + }, + hex: { + byteLength: (string) => string.length >>> 1, + encoding: "hex", + encodingVal: encodingsMap.hex, + indexOf: (buf, val, byteOffset, dir) => + indexOfBuffer( + buf, + hexToBytes(val), + byteOffset, + encodingsMap.hex, + dir, + ), + slice: (buf, start, end) => buf.hexSlice(start, end), + write: (buf, string, offset, len) => buf.hexWrite(string, offset, len), + }, + latin1: { + byteLength: (string) => string.length, + encoding: "latin1", + encodingVal: encodingsMap.latin1, + indexOf: (buf, val, byteOffset, dir) => + indexOfBuffer( + buf, + asciiToBytes(val), + byteOffset, + encodingsMap.latin1, + dir, + ), + slice: (buf, start, end) => buf.latin1Slice(start, end), + write: (buf, string, offset, len) => buf.latin1Write(string, offset, len), + }, + ucs2: { + byteLength: (string) => string.length * 2, + encoding: "ucs2", + encodingVal: encodingsMap.utf16le, + indexOf: (buf, val, byteOffset, dir) => + indexOfBuffer( + buf, + utf16leToBytes(val), + byteOffset, + encodingsMap.utf16le, + dir, + ), + slice: (buf, start, end) => buf.ucs2Slice(start, end), + write: (buf, string, offset, len) => buf.ucs2Write(string, offset, len), + }, + utf8: { + byteLength: byteLengthUtf8, + encoding: "utf8", + encodingVal: encodingsMap.utf8, + indexOf: (buf, val, byteOffset, dir) => + indexOfBuffer( + buf, + utf8Encoder.encode(val), + byteOffset, + encodingsMap.utf8, + dir, + ), + slice: (buf, start, end) => buf.utf8Slice(start, end), + write: (buf, string, offset, len) => buf.utf8Write(string, offset, len), + }, + utf16le: { + byteLength: (string) => string.length * 2, + encoding: "utf16le", + encodingVal: encodingsMap.utf16le, + indexOf: (buf, val, byteOffset, dir) => + indexOfBuffer( + buf, + utf16leToBytes(val), + byteOffset, + encodingsMap.utf16le, + dir, + ), + slice: (buf, start, end) => buf.ucs2Slice(start, end), + write: (buf, string, offset, len) => buf.ucs2Write(string, offset, len), + }, +}; + +export function getEncodingOps(encoding) { + encoding = String(encoding).toLowerCase(); + switch (encoding.length) { + case 4: + if (encoding === "utf8") return encodingOps.utf8; + if (encoding === "ucs2") return encodingOps.ucs2; + break; + case 5: + if (encoding === "utf-8") return encodingOps.utf8; + if (encoding === "ascii") return encodingOps.ascii; + if (encoding === "ucs-2") return encodingOps.ucs2; + break; + case 7: + if (encoding === "utf16le") { + return encodingOps.utf16le; + } + break; + case 8: + if (encoding === "utf-16le") { + return encodingOps.utf16le; + } + break; + // deno-lint-ignore no-fallthrough + case 6: + if (encoding === "latin1" || encoding === "binary") { + return encodingOps.latin1; + } + if (encoding === "base64") return encodingOps.base64; + case 3: + if (encoding === "hex") { + return encodingOps.hex; + } + break; + case 9: + if (encoding === "base64url") { + return encodingOps.base64url; + } + break; + } +} + +export function _copyActual( + source, + target, + targetStart, + sourceStart, + sourceEnd, +) { + if (sourceEnd - sourceStart > target.length - targetStart) { + sourceEnd = sourceStart + target.length - targetStart; + } + + let nb = sourceEnd - sourceStart; + const sourceLen = source.length - sourceStart; + if (nb > sourceLen) { + nb = sourceLen; + } + + if (sourceStart !== 0 || sourceEnd < source.length) { + source = new Uint8Array(source.buffer, source.byteOffset + sourceStart, nb); + } + + target.set(source, targetStart); + + return nb; +} + +export function boundsError(value, length, type) { + if (Math.floor(value) !== value) { + validateNumber(value, type); + throw new codes.ERR_OUT_OF_RANGE(type || "offset", "an integer", value); + } + + if (length < 0) { + throw new codes.ERR_BUFFER_OUT_OF_BOUNDS(); + } + + throw new codes.ERR_OUT_OF_RANGE( + type || "offset", + `>= ${type ? 1 : 0} and <= ${length}`, + value, + ); +} + +export function validateNumber(value, name) { + if (typeof value !== "number") { + throw new codes.ERR_INVALID_ARG_TYPE(name, "number", value); + } +} + +function checkInt(value, min, max, buf, offset, byteLength) { + if (value > max || value < min) { + const n = typeof min === "bigint" ? "n" : ""; + let range; + if (byteLength > 3) { + if (min === 0 || min === 0n) { + range = `>= 0${n} and < 2${n} ** ${(byteLength + 1) * 8}${n}`; + } else { + range = `>= -(2${n} ** ${(byteLength + 1) * 8 - 1}${n}) and ` + + `< 2${n} ** ${(byteLength + 1) * 8 - 1}${n}`; + } + } else { + range = `>= ${min}${n} and <= ${max}${n}`; + } + throw new codes.ERR_OUT_OF_RANGE("value", range, value); + } + checkBounds(buf, offset, byteLength); +} + +export function toInteger(n, defaultVal) { + n = +n; + if ( + !Number.isNaN(n) && + n >= Number.MIN_SAFE_INTEGER && + n <= Number.MAX_SAFE_INTEGER + ) { + return ((n % 1) === 0 ? n : Math.floor(n)); + } + return defaultVal; +} + +// deno-lint-ignore camelcase +export function writeU_Int8(buf, value, offset, min, max) { + value = +value; + validateNumber(offset, "offset"); + if (value > max || value < min) { + throw new codes.ERR_OUT_OF_RANGE("value", `>= ${min} and <= ${max}`, value); + } + if (buf[offset] === undefined) { + boundsError(offset, buf.length - 1); + } + + buf[offset] = value; + return offset + 1; +} + +// deno-lint-ignore camelcase +export function writeU_Int16BE(buf, value, offset, min, max) { + value = +value; + checkInt(value, min, max, buf, offset, 1); + + buf[offset++] = value >>> 8; + buf[offset++] = value; + return offset; +} + +export function _writeUInt32LE(buf, value, offset, min, max) { + value = +value; + checkInt(value, min, max, buf, offset, 3); + + buf[offset++] = value; + value = value >>> 8; + buf[offset++] = value; + value = value >>> 8; + buf[offset++] = value; + value = value >>> 8; + buf[offset++] = value; + return offset; +} + +// deno-lint-ignore camelcase +export function writeU_Int16LE(buf, value, offset, min, max) { + value = +value; + checkInt(value, min, max, buf, offset, 1); + + buf[offset++] = value; + buf[offset++] = value >>> 8; + return offset; +} + +export function _writeUInt32BE(buf, value, offset, min, max) { + value = +value; + checkInt(value, min, max, buf, offset, 3); + + buf[offset + 3] = value; + value = value >>> 8; + buf[offset + 2] = value; + value = value >>> 8; + buf[offset + 1] = value; + value = value >>> 8; + buf[offset] = value; + return offset + 4; +} + +// deno-lint-ignore camelcase +export function writeU_Int48BE(buf, value, offset, min, max) { + value = +value; + checkInt(value, min, max, buf, offset, 5); + + const newVal = Math.floor(value * 2 ** -32); + buf[offset++] = newVal >>> 8; + buf[offset++] = newVal; + buf[offset + 3] = value; + value = value >>> 8; + buf[offset + 2] = value; + value = value >>> 8; + buf[offset + 1] = value; + value = value >>> 8; + buf[offset] = value; + return offset + 4; +} + +// deno-lint-ignore camelcase +export function writeU_Int40BE(buf, value, offset, min, max) { + value = +value; + checkInt(value, min, max, buf, offset, 4); + + buf[offset++] = Math.floor(value * 2 ** -32); + buf[offset + 3] = value; + value = value >>> 8; + buf[offset + 2] = value; + value = value >>> 8; + buf[offset + 1] = value; + value = value >>> 8; + buf[offset] = value; + return offset + 4; +} + +// deno-lint-ignore camelcase +export function writeU_Int32BE(buf, value, offset, min, max) { + value = +value; + checkInt(value, min, max, buf, offset, 3); + + buf[offset + 3] = value; + value = value >>> 8; + buf[offset + 2] = value; + value = value >>> 8; + buf[offset + 1] = value; + value = value >>> 8; + buf[offset] = value; + return offset + 4; +} + +// deno-lint-ignore camelcase +export function writeU_Int24BE(buf, value, offset, min, max) { + value = +value; + checkInt(value, min, max, buf, offset, 2); + + buf[offset + 2] = value; + value = value >>> 8; + buf[offset + 1] = value; + value = value >>> 8; + buf[offset] = value; + return offset + 3; +} + +export function validateOffset( + value, + name, + min = 0, + max = Number.MAX_SAFE_INTEGER, +) { + if (typeof value !== "number") { + throw new codes.ERR_INVALID_ARG_TYPE(name, "number", value); + } + if (!Number.isInteger(value)) { + throw new codes.ERR_OUT_OF_RANGE(name, "an integer", value); + } + if (value < min || value > max) { + throw new codes.ERR_OUT_OF_RANGE(name, `>= ${min} && <= ${max}`, value); + } +} + +// deno-lint-ignore camelcase +export function writeU_Int48LE(buf, value, offset, min, max) { + value = +value; + checkInt(value, min, max, buf, offset, 5); + + const newVal = Math.floor(value * 2 ** -32); + buf[offset++] = value; + value = value >>> 8; + buf[offset++] = value; + value = value >>> 8; + buf[offset++] = value; + value = value >>> 8; + buf[offset++] = value; + buf[offset++] = newVal; + buf[offset++] = newVal >>> 8; + return offset; +} + +// deno-lint-ignore camelcase +export function writeU_Int40LE(buf, value, offset, min, max) { + value = +value; + checkInt(value, min, max, buf, offset, 4); + + const newVal = value; + buf[offset++] = value; + value = value >>> 8; + buf[offset++] = value; + value = value >>> 8; + buf[offset++] = value; + value = value >>> 8; + buf[offset++] = value; + buf[offset++] = Math.floor(newVal * 2 ** -32); + return offset; +} + +// deno-lint-ignore camelcase +export function writeU_Int32LE(buf, value, offset, min, max) { + value = +value; + checkInt(value, min, max, buf, offset, 3); + + buf[offset++] = value; + value = value >>> 8; + buf[offset++] = value; + value = value >>> 8; + buf[offset++] = value; + value = value >>> 8; + buf[offset++] = value; + return offset; +} + +// deno-lint-ignore camelcase +export function writeU_Int24LE(buf, value, offset, min, max) { + value = +value; + checkInt(value, min, max, buf, offset, 2); + + buf[offset++] = value; + value = value >>> 8; + buf[offset++] = value; + value = value >>> 8; + buf[offset++] = value; + return offset; +} + +export default { + atob, + btoa, + Blob, + Buffer, + constants, + kMaxLength, + kStringMaxLength, + SlowBuffer, +}; diff --git a/ext/node/polyfills/internal/child_process.ts b/ext/node/polyfills/internal/child_process.ts new file mode 100644 index 00000000000000..81a404c14a33bc --- /dev/null +++ b/ext/node/polyfills/internal/child_process.ts @@ -0,0 +1,1021 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. + +// This module implements 'child_process' module of Node.JS API. +// ref: https://nodejs.org/api/child_process.html +import { assert } from "internal:deno_node/polyfills/_util/asserts.ts"; +import { EventEmitter } from "internal:deno_node/polyfills/events.ts"; +import { os } from "internal:deno_node/polyfills/internal_binding/constants.ts"; +import { + notImplemented, + warnNotImplemented, +} from "internal:deno_node/polyfills/_utils.ts"; +import { + Readable, + Stream, + Writable, +} from "internal:deno_node/polyfills/stream.ts"; +import { deferred } from "internal:deno_node/polyfills/_util/async.ts"; +import { isWindows } from "internal:deno_node/polyfills/_util/os.ts"; +import { nextTick } from "internal:deno_node/polyfills/_next_tick.ts"; +import { + AbortError, + ERR_INVALID_ARG_TYPE, + ERR_INVALID_ARG_VALUE, + ERR_UNKNOWN_SIGNAL, +} from "internal:deno_node/polyfills/internal/errors.ts"; +import { Buffer } from "internal:deno_node/polyfills/buffer.ts"; +import { errnoException } from "internal:deno_node/polyfills/internal/errors.ts"; +import { ErrnoException } from "internal:deno_node/polyfills/_global.d.ts"; +import { codeMap } from "internal:deno_node/polyfills/internal_binding/uv.ts"; +import { + isInt32, + validateBoolean, + validateObject, + validateString, +} from "internal:deno_node/polyfills/internal/validators.mjs"; +import { + ArrayIsArray, + ArrayPrototypeFilter, + ArrayPrototypeJoin, + ArrayPrototypePush, + ArrayPrototypeSlice, + ArrayPrototypeSort, + ArrayPrototypeUnshift, + ObjectPrototypeHasOwnProperty, + StringPrototypeToUpperCase, +} from "internal:deno_node/polyfills/internal/primordials.mjs"; +import { kEmptyObject } from "internal:deno_node/polyfills/internal/util.mjs"; +import { getValidatedPath } from "internal:deno_node/polyfills/internal/fs/utils.mjs"; +import process from "internal:deno_node/polyfills/process.ts"; + +export function mapValues( + record: Readonly>, + transformer: (value: T) => O, +): Record { + const ret: Record = {}; + const entries = Object.entries(record); + + for (const [key, value] of entries) { + const mappedValue = transformer(value); + + ret[key] = mappedValue; + } + + return ret; +} + +type NodeStdio = "pipe" | "overlapped" | "ignore" | "inherit" | "ipc"; +type DenoStdio = "inherit" | "piped" | "null"; + +export function stdioStringToArray( + stdio: NodeStdio, + channel: NodeStdio | number, +) { + const options: (NodeStdio | number)[] = []; + + switch (stdio) { + case "ignore": + case "overlapped": + case "pipe": + options.push(stdio, stdio, stdio); + break; + case "inherit": + options.push(stdio, stdio, stdio); + break; + default: + throw new ERR_INVALID_ARG_VALUE("stdio", stdio); + } + + if (channel) options.push(channel); + + return options; +} + +export class ChildProcess extends EventEmitter { + /** + * The exit code of the child process. This property will be `null` until the child process exits. + */ + exitCode: number | null = null; + + /** + * This property is set to `true` after `kill()` is called. + */ + killed = false; + + /** + * The PID of this child process. + */ + pid!: number; + + /** + * The signal received by this child process. + */ + signalCode: string | null = null; + + /** + * Command line arguments given to this child process. + */ + spawnargs: string[]; + + /** + * The executable file name of this child process. + */ + spawnfile: string; + + /** + * This property represents the child process's stdin. + */ + stdin: Writable | null = null; + + /** + * This property represents the child process's stdout. + */ + stdout: Readable | null = null; + + /** + * This property represents the child process's stderr. + */ + stderr: Readable | null = null; + + /** + * Pipes to this child process. + */ + stdio: [Writable | null, Readable | null, Readable | null] = [ + null, + null, + null, + ]; + + #process!: Deno.ChildProcess; + #spawned = deferred(); + + constructor( + command: string, + args?: string[], + options?: ChildProcessOptions, + ) { + super(); + + const { + env = {}, + stdio = ["pipe", "pipe", "pipe"], + cwd, + shell = false, + signal, + windowsVerbatimArguments = false, + } = options || {}; + const [ + stdin = "pipe", + stdout = "pipe", + stderr = "pipe", + _channel, // TODO(kt3k): handle this correctly + ] = normalizeStdioOption(stdio); + const [cmd, cmdArgs] = buildCommand( + command, + args || [], + shell, + ); + this.spawnfile = cmd; + this.spawnargs = [cmd, ...cmdArgs]; + + const stringEnv = mapValues(env, (value) => value.toString()); + try { + this.#process = new Deno.Command(cmd, { + args: cmdArgs, + cwd, + env: stringEnv, + stdin: toDenoStdio(stdin as NodeStdio | number), + stdout: toDenoStdio(stdout as NodeStdio | number), + stderr: toDenoStdio(stderr as NodeStdio | number), + windowsRawArguments: windowsVerbatimArguments, + }).spawn(); + this.pid = this.#process.pid; + + if (stdin === "pipe") { + assert(this.#process.stdin); + this.stdin = Writable.fromWeb(this.#process.stdin); + } + + if (stdout === "pipe") { + assert(this.#process.stdout); + this.stdout = Readable.fromWeb(this.#process.stdout); + } + + if (stderr === "pipe") { + assert(this.#process.stderr); + this.stderr = Readable.fromWeb(this.#process.stderr); + } + + this.stdio[0] = this.stdin; + this.stdio[1] = this.stdout; + this.stdio[2] = this.stderr; + + nextTick(() => { + this.emit("spawn"); + this.#spawned.resolve(); + }); + + if (signal) { + const onAbortListener = () => { + try { + if (this.kill("SIGKILL")) { + this.emit("error", new AbortError()); + } + } catch (err) { + this.emit("error", err); + } + }; + if (signal.aborted) { + nextTick(onAbortListener); + } else { + signal.addEventListener("abort", onAbortListener, { once: true }); + this.addListener( + "exit", + () => signal.removeEventListener("abort", onAbortListener), + ); + } + } + + (async () => { + const status = await this.#process.status; + this.exitCode = status.code; + this.#spawned.then(async () => { + const exitCode = this.signalCode == null ? this.exitCode : null; + const signalCode = this.signalCode == null ? null : this.signalCode; + // The 'exit' and 'close' events must be emitted after the 'spawn' event. + this.emit("exit", exitCode, signalCode); + await this.#_waitForChildStreamsToClose(); + this.#closePipes(); + this.emit("close", exitCode, signalCode); + }); + })(); + } catch (err) { + this.#_handleError(err); + } + } + + /** + * @param signal NOTE: this parameter is not yet implemented. + */ + kill(signal?: number | string): boolean { + if (this.killed) { + return this.killed; + } + + const denoSignal = signal == null ? "SIGTERM" : toDenoSignal(signal); + this.#closePipes(); + try { + this.#process.kill(denoSignal); + } catch (err) { + const alreadyClosed = err instanceof TypeError || + err instanceof Deno.errors.PermissionDenied; + if (!alreadyClosed) { + throw err; + } + } + this.killed = true; + this.signalCode = denoSignal; + return this.killed; + } + + ref() { + this.#process.ref(); + } + + unref() { + this.#process.unref(); + } + + disconnect() { + warnNotImplemented("ChildProcess.prototype.disconnect"); + } + + async #_waitForChildStreamsToClose() { + const promises = [] as Array>; + if (this.stdin && !this.stdin.destroyed) { + assert(this.stdin); + this.stdin.destroy(); + promises.push(waitForStreamToClose(this.stdin)); + } + if (this.stdout && !this.stdout.destroyed) { + promises.push(waitForReadableToClose(this.stdout)); + } + if (this.stderr && !this.stderr.destroyed) { + promises.push(waitForReadableToClose(this.stderr)); + } + await Promise.all(promises); + } + + #_handleError(err: unknown) { + nextTick(() => { + this.emit("error", err); // TODO(uki00a) Convert `err` into nodejs's `SystemError` class. + }); + } + + #closePipes() { + if (this.stdin) { + assert(this.stdin); + this.stdin.destroy(); + } + } +} + +const supportedNodeStdioTypes: NodeStdio[] = ["pipe", "ignore", "inherit"]; +function toDenoStdio( + pipe: NodeStdio | number | Stream | null | undefined, +): DenoStdio { + if ( + !supportedNodeStdioTypes.includes(pipe as NodeStdio) || + typeof pipe === "number" || pipe instanceof Stream + ) { + notImplemented(`toDenoStdio pipe=${typeof pipe} (${pipe})`); + } + switch (pipe) { + case "pipe": + case undefined: + case null: + return "piped"; + case "ignore": + return "null"; + case "inherit": + return "inherit"; + default: + notImplemented(`toDenoStdio pipe=${typeof pipe} (${pipe})`); + } +} + +function toDenoSignal(signal: number | string): Deno.Signal { + if (typeof signal === "number") { + for (const name of keys(os.signals)) { + if (os.signals[name] === signal) { + return name as Deno.Signal; + } + } + throw new ERR_UNKNOWN_SIGNAL(String(signal)); + } + + const denoSignal = signal as Deno.Signal; + if (denoSignal in os.signals) { + return denoSignal; + } + throw new ERR_UNKNOWN_SIGNAL(signal); +} + +function keys>(object: T): Array { + return Object.keys(object); +} + +export interface ChildProcessOptions { + /** + * Current working directory of the child process. + */ + cwd?: string | URL; + + /** + * Environment variables passed to the child process. + */ + env?: Record; + + /** + * This option defines child process's stdio configuration. + * @see https://nodejs.org/api/child_process.html#child_process_options_stdio + */ + stdio?: Array | NodeStdio; + + /** + * NOTE: This option is not yet implemented. + */ + detached?: boolean; + + /** + * NOTE: This option is not yet implemented. + */ + uid?: number; + + /** + * NOTE: This option is not yet implemented. + */ + gid?: number; + + /** + * NOTE: This option is not yet implemented. + */ + argv0?: string; + + /** + * * If this option is `true`, run the command in the shell. + * * If this option is a string, run the command in the specified shell. + */ + shell?: string | boolean; + + /** + * Allows aborting the child process using an AbortSignal. + */ + signal?: AbortSignal; + + /** + * NOTE: This option is not yet implemented. + */ + serialization?: "json" | "advanced"; + + /** No quoting or escaping of arguments is done on Windows. Ignored on Unix. + * Default: false. */ + windowsVerbatimArguments?: boolean; + + /** + * NOTE: This option is not yet implemented. + */ + windowsHide?: boolean; +} + +function copyProcessEnvToEnv( + env: Record, + name: string, + optionEnv?: Record, +) { + if ( + Deno.env.get(name) && + (!optionEnv || + !ObjectPrototypeHasOwnProperty(optionEnv, name)) + ) { + env[name] = Deno.env.get(name); + } +} + +function normalizeStdioOption( + stdio: Array | NodeStdio = [ + "pipe", + "pipe", + "pipe", + ], +) { + if (Array.isArray(stdio)) { + return stdio; + } else { + switch (stdio) { + case "overlapped": + if (isWindows) { + notImplemented("normalizeStdioOption overlapped (on windows)"); + } + // 'overlapped' is same as 'piped' on non Windows system. + return ["pipe", "pipe", "pipe"]; + case "pipe": + return ["pipe", "pipe", "pipe"]; + case "inherit": + return ["inherit", "inherit", "inherit"]; + case "ignore": + return ["ignore", "ignore", "ignore"]; + default: + notImplemented(`normalizeStdioOption stdio=${typeof stdio} (${stdio})`); + } + } +} + +export function normalizeSpawnArguments( + file: string, + args: string[], + options: SpawnOptions & SpawnSyncOptions, +) { + validateString(file, "file"); + + if (file.length === 0) { + throw new ERR_INVALID_ARG_VALUE("file", file, "cannot be empty"); + } + + if (ArrayIsArray(args)) { + args = ArrayPrototypeSlice(args); + } else if (args == null) { + args = []; + } else if (typeof args !== "object") { + throw new ERR_INVALID_ARG_TYPE("args", "object", args); + } else { + options = args; + args = []; + } + + if (options === undefined) { + options = kEmptyObject; + } else { + validateObject(options, "options"); + } + + let cwd = options.cwd; + + // Validate the cwd, if present. + if (cwd != null) { + cwd = getValidatedPath(cwd, "options.cwd") as string; + } + + // Validate detached, if present. + if (options.detached != null) { + validateBoolean(options.detached, "options.detached"); + } + + // Validate the uid, if present. + if (options.uid != null && !isInt32(options.uid)) { + throw new ERR_INVALID_ARG_TYPE("options.uid", "int32", options.uid); + } + + // Validate the gid, if present. + if (options.gid != null && !isInt32(options.gid)) { + throw new ERR_INVALID_ARG_TYPE("options.gid", "int32", options.gid); + } + + // Validate the shell, if present. + if ( + options.shell != null && + typeof options.shell !== "boolean" && + typeof options.shell !== "string" + ) { + throw new ERR_INVALID_ARG_TYPE( + "options.shell", + ["boolean", "string"], + options.shell, + ); + } + + // Validate argv0, if present. + if (options.argv0 != null) { + validateString(options.argv0, "options.argv0"); + } + + // Validate windowsHide, if present. + if (options.windowsHide != null) { + validateBoolean(options.windowsHide, "options.windowsHide"); + } + + // Validate windowsVerbatimArguments, if present. + let { windowsVerbatimArguments } = options; + if (windowsVerbatimArguments != null) { + validateBoolean( + windowsVerbatimArguments, + "options.windowsVerbatimArguments", + ); + } + + if (options.shell) { + const command = ArrayPrototypeJoin([file, ...args], " "); + // Set the shell, switches, and commands. + if (process.platform === "win32") { + if (typeof options.shell === "string") { + file = options.shell; + } else { + file = Deno.env.get("comspec") || "cmd.exe"; + } + // '/d /s /c' is used only for cmd.exe. + if (/^(?:.*\\)?cmd(?:\.exe)?$/i.exec(file) !== null) { + args = ["/d", "/s", "/c", `"${command}"`]; + windowsVerbatimArguments = true; + } else { + args = ["-c", command]; + } + } else { + /** TODO: add Android condition */ + if (typeof options.shell === "string") { + file = options.shell; + } else { + file = "/bin/sh"; + } + args = ["-c", command]; + } + } + + if (typeof options.argv0 === "string") { + ArrayPrototypeUnshift(args, options.argv0); + } else { + ArrayPrototypeUnshift(args, file); + } + + const env = options.env || Deno.env.toObject(); + const envPairs: string[][] = []; + + // process.env.NODE_V8_COVERAGE always propagates, making it possible to + // collect coverage for programs that spawn with white-listed environment. + copyProcessEnvToEnv(env, "NODE_V8_COVERAGE", options.env); + + /** TODO: add `isZOS` condition */ + + let envKeys: string[] = []; + // Prototype values are intentionally included. + for (const key in env) { + if (Object.hasOwn(env, key)) { + ArrayPrototypePush(envKeys, key); + } + } + + if (process.platform === "win32") { + // On Windows env keys are case insensitive. Filter out duplicates, + // keeping only the first one (in lexicographic order) + /** TODO: implement SafeSet and makeSafe */ + const sawKey = new Set(); + envKeys = ArrayPrototypeFilter( + ArrayPrototypeSort(envKeys), + (key: string) => { + const uppercaseKey = StringPrototypeToUpperCase(key); + if (sawKey.has(uppercaseKey)) { + return false; + } + sawKey.add(uppercaseKey); + return true; + }, + ); + } + + for (const key of envKeys) { + const value = env[key]; + if (value !== undefined) { + ArrayPrototypePush(envPairs, `${key}=${value}`); + } + } + + return { + // Make a shallow copy so we don't clobber the user's options object. + ...options, + args, + cwd, + detached: !!options.detached, + envPairs, + file, + windowsHide: !!options.windowsHide, + windowsVerbatimArguments: !!windowsVerbatimArguments, + }; +} + +function waitForReadableToClose(readable: Readable) { + readable.resume(); // Ensure buffered data will be consumed. + return waitForStreamToClose(readable as unknown as Stream); +} + +function waitForStreamToClose(stream: Stream) { + const promise = deferred(); + const cleanup = () => { + stream.removeListener("close", onClose); + stream.removeListener("error", onError); + }; + const onClose = () => { + cleanup(); + promise.resolve(); + }; + const onError = (err: Error) => { + cleanup(); + promise.reject(err); + }; + stream.once("close", onClose); + stream.once("error", onError); + return promise; +} + +/** + * This function is based on https://github.com/nodejs/node/blob/fc6426ccc4b4cb73076356fb6dbf46a28953af01/lib/child_process.js#L504-L528. + * Copyright Joyent, Inc. and other Node contributors. All rights reserved. MIT license. + */ +function buildCommand( + file: string, + args: string[], + shell: string | boolean, +): [string, string[]] { + if (file === Deno.execPath()) { + // The user is trying to spawn another Deno process as Node.js. + args = toDenoArgs(args); + } + + if (shell) { + const command = [file, ...args].join(" "); + + // Set the shell, switches, and commands. + if (isWindows) { + if (typeof shell === "string") { + file = shell; + } else { + file = Deno.env.get("comspec") || "cmd.exe"; + } + // '/d /s /c' is used only for cmd.exe. + if (/^(?:.*\\)?cmd(?:\.exe)?$/i.test(file)) { + args = ["/d", "/s", "/c", `"${command}"`]; + } else { + args = ["-c", command]; + } + } else { + if (typeof shell === "string") { + file = shell; + } else { + file = "/bin/sh"; + } + args = ["-c", command]; + } + } + return [file, args]; +} + +function _createSpawnSyncError( + status: string, + command: string, + args: string[] = [], +): ErrnoException { + const error = errnoException( + codeMap.get(status), + "spawnSync " + command, + ); + error.path = command; + error.spawnargs = args; + return error; +} + +export interface SpawnOptions extends ChildProcessOptions { + /** + * NOTE: This option is not yet implemented. + */ + timeout?: number; + /** + * NOTE: This option is not yet implemented. + */ + killSignal?: string; +} + +export interface SpawnSyncOptions extends + Pick< + ChildProcessOptions, + | "cwd" + | "env" + | "argv0" + | "stdio" + | "uid" + | "gid" + | "shell" + | "windowsVerbatimArguments" + | "windowsHide" + > { + input?: string | Buffer | DataView; + timeout?: number; + maxBuffer?: number; + encoding?: string; + /** + * NOTE: This option is not yet implemented. + */ + killSignal?: string; +} + +export interface SpawnSyncResult { + pid?: number; + output?: [string | null, string | Buffer | null, string | Buffer | null]; + stdout?: Buffer | string | null; + stderr?: Buffer | string | null; + status?: number | null; + signal?: string | null; + error?: Error; +} + +function parseSpawnSyncOutputStreams( + output: Deno.CommandOutput, + name: "stdout" | "stderr", +): string | Buffer | null { + // new Deno.Command().outputSync() returns getters for stdout and stderr that throw when set + // to 'inherit'. + try { + return Buffer.from(output[name]) as string | Buffer; + } catch { + return null; + } +} + +export function spawnSync( + command: string, + args: string[], + options: SpawnSyncOptions, +): SpawnSyncResult { + const { + env = Deno.env.toObject(), + stdio = ["pipe", "pipe", "pipe"], + shell = false, + cwd, + encoding, + uid, + gid, + maxBuffer, + windowsVerbatimArguments = false, + } = options; + const normalizedStdio = normalizeStdioOption(stdio); + [command, args] = buildCommand(command, args ?? [], shell); + + const result: SpawnSyncResult = {}; + try { + const output = new Deno.Command(command, { + args, + cwd, + env, + stdout: toDenoStdio(normalizedStdio[1] as NodeStdio | number), + stderr: toDenoStdio(normalizedStdio[2] as NodeStdio | number), + uid, + gid, + windowsRawArguments: windowsVerbatimArguments, + }).outputSync(); + + const status = output.signal ? null : 0; + let stdout = parseSpawnSyncOutputStreams(output, "stdout"); + let stderr = parseSpawnSyncOutputStreams(output, "stderr"); + + if ( + (stdout && stdout.length > maxBuffer!) || + (stderr && stderr.length > maxBuffer!) + ) { + result.error = _createSpawnSyncError("ENOBUFS", command, args); + } + + if (encoding && encoding !== "buffer") { + stdout = stdout && stdout.toString(encoding); + stderr = stderr && stderr.toString(encoding); + } + + result.status = status; + result.signal = output.signal; + result.stdout = stdout; + result.stderr = stderr; + result.output = [output.signal, stdout, stderr]; + } catch (err) { + if (err instanceof Deno.errors.NotFound) { + result.error = _createSpawnSyncError("ENOENT", command, args); + } + } + return result; +} + +// These are Node.js CLI flags that expect a value. It's necessary to +// understand these flags in order to properly replace flags passed to the +// child process. For example, -e is a Node flag for eval mode if it is part +// of process.execArgv. However, -e could also be an application flag if it is +// part of process.execv instead. We only want to process execArgv flags. +const kLongArgType = 1; +const kShortArgType = 2; +const kLongArg = { type: kLongArgType }; +const kShortArg = { type: kShortArgType }; +const kNodeFlagsMap = new Map([ + ["--build-snapshot", kLongArg], + ["-c", kShortArg], + ["--check", kLongArg], + ["-C", kShortArg], + ["--conditions", kLongArg], + ["--cpu-prof-dir", kLongArg], + ["--cpu-prof-interval", kLongArg], + ["--cpu-prof-name", kLongArg], + ["--diagnostic-dir", kLongArg], + ["--disable-proto", kLongArg], + ["--dns-result-order", kLongArg], + ["-e", kShortArg], + ["--eval", kLongArg], + ["--experimental-loader", kLongArg], + ["--experimental-policy", kLongArg], + ["--experimental-specifier-resolution", kLongArg], + ["--heapsnapshot-near-heap-limit", kLongArg], + ["--heapsnapshot-signal", kLongArg], + ["--heap-prof-dir", kLongArg], + ["--heap-prof-interval", kLongArg], + ["--heap-prof-name", kLongArg], + ["--icu-data-dir", kLongArg], + ["--input-type", kLongArg], + ["--inspect-publish-uid", kLongArg], + ["--max-http-header-size", kLongArg], + ["--openssl-config", kLongArg], + ["-p", kShortArg], + ["--print", kLongArg], + ["--policy-integrity", kLongArg], + ["--prof-process", kLongArg], + ["-r", kShortArg], + ["--require", kLongArg], + ["--redirect-warnings", kLongArg], + ["--report-dir", kLongArg], + ["--report-directory", kLongArg], + ["--report-filename", kLongArg], + ["--report-signal", kLongArg], + ["--secure-heap", kLongArg], + ["--secure-heap-min", kLongArg], + ["--snapshot-blob", kLongArg], + ["--title", kLongArg], + ["--tls-cipher-list", kLongArg], + ["--tls-keylog", kLongArg], + ["--unhandled-rejections", kLongArg], + ["--use-largepages", kLongArg], + ["--v8-pool-size", kLongArg], +]); +const kDenoSubcommands = new Set([ + "bench", + "bundle", + "cache", + "check", + "compile", + "completions", + "coverage", + "doc", + "eval", + "fmt", + "help", + "info", + "init", + "install", + "lint", + "lsp", + "repl", + "run", + "tasks", + "test", + "types", + "uninstall", + "upgrade", + "vendor", +]); + +function toDenoArgs(args: string[]): string[] { + if (args.length === 0) { + return args; + } + + // Update this logic as more CLI arguments are mapped from Node to Deno. + const denoArgs: string[] = []; + let useRunArgs = true; + + for (let i = 0; i < args.length; i++) { + const arg = args[i]; + + if (arg.charAt(0) !== "-" || arg === "--") { + // Not a flag or no more arguments. + + // If the arg is a Deno subcommand, then the child process is being + // spawned as Deno, not Deno in Node compat mode. In this case, bail out + // and return the original args. + if (kDenoSubcommands.has(arg)) { + return args; + } + + // Copy of the rest of the arguments to the output. + for (let j = i; j < args.length; j++) { + denoArgs.push(args[j]); + } + + break; + } + + // Something that looks like a flag was passed. + let flag = arg; + let flagInfo = kNodeFlagsMap.get(arg); + let isLongWithValue = false; + let flagValue; + + if (flagInfo === undefined) { + // If the flag was not found, it's either not a known flag or it's a long + // flag containing an '='. + const splitAt = arg.indexOf("="); + + if (splitAt !== -1) { + flag = arg.slice(0, splitAt); + flagInfo = kNodeFlagsMap.get(flag); + flagValue = arg.slice(splitAt + 1); + isLongWithValue = true; + } + } + + if (flagInfo === undefined) { + // Not a known flag that expects a value. Just copy it to the output. + denoArgs.push(arg); + continue; + } + + // This is a flag with a value. Get the value if we don't already have it. + if (flagValue === undefined) { + i++; + + if (i >= args.length) { + // There was user error. There should be another arg for the value, but + // there isn't one. Just copy the arg to the output. It's not going + // to work anyway. + denoArgs.push(arg); + continue; + } + + flagValue = args[i]; + } + + // Remap Node's eval flags to Deno. + if (flag === "-e" || flag === "--eval") { + denoArgs.push("eval", flagValue); + useRunArgs = false; + } else if (isLongWithValue) { + denoArgs.push(arg); + } else { + denoArgs.push(flag, flagValue); + } + } + + if (useRunArgs) { + // -A is not ideal, but needed to propagate permissions. + // --unstable is needed for Node compat. + denoArgs.unshift("run", "-A", "--unstable"); + } + + return denoArgs; +} + +export default { + ChildProcess, + normalizeSpawnArguments, + stdioStringToArray, + spawnSync, +}; diff --git a/ext/node/polyfills/internal/cli_table.ts b/ext/node/polyfills/internal/cli_table.ts new file mode 100644 index 00000000000000..68cc6d0443512a --- /dev/null +++ b/ext/node/polyfills/internal/cli_table.ts @@ -0,0 +1,85 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. +// Copyright Joyent and Node contributors. All rights reserved. MIT license. + +import { getStringWidth } from "internal:deno_node/polyfills/internal/util/inspect.mjs"; + +// The use of Unicode characters below is the only non-comment use of non-ASCII +// Unicode characters in Node.js built-in modules. If they are ever removed or +// rewritten with \u escapes, then a test will need to be (re-)added to Node.js +// core to verify that Unicode characters work in built-ins. +// Refs: https://github.com/nodejs/node/issues/10673 +const tableChars = { + middleMiddle: "─", + rowMiddle: "┼", + topRight: "┐", + topLeft: "┌", + leftMiddle: "├", + topMiddle: "┬", + bottomRight: "┘", + bottomLeft: "└", + bottomMiddle: "┴", + rightMiddle: "┤", + left: "│ ", + right: " │", + middle: " │ ", +}; + +const renderRow = (row: string[], columnWidths: number[]) => { + let out = tableChars.left; + for (let i = 0; i < row.length; i++) { + const cell = row[i]; + const len = getStringWidth(cell); + const needed = (columnWidths[i] - len) / 2; + // round(needed) + ceil(needed) will always add up to the amount + // of spaces we need while also left justifying the output. + out += " ".repeat(needed) + cell + + " ".repeat(Math.ceil(needed)); + if (i !== row.length - 1) { + out += tableChars.middle; + } + } + out += tableChars.right; + return out; +}; + +const table = (head: string[], columns: string[][]) => { + const rows: string[][] = []; + const columnWidths = head.map((h) => getStringWidth(h)); + const longestColumn = Math.max(...columns.map((a) => a.length)); + + for (let i = 0; i < head.length; i++) { + const column = columns[i]; + for (let j = 0; j < longestColumn; j++) { + if (rows[j] === undefined) { + rows[j] = []; + } + const value = rows[j][i] = Object.hasOwn(column, j) ? column[j] : ""; + const width = columnWidths[i] || 0; + const counted = getStringWidth(value); + columnWidths[i] = Math.max(width, counted); + } + } + + const divider = columnWidths.map((i) => + tableChars.middleMiddle.repeat(i + 2) + ); + + let result = tableChars.topLeft + + divider.join(tableChars.topMiddle) + + tableChars.topRight + "\n" + + renderRow(head, columnWidths) + "\n" + + tableChars.leftMiddle + + divider.join(tableChars.rowMiddle) + + tableChars.rightMiddle + "\n"; + + for (const row of rows) { + result += `${renderRow(row, columnWidths)}\n`; + } + + result += tableChars.bottomLeft + + divider.join(tableChars.bottomMiddle) + + tableChars.bottomRight; + + return result; +}; +export default table; diff --git a/ext/node/polyfills/internal/console/constructor.mjs b/ext/node/polyfills/internal/console/constructor.mjs new file mode 100644 index 00000000000000..362c97f6893858 --- /dev/null +++ b/ext/node/polyfills/internal/console/constructor.mjs @@ -0,0 +1,677 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. +// Copyright Joyent and Node contributors. All rights reserved. MIT license. + +// Mock trace for now +const trace = () => {}; +import { + ERR_CONSOLE_WRITABLE_STREAM, + ERR_INCOMPATIBLE_OPTION_PAIR, + ERR_INVALID_ARG_VALUE, + isStackOverflowError, +} from "internal:deno_node/polyfills/internal/errors.ts"; +import { + validateArray, + validateInteger, + validateObject, +} from "internal:deno_node/polyfills/internal/validators.mjs"; +const previewEntries = (iter, isKeyValue) => { + if (isKeyValue) { + const arr = [...iter]; + if (Array.isArray(arr[0]) && arr[0].length === 2) { + return [[].concat(...arr), true]; + } + return [arr, false]; + } else { + return [...iter]; + } +}; +import { Buffer } from "internal:deno_node/polyfills/buffer.ts"; +const { isBuffer } = Buffer; +import { formatWithOptions, inspect } from "internal:deno_node/polyfills/internal/util/inspect.mjs"; +import { + isMap, + isMapIterator, + isSet, + isSetIterator, + isTypedArray, +} from "internal:deno_node/polyfills/internal/util/types.ts"; +import { + CHAR_LOWERCASE_B as kTraceBegin, + CHAR_LOWERCASE_E as kTraceEnd, + CHAR_LOWERCASE_N as kTraceInstant, + CHAR_UPPERCASE_C as kTraceCount, +} from "internal:deno_node/polyfills/internal/constants.ts"; +import { clearScreenDown, cursorTo } from "internal:deno_node/polyfills/internal/readline/callbacks.mjs"; +import cliTable from "internal:deno_node/polyfills/internal/cli_table.ts"; +const kCounts = Symbol("counts"); + +const kTraceConsoleCategory = "node,node.console"; + +const kSecond = 1000; +const kMinute = 60 * kSecond; +const kHour = 60 * kMinute; +const kMaxGroupIndentation = 1000; + +// Track amount of indentation required via `console.group()`. +const kGroupIndent = Symbol("kGroupIndent"); +const kGroupIndentationWidth = Symbol("kGroupIndentWidth"); +const kFormatForStderr = Symbol("kFormatForStderr"); +const kFormatForStdout = Symbol("kFormatForStdout"); +const kGetInspectOptions = Symbol("kGetInspectOptions"); +const kColorMode = Symbol("kColorMode"); +const kIsConsole = Symbol("kIsConsole"); +const kWriteToConsole = Symbol("kWriteToConsole"); +const kBindProperties = Symbol("kBindProperties"); +const kBindStreamsEager = Symbol("kBindStreamsEager"); +const kBindStreamsLazy = Symbol("kBindStreamsLazy"); +const kUseStdout = Symbol("kUseStdout"); +const kUseStderr = Symbol("kUseStderr"); + +const optionsMap = new WeakMap(); + +function Console(options /* or: stdout, stderr, ignoreErrors = true */) { + // We have to test new.target here to see if this function is called + // with new, because we need to define a custom instanceof to accommodate + // the global console. + if (!new.target) { + return Reflect.construct(Console, arguments); + } + + if (!options || typeof options.write === "function") { + options = { + stdout: options, + stderr: arguments[1], + ignoreErrors: arguments[2], + }; + } + + const { + stdout, + stderr = stdout, + ignoreErrors = true, + colorMode = "auto", + inspectOptions, + groupIndentation, + } = options; + + if (!stdout || typeof stdout.write !== "function") { + throw new ERR_CONSOLE_WRITABLE_STREAM("stdout"); + } + if (!stderr || typeof stderr.write !== "function") { + throw new ERR_CONSOLE_WRITABLE_STREAM("stderr"); + } + + if (typeof colorMode !== "boolean" && colorMode !== "auto") { + throw new ERR_INVALID_ARG_VALUE("colorMode", colorMode); + } + + if (groupIndentation !== undefined) { + validateInteger( + groupIndentation, + "groupIndentation", + 0, + kMaxGroupIndentation, + ); + } + + if (inspectOptions !== undefined) { + validateObject(inspectOptions, "options.inspectOptions"); + + if ( + inspectOptions.colors !== undefined && + options.colorMode !== undefined + ) { + throw new ERR_INCOMPATIBLE_OPTION_PAIR( + "options.inspectOptions.color", + "colorMode", + ); + } + optionsMap.set(this, inspectOptions); + } + + // Bind the prototype functions to this Console instance + Object.keys(Console.prototype).forEach((key) => { + // We have to bind the methods grabbed from the instance instead of from + // the prototype so that users extending the Console can override them + // from the prototype chain of the subclass. + this[key] = this[key].bind(this); + Object.defineProperty(this[key], "name", { + value: key, + }); + }); + + this[kBindStreamsEager](stdout, stderr); + this[kBindProperties](ignoreErrors, colorMode, groupIndentation); +} + +const consolePropAttributes = { + writable: true, + enumerable: false, + configurable: true, +}; + +// Fixup global.console instanceof global.console.Console +Object.defineProperty(Console, Symbol.hasInstance, { + value(instance) { + return instance === console || instance[kIsConsole]; + }, +}); + +const kColorInspectOptions = { colors: true }; +const kNoColorInspectOptions = {}; + +Object.defineProperties(Console.prototype, { + [kBindStreamsEager]: { + ...consolePropAttributes, + // Eager version for the Console constructor + value: function (stdout, stderr) { + Object.defineProperties(this, { + "_stdout": { ...consolePropAttributes, value: stdout }, + "_stderr": { ...consolePropAttributes, value: stderr }, + }); + }, + }, + [kBindStreamsLazy]: { + ...consolePropAttributes, + // Lazily load the stdout and stderr from an object so we don't + // create the stdio streams when they are not even accessed + value: function (object) { + let stdout; + let stderr; + Object.defineProperties(this, { + "_stdout": { + enumerable: false, + configurable: true, + get() { + if (!stdout) stdout = object.stdout; + return stdout; + }, + set(value) { + stdout = value; + }, + }, + "_stderr": { + enumerable: false, + configurable: true, + get() { + if (!stderr) stderr = object.stderr; + return stderr; + }, + set(value) { + stderr = value; + }, + }, + }); + }, + }, + [kBindProperties]: { + ...consolePropAttributes, + value: function (ignoreErrors, colorMode, groupIndentation = 2) { + Object.defineProperties(this, { + "_stdoutErrorHandler": { + ...consolePropAttributes, + value: createWriteErrorHandler(this, kUseStdout), + }, + "_stderrErrorHandler": { + ...consolePropAttributes, + value: createWriteErrorHandler(this, kUseStderr), + }, + "_ignoreErrors": { + ...consolePropAttributes, + value: Boolean(ignoreErrors), + }, + "_times": { ...consolePropAttributes, value: new Map() }, + // Corresponds to https://console.spec.whatwg.org/#count-map + [kCounts]: { ...consolePropAttributes, value: new Map() }, + [kColorMode]: { ...consolePropAttributes, value: colorMode }, + [kIsConsole]: { ...consolePropAttributes, value: true }, + [kGroupIndent]: { ...consolePropAttributes, value: "" }, + [kGroupIndentationWidth]: { + ...consolePropAttributes, + value: groupIndentation, + }, + [Symbol.toStringTag]: { + writable: false, + enumerable: false, + configurable: true, + value: "console", + }, + }); + }, + }, + [kWriteToConsole]: { + ...consolePropAttributes, + value: function (streamSymbol, string) { + const ignoreErrors = this._ignoreErrors; + const groupIndent = this[kGroupIndent]; + + const useStdout = streamSymbol === kUseStdout; + const stream = useStdout ? this._stdout : this._stderr; + const errorHandler = useStdout + ? this._stdoutErrorHandler + : this._stderrErrorHandler; + + if (groupIndent.length !== 0) { + if (string.includes("\n")) { + string = string.replace(/\n/g, `\n${groupIndent}`); + } + string = groupIndent + string; + } + string += "\n"; + + if (ignoreErrors === false) return stream.write(string); + + // There may be an error occurring synchronously (e.g. for files or TTYs + // on POSIX systems) or asynchronously (e.g. pipes on POSIX systems), so + // handle both situations. + try { + // Add and later remove a noop error handler to catch synchronous + // errors. + if (stream.listenerCount("error") === 0) { + stream.once("error", noop); + } + + stream.write(string, errorHandler); + } catch (e) { + // Console is a debugging utility, so it swallowing errors is not + // desirable even in edge cases such as low stack space. + if (isStackOverflowError(e)) { + throw e; + } + // Sorry, there's no proper way to pass along the error here. + } finally { + stream.removeListener("error", noop); + } + }, + }, + [kGetInspectOptions]: { + ...consolePropAttributes, + value: function (stream) { + let color = this[kColorMode]; + if (color === "auto") { + color = stream.isTTY && ( + typeof stream.getColorDepth === "function" + ? stream.getColorDepth() > 2 + : true + ); + } + + const options = optionsMap.get(this); + if (options) { + if (options.colors === undefined) { + options.colors = color; + } + return options; + } + + return color ? kColorInspectOptions : kNoColorInspectOptions; + }, + }, + [kFormatForStdout]: { + ...consolePropAttributes, + value: function (args) { + const opts = this[kGetInspectOptions](this._stdout); + args.unshift(opts); + return Reflect.apply(formatWithOptions, null, args); + }, + }, + [kFormatForStderr]: { + ...consolePropAttributes, + value: function (args) { + const opts = this[kGetInspectOptions](this._stderr); + args.unshift(opts); + return Reflect.apply(formatWithOptions, null, args); + }, + }, +}); + +// Make a function that can serve as the callback passed to `stream.write()`. +function createWriteErrorHandler(instance, streamSymbol) { + return (err) => { + // This conditional evaluates to true if and only if there was an error + // that was not already emitted (which happens when the _write callback + // is invoked asynchronously). + const stream = streamSymbol === kUseStdout + ? instance._stdout + : instance._stderr; + if (err !== null && !stream._writableState.errorEmitted) { + // If there was an error, it will be emitted on `stream` as + // an `error` event. Adding a `once` listener will keep that error + // from becoming an uncaught exception, but since the handler is + // removed after the event, non-console.* writes won't be affected. + // we are only adding noop if there is no one else listening for 'error' + if (stream.listenerCount("error") === 0) { + stream.once("error", noop); + } + } + }; +} + +const consoleMethods = { + log(...args) { + this[kWriteToConsole](kUseStdout, this[kFormatForStdout](args)); + }, + + warn(...args) { + this[kWriteToConsole](kUseStderr, this[kFormatForStderr](args)); + }, + + dir(object, options) { + this[kWriteToConsole]( + kUseStdout, + inspect(object, { + customInspect: false, + ...this[kGetInspectOptions](this._stdout), + ...options, + }), + ); + }, + + time(label = "default") { + // Coerces everything other than Symbol to a string + label = `${label}`; + if (this._times.has(label)) { + emitWarning(`Label '${label}' already exists for console.time()`); + return; + } + trace(kTraceBegin, kTraceConsoleCategory, `time::${label}`, 0); + this._times.set(label, process.hrtime()); + }, + + timeEnd(label = "default") { + // Coerces everything other than Symbol to a string + label = `${label}`; + const found = timeLogImpl(this, "timeEnd", label); + trace(kTraceEnd, kTraceConsoleCategory, `time::${label}`, 0); + if (found) { + this._times.delete(label); + } + }, + + timeLog(label = "default", ...data) { + // Coerces everything other than Symbol to a string + label = `${label}`; + timeLogImpl(this, "timeLog", label, data); + trace(kTraceInstant, kTraceConsoleCategory, `time::${label}`, 0); + }, + + trace: function trace(...args) { + const err = { + name: "Trace", + message: this[kFormatForStderr](args), + }; + Error.captureStackTrace(err, trace); + this.error(err.stack); + }, + + assert(expression, ...args) { + if (!expression) { + args[0] = `Assertion failed${args.length === 0 ? "" : `: ${args[0]}`}`; + // The arguments will be formatted in warn() again + Reflect.apply(this.warn, this, args); + } + }, + + // Defined by: https://console.spec.whatwg.org/#clear + clear() { + // It only makes sense to clear if _stdout is a TTY. + // Otherwise, do nothing. + if (this._stdout.isTTY && process.env.TERM !== "dumb") { + cursorTo(this._stdout, 0, 0); + clearScreenDown(this._stdout); + } + }, + + // Defined by: https://console.spec.whatwg.org/#count + count(label = "default") { + // Ensures that label is a string, and only things that can be + // coerced to strings. e.g. Symbol is not allowed + label = `${label}`; + const counts = this[kCounts]; + let count = counts.get(label); + if (count === undefined) { + count = 1; + } else { + count++; + } + counts.set(label, count); + trace(kTraceCount, kTraceConsoleCategory, `count::${label}`, 0, count); + this.log(`${label}: ${count}`); + }, + + // Defined by: https://console.spec.whatwg.org/#countreset + countReset(label = "default") { + const counts = this[kCounts]; + if (!counts.has(label)) { + emitWarning(`Count for '${label}' does not exist`); + return; + } + trace(kTraceCount, kTraceConsoleCategory, `count::${label}`, 0, 0); + counts.delete(`${label}`); + }, + + group(...data) { + if (data.length > 0) { + Reflect.apply(this.log, this, data); + } + this[kGroupIndent] += " ".repeat(this[kGroupIndentationWidth]); + }, + + groupEnd() { + this[kGroupIndent] = this[kGroupIndent].slice( + 0, + this[kGroupIndent].length - this[kGroupIndentationWidth], + ); + }, + + // https://console.spec.whatwg.org/#table + table(tabularData, properties) { + console.log("tabularData", tabularData); + if (properties !== undefined) { + validateArray(properties, "properties"); + } + + if (tabularData === null || typeof tabularData !== "object") { + return this.log(tabularData); + } + + const final = (k, v) => this.log(cliTable(k, v)); + + const _inspect = (v) => { + const depth = v !== null && + typeof v === "object" && + !isArray(v) && + Object.keys(v).length > 2 + ? -1 + : 0; + const opt = { + depth, + maxArrayLength: 3, + breakLength: Infinity, + ...this[kGetInspectOptions](this._stdout), + }; + return inspect(v, opt); + }; + const getIndexArray = (length) => + Array.from( + { length }, + (_, i) => _inspect(i), + ); + + const mapIter = isMapIterator(tabularData); + let isKeyValue = false; + let i = 0; + if (mapIter) { + const res = previewEntries(tabularData, true); + tabularData = res[0]; + isKeyValue = res[1]; + } + + if (isKeyValue || isMap(tabularData)) { + const keys = []; + const values = []; + let length = 0; + if (mapIter) { + for (; i < tabularData.length / 2; ++i) { + keys.push(_inspect(tabularData[i * 2])); + values.push(_inspect(tabularData[i * 2 + 1])); + length++; + } + } else { + for (const { 0: k, 1: v } of tabularData) { + keys.push(_inspect(k)); + values.push(_inspect(v)); + length++; + } + } + return final([ + iterKey, + keyKey, + valuesKey, + ], [ + getIndexArray(length), + keys, + values, + ]); + } + + const setIter = isSetIterator(tabularData); + if (setIter) { + tabularData = previewEntries(tabularData); + } + + const setlike = setIter || mapIter || isSet(tabularData); + if (setlike) { + const values = []; + let length = 0; + console.log("tabularData", tabularData); + for (const v of tabularData) { + values.push(_inspect(v)); + length++; + } + return final([iterKey, valuesKey], [getIndexArray(length), values]); + } + + const map = Object.create(null); + let hasPrimitives = false; + const valuesKeyArray = []; + const indexKeyArray = Object.keys(tabularData); + + for (; i < indexKeyArray.length; i++) { + const item = tabularData[indexKeyArray[i]]; + const primitive = item === null || + (typeof item !== "function" && typeof item !== "object"); + if (properties === undefined && primitive) { + hasPrimitives = true; + valuesKeyArray[i] = _inspect(item); + } else { + const keys = properties || Object.keys(item); + for (const key of keys) { + if (map[key] === undefined) { + map[key] = []; + } + if ( + (primitive && properties) || + !Object.hasOwn(item, key) + ) { + map[key][i] = ""; + } else { + map[key][i] = _inspect(item[key]); + } + } + } + } + + const keys = Object.keys(map); + const values = Object.values(map); + if (hasPrimitives) { + keys.push(valuesKey); + values.push(valuesKeyArray); + } + keys.unshift(indexKey); + values.unshift(indexKeyArray); + + return final(keys, values); + }, +}; + +// Returns true if label was found +function timeLogImpl(self, name, label, data) { + const time = self._times.get(label); + if (time === undefined) { + emitWarning(`No such label '${label}' for console.${name}()`); + return false; + } + const duration = process.hrtime(time); + const ms = duration[0] * 1000 + duration[1] / 1e6; + + const formatted = formatTime(ms); + + if (data === undefined) { + self.log("%s: %s", label, formatted); + } else { + self.log("%s: %s", label, formatted, ...data); + } + return true; +} + +function pad(value) { + return `${value}`.padStart(2, "0"); +} + +function formatTime(ms) { + let hours = 0; + let minutes = 0; + let seconds = 0; + + if (ms >= kSecond) { + if (ms >= kMinute) { + if (ms >= kHour) { + hours = Math.floor(ms / kHour); + ms = ms % kHour; + } + minutes = Math.floor(ms / kMinute); + ms = ms % kMinute; + } + seconds = ms / kSecond; + } + + if (hours !== 0 || minutes !== 0) { + ({ 0: seconds, 1: ms } = seconds.toFixed(3).split(".")); + const res = hours !== 0 ? `${hours}:${pad(minutes)}` : minutes; + return `${res}:${pad(seconds)}.${ms} (${hours !== 0 ? "h:m" : ""}m:ss.mmm)`; + } + + if (seconds !== 0) { + return `${seconds.toFixed(3)}s`; + } + + return `${Number(ms.toFixed(3))}ms`; +} + +const keyKey = "Key"; +const valuesKey = "Values"; +const indexKey = "(index)"; +const iterKey = "(iteration index)"; + +const isArray = (v) => Array.isArray(v) || isTypedArray(v) || isBuffer(v); + +function noop() {} + +for (const method of Reflect.ownKeys(consoleMethods)) { + Console.prototype[method] = consoleMethods[method]; +} + +Console.prototype.debug = Console.prototype.log; +Console.prototype.info = Console.prototype.log; +Console.prototype.dirxml = Console.prototype.log; +Console.prototype.error = Console.prototype.warn; +Console.prototype.groupCollapsed = Console.prototype.group; + +export { Console, formatTime, kBindProperties, kBindStreamsLazy }; +export default { + Console, + kBindStreamsLazy, + kBindProperties, + formatTime, +}; diff --git a/ext/node/polyfills/internal/constants.ts b/ext/node/polyfills/internal/constants.ts new file mode 100644 index 00000000000000..5c5cafe8d19de6 --- /dev/null +++ b/ext/node/polyfills/internal/constants.ts @@ -0,0 +1,105 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. +// Copyright Joyent and Node contributors. All rights reserved. MIT license. + +const { ops } = globalThis.__bootstrap.core; +const isWindows = ops.op_node_build_os() === "windows"; + +// Alphabet chars. +export const CHAR_UPPERCASE_A = 65; /* A */ +export const CHAR_LOWERCASE_A = 97; /* a */ +export const CHAR_UPPERCASE_Z = 90; /* Z */ +export const CHAR_LOWERCASE_Z = 122; /* z */ +export const CHAR_UPPERCASE_C = 67; /* C */ +export const CHAR_LOWERCASE_B = 98; /* b */ +export const CHAR_LOWERCASE_E = 101; /* e */ +export const CHAR_LOWERCASE_N = 110; /* n */ + +// Non-alphabetic chars. +export const CHAR_DOT = 46; /* . */ +export const CHAR_FORWARD_SLASH = 47; /* / */ +export const CHAR_BACKWARD_SLASH = 92; /* \ */ +export const CHAR_VERTICAL_LINE = 124; /* | */ +export const CHAR_COLON = 58; /* = */ +export const CHAR_QUESTION_MARK = 63; /* ? */ +export const CHAR_UNDERSCORE = 95; /* _ */ +export const CHAR_LINE_FEED = 10; /* \n */ +export const CHAR_CARRIAGE_RETURN = 13; /* \r */ +export const CHAR_TAB = 9; /* \t */ +export const CHAR_FORM_FEED = 12; /* \f */ +export const CHAR_EXCLAMATION_MARK = 33; /* ! */ +export const CHAR_HASH = 35; /* # */ +export const CHAR_SPACE = 32; /* */ +export const CHAR_NO_BREAK_SPACE = 160; /* \u00A0 */ +export const CHAR_ZERO_WIDTH_NOBREAK_SPACE = 65279; /* \uFEFF */ +export const CHAR_LEFT_SQUARE_BRACKET = 91; /* [ */ +export const CHAR_RIGHT_SQUARE_BRACKET = 93; /* ] */ +export const CHAR_LEFT_ANGLE_BRACKET = 60; /* < */ +export const CHAR_RIGHT_ANGLE_BRACKET = 62; /* > */ +export const CHAR_LEFT_CURLY_BRACKET = 123; /* { */ +export const CHAR_RIGHT_CURLY_BRACKET = 125; /* } */ +export const CHAR_HYPHEN_MINUS = 45; /* - */ +export const CHAR_PLUS = 43; /* + */ +export const CHAR_DOUBLE_QUOTE = 34; /* " */ +export const CHAR_SINGLE_QUOTE = 39; /* ' */ +export const CHAR_PERCENT = 37; /* % */ +export const CHAR_SEMICOLON = 59; /* ; */ +export const CHAR_CIRCUMFLEX_ACCENT = 94; /* ^ */ +export const CHAR_GRAVE_ACCENT = 96; /* ` */ +export const CHAR_AT = 64; /* @ */ +export const CHAR_AMPERSAND = 38; /* & */ +export const CHAR_EQUAL = 61; /* = */ + +// Digits +export const CHAR_0 = 48; /* 0 */ +export const CHAR_9 = 57; /* 9 */ + +export const EOL = isWindows ? "\r\n" : "\n"; + +export default { + CHAR_UPPERCASE_A, + CHAR_LOWERCASE_A, + CHAR_UPPERCASE_Z, + CHAR_LOWERCASE_Z, + CHAR_UPPERCASE_C, + CHAR_LOWERCASE_B, + CHAR_LOWERCASE_E, + CHAR_LOWERCASE_N, + CHAR_DOT, + CHAR_FORWARD_SLASH, + CHAR_BACKWARD_SLASH, + CHAR_VERTICAL_LINE, + CHAR_COLON, + CHAR_QUESTION_MARK, + CHAR_UNDERSCORE, + CHAR_LINE_FEED, + CHAR_CARRIAGE_RETURN, + CHAR_TAB, + CHAR_FORM_FEED, + CHAR_EXCLAMATION_MARK, + CHAR_HASH, + CHAR_SPACE, + CHAR_NO_BREAK_SPACE, + CHAR_ZERO_WIDTH_NOBREAK_SPACE, + CHAR_LEFT_SQUARE_BRACKET, + CHAR_RIGHT_SQUARE_BRACKET, + CHAR_LEFT_ANGLE_BRACKET, + CHAR_RIGHT_ANGLE_BRACKET, + CHAR_LEFT_CURLY_BRACKET, + CHAR_RIGHT_CURLY_BRACKET, + CHAR_HYPHEN_MINUS, + CHAR_PLUS, + CHAR_DOUBLE_QUOTE, + CHAR_SINGLE_QUOTE, + CHAR_PERCENT, + CHAR_SEMICOLON, + CHAR_CIRCUMFLEX_ACCENT, + CHAR_GRAVE_ACCENT, + CHAR_AT, + CHAR_AMPERSAND, + CHAR_EQUAL, + + CHAR_0, + CHAR_9, + + EOL, +}; diff --git a/ext/node/polyfills/internal/crypto/_hex.ts b/ext/node/polyfills/internal/crypto/_hex.ts new file mode 100644 index 00000000000000..5cc44aaa875a39 --- /dev/null +++ b/ext/node/polyfills/internal/crypto/_hex.ts @@ -0,0 +1,19 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. + +// deno-fmt-ignore +const hexTable = new Uint8Array([ + 48, 49, 50, 51, 52, 53, + 54, 55, 56, 57, 97, 98, + 99, 100, 101, 102 +]); + +/** Encodes `src` into `src.length * 2` bytes. */ +export function encode(src: Uint8Array): Uint8Array { + const dst = new Uint8Array(src.length * 2); + for (let i = 0; i < dst.length; i++) { + const v = src[i]; + dst[i * 2] = hexTable[v >> 4]; + dst[i * 2 + 1] = hexTable[v & 0x0f]; + } + return dst; +} diff --git a/ext/node/polyfills/internal/crypto/_keys.ts b/ext/node/polyfills/internal/crypto/_keys.ts new file mode 100644 index 00000000000000..794582bf17b86b --- /dev/null +++ b/ext/node/polyfills/internal/crypto/_keys.ts @@ -0,0 +1,16 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. +import { kKeyObject } from "internal:deno_node/polyfills/internal/crypto/constants.ts"; + +export const kKeyType = Symbol("kKeyType"); + +export function isKeyObject(obj: unknown): boolean { + return ( + obj != null && (obj as Record)[kKeyType] !== undefined + ); +} + +export function isCryptoKey(obj: unknown): boolean { + return ( + obj != null && (obj as Record)[kKeyObject] !== undefined + ); +} diff --git a/ext/node/polyfills/internal/crypto/_randomBytes.ts b/ext/node/polyfills/internal/crypto/_randomBytes.ts new file mode 100644 index 00000000000000..41678fcf1f8693 --- /dev/null +++ b/ext/node/polyfills/internal/crypto/_randomBytes.ts @@ -0,0 +1,70 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. +import { Buffer } from "internal:deno_node/polyfills/buffer.ts"; + +export const MAX_RANDOM_VALUES = 65536; +export const MAX_SIZE = 4294967295; + +function generateRandomBytes(size: number) { + if (size > MAX_SIZE) { + throw new RangeError( + `The value of "size" is out of range. It must be >= 0 && <= ${MAX_SIZE}. Received ${size}`, + ); + } + + const bytes = Buffer.allocUnsafe(size); + + //Work around for getRandomValues max generation + if (size > MAX_RANDOM_VALUES) { + for (let generated = 0; generated < size; generated += MAX_RANDOM_VALUES) { + globalThis.crypto.getRandomValues( + bytes.slice(generated, generated + MAX_RANDOM_VALUES), + ); + } + } else { + globalThis.crypto.getRandomValues(bytes); + } + + return bytes; +} + +/** + * @param size Buffer length, must be equal or greater than zero + */ +export default function randomBytes(size: number): Buffer; +export default function randomBytes( + size: number, + cb?: (err: Error | null, buf?: Buffer) => void, +): void; +export default function randomBytes( + size: number, + cb?: (err: Error | null, buf?: Buffer) => void, +): Buffer | void { + if (typeof cb === "function") { + let err: Error | null = null, bytes: Buffer; + try { + bytes = generateRandomBytes(size); + } catch (e) { + //NodeJS nonsense + //If the size is out of range it will throw sync, otherwise throw async + if ( + e instanceof RangeError && + e.message.includes('The value of "size" is out of range') + ) { + throw e; + } else if (e instanceof Error) { + err = e; + } else { + err = new Error("[non-error thrown]"); + } + } + setTimeout(() => { + if (err) { + cb(err); + } else { + cb(null, bytes); + } + }, 0); + } else { + return generateRandomBytes(size); + } +} diff --git a/ext/node/polyfills/internal/crypto/_randomFill.ts b/ext/node/polyfills/internal/crypto/_randomFill.ts new file mode 100644 index 00000000000000..04507269688dec --- /dev/null +++ b/ext/node/polyfills/internal/crypto/_randomFill.ts @@ -0,0 +1,84 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. +import randomBytes, { + MAX_SIZE as kMaxUint32, +} from "internal:deno_node/polyfills/internal/crypto/_randomBytes.ts"; +import { Buffer } from "internal:deno_node/polyfills/buffer.ts"; + +const kBufferMaxLength = 0x7fffffff; + +function assertOffset(offset: number, length: number) { + if (offset > kMaxUint32 || offset < 0) { + throw new TypeError("offset must be a uint32"); + } + + if (offset > kBufferMaxLength || offset > length) { + throw new RangeError("offset out of range"); + } +} + +function assertSize(size: number, offset: number, length: number) { + if (size > kMaxUint32 || size < 0) { + throw new TypeError("size must be a uint32"); + } + + if (size + offset > length || size > kBufferMaxLength) { + throw new RangeError("buffer too small"); + } +} + +export default function randomFill( + buf: Buffer, + cb: (err: Error | null, buf: Buffer) => void, +): void; + +export default function randomFill( + buf: Buffer, + offset: number, + cb: (err: Error | null, buf: Buffer) => void, +): void; + +export default function randomFill( + buf: Buffer, + offset: number, + size: number, + cb: (err: Error | null, buf: Buffer) => void, +): void; + +export default function randomFill( + buf: Buffer, + offset?: number | ((err: Error | null, buf: Buffer) => void), + size?: number | ((err: Error | null, buf: Buffer) => void), + cb?: (err: Error | null, buf: Buffer) => void, +) { + if (typeof offset === "function") { + cb = offset; + offset = 0; + size = buf.length; + } else if (typeof size === "function") { + cb = size; + size = buf.length - Number(offset as number); + } + + assertOffset(offset as number, buf.length); + assertSize(size as number, offset as number, buf.length); + + randomBytes(size as number, (err, bytes) => { + if (err) return cb!(err, buf); + bytes?.copy(buf, offset as number); + cb!(null, buf); + }); +} + +export function randomFillSync(buf: Buffer, offset = 0, size?: number) { + assertOffset(offset, buf.length); + + if (size === undefined) size = buf.length - offset; + + assertSize(size, offset, buf.length); + + const bytes = randomBytes(size); + + bytes.copy(buf, offset); + + return buf; +} diff --git a/ext/node/polyfills/internal/crypto/_randomInt.ts b/ext/node/polyfills/internal/crypto/_randomInt.ts new file mode 100644 index 00000000000000..6372515410f144 --- /dev/null +++ b/ext/node/polyfills/internal/crypto/_randomInt.ts @@ -0,0 +1,60 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. +export default function randomInt(max: number): number; +export default function randomInt(min: number, max: number): number; +export default function randomInt( + max: number, + cb: (err: Error | null, n?: number) => void, +): void; +export default function randomInt( + min: number, + max: number, + cb: (err: Error | null, n?: number) => void, +): void; + +export default function randomInt( + max: number, + min?: ((err: Error | null, n?: number) => void) | number, + cb?: (err: Error | null, n?: number) => void, +): number | void { + if (typeof max === "number" && typeof min === "number") { + [max, min] = [min, max]; + } + if (min === undefined) min = 0; + else if (typeof min === "function") { + cb = min; + min = 0; + } + + if ( + !Number.isSafeInteger(min) || + typeof max === "number" && !Number.isSafeInteger(max) + ) { + throw new Error("max or min is not a Safe Number"); + } + + if (max - min > Math.pow(2, 48)) { + throw new RangeError("max - min should be less than 2^48!"); + } + + if (min >= max) { + throw new Error("Min is bigger than Max!"); + } + + const randomBuffer = new Uint32Array(1); + + globalThis.crypto.getRandomValues(randomBuffer); + + const randomNumber = randomBuffer[0] / (0xffffffff + 1); + + min = Math.ceil(min); + max = Math.floor(max); + + const result = Math.floor(randomNumber * (max - min)) + min; + + if (cb) { + cb(null, result); + return; + } + + return result; +} diff --git a/ext/node/polyfills/internal/crypto/certificate.ts b/ext/node/polyfills/internal/crypto/certificate.ts new file mode 100644 index 00000000000000..f6fb333a9ca4b5 --- /dev/null +++ b/ext/node/polyfills/internal/crypto/certificate.ts @@ -0,0 +1,23 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. +// Copyright Joyent, Inc. and Node.js contributors. All rights reserved. MIT license. + +import { notImplemented } from "internal:deno_node/polyfills/_utils.ts"; +import { Buffer } from "internal:deno_node/polyfills/buffer.ts"; +import { BinaryLike } from "internal:deno_node/polyfills/internal/crypto/types.ts"; + +export class Certificate { + static Certificate = Certificate; + static exportChallenge(_spkac: BinaryLike, _encoding?: string): Buffer { + notImplemented("crypto.Certificate.exportChallenge"); + } + + static exportPublicKey(_spkac: BinaryLike, _encoding?: string): Buffer { + notImplemented("crypto.Certificate.exportPublicKey"); + } + + static verifySpkac(_spkac: BinaryLike, _encoding?: string): boolean { + notImplemented("crypto.Certificate.verifySpkac"); + } +} + +export default Certificate; diff --git a/ext/node/polyfills/internal/crypto/cipher.ts b/ext/node/polyfills/internal/crypto/cipher.ts new file mode 100644 index 00000000000000..ddef000761d922 --- /dev/null +++ b/ext/node/polyfills/internal/crypto/cipher.ts @@ -0,0 +1,309 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. +// Copyright Joyent, Inc. and Node.js contributors. All rights reserved. MIT license. + +import { ERR_INVALID_ARG_TYPE } from "internal:deno_node/polyfills/internal/errors.ts"; +import { + validateInt32, + validateObject, +} from "internal:deno_node/polyfills/internal/validators.mjs"; +import { Buffer } from "internal:deno_node/polyfills/buffer.ts"; +import { notImplemented } from "internal:deno_node/polyfills/_utils.ts"; +import type { TransformOptions } from "internal:deno_node/polyfills/_stream.d.ts"; +import { Transform } from "internal:deno_node/polyfills/_stream.mjs"; +import { KeyObject } from "internal:deno_node/polyfills/internal/crypto/keys.ts"; +import type { BufferEncoding } from "internal:deno_node/polyfills/_global.d.ts"; +import type { + BinaryLike, + Encoding, +} from "internal:deno_node/polyfills/internal/crypto/types.ts"; + +const { ops } = globalThis.__bootstrap.core; + +export type CipherCCMTypes = + | "aes-128-ccm" + | "aes-192-ccm" + | "aes-256-ccm" + | "chacha20-poly1305"; +export type CipherGCMTypes = "aes-128-gcm" | "aes-192-gcm" | "aes-256-gcm"; +export type CipherOCBTypes = "aes-128-ocb" | "aes-192-ocb" | "aes-256-ocb"; + +export type CipherKey = BinaryLike | KeyObject; + +export interface CipherCCMOptions extends TransformOptions { + authTagLength: number; +} + +export interface CipherGCMOptions extends TransformOptions { + authTagLength?: number | undefined; +} + +export interface CipherOCBOptions extends TransformOptions { + authTagLength: number; +} + +export interface Cipher extends ReturnType { + update(data: BinaryLike): Buffer; + update(data: string, inputEncoding: Encoding): Buffer; + update( + data: ArrayBufferView, + inputEncoding: undefined, + outputEncoding: Encoding, + ): string; + update( + data: string, + inputEncoding: Encoding | undefined, + outputEncoding: Encoding, + ): string; + + final(): Buffer; + final(outputEncoding: BufferEncoding): string; + + setAutoPadding(autoPadding?: boolean): this; +} + +export type Decipher = Cipher; + +export interface CipherCCM extends Cipher { + setAAD( + buffer: ArrayBufferView, + options: { + plaintextLength: number; + }, + ): this; + getAuthTag(): Buffer; +} + +export interface CipherGCM extends Cipher { + setAAD( + buffer: ArrayBufferView, + options?: { + plaintextLength: number; + }, + ): this; + getAuthTag(): Buffer; +} + +export interface CipherOCB extends Cipher { + setAAD( + buffer: ArrayBufferView, + options?: { + plaintextLength: number; + }, + ): this; + getAuthTag(): Buffer; +} + +export interface DecipherCCM extends Decipher { + setAuthTag(buffer: ArrayBufferView): this; + setAAD( + buffer: ArrayBufferView, + options: { + plaintextLength: number; + }, + ): this; +} + +export interface DecipherGCM extends Decipher { + setAuthTag(buffer: ArrayBufferView): this; + setAAD( + buffer: ArrayBufferView, + options?: { + plaintextLength: number; + }, + ): this; +} + +export interface DecipherOCB extends Decipher { + setAuthTag(buffer: ArrayBufferView): this; + setAAD( + buffer: ArrayBufferView, + options?: { + plaintextLength: number; + }, + ): this; +} + +export class Cipheriv extends Transform implements Cipher { + constructor( + _cipher: string, + _key: CipherKey, + _iv: BinaryLike | null, + _options?: TransformOptions, + ) { + super(); + + notImplemented("crypto.Cipheriv"); + } + + final(): Buffer; + final(outputEncoding: BufferEncoding): string; + final(_outputEncoding?: string): Buffer | string { + notImplemented("crypto.Cipheriv.prototype.final"); + } + + getAuthTag(): Buffer { + notImplemented("crypto.Cipheriv.prototype.getAuthTag"); + } + + setAAD( + _buffer: ArrayBufferView, + _options?: { + plaintextLength: number; + }, + ): this { + notImplemented("crypto.Cipheriv.prototype.setAAD"); + } + + setAutoPadding(_autoPadding?: boolean): this { + notImplemented("crypto.Cipheriv.prototype.setAutoPadding"); + } + + update(data: BinaryLike): Buffer; + update(data: string, inputEncoding: Encoding): Buffer; + update( + data: ArrayBufferView, + inputEncoding: undefined, + outputEncoding: Encoding, + ): string; + update( + data: string, + inputEncoding: Encoding | undefined, + outputEncoding: Encoding, + ): string; + update( + _data: string | BinaryLike | ArrayBufferView, + _inputEncoding?: Encoding, + _outputEncoding?: Encoding, + ): Buffer | string { + notImplemented("crypto.Cipheriv.prototype.update"); + } +} + +export class Decipheriv extends Transform implements Cipher { + constructor( + _cipher: string, + _key: CipherKey, + _iv: BinaryLike | null, + _options?: TransformOptions, + ) { + super(); + + notImplemented("crypto.Decipheriv"); + } + + final(): Buffer; + final(outputEncoding: BufferEncoding): string; + final(_outputEncoding?: string): Buffer | string { + notImplemented("crypto.Decipheriv.prototype.final"); + } + + setAAD( + _buffer: ArrayBufferView, + _options?: { + plaintextLength: number; + }, + ): this { + notImplemented("crypto.Decipheriv.prototype.setAAD"); + } + + setAuthTag(_buffer: BinaryLike, _encoding?: string): this { + notImplemented("crypto.Decipheriv.prototype.setAuthTag"); + } + + setAutoPadding(_autoPadding?: boolean): this { + notImplemented("crypto.Decipheriv.prototype.setAutoPadding"); + } + + update(data: BinaryLike): Buffer; + update(data: string, inputEncoding: Encoding): Buffer; + update( + data: ArrayBufferView, + inputEncoding: undefined, + outputEncoding: Encoding, + ): string; + update( + data: string, + inputEncoding: Encoding | undefined, + outputEncoding: Encoding, + ): string; + update( + _data: string | BinaryLike | ArrayBufferView, + _inputEncoding?: Encoding, + _outputEncoding?: Encoding, + ): Buffer | string { + notImplemented("crypto.Decipheriv.prototype.update"); + } +} + +export function getCipherInfo( + nameOrNid: string | number, + options?: { keyLength?: number; ivLength?: number }, +) { + if (typeof nameOrNid !== "string" && typeof nameOrNid !== "number") { + throw new ERR_INVALID_ARG_TYPE( + "nameOrNid", + ["string", "number"], + nameOrNid, + ); + } + + if (typeof nameOrNid === "number") { + validateInt32(nameOrNid, "nameOrNid"); + } + + let keyLength, ivLength; + + if (options !== undefined) { + validateObject(options, "options"); + + ({ keyLength, ivLength } = options); + + if (keyLength !== undefined) { + validateInt32(keyLength, "options.keyLength"); + } + + if (ivLength !== undefined) { + validateInt32(ivLength, "options.ivLength"); + } + } + + notImplemented("crypto.getCipherInfo"); +} + +export function privateEncrypt( + privateKey: ArrayBufferView | string | KeyObject, + buffer: ArrayBufferView | string | KeyObject, +): Buffer { + const padding = privateKey.padding || 1; + return ops.op_node_private_encrypt(privateKey, buffer, padding); +} + +export function privateDecrypt( + privateKey: ArrayBufferView | string | KeyObject, + buffer: ArrayBufferView | string | KeyObject, +): Buffer { + const padding = privateKey.padding || 1; + return ops.op_node_private_decrypt(privateKey, buffer, padding); +} + +export function publicEncrypt( + publicKey: ArrayBufferView | string | KeyObject, + buffer: ArrayBufferView | string | KeyObject, +): Buffer { + const padding = publicKey.padding || 1; + return ops.op_node_public_encrypt(publicKey, buffer, padding); +} + +export function publicDecrypt() { + notImplemented("crypto.publicDecrypt"); +} + +export default { + privateDecrypt, + privateEncrypt, + publicDecrypt, + publicEncrypt, + Cipheriv, + Decipheriv, + getCipherInfo, +}; diff --git a/ext/node/polyfills/internal/crypto/constants.ts b/ext/node/polyfills/internal/crypto/constants.ts new file mode 100644 index 00000000000000..d62415360118c3 --- /dev/null +++ b/ext/node/polyfills/internal/crypto/constants.ts @@ -0,0 +1,5 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. +// Copyright Joyent, Inc. and Node.js contributors. All rights reserved. MIT license. + +export const kHandle = Symbol("kHandle"); +export const kKeyObject = Symbol("kKeyObject"); diff --git a/ext/node/polyfills/internal/crypto/diffiehellman.ts b/ext/node/polyfills/internal/crypto/diffiehellman.ts new file mode 100644 index 00000000000000..eb903ccb3f39f1 --- /dev/null +++ b/ext/node/polyfills/internal/crypto/diffiehellman.ts @@ -0,0 +1,306 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. +// Copyright Joyent, Inc. and Node.js contributors. All rights reserved. MIT license. + +import { notImplemented } from "internal:deno_node/polyfills/_utils.ts"; +import { + isAnyArrayBuffer, + isArrayBufferView, +} from "internal:deno_node/polyfills/internal/util/types.ts"; +import { ERR_INVALID_ARG_TYPE } from "internal:deno_node/polyfills/internal/errors.ts"; +import { + validateInt32, + validateString, +} from "internal:deno_node/polyfills/internal/validators.mjs"; +import { Buffer } from "internal:deno_node/polyfills/buffer.ts"; +import { + getDefaultEncoding, + toBuf, +} from "internal:deno_node/polyfills/internal/crypto/util.ts"; +import type { + BinaryLike, + BinaryToTextEncoding, + ECDHKeyFormat, +} from "internal:deno_node/polyfills/internal/crypto/types.ts"; +import { KeyObject } from "internal:deno_node/polyfills/internal/crypto/keys.ts"; +import type { BufferEncoding } from "internal:deno_node/polyfills/_global.d.ts"; + +const DH_GENERATOR = 2; + +export class DiffieHellman { + verifyError!: number; + + constructor( + sizeOrKey: unknown, + keyEncoding?: unknown, + generator?: unknown, + genEncoding?: unknown, + ) { + if ( + typeof sizeOrKey !== "number" && + typeof sizeOrKey !== "string" && + !isArrayBufferView(sizeOrKey) && + !isAnyArrayBuffer(sizeOrKey) + ) { + throw new ERR_INVALID_ARG_TYPE( + "sizeOrKey", + ["number", "string", "ArrayBuffer", "Buffer", "TypedArray", "DataView"], + sizeOrKey, + ); + } + + if (typeof sizeOrKey === "number") { + validateInt32(sizeOrKey, "sizeOrKey"); + } + + if ( + keyEncoding && + !Buffer.isEncoding(keyEncoding as BinaryToTextEncoding) && + keyEncoding !== "buffer" + ) { + genEncoding = generator; + generator = keyEncoding; + keyEncoding = false; + } + + const encoding = getDefaultEncoding(); + keyEncoding = keyEncoding || encoding; + genEncoding = genEncoding || encoding; + + if (typeof sizeOrKey !== "number") { + sizeOrKey = toBuf(sizeOrKey as string, keyEncoding as string); + } + + if (!generator) { + generator = DH_GENERATOR; + } else if (typeof generator === "number") { + validateInt32(generator, "generator"); + } else if (typeof generator === "string") { + generator = toBuf(generator, genEncoding as string); + } else if (!isArrayBufferView(generator) && !isAnyArrayBuffer(generator)) { + throw new ERR_INVALID_ARG_TYPE( + "generator", + ["number", "string", "ArrayBuffer", "Buffer", "TypedArray", "DataView"], + generator, + ); + } + + notImplemented("crypto.DiffieHellman"); + } + + computeSecret(otherPublicKey: ArrayBufferView): Buffer; + computeSecret( + otherPublicKey: string, + inputEncoding: BinaryToTextEncoding, + ): Buffer; + computeSecret( + otherPublicKey: ArrayBufferView, + outputEncoding: BinaryToTextEncoding, + ): string; + computeSecret( + otherPublicKey: string, + inputEncoding: BinaryToTextEncoding, + outputEncoding: BinaryToTextEncoding, + ): string; + computeSecret( + _otherPublicKey: ArrayBufferView | string, + _inputEncoding?: BinaryToTextEncoding, + _outputEncoding?: BinaryToTextEncoding, + ): Buffer | string { + notImplemented("crypto.DiffieHellman.prototype.computeSecret"); + } + + generateKeys(): Buffer; + generateKeys(encoding: BinaryToTextEncoding): string; + generateKeys(_encoding?: BinaryToTextEncoding): Buffer | string { + notImplemented("crypto.DiffieHellman.prototype.generateKeys"); + } + + getGenerator(): Buffer; + getGenerator(encoding: BinaryToTextEncoding): string; + getGenerator(_encoding?: BinaryToTextEncoding): Buffer | string { + notImplemented("crypto.DiffieHellman.prototype.getGenerator"); + } + + getPrime(): Buffer; + getPrime(encoding: BinaryToTextEncoding): string; + getPrime(_encoding?: BinaryToTextEncoding): Buffer | string { + notImplemented("crypto.DiffieHellman.prototype.getPrime"); + } + + getPrivateKey(): Buffer; + getPrivateKey(encoding: BinaryToTextEncoding): string; + getPrivateKey(_encoding?: BinaryToTextEncoding): Buffer | string { + notImplemented("crypto.DiffieHellman.prototype.getPrivateKey"); + } + + getPublicKey(): Buffer; + getPublicKey(encoding: BinaryToTextEncoding): string; + getPublicKey(_encoding?: BinaryToTextEncoding): Buffer | string { + notImplemented("crypto.DiffieHellman.prototype.getPublicKey"); + } + + setPrivateKey(privateKey: ArrayBufferView): void; + setPrivateKey(privateKey: string, encoding: BufferEncoding): void; + setPrivateKey( + _privateKey: ArrayBufferView | string, + _encoding?: BufferEncoding, + ) { + notImplemented("crypto.DiffieHellman.prototype.setPrivateKey"); + } + + setPublicKey(publicKey: ArrayBufferView): void; + setPublicKey(publicKey: string, encoding: BufferEncoding): void; + setPublicKey( + _publicKey: ArrayBufferView | string, + _encoding?: BufferEncoding, + ) { + notImplemented("crypto.DiffieHellman.prototype.setPublicKey"); + } +} + +export class DiffieHellmanGroup { + verifyError!: number; + + constructor(_name: string) { + notImplemented("crypto.DiffieHellmanGroup"); + } + + computeSecret(otherPublicKey: ArrayBufferView): Buffer; + computeSecret( + otherPublicKey: string, + inputEncoding: BinaryToTextEncoding, + ): Buffer; + computeSecret( + otherPublicKey: ArrayBufferView, + outputEncoding: BinaryToTextEncoding, + ): string; + computeSecret( + otherPublicKey: string, + inputEncoding: BinaryToTextEncoding, + outputEncoding: BinaryToTextEncoding, + ): string; + computeSecret( + _otherPublicKey: ArrayBufferView | string, + _inputEncoding?: BinaryToTextEncoding, + _outputEncoding?: BinaryToTextEncoding, + ): Buffer | string { + notImplemented("crypto.DiffieHellman.prototype.computeSecret"); + } + + generateKeys(): Buffer; + generateKeys(encoding: BinaryToTextEncoding): string; + generateKeys(_encoding?: BinaryToTextEncoding): Buffer | string { + notImplemented("crypto.DiffieHellman.prototype.generateKeys"); + } + + getGenerator(): Buffer; + getGenerator(encoding: BinaryToTextEncoding): string; + getGenerator(_encoding?: BinaryToTextEncoding): Buffer | string { + notImplemented("crypto.DiffieHellman.prototype.getGenerator"); + } + + getPrime(): Buffer; + getPrime(encoding: BinaryToTextEncoding): string; + getPrime(_encoding?: BinaryToTextEncoding): Buffer | string { + notImplemented("crypto.DiffieHellman.prototype.getPrime"); + } + + getPrivateKey(): Buffer; + getPrivateKey(encoding: BinaryToTextEncoding): string; + getPrivateKey(_encoding?: BinaryToTextEncoding): Buffer | string { + notImplemented("crypto.DiffieHellman.prototype.getPrivateKey"); + } + + getPublicKey(): Buffer; + getPublicKey(encoding: BinaryToTextEncoding): string; + getPublicKey(_encoding?: BinaryToTextEncoding): Buffer | string { + notImplemented("crypto.DiffieHellman.prototype.getPublicKey"); + } +} + +export class ECDH { + constructor(curve: string) { + validateString(curve, "curve"); + + notImplemented("crypto.ECDH"); + } + + static convertKey( + _key: BinaryLike, + _curve: string, + _inputEncoding?: BinaryToTextEncoding, + _outputEncoding?: "latin1" | "hex" | "base64" | "base64url", + _format?: "uncompressed" | "compressed" | "hybrid", + ): Buffer | string { + notImplemented("crypto.ECDH.prototype.convertKey"); + } + + computeSecret(otherPublicKey: ArrayBufferView): Buffer; + computeSecret( + otherPublicKey: string, + inputEncoding: BinaryToTextEncoding, + ): Buffer; + computeSecret( + otherPublicKey: ArrayBufferView, + outputEncoding: BinaryToTextEncoding, + ): string; + computeSecret( + otherPublicKey: string, + inputEncoding: BinaryToTextEncoding, + outputEncoding: BinaryToTextEncoding, + ): string; + computeSecret( + _otherPublicKey: ArrayBufferView | string, + _inputEncoding?: BinaryToTextEncoding, + _outputEncoding?: BinaryToTextEncoding, + ): Buffer | string { + notImplemented("crypto.ECDH.prototype.computeSecret"); + } + + generateKeys(): Buffer; + generateKeys(encoding: BinaryToTextEncoding, format?: ECDHKeyFormat): string; + generateKeys( + _encoding?: BinaryToTextEncoding, + _format?: ECDHKeyFormat, + ): Buffer | string { + notImplemented("crypto.ECDH.prototype.generateKeys"); + } + + getPrivateKey(): Buffer; + getPrivateKey(encoding: BinaryToTextEncoding): string; + getPrivateKey(_encoding?: BinaryToTextEncoding): Buffer | string { + notImplemented("crypto.ECDH.prototype.getPrivateKey"); + } + + getPublicKey(): Buffer; + getPublicKey(encoding: BinaryToTextEncoding, format?: ECDHKeyFormat): string; + getPublicKey( + _encoding?: BinaryToTextEncoding, + _format?: ECDHKeyFormat, + ): Buffer | string { + notImplemented("crypto.ECDH.prototype.getPublicKey"); + } + + setPrivateKey(privateKey: ArrayBufferView): void; + setPrivateKey(privateKey: string, encoding: BinaryToTextEncoding): void; + setPrivateKey( + _privateKey: ArrayBufferView | string, + _encoding?: BinaryToTextEncoding, + ): Buffer | string { + notImplemented("crypto.ECDH.prototype.setPrivateKey"); + } +} + +export function diffieHellman(_options: { + privateKey: KeyObject; + publicKey: KeyObject; +}): Buffer { + notImplemented("crypto.diffieHellman"); +} + +export default { + DiffieHellman, + DiffieHellmanGroup, + ECDH, + diffieHellman, +}; diff --git a/ext/node/polyfills/internal/crypto/hash.ts b/ext/node/polyfills/internal/crypto/hash.ts new file mode 100644 index 00000000000000..e6e2409a23d908 --- /dev/null +++ b/ext/node/polyfills/internal/crypto/hash.ts @@ -0,0 +1,230 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. +// Copyright Joyent, Inc. and Node.js contributors. All rights reserved. MIT license. + +import { + TextDecoder, + TextEncoder, +} from "internal:deno_web/08_text_encoding.js"; +import { Buffer } from "internal:deno_node/polyfills/buffer.ts"; +import { Transform } from "internal:deno_node/polyfills/stream.ts"; +import { encode as encodeToHex } from "internal:deno_node/polyfills/internal/crypto/_hex.ts"; +import { + forgivingBase64Encode as encodeToBase64, + forgivingBase64UrlEncode as encodeToBase64Url, +} from "internal:deno_web/00_infra.js"; +import type { TransformOptions } from "internal:deno_node/polyfills/_stream.d.ts"; +import { validateString } from "internal:deno_node/polyfills/internal/validators.mjs"; +import type { + BinaryToTextEncoding, + Encoding, +} from "internal:deno_node/polyfills/internal/crypto/types.ts"; +import { + KeyObject, + prepareSecretKey, +} from "internal:deno_node/polyfills/internal/crypto/keys.ts"; +import { notImplemented } from "internal:deno_node/polyfills/_utils.ts"; + +const { ops } = globalThis.__bootstrap.core; + +const coerceToBytes = (data: string | BufferSource): Uint8Array => { + if (data instanceof Uint8Array) { + return data; + } else if (typeof data === "string") { + // This assumes UTF-8, which may not be correct. + return new TextEncoder().encode(data); + } else if (ArrayBuffer.isView(data)) { + return new Uint8Array(data.buffer, data.byteOffset, data.byteLength); + } else if (data instanceof ArrayBuffer) { + return new Uint8Array(data); + } else { + throw new TypeError("expected data to be string | BufferSource"); + } +}; + +/** + * The Hash class is a utility for creating hash digests of data. It can be used in one of two ways: + * + * - As a stream that is both readable and writable, where data is written to produce a computed hash digest on the readable side, or + * - Using the hash.update() and hash.digest() methods to produce the computed hash. + * + * The crypto.createHash() method is used to create Hash instances. Hash objects are not to be created directly using the new keyword. + */ +export class Hash extends Transform { + #context: number; + + constructor( + algorithm: string | number, + _opts?: TransformOptions, + ) { + super({ + transform(chunk: string, _encoding: string, callback: () => void) { + ops.op_node_hash_update(context, coerceToBytes(chunk)); + callback(); + }, + flush(callback: () => void) { + this.push(context.digest(undefined)); + callback(); + }, + }); + + if (typeof algorithm === "string") { + this.#context = ops.op_node_create_hash( + algorithm, + ); + } else { + this.#context = algorithm; + } + + const context = this.#context; + } + + copy(): Hash { + return new Hash(ops.op_node_clone_hash(this.#context)); + } + + /** + * Updates the hash content with the given data. + */ + update(data: string | ArrayBuffer, _encoding?: string): this { + let bytes; + if (typeof data === "string") { + data = new TextEncoder().encode(data); + bytes = coerceToBytes(data); + } else { + bytes = coerceToBytes(data); + } + + ops.op_node_hash_update(this.#context, bytes); + + return this; + } + + /** + * Calculates the digest of all of the data. + * + * If encoding is provided a string will be returned; otherwise a Buffer is returned. + * + * Supported encodings are currently 'hex', 'binary', 'base64', 'base64url'. + */ + digest(encoding?: string): Buffer | string { + const digest = ops.op_node_hash_digest(this.#context); + if (encoding === undefined) { + return Buffer.from(digest); + } + + switch (encoding) { + case "hex": + return new TextDecoder().decode(encodeToHex(new Uint8Array(digest))); + case "binary": + return String.fromCharCode(...digest); + case "base64": + return encodeToBase64(digest); + case "base64url": + return encodeToBase64Url(digest); + case "buffer": + return Buffer.from(digest); + default: + return Buffer.from(digest).toString(encoding); + } + } +} + +export function Hmac( + hmac: string, + key: string | ArrayBuffer | KeyObject, + options?: TransformOptions, +): Hmac { + return new HmacImpl(hmac, key, options); +} + +type Hmac = HmacImpl; + +class HmacImpl extends Transform { + #ipad: Uint8Array; + #opad: Uint8Array; + #ZEROES = Buffer.alloc(128); + #algorithm: string; + #hash: Hash; + + constructor( + hmac: string, + key: string | ArrayBuffer | KeyObject, + options?: TransformOptions, + ) { + super({ + transform(chunk: string, encoding: string, callback: () => void) { + // deno-lint-ignore no-explicit-any + self.update(coerceToBytes(chunk), encoding as any); + callback(); + }, + flush(callback: () => void) { + this.push(self.digest()); + callback(); + }, + }); + // deno-lint-ignore no-this-alias + const self = this; + if (key instanceof KeyObject) { + notImplemented("Hmac: KeyObject key is not implemented"); + } + + validateString(hmac, "hmac"); + const u8Key = prepareSecretKey(key, options?.encoding) as Buffer; + + const alg = hmac.toLowerCase(); + this.#hash = new Hash(alg, options); + this.#algorithm = alg; + const blockSize = (alg === "sha512" || alg === "sha384") ? 128 : 64; + const keySize = u8Key.length; + + let bufKey: Buffer; + + if (keySize > blockSize) { + bufKey = this.#hash.update(u8Key).digest() as Buffer; + } else { + bufKey = Buffer.concat([u8Key, this.#ZEROES], blockSize); + } + + this.#ipad = Buffer.allocUnsafe(blockSize); + this.#opad = Buffer.allocUnsafe(blockSize); + + for (let i = 0; i < blockSize; i++) { + this.#ipad[i] = bufKey[i] ^ 0x36; + this.#opad[i] = bufKey[i] ^ 0x5C; + } + + this.#hash = new Hash(alg); + this.#hash.update(this.#ipad); + } + + digest(): Buffer; + digest(encoding: BinaryToTextEncoding): string; + digest(encoding?: BinaryToTextEncoding): Buffer | string { + const result = this.#hash.digest(); + + return new Hash(this.#algorithm).update(this.#opad).update(result).digest( + encoding, + ); + } + + update(data: string | ArrayBuffer, inputEncoding?: Encoding): this { + this.#hash.update(data, inputEncoding); + return this; + } +} + +Hmac.prototype = HmacImpl.prototype; + +/** + * Creates and returns a Hash object that can be used to generate hash digests + * using the given `algorithm`. Optional `options` argument controls stream behavior. + */ +export function createHash(algorithm: string, opts?: TransformOptions) { + return new Hash(algorithm, opts); +} + +export default { + Hash, + Hmac, + createHash, +}; diff --git a/ext/node/polyfills/internal/crypto/hkdf.ts b/ext/node/polyfills/internal/crypto/hkdf.ts new file mode 100644 index 00000000000000..aebdd9152f2589 --- /dev/null +++ b/ext/node/polyfills/internal/crypto/hkdf.ts @@ -0,0 +1,130 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. +// Copyright Joyent, Inc. and Node.js contributors. All rights reserved. MIT license. + +import { + validateFunction, + validateInteger, + validateString, +} from "internal:deno_node/polyfills/internal/validators.mjs"; +import { + ERR_INVALID_ARG_TYPE, + ERR_OUT_OF_RANGE, + hideStackFrames, +} from "internal:deno_node/polyfills/internal/errors.ts"; +import { + toBuf, + validateByteSource, +} from "internal:deno_node/polyfills/internal/crypto/util.ts"; +import { + createSecretKey, + isKeyObject, + KeyObject, +} from "internal:deno_node/polyfills/internal/crypto/keys.ts"; +import type { BinaryLike } from "internal:deno_node/polyfills/internal/crypto/types.ts"; +import { kMaxLength } from "internal:deno_node/polyfills/internal/buffer.mjs"; +import { + isAnyArrayBuffer, + isArrayBufferView, +} from "internal:deno_node/polyfills/internal/util/types.ts"; +import { notImplemented } from "internal:deno_node/polyfills/_utils.ts"; + +const validateParameters = hideStackFrames((hash, key, salt, info, length) => { + key = prepareKey(key); + salt = toBuf(salt); + info = toBuf(info); + + validateString(hash, "digest"); + validateByteSource(salt, "salt"); + validateByteSource(info, "info"); + + validateInteger(length, "length", 0, kMaxLength); + + if (info.byteLength > 1024) { + throw new ERR_OUT_OF_RANGE( + "info", + "must not contain more than 1024 bytes", + info.byteLength, + ); + } + + return { + hash, + key, + salt, + info, + length, + }; +}); + +function prepareKey(key: BinaryLike | KeyObject) { + if (isKeyObject(key)) { + return key; + } + + if (isAnyArrayBuffer(key)) { + return createSecretKey(new Uint8Array(key as unknown as ArrayBufferLike)); + } + + key = toBuf(key as string); + + if (!isArrayBufferView(key)) { + throw new ERR_INVALID_ARG_TYPE( + "ikm", + [ + "string", + "SecretKeyObject", + "ArrayBuffer", + "TypedArray", + "DataView", + "Buffer", + ], + key, + ); + } + + return createSecretKey(key); +} + +export function hkdf( + hash: string, + key: BinaryLike | KeyObject, + salt: BinaryLike, + info: BinaryLike, + length: number, + callback: (err: Error | null, derivedKey: ArrayBuffer) => void, +) { + ({ hash, key, salt, info, length } = validateParameters( + hash, + key, + salt, + info, + length, + )); + + validateFunction(callback, "callback"); + + notImplemented("crypto.hkdf"); +} + +export function hkdfSync( + hash: string, + key: BinaryLike | KeyObject, + salt: BinaryLike, + info: BinaryLike, + length: number, +) { + ({ hash, key, salt, info, length } = validateParameters( + hash, + key, + salt, + info, + length, + )); + + notImplemented("crypto.hkdfSync"); +} + +export default { + hkdf, + hkdfSync, +}; diff --git a/ext/node/polyfills/internal/crypto/keygen.ts b/ext/node/polyfills/internal/crypto/keygen.ts new file mode 100644 index 00000000000000..1a947b95badcf7 --- /dev/null +++ b/ext/node/polyfills/internal/crypto/keygen.ts @@ -0,0 +1,682 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. +// Copyright Joyent, Inc. and Node.js contributors. All rights reserved. MIT license. + +import { KeyObject } from "internal:deno_node/polyfills/internal/crypto/keys.ts"; +import { notImplemented } from "internal:deno_node/polyfills/_utils.ts"; +import { Buffer } from "internal:deno_node/polyfills/buffer.ts"; +import { + KeyFormat, + KeyType, +} from "internal:deno_node/polyfills/internal/crypto/types.ts"; + +export function generateKey( + _type: "hmac" | "aes", + _options: { + length: number; + }, + _callback: (err: Error | null, key: KeyObject) => void, +) { + notImplemented("crypto.generateKey"); +} + +export interface BasePrivateKeyEncodingOptions { + format: T; + cipher?: string | undefined; + passphrase?: string | undefined; +} + +export interface RSAKeyPairOptions< + PubF extends KeyFormat, + PrivF extends KeyFormat, +> { + /** + * Key size in bits + */ + modulusLength: number; + /** + * Public exponent + * @default 0x10001 + */ + publicExponent?: number | undefined; + publicKeyEncoding: { + type: "pkcs1" | "spki"; + format: PubF; + }; + privateKeyEncoding: BasePrivateKeyEncodingOptions & { + type: "pkcs1" | "pkcs8"; + }; +} + +export interface RSAPSSKeyPairOptions< + PubF extends KeyFormat, + PrivF extends KeyFormat, +> { + /** + * Key size in bits + */ + modulusLength: number; + /** + * Public exponent + * @default 0x10001 + */ + publicExponent?: number | undefined; + /** + * Name of the message digest + */ + hashAlgorithm?: string; + /** + * Name of the message digest used by MGF1 + */ + mgf1HashAlgorithm?: string; + /** + * Minimal salt length in bytes + */ + saltLength?: string; + publicKeyEncoding: { + type: "spki"; + format: PubF; + }; + privateKeyEncoding: BasePrivateKeyEncodingOptions & { + type: "pkcs8"; + }; +} + +export interface DSAKeyPairOptions< + PubF extends KeyFormat, + PrivF extends KeyFormat, +> { + /** + * Key size in bits + */ + modulusLength: number; + /** + * Size of q in bits + */ + divisorLength: number; + publicKeyEncoding: { + type: "spki"; + format: PubF; + }; + privateKeyEncoding: BasePrivateKeyEncodingOptions & { + type: "pkcs8"; + }; +} + +export interface ECKeyPairOptions< + PubF extends KeyFormat, + PrivF extends KeyFormat, +> { + /** + * Name of the curve to use. + */ + namedCurve: string; + publicKeyEncoding: { + type: "pkcs1" | "spki"; + format: PubF; + }; + privateKeyEncoding: BasePrivateKeyEncodingOptions & { + type: "sec1" | "pkcs8"; + }; +} + +export interface ED25519KeyPairOptions< + PubF extends KeyFormat, + PrivF extends KeyFormat, +> { + publicKeyEncoding: { + type: "spki"; + format: PubF; + }; + privateKeyEncoding: BasePrivateKeyEncodingOptions & { + type: "pkcs8"; + }; +} + +export interface ED448KeyPairOptions< + PubF extends KeyFormat, + PrivF extends KeyFormat, +> { + publicKeyEncoding: { + type: "spki"; + format: PubF; + }; + privateKeyEncoding: BasePrivateKeyEncodingOptions & { + type: "pkcs8"; + }; +} + +export interface X25519KeyPairOptions< + PubF extends KeyFormat, + PrivF extends KeyFormat, +> { + publicKeyEncoding: { + type: "spki"; + format: PubF; + }; + privateKeyEncoding: BasePrivateKeyEncodingOptions & { + type: "pkcs8"; + }; +} + +export interface X448KeyPairOptions< + PubF extends KeyFormat, + PrivF extends KeyFormat, +> { + publicKeyEncoding: { + type: "spki"; + format: PubF; + }; + privateKeyEncoding: BasePrivateKeyEncodingOptions & { + type: "pkcs8"; + }; +} + +export interface RSAKeyPairKeyObjectOptions { + /** + * Key size in bits + */ + modulusLength: number; + /** + * Public exponent + * @default 0x10001 + */ + publicExponent?: number | undefined; +} + +export interface RSAPSSKeyPairKeyObjectOptions { + /** + * Key size in bits + */ + modulusLength: number; + /** + * Public exponent + * @default 0x10001 + */ + publicExponent?: number | undefined; + /** + * Name of the message digest + */ + hashAlgorithm?: string; + /** + * Name of the message digest used by MGF1 + */ + mgf1HashAlgorithm?: string; + /** + * Minimal salt length in bytes + */ + saltLength?: string; +} + +export interface DSAKeyPairKeyObjectOptions { + /** + * Key size in bits + */ + modulusLength: number; + /** + * Size of q in bits + */ + divisorLength: number; +} + +// deno-lint-ignore no-empty-interface +export interface ED25519KeyPairKeyObjectOptions {} + +// deno-lint-ignore no-empty-interface +export interface ED448KeyPairKeyObjectOptions {} + +// deno-lint-ignore no-empty-interface +export interface X25519KeyPairKeyObjectOptions {} + +// deno-lint-ignore no-empty-interface +export interface X448KeyPairKeyObjectOptions {} + +export interface ECKeyPairKeyObjectOptions { + /** + * Name of the curve to use + */ + namedCurve: string; +} + +export function generateKeyPair( + type: "rsa", + options: RSAKeyPairOptions<"pem", "pem">, + callback: (err: Error | null, publicKey: string, privateKey: string) => void, +): void; +export function generateKeyPair( + type: "rsa", + options: RSAKeyPairOptions<"pem", "der">, + callback: (err: Error | null, publicKey: string, privateKey: Buffer) => void, +): void; +export function generateKeyPair( + type: "rsa", + options: RSAKeyPairOptions<"der", "pem">, + callback: (err: Error | null, publicKey: Buffer, privateKey: string) => void, +): void; +export function generateKeyPair( + type: "rsa", + options: RSAKeyPairOptions<"der", "der">, + callback: (err: Error | null, publicKey: Buffer, privateKey: Buffer) => void, +): void; +export function generateKeyPair( + type: "rsa", + options: RSAKeyPairKeyObjectOptions, + callback: ( + err: Error | null, + publicKey: KeyObject, + privateKey: KeyObject, + ) => void, +): void; +export function generateKeyPair( + type: "rsa-pss", + options: RSAPSSKeyPairOptions<"pem", "pem">, + callback: (err: Error | null, publicKey: string, privateKey: string) => void, +): void; +export function generateKeyPair( + type: "rsa-pss", + options: RSAPSSKeyPairOptions<"pem", "der">, + callback: (err: Error | null, publicKey: string, privateKey: Buffer) => void, +): void; +export function generateKeyPair( + type: "rsa-pss", + options: RSAPSSKeyPairOptions<"der", "pem">, + callback: (err: Error | null, publicKey: Buffer, privateKey: string) => void, +): void; +export function generateKeyPair( + type: "rsa-pss", + options: RSAPSSKeyPairOptions<"der", "der">, + callback: (err: Error | null, publicKey: Buffer, privateKey: Buffer) => void, +): void; +export function generateKeyPair( + type: "rsa-pss", + options: RSAPSSKeyPairKeyObjectOptions, + callback: ( + err: Error | null, + publicKey: KeyObject, + privateKey: KeyObject, + ) => void, +): void; +export function generateKeyPair( + type: "dsa", + options: DSAKeyPairOptions<"pem", "pem">, + callback: (err: Error | null, publicKey: string, privateKey: string) => void, +): void; +export function generateKeyPair( + type: "dsa", + options: DSAKeyPairOptions<"pem", "der">, + callback: (err: Error | null, publicKey: string, privateKey: Buffer) => void, +): void; +export function generateKeyPair( + type: "dsa", + options: DSAKeyPairOptions<"der", "pem">, + callback: (err: Error | null, publicKey: Buffer, privateKey: string) => void, +): void; +export function generateKeyPair( + type: "dsa", + options: DSAKeyPairOptions<"der", "der">, + callback: (err: Error | null, publicKey: Buffer, privateKey: Buffer) => void, +): void; +export function generateKeyPair( + type: "dsa", + options: DSAKeyPairKeyObjectOptions, + callback: ( + err: Error | null, + publicKey: KeyObject, + privateKey: KeyObject, + ) => void, +): void; +export function generateKeyPair( + type: "ec", + options: ECKeyPairOptions<"pem", "pem">, + callback: (err: Error | null, publicKey: string, privateKey: string) => void, +): void; +export function generateKeyPair( + type: "ec", + options: ECKeyPairOptions<"pem", "der">, + callback: (err: Error | null, publicKey: string, privateKey: Buffer) => void, +): void; +export function generateKeyPair( + type: "ec", + options: ECKeyPairOptions<"der", "pem">, + callback: (err: Error | null, publicKey: Buffer, privateKey: string) => void, +): void; +export function generateKeyPair( + type: "ec", + options: ECKeyPairOptions<"der", "der">, + callback: (err: Error | null, publicKey: Buffer, privateKey: Buffer) => void, +): void; +export function generateKeyPair( + type: "ec", + options: ECKeyPairKeyObjectOptions, + callback: ( + err: Error | null, + publicKey: KeyObject, + privateKey: KeyObject, + ) => void, +): void; +export function generateKeyPair( + type: "ed25519", + options: ED25519KeyPairOptions<"pem", "pem">, + callback: (err: Error | null, publicKey: string, privateKey: string) => void, +): void; +export function generateKeyPair( + type: "ed25519", + options: ED25519KeyPairOptions<"pem", "der">, + callback: (err: Error | null, publicKey: string, privateKey: Buffer) => void, +): void; +export function generateKeyPair( + type: "ed25519", + options: ED25519KeyPairOptions<"der", "pem">, + callback: (err: Error | null, publicKey: Buffer, privateKey: string) => void, +): void; +export function generateKeyPair( + type: "ed25519", + options: ED25519KeyPairOptions<"der", "der">, + callback: (err: Error | null, publicKey: Buffer, privateKey: Buffer) => void, +): void; +export function generateKeyPair( + type: "ed25519", + options: ED25519KeyPairKeyObjectOptions | undefined, + callback: ( + err: Error | null, + publicKey: KeyObject, + privateKey: KeyObject, + ) => void, +): void; +export function generateKeyPair( + type: "ed448", + options: ED448KeyPairOptions<"pem", "pem">, + callback: (err: Error | null, publicKey: string, privateKey: string) => void, +): void; +export function generateKeyPair( + type: "ed448", + options: ED448KeyPairOptions<"pem", "der">, + callback: (err: Error | null, publicKey: string, privateKey: Buffer) => void, +): void; +export function generateKeyPair( + type: "ed448", + options: ED448KeyPairOptions<"der", "pem">, + callback: (err: Error | null, publicKey: Buffer, privateKey: string) => void, +): void; +export function generateKeyPair( + type: "ed448", + options: ED448KeyPairOptions<"der", "der">, + callback: (err: Error | null, publicKey: Buffer, privateKey: Buffer) => void, +): void; +export function generateKeyPair( + type: "ed448", + options: ED448KeyPairKeyObjectOptions | undefined, + callback: ( + err: Error | null, + publicKey: KeyObject, + privateKey: KeyObject, + ) => void, +): void; +export function generateKeyPair( + type: "x25519", + options: X25519KeyPairOptions<"pem", "pem">, + callback: (err: Error | null, publicKey: string, privateKey: string) => void, +): void; +export function generateKeyPair( + type: "x25519", + options: X25519KeyPairOptions<"pem", "der">, + callback: (err: Error | null, publicKey: string, privateKey: Buffer) => void, +): void; +export function generateKeyPair( + type: "x25519", + options: X25519KeyPairOptions<"der", "pem">, + callback: (err: Error | null, publicKey: Buffer, privateKey: string) => void, +): void; +export function generateKeyPair( + type: "x25519", + options: X25519KeyPairOptions<"der", "der">, + callback: (err: Error | null, publicKey: Buffer, privateKey: Buffer) => void, +): void; +export function generateKeyPair( + type: "x25519", + options: X25519KeyPairKeyObjectOptions | undefined, + callback: ( + err: Error | null, + publicKey: KeyObject, + privateKey: KeyObject, + ) => void, +): void; +export function generateKeyPair( + type: "x448", + options: X448KeyPairOptions<"pem", "pem">, + callback: (err: Error | null, publicKey: string, privateKey: string) => void, +): void; +export function generateKeyPair( + type: "x448", + options: X448KeyPairOptions<"pem", "der">, + callback: (err: Error | null, publicKey: string, privateKey: Buffer) => void, +): void; +export function generateKeyPair( + type: "x448", + options: X448KeyPairOptions<"der", "pem">, + callback: (err: Error | null, publicKey: Buffer, privateKey: string) => void, +): void; +export function generateKeyPair( + type: "x448", + options: X448KeyPairOptions<"der", "der">, + callback: (err: Error | null, publicKey: Buffer, privateKey: Buffer) => void, +): void; +export function generateKeyPair( + type: "x448", + options: X448KeyPairKeyObjectOptions | undefined, + callback: ( + err: Error | null, + publicKey: KeyObject, + privateKey: KeyObject, + ) => void, +): void; +export function generateKeyPair( + _type: KeyType, + _options: unknown, + _callback: ( + err: Error | null, + // deno-lint-ignore no-explicit-any + publicKey: any, + // deno-lint-ignore no-explicit-any + privateKey: any, + ) => void, +) { + notImplemented("crypto.generateKeyPair"); +} + +export interface KeyPairKeyObjectResult { + publicKey: KeyObject; + privateKey: KeyObject; +} + +export interface KeyPairSyncResult< + T1 extends string | Buffer, + T2 extends string | Buffer, +> { + publicKey: T1; + privateKey: T2; +} + +export function generateKeyPairSync( + type: "rsa", + options: RSAKeyPairOptions<"pem", "pem">, +): KeyPairSyncResult; +export function generateKeyPairSync( + type: "rsa", + options: RSAKeyPairOptions<"pem", "der">, +): KeyPairSyncResult; +export function generateKeyPairSync( + type: "rsa", + options: RSAKeyPairOptions<"der", "pem">, +): KeyPairSyncResult; +export function generateKeyPairSync( + type: "rsa", + options: RSAKeyPairOptions<"der", "der">, +): KeyPairSyncResult; +export function generateKeyPairSync( + type: "rsa", + options: RSAKeyPairKeyObjectOptions, +): KeyPairKeyObjectResult; +export function generateKeyPairSync( + type: "rsa-pss", + options: RSAPSSKeyPairOptions<"pem", "pem">, +): KeyPairSyncResult; +export function generateKeyPairSync( + type: "rsa-pss", + options: RSAPSSKeyPairOptions<"pem", "der">, +): KeyPairSyncResult; +export function generateKeyPairSync( + type: "rsa-pss", + options: RSAPSSKeyPairOptions<"der", "pem">, +): KeyPairSyncResult; +export function generateKeyPairSync( + type: "rsa-pss", + options: RSAPSSKeyPairOptions<"der", "der">, +): KeyPairSyncResult; +export function generateKeyPairSync( + type: "rsa-pss", + options: RSAPSSKeyPairKeyObjectOptions, +): KeyPairKeyObjectResult; +export function generateKeyPairSync( + type: "dsa", + options: DSAKeyPairOptions<"pem", "pem">, +): KeyPairSyncResult; +export function generateKeyPairSync( + type: "dsa", + options: DSAKeyPairOptions<"pem", "der">, +): KeyPairSyncResult; +export function generateKeyPairSync( + type: "dsa", + options: DSAKeyPairOptions<"der", "pem">, +): KeyPairSyncResult; +export function generateKeyPairSync( + type: "dsa", + options: DSAKeyPairOptions<"der", "der">, +): KeyPairSyncResult; +export function generateKeyPairSync( + type: "dsa", + options: DSAKeyPairKeyObjectOptions, +): KeyPairKeyObjectResult; +export function generateKeyPairSync( + type: "ec", + options: ECKeyPairOptions<"pem", "pem">, +): KeyPairSyncResult; +export function generateKeyPairSync( + type: "ec", + options: ECKeyPairOptions<"pem", "der">, +): KeyPairSyncResult; +export function generateKeyPairSync( + type: "ec", + options: ECKeyPairOptions<"der", "pem">, +): KeyPairSyncResult; +export function generateKeyPairSync( + type: "ec", + options: ECKeyPairOptions<"der", "der">, +): KeyPairSyncResult; +export function generateKeyPairSync( + type: "ec", + options: ECKeyPairKeyObjectOptions, +): KeyPairKeyObjectResult; +export function generateKeyPairSync( + type: "ed25519", + options: ED25519KeyPairOptions<"pem", "pem">, +): KeyPairSyncResult; +export function generateKeyPairSync( + type: "ed25519", + options: ED25519KeyPairOptions<"pem", "der">, +): KeyPairSyncResult; +export function generateKeyPairSync( + type: "ed25519", + options: ED25519KeyPairOptions<"der", "pem">, +): KeyPairSyncResult; +export function generateKeyPairSync( + type: "ed25519", + options: ED25519KeyPairOptions<"der", "der">, +): KeyPairSyncResult; +export function generateKeyPairSync( + type: "ed25519", + options?: ED25519KeyPairKeyObjectOptions, +): KeyPairKeyObjectResult; +export function generateKeyPairSync( + type: "ed448", + options: ED448KeyPairOptions<"pem", "pem">, +): KeyPairSyncResult; +export function generateKeyPairSync( + type: "ed448", + options: ED448KeyPairOptions<"pem", "der">, +): KeyPairSyncResult; +export function generateKeyPairSync( + type: "ed448", + options: ED448KeyPairOptions<"der", "pem">, +): KeyPairSyncResult; +export function generateKeyPairSync( + type: "ed448", + options: ED448KeyPairOptions<"der", "der">, +): KeyPairSyncResult; +export function generateKeyPairSync( + type: "ed448", + options?: ED448KeyPairKeyObjectOptions, +): KeyPairKeyObjectResult; +export function generateKeyPairSync( + type: "x25519", + options: X25519KeyPairOptions<"pem", "pem">, +): KeyPairSyncResult; +export function generateKeyPairSync( + type: "x25519", + options: X25519KeyPairOptions<"pem", "der">, +): KeyPairSyncResult; +export function generateKeyPairSync( + type: "x25519", + options: X25519KeyPairOptions<"der", "pem">, +): KeyPairSyncResult; +export function generateKeyPairSync( + type: "x25519", + options: X25519KeyPairOptions<"der", "der">, +): KeyPairSyncResult; +export function generateKeyPairSync( + type: "x25519", + options?: X25519KeyPairKeyObjectOptions, +): KeyPairKeyObjectResult; +export function generateKeyPairSync( + type: "x448", + options: X448KeyPairOptions<"pem", "pem">, +): KeyPairSyncResult; +export function generateKeyPairSync( + type: "x448", + options: X448KeyPairOptions<"pem", "der">, +): KeyPairSyncResult; +export function generateKeyPairSync( + type: "x448", + options: X448KeyPairOptions<"der", "pem">, +): KeyPairSyncResult; +export function generateKeyPairSync( + type: "x448", + options: X448KeyPairOptions<"der", "der">, +): KeyPairSyncResult; +export function generateKeyPairSync( + type: "x448", + options?: X448KeyPairKeyObjectOptions, +): KeyPairKeyObjectResult; +export function generateKeyPairSync( + _type: KeyType, + _options: unknown, +): + | KeyPairKeyObjectResult + | KeyPairSyncResult { + notImplemented("crypto.generateKeyPairSync"); +} + +export function generateKeySync( + _type: "hmac" | "aes", + _options: { + length: number; + }, +): KeyObject { + notImplemented("crypto.generateKeySync"); +} + +export default { + generateKey, + generateKeySync, + generateKeyPair, + generateKeyPairSync, +}; diff --git a/ext/node/polyfills/internal/crypto/keys.ts b/ext/node/polyfills/internal/crypto/keys.ts new file mode 100644 index 00000000000000..7c9e7bad9506bc --- /dev/null +++ b/ext/node/polyfills/internal/crypto/keys.ts @@ -0,0 +1,294 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. +// Copyright Joyent, Inc. and Node.js contributors. All rights reserved. MIT license. + +import { + kHandle, + kKeyObject, +} from "internal:deno_node/polyfills/internal/crypto/constants.ts"; +import { + ERR_CRYPTO_INVALID_KEY_OBJECT_TYPE, + ERR_INVALID_ARG_TYPE, + ERR_INVALID_ARG_VALUE, +} from "internal:deno_node/polyfills/internal/errors.ts"; +import { notImplemented } from "internal:deno_node/polyfills/_utils.ts"; +import type { + KeyFormat, + KeyType, + PrivateKeyInput, + PublicKeyInput, +} from "internal:deno_node/polyfills/internal/crypto/types.ts"; +import { Buffer } from "internal:deno_node/polyfills/buffer.ts"; +import { + isAnyArrayBuffer, + isArrayBufferView, +} from "internal:deno_node/polyfills/internal/util/types.ts"; +import { hideStackFrames } from "internal:deno_node/polyfills/internal/errors.ts"; +import { + isCryptoKey as isCryptoKey_, + isKeyObject as isKeyObject_, + kKeyType, +} from "internal:deno_node/polyfills/internal/crypto/_keys.ts"; + +const getArrayBufferOrView = hideStackFrames( + ( + buffer, + name, + encoding, + ): + | ArrayBuffer + | SharedArrayBuffer + | Buffer + | DataView + | BigInt64Array + | BigUint64Array + | Float32Array + | Float64Array + | Int8Array + | Int16Array + | Int32Array + | Uint8Array + | Uint8ClampedArray + | Uint16Array + | Uint32Array => { + if (isAnyArrayBuffer(buffer)) { + return buffer; + } + if (typeof buffer === "string") { + if (encoding === "buffer") { + encoding = "utf8"; + } + return Buffer.from(buffer, encoding); + } + if (!isArrayBufferView(buffer)) { + throw new ERR_INVALID_ARG_TYPE( + name, + [ + "string", + "ArrayBuffer", + "Buffer", + "TypedArray", + "DataView", + ], + buffer, + ); + } + return buffer; + }, +); + +export interface AsymmetricKeyDetails { + /** + * Key size in bits (RSA, DSA). + */ + modulusLength?: number | undefined; + /** + * Public exponent (RSA). + */ + publicExponent?: bigint | undefined; + /** + * Name of the message digest (RSA-PSS). + */ + hashAlgorithm?: string | undefined; + /** + * Name of the message digest used by MGF1 (RSA-PSS). + */ + mgf1HashAlgorithm?: string | undefined; + /** + * Minimal salt length in bytes (RSA-PSS). + */ + saltLength?: number | undefined; + /** + * Size of q in bits (DSA). + */ + divisorLength?: number | undefined; + /** + * Name of the curve (EC). + */ + namedCurve?: string | undefined; +} + +export type KeyObjectType = "secret" | "public" | "private"; + +export interface KeyExportOptions { + type: "pkcs1" | "spki" | "pkcs8" | "sec1"; + format: T; + cipher?: string | undefined; + passphrase?: string | Buffer | undefined; +} + +export interface JwkKeyExportOptions { + format: "jwk"; +} + +export function isKeyObject(obj: unknown): obj is KeyObject { + return isKeyObject_(obj); +} + +export function isCryptoKey( + obj: unknown, +): obj is { type: string; [kKeyObject]: KeyObject } { + return isCryptoKey_(obj); +} + +export class KeyObject { + [kKeyType]: KeyObjectType; + [kHandle]: unknown; + + constructor(type: KeyObjectType, handle: unknown) { + if (type !== "secret" && type !== "public" && type !== "private") { + throw new ERR_INVALID_ARG_VALUE("type", type); + } + + if (typeof handle !== "object") { + throw new ERR_INVALID_ARG_TYPE("handle", "object", handle); + } + + this[kKeyType] = type; + + Object.defineProperty(this, kHandle, { + value: handle, + enumerable: false, + configurable: false, + writable: false, + }); + } + + get type(): KeyObjectType { + return this[kKeyType]; + } + + get asymmetricKeyDetails(): AsymmetricKeyDetails | undefined { + notImplemented("crypto.KeyObject.prototype.asymmetricKeyDetails"); + + return undefined; + } + + get asymmetricKeyType(): KeyType | undefined { + notImplemented("crypto.KeyObject.prototype.asymmetricKeyType"); + + return undefined; + } + + get symmetricKeySize(): number | undefined { + notImplemented("crypto.KeyObject.prototype.symmetricKeySize"); + + return undefined; + } + + static from(key: CryptoKey): KeyObject { + if (!isCryptoKey(key)) { + throw new ERR_INVALID_ARG_TYPE("key", "CryptoKey", key); + } + + notImplemented("crypto.KeyObject.prototype.from"); + } + + equals(otherKeyObject: KeyObject): boolean { + if (!isKeyObject(otherKeyObject)) { + throw new ERR_INVALID_ARG_TYPE( + "otherKeyObject", + "KeyObject", + otherKeyObject, + ); + } + + notImplemented("crypto.KeyObject.prototype.equals"); + } + + export(options: KeyExportOptions<"pem">): string | Buffer; + export(options?: KeyExportOptions<"der">): Buffer; + export(options?: JwkKeyExportOptions): JsonWebKey; + export(_options?: unknown): string | Buffer | JsonWebKey { + notImplemented("crypto.KeyObject.prototype.asymmetricKeyType"); + } +} + +export interface JsonWebKeyInput { + key: JsonWebKey; + format: "jwk"; +} + +export function createPrivateKey( + _key: PrivateKeyInput | string | Buffer | JsonWebKeyInput, +): KeyObject { + notImplemented("crypto.createPrivateKey"); +} + +export function createPublicKey( + _key: PublicKeyInput | string | Buffer | KeyObject | JsonWebKeyInput, +): KeyObject { + notImplemented("crypto.createPublicKey"); +} + +function getKeyTypes(allowKeyObject: boolean, bufferOnly = false) { + const types = [ + "ArrayBuffer", + "Buffer", + "TypedArray", + "DataView", + "string", // Only if bufferOnly == false + "KeyObject", // Only if allowKeyObject == true && bufferOnly == false + "CryptoKey", // Only if allowKeyObject == true && bufferOnly == false + ]; + if (bufferOnly) { + return types.slice(0, 4); + } else if (!allowKeyObject) { + return types.slice(0, 5); + } + return types; +} + +export function prepareSecretKey( + key: string | ArrayBuffer | KeyObject, + encoding: string | undefined, + bufferOnly = false, +) { + if (!bufferOnly) { + if (isKeyObject(key)) { + if (key.type !== "secret") { + throw new ERR_CRYPTO_INVALID_KEY_OBJECT_TYPE(key.type, "secret"); + } + return key[kHandle]; + } else if (isCryptoKey(key)) { + if (key.type !== "secret") { + throw new ERR_CRYPTO_INVALID_KEY_OBJECT_TYPE(key.type, "secret"); + } + return key[kKeyObject][kHandle]; + } + } + if ( + typeof key !== "string" && + !isArrayBufferView(key) && + !isAnyArrayBuffer(key) + ) { + throw new ERR_INVALID_ARG_TYPE( + "key", + getKeyTypes(!bufferOnly, bufferOnly), + key, + ); + } + + return getArrayBufferOrView(key, "key", encoding); +} + +export function createSecretKey(key: ArrayBufferView): KeyObject; +export function createSecretKey( + key: string, + encoding: string, +): KeyObject; +export function createSecretKey( + _key: string | ArrayBufferView, + _encoding?: string, +): KeyObject { + notImplemented("crypto.createSecretKey"); +} + +export default { + createPrivateKey, + createPublicKey, + createSecretKey, + isKeyObject, + isCryptoKey, + KeyObject, + prepareSecretKey, +}; diff --git a/ext/node/polyfills/internal/crypto/pbkdf2.ts b/ext/node/polyfills/internal/crypto/pbkdf2.ts new file mode 100644 index 00000000000000..a3d821ae79ea3f --- /dev/null +++ b/ext/node/polyfills/internal/crypto/pbkdf2.ts @@ -0,0 +1,183 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. +import { Buffer } from "internal:deno_node/polyfills/buffer.ts"; +import { createHash } from "internal:deno_node/polyfills/internal/crypto/hash.ts"; +import { HASH_DATA } from "internal:deno_node/polyfills/internal/crypto/types.ts"; + +export const MAX_ALLOC = Math.pow(2, 30) - 1; + +export type NormalizedAlgorithms = + | "md5" + | "ripemd160" + | "sha1" + | "sha224" + | "sha256" + | "sha384" + | "sha512"; + +export type Algorithms = + | "md5" + | "ripemd160" + | "rmd160" + | "sha1" + | "sha224" + | "sha256" + | "sha384" + | "sha512"; + +const createHasher = (algorithm: string) => (value: Uint8Array) => + Buffer.from(createHash(algorithm).update(value).digest() as Buffer); + +function getZeroes(zeros: number) { + return Buffer.alloc(zeros); +} + +const sizes = { + md5: 16, + sha1: 20, + sha224: 28, + sha256: 32, + sha384: 48, + sha512: 64, + rmd160: 20, + ripemd160: 20, +}; + +function toBuffer(bufferable: HASH_DATA) { + if (bufferable instanceof Uint8Array || typeof bufferable === "string") { + return Buffer.from(bufferable as Uint8Array); + } else { + return Buffer.from(bufferable.buffer); + } +} + +export class Hmac { + hash: (value: Uint8Array) => Buffer; + ipad1: Buffer; + opad: Buffer; + alg: string; + blocksize: number; + size: number; + ipad2: Buffer; + + constructor(alg: Algorithms, key: Buffer, saltLen: number) { + this.hash = createHasher(alg); + + const blocksize = alg === "sha512" || alg === "sha384" ? 128 : 64; + + if (key.length > blocksize) { + key = this.hash(key); + } else if (key.length < blocksize) { + key = Buffer.concat([key, getZeroes(blocksize - key.length)], blocksize); + } + + const ipad = Buffer.allocUnsafe(blocksize + sizes[alg]); + const opad = Buffer.allocUnsafe(blocksize + sizes[alg]); + for (let i = 0; i < blocksize; i++) { + ipad[i] = key[i] ^ 0x36; + opad[i] = key[i] ^ 0x5c; + } + + const ipad1 = Buffer.allocUnsafe(blocksize + saltLen + 4); + ipad.copy(ipad1, 0, 0, blocksize); + + this.ipad1 = ipad1; + this.ipad2 = ipad; + this.opad = opad; + this.alg = alg; + this.blocksize = blocksize; + this.size = sizes[alg]; + } + + run(data: Buffer, ipad: Buffer) { + data.copy(ipad, this.blocksize); + const h = this.hash(ipad); + h.copy(this.opad, this.blocksize); + return this.hash(this.opad); + } +} + +/** + * @param iterations Needs to be higher or equal than zero + * @param keylen Needs to be higher or equal than zero but less than max allocation size (2^30) + * @param digest Algorithm to be used for encryption + */ +export function pbkdf2Sync( + password: HASH_DATA, + salt: HASH_DATA, + iterations: number, + keylen: number, + digest: Algorithms = "sha1", +): Buffer { + if (typeof iterations !== "number" || iterations < 0) { + throw new TypeError("Bad iterations"); + } + if (typeof keylen !== "number" || keylen < 0 || keylen > MAX_ALLOC) { + throw new TypeError("Bad key length"); + } + + const bufferedPassword = toBuffer(password); + const bufferedSalt = toBuffer(salt); + + const hmac = new Hmac(digest, bufferedPassword, bufferedSalt.length); + + const DK = Buffer.allocUnsafe(keylen); + const block1 = Buffer.allocUnsafe(bufferedSalt.length + 4); + bufferedSalt.copy(block1, 0, 0, bufferedSalt.length); + + let destPos = 0; + const hLen = sizes[digest]; + const l = Math.ceil(keylen / hLen); + + for (let i = 1; i <= l; i++) { + block1.writeUInt32BE(i, bufferedSalt.length); + + const T = hmac.run(block1, hmac.ipad1); + let U = T; + + for (let j = 1; j < iterations; j++) { + U = hmac.run(U, hmac.ipad2); + for (let k = 0; k < hLen; k++) T[k] ^= U[k]; + } + + T.copy(DK, destPos); + destPos += hLen; + } + + return DK; +} + +/** + * @param iterations Needs to be higher or equal than zero + * @param keylen Needs to be higher or equal than zero but less than max allocation size (2^30) + * @param digest Algorithm to be used for encryption + */ +export function pbkdf2( + password: HASH_DATA, + salt: HASH_DATA, + iterations: number, + keylen: number, + digest: Algorithms = "sha1", + callback: (err: Error | null, derivedKey?: Buffer) => void, +) { + setTimeout(() => { + let err = null, + res; + try { + res = pbkdf2Sync(password, salt, iterations, keylen, digest); + } catch (e) { + err = e; + } + if (err) { + callback(err instanceof Error ? err : new Error("[non-error thrown]")); + } else { + callback(null, res); + } + }, 0); +} + +export default { + Hmac, + MAX_ALLOC, + pbkdf2, + pbkdf2Sync, +}; diff --git a/ext/node/polyfills/internal/crypto/random.ts b/ext/node/polyfills/internal/crypto/random.ts new file mode 100644 index 00000000000000..158ea40dac1c74 --- /dev/null +++ b/ext/node/polyfills/internal/crypto/random.ts @@ -0,0 +1,140 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. +// Copyright Joyent, Inc. and Node.js contributors. All rights reserved. MIT license. + +import { notImplemented } from "internal:deno_node/polyfills/_utils.ts"; +import randomBytes from "internal:deno_node/polyfills/internal/crypto/_randomBytes.ts"; +import randomFill, { + randomFillSync, +} from "internal:deno_node/polyfills/internal/crypto/_randomFill.ts"; +import randomInt from "internal:deno_node/polyfills/internal/crypto/_randomInt.ts"; + +export { default as randomBytes } from "internal:deno_node/polyfills/internal/crypto/_randomBytes.ts"; +export { + default as randomFill, + randomFillSync, +} from "internal:deno_node/polyfills/internal/crypto/_randomFill.ts"; +export { default as randomInt } from "internal:deno_node/polyfills/internal/crypto/_randomInt.ts"; + +export type LargeNumberLike = + | ArrayBufferView + | SharedArrayBuffer + | ArrayBuffer + | bigint; + +export interface CheckPrimeOptions { + /** + * The number of Miller-Rabin probabilistic primality iterations to perform. + * When the value is 0 (zero), a number of checks is used that yields a false positive rate of at most 2-64 for random input. + * Care must be used when selecting a number of checks. + * Refer to the OpenSSL documentation for the BN_is_prime_ex function nchecks options for more details. + * + * @default 0 + */ + checks?: number | undefined; +} + +export function checkPrime( + candidate: LargeNumberLike, + callback: (err: Error | null, result: boolean) => void, +): void; +export function checkPrime( + candidate: LargeNumberLike, + options: CheckPrimeOptions, + callback: (err: Error | null, result: boolean) => void, +): void; +export function checkPrime( + _candidate: LargeNumberLike, + _options?: CheckPrimeOptions | ((err: Error | null, result: boolean) => void), + _callback?: (err: Error | null, result: boolean) => void, +) { + notImplemented("crypto.checkPrime"); +} + +export function checkPrimeSync( + _candidate: LargeNumberLike, + _options?: CheckPrimeOptions, +): boolean { + notImplemented("crypto.checkPrimeSync"); +} + +export interface GeneratePrimeOptions { + add?: LargeNumberLike | undefined; + rem?: LargeNumberLike | undefined; + /** + * @default false + */ + safe?: boolean | undefined; + bigint?: boolean | undefined; +} + +export interface GeneratePrimeOptionsBigInt extends GeneratePrimeOptions { + bigint: true; +} + +export interface GeneratePrimeOptionsArrayBuffer extends GeneratePrimeOptions { + bigint?: false | undefined; +} + +export function generatePrime( + size: number, + callback: (err: Error | null, prime: ArrayBuffer) => void, +): void; +export function generatePrime( + size: number, + options: GeneratePrimeOptionsBigInt, + callback: (err: Error | null, prime: bigint) => void, +): void; +export function generatePrime( + size: number, + options: GeneratePrimeOptionsArrayBuffer, + callback: (err: Error | null, prime: ArrayBuffer) => void, +): void; +export function generatePrime( + size: number, + options: GeneratePrimeOptions, + callback: (err: Error | null, prime: ArrayBuffer | bigint) => void, +): void; +export function generatePrime( + _size: number, + _options?: unknown, + _callback?: unknown, +) { + notImplemented("crypto.generatePrime"); +} + +export function generatePrimeSync(size: number): ArrayBuffer; +export function generatePrimeSync( + size: number, + options: GeneratePrimeOptionsBigInt, +): bigint; +export function generatePrimeSync( + size: number, + options: GeneratePrimeOptionsArrayBuffer, +): ArrayBuffer; +export function generatePrimeSync( + size: number, + options: GeneratePrimeOptions, +): ArrayBuffer | bigint; +export function generatePrimeSync( + _size: number, + _options?: + | GeneratePrimeOptionsBigInt + | GeneratePrimeOptionsArrayBuffer + | GeneratePrimeOptions, +): ArrayBuffer | bigint { + notImplemented("crypto.generatePrimeSync"); +} + +export const randomUUID = () => globalThis.crypto.randomUUID(); + +export default { + checkPrime, + checkPrimeSync, + generatePrime, + generatePrimeSync, + randomUUID, + randomInt, + randomBytes, + randomFill, + randomFillSync, +}; diff --git a/ext/node/polyfills/internal/crypto/scrypt.ts b/ext/node/polyfills/internal/crypto/scrypt.ts new file mode 100644 index 00000000000000..1bdafb63d4acbc --- /dev/null +++ b/ext/node/polyfills/internal/crypto/scrypt.ts @@ -0,0 +1,278 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. +/* +MIT License + +Copyright (c) 2018 cryptocoinjs + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + */ + +import { Buffer } from "internal:deno_node/polyfills/buffer.ts"; +import { pbkdf2Sync as pbkdf2 } from "internal:deno_node/polyfills/internal/crypto/pbkdf2.ts"; +import { HASH_DATA } from "internal:deno_node/polyfills/internal/crypto/types.ts"; + +type Opts = Partial<{ + N: number; + cost: number; + p: number; + parallelization: number; + r: number; + blockSize: number; + maxmem: number; +}>; + +const fixOpts = (opts?: Opts) => { + const out = { N: 16384, p: 1, r: 8, maxmem: 32 << 20 }; + if (!opts) return out; + + if (opts.N) out.N = opts.N; + else if (opts.cost) out.N = opts.cost; + + if (opts.p) out.p = opts.p; + else if (opts.parallelization) out.p = opts.parallelization; + + if (opts.r) out.r = opts.r; + else if (opts.blockSize) out.r = opts.blockSize; + + if (opts.maxmem) out.maxmem = opts.maxmem; + + return out; +}; + +function blockxor(S: Buffer, Si: number, D: Buffer, Di: number, len: number) { + let i = -1; + while (++i < len) D[Di + i] ^= S[Si + i]; +} +function arraycopy( + src: Buffer, + srcPos: number, + dest: Buffer, + destPos: number, + length: number, +) { + src.copy(dest, destPos, srcPos, srcPos + length); +} + +const R = (a: number, b: number) => (a << b) | (a >>> (32 - b)); + +class ScryptRom { + B: Buffer; + r: number; + N: number; + p: number; + XY: Buffer; + V: Buffer; + B32: Int32Array; + x: Int32Array; + _X: Buffer; + constructor(b: Buffer, r: number, N: number, p: number) { + this.B = b; + this.r = r; + this.N = N; + this.p = p; + this.XY = Buffer.allocUnsafe(256 * r); + this.V = Buffer.allocUnsafe(128 * r * N); + this.B32 = new Int32Array(16); // salsa20_8 + this.x = new Int32Array(16); // salsa20_8 + this._X = Buffer.allocUnsafe(64); // blockmix_salsa8 + } + + run() { + const p = this.p | 0; + const r = this.r | 0; + for (let i = 0; i < p; i++) this.scryptROMix(i, r); + + return this.B; + } + + scryptROMix(i: number, r: number) { + const blockStart = i * 128 * r; + const offset = (2 * r - 1) * 64; + const blockLen = 128 * r; + const B = this.B; + const N = this.N | 0; + const V = this.V; + const XY = this.XY; + B.copy(XY, 0, blockStart, blockStart + blockLen); + for (let i1 = 0; i1 < N; i1++) { + XY.copy(V, i1 * blockLen, 0, blockLen); + this.blockmix_salsa8(blockLen); + } + + let j: number; + for (let i2 = 0; i2 < N; i2++) { + j = XY.readUInt32LE(offset) & (N - 1); + blockxor(V, j * blockLen, XY, 0, blockLen); + this.blockmix_salsa8(blockLen); + } + XY.copy(B, blockStart, 0, blockLen); + } + + blockmix_salsa8(blockLen: number) { + const BY = this.XY; + const r = this.r; + const _X = this._X; + arraycopy(BY, (2 * r - 1) * 64, _X, 0, 64); + let i; + for (i = 0; i < 2 * r; i++) { + blockxor(BY, i * 64, _X, 0, 64); + this.salsa20_8(); + arraycopy(_X, 0, BY, blockLen + i * 64, 64); + } + for (i = 0; i < r; i++) { + arraycopy(BY, blockLen + i * 2 * 64, BY, i * 64, 64); + arraycopy(BY, blockLen + (i * 2 + 1) * 64, BY, (i + r) * 64, 64); + } + } + + salsa20_8() { + const B32 = this.B32; + const B = this._X; + const x = this.x; + + let i; + for (i = 0; i < 16; i++) { + B32[i] = (B[i * 4 + 0] & 0xff) << 0; + B32[i] |= (B[i * 4 + 1] & 0xff) << 8; + B32[i] |= (B[i * 4 + 2] & 0xff) << 16; + B32[i] |= (B[i * 4 + 3] & 0xff) << 24; + } + + for (i = 0; i < 16; i++) x[i] = B32[i]; + + for (i = 0; i < 4; i++) { + x[4] ^= R(x[0] + x[12], 7); + x[8] ^= R(x[4] + x[0], 9); + x[12] ^= R(x[8] + x[4], 13); + x[0] ^= R(x[12] + x[8], 18); + x[9] ^= R(x[5] + x[1], 7); + x[13] ^= R(x[9] + x[5], 9); + x[1] ^= R(x[13] + x[9], 13); + x[5] ^= R(x[1] + x[13], 18); + x[14] ^= R(x[10] + x[6], 7); + x[2] ^= R(x[14] + x[10], 9); + x[6] ^= R(x[2] + x[14], 13); + x[10] ^= R(x[6] + x[2], 18); + x[3] ^= R(x[15] + x[11], 7); + x[7] ^= R(x[3] + x[15], 9); + x[11] ^= R(x[7] + x[3], 13); + x[15] ^= R(x[11] + x[7], 18); + x[1] ^= R(x[0] + x[3], 7); + x[2] ^= R(x[1] + x[0], 9); + x[3] ^= R(x[2] + x[1], 13); + x[0] ^= R(x[3] + x[2], 18); + x[6] ^= R(x[5] + x[4], 7); + x[7] ^= R(x[6] + x[5], 9); + x[4] ^= R(x[7] + x[6], 13); + x[5] ^= R(x[4] + x[7], 18); + x[11] ^= R(x[10] + x[9], 7); + x[8] ^= R(x[11] + x[10], 9); + x[9] ^= R(x[8] + x[11], 13); + x[10] ^= R(x[9] + x[8], 18); + x[12] ^= R(x[15] + x[14], 7); + x[13] ^= R(x[12] + x[15], 9); + x[14] ^= R(x[13] + x[12], 13); + x[15] ^= R(x[14] + x[13], 18); + } + for (i = 0; i < 16; i++) B32[i] += x[i]; + + let bi; + + for (i = 0; i < 16; i++) { + bi = i * 4; + B[bi + 0] = (B32[i] >> 0) & 0xff; + B[bi + 1] = (B32[i] >> 8) & 0xff; + B[bi + 2] = (B32[i] >> 16) & 0xff; + B[bi + 3] = (B32[i] >> 24) & 0xff; + } + } + + clean() { + this.XY.fill(0); + this.V.fill(0); + this._X.fill(0); + this.B.fill(0); + for (let i = 0; i < 16; i++) { + this.B32[i] = 0; + this.x[i] = 0; + } + } +} + +export function scryptSync( + password: HASH_DATA, + salt: HASH_DATA, + keylen: number, + _opts?: Opts, +): Buffer { + const { N, r, p, maxmem } = fixOpts(_opts); + + const blen = p * 128 * r; + + if (32 * r * (N + 2) * 4 + blen > maxmem) { + throw new Error("excedes max memory"); + } + + const b = pbkdf2(password, salt, 1, blen, "sha256"); + + const scryptRom = new ScryptRom(b, r, N, p); + const out = scryptRom.run(); + + const fin = pbkdf2(password, out, 1, keylen, "sha256"); + scryptRom.clean(); + return fin; +} + +type Callback = (err: unknown, result?: Buffer) => void; + +export function scrypt( + password: HASH_DATA, + salt: HASH_DATA, + keylen: number, + _opts: Opts | null | Callback, + cb?: Callback, +) { + if (!cb) { + cb = _opts as Callback; + _opts = null; + } + const { N, r, p, maxmem } = fixOpts(_opts as Opts); + + const blen = p * 128 * r; + if (32 * r * (N + 2) * 4 + blen > maxmem) { + throw new Error("excedes max memory"); + } + + try { + const b = pbkdf2(password, salt, 1, blen, "sha256"); + + const scryptRom = new ScryptRom(b, r, N, p); + const out = scryptRom.run(); + const result = pbkdf2(password, out, 1, keylen, "sha256"); + scryptRom.clean(); + cb(null, result); + } catch (err: unknown) { + return cb(err); + } +} + +export default { + scrypt, + scryptSync, +}; diff --git a/ext/node/polyfills/internal/crypto/sig.ts b/ext/node/polyfills/internal/crypto/sig.ts new file mode 100644 index 00000000000000..6c163c8e5baef1 --- /dev/null +++ b/ext/node/polyfills/internal/crypto/sig.ts @@ -0,0 +1,148 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. +// Copyright Joyent, Inc. and Node.js contributors. All rights reserved. MIT license. + +import { notImplemented } from "internal:deno_node/polyfills/_utils.ts"; +import { validateString } from "internal:deno_node/polyfills/internal/validators.mjs"; +import { Buffer } from "internal:deno_node/polyfills/buffer.ts"; +import type { WritableOptions } from "internal:deno_node/polyfills/_stream.d.ts"; +import Writable from "internal:deno_node/polyfills/internal/streams/writable.mjs"; +import type { + BinaryLike, + BinaryToTextEncoding, + Encoding, + PrivateKeyInput, + PublicKeyInput, +} from "internal:deno_node/polyfills/internal/crypto/types.ts"; +import { KeyObject } from "internal:deno_node/polyfills/internal/crypto/keys.ts"; + +export type DSAEncoding = "der" | "ieee-p1363"; + +export interface SigningOptions { + padding?: number | undefined; + saltLength?: number | undefined; + dsaEncoding?: DSAEncoding | undefined; +} + +export interface SignPrivateKeyInput extends PrivateKeyInput, SigningOptions {} + +export interface SignKeyObjectInput extends SigningOptions { + key: KeyObject; +} +export interface VerifyPublicKeyInput extends PublicKeyInput, SigningOptions {} + +export interface VerifyKeyObjectInput extends SigningOptions { + key: KeyObject; +} + +export type KeyLike = string | Buffer | KeyObject; + +export class Sign extends Writable { + constructor(algorithm: string, _options?: WritableOptions) { + validateString(algorithm, "algorithm"); + + super(); + + notImplemented("crypto.Sign"); + } + + sign(privateKey: KeyLike | SignKeyObjectInput | SignPrivateKeyInput): Buffer; + sign( + privateKey: KeyLike | SignKeyObjectInput | SignPrivateKeyInput, + outputFormat: BinaryToTextEncoding, + ): string; + sign( + _privateKey: KeyLike | SignKeyObjectInput | SignPrivateKeyInput, + _outputEncoding?: BinaryToTextEncoding, + ): Buffer | string { + notImplemented("crypto.Sign.prototype.sign"); + } + + update(data: BinaryLike): this; + update(data: string, inputEncoding: Encoding): this; + update(_data: BinaryLike | string, _inputEncoding?: Encoding): this { + notImplemented("crypto.Sign.prototype.update"); + } +} + +export class Verify extends Writable { + constructor(algorithm: string, _options?: WritableOptions) { + validateString(algorithm, "algorithm"); + + super(); + + notImplemented("crypto.Verify"); + } + + update(data: BinaryLike): this; + update(data: string, inputEncoding: Encoding): this; + update(_data: BinaryLike, _inputEncoding?: string): this { + notImplemented("crypto.Sign.prototype.update"); + } + + verify( + object: KeyLike | VerifyKeyObjectInput | VerifyPublicKeyInput, + signature: ArrayBufferView, + ): boolean; + verify( + object: KeyLike | VerifyKeyObjectInput | VerifyPublicKeyInput, + signature: string, + signatureEncoding?: BinaryToTextEncoding, + ): boolean; + verify( + _object: KeyLike | VerifyKeyObjectInput | VerifyPublicKeyInput, + _signature: ArrayBufferView | string, + _signatureEncoding?: BinaryToTextEncoding, + ): boolean { + notImplemented("crypto.Sign.prototype.sign"); + } +} + +export function signOneShot( + algorithm: string | null | undefined, + data: ArrayBufferView, + key: KeyLike | SignKeyObjectInput | SignPrivateKeyInput, +): Buffer; +export function signOneShot( + algorithm: string | null | undefined, + data: ArrayBufferView, + key: KeyLike | SignKeyObjectInput | SignPrivateKeyInput, + callback: (error: Error | null, data: Buffer) => void, +): void; +export function signOneShot( + _algorithm: string | null | undefined, + _data: ArrayBufferView, + _key: KeyLike | SignKeyObjectInput | SignPrivateKeyInput, + _callback?: (error: Error | null, data: Buffer) => void, +): Buffer | void { + notImplemented("crypto.sign"); +} + +export function verifyOneShot( + algorithm: string | null | undefined, + data: ArrayBufferView, + key: KeyLike | VerifyKeyObjectInput | VerifyPublicKeyInput, + signature: ArrayBufferView, +): boolean; +export function verifyOneShot( + algorithm: string | null | undefined, + data: ArrayBufferView, + key: KeyLike | VerifyKeyObjectInput | VerifyPublicKeyInput, + signature: ArrayBufferView, + callback: (error: Error | null, result: boolean) => void, +): void; +export function verifyOneShot( + _algorithm: string | null | undefined, + _data: ArrayBufferView, + _key: KeyLike | VerifyKeyObjectInput | VerifyPublicKeyInput, + _signature: ArrayBufferView, + _callback?: (error: Error | null, result: boolean) => void, +): boolean | void { + notImplemented("crypto.verify"); +} + +export default { + signOneShot, + verifyOneShot, + Sign, + Verify, +}; diff --git a/ext/node/polyfills/internal/crypto/types.ts b/ext/node/polyfills/internal/crypto/types.ts new file mode 100644 index 00000000000000..3bb9ec1600b380 --- /dev/null +++ b/ext/node/polyfills/internal/crypto/types.ts @@ -0,0 +1,46 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. +// Copyright Joyent, Inc. and Node.js contributors. All rights reserved. MIT license. + +import { Buffer } from "internal:deno_node/polyfills/buffer.ts"; + +export type HASH_DATA = string | ArrayBufferView | Buffer; + +export type BinaryToTextEncoding = "base64" | "base64url" | "hex" | "binary"; + +export type CharacterEncoding = "utf8" | "utf-8" | "utf16le" | "latin1"; + +export type LegacyCharacterEncoding = "ascii" | "binary" | "ucs2" | "ucs-2"; + +export type Encoding = + | BinaryToTextEncoding + | CharacterEncoding + | LegacyCharacterEncoding; + +export type ECDHKeyFormat = "compressed" | "uncompressed" | "hybrid"; + +export type BinaryLike = string | ArrayBufferView; + +export type KeyFormat = "pem" | "der"; + +export type KeyType = + | "rsa" + | "rsa-pss" + | "dsa" + | "ec" + | "ed25519" + | "ed448" + | "x25519" + | "x448"; + +export interface PrivateKeyInput { + key: string | Buffer; + format?: KeyFormat | undefined; + type?: "pkcs1" | "pkcs8" | "sec1" | undefined; + passphrase?: string | Buffer | undefined; +} + +export interface PublicKeyInput { + key: string | Buffer; + format?: KeyFormat | undefined; + type?: "pkcs1" | "spki" | undefined; +} diff --git a/ext/node/polyfills/internal/crypto/util.ts b/ext/node/polyfills/internal/crypto/util.ts new file mode 100644 index 00000000000000..8a7f7a1b6baac4 --- /dev/null +++ b/ext/node/polyfills/internal/crypto/util.ts @@ -0,0 +1,150 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. +// Copyright Joyent, Inc. and Node.js contributors. All rights reserved. MIT license. + +import { notImplemented } from "internal:deno_node/polyfills/_utils.ts"; +import { Buffer } from "internal:deno_node/polyfills/buffer.ts"; +import { + ERR_INVALID_ARG_TYPE, + hideStackFrames, +} from "internal:deno_node/polyfills/internal/errors.ts"; +import { + isAnyArrayBuffer, + isArrayBufferView, +} from "internal:deno_node/polyfills/internal/util/types.ts"; +import { crypto as constants } from "internal:deno_node/polyfills/internal_binding/constants.ts"; +import { + kHandle, + kKeyObject, +} from "internal:deno_node/polyfills/internal/crypto/constants.ts"; + +// TODO(kt3k): Generate this list from `digestAlgorithms` +// of std/crypto/_wasm/mod.ts +const digestAlgorithms = [ + "blake2b256", + "blake2b384", + "blake2b", + "blake2s", + "blake3", + "keccak-224", + "keccak-256", + "keccak-384", + "keccak-512", + "sha384", + "sha3-224", + "sha3-256", + "sha3-384", + "sha3-512", + "shake128", + "shake256", + "tiger", + "rmd160", + "sha224", + "sha256", + "sha512", + "md4", + "md5", + "sha1", +]; + +// deno-fmt-ignore +const supportedCiphers = [ + "aes-128-ecb", "aes-192-ecb", + "aes-256-ecb", "aes-128-cbc", + "aes-192-cbc", "aes-256-cbc", + "aes128", "aes192", + "aes256", "aes-128-cfb", + "aes-192-cfb", "aes-256-cfb", + "aes-128-cfb8", "aes-192-cfb8", + "aes-256-cfb8", "aes-128-cfb1", + "aes-192-cfb1", "aes-256-cfb1", + "aes-128-ofb", "aes-192-ofb", + "aes-256-ofb", "aes-128-ctr", + "aes-192-ctr", "aes-256-ctr", + "aes-128-gcm", "aes-192-gcm", + "aes-256-gcm" +]; + +export function getCiphers(): string[] { + return supportedCiphers; +} + +let defaultEncoding = "buffer"; + +export function setDefaultEncoding(val: string) { + defaultEncoding = val; +} + +export function getDefaultEncoding(): string { + return defaultEncoding; +} + +// This is here because many functions accepted binary strings without +// any explicit encoding in older versions of node, and we don't want +// to break them unnecessarily. +export function toBuf(val: string | Buffer, encoding?: string): Buffer { + if (typeof val === "string") { + if (encoding === "buffer") { + encoding = "utf8"; + } + + return Buffer.from(val, encoding); + } + + return val; +} + +export const validateByteSource = hideStackFrames((val, name) => { + val = toBuf(val); + + if (isAnyArrayBuffer(val) || isArrayBufferView(val)) { + return; + } + + throw new ERR_INVALID_ARG_TYPE( + name, + ["string", "ArrayBuffer", "TypedArray", "DataView", "Buffer"], + val, + ); +}); + +/** + * Returns an array of the names of the supported hash algorithms, such as 'sha1'. + */ +export function getHashes(): readonly string[] { + return digestAlgorithms; +} + +export function getCurves(): readonly string[] { + notImplemented("crypto.getCurves"); +} + +export interface SecureHeapUsage { + total: number; + min: number; + used: number; + utilization: number; +} + +export function secureHeapUsed(): SecureHeapUsage { + notImplemented("crypto.secureHeapUsed"); +} + +export function setEngine(_engine: string, _flags: typeof constants) { + notImplemented("crypto.setEngine"); +} + +export { kHandle, kKeyObject }; + +export default { + getDefaultEncoding, + getHashes, + setDefaultEncoding, + getCiphers, + getCurves, + secureHeapUsed, + setEngine, + validateByteSource, + toBuf, + kHandle, + kKeyObject, +}; diff --git a/ext/node/polyfills/internal/crypto/x509.ts b/ext/node/polyfills/internal/crypto/x509.ts new file mode 100644 index 00000000000000..0722d78652d89a --- /dev/null +++ b/ext/node/polyfills/internal/crypto/x509.ts @@ -0,0 +1,186 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. +// Copyright Joyent, Inc. and Node.js contributors. All rights reserved. MIT license. + +import { KeyObject } from "internal:deno_node/polyfills/internal/crypto/keys.ts"; +import { Buffer } from "internal:deno_node/polyfills/buffer.ts"; +import { ERR_INVALID_ARG_TYPE } from "internal:deno_node/polyfills/internal/errors.ts"; +import { isArrayBufferView } from "internal:deno_node/polyfills/internal/util/types.ts"; +import { notImplemented } from "internal:deno_node/polyfills/_utils.ts"; +import { BinaryLike } from "internal:deno_node/polyfills/internal/crypto/types.ts"; + +// deno-lint-ignore no-explicit-any +export type PeerCertificate = any; + +export interface X509CheckOptions { + /** + * @default 'always' + */ + subject: "always" | "never"; + /** + * @default true + */ + wildcards: boolean; + /** + * @default true + */ + partialWildcards: boolean; + /** + * @default false + */ + multiLabelWildcards: boolean; + /** + * @default false + */ + singleLabelSubdomains: boolean; +} + +export class X509Certificate { + constructor(buffer: BinaryLike) { + if (typeof buffer === "string") { + buffer = Buffer.from(buffer); + } + + if (!isArrayBufferView(buffer)) { + throw new ERR_INVALID_ARG_TYPE( + "buffer", + ["string", "Buffer", "TypedArray", "DataView"], + buffer, + ); + } + + notImplemented("crypto.X509Certificate"); + } + + get ca(): boolean { + notImplemented("crypto.X509Certificate.prototype.ca"); + + return false; + } + + checkEmail( + _email: string, + _options?: Pick, + ): string | undefined { + notImplemented("crypto.X509Certificate.prototype.checkEmail"); + } + + checkHost(_name: string, _options?: X509CheckOptions): string | undefined { + notImplemented("crypto.X509Certificate.prototype.checkHost"); + } + + checkIP(_ip: string): string | undefined { + notImplemented("crypto.X509Certificate.prototype.checkIP"); + } + + checkIssued(_otherCert: X509Certificate): boolean { + notImplemented("crypto.X509Certificate.prototype.checkIssued"); + } + + checkPrivateKey(_privateKey: KeyObject): boolean { + notImplemented("crypto.X509Certificate.prototype.checkPrivateKey"); + } + + get fingerprint(): string { + notImplemented("crypto.X509Certificate.prototype.fingerprint"); + + return ""; + } + + get fingerprint256(): string { + notImplemented("crypto.X509Certificate.prototype.fingerprint256"); + + return ""; + } + + get fingerprint512(): string { + notImplemented("crypto.X509Certificate.prototype.fingerprint512"); + + return ""; + } + + get infoAccess(): string | undefined { + notImplemented("crypto.X509Certificate.prototype.infoAccess"); + + return ""; + } + + get issuer(): string { + notImplemented("crypto.X509Certificate.prototype.issuer"); + + return ""; + } + + get issuerCertificate(): X509Certificate | undefined { + notImplemented("crypto.X509Certificate.prototype.issuerCertificate"); + + return {} as X509Certificate; + } + + get keyUsage(): string[] { + notImplemented("crypto.X509Certificate.prototype.keyUsage"); + + return []; + } + + get publicKey(): KeyObject { + notImplemented("crypto.X509Certificate.prototype.publicKey"); + + return {} as KeyObject; + } + + get raw(): Buffer { + notImplemented("crypto.X509Certificate.prototype.raw"); + + return {} as Buffer; + } + + get serialNumber(): string { + notImplemented("crypto.X509Certificate.prototype.serialNumber"); + + return ""; + } + + get subject(): string { + notImplemented("crypto.X509Certificate.prototype.subject"); + + return ""; + } + + get subjectAltName(): string | undefined { + notImplemented("crypto.X509Certificate.prototype.subjectAltName"); + + return ""; + } + + toJSON(): string { + return this.toString(); + } + + toLegacyObject(): PeerCertificate { + notImplemented("crypto.X509Certificate.prototype.toLegacyObject"); + } + + toString(): string { + notImplemented("crypto.X509Certificate.prototype.toString"); + } + + get validFrom(): string { + notImplemented("crypto.X509Certificate.prototype.validFrom"); + + return ""; + } + + get validTo(): string { + notImplemented("crypto.X509Certificate.prototype.validTo"); + + return ""; + } + + verify(_publicKey: KeyObject): boolean { + notImplemented("crypto.X509Certificate.prototype.verify"); + } +} + +export default { + X509Certificate, +}; diff --git a/ext/node/polyfills/internal/dgram.ts b/ext/node/polyfills/internal/dgram.ts new file mode 100644 index 00000000000000..8e8e50fabda583 --- /dev/null +++ b/ext/node/polyfills/internal/dgram.ts @@ -0,0 +1,129 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. +// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. + +import { lookup as defaultLookup } from "internal:deno_node/polyfills/dns.ts"; +import { + isInt32, + validateFunction, +} from "internal:deno_node/polyfills/internal/validators.mjs"; +import type { ErrnoException } from "internal:deno_node/polyfills/internal/errors.ts"; +import { ERR_SOCKET_BAD_TYPE } from "internal:deno_node/polyfills/internal/errors.ts"; +import { UDP } from "internal:deno_node/polyfills/internal_binding/udp_wrap.ts"; +import { guessHandleType } from "internal:deno_node/polyfills/internal_binding/util.ts"; +import { codeMap } from "internal:deno_node/polyfills/internal_binding/uv.ts"; + +export type SocketType = "udp4" | "udp6"; + +export const kStateSymbol: unique symbol = Symbol("kStateSymbol"); + +function lookup4( + lookup: typeof defaultLookup, + address: string, + callback: ( + err: ErrnoException | null, + address: string, + family: number, + ) => void, +) { + return lookup(address || "127.0.0.1", 4, callback); +} + +function lookup6( + lookup: typeof defaultLookup, + address: string, + callback: ( + err: ErrnoException | null, + address: string, + family: number, + ) => void, +) { + return lookup(address || "::1", 6, callback); +} + +export function newHandle( + type: SocketType, + lookup?: typeof defaultLookup, +): UDP { + if (lookup === undefined) { + lookup = defaultLookup; + } else { + validateFunction(lookup, "lookup"); + } + + if (type === "udp4") { + const handle = new UDP(); + + handle.lookup = lookup4.bind(handle, lookup); + + return handle; + } + + if (type === "udp6") { + const handle = new UDP(); + + handle.lookup = lookup6.bind(handle, lookup); + handle.bind = handle.bind6; + handle.connect = handle.connect6; + handle.send = handle.send6; + + return handle; + } + + throw new ERR_SOCKET_BAD_TYPE(); +} + +export function _createSocketHandle( + address: string, + port: number, + addressType: SocketType, + fd: number, + flags: number, +) { + const handle = newHandle(addressType); + let err; + + if (isInt32(fd) && fd > 0) { + const type = guessHandleType(fd); + + if (type !== "UDP") { + err = codeMap.get("EINVAL")!; + } else { + err = handle.open(fd); + } + } else if (port || address) { + err = handle.bind(address, port || 0, flags); + } + + if (err) { + handle.close(); + + return err; + } + + return handle; +} + +export default { + kStateSymbol, + newHandle, + _createSocketHandle, +}; diff --git a/ext/node/polyfills/internal/dns/promises.ts b/ext/node/polyfills/internal/dns/promises.ts new file mode 100644 index 00000000000000..40f57fd2c02f16 --- /dev/null +++ b/ext/node/polyfills/internal/dns/promises.ts @@ -0,0 +1,511 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. +// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. + +import { + validateBoolean, + validateNumber, + validateOneOf, + validateString, +} from "internal:deno_node/polyfills/internal/validators.mjs"; +import { isIP } from "internal:deno_node/polyfills/internal/net.ts"; +import { + emitInvalidHostnameWarning, + getDefaultResolver, + getDefaultVerbatim, + isFamily, + isLookupOptions, + Resolver as CallbackResolver, + validateHints, +} from "internal:deno_node/polyfills/internal/dns/utils.ts"; +import type { + LookupAddress, + LookupAllOptions, + LookupOneOptions, + LookupOptions, + Records, + ResolveOptions, + ResolveWithTtlOptions, +} from "internal:deno_node/polyfills/internal/dns/utils.ts"; +import { + dnsException, + ERR_INVALID_ARG_TYPE, + ERR_INVALID_ARG_VALUE, +} from "internal:deno_node/polyfills/internal/errors.ts"; +import { + ChannelWrapQuery, + getaddrinfo, + GetAddrInfoReqWrap, + QueryReqWrap, +} from "internal:deno_node/polyfills/internal_binding/cares_wrap.ts"; +import { toASCII } from "internal:deno_node/polyfills/punycode.ts"; + +function onlookup( + this: GetAddrInfoReqWrap, + err: number | null, + addresses: string[], +) { + if (err) { + this.reject(dnsException(err, "getaddrinfo", this.hostname)); + return; + } + + const family = this.family || isIP(addresses[0]); + this.resolve({ address: addresses[0], family }); +} + +function onlookupall( + this: GetAddrInfoReqWrap, + err: number | null, + addresses: string[], +) { + if (err) { + this.reject(dnsException(err, "getaddrinfo", this.hostname)); + + return; + } + + const family = this.family; + const parsedAddresses = []; + + for (let i = 0; i < addresses.length; i++) { + const address = addresses[i]; + parsedAddresses[i] = { + address, + family: family ? family : isIP(address), + }; + } + + this.resolve(parsedAddresses); +} + +function createLookupPromise( + family: number, + hostname: string, + all: boolean, + hints: number, + verbatim: boolean, +): Promise { + return new Promise((resolve, reject) => { + if (!hostname) { + emitInvalidHostnameWarning(hostname); + resolve(all ? [] : { address: null, family: family === 6 ? 6 : 4 }); + + return; + } + + const matchedFamily = isIP(hostname); + + if (matchedFamily !== 0) { + const result = { address: hostname, family: matchedFamily }; + resolve(all ? [result] : result); + + return; + } + + const req = new GetAddrInfoReqWrap(); + + req.family = family; + req.hostname = hostname; + req.oncomplete = all ? onlookupall : onlookup; + req.resolve = resolve; + req.reject = reject; + + const err = getaddrinfo(req, toASCII(hostname), family, hints, verbatim); + + if (err) { + reject(dnsException(err, "getaddrinfo", hostname)); + } + }); +} + +const validFamilies = [0, 4, 6]; + +export function lookup( + hostname: string, + family: number, +): Promise; +export function lookup( + hostname: string, + options: LookupOneOptions, +): Promise; +export function lookup( + hostname: string, + options: LookupAllOptions, +): Promise; +export function lookup( + hostname: string, + options: LookupOptions, +): Promise; +export function lookup( + hostname: string, + options: unknown, +): Promise { + let hints = 0; + let family = 0; + let all = false; + let verbatim = getDefaultVerbatim(); + + // Parse arguments + if (hostname) { + validateString(hostname, "hostname"); + } + + if (isFamily(options)) { + validateOneOf(options, "family", validFamilies); + family = options; + } else if (!isLookupOptions(options)) { + throw new ERR_INVALID_ARG_TYPE("options", ["integer", "object"], options); + } else { + if (options?.hints != null) { + validateNumber(options.hints, "options.hints"); + hints = options.hints >>> 0; + validateHints(hints); + } + + if (options?.family != null) { + validateOneOf(options.family, "options.family", validFamilies); + family = options.family; + } + + if (options?.all != null) { + validateBoolean(options.all, "options.all"); + all = options.all; + } + + if (options?.verbatim != null) { + validateBoolean(options.verbatim, "options.verbatim"); + verbatim = options.verbatim; + } + } + + return createLookupPromise(family, hostname, all, hints, verbatim); +} + +function onresolve( + this: QueryReqWrap, + err: number, + records: Records, + ttls?: number[], +) { + if (err) { + this.reject(dnsException(err, this.bindingName, this.hostname)); + + return; + } + + const parsedRecords = ttls && this.ttl + ? (records as string[]).map((address: string, index: number) => ({ + address, + ttl: ttls[index], + })) + : records; + + this.resolve(parsedRecords); +} + +function createResolverPromise( + resolver: Resolver, + bindingName: keyof ChannelWrapQuery, + hostname: string, + ttl: boolean, +) { + return new Promise((resolve, reject) => { + const req = new QueryReqWrap(); + + req.bindingName = bindingName; + req.hostname = hostname; + req.oncomplete = onresolve; + req.resolve = resolve; + req.reject = reject; + req.ttl = ttl; + + const err = resolver._handle[bindingName](req, toASCII(hostname)); + + if (err) { + reject(dnsException(err, bindingName, hostname)); + } + }); +} + +function resolver(bindingName: keyof ChannelWrapQuery) { + function query( + this: Resolver, + name: string, + options?: unknown, + ) { + validateString(name, "name"); + + const ttl = !!(options && (options as ResolveOptions).ttl); + + return createResolverPromise(this, bindingName, name, ttl); + } + + Object.defineProperty(query, "name", { value: bindingName }); + + return query; +} + +const resolveMap = Object.create(null); + +class Resolver extends CallbackResolver { + // deno-lint-ignore no-explicit-any + [resolveMethod: string]: any; +} + +Resolver.prototype.resolveAny = resolveMap.ANY = resolver("queryAny"); +Resolver.prototype.resolve4 = resolveMap.A = resolver("queryA"); +Resolver.prototype.resolve6 = resolveMap.AAAA = resolver("queryAaaa"); +Resolver.prototype.resolveCaa = resolveMap.CAA = resolver("queryCaa"); +Resolver.prototype.resolveCname = resolveMap.CNAME = resolver("queryCname"); +Resolver.prototype.resolveMx = resolveMap.MX = resolver("queryMx"); +Resolver.prototype.resolveNs = resolveMap.NS = resolver("queryNs"); +Resolver.prototype.resolveTxt = resolveMap.TXT = resolver("queryTxt"); +Resolver.prototype.resolveSrv = resolveMap.SRV = resolver("querySrv"); +Resolver.prototype.resolvePtr = resolveMap.PTR = resolver("queryPtr"); +Resolver.prototype.resolveNaptr = resolveMap.NAPTR = resolver("queryNaptr"); +Resolver.prototype.resolveSoa = resolveMap.SOA = resolver("querySoa"); +Resolver.prototype.reverse = resolver("getHostByAddr"); +Resolver.prototype.resolve = _resolve; + +function _resolve( + this: Resolver, + hostname: string, + rrtype?: string, +) { + let resolver; + + if (typeof hostname !== "string") { + throw new ERR_INVALID_ARG_TYPE("name", "string", hostname); + } + + if (rrtype !== undefined) { + validateString(rrtype, "rrtype"); + + resolver = resolveMap[rrtype]; + + if (typeof resolver !== "function") { + throw new ERR_INVALID_ARG_VALUE("rrtype", rrtype); + } + } else { + resolver = resolveMap.A; + } + + return Reflect.apply(resolver, this, [hostname]); +} + +// The Node implementation uses `bindDefaultResolver` to set the follow methods +// on `module.exports` bound to the current `defaultResolver`. We don't have +// the same ability in ESM but can simulate this (at some cost) by explicitly +// exporting these methods which dynamically bind to the default resolver when +// called. + +export function getServers(): string[] { + return Resolver.prototype.getServers.bind(getDefaultResolver())(); +} + +export function resolveAny( + hostname: string, +) { + return Resolver.prototype.resolveAny.bind(getDefaultResolver() as Resolver)( + hostname, + ); +} + +export function resolve4( + hostname: string, +): Promise; +export function resolve4( + hostname: string, + options: ResolveWithTtlOptions, +): Promise; +export function resolve4( + hostname: string, + options: ResolveOptions, +): Promise; +export function resolve4(hostname: string, options?: unknown) { + return Resolver.prototype.resolve4.bind(getDefaultResolver() as Resolver)( + hostname, + options, + ); +} + +export function resolve6(hostname: string): Promise; +export function resolve6( + hostname: string, + options: ResolveWithTtlOptions, +): Promise; +export function resolve6( + hostname: string, + options: ResolveOptions, +): Promise; +export function resolve6(hostname: string, options?: unknown) { + return Resolver.prototype.resolve6.bind(getDefaultResolver() as Resolver)( + hostname, + options, + ); +} + +export function resolveCaa( + hostname: string, +) { + return Resolver.prototype.resolveCaa.bind(getDefaultResolver() as Resolver)( + hostname, + ); +} + +export function resolveCname( + hostname: string, +) { + return Resolver.prototype.resolveCname.bind(getDefaultResolver() as Resolver)( + hostname, + ); +} + +export function resolveMx( + hostname: string, +) { + return Resolver.prototype.resolveMx.bind(getDefaultResolver() as Resolver)( + hostname, + ); +} + +export function resolveNs(hostname: string) { + return Resolver.prototype.resolveNs.bind(getDefaultResolver() as Resolver)( + hostname, + ); +} + +export function resolveTxt(hostname: string) { + return Resolver.prototype.resolveTxt.bind(getDefaultResolver() as Resolver)( + hostname, + ); +} + +export function resolveSrv(hostname: string) { + return Resolver.prototype.resolveSrv.bind(getDefaultResolver() as Resolver)( + hostname, + ); +} + +export function resolvePtr(hostname: string) { + return Resolver.prototype.resolvePtr.bind(getDefaultResolver() as Resolver)( + hostname, + ); +} + +export function resolveNaptr(hostname: string) { + return Resolver.prototype.resolveNaptr.bind(getDefaultResolver() as Resolver)( + hostname, + ); +} + +export function resolveSoa(hostname: string) { + return Resolver.prototype.resolveSoa.bind(getDefaultResolver() as Resolver)( + hostname, + ); +} + +export function reverse(ip: string) { + return Resolver.prototype.reverse.bind(getDefaultResolver() as Resolver)( + ip, + ); +} + +export function resolve( + hostname: string, +): Promise; +export function resolve( + hostname: string, + rrtype: "A", +): Promise; +export function resolve( + hostname: string, + rrtype: "AAAA", +): Promise; +export function resolve( + hostname: string, + rrtype: "ANY", +): Promise; +export function resolve( + hostname: string, + rrtype: "CNAME", +): Promise; +export function resolve( + hostname: string, + rrtype: "MX", +): Promise; +export function resolve( + hostname: string, + rrtype: "NAPTR", +): Promise; +export function resolve( + hostname: string, + rrtype: "NS", +): Promise; +export function resolve( + hostname: string, + rrtype: "PTR", +): Promise; +export function resolve( + hostname: string, + rrtype: "SOA", +): Promise; +export function resolve( + hostname: string, + rrtype: "SRV", +): Promise; +export function resolve( + hostname: string, + rrtype: "TXT", +): Promise; +export function resolve( + hostname: string, + rrtype: string, +): Promise; +export function resolve(hostname: string, rrtype?: string) { + return Resolver.prototype.resolve.bind(getDefaultResolver() as Resolver)( + hostname, + rrtype, + ); +} + +export { Resolver }; + +export default { + lookup, + Resolver, + getServers, + resolveAny, + resolve4, + resolve6, + resolveCaa, + resolveCname, + resolveMx, + resolveNs, + resolveTxt, + resolveSrv, + resolvePtr, + resolveNaptr, + resolveSoa, + resolve, + reverse, +}; diff --git a/ext/node/polyfills/internal/dns/utils.ts b/ext/node/polyfills/internal/dns/utils.ts new file mode 100644 index 00000000000000..0afd106170e2b2 --- /dev/null +++ b/ext/node/polyfills/internal/dns/utils.ts @@ -0,0 +1,456 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. +// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. + +import { getOptionValue } from "internal:deno_node/polyfills/internal/options.ts"; +import { emitWarning } from "internal:deno_node/polyfills/process.ts"; +import { + AI_ADDRCONFIG, + AI_ALL, + AI_V4MAPPED, +} from "internal:deno_node/polyfills/internal_binding/ares.ts"; +import { + ChannelWrap, + strerror, +} from "internal:deno_node/polyfills/internal_binding/cares_wrap.ts"; +import { + ERR_DNS_SET_SERVERS_FAILED, + ERR_INVALID_ARG_VALUE, + ERR_INVALID_IP_ADDRESS, +} from "internal:deno_node/polyfills/internal/errors.ts"; +import type { ErrnoException } from "internal:deno_node/polyfills/internal/errors.ts"; +import { + validateArray, + validateInt32, + validateOneOf, + validateString, +} from "internal:deno_node/polyfills/internal/validators.mjs"; +import { isIP } from "internal:deno_node/polyfills/internal/net.ts"; + +export interface LookupOptions { + family?: number | undefined; + hints?: number | undefined; + all?: boolean | undefined; + verbatim?: boolean | undefined; +} + +export interface LookupOneOptions extends LookupOptions { + all?: false | undefined; +} + +export interface LookupAllOptions extends LookupOptions { + all: true; +} + +export interface LookupAddress { + address: string | null; + family: number; +} + +export function isLookupOptions( + options: unknown, +): options is LookupOptions | undefined { + return typeof options === "object" || typeof options === "undefined"; +} + +export function isLookupCallback( + options: unknown, +): options is (...args: unknown[]) => void { + return typeof options === "function"; +} + +export function isFamily(options: unknown): options is number { + return typeof options === "number"; +} + +export interface ResolveOptions { + ttl?: boolean; +} + +export interface ResolveWithTtlOptions extends ResolveOptions { + ttl: true; +} + +export interface RecordWithTtl { + address: string; + ttl: number; +} + +export interface AnyARecord extends RecordWithTtl { + type: "A"; +} + +export interface AnyAaaaRecord extends RecordWithTtl { + type: "AAAA"; +} + +export interface CaaRecord { + critial: number; + issue?: string | undefined; + issuewild?: string | undefined; + iodef?: string | undefined; + contactemail?: string | undefined; + contactphone?: string | undefined; +} + +export interface MxRecord { + priority: number; + exchange: string; +} + +export interface AnyMxRecord extends MxRecord { + type: "MX"; +} + +export interface NaptrRecord { + flags: string; + service: string; + regexp: string; + replacement: string; + order: number; + preference: number; +} + +export interface AnyNaptrRecord extends NaptrRecord { + type: "NAPTR"; +} + +export interface SoaRecord { + nsname: string; + hostmaster: string; + serial: number; + refresh: number; + retry: number; + expire: number; + minttl: number; +} + +export interface AnySoaRecord extends SoaRecord { + type: "SOA"; +} + +export interface SrvRecord { + priority: number; + weight: number; + port: number; + name: string; +} + +export interface AnySrvRecord extends SrvRecord { + type: "SRV"; +} + +export interface AnyTxtRecord { + type: "TXT"; + entries: string[]; +} + +export interface AnyNsRecord { + type: "NS"; + value: string; +} + +export interface AnyPtrRecord { + type: "PTR"; + value: string; +} + +export interface AnyCnameRecord { + type: "CNAME"; + value: string; +} + +export type AnyRecord = + | AnyARecord + | AnyAaaaRecord + | AnyCnameRecord + | AnyMxRecord + | AnyNaptrRecord + | AnyNsRecord + | AnyPtrRecord + | AnySoaRecord + | AnySrvRecord + | AnyTxtRecord; + +export type Records = + | string[] + | AnyRecord[] + | MxRecord[] + | NaptrRecord[] + | SoaRecord + | SrvRecord[] + | string[]; + +export type ResolveCallback = ( + err: ErrnoException | null, + addresses: Records, +) => void; + +export function isResolveCallback( + callback: unknown, +): callback is ResolveCallback { + return typeof callback === "function"; +} + +const IANA_DNS_PORT = 53; +const IPv6RE = /^\[([^[\]]*)\]/; +const addrSplitRE = /(^.+?)(?::(\d+))?$/; + +export function validateTimeout(options?: { timeout?: number }) { + const { timeout = -1 } = { ...options }; + validateInt32(timeout, "options.timeout", -1, 2 ** 31 - 1); + return timeout; +} + +export function validateTries(options?: { tries?: number }) { + const { tries = 4 } = { ...options }; + validateInt32(tries, "options.tries", 1, 2 ** 31 - 1); + return tries; +} + +export interface ResolverOptions { + timeout?: number | undefined; + /** + * @default 4 + */ + tries?: number; +} + +/** + * An independent resolver for DNS requests. + * + * Creating a new resolver uses the default server settings. Setting + * the servers used for a resolver using `resolver.setServers()` does not affect + * other resolvers: + * + * ```js + * const { Resolver } = require('dns'); + * const resolver = new Resolver(); + * resolver.setServers(['4.4.4.4']); + * + * // This request will use the server at 4.4.4.4, independent of global settings. + * resolver.resolve4('example.org', (err, addresses) => { + * // ... + * }); + * ``` + * + * The following methods from the `dns` module are available: + * + * - `resolver.getServers()` + * - `resolver.resolve()` + * - `resolver.resolve4()` + * - `resolver.resolve6()` + * - `resolver.resolveAny()` + * - `resolver.resolveCaa()` + * - `resolver.resolveCname()` + * - `resolver.resolveMx()` + * - `resolver.resolveNaptr()` + * - `resolver.resolveNs()` + * - `resolver.resolvePtr()` + * - `resolver.resolveSoa()` + * - `resolver.resolveSrv()` + * - `resolver.resolveTxt()` + * - `resolver.reverse()` + * - `resolver.setServers()` + */ +export class Resolver { + _handle!: ChannelWrap; + + constructor(options?: ResolverOptions) { + const timeout = validateTimeout(options); + const tries = validateTries(options); + this._handle = new ChannelWrap(timeout, tries); + } + + cancel() { + this._handle.cancel(); + } + + getServers(): string[] { + return this._handle.getServers().map((val: [string, number]) => { + if (!val[1] || val[1] === IANA_DNS_PORT) { + return val[0]; + } + + const host = isIP(val[0]) === 6 ? `[${val[0]}]` : val[0]; + return `${host}:${val[1]}`; + }); + } + + setServers(servers: ReadonlyArray) { + validateArray(servers, "servers"); + + // Cache the original servers because in the event of an error while + // setting the servers, c-ares won't have any servers available for + // resolution. + const orig = this._handle.getServers(); + const newSet: [number, string, number][] = []; + + servers.forEach((serv, index) => { + validateString(serv, `servers[${index}]`); + let ipVersion = isIP(serv); + + if (ipVersion !== 0) { + return newSet.push([ipVersion, serv, IANA_DNS_PORT]); + } + + const match = serv.match(IPv6RE); + + // Check for an IPv6 in brackets. + if (match) { + ipVersion = isIP(match[1]); + + if (ipVersion !== 0) { + const port = Number.parseInt(serv.replace(addrSplitRE, "$2")) || + IANA_DNS_PORT; + + return newSet.push([ipVersion, match[1], port]); + } + } + + // addr::port + const addrSplitMatch = serv.match(addrSplitRE); + + if (addrSplitMatch) { + const hostIP = addrSplitMatch[1]; + const port = addrSplitMatch[2] || `${IANA_DNS_PORT}`; + + ipVersion = isIP(hostIP); + + if (ipVersion !== 0) { + return newSet.push([ipVersion, hostIP, Number.parseInt(port)]); + } + } + + throw new ERR_INVALID_IP_ADDRESS(serv); + }); + + const errorNumber = this._handle.setServers(newSet); + + if (errorNumber !== 0) { + // Reset the servers to the old servers, because ares probably unset them. + this._handle.setServers(orig.join(",")); + const err = strerror(errorNumber); + + throw new ERR_DNS_SET_SERVERS_FAILED(err, servers.toString()); + } + } + + /** + * The resolver instance will send its requests from the specified IP address. + * This allows programs to specify outbound interfaces when used on multi-homed + * systems. + * + * If a v4 or v6 address is not specified, it is set to the default, and the + * operating system will choose a local address automatically. + * + * The resolver will use the v4 local address when making requests to IPv4 DNS + * servers, and the v6 local address when making requests to IPv6 DNS servers. + * The `rrtype` of resolution requests has no impact on the local address used. + * + * @param [ipv4='0.0.0.0'] A string representation of an IPv4 address. + * @param [ipv6='::0'] A string representation of an IPv6 address. + */ + setLocalAddress(ipv4: string, ipv6?: string) { + validateString(ipv4, "ipv4"); + + if (ipv6 !== undefined) { + validateString(ipv6, "ipv6"); + } + + this._handle.setLocalAddress(ipv4, ipv6); + } +} + +let defaultResolver = new Resolver(); + +export function getDefaultResolver(): Resolver { + return defaultResolver; +} + +export function setDefaultResolver(resolver: T) { + defaultResolver = resolver; +} + +export function validateHints(hints: number) { + if ((hints & ~(AI_ADDRCONFIG | AI_ALL | AI_V4MAPPED)) !== 0) { + throw new ERR_INVALID_ARG_VALUE("hints", hints, "is invalid"); + } +} + +let invalidHostnameWarningEmitted = false; + +export function emitInvalidHostnameWarning(hostname: string) { + if (invalidHostnameWarningEmitted) { + return; + } + + invalidHostnameWarningEmitted = true; + + emitWarning( + `The provided hostname "${hostname}" is not a valid ` + + "hostname, and is supported in the dns module solely for compatibility.", + "DeprecationWarning", + "DEP0118", + ); +} + +let dnsOrder = getOptionValue("--dns-result-order") || "ipv4first"; + +export function getDefaultVerbatim() { + switch (dnsOrder) { + case "verbatim": { + return true; + } + case "ipv4first": { + return false; + } + default: { + return false; + } + } +} + +/** + * Set the default value of `verbatim` in `lookup` and `dnsPromises.lookup()`. + * The value could be: + * + * - `ipv4first`: sets default `verbatim` `false`. + * - `verbatim`: sets default `verbatim` `true`. + * + * The default is `ipv4first` and `setDefaultResultOrder` have higher + * priority than `--dns-result-order`. When using `worker threads`, + * `setDefaultResultOrder` from the main thread won't affect the default + * dns orders in workers. + * + * @param order must be `'ipv4first'` or `'verbatim'`. + */ +export function setDefaultResultOrder(order: "ipv4first" | "verbatim") { + validateOneOf(order, "dnsOrder", ["verbatim", "ipv4first"]); + dnsOrder = order; +} + +export function defaultResolverSetServers(servers: string[]) { + const resolver = new Resolver(); + + resolver.setServers(servers); + setDefaultResolver(resolver); +} diff --git a/ext/node/polyfills/internal/dtrace.ts b/ext/node/polyfills/internal/dtrace.ts new file mode 100644 index 00000000000000..5a44703366a0de --- /dev/null +++ b/ext/node/polyfills/internal/dtrace.ts @@ -0,0 +1,41 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. +// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. + +// deno-lint-ignore-file + +const { + DTRACE_HTTP_CLIENT_REQUEST = (..._args: any[]) => {}, + DTRACE_HTTP_CLIENT_RESPONSE = (..._args: any[]) => {}, + DTRACE_HTTP_SERVER_REQUEST = (..._args: any[]) => {}, + DTRACE_HTTP_SERVER_RESPONSE = (..._args: any[]) => {}, + DTRACE_NET_SERVER_CONNECTION = (..._args: any[]) => {}, + DTRACE_NET_STREAM_END = (..._args: any[]) => {}, +} = {}; + +export { + DTRACE_HTTP_CLIENT_REQUEST, + DTRACE_HTTP_CLIENT_RESPONSE, + DTRACE_HTTP_SERVER_REQUEST, + DTRACE_HTTP_SERVER_RESPONSE, + DTRACE_NET_SERVER_CONNECTION, + DTRACE_NET_STREAM_END, +}; diff --git a/ext/node/polyfills/internal/error_codes.ts b/ext/node/polyfills/internal/error_codes.ts new file mode 100644 index 00000000000000..6af88bb112e539 --- /dev/null +++ b/ext/node/polyfills/internal/error_codes.ts @@ -0,0 +1,7 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. + +// Lazily initializes the error classes in this object. +// This trick is necessary for avoiding circular dendencies between +// `internal/errors` and other modules. +// deno-lint-ignore no-explicit-any +export const codes: Record = {}; diff --git a/ext/node/polyfills/internal/errors.ts b/ext/node/polyfills/internal/errors.ts new file mode 100644 index 00000000000000..67f729f8d392ca --- /dev/null +++ b/ext/node/polyfills/internal/errors.ts @@ -0,0 +1,2864 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. +// Copyright Node.js contributors. All rights reserved. MIT License. +/** NOT IMPLEMENTED + * ERR_MANIFEST_ASSERT_INTEGRITY + * ERR_QUICSESSION_VERSION_NEGOTIATION + * ERR_REQUIRE_ESM + * ERR_TLS_CERT_ALTNAME_INVALID + * ERR_WORKER_INVALID_EXEC_ARGV + * ERR_WORKER_PATH + * ERR_QUIC_ERROR + * ERR_SYSTEM_ERROR //System error, shouldn't ever happen inside Deno + * ERR_TTY_INIT_FAILED //System error, shouldn't ever happen inside Deno + * ERR_INVALID_PACKAGE_CONFIG // package.json stuff, probably useless + */ + +import { inspect } from "internal:deno_node/polyfills/internal/util/inspect.mjs"; +import { codes } from "internal:deno_node/polyfills/internal/error_codes.ts"; +import { + codeMap, + errorMap, + mapSysErrnoToUvErrno, +} from "internal:deno_node/polyfills/internal_binding/uv.ts"; +import { assert } from "internal:deno_node/polyfills/_util/asserts.ts"; +import { isWindows } from "internal:deno_node/polyfills/_util/os.ts"; +import { os as osConstants } from "internal:deno_node/polyfills/internal_binding/constants.ts"; +import { hideStackFrames } from "internal:deno_node/polyfills/internal/hide_stack_frames.ts"; +import { getSystemErrorName } from "internal:deno_node/polyfills/_utils.ts"; + +export { errorMap }; + +const kIsNodeError = Symbol("kIsNodeError"); + +/** + * @see https://github.com/nodejs/node/blob/f3eb224/lib/internal/errors.js + */ +const classRegExp = /^([A-Z][a-z0-9]*)+$/; + +/** + * @see https://github.com/nodejs/node/blob/f3eb224/lib/internal/errors.js + * @description Sorted by a rough estimate on most frequently used entries. + */ +const kTypes = [ + "string", + "function", + "number", + "object", + // Accept 'Function' and 'Object' as alternative to the lower cased version. + "Function", + "Object", + "boolean", + "bigint", + "symbol", +]; + +// Node uses an AbortError that isn't exactly the same as the DOMException +// to make usage of the error in userland and readable-stream easier. +// It is a regular error with `.code` and `.name`. +export class AbortError extends Error { + code: string; + + constructor(message = "The operation was aborted", options?: ErrorOptions) { + if (options !== undefined && typeof options !== "object") { + throw new codes.ERR_INVALID_ARG_TYPE("options", "Object", options); + } + super(message, options); + this.code = "ABORT_ERR"; + this.name = "AbortError"; + } +} + +let maxStackErrorName: string | undefined; +let maxStackErrorMessage: string | undefined; +/** + * Returns true if `err.name` and `err.message` are equal to engine-specific + * values indicating max call stack size has been exceeded. + * "Maximum call stack size exceeded" in V8. + */ +export function isStackOverflowError(err: Error): boolean { + if (maxStackErrorMessage === undefined) { + try { + // deno-lint-ignore no-inner-declarations + function overflowStack() { + overflowStack(); + } + overflowStack(); + // deno-lint-ignore no-explicit-any + } catch (err: any) { + maxStackErrorMessage = err.message; + maxStackErrorName = err.name; + } + } + + return err && err.name === maxStackErrorName && + err.message === maxStackErrorMessage; +} + +function addNumericalSeparator(val: string) { + let res = ""; + let i = val.length; + const start = val[0] === "-" ? 1 : 0; + for (; i >= start + 4; i -= 3) { + res = `_${val.slice(i - 3, i)}${res}`; + } + return `${val.slice(0, i)}${res}`; +} + +const captureLargerStackTrace = hideStackFrames( + function captureLargerStackTrace(err) { + // @ts-ignore this function is not available in lib.dom.d.ts + Error.captureStackTrace(err); + + return err; + }, +); + +export interface ErrnoException extends Error { + errno?: number; + code?: string; + path?: string; + syscall?: string; + spawnargs?: string[]; +} + +/** + * This creates an error compatible with errors produced in the C++ + * This function should replace the deprecated + * `exceptionWithHostPort()` function. + * + * @param err A libuv error number + * @param syscall + * @param address + * @param port + * @return The error. + */ +export const uvExceptionWithHostPort = hideStackFrames( + function uvExceptionWithHostPort( + err: number, + syscall: string, + address?: string | null, + port?: number | null, + ) { + const { 0: code, 1: uvmsg } = uvErrmapGet(err) || uvUnmappedError; + const message = `${syscall} ${code}: ${uvmsg}`; + let details = ""; + + if (port && port > 0) { + details = ` ${address}:${port}`; + } else if (address) { + details = ` ${address}`; + } + + // deno-lint-ignore no-explicit-any + const ex: any = new Error(`${message}${details}`); + ex.code = code; + ex.errno = err; + ex.syscall = syscall; + ex.address = address; + + if (port) { + ex.port = port; + } + + return captureLargerStackTrace(ex); + }, +); + +/** + * This used to be `util._errnoException()`. + * + * @param err A libuv error number + * @param syscall + * @param original + * @return A `ErrnoException` + */ +export const errnoException = hideStackFrames(function errnoException( + err, + syscall, + original?, +): ErrnoException { + const code = getSystemErrorName(err); + const message = original + ? `${syscall} ${code} ${original}` + : `${syscall} ${code}`; + + // deno-lint-ignore no-explicit-any + const ex: any = new Error(message); + ex.errno = err; + ex.code = code; + ex.syscall = syscall; + + return captureLargerStackTrace(ex); +}); + +function uvErrmapGet(name: number) { + return errorMap.get(name); +} + +const uvUnmappedError = ["UNKNOWN", "unknown error"]; + +/** + * This creates an error compatible with errors produced in the C++ + * function UVException using a context object with data assembled in C++. + * The goal is to migrate them to ERR_* errors later when compatibility is + * not a concern. + * + * @param ctx + * @return The error. + */ +export const uvException = hideStackFrames(function uvException(ctx) { + const { 0: code, 1: uvmsg } = uvErrmapGet(ctx.errno) || uvUnmappedError; + + let message = `${code}: ${ctx.message || uvmsg}, ${ctx.syscall}`; + + let path; + let dest; + + if (ctx.path) { + path = ctx.path.toString(); + message += ` '${path}'`; + } + if (ctx.dest) { + dest = ctx.dest.toString(); + message += ` -> '${dest}'`; + } + + // deno-lint-ignore no-explicit-any + const err: any = new Error(message); + + for (const prop of Object.keys(ctx)) { + if (prop === "message" || prop === "path" || prop === "dest") { + continue; + } + + err[prop] = ctx[prop]; + } + + err.code = code; + + if (path) { + err.path = path; + } + + if (dest) { + err.dest = dest; + } + + return captureLargerStackTrace(err); +}); + +/** + * Deprecated, new function is `uvExceptionWithHostPort()` + * New function added the error description directly + * from C++. this method for backwards compatibility + * @param err A libuv error number + * @param syscall + * @param address + * @param port + * @param additional + */ +export const exceptionWithHostPort = hideStackFrames( + function exceptionWithHostPort( + err: number, + syscall: string, + address: string, + port: number, + additional?: string, + ) { + const code = getSystemErrorName(err); + let details = ""; + + if (port && port > 0) { + details = ` ${address}:${port}`; + } else if (address) { + details = ` ${address}`; + } + + if (additional) { + details += ` - Local (${additional})`; + } + + // deno-lint-ignore no-explicit-any + const ex: any = new Error(`${syscall} ${code}${details}`); + ex.errno = err; + ex.code = code; + ex.syscall = syscall; + ex.address = address; + + if (port) { + ex.port = port; + } + + return captureLargerStackTrace(ex); + }, +); + +/** + * @param code A libuv error number or a c-ares error code + * @param syscall + * @param hostname + */ +export const dnsException = hideStackFrames(function (code, syscall, hostname) { + let errno; + + // If `code` is of type number, it is a libuv error number, else it is a + // c-ares error code. + if (typeof code === "number") { + errno = code; + // ENOTFOUND is not a proper POSIX error, but this error has been in place + // long enough that it's not practical to remove it. + if ( + code === codeMap.get("EAI_NODATA") || + code === codeMap.get("EAI_NONAME") + ) { + code = "ENOTFOUND"; // Fabricated error name. + } else { + code = getSystemErrorName(code); + } + } + + const message = `${syscall} ${code}${hostname ? ` ${hostname}` : ""}`; + + // deno-lint-ignore no-explicit-any + const ex: any = new Error(message); + ex.errno = errno; + ex.code = code; + ex.syscall = syscall; + + if (hostname) { + ex.hostname = hostname; + } + + return captureLargerStackTrace(ex); +}); + +/** + * All error instances in Node have additional methods and properties + * This export class is meant to be extended by these instances abstracting native JS error instances + */ +export class NodeErrorAbstraction extends Error { + code: string; + + constructor(name: string, code: string, message: string) { + super(message); + this.code = code; + this.name = name; + //This number changes depending on the name of this class + //20 characters as of now + this.stack = this.stack && `${name} [${this.code}]${this.stack.slice(20)}`; + } + + override toString() { + return `${this.name} [${this.code}]: ${this.message}`; + } +} + +export class NodeError extends NodeErrorAbstraction { + constructor(code: string, message: string) { + super(Error.prototype.name, code, message); + } +} + +export class NodeSyntaxError extends NodeErrorAbstraction + implements SyntaxError { + constructor(code: string, message: string) { + super(SyntaxError.prototype.name, code, message); + Object.setPrototypeOf(this, SyntaxError.prototype); + this.toString = function () { + return `${this.name} [${this.code}]: ${this.message}`; + }; + } +} + +export class NodeRangeError extends NodeErrorAbstraction { + constructor(code: string, message: string) { + super(RangeError.prototype.name, code, message); + Object.setPrototypeOf(this, RangeError.prototype); + this.toString = function () { + return `${this.name} [${this.code}]: ${this.message}`; + }; + } +} + +export class NodeTypeError extends NodeErrorAbstraction implements TypeError { + constructor(code: string, message: string) { + super(TypeError.prototype.name, code, message); + Object.setPrototypeOf(this, TypeError.prototype); + this.toString = function () { + return `${this.name} [${this.code}]: ${this.message}`; + }; + } +} + +export class NodeURIError extends NodeErrorAbstraction implements URIError { + constructor(code: string, message: string) { + super(URIError.prototype.name, code, message); + Object.setPrototypeOf(this, URIError.prototype); + this.toString = function () { + return `${this.name} [${this.code}]: ${this.message}`; + }; + } +} + +export interface NodeSystemErrorCtx { + code: string; + syscall: string; + message: string; + errno: number; + path?: string; + dest?: string; +} +// A specialized Error that includes an additional info property with +// additional information about the error condition. +// It has the properties present in a UVException but with a custom error +// message followed by the uv error code and uv error message. +// It also has its own error code with the original uv error context put into +// `err.info`. +// The context passed into this error must have .code, .syscall and .message, +// and may have .path and .dest. +class NodeSystemError extends NodeErrorAbstraction { + constructor(key: string, context: NodeSystemErrorCtx, msgPrefix: string) { + let message = `${msgPrefix}: ${context.syscall} returned ` + + `${context.code} (${context.message})`; + + if (context.path !== undefined) { + message += ` ${context.path}`; + } + if (context.dest !== undefined) { + message += ` => ${context.dest}`; + } + + super("SystemError", key, message); + + captureLargerStackTrace(this); + + Object.defineProperties(this, { + [kIsNodeError]: { + value: true, + enumerable: false, + writable: false, + configurable: true, + }, + info: { + value: context, + enumerable: true, + configurable: true, + writable: false, + }, + errno: { + get() { + return context.errno; + }, + set: (value) => { + context.errno = value; + }, + enumerable: true, + configurable: true, + }, + syscall: { + get() { + return context.syscall; + }, + set: (value) => { + context.syscall = value; + }, + enumerable: true, + configurable: true, + }, + }); + + if (context.path !== undefined) { + Object.defineProperty(this, "path", { + get() { + return context.path; + }, + set: (value) => { + context.path = value; + }, + enumerable: true, + configurable: true, + }); + } + + if (context.dest !== undefined) { + Object.defineProperty(this, "dest", { + get() { + return context.dest; + }, + set: (value) => { + context.dest = value; + }, + enumerable: true, + configurable: true, + }); + } + } + + override toString() { + return `${this.name} [${this.code}]: ${this.message}`; + } +} + +function makeSystemErrorWithCode(key: string, msgPrfix: string) { + return class NodeError extends NodeSystemError { + constructor(ctx: NodeSystemErrorCtx) { + super(key, ctx, msgPrfix); + } + }; +} + +export const ERR_FS_EISDIR = makeSystemErrorWithCode( + "ERR_FS_EISDIR", + "Path is a directory", +); + +function createInvalidArgType( + name: string, + expected: string | string[], +): string { + // https://github.com/nodejs/node/blob/f3eb224/lib/internal/errors.js#L1037-L1087 + expected = Array.isArray(expected) ? expected : [expected]; + let msg = "The "; + if (name.endsWith(" argument")) { + // For cases like 'first argument' + msg += `${name} `; + } else { + const type = name.includes(".") ? "property" : "argument"; + msg += `"${name}" ${type} `; + } + msg += "must be "; + + const types = []; + const instances = []; + const other = []; + for (const value of expected) { + if (kTypes.includes(value)) { + types.push(value.toLocaleLowerCase()); + } else if (classRegExp.test(value)) { + instances.push(value); + } else { + other.push(value); + } + } + + // Special handle `object` in case other instances are allowed to outline + // the differences between each other. + if (instances.length > 0) { + const pos = types.indexOf("object"); + if (pos !== -1) { + types.splice(pos, 1); + instances.push("Object"); + } + } + + if (types.length > 0) { + if (types.length > 2) { + const last = types.pop(); + msg += `one of type ${types.join(", ")}, or ${last}`; + } else if (types.length === 2) { + msg += `one of type ${types[0]} or ${types[1]}`; + } else { + msg += `of type ${types[0]}`; + } + if (instances.length > 0 || other.length > 0) { + msg += " or "; + } + } + + if (instances.length > 0) { + if (instances.length > 2) { + const last = instances.pop(); + msg += `an instance of ${instances.join(", ")}, or ${last}`; + } else { + msg += `an instance of ${instances[0]}`; + if (instances.length === 2) { + msg += ` or ${instances[1]}`; + } + } + if (other.length > 0) { + msg += " or "; + } + } + + if (other.length > 0) { + if (other.length > 2) { + const last = other.pop(); + msg += `one of ${other.join(", ")}, or ${last}`; + } else if (other.length === 2) { + msg += `one of ${other[0]} or ${other[1]}`; + } else { + if (other[0].toLowerCase() !== other[0]) { + msg += "an "; + } + msg += `${other[0]}`; + } + } + + return msg; +} + +export class ERR_INVALID_ARG_TYPE_RANGE extends NodeRangeError { + constructor(name: string, expected: string | string[], actual: unknown) { + const msg = createInvalidArgType(name, expected); + + super("ERR_INVALID_ARG_TYPE", `${msg}.${invalidArgTypeHelper(actual)}`); + } +} + +export class ERR_INVALID_ARG_TYPE extends NodeTypeError { + constructor(name: string, expected: string | string[], actual: unknown) { + const msg = createInvalidArgType(name, expected); + + super("ERR_INVALID_ARG_TYPE", `${msg}.${invalidArgTypeHelper(actual)}`); + } + + static RangeError = ERR_INVALID_ARG_TYPE_RANGE; +} + +export class ERR_INVALID_ARG_VALUE_RANGE extends NodeRangeError { + constructor(name: string, value: unknown, reason: string = "is invalid") { + const type = name.includes(".") ? "property" : "argument"; + const inspected = inspect(value); + + super( + "ERR_INVALID_ARG_VALUE", + `The ${type} '${name}' ${reason}. Received ${inspected}`, + ); + } +} + +export class ERR_INVALID_ARG_VALUE extends NodeTypeError { + constructor(name: string, value: unknown, reason: string = "is invalid") { + const type = name.includes(".") ? "property" : "argument"; + const inspected = inspect(value); + + super( + "ERR_INVALID_ARG_VALUE", + `The ${type} '${name}' ${reason}. Received ${inspected}`, + ); + } + + static RangeError = ERR_INVALID_ARG_VALUE_RANGE; +} + +// A helper function to simplify checking for ERR_INVALID_ARG_TYPE output. +// deno-lint-ignore no-explicit-any +function invalidArgTypeHelper(input: any) { + if (input == null) { + return ` Received ${input}`; + } + if (typeof input === "function" && input.name) { + return ` Received function ${input.name}`; + } + if (typeof input === "object") { + if (input.constructor && input.constructor.name) { + return ` Received an instance of ${input.constructor.name}`; + } + return ` Received ${inspect(input, { depth: -1 })}`; + } + let inspected = inspect(input, { colors: false }); + if (inspected.length > 25) { + inspected = `${inspected.slice(0, 25)}...`; + } + return ` Received type ${typeof input} (${inspected})`; +} + +export class ERR_OUT_OF_RANGE extends RangeError { + code = "ERR_OUT_OF_RANGE"; + + constructor( + str: string, + range: string, + input: unknown, + replaceDefaultBoolean = false, + ) { + assert(range, 'Missing "range" argument'); + let msg = replaceDefaultBoolean + ? str + : `The value of "${str}" is out of range.`; + let received; + if (Number.isInteger(input) && Math.abs(input as number) > 2 ** 32) { + received = addNumericalSeparator(String(input)); + } else if (typeof input === "bigint") { + received = String(input); + if (input > 2n ** 32n || input < -(2n ** 32n)) { + received = addNumericalSeparator(received); + } + received += "n"; + } else { + received = inspect(input); + } + msg += ` It must be ${range}. Received ${received}`; + + super(msg); + + const { name } = this; + // Add the error code to the name to include it in the stack trace. + this.name = `${name} [${this.code}]`; + // Access the stack to generate the error message including the error code from the name. + this.stack; + // Reset the name to the actual name. + this.name = name; + } +} + +export class ERR_AMBIGUOUS_ARGUMENT extends NodeTypeError { + constructor(x: string, y: string) { + super("ERR_AMBIGUOUS_ARGUMENT", `The "${x}" argument is ambiguous. ${y}`); + } +} + +export class ERR_ARG_NOT_ITERABLE extends NodeTypeError { + constructor(x: string) { + super("ERR_ARG_NOT_ITERABLE", `${x} must be iterable`); + } +} + +export class ERR_ASSERTION extends NodeError { + constructor(x: string) { + super("ERR_ASSERTION", `${x}`); + } +} + +export class ERR_ASYNC_CALLBACK extends NodeTypeError { + constructor(x: string) { + super("ERR_ASYNC_CALLBACK", `${x} must be a function`); + } +} + +export class ERR_ASYNC_TYPE extends NodeTypeError { + constructor(x: string) { + super("ERR_ASYNC_TYPE", `Invalid name for async "type": ${x}`); + } +} + +export class ERR_BROTLI_INVALID_PARAM extends NodeRangeError { + constructor(x: string) { + super("ERR_BROTLI_INVALID_PARAM", `${x} is not a valid Brotli parameter`); + } +} + +export class ERR_BUFFER_OUT_OF_BOUNDS extends NodeRangeError { + constructor(name?: string) { + super( + "ERR_BUFFER_OUT_OF_BOUNDS", + name + ? `"${name}" is outside of buffer bounds` + : "Attempt to access memory outside buffer bounds", + ); + } +} + +export class ERR_BUFFER_TOO_LARGE extends NodeRangeError { + constructor(x: string) { + super( + "ERR_BUFFER_TOO_LARGE", + `Cannot create a Buffer larger than ${x} bytes`, + ); + } +} + +export class ERR_CANNOT_WATCH_SIGINT extends NodeError { + constructor() { + super("ERR_CANNOT_WATCH_SIGINT", "Cannot watch for SIGINT signals"); + } +} + +export class ERR_CHILD_CLOSED_BEFORE_REPLY extends NodeError { + constructor() { + super( + "ERR_CHILD_CLOSED_BEFORE_REPLY", + "Child closed before reply received", + ); + } +} + +export class ERR_CHILD_PROCESS_IPC_REQUIRED extends NodeError { + constructor(x: string) { + super( + "ERR_CHILD_PROCESS_IPC_REQUIRED", + `Forked processes must have an IPC channel, missing value 'ipc' in ${x}`, + ); + } +} + +export class ERR_CHILD_PROCESS_STDIO_MAXBUFFER extends NodeRangeError { + constructor(x: string) { + super( + "ERR_CHILD_PROCESS_STDIO_MAXBUFFER", + `${x} maxBuffer length exceeded`, + ); + } +} + +export class ERR_CONSOLE_WRITABLE_STREAM extends NodeTypeError { + constructor(x: string) { + super( + "ERR_CONSOLE_WRITABLE_STREAM", + `Console expects a writable stream instance for ${x}`, + ); + } +} + +export class ERR_CONTEXT_NOT_INITIALIZED extends NodeError { + constructor() { + super("ERR_CONTEXT_NOT_INITIALIZED", "context used is not initialized"); + } +} + +export class ERR_CPU_USAGE extends NodeError { + constructor(x: string) { + super("ERR_CPU_USAGE", `Unable to obtain cpu usage ${x}`); + } +} + +export class ERR_CRYPTO_CUSTOM_ENGINE_NOT_SUPPORTED extends NodeError { + constructor() { + super( + "ERR_CRYPTO_CUSTOM_ENGINE_NOT_SUPPORTED", + "Custom engines not supported by this OpenSSL", + ); + } +} + +export class ERR_CRYPTO_ECDH_INVALID_FORMAT extends NodeTypeError { + constructor(x: string) { + super("ERR_CRYPTO_ECDH_INVALID_FORMAT", `Invalid ECDH format: ${x}`); + } +} + +export class ERR_CRYPTO_ECDH_INVALID_PUBLIC_KEY extends NodeError { + constructor() { + super( + "ERR_CRYPTO_ECDH_INVALID_PUBLIC_KEY", + "Public key is not valid for specified curve", + ); + } +} + +export class ERR_CRYPTO_ENGINE_UNKNOWN extends NodeError { + constructor(x: string) { + super("ERR_CRYPTO_ENGINE_UNKNOWN", `Engine "${x}" was not found`); + } +} + +export class ERR_CRYPTO_FIPS_FORCED extends NodeError { + constructor() { + super( + "ERR_CRYPTO_FIPS_FORCED", + "Cannot set FIPS mode, it was forced with --force-fips at startup.", + ); + } +} + +export class ERR_CRYPTO_FIPS_UNAVAILABLE extends NodeError { + constructor() { + super( + "ERR_CRYPTO_FIPS_UNAVAILABLE", + "Cannot set FIPS mode in a non-FIPS build.", + ); + } +} + +export class ERR_CRYPTO_HASH_FINALIZED extends NodeError { + constructor() { + super("ERR_CRYPTO_HASH_FINALIZED", "Digest already called"); + } +} + +export class ERR_CRYPTO_HASH_UPDATE_FAILED extends NodeError { + constructor() { + super("ERR_CRYPTO_HASH_UPDATE_FAILED", "Hash update failed"); + } +} + +export class ERR_CRYPTO_INCOMPATIBLE_KEY extends NodeError { + constructor(x: string, y: string) { + super("ERR_CRYPTO_INCOMPATIBLE_KEY", `Incompatible ${x}: ${y}`); + } +} + +export class ERR_CRYPTO_INCOMPATIBLE_KEY_OPTIONS extends NodeError { + constructor(x: string, y: string) { + super( + "ERR_CRYPTO_INCOMPATIBLE_KEY_OPTIONS", + `The selected key encoding ${x} ${y}.`, + ); + } +} + +export class ERR_CRYPTO_INVALID_DIGEST extends NodeTypeError { + constructor(x: string) { + super("ERR_CRYPTO_INVALID_DIGEST", `Invalid digest: ${x}`); + } +} + +export class ERR_CRYPTO_INVALID_KEY_OBJECT_TYPE extends NodeTypeError { + constructor(x: string, y: string) { + super( + "ERR_CRYPTO_INVALID_KEY_OBJECT_TYPE", + `Invalid key object type ${x}, expected ${y}.`, + ); + } +} + +export class ERR_CRYPTO_INVALID_STATE extends NodeError { + constructor(x: string) { + super("ERR_CRYPTO_INVALID_STATE", `Invalid state for operation ${x}`); + } +} + +export class ERR_CRYPTO_PBKDF2_ERROR extends NodeError { + constructor() { + super("ERR_CRYPTO_PBKDF2_ERROR", "PBKDF2 error"); + } +} + +export class ERR_CRYPTO_SCRYPT_INVALID_PARAMETER extends NodeError { + constructor() { + super("ERR_CRYPTO_SCRYPT_INVALID_PARAMETER", "Invalid scrypt parameter"); + } +} + +export class ERR_CRYPTO_SCRYPT_NOT_SUPPORTED extends NodeError { + constructor() { + super("ERR_CRYPTO_SCRYPT_NOT_SUPPORTED", "Scrypt algorithm not supported"); + } +} + +export class ERR_CRYPTO_SIGN_KEY_REQUIRED extends NodeError { + constructor() { + super("ERR_CRYPTO_SIGN_KEY_REQUIRED", "No key provided to sign"); + } +} + +export class ERR_DIR_CLOSED extends NodeError { + constructor() { + super("ERR_DIR_CLOSED", "Directory handle was closed"); + } +} + +export class ERR_DIR_CONCURRENT_OPERATION extends NodeError { + constructor() { + super( + "ERR_DIR_CONCURRENT_OPERATION", + "Cannot do synchronous work on directory handle with concurrent asynchronous operations", + ); + } +} + +export class ERR_DNS_SET_SERVERS_FAILED extends NodeError { + constructor(x: string, y: string) { + super( + "ERR_DNS_SET_SERVERS_FAILED", + `c-ares failed to set servers: "${x}" [${y}]`, + ); + } +} + +export class ERR_DOMAIN_CALLBACK_NOT_AVAILABLE extends NodeError { + constructor() { + super( + "ERR_DOMAIN_CALLBACK_NOT_AVAILABLE", + "A callback was registered through " + + "process.setUncaughtExceptionCaptureCallback(), which is mutually " + + "exclusive with using the `domain` module", + ); + } +} + +export class ERR_DOMAIN_CANNOT_SET_UNCAUGHT_EXCEPTION_CAPTURE + extends NodeError { + constructor() { + super( + "ERR_DOMAIN_CANNOT_SET_UNCAUGHT_EXCEPTION_CAPTURE", + "The `domain` module is in use, which is mutually exclusive with calling " + + "process.setUncaughtExceptionCaptureCallback()", + ); + } +} + +export class ERR_ENCODING_INVALID_ENCODED_DATA extends NodeErrorAbstraction + implements TypeError { + errno: number; + constructor(encoding: string, ret: number) { + super( + TypeError.prototype.name, + "ERR_ENCODING_INVALID_ENCODED_DATA", + `The encoded data was not valid for encoding ${encoding}`, + ); + Object.setPrototypeOf(this, TypeError.prototype); + + this.errno = ret; + } +} + +export class ERR_ENCODING_NOT_SUPPORTED extends NodeRangeError { + constructor(x: string) { + super("ERR_ENCODING_NOT_SUPPORTED", `The "${x}" encoding is not supported`); + } +} +export class ERR_EVAL_ESM_CANNOT_PRINT extends NodeError { + constructor() { + super("ERR_EVAL_ESM_CANNOT_PRINT", `--print cannot be used with ESM input`); + } +} +export class ERR_EVENT_RECURSION extends NodeError { + constructor(x: string) { + super( + "ERR_EVENT_RECURSION", + `The event "${x}" is already being dispatched`, + ); + } +} +export class ERR_FEATURE_UNAVAILABLE_ON_PLATFORM extends NodeTypeError { + constructor(x: string) { + super( + "ERR_FEATURE_UNAVAILABLE_ON_PLATFORM", + `The feature ${x} is unavailable on the current platform, which is being used to run Node.js`, + ); + } +} +export class ERR_FS_FILE_TOO_LARGE extends NodeRangeError { + constructor(x: string) { + super("ERR_FS_FILE_TOO_LARGE", `File size (${x}) is greater than 2 GB`); + } +} +export class ERR_FS_INVALID_SYMLINK_TYPE extends NodeError { + constructor(x: string) { + super( + "ERR_FS_INVALID_SYMLINK_TYPE", + `Symlink type must be one of "dir", "file", or "junction". Received "${x}"`, + ); + } +} +export class ERR_HTTP2_ALTSVC_INVALID_ORIGIN extends NodeTypeError { + constructor() { + super( + "ERR_HTTP2_ALTSVC_INVALID_ORIGIN", + `HTTP/2 ALTSVC frames require a valid origin`, + ); + } +} +export class ERR_HTTP2_ALTSVC_LENGTH extends NodeTypeError { + constructor() { + super( + "ERR_HTTP2_ALTSVC_LENGTH", + `HTTP/2 ALTSVC frames are limited to 16382 bytes`, + ); + } +} +export class ERR_HTTP2_CONNECT_AUTHORITY extends NodeError { + constructor() { + super( + "ERR_HTTP2_CONNECT_AUTHORITY", + `:authority header is required for CONNECT requests`, + ); + } +} +export class ERR_HTTP2_CONNECT_PATH extends NodeError { + constructor() { + super( + "ERR_HTTP2_CONNECT_PATH", + `The :path header is forbidden for CONNECT requests`, + ); + } +} +export class ERR_HTTP2_CONNECT_SCHEME extends NodeError { + constructor() { + super( + "ERR_HTTP2_CONNECT_SCHEME", + `The :scheme header is forbidden for CONNECT requests`, + ); + } +} +export class ERR_HTTP2_GOAWAY_SESSION extends NodeError { + constructor() { + super( + "ERR_HTTP2_GOAWAY_SESSION", + `New streams cannot be created after receiving a GOAWAY`, + ); + } +} +export class ERR_HTTP2_HEADERS_AFTER_RESPOND extends NodeError { + constructor() { + super( + "ERR_HTTP2_HEADERS_AFTER_RESPOND", + `Cannot specify additional headers after response initiated`, + ); + } +} +export class ERR_HTTP2_HEADERS_SENT extends NodeError { + constructor() { + super("ERR_HTTP2_HEADERS_SENT", `Response has already been initiated.`); + } +} +export class ERR_HTTP2_HEADER_SINGLE_VALUE extends NodeTypeError { + constructor(x: string) { + super( + "ERR_HTTP2_HEADER_SINGLE_VALUE", + `Header field "${x}" must only have a single value`, + ); + } +} +export class ERR_HTTP2_INFO_STATUS_NOT_ALLOWED extends NodeRangeError { + constructor() { + super( + "ERR_HTTP2_INFO_STATUS_NOT_ALLOWED", + `Informational status codes cannot be used`, + ); + } +} +export class ERR_HTTP2_INVALID_CONNECTION_HEADERS extends NodeTypeError { + constructor(x: string) { + super( + "ERR_HTTP2_INVALID_CONNECTION_HEADERS", + `HTTP/1 Connection specific headers are forbidden: "${x}"`, + ); + } +} +export class ERR_HTTP2_INVALID_HEADER_VALUE extends NodeTypeError { + constructor(x: string, y: string) { + super( + "ERR_HTTP2_INVALID_HEADER_VALUE", + `Invalid value "${x}" for header "${y}"`, + ); + } +} +export class ERR_HTTP2_INVALID_INFO_STATUS extends NodeRangeError { + constructor(x: string) { + super( + "ERR_HTTP2_INVALID_INFO_STATUS", + `Invalid informational status code: ${x}`, + ); + } +} +export class ERR_HTTP2_INVALID_ORIGIN extends NodeTypeError { + constructor() { + super( + "ERR_HTTP2_INVALID_ORIGIN", + `HTTP/2 ORIGIN frames require a valid origin`, + ); + } +} +export class ERR_HTTP2_INVALID_PACKED_SETTINGS_LENGTH extends NodeRangeError { + constructor() { + super( + "ERR_HTTP2_INVALID_PACKED_SETTINGS_LENGTH", + `Packed settings length must be a multiple of six`, + ); + } +} +export class ERR_HTTP2_INVALID_PSEUDOHEADER extends NodeTypeError { + constructor(x: string) { + super( + "ERR_HTTP2_INVALID_PSEUDOHEADER", + `"${x}" is an invalid pseudoheader or is used incorrectly`, + ); + } +} +export class ERR_HTTP2_INVALID_SESSION extends NodeError { + constructor() { + super("ERR_HTTP2_INVALID_SESSION", `The session has been destroyed`); + } +} +export class ERR_HTTP2_INVALID_STREAM extends NodeError { + constructor() { + super("ERR_HTTP2_INVALID_STREAM", `The stream has been destroyed`); + } +} +export class ERR_HTTP2_MAX_PENDING_SETTINGS_ACK extends NodeError { + constructor() { + super( + "ERR_HTTP2_MAX_PENDING_SETTINGS_ACK", + `Maximum number of pending settings acknowledgements`, + ); + } +} +export class ERR_HTTP2_NESTED_PUSH extends NodeError { + constructor() { + super( + "ERR_HTTP2_NESTED_PUSH", + `A push stream cannot initiate another push stream.`, + ); + } +} +export class ERR_HTTP2_NO_SOCKET_MANIPULATION extends NodeError { + constructor() { + super( + "ERR_HTTP2_NO_SOCKET_MANIPULATION", + `HTTP/2 sockets should not be directly manipulated (e.g. read and written)`, + ); + } +} +export class ERR_HTTP2_ORIGIN_LENGTH extends NodeTypeError { + constructor() { + super( + "ERR_HTTP2_ORIGIN_LENGTH", + `HTTP/2 ORIGIN frames are limited to 16382 bytes`, + ); + } +} +export class ERR_HTTP2_OUT_OF_STREAMS extends NodeError { + constructor() { + super( + "ERR_HTTP2_OUT_OF_STREAMS", + `No stream ID is available because maximum stream ID has been reached`, + ); + } +} +export class ERR_HTTP2_PAYLOAD_FORBIDDEN extends NodeError { + constructor(x: string) { + super( + "ERR_HTTP2_PAYLOAD_FORBIDDEN", + `Responses with ${x} status must not have a payload`, + ); + } +} +export class ERR_HTTP2_PING_CANCEL extends NodeError { + constructor() { + super("ERR_HTTP2_PING_CANCEL", `HTTP2 ping cancelled`); + } +} +export class ERR_HTTP2_PING_LENGTH extends NodeRangeError { + constructor() { + super("ERR_HTTP2_PING_LENGTH", `HTTP2 ping payload must be 8 bytes`); + } +} +export class ERR_HTTP2_PSEUDOHEADER_NOT_ALLOWED extends NodeTypeError { + constructor() { + super( + "ERR_HTTP2_PSEUDOHEADER_NOT_ALLOWED", + `Cannot set HTTP/2 pseudo-headers`, + ); + } +} +export class ERR_HTTP2_PUSH_DISABLED extends NodeError { + constructor() { + super("ERR_HTTP2_PUSH_DISABLED", `HTTP/2 client has disabled push streams`); + } +} +export class ERR_HTTP2_SEND_FILE extends NodeError { + constructor() { + super("ERR_HTTP2_SEND_FILE", `Directories cannot be sent`); + } +} +export class ERR_HTTP2_SEND_FILE_NOSEEK extends NodeError { + constructor() { + super( + "ERR_HTTP2_SEND_FILE_NOSEEK", + `Offset or length can only be specified for regular files`, + ); + } +} +export class ERR_HTTP2_SESSION_ERROR extends NodeError { + constructor(x: string) { + super("ERR_HTTP2_SESSION_ERROR", `Session closed with error code ${x}`); + } +} +export class ERR_HTTP2_SETTINGS_CANCEL extends NodeError { + constructor() { + super("ERR_HTTP2_SETTINGS_CANCEL", `HTTP2 session settings canceled`); + } +} +export class ERR_HTTP2_SOCKET_BOUND extends NodeError { + constructor() { + super( + "ERR_HTTP2_SOCKET_BOUND", + `The socket is already bound to an Http2Session`, + ); + } +} +export class ERR_HTTP2_SOCKET_UNBOUND extends NodeError { + constructor() { + super( + "ERR_HTTP2_SOCKET_UNBOUND", + `The socket has been disconnected from the Http2Session`, + ); + } +} +export class ERR_HTTP2_STATUS_101 extends NodeError { + constructor() { + super( + "ERR_HTTP2_STATUS_101", + `HTTP status code 101 (Switching Protocols) is forbidden in HTTP/2`, + ); + } +} +export class ERR_HTTP2_STATUS_INVALID extends NodeRangeError { + constructor(x: string) { + super("ERR_HTTP2_STATUS_INVALID", `Invalid status code: ${x}`); + } +} +export class ERR_HTTP2_STREAM_ERROR extends NodeError { + constructor(x: string) { + super("ERR_HTTP2_STREAM_ERROR", `Stream closed with error code ${x}`); + } +} +export class ERR_HTTP2_STREAM_SELF_DEPENDENCY extends NodeError { + constructor() { + super( + "ERR_HTTP2_STREAM_SELF_DEPENDENCY", + `A stream cannot depend on itself`, + ); + } +} +export class ERR_HTTP2_TRAILERS_ALREADY_SENT extends NodeError { + constructor() { + super( + "ERR_HTTP2_TRAILERS_ALREADY_SENT", + `Trailing headers have already been sent`, + ); + } +} +export class ERR_HTTP2_TRAILERS_NOT_READY extends NodeError { + constructor() { + super( + "ERR_HTTP2_TRAILERS_NOT_READY", + `Trailing headers cannot be sent until after the wantTrailers event is emitted`, + ); + } +} +export class ERR_HTTP2_UNSUPPORTED_PROTOCOL extends NodeError { + constructor(x: string) { + super("ERR_HTTP2_UNSUPPORTED_PROTOCOL", `protocol "${x}" is unsupported.`); + } +} +export class ERR_HTTP_HEADERS_SENT extends NodeError { + constructor(x: string) { + super( + "ERR_HTTP_HEADERS_SENT", + `Cannot ${x} headers after they are sent to the client`, + ); + } +} +export class ERR_HTTP_INVALID_HEADER_VALUE extends NodeTypeError { + constructor(x: string, y: string) { + super( + "ERR_HTTP_INVALID_HEADER_VALUE", + `Invalid value "${x}" for header "${y}"`, + ); + } +} +export class ERR_HTTP_INVALID_STATUS_CODE extends NodeRangeError { + constructor(x: string) { + super("ERR_HTTP_INVALID_STATUS_CODE", `Invalid status code: ${x}`); + } +} +export class ERR_HTTP_SOCKET_ENCODING extends NodeError { + constructor() { + super( + "ERR_HTTP_SOCKET_ENCODING", + `Changing the socket encoding is not allowed per RFC7230 Section 3.`, + ); + } +} +export class ERR_HTTP_TRAILER_INVALID extends NodeError { + constructor() { + super( + "ERR_HTTP_TRAILER_INVALID", + `Trailers are invalid with this transfer encoding`, + ); + } +} +export class ERR_INCOMPATIBLE_OPTION_PAIR extends NodeTypeError { + constructor(x: string, y: string) { + super( + "ERR_INCOMPATIBLE_OPTION_PAIR", + `Option "${x}" cannot be used in combination with option "${y}"`, + ); + } +} +export class ERR_INPUT_TYPE_NOT_ALLOWED extends NodeError { + constructor() { + super( + "ERR_INPUT_TYPE_NOT_ALLOWED", + `--input-type can only be used with string input via --eval, --print, or STDIN`, + ); + } +} +export class ERR_INSPECTOR_ALREADY_ACTIVATED extends NodeError { + constructor() { + super( + "ERR_INSPECTOR_ALREADY_ACTIVATED", + `Inspector is already activated. Close it with inspector.close() before activating it again.`, + ); + } +} +export class ERR_INSPECTOR_ALREADY_CONNECTED extends NodeError { + constructor(x: string) { + super("ERR_INSPECTOR_ALREADY_CONNECTED", `${x} is already connected`); + } +} +export class ERR_INSPECTOR_CLOSED extends NodeError { + constructor() { + super("ERR_INSPECTOR_CLOSED", `Session was closed`); + } +} +export class ERR_INSPECTOR_COMMAND extends NodeError { + constructor(x: number, y: string) { + super("ERR_INSPECTOR_COMMAND", `Inspector error ${x}: ${y}`); + } +} +export class ERR_INSPECTOR_NOT_ACTIVE extends NodeError { + constructor() { + super("ERR_INSPECTOR_NOT_ACTIVE", `Inspector is not active`); + } +} +export class ERR_INSPECTOR_NOT_AVAILABLE extends NodeError { + constructor() { + super("ERR_INSPECTOR_NOT_AVAILABLE", `Inspector is not available`); + } +} +export class ERR_INSPECTOR_NOT_CONNECTED extends NodeError { + constructor() { + super("ERR_INSPECTOR_NOT_CONNECTED", `Session is not connected`); + } +} +export class ERR_INSPECTOR_NOT_WORKER extends NodeError { + constructor() { + super("ERR_INSPECTOR_NOT_WORKER", `Current thread is not a worker`); + } +} +export class ERR_INVALID_ASYNC_ID extends NodeRangeError { + constructor(x: string, y: string | number) { + super("ERR_INVALID_ASYNC_ID", `Invalid ${x} value: ${y}`); + } +} +export class ERR_INVALID_BUFFER_SIZE extends NodeRangeError { + constructor(x: string) { + super("ERR_INVALID_BUFFER_SIZE", `Buffer size must be a multiple of ${x}`); + } +} +export class ERR_INVALID_CURSOR_POS extends NodeTypeError { + constructor() { + super( + "ERR_INVALID_CURSOR_POS", + `Cannot set cursor row without setting its column`, + ); + } +} +export class ERR_INVALID_FD extends NodeRangeError { + constructor(x: string) { + super("ERR_INVALID_FD", `"fd" must be a positive integer: ${x}`); + } +} +export class ERR_INVALID_FD_TYPE extends NodeTypeError { + constructor(x: string) { + super("ERR_INVALID_FD_TYPE", `Unsupported fd type: ${x}`); + } +} +export class ERR_INVALID_FILE_URL_HOST extends NodeTypeError { + constructor(x: string) { + super( + "ERR_INVALID_FILE_URL_HOST", + `File URL host must be "localhost" or empty on ${x}`, + ); + } +} +export class ERR_INVALID_FILE_URL_PATH extends NodeTypeError { + constructor(x: string) { + super("ERR_INVALID_FILE_URL_PATH", `File URL path ${x}`); + } +} +export class ERR_INVALID_HANDLE_TYPE extends NodeTypeError { + constructor() { + super("ERR_INVALID_HANDLE_TYPE", `This handle type cannot be sent`); + } +} +export class ERR_INVALID_HTTP_TOKEN extends NodeTypeError { + constructor(x: string, y: string) { + super("ERR_INVALID_HTTP_TOKEN", `${x} must be a valid HTTP token ["${y}"]`); + } +} +export class ERR_INVALID_IP_ADDRESS extends NodeTypeError { + constructor(x: string) { + super("ERR_INVALID_IP_ADDRESS", `Invalid IP address: ${x}`); + } +} +export class ERR_INVALID_OPT_VALUE_ENCODING extends NodeTypeError { + constructor(x: string) { + super( + "ERR_INVALID_OPT_VALUE_ENCODING", + `The value "${x}" is invalid for option "encoding"`, + ); + } +} +export class ERR_INVALID_PERFORMANCE_MARK extends NodeError { + constructor(x: string) { + super( + "ERR_INVALID_PERFORMANCE_MARK", + `The "${x}" performance mark has not been set`, + ); + } +} +export class ERR_INVALID_PROTOCOL extends NodeTypeError { + constructor(x: string, y: string) { + super( + "ERR_INVALID_PROTOCOL", + `Protocol "${x}" not supported. Expected "${y}"`, + ); + } +} +export class ERR_INVALID_REPL_EVAL_CONFIG extends NodeTypeError { + constructor() { + super( + "ERR_INVALID_REPL_EVAL_CONFIG", + `Cannot specify both "breakEvalOnSigint" and "eval" for REPL`, + ); + } +} +export class ERR_INVALID_REPL_INPUT extends NodeTypeError { + constructor(x: string) { + super("ERR_INVALID_REPL_INPUT", `${x}`); + } +} +export class ERR_INVALID_SYNC_FORK_INPUT extends NodeTypeError { + constructor(x: string) { + super( + "ERR_INVALID_SYNC_FORK_INPUT", + `Asynchronous forks do not support Buffer, TypedArray, DataView or string input: ${x}`, + ); + } +} +export class ERR_INVALID_THIS extends NodeTypeError { + constructor(x: string) { + super("ERR_INVALID_THIS", `Value of "this" must be of type ${x}`); + } +} +export class ERR_INVALID_TUPLE extends NodeTypeError { + constructor(x: string, y: string) { + super("ERR_INVALID_TUPLE", `${x} must be an iterable ${y} tuple`); + } +} +export class ERR_INVALID_URI extends NodeURIError { + constructor() { + super("ERR_INVALID_URI", `URI malformed`); + } +} +export class ERR_IPC_CHANNEL_CLOSED extends NodeError { + constructor() { + super("ERR_IPC_CHANNEL_CLOSED", `Channel closed`); + } +} +export class ERR_IPC_DISCONNECTED extends NodeError { + constructor() { + super("ERR_IPC_DISCONNECTED", `IPC channel is already disconnected`); + } +} +export class ERR_IPC_ONE_PIPE extends NodeError { + constructor() { + super("ERR_IPC_ONE_PIPE", `Child process can have only one IPC pipe`); + } +} +export class ERR_IPC_SYNC_FORK extends NodeError { + constructor() { + super("ERR_IPC_SYNC_FORK", `IPC cannot be used with synchronous forks`); + } +} +export class ERR_MANIFEST_DEPENDENCY_MISSING extends NodeError { + constructor(x: string, y: string) { + super( + "ERR_MANIFEST_DEPENDENCY_MISSING", + `Manifest resource ${x} does not list ${y} as a dependency specifier`, + ); + } +} +export class ERR_MANIFEST_INTEGRITY_MISMATCH extends NodeSyntaxError { + constructor(x: string) { + super( + "ERR_MANIFEST_INTEGRITY_MISMATCH", + `Manifest resource ${x} has multiple entries but integrity lists do not match`, + ); + } +} +export class ERR_MANIFEST_INVALID_RESOURCE_FIELD extends NodeTypeError { + constructor(x: string, y: string) { + super( + "ERR_MANIFEST_INVALID_RESOURCE_FIELD", + `Manifest resource ${x} has invalid property value for ${y}`, + ); + } +} +export class ERR_MANIFEST_TDZ extends NodeError { + constructor() { + super("ERR_MANIFEST_TDZ", `Manifest initialization has not yet run`); + } +} +export class ERR_MANIFEST_UNKNOWN_ONERROR extends NodeSyntaxError { + constructor(x: string) { + super( + "ERR_MANIFEST_UNKNOWN_ONERROR", + `Manifest specified unknown error behavior "${x}".`, + ); + } +} +export class ERR_METHOD_NOT_IMPLEMENTED extends NodeError { + constructor(x: string) { + super("ERR_METHOD_NOT_IMPLEMENTED", `The ${x} method is not implemented`); + } +} +export class ERR_MISSING_ARGS extends NodeTypeError { + constructor(...args: (string | string[])[]) { + let msg = "The "; + + const len = args.length; + + const wrap = (a: unknown) => `"${a}"`; + + args = args.map((a) => + Array.isArray(a) ? a.map(wrap).join(" or ") : wrap(a) + ); + + switch (len) { + case 1: + msg += `${args[0]} argument`; + break; + case 2: + msg += `${args[0]} and ${args[1]} arguments`; + break; + default: + msg += args.slice(0, len - 1).join(", "); + msg += `, and ${args[len - 1]} arguments`; + break; + } + + super("ERR_MISSING_ARGS", `${msg} must be specified`); + } +} +export class ERR_MISSING_OPTION extends NodeTypeError { + constructor(x: string) { + super("ERR_MISSING_OPTION", `${x} is required`); + } +} +export class ERR_MULTIPLE_CALLBACK extends NodeError { + constructor() { + super("ERR_MULTIPLE_CALLBACK", `Callback called multiple times`); + } +} +export class ERR_NAPI_CONS_FUNCTION extends NodeTypeError { + constructor() { + super("ERR_NAPI_CONS_FUNCTION", `Constructor must be a function`); + } +} +export class ERR_NAPI_INVALID_DATAVIEW_ARGS extends NodeRangeError { + constructor() { + super( + "ERR_NAPI_INVALID_DATAVIEW_ARGS", + `byte_offset + byte_length should be less than or equal to the size in bytes of the array passed in`, + ); + } +} +export class ERR_NAPI_INVALID_TYPEDARRAY_ALIGNMENT extends NodeRangeError { + constructor(x: string, y: string) { + super( + "ERR_NAPI_INVALID_TYPEDARRAY_ALIGNMENT", + `start offset of ${x} should be a multiple of ${y}`, + ); + } +} +export class ERR_NAPI_INVALID_TYPEDARRAY_LENGTH extends NodeRangeError { + constructor() { + super("ERR_NAPI_INVALID_TYPEDARRAY_LENGTH", `Invalid typed array length`); + } +} +export class ERR_NO_CRYPTO extends NodeError { + constructor() { + super( + "ERR_NO_CRYPTO", + `Node.js is not compiled with OpenSSL crypto support`, + ); + } +} +export class ERR_NO_ICU extends NodeTypeError { + constructor(x: string) { + super( + "ERR_NO_ICU", + `${x} is not supported on Node.js compiled without ICU`, + ); + } +} +export class ERR_QUICCLIENTSESSION_FAILED extends NodeError { + constructor(x: string) { + super( + "ERR_QUICCLIENTSESSION_FAILED", + `Failed to create a new QuicClientSession: ${x}`, + ); + } +} +export class ERR_QUICCLIENTSESSION_FAILED_SETSOCKET extends NodeError { + constructor() { + super( + "ERR_QUICCLIENTSESSION_FAILED_SETSOCKET", + `Failed to set the QuicSocket`, + ); + } +} +export class ERR_QUICSESSION_DESTROYED extends NodeError { + constructor(x: string) { + super( + "ERR_QUICSESSION_DESTROYED", + `Cannot call ${x} after a QuicSession has been destroyed`, + ); + } +} +export class ERR_QUICSESSION_INVALID_DCID extends NodeError { + constructor(x: string) { + super("ERR_QUICSESSION_INVALID_DCID", `Invalid DCID value: ${x}`); + } +} +export class ERR_QUICSESSION_UPDATEKEY extends NodeError { + constructor() { + super("ERR_QUICSESSION_UPDATEKEY", `Unable to update QuicSession keys`); + } +} +export class ERR_QUICSOCKET_DESTROYED extends NodeError { + constructor(x: string) { + super( + "ERR_QUICSOCKET_DESTROYED", + `Cannot call ${x} after a QuicSocket has been destroyed`, + ); + } +} +export class ERR_QUICSOCKET_INVALID_STATELESS_RESET_SECRET_LENGTH + extends NodeError { + constructor() { + super( + "ERR_QUICSOCKET_INVALID_STATELESS_RESET_SECRET_LENGTH", + `The stateResetToken must be exactly 16-bytes in length`, + ); + } +} +export class ERR_QUICSOCKET_LISTENING extends NodeError { + constructor() { + super("ERR_QUICSOCKET_LISTENING", `This QuicSocket is already listening`); + } +} +export class ERR_QUICSOCKET_UNBOUND extends NodeError { + constructor(x: string) { + super( + "ERR_QUICSOCKET_UNBOUND", + `Cannot call ${x} before a QuicSocket has been bound`, + ); + } +} +export class ERR_QUICSTREAM_DESTROYED extends NodeError { + constructor(x: string) { + super( + "ERR_QUICSTREAM_DESTROYED", + `Cannot call ${x} after a QuicStream has been destroyed`, + ); + } +} +export class ERR_QUICSTREAM_INVALID_PUSH extends NodeError { + constructor() { + super( + "ERR_QUICSTREAM_INVALID_PUSH", + `Push streams are only supported on client-initiated, bidirectional streams`, + ); + } +} +export class ERR_QUICSTREAM_OPEN_FAILED extends NodeError { + constructor() { + super("ERR_QUICSTREAM_OPEN_FAILED", `Opening a new QuicStream failed`); + } +} +export class ERR_QUICSTREAM_UNSUPPORTED_PUSH extends NodeError { + constructor() { + super( + "ERR_QUICSTREAM_UNSUPPORTED_PUSH", + `Push streams are not supported on this QuicSession`, + ); + } +} +export class ERR_QUIC_TLS13_REQUIRED extends NodeError { + constructor() { + super("ERR_QUIC_TLS13_REQUIRED", `QUIC requires TLS version 1.3`); + } +} +export class ERR_SCRIPT_EXECUTION_INTERRUPTED extends NodeError { + constructor() { + super( + "ERR_SCRIPT_EXECUTION_INTERRUPTED", + "Script execution was interrupted by `SIGINT`", + ); + } +} +export class ERR_SERVER_ALREADY_LISTEN extends NodeError { + constructor() { + super( + "ERR_SERVER_ALREADY_LISTEN", + `Listen method has been called more than once without closing.`, + ); + } +} +export class ERR_SERVER_NOT_RUNNING extends NodeError { + constructor() { + super("ERR_SERVER_NOT_RUNNING", `Server is not running.`); + } +} +export class ERR_SOCKET_ALREADY_BOUND extends NodeError { + constructor() { + super("ERR_SOCKET_ALREADY_BOUND", `Socket is already bound`); + } +} +export class ERR_SOCKET_BAD_BUFFER_SIZE extends NodeTypeError { + constructor() { + super( + "ERR_SOCKET_BAD_BUFFER_SIZE", + `Buffer size must be a positive integer`, + ); + } +} +export class ERR_SOCKET_BAD_PORT extends NodeRangeError { + constructor(name: string, port: unknown, allowZero = true) { + assert( + typeof allowZero === "boolean", + "The 'allowZero' argument must be of type boolean.", + ); + + const operator = allowZero ? ">=" : ">"; + + super( + "ERR_SOCKET_BAD_PORT", + `${name} should be ${operator} 0 and < 65536. Received ${port}.`, + ); + } +} +export class ERR_SOCKET_BAD_TYPE extends NodeTypeError { + constructor() { + super( + "ERR_SOCKET_BAD_TYPE", + `Bad socket type specified. Valid types are: udp4, udp6`, + ); + } +} +export class ERR_SOCKET_BUFFER_SIZE extends NodeSystemError { + constructor(ctx: NodeSystemErrorCtx) { + super("ERR_SOCKET_BUFFER_SIZE", ctx, "Could not get or set buffer size"); + } +} +export class ERR_SOCKET_CLOSED extends NodeError { + constructor() { + super("ERR_SOCKET_CLOSED", `Socket is closed`); + } +} +export class ERR_SOCKET_DGRAM_IS_CONNECTED extends NodeError { + constructor() { + super("ERR_SOCKET_DGRAM_IS_CONNECTED", `Already connected`); + } +} +export class ERR_SOCKET_DGRAM_NOT_CONNECTED extends NodeError { + constructor() { + super("ERR_SOCKET_DGRAM_NOT_CONNECTED", `Not connected`); + } +} +export class ERR_SOCKET_DGRAM_NOT_RUNNING extends NodeError { + constructor() { + super("ERR_SOCKET_DGRAM_NOT_RUNNING", `Not running`); + } +} +export class ERR_SRI_PARSE extends NodeSyntaxError { + constructor(name: string, char: string, position: number) { + super( + "ERR_SRI_PARSE", + `Subresource Integrity string ${name} had an unexpected ${char} at position ${position}`, + ); + } +} +export class ERR_STREAM_ALREADY_FINISHED extends NodeError { + constructor(x: string) { + super( + "ERR_STREAM_ALREADY_FINISHED", + `Cannot call ${x} after a stream was finished`, + ); + } +} +export class ERR_STREAM_CANNOT_PIPE extends NodeError { + constructor() { + super("ERR_STREAM_CANNOT_PIPE", `Cannot pipe, not readable`); + } +} +export class ERR_STREAM_DESTROYED extends NodeError { + constructor(x: string) { + super( + "ERR_STREAM_DESTROYED", + `Cannot call ${x} after a stream was destroyed`, + ); + } +} +export class ERR_STREAM_NULL_VALUES extends NodeTypeError { + constructor() { + super("ERR_STREAM_NULL_VALUES", `May not write null values to stream`); + } +} +export class ERR_STREAM_PREMATURE_CLOSE extends NodeError { + constructor() { + super("ERR_STREAM_PREMATURE_CLOSE", `Premature close`); + } +} +export class ERR_STREAM_PUSH_AFTER_EOF extends NodeError { + constructor() { + super("ERR_STREAM_PUSH_AFTER_EOF", `stream.push() after EOF`); + } +} +export class ERR_STREAM_UNSHIFT_AFTER_END_EVENT extends NodeError { + constructor() { + super( + "ERR_STREAM_UNSHIFT_AFTER_END_EVENT", + `stream.unshift() after end event`, + ); + } +} +export class ERR_STREAM_WRAP extends NodeError { + constructor() { + super( + "ERR_STREAM_WRAP", + `Stream has StringDecoder set or is in objectMode`, + ); + } +} +export class ERR_STREAM_WRITE_AFTER_END extends NodeError { + constructor() { + super("ERR_STREAM_WRITE_AFTER_END", `write after end`); + } +} +export class ERR_SYNTHETIC extends NodeError { + constructor() { + super("ERR_SYNTHETIC", `JavaScript Callstack`); + } +} +export class ERR_TLS_CERT_ALTNAME_INVALID extends NodeError { + reason: string; + host: string; + cert: string; + + constructor(reason: string, host: string, cert: string) { + super( + "ERR_TLS_CERT_ALTNAME_INVALID", + `Hostname/IP does not match certificate's altnames: ${reason}`, + ); + this.reason = reason; + this.host = host; + this.cert = cert; + } +} +export class ERR_TLS_DH_PARAM_SIZE extends NodeError { + constructor(x: string) { + super("ERR_TLS_DH_PARAM_SIZE", `DH parameter size ${x} is less than 2048`); + } +} +export class ERR_TLS_HANDSHAKE_TIMEOUT extends NodeError { + constructor() { + super("ERR_TLS_HANDSHAKE_TIMEOUT", `TLS handshake timeout`); + } +} +export class ERR_TLS_INVALID_CONTEXT extends NodeTypeError { + constructor(x: string) { + super("ERR_TLS_INVALID_CONTEXT", `${x} must be a SecureContext`); + } +} +export class ERR_TLS_INVALID_STATE extends NodeError { + constructor() { + super( + "ERR_TLS_INVALID_STATE", + `TLS socket connection must be securely established`, + ); + } +} +export class ERR_TLS_INVALID_PROTOCOL_VERSION extends NodeTypeError { + constructor(protocol: string, x: string) { + super( + "ERR_TLS_INVALID_PROTOCOL_VERSION", + `${protocol} is not a valid ${x} TLS protocol version`, + ); + } +} +export class ERR_TLS_PROTOCOL_VERSION_CONFLICT extends NodeTypeError { + constructor(prevProtocol: string, protocol: string) { + super( + "ERR_TLS_PROTOCOL_VERSION_CONFLICT", + `TLS protocol version ${prevProtocol} conflicts with secureProtocol ${protocol}`, + ); + } +} +export class ERR_TLS_RENEGOTIATION_DISABLED extends NodeError { + constructor() { + super( + "ERR_TLS_RENEGOTIATION_DISABLED", + `TLS session renegotiation disabled for this socket`, + ); + } +} +export class ERR_TLS_REQUIRED_SERVER_NAME extends NodeError { + constructor() { + super( + "ERR_TLS_REQUIRED_SERVER_NAME", + `"servername" is required parameter for Server.addContext`, + ); + } +} +export class ERR_TLS_SESSION_ATTACK extends NodeError { + constructor() { + super( + "ERR_TLS_SESSION_ATTACK", + `TLS session renegotiation attack detected`, + ); + } +} +export class ERR_TLS_SNI_FROM_SERVER extends NodeError { + constructor() { + super( + "ERR_TLS_SNI_FROM_SERVER", + `Cannot issue SNI from a TLS server-side socket`, + ); + } +} +export class ERR_TRACE_EVENTS_CATEGORY_REQUIRED extends NodeTypeError { + constructor() { + super( + "ERR_TRACE_EVENTS_CATEGORY_REQUIRED", + `At least one category is required`, + ); + } +} +export class ERR_TRACE_EVENTS_UNAVAILABLE extends NodeError { + constructor() { + super("ERR_TRACE_EVENTS_UNAVAILABLE", `Trace events are unavailable`); + } +} +export class ERR_UNAVAILABLE_DURING_EXIT extends NodeError { + constructor() { + super( + "ERR_UNAVAILABLE_DURING_EXIT", + `Cannot call function in process exit handler`, + ); + } +} +export class ERR_UNCAUGHT_EXCEPTION_CAPTURE_ALREADY_SET extends NodeError { + constructor() { + super( + "ERR_UNCAUGHT_EXCEPTION_CAPTURE_ALREADY_SET", + "`process.setupUncaughtExceptionCapture()` was called while a capture callback was already active", + ); + } +} +export class ERR_UNESCAPED_CHARACTERS extends NodeTypeError { + constructor(x: string) { + super("ERR_UNESCAPED_CHARACTERS", `${x} contains unescaped characters`); + } +} +export class ERR_UNHANDLED_ERROR extends NodeError { + constructor(x: string) { + super("ERR_UNHANDLED_ERROR", `Unhandled error. (${x})`); + } +} +export class ERR_UNKNOWN_BUILTIN_MODULE extends NodeError { + constructor(x: string) { + super("ERR_UNKNOWN_BUILTIN_MODULE", `No such built-in module: ${x}`); + } +} +export class ERR_UNKNOWN_CREDENTIAL extends NodeError { + constructor(x: string, y: string) { + super("ERR_UNKNOWN_CREDENTIAL", `${x} identifier does not exist: ${y}`); + } +} +export class ERR_UNKNOWN_ENCODING extends NodeTypeError { + constructor(x: string) { + super("ERR_UNKNOWN_ENCODING", `Unknown encoding: ${x}`); + } +} +export class ERR_UNKNOWN_FILE_EXTENSION extends NodeTypeError { + constructor(x: string, y: string) { + super( + "ERR_UNKNOWN_FILE_EXTENSION", + `Unknown file extension "${x}" for ${y}`, + ); + } +} +export class ERR_UNKNOWN_MODULE_FORMAT extends NodeRangeError { + constructor(x: string) { + super("ERR_UNKNOWN_MODULE_FORMAT", `Unknown module format: ${x}`); + } +} +export class ERR_UNKNOWN_SIGNAL extends NodeTypeError { + constructor(x: string) { + super("ERR_UNKNOWN_SIGNAL", `Unknown signal: ${x}`); + } +} +export class ERR_UNSUPPORTED_DIR_IMPORT extends NodeError { + constructor(x: string, y: string) { + super( + "ERR_UNSUPPORTED_DIR_IMPORT", + `Directory import '${x}' is not supported resolving ES modules, imported from ${y}`, + ); + } +} +export class ERR_UNSUPPORTED_ESM_URL_SCHEME extends NodeError { + constructor() { + super( + "ERR_UNSUPPORTED_ESM_URL_SCHEME", + `Only file and data URLs are supported by the default ESM loader`, + ); + } +} +export class ERR_USE_AFTER_CLOSE extends NodeError { + constructor(x: string) { + super( + "ERR_USE_AFTER_CLOSE", + `${x} was closed`, + ); + } +} +export class ERR_V8BREAKITERATOR extends NodeError { + constructor() { + super( + "ERR_V8BREAKITERATOR", + `Full ICU data not installed. See https://github.com/nodejs/node/wiki/Intl`, + ); + } +} +export class ERR_VALID_PERFORMANCE_ENTRY_TYPE extends NodeError { + constructor() { + super( + "ERR_VALID_PERFORMANCE_ENTRY_TYPE", + `At least one valid performance entry type is required`, + ); + } +} +export class ERR_VM_DYNAMIC_IMPORT_CALLBACK_MISSING extends NodeTypeError { + constructor() { + super( + "ERR_VM_DYNAMIC_IMPORT_CALLBACK_MISSING", + `A dynamic import callback was not specified.`, + ); + } +} +export class ERR_VM_MODULE_ALREADY_LINKED extends NodeError { + constructor() { + super("ERR_VM_MODULE_ALREADY_LINKED", `Module has already been linked`); + } +} +export class ERR_VM_MODULE_CANNOT_CREATE_CACHED_DATA extends NodeError { + constructor() { + super( + "ERR_VM_MODULE_CANNOT_CREATE_CACHED_DATA", + `Cached data cannot be created for a module which has been evaluated`, + ); + } +} +export class ERR_VM_MODULE_DIFFERENT_CONTEXT extends NodeError { + constructor() { + super( + "ERR_VM_MODULE_DIFFERENT_CONTEXT", + `Linked modules must use the same context`, + ); + } +} +export class ERR_VM_MODULE_LINKING_ERRORED extends NodeError { + constructor() { + super( + "ERR_VM_MODULE_LINKING_ERRORED", + `Linking has already failed for the provided module`, + ); + } +} +export class ERR_VM_MODULE_NOT_MODULE extends NodeError { + constructor() { + super( + "ERR_VM_MODULE_NOT_MODULE", + `Provided module is not an instance of Module`, + ); + } +} +export class ERR_VM_MODULE_STATUS extends NodeError { + constructor(x: string) { + super("ERR_VM_MODULE_STATUS", `Module status ${x}`); + } +} +export class ERR_WASI_ALREADY_STARTED extends NodeError { + constructor() { + super("ERR_WASI_ALREADY_STARTED", `WASI instance has already started`); + } +} +export class ERR_WORKER_INIT_FAILED extends NodeError { + constructor(x: string) { + super("ERR_WORKER_INIT_FAILED", `Worker initialization failure: ${x}`); + } +} +export class ERR_WORKER_NOT_RUNNING extends NodeError { + constructor() { + super("ERR_WORKER_NOT_RUNNING", `Worker instance not running`); + } +} +export class ERR_WORKER_OUT_OF_MEMORY extends NodeError { + constructor(x: string) { + super( + "ERR_WORKER_OUT_OF_MEMORY", + `Worker terminated due to reaching memory limit: ${x}`, + ); + } +} +export class ERR_WORKER_UNSERIALIZABLE_ERROR extends NodeError { + constructor() { + super( + "ERR_WORKER_UNSERIALIZABLE_ERROR", + `Serializing an uncaught exception failed`, + ); + } +} +export class ERR_WORKER_UNSUPPORTED_EXTENSION extends NodeTypeError { + constructor(x: string) { + super( + "ERR_WORKER_UNSUPPORTED_EXTENSION", + `The worker script extension must be ".js", ".mjs", or ".cjs". Received "${x}"`, + ); + } +} +export class ERR_WORKER_UNSUPPORTED_OPERATION extends NodeTypeError { + constructor(x: string) { + super( + "ERR_WORKER_UNSUPPORTED_OPERATION", + `${x} is not supported in workers`, + ); + } +} +export class ERR_ZLIB_INITIALIZATION_FAILED extends NodeError { + constructor() { + super("ERR_ZLIB_INITIALIZATION_FAILED", `Initialization failed`); + } +} +export class ERR_FALSY_VALUE_REJECTION extends NodeError { + reason: string; + constructor(reason: string) { + super("ERR_FALSY_VALUE_REJECTION", "Promise was rejected with falsy value"); + this.reason = reason; + } +} +export class ERR_HTTP2_INVALID_SETTING_VALUE extends NodeRangeError { + actual: unknown; + min?: number; + max?: number; + + constructor(name: string, actual: unknown, min?: number, max?: number) { + super( + "ERR_HTTP2_INVALID_SETTING_VALUE", + `Invalid value for setting "${name}": ${actual}`, + ); + this.actual = actual; + if (min !== undefined) { + this.min = min; + this.max = max; + } + } +} +export class ERR_HTTP2_STREAM_CANCEL extends NodeError { + override cause?: Error; + constructor(error: Error) { + super( + "ERR_HTTP2_STREAM_CANCEL", + typeof error.message === "string" + ? `The pending stream has been canceled (caused by: ${error.message})` + : "The pending stream has been canceled", + ); + if (error) { + this.cause = error; + } + } +} + +export class ERR_INVALID_ADDRESS_FAMILY extends NodeRangeError { + host: string; + port: number; + constructor(addressType: string, host: string, port: number) { + super( + "ERR_INVALID_ADDRESS_FAMILY", + `Invalid address family: ${addressType} ${host}:${port}`, + ); + this.host = host; + this.port = port; + } +} + +export class ERR_INVALID_CHAR extends NodeTypeError { + constructor(name: string, field?: string) { + super( + "ERR_INVALID_CHAR", + field + ? `Invalid character in ${name}` + : `Invalid character in ${name} ["${field}"]`, + ); + } +} + +export class ERR_INVALID_OPT_VALUE extends NodeTypeError { + constructor(name: string, value: unknown) { + super( + "ERR_INVALID_OPT_VALUE", + `The value "${value}" is invalid for option "${name}"`, + ); + } +} + +export class ERR_INVALID_RETURN_PROPERTY extends NodeTypeError { + constructor(input: string, name: string, prop: string, value: string) { + super( + "ERR_INVALID_RETURN_PROPERTY", + `Expected a valid ${input} to be returned for the "${prop}" from the "${name}" function but got ${value}.`, + ); + } +} + +// deno-lint-ignore no-explicit-any +function buildReturnPropertyType(value: any) { + if (value && value.constructor && value.constructor.name) { + return `instance of ${value.constructor.name}`; + } else { + return `type ${typeof value}`; + } +} + +export class ERR_INVALID_RETURN_PROPERTY_VALUE extends NodeTypeError { + constructor(input: string, name: string, prop: string, value: unknown) { + super( + "ERR_INVALID_RETURN_PROPERTY_VALUE", + `Expected ${input} to be returned for the "${prop}" from the "${name}" function but got ${ + buildReturnPropertyType( + value, + ) + }.`, + ); + } +} + +export class ERR_INVALID_RETURN_VALUE extends NodeTypeError { + constructor(input: string, name: string, value: unknown) { + super( + "ERR_INVALID_RETURN_VALUE", + `Expected ${input} to be returned from the "${name}" function but got ${ + determineSpecificType( + value, + ) + }.`, + ); + } +} + +export class ERR_INVALID_URL extends NodeTypeError { + input: string; + constructor(input: string) { + super("ERR_INVALID_URL", `Invalid URL: ${input}`); + this.input = input; + } +} + +export class ERR_INVALID_URL_SCHEME extends NodeTypeError { + constructor(expected: string | [string] | [string, string]) { + expected = Array.isArray(expected) ? expected : [expected]; + const res = expected.length === 2 + ? `one of scheme ${expected[0]} or ${expected[1]}` + : `of scheme ${expected[0]}`; + super("ERR_INVALID_URL_SCHEME", `The URL must be ${res}`); + } +} + +export class ERR_MODULE_NOT_FOUND extends NodeError { + constructor(path: string, base: string, type: string = "package") { + super( + "ERR_MODULE_NOT_FOUND", + `Cannot find ${type} '${path}' imported from ${base}`, + ); + } +} + +export class ERR_INVALID_PACKAGE_CONFIG extends NodeError { + constructor(path: string, base?: string, message?: string) { + const msg = `Invalid package config ${path}${ + base ? ` while importing ${base}` : "" + }${message ? `. ${message}` : ""}`; + super("ERR_INVALID_PACKAGE_CONFIG", msg); + } +} + +export class ERR_INVALID_MODULE_SPECIFIER extends NodeTypeError { + constructor(request: string, reason: string, base?: string) { + super( + "ERR_INVALID_MODULE_SPECIFIER", + `Invalid module "${request}" ${reason}${ + base ? ` imported from ${base}` : "" + }`, + ); + } +} + +export class ERR_INVALID_PACKAGE_TARGET extends NodeError { + constructor( + pkgPath: string, + key: string, + // deno-lint-ignore no-explicit-any + target: any, + isImport?: boolean, + base?: string, + ) { + let msg: string; + const relError = typeof target === "string" && + !isImport && + target.length && + !target.startsWith("./"); + if (key === ".") { + assert(isImport === false); + msg = `Invalid "exports" main target ${JSON.stringify(target)} defined ` + + `in the package config ${pkgPath}package.json${ + base ? ` imported from ${base}` : "" + }${relError ? '; targets must start with "./"' : ""}`; + } else { + msg = `Invalid "${isImport ? "imports" : "exports"}" target ${ + JSON.stringify( + target, + ) + } defined for '${key}' in the package config ${pkgPath}package.json${ + base ? ` imported from ${base}` : "" + }${relError ? '; targets must start with "./"' : ""}`; + } + super("ERR_INVALID_PACKAGE_TARGET", msg); + } +} + +export class ERR_PACKAGE_IMPORT_NOT_DEFINED extends NodeTypeError { + constructor( + specifier: string, + packagePath: string | undefined, + base: string, + ) { + const msg = `Package import specifier "${specifier}" is not defined${ + packagePath ? ` in package ${packagePath}package.json` : "" + } imported from ${base}`; + + super("ERR_PACKAGE_IMPORT_NOT_DEFINED", msg); + } +} + +export class ERR_PACKAGE_PATH_NOT_EXPORTED extends NodeError { + constructor(subpath: string, pkgPath: string, basePath?: string) { + let msg: string; + if (subpath === ".") { + msg = `No "exports" main defined in ${pkgPath}package.json${ + basePath ? ` imported from ${basePath}` : "" + }`; + } else { + msg = + `Package subpath '${subpath}' is not defined by "exports" in ${pkgPath}package.json${ + basePath ? ` imported from ${basePath}` : "" + }`; + } + + super("ERR_PACKAGE_PATH_NOT_EXPORTED", msg); + } +} + +export class ERR_INTERNAL_ASSERTION extends NodeError { + constructor(message?: string) { + const suffix = "This is caused by either a bug in Node.js " + + "or incorrect usage of Node.js internals.\n" + + "Please open an issue with this stack trace at " + + "https://github.com/nodejs/node/issues\n"; + super( + "ERR_INTERNAL_ASSERTION", + message === undefined ? suffix : `${message}\n${suffix}`, + ); + } +} + +// Using `fs.rmdir` on a path that is a file results in an ENOENT error on Windows and an ENOTDIR error on POSIX. +export class ERR_FS_RMDIR_ENOTDIR extends NodeSystemError { + constructor(path: string) { + const code = isWindows ? "ENOENT" : "ENOTDIR"; + const ctx: NodeSystemErrorCtx = { + message: "not a directory", + path, + syscall: "rmdir", + code, + errno: isWindows ? osConstants.errno.ENOENT : osConstants.errno.ENOTDIR, + }; + super(code, ctx, "Path is not a directory"); + } +} + +interface UvExceptionContext { + syscall: string; + path?: string; +} +export function denoErrorToNodeError(e: Error, ctx: UvExceptionContext) { + const errno = extractOsErrorNumberFromErrorMessage(e); + if (typeof errno === "undefined") { + return e; + } + + const ex = uvException({ + errno: mapSysErrnoToUvErrno(errno), + ...ctx, + }); + return ex; +} + +function extractOsErrorNumberFromErrorMessage(e: unknown): number | undefined { + const match = e instanceof Error + ? e.message.match(/\(os error (\d+)\)/) + : false; + + if (match) { + return +match[1]; + } + + return undefined; +} + +export function connResetException(msg: string) { + const ex = new Error(msg); + // deno-lint-ignore no-explicit-any + (ex as any).code = "ECONNRESET"; + return ex; +} + +export function aggregateTwoErrors( + innerError: AggregateError, + outerError: AggregateError & { code: string }, +) { + if (innerError && outerError && innerError !== outerError) { + if (Array.isArray(outerError.errors)) { + // If `outerError` is already an `AggregateError`. + outerError.errors.push(innerError); + return outerError; + } + // eslint-disable-next-line no-restricted-syntax + const err = new AggregateError( + [ + outerError, + innerError, + ], + outerError.message, + ); + // deno-lint-ignore no-explicit-any + (err as any).code = outerError.code; + return err; + } + return innerError || outerError; +} +codes.ERR_IPC_CHANNEL_CLOSED = ERR_IPC_CHANNEL_CLOSED; +codes.ERR_INVALID_ARG_TYPE = ERR_INVALID_ARG_TYPE; +codes.ERR_INVALID_ARG_VALUE = ERR_INVALID_ARG_VALUE; +codes.ERR_OUT_OF_RANGE = ERR_OUT_OF_RANGE; +codes.ERR_SOCKET_BAD_PORT = ERR_SOCKET_BAD_PORT; +codes.ERR_BUFFER_OUT_OF_BOUNDS = ERR_BUFFER_OUT_OF_BOUNDS; +codes.ERR_UNKNOWN_ENCODING = ERR_UNKNOWN_ENCODING; +// TODO(kt3k): assign all error classes here. + +/** + * This creates a generic Node.js error. + * + * @param message The error message. + * @param errorProperties Object with additional properties to be added to the error. + * @returns + */ +const genericNodeError = hideStackFrames( + function genericNodeError(message, errorProperties) { + // eslint-disable-next-line no-restricted-syntax + const err = new Error(message); + Object.assign(err, errorProperties); + + return err; + }, +); + +/** + * Determine the specific type of a value for type-mismatch errors. + * @param {*} value + * @returns {string} + */ +// deno-lint-ignore no-explicit-any +function determineSpecificType(value: any) { + if (value == null) { + return "" + value; + } + if (typeof value === "function" && value.name) { + return `function ${value.name}`; + } + if (typeof value === "object") { + if (value.constructor?.name) { + return `an instance of ${value.constructor.name}`; + } + return `${inspect(value, { depth: -1 })}`; + } + let inspected = inspect(value, { colors: false }); + if (inspected.length > 28) inspected = `${inspected.slice(0, 25)}...`; + + return `type ${typeof value} (${inspected})`; +} + +export { codes, genericNodeError, hideStackFrames }; + +export default { + AbortError, + ERR_AMBIGUOUS_ARGUMENT, + ERR_ARG_NOT_ITERABLE, + ERR_ASSERTION, + ERR_ASYNC_CALLBACK, + ERR_ASYNC_TYPE, + ERR_BROTLI_INVALID_PARAM, + ERR_BUFFER_OUT_OF_BOUNDS, + ERR_BUFFER_TOO_LARGE, + ERR_CANNOT_WATCH_SIGINT, + ERR_CHILD_CLOSED_BEFORE_REPLY, + ERR_CHILD_PROCESS_IPC_REQUIRED, + ERR_CHILD_PROCESS_STDIO_MAXBUFFER, + ERR_CONSOLE_WRITABLE_STREAM, + ERR_CONTEXT_NOT_INITIALIZED, + ERR_CPU_USAGE, + ERR_CRYPTO_CUSTOM_ENGINE_NOT_SUPPORTED, + ERR_CRYPTO_ECDH_INVALID_FORMAT, + ERR_CRYPTO_ECDH_INVALID_PUBLIC_KEY, + ERR_CRYPTO_ENGINE_UNKNOWN, + ERR_CRYPTO_FIPS_FORCED, + ERR_CRYPTO_FIPS_UNAVAILABLE, + ERR_CRYPTO_HASH_FINALIZED, + ERR_CRYPTO_HASH_UPDATE_FAILED, + ERR_CRYPTO_INCOMPATIBLE_KEY, + ERR_CRYPTO_INCOMPATIBLE_KEY_OPTIONS, + ERR_CRYPTO_INVALID_DIGEST, + ERR_CRYPTO_INVALID_KEY_OBJECT_TYPE, + ERR_CRYPTO_INVALID_STATE, + ERR_CRYPTO_PBKDF2_ERROR, + ERR_CRYPTO_SCRYPT_INVALID_PARAMETER, + ERR_CRYPTO_SCRYPT_NOT_SUPPORTED, + ERR_CRYPTO_SIGN_KEY_REQUIRED, + ERR_DIR_CLOSED, + ERR_DIR_CONCURRENT_OPERATION, + ERR_DNS_SET_SERVERS_FAILED, + ERR_DOMAIN_CALLBACK_NOT_AVAILABLE, + ERR_DOMAIN_CANNOT_SET_UNCAUGHT_EXCEPTION_CAPTURE, + ERR_ENCODING_INVALID_ENCODED_DATA, + ERR_ENCODING_NOT_SUPPORTED, + ERR_EVAL_ESM_CANNOT_PRINT, + ERR_EVENT_RECURSION, + ERR_FALSY_VALUE_REJECTION, + ERR_FEATURE_UNAVAILABLE_ON_PLATFORM, + ERR_FS_EISDIR, + ERR_FS_FILE_TOO_LARGE, + ERR_FS_INVALID_SYMLINK_TYPE, + ERR_FS_RMDIR_ENOTDIR, + ERR_HTTP2_ALTSVC_INVALID_ORIGIN, + ERR_HTTP2_ALTSVC_LENGTH, + ERR_HTTP2_CONNECT_AUTHORITY, + ERR_HTTP2_CONNECT_PATH, + ERR_HTTP2_CONNECT_SCHEME, + ERR_HTTP2_GOAWAY_SESSION, + ERR_HTTP2_HEADERS_AFTER_RESPOND, + ERR_HTTP2_HEADERS_SENT, + ERR_HTTP2_HEADER_SINGLE_VALUE, + ERR_HTTP2_INFO_STATUS_NOT_ALLOWED, + ERR_HTTP2_INVALID_CONNECTION_HEADERS, + ERR_HTTP2_INVALID_HEADER_VALUE, + ERR_HTTP2_INVALID_INFO_STATUS, + ERR_HTTP2_INVALID_ORIGIN, + ERR_HTTP2_INVALID_PACKED_SETTINGS_LENGTH, + ERR_HTTP2_INVALID_PSEUDOHEADER, + ERR_HTTP2_INVALID_SESSION, + ERR_HTTP2_INVALID_SETTING_VALUE, + ERR_HTTP2_INVALID_STREAM, + ERR_HTTP2_MAX_PENDING_SETTINGS_ACK, + ERR_HTTP2_NESTED_PUSH, + ERR_HTTP2_NO_SOCKET_MANIPULATION, + ERR_HTTP2_ORIGIN_LENGTH, + ERR_HTTP2_OUT_OF_STREAMS, + ERR_HTTP2_PAYLOAD_FORBIDDEN, + ERR_HTTP2_PING_CANCEL, + ERR_HTTP2_PING_LENGTH, + ERR_HTTP2_PSEUDOHEADER_NOT_ALLOWED, + ERR_HTTP2_PUSH_DISABLED, + ERR_HTTP2_SEND_FILE, + ERR_HTTP2_SEND_FILE_NOSEEK, + ERR_HTTP2_SESSION_ERROR, + ERR_HTTP2_SETTINGS_CANCEL, + ERR_HTTP2_SOCKET_BOUND, + ERR_HTTP2_SOCKET_UNBOUND, + ERR_HTTP2_STATUS_101, + ERR_HTTP2_STATUS_INVALID, + ERR_HTTP2_STREAM_CANCEL, + ERR_HTTP2_STREAM_ERROR, + ERR_HTTP2_STREAM_SELF_DEPENDENCY, + ERR_HTTP2_TRAILERS_ALREADY_SENT, + ERR_HTTP2_TRAILERS_NOT_READY, + ERR_HTTP2_UNSUPPORTED_PROTOCOL, + ERR_HTTP_HEADERS_SENT, + ERR_HTTP_INVALID_HEADER_VALUE, + ERR_HTTP_INVALID_STATUS_CODE, + ERR_HTTP_SOCKET_ENCODING, + ERR_HTTP_TRAILER_INVALID, + ERR_INCOMPATIBLE_OPTION_PAIR, + ERR_INPUT_TYPE_NOT_ALLOWED, + ERR_INSPECTOR_ALREADY_ACTIVATED, + ERR_INSPECTOR_ALREADY_CONNECTED, + ERR_INSPECTOR_CLOSED, + ERR_INSPECTOR_COMMAND, + ERR_INSPECTOR_NOT_ACTIVE, + ERR_INSPECTOR_NOT_AVAILABLE, + ERR_INSPECTOR_NOT_CONNECTED, + ERR_INSPECTOR_NOT_WORKER, + ERR_INTERNAL_ASSERTION, + ERR_INVALID_ADDRESS_FAMILY, + ERR_INVALID_ARG_TYPE, + ERR_INVALID_ARG_TYPE_RANGE, + ERR_INVALID_ARG_VALUE, + ERR_INVALID_ARG_VALUE_RANGE, + ERR_INVALID_ASYNC_ID, + ERR_INVALID_BUFFER_SIZE, + ERR_INVALID_CHAR, + ERR_INVALID_CURSOR_POS, + ERR_INVALID_FD, + ERR_INVALID_FD_TYPE, + ERR_INVALID_FILE_URL_HOST, + ERR_INVALID_FILE_URL_PATH, + ERR_INVALID_HANDLE_TYPE, + ERR_INVALID_HTTP_TOKEN, + ERR_INVALID_IP_ADDRESS, + ERR_INVALID_MODULE_SPECIFIER, + ERR_INVALID_OPT_VALUE, + ERR_INVALID_OPT_VALUE_ENCODING, + ERR_INVALID_PACKAGE_CONFIG, + ERR_INVALID_PACKAGE_TARGET, + ERR_INVALID_PERFORMANCE_MARK, + ERR_INVALID_PROTOCOL, + ERR_INVALID_REPL_EVAL_CONFIG, + ERR_INVALID_REPL_INPUT, + ERR_INVALID_RETURN_PROPERTY, + ERR_INVALID_RETURN_PROPERTY_VALUE, + ERR_INVALID_RETURN_VALUE, + ERR_INVALID_SYNC_FORK_INPUT, + ERR_INVALID_THIS, + ERR_INVALID_TUPLE, + ERR_INVALID_URI, + ERR_INVALID_URL, + ERR_INVALID_URL_SCHEME, + ERR_IPC_CHANNEL_CLOSED, + ERR_IPC_DISCONNECTED, + ERR_IPC_ONE_PIPE, + ERR_IPC_SYNC_FORK, + ERR_MANIFEST_DEPENDENCY_MISSING, + ERR_MANIFEST_INTEGRITY_MISMATCH, + ERR_MANIFEST_INVALID_RESOURCE_FIELD, + ERR_MANIFEST_TDZ, + ERR_MANIFEST_UNKNOWN_ONERROR, + ERR_METHOD_NOT_IMPLEMENTED, + ERR_MISSING_ARGS, + ERR_MISSING_OPTION, + ERR_MODULE_NOT_FOUND, + ERR_MULTIPLE_CALLBACK, + ERR_NAPI_CONS_FUNCTION, + ERR_NAPI_INVALID_DATAVIEW_ARGS, + ERR_NAPI_INVALID_TYPEDARRAY_ALIGNMENT, + ERR_NAPI_INVALID_TYPEDARRAY_LENGTH, + ERR_NO_CRYPTO, + ERR_NO_ICU, + ERR_OUT_OF_RANGE, + ERR_PACKAGE_IMPORT_NOT_DEFINED, + ERR_PACKAGE_PATH_NOT_EXPORTED, + ERR_QUICCLIENTSESSION_FAILED, + ERR_QUICCLIENTSESSION_FAILED_SETSOCKET, + ERR_QUICSESSION_DESTROYED, + ERR_QUICSESSION_INVALID_DCID, + ERR_QUICSESSION_UPDATEKEY, + ERR_QUICSOCKET_DESTROYED, + ERR_QUICSOCKET_INVALID_STATELESS_RESET_SECRET_LENGTH, + ERR_QUICSOCKET_LISTENING, + ERR_QUICSOCKET_UNBOUND, + ERR_QUICSTREAM_DESTROYED, + ERR_QUICSTREAM_INVALID_PUSH, + ERR_QUICSTREAM_OPEN_FAILED, + ERR_QUICSTREAM_UNSUPPORTED_PUSH, + ERR_QUIC_TLS13_REQUIRED, + ERR_SCRIPT_EXECUTION_INTERRUPTED, + ERR_SERVER_ALREADY_LISTEN, + ERR_SERVER_NOT_RUNNING, + ERR_SOCKET_ALREADY_BOUND, + ERR_SOCKET_BAD_BUFFER_SIZE, + ERR_SOCKET_BAD_PORT, + ERR_SOCKET_BAD_TYPE, + ERR_SOCKET_BUFFER_SIZE, + ERR_SOCKET_CLOSED, + ERR_SOCKET_DGRAM_IS_CONNECTED, + ERR_SOCKET_DGRAM_NOT_CONNECTED, + ERR_SOCKET_DGRAM_NOT_RUNNING, + ERR_SRI_PARSE, + ERR_STREAM_ALREADY_FINISHED, + ERR_STREAM_CANNOT_PIPE, + ERR_STREAM_DESTROYED, + ERR_STREAM_NULL_VALUES, + ERR_STREAM_PREMATURE_CLOSE, + ERR_STREAM_PUSH_AFTER_EOF, + ERR_STREAM_UNSHIFT_AFTER_END_EVENT, + ERR_STREAM_WRAP, + ERR_STREAM_WRITE_AFTER_END, + ERR_SYNTHETIC, + ERR_TLS_CERT_ALTNAME_INVALID, + ERR_TLS_DH_PARAM_SIZE, + ERR_TLS_HANDSHAKE_TIMEOUT, + ERR_TLS_INVALID_CONTEXT, + ERR_TLS_INVALID_PROTOCOL_VERSION, + ERR_TLS_INVALID_STATE, + ERR_TLS_PROTOCOL_VERSION_CONFLICT, + ERR_TLS_RENEGOTIATION_DISABLED, + ERR_TLS_REQUIRED_SERVER_NAME, + ERR_TLS_SESSION_ATTACK, + ERR_TLS_SNI_FROM_SERVER, + ERR_TRACE_EVENTS_CATEGORY_REQUIRED, + ERR_TRACE_EVENTS_UNAVAILABLE, + ERR_UNAVAILABLE_DURING_EXIT, + ERR_UNCAUGHT_EXCEPTION_CAPTURE_ALREADY_SET, + ERR_UNESCAPED_CHARACTERS, + ERR_UNHANDLED_ERROR, + ERR_UNKNOWN_BUILTIN_MODULE, + ERR_UNKNOWN_CREDENTIAL, + ERR_UNKNOWN_ENCODING, + ERR_UNKNOWN_FILE_EXTENSION, + ERR_UNKNOWN_MODULE_FORMAT, + ERR_UNKNOWN_SIGNAL, + ERR_UNSUPPORTED_DIR_IMPORT, + ERR_UNSUPPORTED_ESM_URL_SCHEME, + ERR_USE_AFTER_CLOSE, + ERR_V8BREAKITERATOR, + ERR_VALID_PERFORMANCE_ENTRY_TYPE, + ERR_VM_DYNAMIC_IMPORT_CALLBACK_MISSING, + ERR_VM_MODULE_ALREADY_LINKED, + ERR_VM_MODULE_CANNOT_CREATE_CACHED_DATA, + ERR_VM_MODULE_DIFFERENT_CONTEXT, + ERR_VM_MODULE_LINKING_ERRORED, + ERR_VM_MODULE_NOT_MODULE, + ERR_VM_MODULE_STATUS, + ERR_WASI_ALREADY_STARTED, + ERR_WORKER_INIT_FAILED, + ERR_WORKER_NOT_RUNNING, + ERR_WORKER_OUT_OF_MEMORY, + ERR_WORKER_UNSERIALIZABLE_ERROR, + ERR_WORKER_UNSUPPORTED_EXTENSION, + ERR_WORKER_UNSUPPORTED_OPERATION, + ERR_ZLIB_INITIALIZATION_FAILED, + NodeError, + NodeErrorAbstraction, + NodeRangeError, + NodeSyntaxError, + NodeTypeError, + NodeURIError, + aggregateTwoErrors, + codes, + connResetException, + denoErrorToNodeError, + dnsException, + errnoException, + errorMap, + exceptionWithHostPort, + genericNodeError, + hideStackFrames, + isStackOverflowError, + uvException, + uvExceptionWithHostPort, +}; diff --git a/ext/node/polyfills/internal/event_target.mjs b/ext/node/polyfills/internal/event_target.mjs new file mode 100644 index 00000000000000..d542fba944f87a --- /dev/null +++ b/ext/node/polyfills/internal/event_target.mjs @@ -0,0 +1,1111 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. +// Copyright Node.js contributors. All rights reserved. MIT License. + +import { + ERR_EVENT_RECURSION, + ERR_INVALID_ARG_TYPE, + ERR_INVALID_THIS, + ERR_MISSING_ARGS, +} from "internal:deno_node/polyfills/internal/errors.ts"; +import { validateObject, validateString } from "internal:deno_node/polyfills/internal/validators.mjs"; +import { emitWarning } from "internal:deno_node/polyfills/process.ts"; +import { nextTick } from "internal:deno_node/polyfills/_next_tick.ts"; +import { Event as WebEvent, EventTarget as WebEventTarget } from "internal:deno_web/02_event.js"; + +import { + customInspectSymbol, + kEmptyObject, + kEnumerableProperty, +} from "internal:deno_node/polyfills/internal/util.mjs"; +import { inspect } from "internal:deno_node/polyfills/util.ts"; + +const kIsEventTarget = Symbol.for("nodejs.event_target"); +const kIsNodeEventTarget = Symbol("kIsNodeEventTarget"); + +import { EventEmitter } from "internal:deno_node/polyfills/events.ts"; +const { + kMaxEventTargetListeners, + kMaxEventTargetListenersWarned, +} = EventEmitter; + +const kEvents = Symbol("kEvents"); +const kIsBeingDispatched = Symbol("kIsBeingDispatched"); +const kStop = Symbol("kStop"); +const kTarget = Symbol("kTarget"); +const kHandlers = Symbol("khandlers"); +const kWeakHandler = Symbol("kWeak"); + +const kHybridDispatch = Symbol.for("nodejs.internal.kHybridDispatch"); +const kCreateEvent = Symbol("kCreateEvent"); +const kNewListener = Symbol("kNewListener"); +const kRemoveListener = Symbol("kRemoveListener"); +const kIsNodeStyleListener = Symbol("kIsNodeStyleListener"); +const kTrustEvent = Symbol("kTrustEvent"); + +const kType = Symbol("type"); +const kDetail = Symbol("detail"); +const kDefaultPrevented = Symbol("defaultPrevented"); +const kCancelable = Symbol("cancelable"); +const kTimestamp = Symbol("timestamp"); +const kBubbles = Symbol("bubbles"); +const kComposed = Symbol("composed"); +const kPropagationStopped = Symbol("propagationStopped"); + +function isEvent(value) { + return typeof value?.[kType] === "string"; +} + +class Event extends WebEvent { + /** + * @param {string} type + * @param {{ + * bubbles?: boolean, + * cancelable?: boolean, + * composed?: boolean, + * }} [options] + */ + constructor(type, options = null) { + super(type, options); + if (arguments.length === 0) { + throw new ERR_MISSING_ARGS("type"); + } + validateObject(options, "options", { + allowArray: true, + allowFunction: true, + nullable: true, + }); + const { cancelable, bubbles, composed } = { ...options }; + this[kCancelable] = !!cancelable; + this[kBubbles] = !!bubbles; + this[kComposed] = !!composed; + this[kType] = `${type}`; + this[kDefaultPrevented] = false; + this[kTimestamp] = performance.now(); + this[kPropagationStopped] = false; + this[kTarget] = null; + this[kIsBeingDispatched] = false; + } + + [customInspectSymbol](depth, options) { + if (!isEvent(this)) { + throw new ERR_INVALID_THIS("Event"); + } + const name = this.constructor.name; + if (depth < 0) { + return name; + } + + const opts = Object.assign({}, options, { + depth: NumberIsInteger(options.depth) ? options.depth - 1 : options.depth, + }); + + return `${name} ${ + inspect({ + type: this[kType], + defaultPrevented: this[kDefaultPrevented], + cancelable: this[kCancelable], + timeStamp: this[kTimestamp], + }, opts) + }`; + } + + stopImmediatePropagation() { + if (!isEvent(this)) { + throw new ERR_INVALID_THIS("Event"); + } + this[kStop] = true; + } + + preventDefault() { + if (!isEvent(this)) { + throw new ERR_INVALID_THIS("Event"); + } + this[kDefaultPrevented] = true; + } + + /** + * @type {EventTarget} + */ + get target() { + if (!isEvent(this)) { + throw new ERR_INVALID_THIS("Event"); + } + return this[kTarget]; + } + + /** + * @type {EventTarget} + */ + get currentTarget() { + if (!isEvent(this)) { + throw new ERR_INVALID_THIS("Event"); + } + return this[kTarget]; + } + + /** + * @type {EventTarget} + */ + get srcElement() { + if (!isEvent(this)) { + throw new ERR_INVALID_THIS("Event"); + } + return this[kTarget]; + } + + /** + * @type {string} + */ + get type() { + if (!isEvent(this)) { + throw new ERR_INVALID_THIS("Event"); + } + return this[kType]; + } + + /** + * @type {boolean} + */ + get cancelable() { + if (!isEvent(this)) { + throw new ERR_INVALID_THIS("Event"); + } + return this[kCancelable]; + } + + /** + * @type {boolean} + */ + get defaultPrevented() { + if (!isEvent(this)) { + throw new ERR_INVALID_THIS("Event"); + } + return this[kCancelable] && this[kDefaultPrevented]; + } + + /** + * @type {number} + */ + get timeStamp() { + if (!isEvent(this)) { + throw new ERR_INVALID_THIS("Event"); + } + return this[kTimestamp]; + } + + // The following are non-op and unused properties/methods from Web API Event. + // These are not supported in Node.js and are provided purely for + // API completeness. + /** + * @returns {EventTarget[]} + */ + composedPath() { + if (!isEvent(this)) { + throw new ERR_INVALID_THIS("Event"); + } + return this[kIsBeingDispatched] ? [this[kTarget]] : []; + } + + /** + * @type {boolean} + */ + get returnValue() { + if (!isEvent(this)) { + throw new ERR_INVALID_THIS("Event"); + } + return !this.defaultPrevented; + } + + /** + * @type {boolean} + */ + get bubbles() { + if (!isEvent(this)) { + throw new ERR_INVALID_THIS("Event"); + } + return this[kBubbles]; + } + + /** + * @type {boolean} + */ + get composed() { + if (!isEvent(this)) { + throw new ERR_INVALID_THIS("Event"); + } + return this[kComposed]; + } + + /** + * @type {number} + */ + get eventPhase() { + if (!isEvent(this)) { + throw new ERR_INVALID_THIS("Event"); + } + return this[kIsBeingDispatched] ? Event.AT_TARGET : Event.NONE; + } + + /** + * @type {boolean} + */ + get cancelBubble() { + if (!isEvent(this)) { + throw new ERR_INVALID_THIS("Event"); + } + return this[kPropagationStopped]; + } + + /** + * @type {boolean} + */ + set cancelBubble(value) { + if (!isEvent(this)) { + throw new ERR_INVALID_THIS("Event"); + } + if (value) { + this.stopPropagation(); + } + } + + stopPropagation() { + if (!isEvent(this)) { + throw new ERR_INVALID_THIS("Event"); + } + this[kPropagationStopped] = true; + } + + static NONE = 0; + static CAPTURING_PHASE = 1; + static AT_TARGET = 2; + static BUBBLING_PHASE = 3; +} + +Object.defineProperties( + Event.prototype, + { + [Symbol.toStringTag]: { + writable: true, + enumerable: false, + configurable: true, + value: "Event", + }, + stopImmediatePropagation: kEnumerableProperty, + preventDefault: kEnumerableProperty, + target: kEnumerableProperty, + currentTarget: kEnumerableProperty, + srcElement: kEnumerableProperty, + type: kEnumerableProperty, + cancelable: kEnumerableProperty, + defaultPrevented: kEnumerableProperty, + timeStamp: kEnumerableProperty, + composedPath: kEnumerableProperty, + returnValue: kEnumerableProperty, + bubbles: kEnumerableProperty, + composed: kEnumerableProperty, + eventPhase: kEnumerableProperty, + cancelBubble: kEnumerableProperty, + stopPropagation: kEnumerableProperty, + }, +); + +function isCustomEvent(value) { + return isEvent(value) && (value?.[kDetail] !== undefined); +} + +class CustomEvent extends Event { + /** + * @constructor + * @param {string} type + * @param {{ + * bubbles?: boolean, + * cancelable?: boolean, + * composed?: boolean, + * detail?: any, + * }} [options] + */ + constructor(type, options = kEmptyObject) { + if (arguments.length === 0) { + throw new ERR_MISSING_ARGS("type"); + } + super(type, options); + this[kDetail] = options?.detail ?? null; + } + + /** + * @type {any} + */ + get detail() { + if (!isCustomEvent(this)) { + throw new ERR_INVALID_THIS("CustomEvent"); + } + return this[kDetail]; + } +} + +Object.defineProperties(CustomEvent.prototype, { + [Symbol.toStringTag]: { + __proto__: null, + writable: false, + enumerable: false, + configurable: true, + value: "CustomEvent", + }, + detail: kEnumerableProperty, +}); + +class NodeCustomEvent extends Event { + constructor(type, options) { + super(type, options); + if (options?.detail) { + this.detail = options.detail; + } + } +} + +// Weak listener cleanup +// This has to be lazy for snapshots to work +let weakListenersState = null; +// The resource needs to retain the callback so that it doesn't +// get garbage collected now that it's weak. +let objectToWeakListenerMap = null; +function weakListeners() { + weakListenersState ??= new FinalizationRegistry( + (listener) => listener.remove(), + ); + objectToWeakListenerMap ??= new WeakMap(); + return { registry: weakListenersState, map: objectToWeakListenerMap }; +} + +// The listeners for an EventTarget are maintained as a linked list. +// Unfortunately, the way EventTarget is defined, listeners are accounted +// using the tuple [handler,capture], and even if we don't actually make +// use of capture or bubbling, in order to be spec compliant we have to +// take on the additional complexity of supporting it. Fortunately, using +// the linked list makes dispatching faster, even if adding/removing is +// slower. +class Listener { + constructor( + previous, + listener, + once, + capture, + passive, + isNodeStyleListener, + weak, + ) { + this.next = undefined; + if (previous !== undefined) { + previous.next = this; + } + this.previous = previous; + this.listener = listener; + // TODO(benjamingr) these 4 can be 'flags' to save 3 slots + this.once = once; + this.capture = capture; + this.passive = passive; + this.isNodeStyleListener = isNodeStyleListener; + this.removed = false; + this.weak = Boolean(weak); // Don't retain the object + + if (this.weak) { + this.callback = new WeakRef(listener); + weakListeners().registry.register(listener, this, this); + // Make the retainer retain the listener in a WeakMap + weakListeners().map.set(weak, listener); + this.listener = this.callback; + } else if (typeof listener === "function") { + this.callback = listener; + this.listener = listener; + } else { + this.callback = Function.prototype.bind.call( + listener.handleEvent, + listener, + ); + this.listener = listener; + } + } + + same(listener, capture) { + const myListener = this.weak ? this.listener.deref() : this.listener; + return myListener === listener && this.capture === capture; + } + + remove() { + if (this.previous !== undefined) { + this.previous.next = this.next; + } + if (this.next !== undefined) { + this.next.previous = this.previous; + } + this.removed = true; + if (this.weak) { + weakListeners().registry.unregister(this); + } + } +} + +function initEventTarget(self) { + self[kEvents] = new Map(); + self[kMaxEventTargetListeners] = EventEmitter.defaultMaxListeners; + self[kMaxEventTargetListenersWarned] = false; +} + +class EventTarget extends WebEventTarget { + // Used in checking whether an object is an EventTarget. This is a well-known + // symbol as EventTarget may be used cross-realm. + // Ref: https://github.com/nodejs/node/pull/33661 + static [kIsEventTarget] = true; + + constructor() { + super(); + initEventTarget(this); + } + + [kNewListener](size, type, _listener, _once, _capture, _passive, _weak) { + if ( + this[kMaxEventTargetListeners] > 0 && + size > this[kMaxEventTargetListeners] && + !this[kMaxEventTargetListenersWarned] + ) { + this[kMaxEventTargetListenersWarned] = true; + // No error code for this since it is a Warning + // eslint-disable-next-line no-restricted-syntax + const w = new Error( + "Possible EventTarget memory leak detected. " + + `${size} ${type} listeners ` + + `added to ${inspect(this, { depth: -1 })}. Use ` + + "events.setMaxListeners() to increase limit", + ); + w.name = "MaxListenersExceededWarning"; + w.target = this; + w.type = type; + w.count = size; + emitWarning(w); + } + } + [kRemoveListener](_size, _type, _listener, _capture) {} + + /** + * @callback EventTargetCallback + * @param {Event} event + */ + + /** + * @typedef {{ handleEvent: EventTargetCallback }} EventListener + */ + + /** + * @param {string} type + * @param {EventTargetCallback|EventListener} listener + * @param {{ + * capture?: boolean, + * once?: boolean, + * passive?: boolean, + * signal?: AbortSignal + * }} [options] + */ + addEventListener(type, listener, options = {}) { + if (!isEventTarget(this)) { + throw new ERR_INVALID_THIS("EventTarget"); + } + if (arguments.length < 2) { + throw new ERR_MISSING_ARGS("type", "listener"); + } + + // We validateOptions before the shouldAddListeners check because the spec + // requires us to hit getters. + const { + once, + capture, + passive, + signal, + isNodeStyleListener, + weak, + } = validateEventListenerOptions(options); + + if (!shouldAddListener(listener)) { + // The DOM silently allows passing undefined as a second argument + // No error code for this since it is a Warning + // eslint-disable-next-line no-restricted-syntax + const w = new Error( + `addEventListener called with ${listener}` + + " which has no effect.", + ); + w.name = "AddEventListenerArgumentTypeWarning"; + w.target = this; + w.type = type; + emitWarning(w); + return; + } + type = String(type); + + if (signal) { + if (signal.aborted) { + return; + } + // TODO(benjamingr) make this weak somehow? ideally the signal would + // not prevent the event target from GC. + signal.addEventListener("abort", () => { + this.removeEventListener(type, listener, options); + }, { once: true, [kWeakHandler]: this }); + } + + let root = this[kEvents].get(type); + + if (root === undefined) { + root = { size: 1, next: undefined }; + // This is the first handler in our linked list. + new Listener( + root, + listener, + once, + capture, + passive, + isNodeStyleListener, + weak, + ); + this[kNewListener]( + root.size, + type, + listener, + once, + capture, + passive, + weak, + ); + this[kEvents].set(type, root); + return; + } + + let handler = root.next; + let previous = root; + + // We have to walk the linked list to see if we have a match + while (handler !== undefined && !handler.same(listener, capture)) { + previous = handler; + handler = handler.next; + } + + if (handler !== undefined) { // Duplicate! Ignore + return; + } + + new Listener( + previous, + listener, + once, + capture, + passive, + isNodeStyleListener, + weak, + ); + root.size++; + this[kNewListener](root.size, type, listener, once, capture, passive, weak); + } + + /** + * @param {string} type + * @param {EventTargetCallback|EventListener} listener + * @param {{ + * capture?: boolean, + * }} [options] + */ + removeEventListener(type, listener, options = {}) { + if (!isEventTarget(this)) { + throw new ERR_INVALID_THIS("EventTarget"); + } + if (!shouldAddListener(listener)) { + return; + } + + type = String(type); + const capture = options?.capture === true; + + const root = this[kEvents].get(type); + if (root === undefined || root.next === undefined) { + return; + } + + let handler = root.next; + while (handler !== undefined) { + if (handler.same(listener, capture)) { + handler.remove(); + root.size--; + if (root.size === 0) { + this[kEvents].delete(type); + } + this[kRemoveListener](root.size, type, listener, capture); + break; + } + handler = handler.next; + } + } + + /** + * @param {Event} event + */ + dispatchEvent(event) { + if (!isEventTarget(this)) { + throw new ERR_INVALID_THIS("EventTarget"); + } + + if (!(event instanceof globalThis.Event)) { + throw new ERR_INVALID_ARG_TYPE("event", "Event", event); + } + + if (event[kIsBeingDispatched]) { + throw new ERR_EVENT_RECURSION(event.type); + } + + this[kHybridDispatch](event, event.type, event); + + return event.defaultPrevented !== true; + } + + [kHybridDispatch](nodeValue, type, event) { + const createEvent = () => { + if (event === undefined) { + event = this[kCreateEvent](nodeValue, type); + event[kTarget] = this; + event[kIsBeingDispatched] = true; + } + return event; + }; + if (event !== undefined) { + event[kTarget] = this; + event[kIsBeingDispatched] = true; + } + + const root = this[kEvents].get(type); + if (root === undefined || root.next === undefined) { + if (event !== undefined) { + event[kIsBeingDispatched] = false; + } + return true; + } + + let handler = root.next; + let next; + + while ( + handler !== undefined && + (handler.passive || event?.[kStop] !== true) + ) { + // Cache the next item in case this iteration removes the current one + next = handler.next; + + if (handler.removed) { + // Deal with the case an event is removed while event handlers are + // Being processed (removeEventListener called from a listener) + handler = next; + continue; + } + if (handler.once) { + handler.remove(); + root.size--; + const { listener, capture } = handler; + this[kRemoveListener](root.size, type, listener, capture); + } + + try { + let arg; + if (handler.isNodeStyleListener) { + arg = nodeValue; + } else { + arg = createEvent(); + } + const callback = handler.weak + ? handler.callback.deref() + : handler.callback; + let result; + if (callback) { + result = callback.call(this, arg); + if (!handler.isNodeStyleListener) { + arg[kIsBeingDispatched] = false; + } + } + if (result !== undefined && result !== null) { + addCatch(result); + } + } catch (err) { + emitUncaughtException(err); + } + + handler = next; + } + + if (event !== undefined) { + event[kIsBeingDispatched] = false; + } + } + + [kCreateEvent](nodeValue, type) { + return new NodeCustomEvent(type, { detail: nodeValue }); + } + [customInspectSymbol](depth, options) { + if (!isEventTarget(this)) { + throw new ERR_INVALID_THIS("EventTarget"); + } + const name = this.constructor.name; + if (depth < 0) { + return name; + } + + const opts = ObjectAssign({}, options, { + depth: Number.isInteger(options.depth) + ? options.depth - 1 + : options.depth, + }); + + return `${name} ${inspect({}, opts)}`; + } +} + +Object.defineProperties(EventTarget.prototype, { + addEventListener: kEnumerableProperty, + removeEventListener: kEnumerableProperty, + dispatchEvent: kEnumerableProperty, + [Symbol.toStringTag]: { + writable: true, + enumerable: false, + configurable: true, + value: "EventTarget", + }, +}); + +function initNodeEventTarget(self) { + initEventTarget(self); +} + +class NodeEventTarget extends EventTarget { + static [kIsNodeEventTarget] = true; + static defaultMaxListeners = 10; + + constructor() { + super(); + initNodeEventTarget(this); + } + + /** + * @param {number} n + */ + setMaxListeners(n) { + if (!isNodeEventTarget(this)) { + throw new ERR_INVALID_THIS("NodeEventTarget"); + } + EventEmitter.setMaxListeners(n, this); + } + + /** + * @returns {number} + */ + getMaxListeners() { + if (!isNodeEventTarget(this)) { + throw new ERR_INVALID_THIS("NodeEventTarget"); + } + return this[kMaxEventTargetListeners]; + } + + /** + * @returns {string[]} + */ + eventNames() { + if (!isNodeEventTarget(this)) { + throw new ERR_INVALID_THIS("NodeEventTarget"); + } + return Array.from(this[kEvents].keys()); + } + + /** + * @param {string} [type] + * @returns {number} + */ + listenerCount(type) { + if (!isNodeEventTarget(this)) { + throw new ERR_INVALID_THIS("NodeEventTarget"); + } + const root = this[kEvents].get(String(type)); + return root !== undefined ? root.size : 0; + } + + /** + * @param {string} type + * @param {EventTargetCallback|EventListener} listener + * @param {{ + * capture?: boolean, + * }} [options] + * @returns {NodeEventTarget} + */ + off(type, listener, options) { + if (!isNodeEventTarget(this)) { + throw new ERR_INVALID_THIS("NodeEventTarget"); + } + this.removeEventListener(type, listener, options); + return this; + } + + /** + * @param {string} type + * @param {EventTargetCallback|EventListener} listener + * @param {{ + * capture?: boolean, + * }} [options] + * @returns {NodeEventTarget} + */ + removeListener(type, listener, options) { + if (!isNodeEventTarget(this)) { + throw new ERR_INVALID_THIS("NodeEventTarget"); + } + this.removeEventListener(type, listener, options); + return this; + } + + /** + * @param {string} type + * @param {EventTargetCallback|EventListener} listener + * @returns {NodeEventTarget} + */ + on(type, listener) { + if (!isNodeEventTarget(this)) { + throw new ERR_INVALID_THIS("NodeEventTarget"); + } + this.addEventListener(type, listener, { [kIsNodeStyleListener]: true }); + return this; + } + + /** + * @param {string} type + * @param {EventTargetCallback|EventListener} listener + * @returns {NodeEventTarget} + */ + addListener(type, listener) { + if (!isNodeEventTarget(this)) { + throw new ERR_INVALID_THIS("NodeEventTarget"); + } + this.addEventListener(type, listener, { [kIsNodeStyleListener]: true }); + return this; + } + + /** + * @param {string} type + * @param {any} arg + * @returns {boolean} + */ + emit(type, arg) { + if (!isNodeEventTarget(this)) { + throw new ERR_INVALID_THIS("NodeEventTarget"); + } + validateString(type, "type"); + const hadListeners = this.listenerCount(type) > 0; + this[kHybridDispatch](arg, type); + return hadListeners; + } + + /** + * @param {string} type + * @param {EventTargetCallback|EventListener} listener + * @returns {NodeEventTarget} + */ + once(type, listener) { + if (!isNodeEventTarget(this)) { + throw new ERR_INVALID_THIS("NodeEventTarget"); + } + this.addEventListener(type, listener, { + once: true, + [kIsNodeStyleListener]: true, + }); + return this; + } + + /** + * @param {string} type + * @returns {NodeEventTarget} + */ + removeAllListeners(type) { + if (!isNodeEventTarget(this)) { + throw new ERR_INVALID_THIS("NodeEventTarget"); + } + if (type !== undefined) { + this[kEvents].delete(String(type)); + } else { + this[kEvents].clear(); + } + + return this; + } +} + +Object.defineProperties(NodeEventTarget.prototype, { + setMaxListeners: kEnumerableProperty, + getMaxListeners: kEnumerableProperty, + eventNames: kEnumerableProperty, + listenerCount: kEnumerableProperty, + off: kEnumerableProperty, + removeListener: kEnumerableProperty, + on: kEnumerableProperty, + addListener: kEnumerableProperty, + once: kEnumerableProperty, + emit: kEnumerableProperty, + removeAllListeners: kEnumerableProperty, +}); + +// EventTarget API + +function shouldAddListener(listener) { + if ( + typeof listener === "function" || + typeof listener?.handleEvent === "function" + ) { + return true; + } + + if (listener == null) { + return false; + } + + throw new ERR_INVALID_ARG_TYPE("listener", "EventListener", listener); +} + +function validateEventListenerOptions(options) { + if (typeof options === "boolean") { + return { capture: options }; + } + + if (options === null) { + return {}; + } + validateObject(options, "options", { + allowArray: true, + allowFunction: true, + }); + return { + once: Boolean(options.once), + capture: Boolean(options.capture), + passive: Boolean(options.passive), + signal: options.signal, + weak: options[kWeakHandler], + isNodeStyleListener: Boolean(options[kIsNodeStyleListener]), + }; +} + +function isEventTarget(obj) { + return obj instanceof globalThis.EventTarget; +} + +function isNodeEventTarget(obj) { + return obj?.constructor?.[kIsNodeEventTarget]; +} + +function addCatch(promise) { + const then = promise.then; + if (typeof then === "function") { + then.call(promise, undefined, function (err) { + // The callback is called with nextTick to avoid a follow-up + // rejection from this promise. + emitUncaughtException(err); + }); + } +} + +function emitUncaughtException(err) { + nextTick(() => { + throw err; + }); +} + +function makeEventHandler(handler) { + // Event handlers are dispatched in the order they were first set + // See https://github.com/nodejs/node/pull/35949#issuecomment-722496598 + function eventHandler(...args) { + if (typeof eventHandler.handler !== "function") { + return; + } + return Reflect.apply(eventHandler.handler, this, args); + } + eventHandler.handler = handler; + return eventHandler; +} + +function defineEventHandler(emitter, name) { + // 8.1.5.1 Event handlers - basically `on[eventName]` attributes + Object.defineProperty(emitter, `on${name}`, { + get() { + return this[kHandlers]?.get(name)?.handler ?? null; + }, + set(value) { + if (!this[kHandlers]) { + this[kHandlers] = new Map(); + } + let wrappedHandler = this[kHandlers]?.get(name); + if (wrappedHandler) { + if (typeof wrappedHandler.handler === "function") { + this[kEvents].get(name).size--; + const size = this[kEvents].get(name).size; + this[kRemoveListener](size, name, wrappedHandler.handler, false); + } + wrappedHandler.handler = value; + if (typeof wrappedHandler.handler === "function") { + this[kEvents].get(name).size++; + const size = this[kEvents].get(name).size; + this[kNewListener](size, name, value, false, false, false, false); + } + } else { + wrappedHandler = makeEventHandler(value); + this.addEventListener(name, wrappedHandler); + } + this[kHandlers].set(name, wrappedHandler); + }, + configurable: true, + enumerable: true, + }); +} + +const EventEmitterMixin = (Superclass) => { + class MixedEventEmitter extends Superclass { + constructor(...args) { + super(...args); + EventEmitter.call(this); + } + } + const protoProps = Object.getOwnPropertyDescriptors(EventEmitter.prototype); + delete protoProps.constructor; + Object.defineProperties(MixedEventEmitter.prototype, protoProps); + return MixedEventEmitter; +}; + +export { + CustomEvent, + defineEventHandler, + Event, + EventEmitterMixin, + EventTarget, + initEventTarget, + initNodeEventTarget, + isEventTarget, + kCreateEvent, + kEvents, + kNewListener, + kRemoveListener, + kTrustEvent, + kWeakHandler, + NodeEventTarget, +}; + +export default { + CustomEvent, + Event, + EventEmitterMixin, + EventTarget, + NodeEventTarget, + defineEventHandler, + initEventTarget, + initNodeEventTarget, + kCreateEvent, + kNewListener, + kTrustEvent, + kRemoveListener, + kEvents, + kWeakHandler, + isEventTarget, +}; diff --git a/ext/node/polyfills/internal/fixed_queue.ts b/ext/node/polyfills/internal/fixed_queue.ts new file mode 100644 index 00000000000000..e6b8db70fe0cc0 --- /dev/null +++ b/ext/node/polyfills/internal/fixed_queue.ts @@ -0,0 +1,123 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. +// Copyright Joyent, Inc. and other Node contributors. + +// Currently optimal queue size, tested on V8 6.0 - 6.6. Must be power of two. +const kSize = 2048; +const kMask = kSize - 1; + +// The FixedQueue is implemented as a singly-linked list of fixed-size +// circular buffers. It looks something like this: +// +// head tail +// | | +// v v +// +-----------+ <-----\ +-----------+ <------\ +-----------+ +// | [null] | \----- | next | \------- | next | +// +-----------+ +-----------+ +-----------+ +// | item | <-- bottom | item | <-- bottom | [empty] | +// | item | | item | | [empty] | +// | item | | item | | [empty] | +// | item | | item | | [empty] | +// | item | | item | bottom --> | item | +// | item | | item | | item | +// | ... | | ... | | ... | +// | item | | item | | item | +// | item | | item | | item | +// | [empty] | <-- top | item | | item | +// | [empty] | | item | | item | +// | [empty] | | [empty] | <-- top top --> | [empty] | +// +-----------+ +-----------+ +-----------+ +// +// Or, if there is only one circular buffer, it looks something +// like either of these: +// +// head tail head tail +// | | | | +// v v v v +// +-----------+ +-----------+ +// | [null] | | [null] | +// +-----------+ +-----------+ +// | [empty] | | item | +// | [empty] | | item | +// | item | <-- bottom top --> | [empty] | +// | item | | [empty] | +// | [empty] | <-- top bottom --> | item | +// | [empty] | | item | +// +-----------+ +-----------+ +// +// Adding a value means moving `top` forward by one, removing means +// moving `bottom` forward by one. After reaching the end, the queue +// wraps around. +// +// When `top === bottom` the current queue is empty and when +// `top + 1 === bottom` it's full. This wastes a single space of storage +// but allows much quicker checks. + +class FixedCircularBuffer { + bottom: number; + top: number; + list: undefined | Array; + next: FixedCircularBuffer | null; + + constructor() { + this.bottom = 0; + this.top = 0; + this.list = new Array(kSize); + this.next = null; + } + + isEmpty() { + return this.top === this.bottom; + } + + isFull() { + return ((this.top + 1) & kMask) === this.bottom; + } + + push(data: unknown) { + this.list![this.top] = data; + this.top = (this.top + 1) & kMask; + } + + shift() { + const nextItem = this.list![this.bottom]; + if (nextItem === undefined) { + return null; + } + this.list![this.bottom] = undefined; + this.bottom = (this.bottom + 1) & kMask; + return nextItem; + } +} + +export class FixedQueue { + head: FixedCircularBuffer; + tail: FixedCircularBuffer; + + constructor() { + this.head = this.tail = new FixedCircularBuffer(); + } + + isEmpty() { + return this.head.isEmpty(); + } + + push(data: unknown) { + if (this.head.isFull()) { + // Head is full: Creates a new queue, sets the old queue's `.next` to it, + // and sets it as the new main queue. + this.head = this.head.next = new FixedCircularBuffer(); + } + this.head.push(data); + } + + shift() { + const tail = this.tail; + const next = tail.shift(); + if (tail.isEmpty() && tail.next !== null) { + // If there is another queue, it forms the new tail. + this.tail = tail.next; + } + return next; + } +} diff --git a/ext/node/polyfills/internal/freelist.ts b/ext/node/polyfills/internal/freelist.ts new file mode 100644 index 00000000000000..8faba8e68087c4 --- /dev/null +++ b/ext/node/polyfills/internal/freelist.ts @@ -0,0 +1,30 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. +// Copyright Joyent and Node contributors. All rights reserved. MIT license. + +type Fn = (...args: unknown[]) => T; +export class FreeList { + name: string; + ctor: Fn; + max: number; + list: Array; + constructor(name: string, max: number, ctor: Fn) { + this.name = name; + this.ctor = ctor; + this.max = max; + this.list = []; + } + + alloc(): T { + return this.list.length > 0 + ? this.list.pop() + : Reflect.apply(this.ctor, this, arguments); + } + + free(obj: T) { + if (this.list.length < this.max) { + this.list.push(obj); + return true; + } + return false; + } +} diff --git a/ext/node/polyfills/internal/fs/streams.d.ts b/ext/node/polyfills/internal/fs/streams.d.ts new file mode 100644 index 00000000000000..9e70c2431f42a9 --- /dev/null +++ b/ext/node/polyfills/internal/fs/streams.d.ts @@ -0,0 +1,326 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. +// Copyright DefinitelyTyped contributors. All rights reserved. MIT license. +// deno-lint-ignore-file no-explicit-any + +import * as stream from "internal:deno_node/polyfills/_stream.d.ts"; +import * as promises from "internal:deno_node/polyfills/fs/promises.ts"; + +import { Buffer } from "internal:deno_node/polyfills/buffer.ts"; +import { + BufferEncoding, + ErrnoException, +} from "internal:deno_node/polyfills/_global.d.ts"; + +type PathLike = string | Buffer | URL; + +/** + * Instances of `fs.ReadStream` are created and returned using the {@link createReadStream} function. + * @since v0.1.93 + */ +export class ReadStream extends stream.Readable { + close(callback?: (err?: ErrnoException | null) => void): void; + /** + * The number of bytes that have been read so far. + * @since v6.4.0 + */ + bytesRead: number; + /** + * The path to the file the stream is reading from as specified in the first + * argument to `fs.createReadStream()`. If `path` is passed as a string, then`readStream.path` will be a string. If `path` is passed as a `Buffer`, then`readStream.path` will be a + * `Buffer`. If `fd` is specified, then`readStream.path` will be `undefined`. + * @since v0.1.93 + */ + path: string | Buffer; + /** + * This property is `true` if the underlying file has not been opened yet, + * i.e. before the `'ready'` event is emitted. + * @since v11.2.0, v10.16.0 + */ + pending: boolean; + /** + * events.EventEmitter + * 1. open + * 2. close + * 3. ready + */ + addListener(event: "close", listener: () => void): this; + addListener(event: "data", listener: (chunk: Buffer | string) => void): this; + addListener(event: "end", listener: () => void): this; + addListener(event: "error", listener: (err: Error) => void): this; + addListener(event: "open", listener: (fd: number) => void): this; + addListener(event: "pause", listener: () => void): this; + addListener(event: "readable", listener: () => void): this; + addListener(event: "ready", listener: () => void): this; + addListener(event: "resume", listener: () => void): this; + addListener(event: string | symbol, listener: (...args: any[]) => void): this; + on(event: "close", listener: () => void): this; + on(event: "data", listener: (chunk: Buffer | string) => void): this; + on(event: "end", listener: () => void): this; + on(event: "error", listener: (err: Error) => void): this; + on(event: "open", listener: (fd: number) => void): this; + on(event: "pause", listener: () => void): this; + on(event: "readable", listener: () => void): this; + on(event: "ready", listener: () => void): this; + on(event: "resume", listener: () => void): this; + on(event: string | symbol, listener: (...args: any[]) => void): this; + once(event: "close", listener: () => void): this; + once(event: "data", listener: (chunk: Buffer | string) => void): this; + once(event: "end", listener: () => void): this; + once(event: "error", listener: (err: Error) => void): this; + once(event: "open", listener: (fd: number) => void): this; + once(event: "pause", listener: () => void): this; + once(event: "readable", listener: () => void): this; + once(event: "ready", listener: () => void): this; + once(event: "resume", listener: () => void): this; + once(event: string | symbol, listener: (...args: any[]) => void): this; + prependListener(event: "close", listener: () => void): this; + prependListener( + event: "data", + listener: (chunk: Buffer | string) => void, + ): this; + prependListener(event: "end", listener: () => void): this; + prependListener(event: "error", listener: (err: Error) => void): this; + prependListener(event: "open", listener: (fd: number) => void): this; + prependListener(event: "pause", listener: () => void): this; + prependListener(event: "readable", listener: () => void): this; + prependListener(event: "ready", listener: () => void): this; + prependListener(event: "resume", listener: () => void): this; + prependListener( + event: string | symbol, + listener: (...args: any[]) => void, + ): this; + prependOnceListener(event: "close", listener: () => void): this; + prependOnceListener( + event: "data", + listener: (chunk: Buffer | string) => void, + ): this; + prependOnceListener(event: "end", listener: () => void): this; + prependOnceListener(event: "error", listener: (err: Error) => void): this; + prependOnceListener(event: "open", listener: (fd: number) => void): this; + prependOnceListener(event: "pause", listener: () => void): this; + prependOnceListener(event: "readable", listener: () => void): this; + prependOnceListener(event: "ready", listener: () => void): this; + prependOnceListener(event: "resume", listener: () => void): this; + prependOnceListener( + event: string | symbol, + listener: (...args: any[]) => void, + ): this; +} +/** + * * Extends `stream.Writable` + * + * Instances of `fs.WriteStream` are created and returned using the {@link createWriteStream} function. + * @since v0.1.93 + */ +export class WriteStream extends stream.Writable { + /** + * Closes `writeStream`. Optionally accepts a + * callback that will be executed once the `writeStream`is closed. + * @since v0.9.4 + */ + close(callback?: (err?: ErrnoException | null) => void): void; + /** + * The number of bytes written so far. Does not include data that is still queued + * for writing. + * @since v0.4.7 + */ + bytesWritten: number; + /** + * The path to the file the stream is writing to as specified in the first + * argument to {@link createWriteStream}. If `path` is passed as a string, then`writeStream.path` will be a string. If `path` is passed as a `Buffer`, then`writeStream.path` will be a + * `Buffer`. + * @since v0.1.93 + */ + path: string | Buffer; + /** + * This property is `true` if the underlying file has not been opened yet, + * i.e. before the `'ready'` event is emitted. + * @since v11.2.0 + */ + pending: boolean; + /** + * events.EventEmitter + * 1. open + * 2. close + * 3. ready + */ + addListener(event: "close", listener: () => void): this; + addListener(event: "drain", listener: () => void): this; + addListener(event: "error", listener: (err: Error) => void): this; + addListener(event: "finish", listener: () => void): this; + addListener(event: "open", listener: (fd: number) => void): this; + addListener(event: "pipe", listener: (src: stream.Readable) => void): this; + addListener(event: "ready", listener: () => void): this; + addListener(event: "unpipe", listener: (src: stream.Readable) => void): this; + addListener(event: string | symbol, listener: (...args: any[]) => void): this; + on(event: "close", listener: () => void): this; + on(event: "drain", listener: () => void): this; + on(event: "error", listener: (err: Error) => void): this; + on(event: "finish", listener: () => void): this; + on(event: "open", listener: (fd: number) => void): this; + on(event: "pipe", listener: (src: stream.Readable) => void): this; + on(event: "ready", listener: () => void): this; + on(event: "unpipe", listener: (src: stream.Readable) => void): this; + on(event: string | symbol, listener: (...args: any[]) => void): this; + once(event: "close", listener: () => void): this; + once(event: "drain", listener: () => void): this; + once(event: "error", listener: (err: Error) => void): this; + once(event: "finish", listener: () => void): this; + once(event: "open", listener: (fd: number) => void): this; + once(event: "pipe", listener: (src: stream.Readable) => void): this; + once(event: "ready", listener: () => void): this; + once(event: "unpipe", listener: (src: stream.Readable) => void): this; + once(event: string | symbol, listener: (...args: any[]) => void): this; + prependListener(event: "close", listener: () => void): this; + prependListener(event: "drain", listener: () => void): this; + prependListener(event: "error", listener: (err: Error) => void): this; + prependListener(event: "finish", listener: () => void): this; + prependListener(event: "open", listener: (fd: number) => void): this; + prependListener( + event: "pipe", + listener: (src: stream.Readable) => void, + ): this; + prependListener(event: "ready", listener: () => void): this; + prependListener( + event: "unpipe", + listener: (src: stream.Readable) => void, + ): this; + prependListener( + event: string | symbol, + listener: (...args: any[]) => void, + ): this; + prependOnceListener(event: "close", listener: () => void): this; + prependOnceListener(event: "drain", listener: () => void): this; + prependOnceListener(event: "error", listener: (err: Error) => void): this; + prependOnceListener(event: "finish", listener: () => void): this; + prependOnceListener(event: "open", listener: (fd: number) => void): this; + prependOnceListener( + event: "pipe", + listener: (src: stream.Readable) => void, + ): this; + prependOnceListener(event: "ready", listener: () => void): this; + prependOnceListener( + event: "unpipe", + listener: (src: stream.Readable) => void, + ): this; + prependOnceListener( + event: string | symbol, + listener: (...args: any[]) => void, + ): this; +} +interface StreamOptions { + flags?: string | undefined; + encoding?: BufferEncoding | undefined; + // @ts-ignore promises.FileHandle is not implemented + fd?: number | promises.FileHandle | undefined; + mode?: number | undefined; + autoClose?: boolean | undefined; + /** + * @default false + */ + emitClose?: boolean | undefined; + start?: number | undefined; + highWaterMark?: number | undefined; +} +interface ReadStreamOptions extends StreamOptions { + end?: number | undefined; +} +/** + * Unlike the 16 kb default `highWaterMark` for a `stream.Readable`, the stream + * returned by this method has a default `highWaterMark` of 64 kb. + * + * `options` can include `start` and `end` values to read a range of bytes from + * the file instead of the entire file. Both `start` and `end` are inclusive and + * start counting at 0, allowed values are in the + * \[0, [`Number.MAX_SAFE_INTEGER`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number/MAX_SAFE_INTEGER)\] range. If `fd` is specified and `start` is + * omitted or `undefined`, `fs.createReadStream()` reads sequentially from the + * current file position. The `encoding` can be any one of those accepted by `Buffer`. + * + * If `fd` is specified, `ReadStream` will ignore the `path` argument and will use + * the specified file descriptor. This means that no `'open'` event will be + * emitted. `fd` should be blocking; non-blocking `fd`s should be passed to `net.Socket`. + * + * If `fd` points to a character device that only supports blocking reads + * (such as keyboard or sound card), read operations do not finish until data is + * available. This can prevent the process from exiting and the stream from + * closing naturally. + * + * By default, the stream will emit a `'close'` event after it has been + * destroyed. Set the `emitClose` option to `false` to change this behavior. + * + * By providing the `fs` option, it is possible to override the corresponding `fs`implementations for `open`, `read`, and `close`. When providing the `fs` option, + * an override for `read` is required. If no `fd` is provided, an override for`open` is also required. If `autoClose` is `true`, an override for `close` is + * also required. + * + * ```js + * import { createReadStream } from "internal:deno_node/polyfills/internal/fs/fs"; + * + * // Create a stream from some character device. + * const stream = createReadStream('/dev/input/event0'); + * setTimeout(() => { + * stream.close(); // This may not close the stream. + * // Artificially marking end-of-stream, as if the underlying resource had + * // indicated end-of-file by itself, allows the stream to close. + * // This does not cancel pending read operations, and if there is such an + * // operation, the process may still not be able to exit successfully + * // until it finishes. + * stream.push(null); + * stream.read(0); + * }, 100); + * ``` + * + * If `autoClose` is false, then the file descriptor won't be closed, even if + * there's an error. It is the application's responsibility to close it and make + * sure there's no file descriptor leak. If `autoClose` is set to true (default + * behavior), on `'error'` or `'end'` the file descriptor will be closed + * automatically. + * + * `mode` sets the file mode (permission and sticky bits), but only if the + * file was created. + * + * An example to read the last 10 bytes of a file which is 100 bytes long: + * + * ```js + * import { createReadStream } from "internal:deno_node/polyfills/internal/fs/fs"; + * + * createReadStream('sample.txt', { start: 90, end: 99 }); + * ``` + * + * If `options` is a string, then it specifies the encoding. + * @since v0.1.31 + */ +export function createReadStream( + path: PathLike, + options?: BufferEncoding | ReadStreamOptions, +): ReadStream; +/** + * `options` may also include a `start` option to allow writing data at some + * position past the beginning of the file, allowed values are in the + * \[0, [`Number.MAX_SAFE_INTEGER`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number/MAX_SAFE_INTEGER)\] range. Modifying a file rather than + * replacing it may require the `flags` option to be set to `r+` rather than the + * default `w`. The `encoding` can be any one of those accepted by `Buffer`. + * + * If `autoClose` is set to true (default behavior) on `'error'` or `'finish'`the file descriptor will be closed automatically. If `autoClose` is false, + * then the file descriptor won't be closed, even if there's an error. + * It is the application's responsibility to close it and make sure there's no + * file descriptor leak. + * + * By default, the stream will emit a `'close'` event after it has been + * destroyed. Set the `emitClose` option to `false` to change this behavior. + * + * By providing the `fs` option it is possible to override the corresponding `fs`implementations for `open`, `write`, `writev` and `close`. Overriding `write()`without `writev()` can reduce + * performance as some optimizations (`_writev()`) + * will be disabled. When providing the `fs` option, overrides for at least one of`write` and `writev` are required. If no `fd` option is supplied, an override + * for `open` is also required. If `autoClose` is `true`, an override for `close`is also required. + * + * Like `fs.ReadStream`, if `fd` is specified, `fs.WriteStream` will ignore the`path` argument and will use the specified file descriptor. This means that no`'open'` event will be + * emitted. `fd` should be blocking; non-blocking `fd`s + * should be passed to `net.Socket`. + * + * If `options` is a string, then it specifies the encoding. + * @since v0.1.31 + */ +export function createWriteStream( + path: PathLike, + options?: BufferEncoding | StreamOptions, +): WriteStream; diff --git a/ext/node/polyfills/internal/fs/streams.mjs b/ext/node/polyfills/internal/fs/streams.mjs new file mode 100644 index 00000000000000..4d751df761c13c --- /dev/null +++ b/ext/node/polyfills/internal/fs/streams.mjs @@ -0,0 +1,494 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. +// Copyright Joyent, Inc. and Node.js contributors. All rights reserved. MIT license. + +import { ERR_INVALID_ARG_TYPE, ERR_OUT_OF_RANGE } from "internal:deno_node/polyfills/internal/errors.ts"; +import { kEmptyObject } from "internal:deno_node/polyfills/internal/util.mjs"; +import { deprecate } from "internal:deno_node/polyfills/util.ts"; +import { validateFunction, validateInteger } from "internal:deno_node/polyfills/internal/validators.mjs"; +import { errorOrDestroy } from "internal:deno_node/polyfills/internal/streams/destroy.mjs"; +import { open as fsOpen } from "internal:deno_node/polyfills/_fs/_fs_open.ts"; +import { read as fsRead } from "internal:deno_node/polyfills/_fs/_fs_read.ts"; +import { write as fsWrite } from "internal:deno_node/polyfills/_fs/_fs_write.mjs"; +import { writev as fsWritev } from "internal:deno_node/polyfills/_fs/_fs_writev.mjs"; +import { close as fsClose } from "internal:deno_node/polyfills/_fs/_fs_close.ts"; +import { Buffer } from "internal:deno_node/polyfills/buffer.ts"; +import { + copyObject, + getOptions, + getValidatedFd, + validatePath, +} from "internal:deno_node/polyfills/internal/fs/utils.mjs"; +import { finished, Readable, Writable } from "internal:deno_node/polyfills/stream.ts"; +import { toPathIfFileURL } from "internal:deno_node/polyfills/internal/url.ts"; +import { nextTick } from "internal:deno_node/polyfills/_next_tick.ts"; +const kIoDone = Symbol("kIoDone"); +const kIsPerformingIO = Symbol("kIsPerformingIO"); + +const kFs = Symbol("kFs"); + +function _construct(callback) { + // deno-lint-ignore no-this-alias + const stream = this; + if (typeof stream.fd === "number") { + callback(); + return; + } + + if (stream.open !== openWriteFs && stream.open !== openReadFs) { + // Backwards compat for monkey patching open(). + const orgEmit = stream.emit; + stream.emit = function (...args) { + if (args[0] === "open") { + this.emit = orgEmit; + callback(); + Reflect.apply(orgEmit, this, args); + } else if (args[0] === "error") { + this.emit = orgEmit; + callback(args[1]); + } else { + Reflect.apply(orgEmit, this, args); + } + }; + stream.open(); + } else { + stream[kFs].open( + stream.path.toString(), + stream.flags, + stream.mode, + (er, fd) => { + if (er) { + callback(er); + } else { + stream.fd = fd; + callback(); + stream.emit("open", stream.fd); + stream.emit("ready"); + } + }, + ); + } +} + +function close(stream, err, cb) { + if (!stream.fd) { + cb(err); + } else { + stream[kFs].close(stream.fd, (er) => { + cb(er || err); + }); + stream.fd = null; + } +} + +function importFd(stream, options) { + if (typeof options.fd === "number") { + // When fd is a raw descriptor, we must keep our fingers crossed + // that the descriptor won't get closed, or worse, replaced with + // another one + // https://github.com/nodejs/node/issues/35862 + if (stream instanceof ReadStream) { + stream[kFs] = options.fs || { read: fsRead, close: fsClose }; + } + if (stream instanceof WriteStream) { + stream[kFs] = options.fs || + { write: fsWrite, writev: fsWritev, close: fsClose }; + } + return options.fd; + } + + throw new ERR_INVALID_ARG_TYPE("options.fd", ["number"], options.fd); +} + +export function ReadStream(path, options) { + if (!(this instanceof ReadStream)) { + return new ReadStream(path, options); + } + + // A little bit bigger buffer and water marks by default + options = copyObject(getOptions(options, kEmptyObject)); + if (options.highWaterMark === undefined) { + options.highWaterMark = 64 * 1024; + } + + if (options.autoDestroy === undefined) { + options.autoDestroy = false; + } + + if (options.fd == null) { + this.fd = null; + this[kFs] = options.fs || { open: fsOpen, read: fsRead, close: fsClose }; + validateFunction(this[kFs].open, "options.fs.open"); + + // Path will be ignored when fd is specified, so it can be falsy + this.path = toPathIfFileURL(path); + this.flags = options.flags === undefined ? "r" : options.flags; + this.mode = options.mode === undefined ? 0o666 : options.mode; + + validatePath(this.path); + } else { + this.fd = getValidatedFd(importFd(this, options)); + } + + options.autoDestroy = options.autoClose === undefined + ? true + : options.autoClose; + + validateFunction(this[kFs].read, "options.fs.read"); + + if (options.autoDestroy) { + validateFunction(this[kFs].close, "options.fs.close"); + } + + this.start = options.start; + this.end = options.end ?? Infinity; + this.pos = undefined; + this.bytesRead = 0; + this[kIsPerformingIO] = false; + + if (this.start !== undefined) { + validateInteger(this.start, "start", 0); + + this.pos = this.start; + } + + if (this.end !== Infinity) { + validateInteger(this.end, "end", 0); + + if (this.start !== undefined && this.start > this.end) { + throw new ERR_OUT_OF_RANGE( + "start", + `<= "end" (here: ${this.end})`, + this.start, + ); + } + } + + Reflect.apply(Readable, this, [options]); +} + +Object.setPrototypeOf(ReadStream.prototype, Readable.prototype); +Object.setPrototypeOf(ReadStream, Readable); + +Object.defineProperty(ReadStream.prototype, "autoClose", { + get() { + return this._readableState.autoDestroy; + }, + set(val) { + this._readableState.autoDestroy = val; + }, +}); + +const openReadFs = deprecate( + function () { + // Noop. + }, + "ReadStream.prototype.open() is deprecated", + "DEP0135", +); +ReadStream.prototype.open = openReadFs; + +ReadStream.prototype._construct = _construct; + +ReadStream.prototype._read = async function (n) { + n = this.pos !== undefined + ? Math.min(this.end - this.pos + 1, n) + : Math.min(this.end - this.bytesRead + 1, n); + + if (n <= 0) { + this.push(null); + return; + } + + const buf = Buffer.allocUnsafeSlow(n); + + let error = null; + let bytesRead = null; + let buffer = undefined; + + this[kIsPerformingIO] = true; + + await new Promise((resolve) => { + this[kFs] + .read( + this.fd, + buf, + 0, + n, + this.pos ?? null, + (_er, _bytesRead, _buf) => { + error = _er; + bytesRead = _bytesRead; + buffer = _buf; + return resolve(true); + }, + ); + }); + + this[kIsPerformingIO] = false; + + // Tell ._destroy() that it's safe to close the fd now. + if (this.destroyed) { + this.emit(kIoDone, error); + return; + } + + if (error) { + errorOrDestroy(this, error); + } else if ( + typeof bytesRead === "number" && + bytesRead > 0 + ) { + if (this.pos !== undefined) { + this.pos += bytesRead; + } + + this.bytesRead += bytesRead; + + if (bytesRead !== buffer.length) { + // Slow path. Shrink to fit. + // Copy instead of slice so that we don't retain + // large backing buffer for small reads. + const dst = Buffer.allocUnsafeSlow(bytesRead); + buffer.copy(dst, 0, 0, bytesRead); + buffer = dst; + } + + this.push(buffer); + } else { + this.push(null); + } +}; + +ReadStream.prototype._destroy = function (err, cb) { + // Usually for async IO it is safe to close a file descriptor + // even when there are pending operations. However, due to platform + // differences file IO is implemented using synchronous operations + // running in a thread pool. Therefore, file descriptors are not safe + // to close while used in a pending read or write operation. Wait for + // any pending IO (kIsPerformingIO) to complete (kIoDone). + if (this[kIsPerformingIO]) { + this.once(kIoDone, (er) => close(this, err || er, cb)); + } else { + close(this, err, cb); + } +}; + +ReadStream.prototype.close = function (cb) { + if (typeof cb === "function") finished(this, cb); + this.destroy(); +}; + +Object.defineProperty(ReadStream.prototype, "pending", { + get() { + return this.fd === null; + }, + configurable: true, +}); + +export function WriteStream(path, options) { + if (!(this instanceof WriteStream)) { + return new WriteStream(path, options); + } + + options = copyObject(getOptions(options, kEmptyObject)); + + // Only buffers are supported. + options.decodeStrings = true; + + if (options.fd == null) { + this.fd = null; + this[kFs] = options.fs || + { open: fsOpen, write: fsWrite, writev: fsWritev, close: fsClose }; + validateFunction(this[kFs].open, "options.fs.open"); + + // Path will be ignored when fd is specified, so it can be falsy + this.path = toPathIfFileURL(path); + this.flags = options.flags === undefined ? "w" : options.flags; + this.mode = options.mode === undefined ? 0o666 : options.mode; + + validatePath(this.path); + } else { + this.fd = getValidatedFd(importFd(this, options)); + } + + options.autoDestroy = options.autoClose === undefined + ? true + : options.autoClose; + + if (!this[kFs].write && !this[kFs].writev) { + throw new ERR_INVALID_ARG_TYPE( + "options.fs.write", + "function", + this[kFs].write, + ); + } + + if (this[kFs].write) { + validateFunction(this[kFs].write, "options.fs.write"); + } + + if (this[kFs].writev) { + validateFunction(this[kFs].writev, "options.fs.writev"); + } + + if (options.autoDestroy) { + validateFunction(this[kFs].close, "options.fs.close"); + } + + // It's enough to override either, in which case only one will be used. + if (!this[kFs].write) { + this._write = null; + } + if (!this[kFs].writev) { + this._writev = null; + } + + this.start = options.start; + this.pos = undefined; + this.bytesWritten = 0; + this[kIsPerformingIO] = false; + + if (this.start !== undefined) { + validateInteger(this.start, "start", 0); + + this.pos = this.start; + } + + Reflect.apply(Writable, this, [options]); + + if (options.encoding) { + this.setDefaultEncoding(options.encoding); + } +} + +Object.setPrototypeOf(WriteStream.prototype, Writable.prototype); +Object.setPrototypeOf(WriteStream, Writable); + +Object.defineProperty(WriteStream.prototype, "autoClose", { + get() { + return this._writableState.autoDestroy; + }, + set(val) { + this._writableState.autoDestroy = val; + }, +}); + +const openWriteFs = deprecate( + function () { + // Noop. + }, + "WriteStream.prototype.open() is deprecated", + "DEP0135", +); +WriteStream.prototype.open = openWriteFs; + +WriteStream.prototype._construct = _construct; + +WriteStream.prototype._write = function (data, _encoding, cb) { + this[kIsPerformingIO] = true; + this[kFs].write(this.fd, data, 0, data.length, this.pos, (er, bytes) => { + this[kIsPerformingIO] = false; + if (this.destroyed) { + // Tell ._destroy() that it's safe to close the fd now. + cb(er); + return this.emit(kIoDone, er); + } + + if (er) { + return cb(er); + } + + this.bytesWritten += bytes; + cb(); + }); + + if (this.pos !== undefined) { + this.pos += data.length; + } +}; + +WriteStream.prototype._writev = function (data, cb) { + const len = data.length; + const chunks = new Array(len); + let size = 0; + + for (let i = 0; i < len; i++) { + const chunk = data[i].chunk; + + chunks[i] = chunk; + size += chunk.length; + } + + this[kIsPerformingIO] = true; + this[kFs].writev(this.fd, chunks, this.pos ?? null, (er, bytes) => { + this[kIsPerformingIO] = false; + if (this.destroyed) { + // Tell ._destroy() that it's safe to close the fd now. + cb(er); + return this.emit(kIoDone, er); + } + + if (er) { + return cb(er); + } + + this.bytesWritten += bytes; + cb(); + }); + + if (this.pos !== undefined) { + this.pos += size; + } +}; + +WriteStream.prototype._destroy = function (err, cb) { + // Usually for async IO it is safe to close a file descriptor + // even when there are pending operations. However, due to platform + // differences file IO is implemented using synchronous operations + // running in a thread pool. Therefore, file descriptors are not safe + // to close while used in a pending read or write operation. Wait for + // any pending IO (kIsPerformingIO) to complete (kIoDone). + if (this[kIsPerformingIO]) { + this.once(kIoDone, (er) => close(this, err || er, cb)); + } else { + close(this, err, cb); + } +}; + +WriteStream.prototype.close = function (cb) { + if (cb) { + if (this.closed) { + nextTick(cb); + return; + } + this.on("close", cb); + } + + // If we are not autoClosing, we should call + // destroy on 'finish'. + if (!this.autoClose) { + this.on("finish", this.destroy); + } + + // We use end() instead of destroy() because of + // https://github.com/nodejs/node/issues/2006 + this.end(); +}; + +// There is no shutdown() for files. +WriteStream.prototype.destroySoon = WriteStream.prototype.end; + +Object.defineProperty(WriteStream.prototype, "pending", { + get() { + return this.fd === null; + }, + configurable: true, +}); + +export function createReadStream(path, options) { + return new ReadStream(path, options); +} + +export function createWriteStream(path, options) { + return new WriteStream(path, options); +} diff --git a/ext/node/polyfills/internal/fs/utils.mjs b/ext/node/polyfills/internal/fs/utils.mjs new file mode 100644 index 00000000000000..9d74c5eee34e23 --- /dev/null +++ b/ext/node/polyfills/internal/fs/utils.mjs @@ -0,0 +1,1045 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. +"use strict"; + +import { Buffer } from "internal:deno_node/polyfills/buffer.ts"; +import { + ERR_FS_EISDIR, + ERR_FS_INVALID_SYMLINK_TYPE, + ERR_INVALID_ARG_TYPE, + ERR_INVALID_ARG_VALUE, + ERR_OUT_OF_RANGE, + hideStackFrames, + uvException, +} from "internal:deno_node/polyfills/internal/errors.ts"; + +import { + isArrayBufferView, + isBigUint64Array, + isDate, + isUint8Array, +} from "internal:deno_node/polyfills/internal/util/types.ts"; +import { once } from "internal:deno_node/polyfills/internal/util.mjs"; +import { deprecate } from "internal:deno_node/polyfills/util.ts"; +import { toPathIfFileURL } from "internal:deno_node/polyfills/internal/url.ts"; +import { + validateAbortSignal, + validateBoolean, + validateFunction, + validateInt32, + validateInteger, + validateObject, + validateUint32, +} from "internal:deno_node/polyfills/internal/validators.mjs"; +import pathModule from "internal:deno_node/polyfills/path.ts"; +const kType = Symbol("type"); +const kStats = Symbol("stats"); +import assert from "internal:deno_node/polyfills/internal/assert.mjs"; +import { lstat, lstatSync } from "internal:deno_node/polyfills/_fs/_fs_lstat.ts"; +import { stat, statSync } from "internal:deno_node/polyfills/_fs/_fs_stat.ts"; +import { isWindows } from "internal:deno_node/polyfills/_util/os.ts"; +import process from "internal:deno_node/polyfills/process.ts"; + +import { + fs as fsConstants, + os as osConstants, +} from "internal:deno_node/polyfills/internal_binding/constants.ts"; +const { + F_OK = 0, + W_OK = 0, + R_OK = 0, + X_OK = 0, + COPYFILE_EXCL, + COPYFILE_FICLONE, + COPYFILE_FICLONE_FORCE, + O_APPEND, + O_CREAT, + O_EXCL, + O_RDONLY, + O_RDWR, + O_SYNC, + O_TRUNC, + O_WRONLY, + S_IFBLK, + S_IFCHR, + S_IFDIR, + S_IFIFO, + S_IFLNK, + S_IFMT, + S_IFREG, + S_IFSOCK, + UV_FS_SYMLINK_DIR, + UV_FS_SYMLINK_JUNCTION, + UV_DIRENT_UNKNOWN, + UV_DIRENT_FILE, + UV_DIRENT_DIR, + UV_DIRENT_LINK, + UV_DIRENT_FIFO, + UV_DIRENT_SOCKET, + UV_DIRENT_CHAR, + UV_DIRENT_BLOCK, +} = fsConstants; + +// The access modes can be any of F_OK, R_OK, W_OK or X_OK. Some might not be +// available on specific systems. They can be used in combination as well +// (F_OK | R_OK | W_OK | X_OK). +const kMinimumAccessMode = Math.min(F_OK, W_OK, R_OK, X_OK); +const kMaximumAccessMode = F_OK | W_OK | R_OK | X_OK; + +const kDefaultCopyMode = 0; +// The copy modes can be any of COPYFILE_EXCL, COPYFILE_FICLONE or +// COPYFILE_FICLONE_FORCE. They can be used in combination as well +// (COPYFILE_EXCL | COPYFILE_FICLONE | COPYFILE_FICLONE_FORCE). +const kMinimumCopyMode = Math.min( + kDefaultCopyMode, + COPYFILE_EXCL, + COPYFILE_FICLONE, + COPYFILE_FICLONE_FORCE, +); +const kMaximumCopyMode = COPYFILE_EXCL | + COPYFILE_FICLONE | + COPYFILE_FICLONE_FORCE; + +// Most platforms don't allow reads or writes >= 2 GB. +// See https://github.com/libuv/libuv/pull/1501. +const kIoMaxLength = 2 ** 31 - 1; + +// Use 64kb in case the file type is not a regular file and thus do not know the +// actual file size. Increasing the value further results in more frequent over +// allocation for small files and consumes CPU time and memory that should be +// used else wise. +// Use up to 512kb per read otherwise to partition reading big files to prevent +// blocking other threads in case the available threads are all in use. +const kReadFileUnknownBufferLength = 64 * 1024; +const kReadFileBufferLength = 512 * 1024; + +const kWriteFileMaxChunkSize = 512 * 1024; + +export const kMaxUserId = 2 ** 32 - 1; + +export function assertEncoding(encoding) { + if (encoding && !Buffer.isEncoding(encoding)) { + const reason = "is invalid encoding"; + throw new ERR_INVALID_ARG_VALUE(encoding, "encoding", reason); + } +} + +export class Dirent { + constructor(name, type) { + this.name = name; + this[kType] = type; + } + + isDirectory() { + return this[kType] === UV_DIRENT_DIR; + } + + isFile() { + return this[kType] === UV_DIRENT_FILE; + } + + isBlockDevice() { + return this[kType] === UV_DIRENT_BLOCK; + } + + isCharacterDevice() { + return this[kType] === UV_DIRENT_CHAR; + } + + isSymbolicLink() { + return this[kType] === UV_DIRENT_LINK; + } + + isFIFO() { + return this[kType] === UV_DIRENT_FIFO; + } + + isSocket() { + return this[kType] === UV_DIRENT_SOCKET; + } +} + +class DirentFromStats extends Dirent { + constructor(name, stats) { + super(name, null); + this[kStats] = stats; + } +} + +for (const name of Reflect.ownKeys(Dirent.prototype)) { + if (name === "constructor") { + continue; + } + DirentFromStats.prototype[name] = function () { + return this[kStats][name](); + }; +} + +export function copyObject(source) { + const target = {}; + for (const key in source) { + target[key] = source[key]; + } + return target; +} + +const bufferSep = Buffer.from(pathModule.sep); + +function join(path, name) { + if ( + (typeof path === "string" || isUint8Array(path)) && + name === undefined + ) { + return path; + } + + if (typeof path === "string" && isUint8Array(name)) { + const pathBuffer = Buffer.from(pathModule.join(path, pathModule.sep)); + return Buffer.concat([pathBuffer, name]); + } + + if (typeof path === "string" && typeof name === "string") { + return pathModule.join(path, name); + } + + if (isUint8Array(path) && isUint8Array(name)) { + return Buffer.concat([path, bufferSep, name]); + } + + throw new ERR_INVALID_ARG_TYPE( + "path", + ["string", "Buffer"], + path, + ); +} + +export function getDirents(path, { 0: names, 1: types }, callback) { + let i; + if (typeof callback === "function") { + const len = names.length; + let toFinish = 0; + callback = once(callback); + for (i = 0; i < len; i++) { + const type = types[i]; + if (type === UV_DIRENT_UNKNOWN) { + const name = names[i]; + const idx = i; + toFinish++; + let filepath; + try { + filepath = join(path, name); + } catch (err) { + callback(err); + return; + } + lstat(filepath, (err, stats) => { + if (err) { + callback(err); + return; + } + names[idx] = new DirentFromStats(name, stats); + if (--toFinish === 0) { + callback(null, names); + } + }); + } else { + names[i] = new Dirent(names[i], types[i]); + } + } + if (toFinish === 0) { + callback(null, names); + } + } else { + const len = names.length; + for (i = 0; i < len; i++) { + names[i] = getDirent(path, names[i], types[i]); + } + return names; + } +} + +export function getDirent(path, name, type, callback) { + if (typeof callback === "function") { + if (type === UV_DIRENT_UNKNOWN) { + let filepath; + try { + filepath = join(path, name); + } catch (err) { + callback(err); + return; + } + lstat(filepath, (err, stats) => { + if (err) { + callback(err); + return; + } + callback(null, new DirentFromStats(name, stats)); + }); + } else { + callback(null, new Dirent(name, type)); + } + } else if (type === UV_DIRENT_UNKNOWN) { + const stats = lstatSync(join(path, name)); + return new DirentFromStats(name, stats); + } else { + return new Dirent(name, type); + } +} + +export function getOptions(options, defaultOptions) { + if ( + options === null || options === undefined || + typeof options === "function" + ) { + return defaultOptions; + } + + if (typeof options === "string") { + defaultOptions = { ...defaultOptions }; + defaultOptions.encoding = options; + options = defaultOptions; + } else if (typeof options !== "object") { + throw new ERR_INVALID_ARG_TYPE("options", ["string", "Object"], options); + } + + if (options.encoding !== "buffer") { + assertEncoding(options.encoding); + } + + if (options.signal !== undefined) { + validateAbortSignal(options.signal, "options.signal"); + } + return options; +} + +/** + * @param {InternalFSBinding.FSSyncContext} ctx + */ +export function handleErrorFromBinding(ctx) { + if (ctx.errno !== undefined) { // libuv error numbers + const err = uvException(ctx); + Error.captureStackTrace(err, handleErrorFromBinding); + throw err; + } + if (ctx.error !== undefined) { // Errors created in C++ land. + // TODO(joyeecheung): currently, ctx.error are encoding errors + // usually caused by memory problems. We need to figure out proper error + // code(s) for this. + Error.captureStackTrace(ctx.error, handleErrorFromBinding); + throw ctx.error; + } +} + +// Check if the path contains null types if it is a string nor Uint8Array, +// otherwise return silently. +export const nullCheck = hideStackFrames( + (path, propName, throwError = true) => { + const pathIsString = typeof path === "string"; + const pathIsUint8Array = isUint8Array(path); + + // We can only perform meaningful checks on strings and Uint8Arrays. + if ( + (!pathIsString && !pathIsUint8Array) || + (pathIsString && !path.includes("\u0000")) || + (pathIsUint8Array && !path.includes(0)) + ) { + return; + } + + const err = new ERR_INVALID_ARG_VALUE( + propName, + path, + "must be a string or Uint8Array without null bytes", + ); + if (throwError) { + throw err; + } + return err; + }, +); + +export function preprocessSymlinkDestination(path, type, linkPath) { + if (!isWindows) { + // No preprocessing is needed on Unix. + return path; + } + path = "" + path; + if (type === "junction") { + // Junctions paths need to be absolute and \\?\-prefixed. + // A relative target is relative to the link's parent directory. + path = pathModule.resolve(linkPath, "..", path); + return pathModule.toNamespacedPath(path); + } + if (pathModule.isAbsolute(path)) { + // If the path is absolute, use the \\?\-prefix to enable long filenames + return pathModule.toNamespacedPath(path); + } + // Windows symlinks don't tolerate forward slashes. + return path.replace(/\//g, "\\"); +} + +// Constructor for file stats. +function StatsBase( + dev, + mode, + nlink, + uid, + gid, + rdev, + blksize, + ino, + size, + blocks, +) { + this.dev = dev; + this.mode = mode; + this.nlink = nlink; + this.uid = uid; + this.gid = gid; + this.rdev = rdev; + this.blksize = blksize; + this.ino = ino; + this.size = size; + this.blocks = blocks; +} + +StatsBase.prototype.isDirectory = function () { + return this._checkModeProperty(S_IFDIR); +}; + +StatsBase.prototype.isFile = function () { + return this._checkModeProperty(S_IFREG); +}; + +StatsBase.prototype.isBlockDevice = function () { + return this._checkModeProperty(S_IFBLK); +}; + +StatsBase.prototype.isCharacterDevice = function () { + return this._checkModeProperty(S_IFCHR); +}; + +StatsBase.prototype.isSymbolicLink = function () { + return this._checkModeProperty(S_IFLNK); +}; + +StatsBase.prototype.isFIFO = function () { + return this._checkModeProperty(S_IFIFO); +}; + +StatsBase.prototype.isSocket = function () { + return this._checkModeProperty(S_IFSOCK); +}; + +const kNsPerMsBigInt = 10n ** 6n; +const kNsPerSecBigInt = 10n ** 9n; +const kMsPerSec = 10 ** 3; +const kNsPerMs = 10 ** 6; +function msFromTimeSpec(sec, nsec) { + return sec * kMsPerSec + nsec / kNsPerMs; +} + +function nsFromTimeSpecBigInt(sec, nsec) { + return sec * kNsPerSecBigInt + nsec; +} + +// The Date constructor performs Math.floor() to the timestamp. +// https://www.ecma-international.org/ecma-262/#sec-timeclip +// Since there may be a precision loss when the timestamp is +// converted to a floating point number, we manually round +// the timestamp here before passing it to Date(). +// Refs: https://github.com/nodejs/node/pull/12607 +function dateFromMs(ms) { + return new Date(Number(ms) + 0.5); +} + +export function BigIntStats( + dev, + mode, + nlink, + uid, + gid, + rdev, + blksize, + ino, + size, + blocks, + atimeNs, + mtimeNs, + ctimeNs, + birthtimeNs, +) { + Reflect.apply(StatsBase, this, [ + dev, + mode, + nlink, + uid, + gid, + rdev, + blksize, + ino, + size, + blocks, + ]); + + this.atimeMs = atimeNs / kNsPerMsBigInt; + this.mtimeMs = mtimeNs / kNsPerMsBigInt; + this.ctimeMs = ctimeNs / kNsPerMsBigInt; + this.birthtimeMs = birthtimeNs / kNsPerMsBigInt; + this.atimeNs = atimeNs; + this.mtimeNs = mtimeNs; + this.ctimeNs = ctimeNs; + this.birthtimeNs = birthtimeNs; + this.atime = dateFromMs(this.atimeMs); + this.mtime = dateFromMs(this.mtimeMs); + this.ctime = dateFromMs(this.ctimeMs); + this.birthtime = dateFromMs(this.birthtimeMs); +} + +Object.setPrototypeOf(BigIntStats.prototype, StatsBase.prototype); +Object.setPrototypeOf(BigIntStats, StatsBase); + +BigIntStats.prototype._checkModeProperty = function (property) { + if ( + isWindows && (property === S_IFIFO || property === S_IFBLK || + property === S_IFSOCK) + ) { + return false; // Some types are not available on Windows + } + return (this.mode & BigInt(S_IFMT)) === BigInt(property); +}; + +export function Stats( + dev, + mode, + nlink, + uid, + gid, + rdev, + blksize, + ino, + size, + blocks, + atimeMs, + mtimeMs, + ctimeMs, + birthtimeMs, +) { + StatsBase.call( + this, + dev, + mode, + nlink, + uid, + gid, + rdev, + blksize, + ino, + size, + blocks, + ); + this.atimeMs = atimeMs; + this.mtimeMs = mtimeMs; + this.ctimeMs = ctimeMs; + this.birthtimeMs = birthtimeMs; + this.atime = dateFromMs(atimeMs); + this.mtime = dateFromMs(mtimeMs); + this.ctime = dateFromMs(ctimeMs); + this.birthtime = dateFromMs(birthtimeMs); +} + +Object.setPrototypeOf(Stats.prototype, StatsBase.prototype); +Object.setPrototypeOf(Stats, StatsBase); + +// HACK: Workaround for https://github.com/standard-things/esm/issues/821. +// TODO(ronag): Remove this as soon as `esm` publishes a fixed version. +Stats.prototype.isFile = StatsBase.prototype.isFile; + +Stats.prototype._checkModeProperty = function (property) { + if ( + isWindows && (property === S_IFIFO || property === S_IFBLK || + property === S_IFSOCK) + ) { + return false; // Some types are not available on Windows + } + return (this.mode & S_IFMT) === property; +}; + +/** + * @param {Float64Array | BigUint64Array} stats + * @param {number} offset + * @returns + */ +export function getStatsFromBinding(stats, offset = 0) { + if (isBigUint64Array(stats)) { + return new BigIntStats( + stats[0 + offset], + stats[1 + offset], + stats[2 + offset], + stats[3 + offset], + stats[4 + offset], + stats[5 + offset], + stats[6 + offset], + stats[7 + offset], + stats[8 + offset], + stats[9 + offset], + nsFromTimeSpecBigInt(stats[10 + offset], stats[11 + offset]), + nsFromTimeSpecBigInt(stats[12 + offset], stats[13 + offset]), + nsFromTimeSpecBigInt(stats[14 + offset], stats[15 + offset]), + nsFromTimeSpecBigInt(stats[16 + offset], stats[17 + offset]), + ); + } + return new Stats( + stats[0 + offset], + stats[1 + offset], + stats[2 + offset], + stats[3 + offset], + stats[4 + offset], + stats[5 + offset], + stats[6 + offset], + stats[7 + offset], + stats[8 + offset], + stats[9 + offset], + msFromTimeSpec(stats[10 + offset], stats[11 + offset]), + msFromTimeSpec(stats[12 + offset], stats[13 + offset]), + msFromTimeSpec(stats[14 + offset], stats[15 + offset]), + msFromTimeSpec(stats[16 + offset], stats[17 + offset]), + ); +} + +export function stringToFlags(flags, name = "flags") { + if (typeof flags === "number") { + validateInt32(flags, name); + return flags; + } + + if (flags == null) { + return O_RDONLY; + } + + switch (flags) { + case "r": + return O_RDONLY; + case "rs": // Fall through. + case "sr": + return O_RDONLY | O_SYNC; + case "r+": + return O_RDWR; + case "rs+": // Fall through. + case "sr+": + return O_RDWR | O_SYNC; + + case "w": + return O_TRUNC | O_CREAT | O_WRONLY; + case "wx": // Fall through. + case "xw": + return O_TRUNC | O_CREAT | O_WRONLY | O_EXCL; + + case "w+": + return O_TRUNC | O_CREAT | O_RDWR; + case "wx+": // Fall through. + case "xw+": + return O_TRUNC | O_CREAT | O_RDWR | O_EXCL; + + case "a": + return O_APPEND | O_CREAT | O_WRONLY; + case "ax": // Fall through. + case "xa": + return O_APPEND | O_CREAT | O_WRONLY | O_EXCL; + case "as": // Fall through. + case "sa": + return O_APPEND | O_CREAT | O_WRONLY | O_SYNC; + + case "a+": + return O_APPEND | O_CREAT | O_RDWR; + case "ax+": // Fall through. + case "xa+": + return O_APPEND | O_CREAT | O_RDWR | O_EXCL; + case "as+": // Fall through. + case "sa+": + return O_APPEND | O_CREAT | O_RDWR | O_SYNC; + } + + throw new ERR_INVALID_ARG_VALUE("flags", flags); +} + +export const stringToSymlinkType = hideStackFrames((type) => { + let flags = 0; + if (typeof type === "string") { + switch (type) { + case "dir": + flags |= UV_FS_SYMLINK_DIR; + break; + case "junction": + flags |= UV_FS_SYMLINK_JUNCTION; + break; + case "file": + break; + default: + throw new ERR_FS_INVALID_SYMLINK_TYPE(type); + } + } + return flags; +}); + +// converts Date or number to a fractional UNIX timestamp +export function toUnixTimestamp(time, name = "time") { + // eslint-disable-next-line eqeqeq + if (typeof time === "string" && +time == time) { + return +time; + } + if (Number.isFinite(time)) { + if (time < 0) { + return Date.now() / 1000; + } + return time; + } + if (isDate(time)) { + // Convert to 123.456 UNIX timestamp + return Date.getTime(time) / 1000; + } + throw new ERR_INVALID_ARG_TYPE(name, ["Date", "Time in seconds"], time); +} + +export const validateOffsetLengthRead = hideStackFrames( + (offset, length, bufferLength) => { + if (offset < 0) { + throw new ERR_OUT_OF_RANGE("offset", ">= 0", offset); + } + if (length < 0) { + throw new ERR_OUT_OF_RANGE("length", ">= 0", length); + } + if (offset + length > bufferLength) { + throw new ERR_OUT_OF_RANGE( + "length", + `<= ${bufferLength - offset}`, + length, + ); + } + }, +); + +export const validateOffsetLengthWrite = hideStackFrames( + (offset, length, byteLength) => { + if (offset > byteLength) { + throw new ERR_OUT_OF_RANGE("offset", `<= ${byteLength}`, offset); + } + + if (length > byteLength - offset) { + throw new ERR_OUT_OF_RANGE("length", `<= ${byteLength - offset}`, length); + } + + if (length < 0) { + throw new ERR_OUT_OF_RANGE("length", ">= 0", length); + } + + validateInt32(length, "length", 0); + }, +); + +export const validatePath = hideStackFrames((path, propName = "path") => { + if (typeof path !== "string" && !isUint8Array(path)) { + throw new ERR_INVALID_ARG_TYPE(propName, ["string", "Buffer", "URL"], path); + } + + const err = nullCheck(path, propName, false); + + if (err !== undefined) { + throw err; + } +}); + +export const getValidatedPath = hideStackFrames( + (fileURLOrPath, propName = "path") => { + const path = toPathIfFileURL(fileURLOrPath); + validatePath(path, propName); + return path; + }, +); + +export const getValidatedFd = hideStackFrames((fd, propName = "fd") => { + if (Object.is(fd, -0)) { + return 0; + } + + validateInt32(fd, propName, 0); + + return fd; +}); + +export const validateBufferArray = hideStackFrames( + (buffers, propName = "buffers") => { + if (!Array.isArray(buffers)) { + throw new ERR_INVALID_ARG_TYPE(propName, "ArrayBufferView[]", buffers); + } + + for (let i = 0; i < buffers.length; i++) { + if (!isArrayBufferView(buffers[i])) { + throw new ERR_INVALID_ARG_TYPE(propName, "ArrayBufferView[]", buffers); + } + } + + return buffers; + }, +); + +let nonPortableTemplateWarn = true; + +export function warnOnNonPortableTemplate(template) { + // Template strings passed to the mkdtemp() family of functions should not + // end with 'X' because they are handled inconsistently across platforms. + if (nonPortableTemplateWarn && template.endsWith("X")) { + process.emitWarning( + "mkdtemp() templates ending with X are not portable. " + + "For details see: https://nodejs.org/api/fs.html", + ); + nonPortableTemplateWarn = false; + } +} + +const defaultCpOptions = { + dereference: false, + errorOnExist: false, + filter: undefined, + force: true, + preserveTimestamps: false, + recursive: false, +}; + +const defaultRmOptions = { + recursive: false, + force: false, + retryDelay: 100, + maxRetries: 0, +}; + +const defaultRmdirOptions = { + retryDelay: 100, + maxRetries: 0, + recursive: false, +}; + +export const validateCpOptions = hideStackFrames((options) => { + if (options === undefined) { + return { ...defaultCpOptions }; + } + validateObject(options, "options"); + options = { ...defaultCpOptions, ...options }; + validateBoolean(options.dereference, "options.dereference"); + validateBoolean(options.errorOnExist, "options.errorOnExist"); + validateBoolean(options.force, "options.force"); + validateBoolean(options.preserveTimestamps, "options.preserveTimestamps"); + validateBoolean(options.recursive, "options.recursive"); + if (options.filter !== undefined) { + validateFunction(options.filter, "options.filter"); + } + return options; +}); + +export const validateRmOptions = hideStackFrames( + (path, options, expectDir, cb) => { + options = validateRmdirOptions(options, defaultRmOptions); + validateBoolean(options.force, "options.force"); + + stat(path, (err, stats) => { + if (err) { + if (options.force && err.code === "ENOENT") { + return cb(null, options); + } + return cb(err, options); + } + + if (expectDir && !stats.isDirectory()) { + return cb(false); + } + + if (stats.isDirectory() && !options.recursive) { + return cb( + new ERR_FS_EISDIR({ + code: "EISDIR", + message: "is a directory", + path, + syscall: "rm", + errno: osConstants.errno.EISDIR, + }), + ); + } + return cb(null, options); + }); + }, +); + +export const validateRmOptionsSync = hideStackFrames( + (path, options, expectDir) => { + options = validateRmdirOptions(options, defaultRmOptions); + validateBoolean(options.force, "options.force"); + + if (!options.force || expectDir || !options.recursive) { + const isDirectory = statSync(path, { throwIfNoEntry: !options.force }) + ?.isDirectory(); + + if (expectDir && !isDirectory) { + return false; + } + + if (isDirectory && !options.recursive) { + throw new ERR_FS_EISDIR({ + code: "EISDIR", + message: "is a directory", + path, + syscall: "rm", + errno: EISDIR, + }); + } + } + + return options; + }, +); + +let recursiveRmdirWarned = process.noDeprecation; +export function emitRecursiveRmdirWarning() { + if (!recursiveRmdirWarned) { + process.emitWarning( + "In future versions of Node.js, fs.rmdir(path, { recursive: true }) " + + "will be removed. Use fs.rm(path, { recursive: true }) instead", + "DeprecationWarning", + "DEP0147", + ); + recursiveRmdirWarned = true; + } +} + +export const validateRmdirOptions = hideStackFrames( + (options, defaults = defaultRmdirOptions) => { + if (options === undefined) { + return defaults; + } + validateObject(options, "options"); + + options = { ...defaults, ...options }; + + validateBoolean(options.recursive, "options.recursive"); + validateInt32(options.retryDelay, "options.retryDelay", 0); + validateUint32(options.maxRetries, "options.maxRetries"); + + return options; + }, +); + +export const getValidMode = hideStackFrames((mode, type) => { + let min = kMinimumAccessMode; + let max = kMaximumAccessMode; + let def = F_OK; + if (type === "copyFile") { + min = kMinimumCopyMode; + max = kMaximumCopyMode; + def = mode || kDefaultCopyMode; + } else { + assert(type === "access"); + } + if (mode == null) { + return def; + } + if (Number.isInteger(mode) && mode >= min && mode <= max) { + return mode; + } + if (typeof mode !== "number") { + throw new ERR_INVALID_ARG_TYPE("mode", "integer", mode); + } + throw new ERR_OUT_OF_RANGE( + "mode", + `an integer >= ${min} && <= ${max}`, + mode, + ); +}); + +export const validateStringAfterArrayBufferView = hideStackFrames( + (buffer, name) => { + if (typeof buffer === "string") { + return; + } + + if ( + typeof buffer === "object" && + buffer !== null && + typeof buffer.toString === "function" && + Object.prototype.hasOwnProperty.call(buffer, "toString") + ) { + return; + } + + throw new ERR_INVALID_ARG_TYPE( + name, + ["string", "Buffer", "TypedArray", "DataView"], + buffer, + ); + }, +); + +export const validatePosition = hideStackFrames((position) => { + if (typeof position === "number") { + validateInteger(position, "position"); + } else if (typeof position === "bigint") { + if (!(position >= -(2n ** 63n) && position <= 2n ** 63n - 1n)) { + throw new ERR_OUT_OF_RANGE( + "position", + `>= ${-(2n ** 63n)} && <= ${2n ** 63n - 1n}`, + position, + ); + } + } else { + throw new ERR_INVALID_ARG_TYPE("position", ["integer", "bigint"], position); + } +}); + +export const realpathCacheKey = Symbol("realpathCacheKey"); +export const constants = { + kIoMaxLength, + kMaxUserId, + kReadFileBufferLength, + kReadFileUnknownBufferLength, + kWriteFileMaxChunkSize, +}; + +export const showStringCoercionDeprecation = deprecate( + () => {}, + "Implicit coercion of objects with own toString property is deprecated.", + "DEP0162", +); + +export default { + constants, + assertEncoding, + BigIntStats, // for testing + copyObject, + Dirent, + emitRecursiveRmdirWarning, + getDirent, + getDirents, + getOptions, + getValidatedFd, + getValidatedPath, + getValidMode, + handleErrorFromBinding, + kMaxUserId, + nullCheck, + preprocessSymlinkDestination, + realpathCacheKey, + getStatsFromBinding, + showStringCoercionDeprecation, + stringToFlags, + stringToSymlinkType, + Stats, + toUnixTimestamp, + validateBufferArray, + validateCpOptions, + validateOffsetLengthRead, + validateOffsetLengthWrite, + validatePath, + validatePosition, + validateRmOptions, + validateRmOptionsSync, + validateRmdirOptions, + validateStringAfterArrayBufferView, + warnOnNonPortableTemplate, +}; diff --git a/ext/node/polyfills/internal/hide_stack_frames.ts b/ext/node/polyfills/internal/hide_stack_frames.ts new file mode 100644 index 00000000000000..e1a6e4c27e2f9c --- /dev/null +++ b/ext/node/polyfills/internal/hide_stack_frames.ts @@ -0,0 +1,16 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. + +// deno-lint-ignore no-explicit-any +type GenericFunction = (...args: any[]) => any; + +/** This function removes unnecessary frames from Node.js core errors. */ +export function hideStackFrames( + fn: T, +): T { + // We rename the functions that will be hidden to cut off the stacktrace + // at the outermost one. + const hidden = "__node_internal_" + fn.name; + Object.defineProperty(fn, "name", { value: hidden }); + + return fn; +} diff --git a/ext/node/polyfills/internal/http.ts b/ext/node/polyfills/internal/http.ts new file mode 100644 index 00000000000000..f541039dc7d5af --- /dev/null +++ b/ext/node/polyfills/internal/http.ts @@ -0,0 +1,38 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. +// Copyright Joyent and Node contributors. All rights reserved. MIT license. + +import { setUnrefTimeout } from "internal:deno_node/polyfills/timers.ts"; +import { notImplemented } from "internal:deno_node/polyfills/_utils.ts"; + +let utcCache: string | undefined; + +export function utcDate() { + if (!utcCache) cache(); + return utcCache; +} + +function cache() { + const d = new Date(); + utcCache = d.toUTCString(); + setUnrefTimeout(resetCache, 1000 - d.getMilliseconds()); +} + +function resetCache() { + utcCache = undefined; +} + +export function emitStatistics( + _statistics: { startTime: [number, number] } | null, +) { + notImplemented("internal/http.emitStatistics"); +} + +export const kOutHeaders = Symbol("kOutHeaders"); +export const kNeedDrain = Symbol("kNeedDrain"); + +export default { + utcDate, + emitStatistics, + kOutHeaders, + kNeedDrain, +}; diff --git a/ext/node/polyfills/internal/idna.ts b/ext/node/polyfills/internal/idna.ts new file mode 100644 index 00000000000000..dd7fe45a67787d --- /dev/null +++ b/ext/node/polyfills/internal/idna.ts @@ -0,0 +1,104 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. +// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. + +// Copyright Mathias Bynens + +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: + +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. + +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +// Adapted from https://github.com/mathiasbynens/punycode.js + +// TODO(cmorten): migrate punycode logic to "icu" internal binding and/or "url" +// internal module so there can be re-use within the "url" module etc. + +"use strict"; + +/** + * Creates an array containing the numeric code points of each Unicode + * character in the string. While JavaScript uses UCS-2 internally, + * this function will convert a pair of surrogate halves (each of which + * UCS-2 exposes as separate characters) into a single code point, + * matching UTF-16. + * + * @param str The Unicode input string (UCS-2). + * @return The new array of code points. + */ +function ucs2decode(str: string) { + const output = []; + let counter = 0; + const length = str.length; + + while (counter < length) { + const value = str.charCodeAt(counter++); + + if (value >= 0xD800 && value <= 0xDBFF && counter < length) { + // It's a high surrogate, and there is a next character. + const extra = str.charCodeAt(counter++); + + if ((extra & 0xFC00) == 0xDC00) { // Low surrogate. + output.push(((value & 0x3FF) << 10) + (extra & 0x3FF) + 0x10000); + } else { + // It's an unmatched surrogate; only append this code unit, in case the + // next code unit is the high surrogate of a surrogate pair. + output.push(value); + counter--; + } + } else { + output.push(value); + } + } + + return output; +} + +/** + * Creates a string based on an array of numeric code points. + * @see `punycode.ucs2.decode` + * @memberOf punycode.ucs2 + * @name encode + * @param codePoints The array of numeric code points. + * @returns The new Unicode string (UCS-2). + */ +function ucs2encode(array: number[]) { + return String.fromCodePoint(...array); +} + +export const ucs2 = { + decode: ucs2decode, + encode: ucs2encode, +}; diff --git a/ext/node/polyfills/internal/net.ts b/ext/node/polyfills/internal/net.ts new file mode 100644 index 00000000000000..2e3a92dfd48e07 --- /dev/null +++ b/ext/node/polyfills/internal/net.ts @@ -0,0 +1,95 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. +// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. + +import { Buffer } from "internal:deno_node/polyfills/buffer.ts"; +import { uvException } from "internal:deno_node/polyfills/internal/errors.ts"; +import { writeBuffer } from "internal:deno_node/polyfills/internal_binding/node_file.ts"; + +// IPv4 Segment +const v4Seg = "(?:[0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])"; +const v4Str = `(${v4Seg}[.]){3}${v4Seg}`; +const IPv4Reg = new RegExp(`^${v4Str}$`); + +// IPv6 Segment +const v6Seg = "(?:[0-9a-fA-F]{1,4})"; +const IPv6Reg = new RegExp( + "^(" + + `(?:${v6Seg}:){7}(?:${v6Seg}|:)|` + + `(?:${v6Seg}:){6}(?:${v4Str}|:${v6Seg}|:)|` + + `(?:${v6Seg}:){5}(?::${v4Str}|(:${v6Seg}){1,2}|:)|` + + `(?:${v6Seg}:){4}(?:(:${v6Seg}){0,1}:${v4Str}|(:${v6Seg}){1,3}|:)|` + + `(?:${v6Seg}:){3}(?:(:${v6Seg}){0,2}:${v4Str}|(:${v6Seg}){1,4}|:)|` + + `(?:${v6Seg}:){2}(?:(:${v6Seg}){0,3}:${v4Str}|(:${v6Seg}){1,5}|:)|` + + `(?:${v6Seg}:){1}(?:(:${v6Seg}){0,4}:${v4Str}|(:${v6Seg}){1,6}|:)|` + + `(?::((?::${v6Seg}){0,5}:${v4Str}|(?::${v6Seg}){1,7}|:))` + + ")(%[0-9a-zA-Z-.:]{1,})?$", +); + +export function isIPv4(ip: string) { + return RegExp.prototype.test.call(IPv4Reg, ip); +} + +export function isIPv6(ip: string) { + return RegExp.prototype.test.call(IPv6Reg, ip); +} + +export function isIP(ip: string) { + if (isIPv4(ip)) { + return 4; + } + if (isIPv6(ip)) { + return 6; + } + + return 0; +} + +export function makeSyncWrite(fd: number) { + return function ( + // deno-lint-ignore no-explicit-any + this: any, + // deno-lint-ignore no-explicit-any + chunk: any, + enc: string, + cb: (err?: Error) => void, + ) { + if (enc !== "buffer") { + chunk = Buffer.from(chunk, enc); + } + + this._handle.bytesWritten += chunk.length; + + const ctx: { errno?: number } = {}; + writeBuffer(fd, chunk, 0, chunk.length, null, ctx); + + if (ctx.errno !== undefined) { + const ex = uvException(ctx); + ex.errno = ctx.errno; + + return cb(ex); + } + + cb(); + }; +} + +export const normalizedArgsSymbol = Symbol("normalizedArgs"); diff --git a/ext/node/polyfills/internal/normalize_encoding.mjs b/ext/node/polyfills/internal/normalize_encoding.mjs new file mode 100644 index 00000000000000..788c86c56da52a --- /dev/null +++ b/ext/node/polyfills/internal/normalize_encoding.mjs @@ -0,0 +1,72 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. + +export function normalizeEncoding(enc) { + if (enc == null || enc === "utf8" || enc === "utf-8") return "utf8"; + return slowCases(enc); +} + +export function slowCases(enc) { + switch (enc.length) { + case 4: + if (enc === "UTF8") return "utf8"; + if (enc === "ucs2" || enc === "UCS2") return "utf16le"; + enc = `${enc}`.toLowerCase(); + if (enc === "utf8") return "utf8"; + if (enc === "ucs2") return "utf16le"; + break; + case 3: + if ( + enc === "hex" || enc === "HEX" || + `${enc}`.toLowerCase() === "hex" + ) { + return "hex"; + } + break; + case 5: + if (enc === "ascii") return "ascii"; + if (enc === "ucs-2") return "utf16le"; + if (enc === "UTF-8") return "utf8"; + if (enc === "ASCII") return "ascii"; + if (enc === "UCS-2") return "utf16le"; + enc = `${enc}`.toLowerCase(); + if (enc === "utf-8") return "utf8"; + if (enc === "ascii") return "ascii"; + if (enc === "ucs-2") return "utf16le"; + break; + case 6: + if (enc === "base64") return "base64"; + if (enc === "latin1" || enc === "binary") return "latin1"; + if (enc === "BASE64") return "base64"; + if (enc === "LATIN1" || enc === "BINARY") return "latin1"; + enc = `${enc}`.toLowerCase(); + if (enc === "base64") return "base64"; + if (enc === "latin1" || enc === "binary") return "latin1"; + break; + case 7: + if ( + enc === "utf16le" || enc === "UTF16LE" || + `${enc}`.toLowerCase() === "utf16le" + ) { + return "utf16le"; + } + break; + case 8: + if ( + enc === "utf-16le" || enc === "UTF-16LE" || + `${enc}`.toLowerCase() === "utf-16le" + ) { + return "utf16le"; + } + break; + case 9: + if ( + enc === "base64url" || enc === "BASE64URL" || + `${enc}`.toLowerCase() === "base64url" + ) { + return "base64url"; + } + break; + default: + if (enc === "") return "utf8"; + } +} diff --git a/ext/node/polyfills/internal/options.ts b/ext/node/polyfills/internal/options.ts new file mode 100644 index 00000000000000..68ccf0528c859b --- /dev/null +++ b/ext/node/polyfills/internal/options.ts @@ -0,0 +1,45 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. +// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. + +import { getOptions } from "internal:deno_node/polyfills/internal_binding/node_options.ts"; + +let optionsMap: Map; + +function getOptionsFromBinding() { + if (!optionsMap) { + ({ options: optionsMap } = getOptions()); + } + + return optionsMap; +} + +export function getOptionValue(optionName: string) { + const options = getOptionsFromBinding(); + + if (optionName.startsWith("--no-")) { + const option = options.get("--" + optionName.slice(5)); + + return option && !option.value; + } + + return options.get(optionName)?.value; +} diff --git a/ext/node/polyfills/internal/primordials.mjs b/ext/node/polyfills/internal/primordials.mjs new file mode 100644 index 00000000000000..1639efdb500c37 --- /dev/null +++ b/ext/node/polyfills/internal/primordials.mjs @@ -0,0 +1,30 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. + +export const ArrayIsArray = Array.isArray; +export const ArrayPrototypeFilter = (that, ...args) => that.filter(...args); +export const ArrayPrototypeForEach = (that, ...args) => that.forEach(...args); +export const ArrayPrototypeIncludes = (that, ...args) => that.includes(...args); +export const ArrayPrototypeJoin = (that, ...args) => that.join(...args); +export const ArrayPrototypePush = (that, ...args) => that.push(...args); +export const ArrayPrototypeSlice = (that, ...args) => that.slice(...args); +export const ArrayPrototypeSome = (that, ...args) => that.some(...args); +export const ArrayPrototypeSort = (that, ...args) => that.sort(...args); +export const ArrayPrototypeUnshift = (that, ...args) => that.unshift(...args); +export const ObjectAssign = Object.assign; +export const ObjectCreate = Object.create; +export const ObjectPrototypeHasOwnProperty = Object.hasOwn; +export const RegExpPrototypeTest = (that, ...args) => that.test(...args); +export const RegExpPrototypeExec = RegExp.prototype.exec; +export const StringFromCharCode = String.fromCharCode; +export const StringPrototypeCharCodeAt = (that, ...args) => + that.charCodeAt(...args); +export const StringPrototypeEndsWith = (that, ...args) => + that.endsWith(...args); +export const StringPrototypeIncludes = (that, ...args) => + that.includes(...args); +export const StringPrototypeReplace = (that, ...args) => that.replace(...args); +export const StringPrototypeSlice = (that, ...args) => that.slice(...args); +export const StringPrototypeSplit = (that, ...args) => that.split(...args); +export const StringPrototypeStartsWith = (that, ...args) => + that.startsWith(...args); +export const StringPrototypeToUpperCase = (that) => that.toUpperCase(); diff --git a/ext/node/polyfills/internal/process/per_thread.mjs b/ext/node/polyfills/internal/process/per_thread.mjs new file mode 100644 index 00000000000000..3146083d1a2e90 --- /dev/null +++ b/ext/node/polyfills/internal/process/per_thread.mjs @@ -0,0 +1,272 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. +// Copyright Joyent, Inc. and Node.js contributors. All rights reserved. MIT license. + +const kInternal = Symbol("internal properties"); + +const replaceUnderscoresRegex = /_/g; +const leadingDashesRegex = /^--?/; +const trailingValuesRegex = /=.*$/; + +// This builds the initial process.allowedNodeEnvironmentFlags +// from data in the config binding. +export function buildAllowedFlags() { + const allowedNodeEnvironmentFlags = [ + "--track-heap-objects", + "--no-track-heap-objects", + "--node-snapshot", + "--no-node-snapshot", + "--require", + "--max-old-space-size", + "--trace-exit", + "--no-trace-exit", + "--disallow-code-generation-from-strings", + "--experimental-json-modules", + "--no-experimental-json-modules", + "--interpreted-frames-native-stack", + "--inspect-brk", + "--no-inspect-brk", + "--trace-tls", + "--no-trace-tls", + "--stack-trace-limit", + "--experimental-repl-await", + "--no-experimental-repl-await", + "--preserve-symlinks", + "--no-preserve-symlinks", + "--report-uncaught-exception", + "--no-report-uncaught-exception", + "--experimental-modules", + "--no-experimental-modules", + "--report-signal", + "--jitless", + "--inspect-port", + "--heapsnapshot-near-heap-limit", + "--tls-keylog", + "--force-context-aware", + "--no-force-context-aware", + "--napi-modules", + "--abort-on-uncaught-exception", + "--diagnostic-dir", + "--verify-base-objects", + "--no-verify-base-objects", + "--unhandled-rejections", + "--perf-basic-prof", + "--trace-atomics-wait", + "--no-trace-atomics-wait", + "--deprecation", + "--no-deprecation", + "--perf-basic-prof-only-functions", + "--perf-prof", + "--max-http-header-size", + "--report-on-signal", + "--no-report-on-signal", + "--throw-deprecation", + "--no-throw-deprecation", + "--warnings", + "--no-warnings", + "--force-fips", + "--no-force-fips", + "--pending-deprecation", + "--no-pending-deprecation", + "--input-type", + "--tls-max-v1.3", + "--no-tls-max-v1.3", + "--tls-min-v1.2", + "--no-tls-min-v1.2", + "--inspect", + "--no-inspect", + "--heapsnapshot-signal", + "--trace-warnings", + "--no-trace-warnings", + "--trace-event-categories", + "--experimental-worker", + "--tls-max-v1.2", + "--no-tls-max-v1.2", + "--perf-prof-unwinding-info", + "--preserve-symlinks-main", + "--no-preserve-symlinks-main", + "--policy-integrity", + "--experimental-wasm-modules", + "--no-experimental-wasm-modules", + "--node-memory-debug", + "--inspect-publish-uid", + "--tls-min-v1.3", + "--no-tls-min-v1.3", + "--experimental-specifier-resolution", + "--secure-heap", + "--tls-min-v1.0", + "--no-tls-min-v1.0", + "--redirect-warnings", + "--experimental-report", + "--trace-event-file-pattern", + "--trace-uncaught", + "--no-trace-uncaught", + "--experimental-loader", + "--http-parser", + "--dns-result-order", + "--trace-sigint", + "--no-trace-sigint", + "--secure-heap-min", + "--enable-fips", + "--no-enable-fips", + "--enable-source-maps", + "--no-enable-source-maps", + "--insecure-http-parser", + "--no-insecure-http-parser", + "--use-openssl-ca", + "--no-use-openssl-ca", + "--tls-cipher-list", + "--experimental-top-level-await", + "--no-experimental-top-level-await", + "--openssl-config", + "--icu-data-dir", + "--v8-pool-size", + "--report-on-fatalerror", + "--no-report-on-fatalerror", + "--title", + "--tls-min-v1.1", + "--no-tls-min-v1.1", + "--report-filename", + "--trace-deprecation", + "--no-trace-deprecation", + "--report-compact", + "--no-report-compact", + "--experimental-policy", + "--experimental-import-meta-resolve", + "--no-experimental-import-meta-resolve", + "--zero-fill-buffers", + "--no-zero-fill-buffers", + "--report-dir", + "--use-bundled-ca", + "--no-use-bundled-ca", + "--experimental-vm-modules", + "--no-experimental-vm-modules", + "--force-async-hooks-checks", + "--no-force-async-hooks-checks", + "--frozen-intrinsics", + "--no-frozen-intrinsics", + "--huge-max-old-generation-size", + "--disable-proto", + "--debug-arraybuffer-allocations", + "--no-debug-arraybuffer-allocations", + "--conditions", + "--experimental-wasi-unstable-preview1", + "--no-experimental-wasi-unstable-preview1", + "--trace-sync-io", + "--no-trace-sync-io", + "--use-largepages", + "--experimental-abortcontroller", + "--debug-port", + "--es-module-specifier-resolution", + "--prof-process", + "-C", + "--loader", + "--report-directory", + "-r", + "--trace-events-enabled", + ]; + + /* + function isAccepted(to) { + if (!to.startsWith("-") || to === "--") return true; + const recursiveExpansion = aliases.get(to); + if (recursiveExpansion) { + if (recursiveExpansion[0] === to) { + recursiveExpansion.splice(0, 1); + } + return recursiveExpansion.every(isAccepted); + } + return options.get(to).envVarSettings === kAllowedInEnvironment; + } + for (const { 0: from, 1: expansion } of aliases) { + if (expansion.every(isAccepted)) { + let canonical = from; + if (canonical.endsWith("=")) { + canonical = canonical.slice(0, canonical.length - 1); + } + if (canonical.endsWith(" ")) { + canonical = canonical.slice(0, canonical.length - 4); + } + allowedNodeEnvironmentFlags.push(canonical); + } + } + */ + + const trimLeadingDashes = (flag) => flag.replace(leadingDashesRegex, ""); + + // Save these for comparison against flags provided to + // process.allowedNodeEnvironmentFlags.has() which lack leading dashes. + const nodeFlags = allowedNodeEnvironmentFlags.map(trimLeadingDashes); + + class NodeEnvironmentFlagsSet extends Set { + constructor(array) { + super(); + this[kInternal] = { array }; + } + + add() { + // No-op, `Set` API compatible + return this; + } + + delete() { + // No-op, `Set` API compatible + return false; + } + + clear() { + // No-op, `Set` API compatible + } + + has(key) { + // This will return `true` based on various possible + // permutations of a flag, including present/missing leading + // dash(es) and/or underscores-for-dashes. + // Strips any values after `=`, inclusive. + // TODO(addaleax): It might be more flexible to run the option parser + // on a dummy option set and see whether it rejects the argument or + // not. + if (typeof key === "string") { + key = key.replace(replaceUnderscoresRegex, "-"); + if (leadingDashesRegex.test(key)) { + key = key.replace(trailingValuesRegex, ""); + return this[kInternal].array.includes(key); + } + return nodeFlags.includes(key); + } + return false; + } + + entries() { + this[kInternal].set ??= new Set(this[kInternal].array); + return this[kInternal].set.entries(); + } + + forEach(callback, thisArg = undefined) { + this[kInternal].array.forEach((v) => + Reflect.apply(callback, thisArg, [v, v, this]) + ); + } + + get size() { + return this[kInternal].array.length; + } + + values() { + this[kInternal].set ??= new Set(this[kInternal].array); + return this[kInternal].set.values(); + } + } + NodeEnvironmentFlagsSet.prototype.keys = + NodeEnvironmentFlagsSet + .prototype[Symbol.iterator] = + NodeEnvironmentFlagsSet.prototype.values; + + Object.freeze(NodeEnvironmentFlagsSet.prototype.constructor); + Object.freeze(NodeEnvironmentFlagsSet.prototype); + + return Object.freeze( + new NodeEnvironmentFlagsSet( + allowedNodeEnvironmentFlags, + ), + ); +} diff --git a/ext/node/polyfills/internal/querystring.ts b/ext/node/polyfills/internal/querystring.ts new file mode 100644 index 00000000000000..c00803afe83f2d --- /dev/null +++ b/ext/node/polyfills/internal/querystring.ts @@ -0,0 +1,92 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. +import { ERR_INVALID_URI } from "internal:deno_node/polyfills/internal/errors.ts"; + +export const hexTable = new Array(256); +for (let i = 0; i < 256; ++i) { + hexTable[i] = "%" + ((i < 16 ? "0" : "") + i.toString(16)).toUpperCase(); +} + +// deno-fmt-ignore +export const isHexTable = new Int8Array([ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 0 - 15 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 16 - 31 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 32 - 47 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, // 48 - 63 + 0, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 64 - 79 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 80 - 95 + 0, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 96 - 111 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 112 - 127 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 128 ... + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // ... 256 +]); + +export function encodeStr( + str: string, + noEscapeTable: Int8Array, + hexTable: string[], +): string { + const len = str.length; + if (len === 0) return ""; + + let out = ""; + let lastPos = 0; + + for (let i = 0; i < len; i++) { + let c = str.charCodeAt(i); + // ASCII + if (c < 0x80) { + if (noEscapeTable[c] === 1) continue; + if (lastPos < i) out += str.slice(lastPos, i); + lastPos = i + 1; + out += hexTable[c]; + continue; + } + + if (lastPos < i) out += str.slice(lastPos, i); + + // Multi-byte characters ... + if (c < 0x800) { + lastPos = i + 1; + out += hexTable[0xc0 | (c >> 6)] + hexTable[0x80 | (c & 0x3f)]; + continue; + } + if (c < 0xd800 || c >= 0xe000) { + lastPos = i + 1; + out += hexTable[0xe0 | (c >> 12)] + + hexTable[0x80 | ((c >> 6) & 0x3f)] + + hexTable[0x80 | (c & 0x3f)]; + continue; + } + // Surrogate pair + ++i; + + // This branch should never happen because all URLSearchParams entries + // should already be converted to USVString. But, included for + // completion's sake anyway. + if (i >= len) throw new ERR_INVALID_URI(); + + const c2 = str.charCodeAt(i) & 0x3ff; + + lastPos = i + 1; + c = 0x10000 + (((c & 0x3ff) << 10) | c2); + out += hexTable[0xf0 | (c >> 18)] + + hexTable[0x80 | ((c >> 12) & 0x3f)] + + hexTable[0x80 | ((c >> 6) & 0x3f)] + + hexTable[0x80 | (c & 0x3f)]; + } + if (lastPos === 0) return str; + if (lastPos < len) return out + str.slice(lastPos); + return out; +} + +export default { + hexTable, + encodeStr, + isHexTable, +}; diff --git a/ext/node/polyfills/internal/readline/callbacks.mjs b/ext/node/polyfills/internal/readline/callbacks.mjs new file mode 100644 index 00000000000000..3be88c89985677 --- /dev/null +++ b/ext/node/polyfills/internal/readline/callbacks.mjs @@ -0,0 +1,137 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. +// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. + +"use strict"; + +import { ERR_INVALID_ARG_VALUE, ERR_INVALID_CURSOR_POS } from "internal:deno_node/polyfills/internal/errors.ts"; + +import { validateFunction } from "internal:deno_node/polyfills/internal/validators.mjs"; + +import { CSI } from "internal:deno_node/polyfills/internal/readline/utils.mjs"; + +const { + kClearLine, + kClearScreenDown, + kClearToLineBeginning, + kClearToLineEnd, +} = CSI; + +/** + * moves the cursor to the x and y coordinate on the given stream + */ + +export function cursorTo(stream, x, y, callback) { + if (callback !== undefined) { + validateFunction(callback, "callback"); + } + + if (typeof y === "function") { + callback = y; + y = undefined; + } + + if (Number.isNaN(x)) throw new ERR_INVALID_ARG_VALUE("x", x); + if (Number.isNaN(y)) throw new ERR_INVALID_ARG_VALUE("y", y); + + if (stream == null || (typeof x !== "number" && typeof y !== "number")) { + if (typeof callback === "function") process.nextTick(callback, null); + return true; + } + + if (typeof x !== "number") throw new ERR_INVALID_CURSOR_POS(); + + const data = typeof y !== "number" ? CSI`${x + 1}G` : CSI`${y + 1};${x + 1}H`; + return stream.write(data, callback); +} + +/** + * moves the cursor relative to its current location + */ + +export function moveCursor(stream, dx, dy, callback) { + if (callback !== undefined) { + validateFunction(callback, "callback"); + } + + if (stream == null || !(dx || dy)) { + if (typeof callback === "function") process.nextTick(callback, null); + return true; + } + + let data = ""; + + if (dx < 0) { + data += CSI`${-dx}D`; + } else if (dx > 0) { + data += CSI`${dx}C`; + } + + if (dy < 0) { + data += CSI`${-dy}A`; + } else if (dy > 0) { + data += CSI`${dy}B`; + } + + return stream.write(data, callback); +} + +/** + * clears the current line the cursor is on: + * -1 for left of the cursor + * +1 for right of the cursor + * 0 for the entire line + */ + +export function clearLine(stream, dir, callback) { + if (callback !== undefined) { + validateFunction(callback, "callback"); + } + + if (stream === null || stream === undefined) { + if (typeof callback === "function") process.nextTick(callback, null); + return true; + } + + const type = dir < 0 + ? kClearToLineBeginning + : dir > 0 + ? kClearToLineEnd + : kClearLine; + return stream.write(type, callback); +} + +/** + * clears the screen from the current position of the cursor down + */ + +export function clearScreenDown(stream, callback) { + if (callback !== undefined) { + validateFunction(callback, "callback"); + } + + if (stream === null || stream === undefined) { + if (typeof callback === "function") process.nextTick(callback, null); + return true; + } + + return stream.write(kClearScreenDown, callback); +} diff --git a/ext/node/polyfills/internal/readline/emitKeypressEvents.mjs b/ext/node/polyfills/internal/readline/emitKeypressEvents.mjs new file mode 100644 index 00000000000000..7f68dac4756c49 --- /dev/null +++ b/ext/node/polyfills/internal/readline/emitKeypressEvents.mjs @@ -0,0 +1,106 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. +// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. + +import { charLengthAt, CSI, emitKeys } from "internal:deno_node/polyfills/internal/readline/utils.mjs"; +import { kSawKeyPress } from "internal:deno_node/polyfills/internal/readline/symbols.mjs"; +import { clearTimeout, setTimeout } from "internal:deno_node/polyfills/timers.ts"; + +const { + kEscape, +} = CSI; + +import { StringDecoder } from "internal:deno_node/polyfills/string_decoder.ts"; + +const KEYPRESS_DECODER = Symbol("keypress-decoder"); +const ESCAPE_DECODER = Symbol("escape-decoder"); + +// GNU readline library - keyseq-timeout is 500ms (default) +const ESCAPE_CODE_TIMEOUT = 500; + +/** + * accepts a readable Stream instance and makes it emit "keypress" events + */ + +export function emitKeypressEvents(stream, iface = {}) { + if (stream[KEYPRESS_DECODER]) return; + + stream[KEYPRESS_DECODER] = new StringDecoder("utf8"); + + stream[ESCAPE_DECODER] = emitKeys(stream); + stream[ESCAPE_DECODER].next(); + + const triggerEscape = () => stream[ESCAPE_DECODER].next(""); + const { escapeCodeTimeout = ESCAPE_CODE_TIMEOUT } = iface; + let timeoutId; + + function onData(input) { + if (stream.listenerCount("keypress") > 0) { + const string = stream[KEYPRESS_DECODER].write(input); + if (string) { + clearTimeout(timeoutId); + + // This supports characters of length 2. + iface[kSawKeyPress] = charLengthAt(string, 0) === string.length; + iface.isCompletionEnabled = false; + + let length = 0; + for (const character of string[Symbol.iterator]()) { + length += character.length; + if (length === string.length) { + iface.isCompletionEnabled = true; + } + + try { + stream[ESCAPE_DECODER].next(character); + // Escape letter at the tail position + if (length === string.length && character === kEscape) { + timeoutId = setTimeout(triggerEscape, escapeCodeTimeout); + } + } catch (err) { + // If the generator throws (it could happen in the `keypress` + // event), we need to restart it. + stream[ESCAPE_DECODER] = emitKeys(stream); + stream[ESCAPE_DECODER].next(); + throw err; + } + } + } + } else { + // Nobody's watching anyway + stream.removeListener("data", onData); + stream.on("newListener", onNewListener); + } + } + + function onNewListener(event) { + if (event === "keypress") { + stream.on("data", onData); + stream.removeListener("newListener", onNewListener); + } + } + + if (stream.listenerCount("keypress") > 0) { + stream.on("data", onData); + } else { + stream.on("newListener", onNewListener); + } +} diff --git a/ext/node/polyfills/internal/readline/interface.mjs b/ext/node/polyfills/internal/readline/interface.mjs new file mode 100644 index 00000000000000..41d05fbf26c3e3 --- /dev/null +++ b/ext/node/polyfills/internal/readline/interface.mjs @@ -0,0 +1,1223 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. +// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. + +// deno-lint-ignore-file camelcase no-inner-declarations no-this-alias + +import { ERR_INVALID_ARG_VALUE, ERR_USE_AFTER_CLOSE } from "internal:deno_node/polyfills/internal/errors.ts"; +import { + validateAbortSignal, + validateArray, + validateString, + validateUint32, +} from "internal:deno_node/polyfills/internal/validators.mjs"; +import { + // inspect, + getStringWidth, + stripVTControlCharacters, +} from "internal:deno_node/polyfills/internal/util/inspect.mjs"; +import EventEmitter from "internal:deno_node/polyfills/events.ts"; +import { emitKeypressEvents } from "internal:deno_node/polyfills/internal/readline/emitKeypressEvents.mjs"; +import { + charLengthAt, + charLengthLeft, + commonPrefix, + kSubstringSearch, +} from "internal:deno_node/polyfills/internal/readline/utils.mjs"; +import { clearScreenDown, cursorTo, moveCursor } from "internal:deno_node/polyfills/internal/readline/callbacks.mjs"; +import { Readable } from "internal:deno_node/polyfills/_stream.mjs"; + +import { StringDecoder } from "internal:deno_node/polyfills/string_decoder.ts"; +import { + kAddHistory, + kDecoder, + kDeleteLeft, + kDeleteLineLeft, + kDeleteLineRight, + kDeleteRight, + kDeleteWordLeft, + kDeleteWordRight, + kGetDisplayPos, + kHistoryNext, + kHistoryPrev, + kInsertString, + kLine, + kLine_buffer, + kMoveCursor, + kNormalWrite, + kOldPrompt, + kOnLine, + kPreviousKey, + kPrompt, + kQuestionCallback, + kRefreshLine, + kSawKeyPress, + kSawReturnAt, + kSetRawMode, + kTabComplete, + kTabCompleter, + kTtyWrite, + kWordLeft, + kWordRight, + kWriteToOutput, +} from "internal:deno_node/polyfills/internal/readline/symbols.mjs"; + +const kHistorySize = 30; +const kMincrlfDelay = 100; +// \r\n, \n, or \r followed by something other than \n +const lineEnding = /\r?\n|\r(?!\n)/; + +const kLineObjectStream = Symbol("line object stream"); +export const kQuestionCancel = Symbol("kQuestionCancel"); +export const kQuestion = Symbol("kQuestion"); + +// GNU readline library - keyseq-timeout is 500ms (default) +const ESCAPE_CODE_TIMEOUT = 500; + +export { + kAddHistory, + kDecoder, + kDeleteLeft, + kDeleteLineLeft, + kDeleteLineRight, + kDeleteRight, + kDeleteWordLeft, + kDeleteWordRight, + kGetDisplayPos, + kHistoryNext, + kHistoryPrev, + kInsertString, + kLine, + kLine_buffer, + kMoveCursor, + kNormalWrite, + kOldPrompt, + kOnLine, + kPreviousKey, + kPrompt, + kQuestionCallback, + kRefreshLine, + kSawKeyPress, + kSawReturnAt, + kSetRawMode, + kTabComplete, + kTabCompleter, + kTtyWrite, + kWordLeft, + kWordRight, + kWriteToOutput, +}; + +export function InterfaceConstructor(input, output, completer, terminal) { + this[kSawReturnAt] = 0; + // TODO(BridgeAR): Document this property. The name is not ideal, so we + // might want to expose an alias and document that instead. + this.isCompletionEnabled = true; + this[kSawKeyPress] = false; + this[kPreviousKey] = null; + this.escapeCodeTimeout = ESCAPE_CODE_TIMEOUT; + this.tabSize = 8; + Function.prototype.call(EventEmitter, this); + + let history; + let historySize; + let removeHistoryDuplicates = false; + let crlfDelay; + let prompt = "> "; + let signal; + + if (input?.input) { + // An options object was given + output = input.output; + completer = input.completer; + terminal = input.terminal; + history = input.history; + historySize = input.historySize; + signal = input.signal; + if (input.tabSize !== undefined) { + validateUint32(input.tabSize, "tabSize", true); + this.tabSize = input.tabSize; + } + removeHistoryDuplicates = input.removeHistoryDuplicates; + if (input.prompt !== undefined) { + prompt = input.prompt; + } + if (input.escapeCodeTimeout !== undefined) { + if (Number.isFinite(input.escapeCodeTimeout)) { + this.escapeCodeTimeout = input.escapeCodeTimeout; + } else { + throw new ERR_INVALID_ARG_VALUE( + "input.escapeCodeTimeout", + this.escapeCodeTimeout, + ); + } + } + + if (signal) { + validateAbortSignal(signal, "options.signal"); + } + + crlfDelay = input.crlfDelay; + input = input.input; + } + + if (completer !== undefined && typeof completer !== "function") { + throw new ERR_INVALID_ARG_VALUE("completer", completer); + } + + if (history === undefined) { + history = []; + } else { + validateArray(history, "history"); + } + + if (historySize === undefined) { + historySize = kHistorySize; + } + + if ( + typeof historySize !== "number" || + Number.isNaN(historySize) || + historySize < 0 + ) { + throw new ERR_INVALID_ARG_VALUE.RangeError("historySize", historySize); + } + + // Backwards compat; check the isTTY prop of the output stream + // when `terminal` was not specified + if (terminal === undefined && !(output === null || output === undefined)) { + terminal = !!output.isTTY; + } + + const self = this; + + this.line = ""; + this[kSubstringSearch] = null; + this.output = output; + this.input = input; + this.history = history; + this.historySize = historySize; + this.removeHistoryDuplicates = !!removeHistoryDuplicates; + this.crlfDelay = crlfDelay + ? Math.max(kMincrlfDelay, crlfDelay) + : kMincrlfDelay; + this.completer = completer; + + this.setPrompt(prompt); + + this.terminal = !!terminal; + + function onerror(err) { + self.emit("error", err); + } + + function ondata(data) { + self[kNormalWrite](data); + } + + function onend() { + if ( + typeof self[kLine_buffer] === "string" && + self[kLine_buffer].length > 0 + ) { + self.emit("line", self[kLine_buffer]); + } + self.close(); + } + + function ontermend() { + if (typeof self.line === "string" && self.line.length > 0) { + self.emit("line", self.line); + } + self.close(); + } + + function onkeypress(s, key) { + self[kTtyWrite](s, key); + if (key && key.sequence) { + // If the key.sequence is half of a surrogate pair + // (>= 0xd800 and <= 0xdfff), refresh the line so + // the character is displayed appropriately. + const ch = key.sequence.codePointAt(0); + if (ch >= 0xd800 && ch <= 0xdfff) self[kRefreshLine](); + } + } + + function onresize() { + self[kRefreshLine](); + } + + this[kLineObjectStream] = undefined; + + input.on("error", onerror); + + if (!this.terminal) { + function onSelfCloseWithoutTerminal() { + input.removeListener("data", ondata); + input.removeListener("error", onerror); + input.removeListener("end", onend); + } + + input.on("data", ondata); + input.on("end", onend); + self.once("close", onSelfCloseWithoutTerminal); + this[kDecoder] = new StringDecoder("utf8"); + } else { + function onSelfCloseWithTerminal() { + input.removeListener("keypress", onkeypress); + input.removeListener("error", onerror); + input.removeListener("end", ontermend); + if (output !== null && output !== undefined) { + output.removeListener("resize", onresize); + } + } + + emitKeypressEvents(input, this); + + // `input` usually refers to stdin + input.on("keypress", onkeypress); + input.on("end", ontermend); + + this[kSetRawMode](true); + this.terminal = true; + + // Cursor position on the line. + this.cursor = 0; + + this.historyIndex = -1; + + if (output !== null && output !== undefined) { + output.on("resize", onresize); + } + + self.once("close", onSelfCloseWithTerminal); + } + + if (signal) { + const onAborted = () => self.close(); + if (signal.aborted) { + process.nextTick(onAborted); + } else { + signal.addEventListener("abort", onAborted, { once: true }); + self.once("close", () => signal.removeEventListener("abort", onAborted)); + } + } + + // Current line + this.line = ""; + + input.resume(); +} + +Object.setPrototypeOf(InterfaceConstructor.prototype, EventEmitter.prototype); +Object.setPrototypeOf(InterfaceConstructor, EventEmitter); + +export class Interface extends InterfaceConstructor { + // eslint-disable-next-line no-useless-constructor + constructor(input, output, completer, terminal) { + super(input, output, completer, terminal); + } + get columns() { + if (this.output && this.output.columns) return this.output.columns; + return Infinity; + } + + /** + * Sets the prompt written to the output. + * @param {string} prompt + * @returns {void} + */ + setPrompt(prompt) { + this[kPrompt] = prompt; + } + + /** + * Returns the current prompt used by `rl.prompt()`. + * @returns {string} + */ + getPrompt() { + return this[kPrompt]; + } + + [kSetRawMode](mode) { + const wasInRawMode = this.input.isRaw; + + if (typeof this.input.setRawMode === "function") { + this.input.setRawMode(mode); + } + + return wasInRawMode; + } + + /** + * Writes the configured `prompt` to a new line in `output`. + * @param {boolean} [preserveCursor] + * @returns {void} + */ + prompt(preserveCursor) { + if (this.paused) this.resume(); + if (this.terminal && process.env.TERM !== "dumb") { + if (!preserveCursor) this.cursor = 0; + this[kRefreshLine](); + } else { + this[kWriteToOutput](this[kPrompt]); + } + } + + [kQuestion](query, cb) { + if (this.closed) { + throw new ERR_USE_AFTER_CLOSE("readline"); + } + if (this[kQuestionCallback]) { + this.prompt(); + } else { + this[kOldPrompt] = this[kPrompt]; + this.setPrompt(query); + this[kQuestionCallback] = cb; + this.prompt(); + } + } + + [kOnLine](line) { + if (this[kQuestionCallback]) { + const cb = this[kQuestionCallback]; + this[kQuestionCallback] = null; + this.setPrompt(this[kOldPrompt]); + cb(line); + } else { + this.emit("line", line); + } + } + + [kQuestionCancel]() { + if (this[kQuestionCallback]) { + this[kQuestionCallback] = null; + this.setPrompt(this[kOldPrompt]); + this.clearLine(); + } + } + + [kWriteToOutput](stringToWrite) { + validateString(stringToWrite, "stringToWrite"); + + if (this.output !== null && this.output !== undefined) { + this.output.write(stringToWrite); + } + } + + [kAddHistory]() { + if (this.line.length === 0) return ""; + + // If the history is disabled then return the line + if (this.historySize === 0) return this.line; + + // If the trimmed line is empty then return the line + if (this.line.trim().length === 0) return this.line; + + if (this.history.length === 0 || this.history[0] !== this.line) { + if (this.removeHistoryDuplicates) { + // Remove older history line if identical to new one + const dupIndex = this.history.indexOf(this.line); + if (dupIndex !== -1) this.history.splice(dupIndex, 1); + } + + this.history.unshift(this.line); + + // Only store so many + if (this.history.length > this.historySize) { + this.history.pop(); + } + } + + this.historyIndex = -1; + + // The listener could change the history object, possibly + // to remove the last added entry if it is sensitive and should + // not be persisted in the history, like a password + const line = this.history[0]; + + // Emit history event to notify listeners of update + this.emit("history", this.history); + + return line; + } + + [kRefreshLine]() { + // line length + const line = this[kPrompt] + this.line; + const dispPos = this[kGetDisplayPos](line); + const lineCols = dispPos.cols; + const lineRows = dispPos.rows; + + // cursor position + const cursorPos = this.getCursorPos(); + + // First move to the bottom of the current line, based on cursor pos + const prevRows = this.prevRows || 0; + if (prevRows > 0) { + moveCursor(this.output, 0, -prevRows); + } + + // Cursor to left edge. + cursorTo(this.output, 0); + // erase data + clearScreenDown(this.output); + + // Write the prompt and the current buffer content. + this[kWriteToOutput](line); + + // Force terminal to allocate a new line + if (lineCols === 0) { + this[kWriteToOutput](" "); + } + + // Move cursor to original position. + cursorTo(this.output, cursorPos.cols); + + const diff = lineRows - cursorPos.rows; + if (diff > 0) { + moveCursor(this.output, 0, -diff); + } + + this.prevRows = cursorPos.rows; + } + + /** + * Closes the `readline.Interface` instance. + * @returns {void} + */ + close() { + if (this.closed) return; + this.pause(); + if (this.terminal) { + this[kSetRawMode](false); + } + this.closed = true; + this.emit("close"); + } + + /** + * Pauses the `input` stream. + * @returns {void | Interface} + */ + pause() { + if (this.paused) return; + this.input.pause(); + this.paused = true; + this.emit("pause"); + return this; + } + + /** + * Resumes the `input` stream if paused. + * @returns {void | Interface} + */ + resume() { + if (!this.paused) return; + this.input.resume(); + this.paused = false; + this.emit("resume"); + return this; + } + + /** + * Writes either `data` or a `key` sequence identified by + * `key` to the `output`. + * @param {string} d + * @param {{ + * ctrl?: boolean; + * meta?: boolean; + * shift?: boolean; + * name?: string; + * }} [key] + * @returns {void} + */ + write(d, key) { + if (this.paused) this.resume(); + if (this.terminal) { + this[kTtyWrite](d, key); + } else { + this[kNormalWrite](d); + } + } + + [kNormalWrite](b) { + if (b === undefined) { + return; + } + let string = this[kDecoder].write(b); + if ( + this[kSawReturnAt] && + Date.now() - this[kSawReturnAt] <= this.crlfDelay + ) { + string = string.replace(/^\n/, ""); + this[kSawReturnAt] = 0; + } + + // Run test() on the new string chunk, not on the entire line buffer. + const newPartContainsEnding = lineEnding.test(string); + + if (this[kLine_buffer]) { + string = this[kLine_buffer] + string; + this[kLine_buffer] = null; + } + if (newPartContainsEnding) { + this[kSawReturnAt] = string.endsWith("\r") ? Date.now() : 0; + + // Got one or more newlines; process into "line" events + const lines = string.split(lineEnding); + // Either '' or (conceivably) the unfinished portion of the next line + string = lines.pop(); + this[kLine_buffer] = string; + for (let n = 0; n < lines.length; n++) this[kOnLine](lines[n]); + } else if (string) { + // No newlines this time, save what we have for next time + this[kLine_buffer] = string; + } + } + + [kInsertString](c) { + if (this.cursor < this.line.length) { + const beg = this.line.slice(0, this.cursor); + const end = this.line.slice( + this.cursor, + this.line.length, + ); + this.line = beg + c + end; + this.cursor += c.length; + this[kRefreshLine](); + } else { + this.line += c; + this.cursor += c.length; + + if (this.getCursorPos().cols === 0) { + this[kRefreshLine](); + } else { + this[kWriteToOutput](c); + } + } + } + + async [kTabComplete](lastKeypressWasTab) { + this.pause(); + const string = this.line.slice(0, this.cursor); + let value; + try { + value = await this.completer(string); + } catch (err) { + // TODO(bartlomieju): inspect is not ported yet + // this[kWriteToOutput](`Tab completion error: ${inspect(err)}`); + this[kWriteToOutput](`Tab completion error: ${err}`); + return; + } finally { + this.resume(); + } + this[kTabCompleter](lastKeypressWasTab, value); + } + + [kTabCompleter](lastKeypressWasTab, { 0: completions, 1: completeOn }) { + // Result and the text that was completed. + + if (!completions || completions.length === 0) { + return; + } + + // If there is a common prefix to all matches, then apply that portion. + const prefix = commonPrefix( + completions.filter((e) => e !== ""), + ); + if ( + prefix.startsWith(completeOn) && + prefix.length > completeOn.length + ) { + this[kInsertString](prefix.slice(completeOn.length)); + return; + } else if (!completeOn.startsWith(prefix)) { + this.line = this.line.slice(0, this.cursor - completeOn.length) + + prefix + + this.line.slice(this.cursor, this.line.length); + this.cursor = this.cursor - completeOn.length + prefix.length; + this._refreshLine(); + return; + } + + if (!lastKeypressWasTab) { + return; + } + + // Apply/show completions. + const completionsWidth = completions.map( + (e) => getStringWidth(e), + ); + const width = Math.max.apply(completionsWidth) + 2; // 2 space padding + let maxColumns = Math.floor(this.columns / width) || 1; + if (maxColumns === Infinity) { + maxColumns = 1; + } + let output = "\r\n"; + let lineIndex = 0; + let whitespace = 0; + for (let i = 0; i < completions.length; i++) { + const completion = completions[i]; + if (completion === "" || lineIndex === maxColumns) { + output += "\r\n"; + lineIndex = 0; + whitespace = 0; + } else { + output += " ".repeat(whitespace); + } + if (completion !== "") { + output += completion; + whitespace = width - completionsWidth[i]; + lineIndex++; + } else { + output += "\r\n"; + } + } + if (lineIndex !== 0) { + output += "\r\n\r\n"; + } + this[kWriteToOutput](output); + this[kRefreshLine](); + } + + [kWordLeft]() { + if (this.cursor > 0) { + // Reverse the string and match a word near beginning + // to avoid quadratic time complexity + const leading = this.line.slice(0, this.cursor); + const reversed = Array.from(leading).reverse().join(""); + const match = reversed.match(/^\s*(?:[^\w\s]+|\w+)?/); + this[kMoveCursor](-match[0].length); + } + } + + [kWordRight]() { + if (this.cursor < this.line.length) { + const trailing = this.line.slice(this.cursor); + const match = trailing.match(/^(?:\s+|[^\w\s]+|\w+)\s*/); + this[kMoveCursor](match[0].length); + } + } + + [kDeleteLeft]() { + if (this.cursor > 0 && this.line.length > 0) { + // The number of UTF-16 units comprising the character to the left + const charSize = charLengthLeft(this.line, this.cursor); + this.line = this.line.slice(0, this.cursor - charSize) + + this.line.slice(this.cursor, this.line.length); + + this.cursor -= charSize; + this[kRefreshLine](); + } + } + + [kDeleteRight]() { + if (this.cursor < this.line.length) { + // The number of UTF-16 units comprising the character to the left + const charSize = charLengthAt(this.line, this.cursor); + this.line = this.line.slice(0, this.cursor) + + this.line.slice( + this.cursor + charSize, + this.line.length, + ); + this[kRefreshLine](); + } + } + + [kDeleteWordLeft]() { + if (this.cursor > 0) { + // Reverse the string and match a word near beginning + // to avoid quadratic time complexity + let leading = this.line.slice(0, this.cursor); + const reversed = Array.from(leading).reverse().join(""); + const match = reversed.match(/^\s*(?:[^\w\s]+|\w+)?/); + leading = leading.slice( + 0, + leading.length - match[0].length, + ); + this.line = leading + + this.line.slice(this.cursor, this.line.length); + this.cursor = leading.length; + this[kRefreshLine](); + } + } + + [kDeleteWordRight]() { + if (this.cursor < this.line.length) { + const trailing = this.line.slice(this.cursor); + const match = trailing.match(/^(?:\s+|\W+|\w+)\s*/); + this.line = this.line.slice(0, this.cursor) + + trailing.slice(match[0].length); + this[kRefreshLine](); + } + } + + [kDeleteLineLeft]() { + this.line = this.line.slice(this.cursor); + this.cursor = 0; + this[kRefreshLine](); + } + + [kDeleteLineRight]() { + this.line = this.line.slice(0, this.cursor); + this[kRefreshLine](); + } + + clearLine() { + this[kMoveCursor](+Infinity); + this[kWriteToOutput]("\r\n"); + this.line = ""; + this.cursor = 0; + this.prevRows = 0; + } + + [kLine]() { + const line = this[kAddHistory](); + this.clearLine(); + this[kOnLine](line); + } + + // TODO(BridgeAR): Add underscores to the search part and a red background in + // case no match is found. This should only be the visual part and not the + // actual line content! + // TODO(BridgeAR): In case the substring based search is active and the end is + // reached, show a comment how to search the history as before. E.g., using + // + N. Only show this after two/three UPs or DOWNs, not on the first + // one. + [kHistoryNext]() { + if (this.historyIndex >= 0) { + const search = this[kSubstringSearch] || ""; + let index = this.historyIndex - 1; + while ( + index >= 0 && + (!this.history[index].startsWith(search) || + this.line === this.history[index]) + ) { + index--; + } + if (index === -1) { + this.line = search; + } else { + this.line = this.history[index]; + } + this.historyIndex = index; + this.cursor = this.line.length; // Set cursor to end of line. + this[kRefreshLine](); + } + } + + [kHistoryPrev]() { + if (this.historyIndex < this.history.length && this.history.length) { + const search = this[kSubstringSearch] || ""; + let index = this.historyIndex + 1; + while ( + index < this.history.length && + (!this.history[index].startsWith(search) || + this.line === this.history[index]) + ) { + index++; + } + if (index === this.history.length) { + this.line = search; + } else { + this.line = this.history[index]; + } + this.historyIndex = index; + this.cursor = this.line.length; // Set cursor to end of line. + this[kRefreshLine](); + } + } + + // Returns the last character's display position of the given string + [kGetDisplayPos](str) { + let offset = 0; + const col = this.columns; + let rows = 0; + str = stripVTControlCharacters(str); + for (const char of str[Symbol.iterator]()) { + if (char === "\n") { + // Rows must be incremented by 1 even if offset = 0 or col = +Infinity. + rows += Math.ceil(offset / col) || 1; + offset = 0; + continue; + } + // Tabs must be aligned by an offset of the tab size. + if (char === "\t") { + offset += this.tabSize - (offset % this.tabSize); + continue; + } + const width = getStringWidth(char); + if (width === 0 || width === 1) { + offset += width; + } else { + // width === 2 + if ((offset + 1) % col === 0) { + offset++; + } + offset += 2; + } + } + const cols = offset % col; + rows += (offset - cols) / col; + return { cols, rows }; + } + + /** + * Returns the real position of the cursor in relation + * to the input prompt + string. + * @returns {{ + * rows: number; + * cols: number; + * }} + */ + getCursorPos() { + const strBeforeCursor = this[kPrompt] + + this.line.slice(0, this.cursor); + return this[kGetDisplayPos](strBeforeCursor); + } + + // This function moves cursor dx places to the right + // (-dx for left) and refreshes the line if it is needed. + [kMoveCursor](dx) { + if (dx === 0) { + return; + } + const oldPos = this.getCursorPos(); + this.cursor += dx; + + // Bounds check + if (this.cursor < 0) { + this.cursor = 0; + } else if (this.cursor > this.line.length) { + this.cursor = this.line.length; + } + + const newPos = this.getCursorPos(); + + // Check if cursor stayed on the line. + if (oldPos.rows === newPos.rows) { + const diffWidth = newPos.cols - oldPos.cols; + moveCursor(this.output, diffWidth, 0); + } else { + this[kRefreshLine](); + } + } + + // Handle a write from the tty + [kTtyWrite](s, key) { + const previousKey = this[kPreviousKey]; + key = key || {}; + this[kPreviousKey] = key; + + // Activate or deactivate substring search. + if ( + (key.name === "up" || key.name === "down") && + !key.ctrl && + !key.meta && + !key.shift + ) { + if (this[kSubstringSearch] === null) { + this[kSubstringSearch] = this.line.slice( + 0, + this.cursor, + ); + } + } else if (this[kSubstringSearch] !== null) { + this[kSubstringSearch] = null; + // Reset the index in case there's no match. + if (this.history.length === this.historyIndex) { + this.historyIndex = -1; + } + } + + // Ignore escape key, fixes + // https://github.com/nodejs/node-v0.x-archive/issues/2876. + if (key.name === "escape") return; + + if (key.ctrl && key.shift) { + /* Control and shift pressed */ + switch (key.name) { + // TODO(BridgeAR): The transmitted escape sequence is `\b` and that is + // identical to -h. It should have a unique escape sequence. + case "backspace": + this[kDeleteLineLeft](); + break; + + case "delete": + this[kDeleteLineRight](); + break; + } + } else if (key.ctrl) { + /* Control key pressed */ + + switch (key.name) { + case "c": + if (this.listenerCount("SIGINT") > 0) { + this.emit("SIGINT"); + } else { + // This readline instance is finished + this.close(); + } + break; + + case "h": // delete left + this[kDeleteLeft](); + break; + + case "d": // delete right or EOF + if (this.cursor === 0 && this.line.length === 0) { + // This readline instance is finished + this.close(); + } else if (this.cursor < this.line.length) { + this[kDeleteRight](); + } + break; + + case "u": // Delete from current to start of line + this[kDeleteLineLeft](); + break; + + case "k": // Delete from current to end of line + this[kDeleteLineRight](); + break; + + case "a": // Go to the start of the line + this[kMoveCursor](-Infinity); + break; + + case "e": // Go to the end of the line + this[kMoveCursor](+Infinity); + break; + + case "b": // back one character + this[kMoveCursor](-charLengthLeft(this.line, this.cursor)); + break; + + case "f": // Forward one character + this[kMoveCursor](+charLengthAt(this.line, this.cursor)); + break; + + case "l": // Clear the whole screen + cursorTo(this.output, 0, 0); + clearScreenDown(this.output); + this[kRefreshLine](); + break; + + case "n": // next history item + this[kHistoryNext](); + break; + + case "p": // Previous history item + this[kHistoryPrev](); + break; + + case "z": + if (process.platform === "win32") break; + if (this.listenerCount("SIGTSTP") > 0) { + this.emit("SIGTSTP"); + } else { + process.once("SIGCONT", () => { + // Don't raise events if stream has already been abandoned. + if (!this.paused) { + // Stream must be paused and resumed after SIGCONT to catch + // SIGINT, SIGTSTP, and EOF. + this.pause(); + this.emit("SIGCONT"); + } + // Explicitly re-enable "raw mode" and move the cursor to + // the correct position. + // See https://github.com/joyent/node/issues/3295. + this[kSetRawMode](true); + this[kRefreshLine](); + }); + this[kSetRawMode](false); + process.kill(process.pid, "SIGTSTP"); + } + break; + + case "w": // Delete backwards to a word boundary + // TODO(BridgeAR): The transmitted escape sequence is `\b` and that is + // identical to -h. It should have a unique escape sequence. + // Falls through + case "backspace": + this[kDeleteWordLeft](); + break; + + case "delete": // Delete forward to a word boundary + this[kDeleteWordRight](); + break; + + case "left": + this[kWordLeft](); + break; + + case "right": + this[kWordRight](); + break; + } + } else if (key.meta) { + /* Meta key pressed */ + + switch (key.name) { + case "b": // backward word + this[kWordLeft](); + break; + + case "f": // forward word + this[kWordRight](); + break; + + case "d": // delete forward word + case "delete": + this[kDeleteWordRight](); + break; + + case "backspace": // Delete backwards to a word boundary + this[kDeleteWordLeft](); + break; + } + } else { + /* No modifier keys used */ + + // \r bookkeeping is only relevant if a \n comes right after. + if (this[kSawReturnAt] && key.name !== "enter") this[kSawReturnAt] = 0; + + switch (key.name) { + case "return": // Carriage return, i.e. \r + this[kSawReturnAt] = Date.now(); + this[kLine](); + break; + + case "enter": + // When key interval > crlfDelay + if ( + this[kSawReturnAt] === 0 || + Date.now() - this[kSawReturnAt] > this.crlfDelay + ) { + this[kLine](); + } + this[kSawReturnAt] = 0; + break; + + case "backspace": + this[kDeleteLeft](); + break; + + case "delete": + this[kDeleteRight](); + break; + + case "left": + // Obtain the code point to the left + this[kMoveCursor](-charLengthLeft(this.line, this.cursor)); + break; + + case "right": + this[kMoveCursor](+charLengthAt(this.line, this.cursor)); + break; + + case "home": + this[kMoveCursor](-Infinity); + break; + + case "end": + this[kMoveCursor](+Infinity); + break; + + case "up": + this[kHistoryPrev](); + break; + + case "down": + this[kHistoryNext](); + break; + + case "tab": + // If tab completion enabled, do that... + if ( + typeof this.completer === "function" && + this.isCompletionEnabled + ) { + const lastKeypressWasTab = previousKey && + previousKey.name === "tab"; + this[kTabComplete](lastKeypressWasTab); + break; + } + // falls through + default: + if (typeof s === "string" && s) { + const lines = s.split(/\r\n|\n|\r/); + for (let i = 0, len = lines.length; i < len; i++) { + if (i > 0) { + this[kLine](); + } + this[kInsertString](lines[i]); + } + } + } + } + } + + /** + * Creates an `AsyncIterator` object that iterates through + * each line in the input stream as a string. + * @typedef {{ + * [Symbol.asyncIterator]: () => InterfaceAsyncIterator, + * next: () => Promise + * }} InterfaceAsyncIterator + * @returns {InterfaceAsyncIterator} + */ + [Symbol.asyncIterator]() { + if (this[kLineObjectStream] === undefined) { + const readable = new Readable({ + objectMode: true, + read: () => { + this.resume(); + }, + destroy: (err, cb) => { + this.off("line", lineListener); + this.off("close", closeListener); + this.close(); + cb(err); + }, + }); + const lineListener = (input) => { + if (!readable.push(input)) { + // TODO(rexagod): drain to resume flow + this.pause(); + } + }; + const closeListener = () => { + readable.push(null); + }; + const errorListener = (err) => { + readable.destroy(err); + }; + this.on("error", errorListener); + this.on("line", lineListener); + this.on("close", closeListener); + this[kLineObjectStream] = readable; + } + + return this[kLineObjectStream][Symbol.asyncIterator](); + } +} diff --git a/ext/node/polyfills/internal/readline/promises.mjs b/ext/node/polyfills/internal/readline/promises.mjs new file mode 100644 index 00000000000000..36aa3de1203766 --- /dev/null +++ b/ext/node/polyfills/internal/readline/promises.mjs @@ -0,0 +1,139 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. +// Copyright Joyent, Inc. and other Node contributors. + +import { ArrayPrototypeJoin, ArrayPrototypePush } from "internal:deno_node/polyfills/internal/primordials.mjs"; + +import { CSI } from "internal:deno_node/polyfills/internal/readline/utils.mjs"; +import { validateBoolean, validateInteger } from "internal:deno_node/polyfills/internal/validators.mjs"; +import { isWritable } from "internal:deno_node/polyfills/internal/streams/utils.mjs"; +import { ERR_INVALID_ARG_TYPE } from "internal:deno_node/polyfills/internal/errors.ts"; + +const { + kClearToLineBeginning, + kClearToLineEnd, + kClearLine, + kClearScreenDown, +} = CSI; + +export class Readline { + #autoCommit = false; + #stream; + #todo = []; + + constructor(stream, options = undefined) { + if (!isWritable(stream)) { + throw new ERR_INVALID_ARG_TYPE("stream", "Writable", stream); + } + this.#stream = stream; + if (options?.autoCommit != null) { + validateBoolean(options.autoCommit, "options.autoCommit"); + this.#autoCommit = options.autoCommit; + } + } + + /** + * Moves the cursor to the x and y coordinate on the given stream. + * @param {integer} x + * @param {integer} [y] + * @returns {Readline} this + */ + cursorTo(x, y = undefined) { + validateInteger(x, "x"); + if (y != null) validateInteger(y, "y"); + + const data = y == null ? CSI`${x + 1}G` : CSI`${y + 1};${x + 1}H`; + if (this.#autoCommit) process.nextTick(() => this.#stream.write(data)); + else ArrayPrototypePush(this.#todo, data); + + return this; + } + + /** + * Moves the cursor relative to its current location. + * @param {integer} dx + * @param {integer} dy + * @returns {Readline} this + */ + moveCursor(dx, dy) { + if (dx || dy) { + validateInteger(dx, "dx"); + validateInteger(dy, "dy"); + + let data = ""; + + if (dx < 0) { + data += CSI`${-dx}D`; + } else if (dx > 0) { + data += CSI`${dx}C`; + } + + if (dy < 0) { + data += CSI`${-dy}A`; + } else if (dy > 0) { + data += CSI`${dy}B`; + } + if (this.#autoCommit) process.nextTick(() => this.#stream.write(data)); + else ArrayPrototypePush(this.#todo, data); + } + return this; + } + + /** + * Clears the current line the cursor is on. + * @param {-1|0|1} dir Direction to clear: + * -1 for left of the cursor + * +1 for right of the cursor + * 0 for the entire line + * @returns {Readline} this + */ + clearLine(dir) { + validateInteger(dir, "dir", -1, 1); + + const data = dir < 0 + ? kClearToLineBeginning + : dir > 0 + ? kClearToLineEnd + : kClearLine; + if (this.#autoCommit) process.nextTick(() => this.#stream.write(data)); + else ArrayPrototypePush(this.#todo, data); + return this; + } + + /** + * Clears the screen from the current position of the cursor down. + * @returns {Readline} this + */ + clearScreenDown() { + if (this.#autoCommit) { + process.nextTick(() => this.#stream.write(kClearScreenDown)); + } else { + ArrayPrototypePush(this.#todo, kClearScreenDown); + } + return this; + } + + /** + * Sends all the pending actions to the associated `stream` and clears the + * internal list of pending actions. + * @returns {Promise} Resolves when all pending actions have been + * flushed to the associated `stream`. + */ + commit() { + return new Promise((resolve) => { + this.#stream.write(ArrayPrototypeJoin(this.#todo, ""), resolve); + this.#todo = []; + }); + } + + /** + * Clears the internal list of pending actions without sending it to the + * associated `stream`. + * @returns {Readline} this + */ + rollback() { + this.#todo = []; + return this; + } +} + +export default Readline; diff --git a/ext/node/polyfills/internal/readline/symbols.mjs b/ext/node/polyfills/internal/readline/symbols.mjs new file mode 100644 index 00000000000000..3a05c64d2d5ea7 --- /dev/null +++ b/ext/node/polyfills/internal/readline/symbols.mjs @@ -0,0 +1,34 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. +// Copyright Joyent, Inc. and Node.js contributors. All rights reserved. MIT license. + +export const kAddHistory = Symbol("_addHistory"); +export const kDecoder = Symbol("_decoder"); +export const kDeleteLeft = Symbol("_deleteLeft"); +export const kDeleteLineLeft = Symbol("_deleteLineLeft"); +export const kDeleteLineRight = Symbol("_deleteLineRight"); +export const kDeleteRight = Symbol("_deleteRight"); +export const kDeleteWordLeft = Symbol("_deleteWordLeft"); +export const kDeleteWordRight = Symbol("_deleteWordRight"); +export const kGetDisplayPos = Symbol("_getDisplayPos"); +export const kHistoryNext = Symbol("_historyNext"); +export const kHistoryPrev = Symbol("_historyPrev"); +export const kInsertString = Symbol("_insertString"); +export const kLine = Symbol("_line"); +export const kLine_buffer = Symbol("_line_buffer"); +export const kMoveCursor = Symbol("_moveCursor"); +export const kNormalWrite = Symbol("_normalWrite"); +export const kOldPrompt = Symbol("_oldPrompt"); +export const kOnLine = Symbol("_onLine"); +export const kPreviousKey = Symbol("_previousKey"); +export const kPrompt = Symbol("_prompt"); +export const kQuestionCallback = Symbol("_questionCallback"); +export const kRefreshLine = Symbol("_refreshLine"); +export const kSawKeyPress = Symbol("_sawKeyPress"); +export const kSawReturnAt = Symbol("_sawReturnAt"); +export const kSetRawMode = Symbol("_setRawMode"); +export const kTabComplete = Symbol("_tabComplete"); +export const kTabCompleter = Symbol("_tabCompleter"); +export const kTtyWrite = Symbol("_ttyWrite"); +export const kWordLeft = Symbol("_wordLeft"); +export const kWordRight = Symbol("_wordRight"); +export const kWriteToOutput = Symbol("_writeToOutput"); diff --git a/ext/node/polyfills/internal/readline/utils.mjs b/ext/node/polyfills/internal/readline/utils.mjs new file mode 100644 index 00000000000000..6224f112b3ded3 --- /dev/null +++ b/ext/node/polyfills/internal/readline/utils.mjs @@ -0,0 +1,580 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. +// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. + +"use strict"; + +const kUTF16SurrogateThreshold = 0x10000; // 2 ** 16 +const kEscape = "\x1b"; +export const kSubstringSearch = Symbol("kSubstringSearch"); + +export function CSI(strings, ...args) { + let ret = `${kEscape}[`; + for (let n = 0; n < strings.length; n++) { + ret += strings[n]; + if (n < args.length) { + ret += args[n]; + } + } + return ret; +} + +CSI.kEscape = kEscape; +CSI.kClearToLineBeginning = CSI`1K`; +CSI.kClearToLineEnd = CSI`0K`; +CSI.kClearLine = CSI`2K`; +CSI.kClearScreenDown = CSI`0J`; + +// TODO(BridgeAR): Treat combined characters as single character, i.e, +// 'a\u0301' and '\u0301a' (both have the same visual output). +// Check Canonical_Combining_Class in +// http://userguide.icu-project.org/strings/properties +export function charLengthLeft(str, i) { + if (i <= 0) { + return 0; + } + if ( + (i > 1 && + str.codePointAt(i - 2) >= kUTF16SurrogateThreshold) || + str.codePointAt(i - 1) >= kUTF16SurrogateThreshold + ) { + return 2; + } + return 1; +} + +export function charLengthAt(str, i) { + if (str.length <= i) { + // Pretend to move to the right. This is necessary to autocomplete while + // moving to the right. + return 1; + } + return str.codePointAt(i) >= kUTF16SurrogateThreshold ? 2 : 1; +} + +/* + Some patterns seen in terminal key escape codes, derived from combos seen + at http://www.midnight-commander.org/browser/lib/tty/key.c + + ESC letter + ESC [ letter + ESC [ modifier letter + ESC [ 1 ; modifier letter + ESC [ num char + ESC [ num ; modifier char + ESC O letter + ESC O modifier letter + ESC O 1 ; modifier letter + ESC N letter + ESC [ [ num ; modifier char + ESC [ [ 1 ; modifier letter + ESC ESC [ num char + ESC ESC O letter + + - char is usually ~ but $ and ^ also happen with rxvt + - modifier is 1 + + (shift * 1) + + (left_alt * 2) + + (ctrl * 4) + + (right_alt * 8) + - two leading ESCs apparently mean the same as one leading ESC +*/ +export function* emitKeys(stream) { + while (true) { + let ch = yield; + let s = ch; + let escaped = false; + const key = { + sequence: null, + name: undefined, + ctrl: false, + meta: false, + shift: false, + }; + + if (ch === kEscape) { + escaped = true; + s += ch = yield; + + if (ch === kEscape) { + s += ch = yield; + } + } + + if (escaped && (ch === "O" || ch === "[")) { + // ANSI escape sequence + let code = ch; + let modifier = 0; + + if (ch === "O") { + // ESC O letter + // ESC O modifier letter + s += ch = yield; + + if (ch >= "0" && ch <= "9") { + modifier = (ch >> 0) - 1; + s += ch = yield; + } + + code += ch; + } else if (ch === "[") { + // ESC [ letter + // ESC [ modifier letter + // ESC [ [ modifier letter + // ESC [ [ num char + s += ch = yield; + + if (ch === "[") { + // \x1b[[A + // ^--- escape codes might have a second bracket + code += ch; + s += ch = yield; + } + + /* + * Here and later we try to buffer just enough data to get + * a complete ascii sequence. + * + * We have basically two classes of ascii characters to process: + * + * 1. `\x1b[24;5~` should be parsed as { code: '[24~', modifier: 5 } + * + * This particular example is featuring Ctrl+F12 in xterm. + * + * - `;5` part is optional, e.g. it could be `\x1b[24~` + * - first part can contain one or two digits + * + * So the generic regexp is like /^\d\d?(;\d)?[~^$]$/ + * + * 2. `\x1b[1;5H` should be parsed as { code: '[H', modifier: 5 } + * + * This particular example is featuring Ctrl+Home in xterm. + * + * - `1;5` part is optional, e.g. it could be `\x1b[H` + * - `1;` part is optional, e.g. it could be `\x1b[5H` + * + * So the generic regexp is like /^((\d;)?\d)?[A-Za-z]$/ + */ + const cmdStart = s.length - 1; + + // Skip one or two leading digits + if (ch >= "0" && ch <= "9") { + s += ch = yield; + + if (ch >= "0" && ch <= "9") { + s += ch = yield; + } + } + + // skip modifier + if (ch === ";") { + s += ch = yield; + + if (ch >= "0" && ch <= "9") { + s += yield; + } + } + + /* + * We buffered enough data, now trying to extract code + * and modifier from it + */ + const cmd = s.slice(cmdStart); + let match; + + if ((match = cmd.match(/^(\d\d?)(;(\d))?([~^$])$/))) { + code += match[1] + match[4]; + modifier = (match[3] || 1) - 1; + } else if ( + (match = cmd.match(/^((\d;)?(\d))?([A-Za-z])$/)) + ) { + code += match[4]; + modifier = (match[3] || 1) - 1; + } else { + code += cmd; + } + } + + // Parse the key modifier + key.ctrl = !!(modifier & 4); + key.meta = !!(modifier & 10); + key.shift = !!(modifier & 1); + key.code = code; + + // Parse the key itself + switch (code) { + /* xterm/gnome ESC [ letter (with modifier) */ + case "[P": + key.name = "f1"; + break; + case "[Q": + key.name = "f2"; + break; + case "[R": + key.name = "f3"; + break; + case "[S": + key.name = "f4"; + break; + + /* xterm/gnome ESC O letter (without modifier) */ + + case "OP": + key.name = "f1"; + break; + case "OQ": + key.name = "f2"; + break; + case "OR": + key.name = "f3"; + break; + case "OS": + key.name = "f4"; + break; + + /* xterm/rxvt ESC [ number ~ */ + + case "[11~": + key.name = "f1"; + break; + case "[12~": + key.name = "f2"; + break; + case "[13~": + key.name = "f3"; + break; + case "[14~": + key.name = "f4"; + break; + + /* from Cygwin and used in libuv */ + + case "[[A": + key.name = "f1"; + break; + case "[[B": + key.name = "f2"; + break; + case "[[C": + key.name = "f3"; + break; + case "[[D": + key.name = "f4"; + break; + case "[[E": + key.name = "f5"; + break; + + /* common */ + + case "[15~": + key.name = "f5"; + break; + case "[17~": + key.name = "f6"; + break; + case "[18~": + key.name = "f7"; + break; + case "[19~": + key.name = "f8"; + break; + case "[20~": + key.name = "f9"; + break; + case "[21~": + key.name = "f10"; + break; + case "[23~": + key.name = "f11"; + break; + case "[24~": + key.name = "f12"; + break; + + /* xterm ESC [ letter */ + + case "[A": + key.name = "up"; + break; + case "[B": + key.name = "down"; + break; + case "[C": + key.name = "right"; + break; + case "[D": + key.name = "left"; + break; + case "[E": + key.name = "clear"; + break; + case "[F": + key.name = "end"; + break; + case "[H": + key.name = "home"; + break; + + /* xterm/gnome ESC O letter */ + + case "OA": + key.name = "up"; + break; + case "OB": + key.name = "down"; + break; + case "OC": + key.name = "right"; + break; + case "OD": + key.name = "left"; + break; + case "OE": + key.name = "clear"; + break; + case "OF": + key.name = "end"; + break; + case "OH": + key.name = "home"; + break; + + /* xterm/rxvt ESC [ number ~ */ + + case "[1~": + key.name = "home"; + break; + case "[2~": + key.name = "insert"; + break; + case "[3~": + key.name = "delete"; + break; + case "[4~": + key.name = "end"; + break; + case "[5~": + key.name = "pageup"; + break; + case "[6~": + key.name = "pagedown"; + break; + + /* putty */ + + case "[[5~": + key.name = "pageup"; + break; + case "[[6~": + key.name = "pagedown"; + break; + + /* rxvt */ + + case "[7~": + key.name = "home"; + break; + case "[8~": + key.name = "end"; + break; + + /* rxvt keys with modifiers */ + + case "[a": + key.name = "up"; + key.shift = true; + break; + case "[b": + key.name = "down"; + key.shift = true; + break; + case "[c": + key.name = "right"; + key.shift = true; + break; + case "[d": + key.name = "left"; + key.shift = true; + break; + case "[e": + key.name = "clear"; + key.shift = true; + break; + + case "[2$": + key.name = "insert"; + key.shift = true; + break; + case "[3$": + key.name = "delete"; + key.shift = true; + break; + case "[5$": + key.name = "pageup"; + key.shift = true; + break; + case "[6$": + key.name = "pagedown"; + key.shift = true; + break; + case "[7$": + key.name = "home"; + key.shift = true; + break; + case "[8$": + key.name = "end"; + key.shift = true; + break; + + case "Oa": + key.name = "up"; + key.ctrl = true; + break; + case "Ob": + key.name = "down"; + key.ctrl = true; + break; + case "Oc": + key.name = "right"; + key.ctrl = true; + break; + case "Od": + key.name = "left"; + key.ctrl = true; + break; + case "Oe": + key.name = "clear"; + key.ctrl = true; + break; + + case "[2^": + key.name = "insert"; + key.ctrl = true; + break; + case "[3^": + key.name = "delete"; + key.ctrl = true; + break; + case "[5^": + key.name = "pageup"; + key.ctrl = true; + break; + case "[6^": + key.name = "pagedown"; + key.ctrl = true; + break; + case "[7^": + key.name = "home"; + key.ctrl = true; + break; + case "[8^": + key.name = "end"; + key.ctrl = true; + break; + + /* misc. */ + + case "[Z": + key.name = "tab"; + key.shift = true; + break; + default: + key.name = "undefined"; + break; + } + } else if (ch === "\r") { + // carriage return + key.name = "return"; + key.meta = escaped; + } else if (ch === "\n") { + // Enter, should have been called linefeed + key.name = "enter"; + key.meta = escaped; + } else if (ch === "\t") { + // tab + key.name = "tab"; + key.meta = escaped; + } else if (ch === "\b" || ch === "\x7f") { + // backspace or ctrl+h + key.name = "backspace"; + key.meta = escaped; + } else if (ch === kEscape) { + // escape key + key.name = "escape"; + key.meta = escaped; + } else if (ch === " ") { + key.name = "space"; + key.meta = escaped; + } else if (!escaped && ch <= "\x1a") { + // ctrl+letter + key.name = String.fromCharCode( + ch.charCodeAt() + "a".charCodeAt() - 1, + ); + key.ctrl = true; + } else if (/^[0-9A-Za-z]$/.test(ch)) { + // Letter, number, shift+letter + key.name = ch.toLowerCase(); + key.shift = /^[A-Z]$/.test(ch); + key.meta = escaped; + } else if (escaped) { + // Escape sequence timeout + key.name = ch.length ? undefined : "escape"; + key.meta = true; + } + + key.sequence = s; + + if (s.length !== 0 && (key.name !== undefined || escaped)) { + /* Named character or sequence */ + stream.emit("keypress", escaped ? undefined : s, key); + } else if (charLengthAt(s, 0) === s.length) { + /* Single unnamed character, e.g. "." */ + stream.emit("keypress", s, key); + } + /* Unrecognized or broken escape sequence, don't emit anything */ + } +} + +// This runs in O(n log n). +export function commonPrefix(strings) { + if (strings.length === 1) { + return strings[0]; + } + const sorted = strings.slice().sort(); + const min = sorted[0]; + const max = sorted[sorted.length - 1]; + for (let i = 0; i < min.length; i++) { + if (min[i] !== max[i]) { + return min.slice(0, i); + } + } + return min; +} + +export default { + CSI, + charLengthAt, + charLengthLeft, + emitKeys, + commonPrefix, + kSubstringSearch, +}; diff --git a/ext/node/polyfills/internal/stream_base_commons.ts b/ext/node/polyfills/internal/stream_base_commons.ts new file mode 100644 index 00000000000000..dd1c74d0f6ceb8 --- /dev/null +++ b/ext/node/polyfills/internal/stream_base_commons.ts @@ -0,0 +1,355 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. +// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. + +import { ownerSymbol } from "internal:deno_node/polyfills/internal/async_hooks.ts"; +import { + kArrayBufferOffset, + kBytesWritten, + kLastWriteWasAsync, + LibuvStreamWrap, + streamBaseState, + WriteWrap, +} from "internal:deno_node/polyfills/internal_binding/stream_wrap.ts"; +import { isUint8Array } from "internal:deno_node/polyfills/internal/util/types.ts"; +import { errnoException } from "internal:deno_node/polyfills/internal/errors.ts"; +import { + getTimerDuration, + kTimeout, +} from "internal:deno_node/polyfills/internal/timers.mjs"; +import { setUnrefTimeout } from "internal:deno_node/polyfills/timers.ts"; +import { validateFunction } from "internal:deno_node/polyfills/internal/validators.mjs"; +import { codeMap } from "internal:deno_node/polyfills/internal_binding/uv.ts"; +import { Buffer } from "internal:deno_node/polyfills/buffer.ts"; + +export const kMaybeDestroy = Symbol("kMaybeDestroy"); +export const kUpdateTimer = Symbol("kUpdateTimer"); +export const kAfterAsyncWrite = Symbol("kAfterAsyncWrite"); +export const kHandle = Symbol("kHandle"); +export const kSession = Symbol("kSession"); +export const kBuffer = Symbol("kBuffer"); +export const kBufferGen = Symbol("kBufferGen"); +export const kBufferCb = Symbol("kBufferCb"); + +// deno-lint-ignore no-explicit-any +function handleWriteReq(req: any, data: any, encoding: string) { + const { handle } = req; + + switch (encoding) { + case "buffer": { + const ret = handle.writeBuffer(req, data); + + if (streamBaseState[kLastWriteWasAsync]) { + req.buffer = data; + } + + return ret; + } + case "latin1": + case "binary": + return handle.writeLatin1String(req, data); + case "utf8": + case "utf-8": + return handle.writeUtf8String(req, data); + case "ascii": + return handle.writeAsciiString(req, data); + case "ucs2": + case "ucs-2": + case "utf16le": + case "utf-16le": + return handle.writeUcs2String(req, data); + default: { + const buffer = Buffer.from(data, encoding); + const ret = handle.writeBuffer(req, buffer); + + if (streamBaseState[kLastWriteWasAsync]) { + req.buffer = buffer; + } + + return ret; + } + } +} + +// deno-lint-ignore no-explicit-any +function onWriteComplete(this: any, status: number) { + let stream = this.handle[ownerSymbol]; + + if (stream.constructor.name === "ReusedHandle") { + stream = stream.handle; + } + + if (stream.destroyed) { + if (typeof this.callback === "function") { + this.callback(null); + } + + return; + } + + if (status < 0) { + const ex = errnoException(status, "write", this.error); + + if (typeof this.callback === "function") { + this.callback(ex); + } else { + stream.destroy(ex); + } + + return; + } + + stream[kUpdateTimer](); + stream[kAfterAsyncWrite](this); + + if (typeof this.callback === "function") { + this.callback(null); + } +} + +function createWriteWrap( + handle: LibuvStreamWrap, + callback: (err?: Error | null) => void, +) { + const req = new WriteWrap(); + + req.handle = handle; + req.oncomplete = onWriteComplete; + req.async = false; + req.bytes = 0; + req.buffer = null; + req.callback = callback; + + return req; +} + +export function writevGeneric( + // deno-lint-ignore no-explicit-any + owner: any, + // deno-lint-ignore no-explicit-any + data: any, + cb: (err?: Error | null) => void, +) { + const req = createWriteWrap(owner[kHandle], cb); + const allBuffers = data.allBuffers; + let chunks; + + if (allBuffers) { + chunks = data; + + for (let i = 0; i < data.length; i++) { + data[i] = data[i].chunk; + } + } else { + chunks = new Array(data.length << 1); + + for (let i = 0; i < data.length; i++) { + const entry = data[i]; + chunks[i * 2] = entry.chunk; + chunks[i * 2 + 1] = entry.encoding; + } + } + + const err = req.handle.writev(req, chunks, allBuffers); + + // Retain chunks + if (err === 0) { + req._chunks = chunks; + } + + afterWriteDispatched(req, err, cb); + + return req; +} + +export function writeGeneric( + // deno-lint-ignore no-explicit-any + owner: any, + // deno-lint-ignore no-explicit-any + data: any, + encoding: string, + cb: (err?: Error | null) => void, +) { + const req = createWriteWrap(owner[kHandle], cb); + const err = handleWriteReq(req, data, encoding); + + afterWriteDispatched(req, err, cb); + + return req; +} + +function afterWriteDispatched( + // deno-lint-ignore no-explicit-any + req: any, + err: number, + cb: (err?: Error | null) => void, +) { + req.bytes = streamBaseState[kBytesWritten]; + req.async = !!streamBaseState[kLastWriteWasAsync]; + + if (err !== 0) { + return cb(errnoException(err, "write", req.error)); + } + + if (!req.async && typeof req.callback === "function") { + req.callback(); + } +} + +// Here we differ from Node slightly. Node makes use of the `kReadBytesOrError` +// entry of the `streamBaseState` array from the `stream_wrap` internal binding. +// Here we pass the `nread` value directly to this method as async Deno APIs +// don't grant us the ability to rely on some mutable array entry setting. +export function onStreamRead( + // deno-lint-ignore no-explicit-any + this: any, + arrayBuffer: Uint8Array, + nread: number, +) { + // deno-lint-ignore no-this-alias + const handle = this; + + let stream = this[ownerSymbol]; + + if (stream.constructor.name === "ReusedHandle") { + stream = stream.handle; + } + + stream[kUpdateTimer](); + + if (nread > 0 && !stream.destroyed) { + let ret; + let result; + const userBuf = stream[kBuffer]; + + if (userBuf) { + result = stream[kBufferCb](nread, userBuf) !== false; + const bufGen = stream[kBufferGen]; + + if (bufGen !== null) { + const nextBuf = bufGen(); + + if (isUint8Array(nextBuf)) { + stream[kBuffer] = ret = nextBuf; + } + } + } else { + const offset = streamBaseState[kArrayBufferOffset]; + const buf = Buffer.from(arrayBuffer, offset, nread); + result = stream.push(buf); + } + + if (!result) { + handle.reading = false; + + if (!stream.destroyed) { + const err = handle.readStop(); + + if (err) { + stream.destroy(errnoException(err, "read")); + } + } + } + + return ret; + } + + if (nread === 0) { + return; + } + + if (nread !== codeMap.get("EOF")) { + // CallJSOnreadMethod expects the return value to be a buffer. + // Ref: https://github.com/nodejs/node/pull/34375 + stream.destroy(errnoException(nread, "read")); + + return; + } + + // Defer this until we actually emit end + if (stream._readableState.endEmitted) { + if (stream[kMaybeDestroy]) { + stream[kMaybeDestroy](); + } + } else { + if (stream[kMaybeDestroy]) { + stream.on("end", stream[kMaybeDestroy]); + } + + if (handle.readStop) { + const err = handle.readStop(); + + if (err) { + // CallJSOnreadMethod expects the return value to be a buffer. + // Ref: https://github.com/nodejs/node/pull/34375 + stream.destroy(errnoException(err, "read")); + + return; + } + } + + // Push a null to signal the end of data. + // Do it before `maybeDestroy` for correct order of events: + // `end` -> `close` + stream.push(null); + stream.read(0); + } +} + +export function setStreamTimeout( + // deno-lint-ignore no-explicit-any + this: any, + msecs: number, + callback?: () => void, +) { + if (this.destroyed) { + return this; + } + + this.timeout = msecs; + + // Type checking identical to timers.enroll() + msecs = getTimerDuration(msecs, "msecs"); + + // Attempt to clear an existing timer in both cases - + // even if it will be rescheduled we don't want to leak an existing timer. + clearTimeout(this[kTimeout]); + + if (msecs === 0) { + if (callback !== undefined) { + validateFunction(callback, "callback"); + this.removeListener("timeout", callback); + } + } else { + this[kTimeout] = setUnrefTimeout(this._onTimeout.bind(this), msecs); + + if (this[kSession]) { + this[kSession][kUpdateTimer](); + } + + if (callback !== undefined) { + validateFunction(callback, "callback"); + this.once("timeout", callback); + } + } + + return this; +} diff --git a/ext/node/polyfills/internal/streams/add-abort-signal.mjs b/ext/node/polyfills/internal/streams/add-abort-signal.mjs new file mode 100644 index 00000000000000..5d7512f1c57f44 --- /dev/null +++ b/ext/node/polyfills/internal/streams/add-abort-signal.mjs @@ -0,0 +1,48 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// deno-lint-ignore-file + +import { AbortError, ERR_INVALID_ARG_TYPE } from "internal:deno_node/polyfills/internal/errors.ts"; +import eos from "internal:deno_node/polyfills/internal/streams/end-of-stream.mjs"; + +// This method is inlined here for readable-stream +// It also does not allow for signal to not exist on the stream +// https://github.com/nodejs/node/pull/36061#discussion_r533718029 +const validateAbortSignal = (signal, name) => { + if ( + typeof signal !== "object" || + !("aborted" in signal) + ) { + throw new ERR_INVALID_ARG_TYPE(name, "AbortSignal", signal); + } +}; + +function isStream(obj) { + return !!(obj && typeof obj.pipe === "function"); +} + +function addAbortSignal(signal, stream) { + validateAbortSignal(signal, "signal"); + if (!isStream(stream)) { + throw new ERR_INVALID_ARG_TYPE("stream", "stream.Stream", stream); + } + return addAbortSignalNoValidate(signal, stream); +} +function addAbortSignalNoValidate(signal, stream) { + if (typeof signal !== "object" || !("aborted" in signal)) { + return stream; + } + const onAbort = () => { + stream.destroy(new AbortError()); + }; + if (signal.aborted) { + onAbort(); + } else { + signal.addEventListener("abort", onAbort); + eos(stream, () => signal.removeEventListener("abort", onAbort)); + } + return stream; +} + +export default { addAbortSignal, addAbortSignalNoValidate }; +export { addAbortSignal, addAbortSignalNoValidate }; diff --git a/ext/node/polyfills/internal/streams/buffer_list.mjs b/ext/node/polyfills/internal/streams/buffer_list.mjs new file mode 100644 index 00000000000000..3016ffba56ddea --- /dev/null +++ b/ext/node/polyfills/internal/streams/buffer_list.mjs @@ -0,0 +1,188 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// deno-lint-ignore-file + +import { Buffer } from "internal:deno_node/polyfills/buffer.ts"; +import { inspect } from "internal:deno_node/polyfills/internal/util/inspect.mjs"; + +class BufferList { + constructor() { + this.head = null; + this.tail = null; + this.length = 0; + } + + push(v) { + const entry = { data: v, next: null }; + if (this.length > 0) { + this.tail.next = entry; + } else { + this.head = entry; + } + this.tail = entry; + ++this.length; + } + + unshift(v) { + const entry = { data: v, next: this.head }; + if (this.length === 0) { + this.tail = entry; + } + this.head = entry; + ++this.length; + } + + shift() { + if (this.length === 0) { + return; + } + const ret = this.head.data; + if (this.length === 1) { + this.head = this.tail = null; + } else { + this.head = this.head.next; + } + --this.length; + return ret; + } + + clear() { + this.head = this.tail = null; + this.length = 0; + } + + join(s) { + if (this.length === 0) { + return ""; + } + let p = this.head; + let ret = "" + p.data; + while (p = p.next) { + ret += s + p.data; + } + return ret; + } + + concat(n) { + if (this.length === 0) { + return Buffer.alloc(0); + } + const ret = Buffer.allocUnsafe(n >>> 0); + let p = this.head; + let i = 0; + while (p) { + ret.set(p.data, i); + i += p.data.length; + p = p.next; + } + return ret; + } + + // Consumes a specified amount of bytes or characters from the buffered data. + consume(n, hasStrings) { + const data = this.head.data; + if (n < data.length) { + // `slice` is the same for buffers and strings. + const slice = data.slice(0, n); + this.head.data = data.slice(n); + return slice; + } + if (n === data.length) { + // First chunk is a perfect match. + return this.shift(); + } + // Result spans more than one buffer. + return hasStrings ? this._getString(n) : this._getBuffer(n); + } + + first() { + return this.head.data; + } + + *[Symbol.iterator]() { + for (let p = this.head; p; p = p.next) { + yield p.data; + } + } + + // Consumes a specified amount of characters from the buffered data. + _getString(n) { + let ret = ""; + let p = this.head; + let c = 0; + do { + const str = p.data; + if (n > str.length) { + ret += str; + n -= str.length; + } else { + if (n === str.length) { + ret += str; + ++c; + if (p.next) { + this.head = p.next; + } else { + this.head = this.tail = null; + } + } else { + ret += str.slice(0, n); + this.head = p; + p.data = str.slice(n); + } + break; + } + ++c; + } while (p = p.next); + this.length -= c; + return ret; + } + + // Consumes a specified amount of bytes from the buffered data. + _getBuffer(n) { + const ret = Buffer.allocUnsafe(n); + const retLen = n; + let p = this.head; + let c = 0; + do { + const buf = p.data; + if (n > buf.length) { + ret.set(buf, retLen - n); + n -= buf.length; + } else { + if (n === buf.length) { + ret.set(buf, retLen - n); + ++c; + if (p.next) { + this.head = p.next; + } else { + this.head = this.tail = null; + } + } else { + ret.set( + new Uint8Array(buf.buffer, buf.byteOffset, n), + retLen - n, + ); + this.head = p; + p.data = buf.slice(n); + } + break; + } + ++c; + } while (p = p.next); + this.length -= c; + return ret; + } + + // Make sure the linked list only shows the minimal necessary information. + [inspect.custom](_, options) { + return inspect(this, { + ...options, + // Only inspect one level. + depth: 0, + // It should not recurse. + customInspect: false, + }); + } +} + +export default BufferList; diff --git a/ext/node/polyfills/internal/streams/destroy.mjs b/ext/node/polyfills/internal/streams/destroy.mjs new file mode 100644 index 00000000000000..b065f2119ba182 --- /dev/null +++ b/ext/node/polyfills/internal/streams/destroy.mjs @@ -0,0 +1,320 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// deno-lint-ignore-file + +import { aggregateTwoErrors, ERR_MULTIPLE_CALLBACK } from "internal:deno_node/polyfills/internal/errors.ts"; +import * as process from "internal:deno_node/polyfills/_process/process.ts"; + +const kDestroy = Symbol("kDestroy"); +const kConstruct = Symbol("kConstruct"); + +function checkError(err, w, r) { + if (err) { + // Avoid V8 leak, https://github.com/nodejs/node/pull/34103#issuecomment-652002364 + err.stack; // eslint-disable-line no-unused-expressions + + if (w && !w.errored) { + w.errored = err; + } + if (r && !r.errored) { + r.errored = err; + } + } +} + +// Backwards compat. cb() is undocumented and unused in core but +// unfortunately might be used by modules. +function destroy(err, cb) { + const r = this._readableState; + const w = this._writableState; + // With duplex streams we use the writable side for state. + const s = w || r; + + if ((w && w.destroyed) || (r && r.destroyed)) { + if (typeof cb === "function") { + cb(); + } + + return this; + } + + // We set destroyed to true before firing error callbacks in order + // to make it re-entrance safe in case destroy() is called within callbacks + checkError(err, w, r); + + if (w) { + w.destroyed = true; + } + if (r) { + r.destroyed = true; + } + + // If still constructing then defer calling _destroy. + if (!s.constructed) { + this.once(kDestroy, function (er) { + _destroy(this, aggregateTwoErrors(er, err), cb); + }); + } else { + _destroy(this, err, cb); + } + + return this; +} + +function _destroy(self, err, cb) { + let called = false; + + function onDestroy(err) { + if (called) { + return; + } + called = true; + + const r = self._readableState; + const w = self._writableState; + + checkError(err, w, r); + + if (w) { + w.closed = true; + } + if (r) { + r.closed = true; + } + + if (typeof cb === "function") { + cb(err); + } + + if (err) { + process.nextTick(emitErrorCloseNT, self, err); + } else { + process.nextTick(emitCloseNT, self); + } + } + try { + const result = self._destroy(err || null, onDestroy); + if (result != null) { + const then = result.then; + if (typeof then === "function") { + then.call( + result, + function () { + process.nextTick(onDestroy, null); + }, + function (err) { + process.nextTick(onDestroy, err); + }, + ); + } + } + } catch (err) { + onDestroy(err); + } +} + +function emitErrorCloseNT(self, err) { + emitErrorNT(self, err); + emitCloseNT(self); +} + +function emitCloseNT(self) { + const r = self._readableState; + const w = self._writableState; + + if (w) { + w.closeEmitted = true; + } + if (r) { + r.closeEmitted = true; + } + + if ((w && w.emitClose) || (r && r.emitClose)) { + self.emit("close"); + } +} + +function emitErrorNT(self, err) { + const r = self._readableState; + const w = self._writableState; + + if ((w && w.errorEmitted) || (r && r.errorEmitted)) { + return; + } + + if (w) { + w.errorEmitted = true; + } + if (r) { + r.errorEmitted = true; + } + + self.emit("error", err); +} + +function undestroy() { + const r = this._readableState; + const w = this._writableState; + + if (r) { + r.constructed = true; + r.closed = false; + r.closeEmitted = false; + r.destroyed = false; + r.errored = null; + r.errorEmitted = false; + r.reading = false; + r.ended = false; + r.endEmitted = false; + } + + if (w) { + w.constructed = true; + w.destroyed = false; + w.closed = false; + w.closeEmitted = false; + w.errored = null; + w.errorEmitted = false; + w.ended = false; + w.ending = false; + w.finalCalled = false; + w.prefinished = false; + w.finished = false; + } +} + +function errorOrDestroy(stream, err, sync) { + // We have tests that rely on errors being emitted + // in the same tick, so changing this is semver major. + // For now when you opt-in to autoDestroy we allow + // the error to be emitted nextTick. In a future + // semver major update we should change the default to this. + + const r = stream._readableState; + const w = stream._writableState; + + if ((w && w.destroyed) || (r && r.destroyed)) { + return this; + } + + if ((r && r.autoDestroy) || (w && w.autoDestroy)) { + stream.destroy(err); + } else if (err) { + // Avoid V8 leak, https://github.com/nodejs/node/pull/34103#issuecomment-652002364 + err.stack; // eslint-disable-line no-unused-expressions + + if (w && !w.errored) { + w.errored = err; + } + if (r && !r.errored) { + r.errored = err; + } + if (sync) { + process.nextTick(emitErrorNT, stream, err); + } else { + emitErrorNT(stream, err); + } + } +} + +function construct(stream, cb) { + if (typeof stream._construct !== "function") { + return; + } + + const r = stream._readableState; + const w = stream._writableState; + + if (r) { + r.constructed = false; + } + if (w) { + w.constructed = false; + } + + stream.once(kConstruct, cb); + + if (stream.listenerCount(kConstruct) > 1) { + // Duplex + return; + } + + process.nextTick(constructNT, stream); +} + +function constructNT(stream) { + let called = false; + + function onConstruct(err) { + if (called) { + errorOrDestroy(stream, err ?? new ERR_MULTIPLE_CALLBACK()); + return; + } + called = true; + + const r = stream._readableState; + const w = stream._writableState; + const s = w || r; + + if (r) { + r.constructed = true; + } + if (w) { + w.constructed = true; + } + + if (s.destroyed) { + stream.emit(kDestroy, err); + } else if (err) { + errorOrDestroy(stream, err, true); + } else { + process.nextTick(emitConstructNT, stream); + } + } + + try { + const result = stream._construct(onConstruct); + if (result != null) { + const then = result.then; + if (typeof then === "function") { + then.call( + result, + function () { + process.nextTick(onConstruct, null); + }, + function (err) { + process.nextTick(onConstruct, err); + }, + ); + } + } + } catch (err) { + onConstruct(err); + } +} + +function emitConstructNT(stream) { + stream.emit(kConstruct); +} + +function isRequest(stream) { + return stream && stream.setHeader && typeof stream.abort === "function"; +} + +// Normalize destroy for legacy. +function destroyer(stream, err) { + if (!stream) return; + if (isRequest(stream)) return stream.abort(); + if (isRequest(stream.req)) return stream.req.abort(); + if (typeof stream.destroy === "function") return stream.destroy(err); + if (typeof stream.close === "function") return stream.close(); +} + +export default { + construct, + destroyer, + destroy, + undestroy, + errorOrDestroy, +}; +export { construct, destroy, destroyer, errorOrDestroy, undestroy }; diff --git a/ext/node/polyfills/internal/streams/duplex.mjs b/ext/node/polyfills/internal/streams/duplex.mjs new file mode 100644 index 00000000000000..b2086d467506ba --- /dev/null +++ b/ext/node/polyfills/internal/streams/duplex.mjs @@ -0,0 +1,9 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// deno-lint-ignore-file + +import { Duplex } from "internal:deno_node/polyfills/_stream.mjs"; +const { from, fromWeb, toWeb } = Duplex; + +export default Duplex; +export { from, fromWeb, toWeb }; diff --git a/ext/node/polyfills/internal/streams/end-of-stream.mjs b/ext/node/polyfills/internal/streams/end-of-stream.mjs new file mode 100644 index 00000000000000..b5c380d56df0b0 --- /dev/null +++ b/ext/node/polyfills/internal/streams/end-of-stream.mjs @@ -0,0 +1,229 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// deno-lint-ignore-file + +import { AbortError, ERR_STREAM_PREMATURE_CLOSE } from "internal:deno_node/polyfills/internal/errors.ts"; +import { once } from "internal:deno_node/polyfills/internal/util.mjs"; +import { + validateAbortSignal, + validateFunction, + validateObject, +} from "internal:deno_node/polyfills/internal/validators.mjs"; +import * as process from "internal:deno_node/polyfills/_process/process.ts"; + +function isRequest(stream) { + return stream.setHeader && typeof stream.abort === "function"; +} + +function isServerResponse(stream) { + return ( + typeof stream._sent100 === "boolean" && + typeof stream._removedConnection === "boolean" && + typeof stream._removedContLen === "boolean" && + typeof stream._removedTE === "boolean" && + typeof stream._closed === "boolean" + ); +} + +function isReadable(stream) { + return typeof stream.readable === "boolean" || + typeof stream.readableEnded === "boolean" || + !!stream._readableState; +} + +function isWritable(stream) { + return typeof stream.writable === "boolean" || + typeof stream.writableEnded === "boolean" || + !!stream._writableState; +} + +function isWritableFinished(stream) { + if (stream.writableFinished) return true; + const wState = stream._writableState; + if (!wState || wState.errored) return false; + return wState.finished || (wState.ended && wState.length === 0); +} + +const nop = () => {}; + +function isReadableEnded(stream) { + if (stream.readableEnded) return true; + const rState = stream._readableState; + if (!rState || rState.errored) return false; + return rState.endEmitted || (rState.ended && rState.length === 0); +} + +function eos(stream, options, callback) { + if (arguments.length === 2) { + callback = options; + options = {}; + } else if (options == null) { + options = {}; + } else { + validateObject(options, "options"); + } + validateFunction(callback, "callback"); + validateAbortSignal(options.signal, "options.signal"); + + callback = once(callback); + + const readable = options.readable || + (options.readable !== false && isReadable(stream)); + const writable = options.writable || + (options.writable !== false && isWritable(stream)); + + const wState = stream._writableState; + const rState = stream._readableState; + const state = wState || rState; + + const onlegacyfinish = () => { + if (!stream.writable) onfinish(); + }; + + // TODO (ronag): Improve soft detection to include core modules and + // common ecosystem modules that do properly emit 'close' but fail + // this generic check. + let willEmitClose = isServerResponse(stream) || ( + state && + state.autoDestroy && + state.emitClose && + state.closed === false && + isReadable(stream) === readable && + isWritable(stream) === writable + ); + + let writableFinished = stream.writableFinished || + (wState && wState.finished); + const onfinish = () => { + writableFinished = true; + // Stream should not be destroyed here. If it is that + // means that user space is doing something differently and + // we cannot trust willEmitClose. + if (stream.destroyed) willEmitClose = false; + + if (willEmitClose && (!stream.readable || readable)) return; + if (!readable || readableEnded) callback.call(stream); + }; + + let readableEnded = stream.readableEnded || + (rState && rState.endEmitted); + const onend = () => { + readableEnded = true; + // Stream should not be destroyed here. If it is that + // means that user space is doing something differently and + // we cannot trust willEmitClose. + if (stream.destroyed) willEmitClose = false; + + if (willEmitClose && (!stream.writable || writable)) return; + if (!writable || writableFinished) callback.call(stream); + }; + + const onerror = (err) => { + callback.call(stream, err); + }; + + const onclose = () => { + if (readable && !readableEnded) { + if (!isReadableEnded(stream)) { + return callback.call(stream, new ERR_STREAM_PREMATURE_CLOSE()); + } + } + if (writable && !writableFinished) { + if (!isWritableFinished(stream)) { + return callback.call(stream, new ERR_STREAM_PREMATURE_CLOSE()); + } + } + callback.call(stream); + }; + + const onrequest = () => { + stream.req.on("finish", onfinish); + }; + + if (isRequest(stream)) { + stream.on("complete", onfinish); + if (!willEmitClose) { + stream.on("abort", onclose); + } + if (stream.req) onrequest(); + else stream.on("request", onrequest); + } else if (writable && !wState) { // legacy streams + stream.on("end", onlegacyfinish); + stream.on("close", onlegacyfinish); + } + + // Not all streams will emit 'close' after 'aborted'. + if (!willEmitClose && typeof stream.aborted === "boolean") { + stream.on("aborted", onclose); + } + + stream.on("end", onend); + stream.on("finish", onfinish); + if (options.error !== false) stream.on("error", onerror); + stream.on("close", onclose); + + // _closed is for OutgoingMessage which is not a proper Writable. + const closed = (!wState && !rState && stream._closed === true) || ( + (wState && wState.closed) || + (rState && rState.closed) || + (wState && wState.errorEmitted) || + (rState && rState.errorEmitted) || + (rState && stream.req && stream.aborted) || + ( + (!wState || !willEmitClose || typeof wState.closed !== "boolean") && + (!rState || !willEmitClose || typeof rState.closed !== "boolean") && + (!writable || (wState && wState.finished)) && + (!readable || (rState && rState.endEmitted)) + ) + ); + + if (closed) { + // TODO(ronag): Re-throw error if errorEmitted? + // TODO(ronag): Throw premature close as if finished was called? + // before being closed? i.e. if closed but not errored, ended or finished. + // TODO(ronag): Throw some kind of error? Does it make sense + // to call finished() on a "finished" stream? + // TODO(ronag): willEmitClose? + process.nextTick(() => { + callback(); + }); + } + + const cleanup = () => { + callback = nop; + stream.removeListener("aborted", onclose); + stream.removeListener("complete", onfinish); + stream.removeListener("abort", onclose); + stream.removeListener("request", onrequest); + if (stream.req) stream.req.removeListener("finish", onfinish); + stream.removeListener("end", onlegacyfinish); + stream.removeListener("close", onlegacyfinish); + stream.removeListener("finish", onfinish); + stream.removeListener("end", onend); + stream.removeListener("error", onerror); + stream.removeListener("close", onclose); + }; + + if (options.signal && !closed) { + const abort = () => { + // Keep it because cleanup removes it. + const endCallback = callback; + cleanup(); + endCallback.call(stream, new AbortError()); + }; + if (options.signal.aborted) { + process.nextTick(abort); + } else { + const originalCallback = callback; + callback = once((...args) => { + options.signal.removeEventListener("abort", abort); + originalCallback.apply(stream, args); + }); + options.signal.addEventListener("abort", abort); + } + } + + return cleanup; +} + +export default eos; diff --git a/ext/node/polyfills/internal/streams/lazy_transform.mjs b/ext/node/polyfills/internal/streams/lazy_transform.mjs new file mode 100644 index 00000000000000..2bb93bd9196257 --- /dev/null +++ b/ext/node/polyfills/internal/streams/lazy_transform.mjs @@ -0,0 +1,53 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// deno-lint-ignore-file + +import { getDefaultEncoding } from "internal:deno_node/polyfills/internal/crypto/util.ts"; +import stream from "internal:deno_node/polyfills/stream.ts"; + +function LazyTransform(options) { + this._options = options; +} +Object.setPrototypeOf(LazyTransform.prototype, stream.Transform.prototype); +Object.setPrototypeOf(LazyTransform, stream.Transform); + +function makeGetter(name) { + return function () { + stream.Transform.call(this, this._options); + this._writableState.decodeStrings = false; + + if (!this._options || !this._options.defaultEncoding) { + this._writableState.defaultEncoding = getDefaultEncoding(); + } + + return this[name]; + }; +} + +function makeSetter(name) { + return function (val) { + Object.defineProperty(this, name, { + value: val, + enumerable: true, + configurable: true, + writable: true, + }); + }; +} + +Object.defineProperties(LazyTransform.prototype, { + _readableState: { + get: makeGetter("_readableState"), + set: makeSetter("_readableState"), + configurable: true, + enumerable: true, + }, + _writableState: { + get: makeGetter("_writableState"), + set: makeSetter("_writableState"), + configurable: true, + enumerable: true, + }, +}); + +export default LazyTransform; diff --git a/ext/node/polyfills/internal/streams/legacy.mjs b/ext/node/polyfills/internal/streams/legacy.mjs new file mode 100644 index 00000000000000..0de18956f19a78 --- /dev/null +++ b/ext/node/polyfills/internal/streams/legacy.mjs @@ -0,0 +1,113 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// deno-lint-ignore-file + +import EE from "internal:deno_node/polyfills/events.ts"; + +function Stream(opts) { + EE.call(this, opts); +} +Object.setPrototypeOf(Stream.prototype, EE.prototype); +Object.setPrototypeOf(Stream, EE); + +Stream.prototype.pipe = function (dest, options) { + // deno-lint-ignore no-this-alias + const source = this; + + function ondata(chunk) { + if (dest.writable && dest.write(chunk) === false && source.pause) { + source.pause(); + } + } + + source.on("data", ondata); + + function ondrain() { + if (source.readable && source.resume) { + source.resume(); + } + } + + dest.on("drain", ondrain); + + // If the 'end' option is not supplied, dest.end() will be called when + // source gets the 'end' or 'close' events. Only dest.end() once. + if (!dest._isStdio && (!options || options.end !== false)) { + source.on("end", onend); + source.on("close", onclose); + } + + let didOnEnd = false; + function onend() { + if (didOnEnd) return; + didOnEnd = true; + + dest.end(); + } + + function onclose() { + if (didOnEnd) return; + didOnEnd = true; + + if (typeof dest.destroy === "function") dest.destroy(); + } + + // Don't leave dangling pipes when there are errors. + function onerror(er) { + cleanup(); + if (EE.listenerCount(this, "error") === 0) { + this.emit("error", er); + } + } + + prependListener(source, "error", onerror); + prependListener(dest, "error", onerror); + + // Remove all the event listeners that were added. + function cleanup() { + source.removeListener("data", ondata); + dest.removeListener("drain", ondrain); + + source.removeListener("end", onend); + source.removeListener("close", onclose); + + source.removeListener("error", onerror); + dest.removeListener("error", onerror); + + source.removeListener("end", cleanup); + source.removeListener("close", cleanup); + + dest.removeListener("close", cleanup); + } + + source.on("end", cleanup); + source.on("close", cleanup); + + dest.on("close", cleanup); + dest.emit("pipe", source); + + // Allow for unix-like usage: A.pipe(B).pipe(C) + return dest; +}; + +function prependListener(emitter, event, fn) { + // Sadly this is not cacheable as some libraries bundle their own + // event emitter implementation with them. + if (typeof emitter.prependListener === "function") { + return emitter.prependListener(event, fn); + } + + // This is a hack to make sure that our error handler is attached before any + // userland ones. NEVER DO THIS. This is here only because this code needs + // to continue to work with older versions of Node.js that do not include + // the prependListener() method. The goal is to eventually remove this hack. + if (!emitter._events || !emitter._events[event]) { + emitter.on(event, fn); + } else if (Array.isArray(emitter._events[event])) { + emitter._events[event].unshift(fn); + } else { + emitter._events[event] = [fn, emitter._events[event]]; + } +} + +export { prependListener, Stream }; diff --git a/ext/node/polyfills/internal/streams/passthrough.mjs b/ext/node/polyfills/internal/streams/passthrough.mjs new file mode 100644 index 00000000000000..136a0484a5e47f --- /dev/null +++ b/ext/node/polyfills/internal/streams/passthrough.mjs @@ -0,0 +1,7 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// deno-lint-ignore-file + +import { PassThrough } from "internal:deno_node/polyfills/_stream.mjs"; + +export default PassThrough; diff --git a/ext/node/polyfills/internal/streams/readable.mjs b/ext/node/polyfills/internal/streams/readable.mjs new file mode 100644 index 00000000000000..36133d29709646 --- /dev/null +++ b/ext/node/polyfills/internal/streams/readable.mjs @@ -0,0 +1,9 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// deno-lint-ignore-file + +import { Readable } from "internal:deno_node/polyfills/_stream.mjs"; +const { ReadableState, _fromList, from, fromWeb, toWeb, wrap } = Readable; + +export default Readable; +export { _fromList, from, fromWeb, ReadableState, toWeb, wrap }; diff --git a/ext/node/polyfills/internal/streams/state.mjs b/ext/node/polyfills/internal/streams/state.mjs new file mode 100644 index 00000000000000..93708fe9d00b7b --- /dev/null +++ b/ext/node/polyfills/internal/streams/state.mjs @@ -0,0 +1,10 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// deno-lint-ignore-file + +function getDefaultHighWaterMark(objectMode) { + return objectMode ? 16 : 16 * 1024; +} + +export default { getDefaultHighWaterMark }; +export { getDefaultHighWaterMark }; diff --git a/ext/node/polyfills/internal/streams/transform.mjs b/ext/node/polyfills/internal/streams/transform.mjs new file mode 100644 index 00000000000000..3fc4fa5cdcf2d6 --- /dev/null +++ b/ext/node/polyfills/internal/streams/transform.mjs @@ -0,0 +1,7 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// deno-lint-ignore-file + +import { Transform } from "internal:deno_node/polyfills/_stream.mjs"; + +export default Transform; diff --git a/ext/node/polyfills/internal/streams/utils.mjs b/ext/node/polyfills/internal/streams/utils.mjs new file mode 100644 index 00000000000000..a575f831d33597 --- /dev/null +++ b/ext/node/polyfills/internal/streams/utils.mjs @@ -0,0 +1,242 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// deno-lint-ignore-file + +const kIsDisturbed = Symbol("kIsDisturbed"); + +function isReadableNodeStream(obj) { + return !!( + obj && + typeof obj.pipe === "function" && + typeof obj.on === "function" && + (!obj._writableState || obj._readableState?.readable !== false) && // Duplex + (!obj._writableState || obj._readableState) // Writable has .pipe. + ); +} + +function isWritableNodeStream(obj) { + return !!( + obj && + typeof obj.write === "function" && + typeof obj.on === "function" && + (!obj._readableState || obj._writableState?.writable !== false) // Duplex + ); +} + +function isDuplexNodeStream(obj) { + return !!( + obj && + (typeof obj.pipe === "function" && obj._readableState) && + typeof obj.on === "function" && + typeof obj.write === "function" + ); +} + +function isNodeStream(obj) { + return ( + obj && + ( + obj._readableState || + obj._writableState || + (typeof obj.write === "function" && typeof obj.on === "function") || + (typeof obj.pipe === "function" && typeof obj.on === "function") + ) + ); +} + +function isDestroyed(stream) { + if (!isNodeStream(stream)) return null; + const wState = stream._writableState; + const rState = stream._readableState; + const state = wState || rState; + return !!(stream.destroyed || state?.destroyed); +} + +// Have been end():d. +function isWritableEnded(stream) { + if (!isWritableNodeStream(stream)) return null; + if (stream.writableEnded === true) return true; + const wState = stream._writableState; + if (wState?.errored) return false; + if (typeof wState?.ended !== "boolean") return null; + return wState.ended; +} + +// Have emitted 'finish'. +function isWritableFinished(stream, strict) { + if (!isWritableNodeStream(stream)) return null; + if (stream.writableFinished === true) return true; + const wState = stream._writableState; + if (wState?.errored) return false; + if (typeof wState?.finished !== "boolean") return null; + return !!( + wState.finished || + (strict === false && wState.ended === true && wState.length === 0) + ); +} + +// Have been push(null):d. +function isReadableEnded(stream) { + if (!isReadableNodeStream(stream)) return null; + if (stream.readableEnded === true) return true; + const rState = stream._readableState; + if (!rState || rState.errored) return false; + if (typeof rState?.ended !== "boolean") return null; + return rState.ended; +} + +// Have emitted 'end'. +function isReadableFinished(stream, strict) { + if (!isReadableNodeStream(stream)) return null; + const rState = stream._readableState; + if (rState?.errored) return false; + if (typeof rState?.endEmitted !== "boolean") return null; + return !!( + rState.endEmitted || + (strict === false && rState.ended === true && rState.length === 0) + ); +} + +function isDisturbed(stream) { + return !!(stream && ( + stream.readableDidRead || + stream.readableAborted || + stream[kIsDisturbed] + )); +} + +function isReadable(stream) { + const r = isReadableNodeStream(stream); + if (r === null || typeof stream?.readable !== "boolean") return null; + if (isDestroyed(stream)) return false; + return r && stream.readable && !isReadableFinished(stream); +} + +function isWritable(stream) { + const r = isWritableNodeStream(stream); + if (r === null || typeof stream?.writable !== "boolean") return null; + if (isDestroyed(stream)) return false; + return r && stream.writable && !isWritableEnded(stream); +} + +function isFinished(stream, opts) { + if (!isNodeStream(stream)) { + return null; + } + + if (isDestroyed(stream)) { + return true; + } + + if (opts?.readable !== false && isReadable(stream)) { + return false; + } + + if (opts?.writable !== false && isWritable(stream)) { + return false; + } + + return true; +} + +function isClosed(stream) { + if (!isNodeStream(stream)) { + return null; + } + + const wState = stream._writableState; + const rState = stream._readableState; + + if ( + typeof wState?.closed === "boolean" || + typeof rState?.closed === "boolean" + ) { + return wState?.closed || rState?.closed; + } + + if (typeof stream._closed === "boolean" && isOutgoingMessage(stream)) { + return stream._closed; + } + + return null; +} + +function isOutgoingMessage(stream) { + return ( + typeof stream._closed === "boolean" && + typeof stream._defaultKeepAlive === "boolean" && + typeof stream._removedConnection === "boolean" && + typeof stream._removedContLen === "boolean" + ); +} + +function isServerResponse(stream) { + return ( + typeof stream._sent100 === "boolean" && + isOutgoingMessage(stream) + ); +} + +function isServerRequest(stream) { + return ( + typeof stream._consuming === "boolean" && + typeof stream._dumped === "boolean" && + stream.req?.upgradeOrConnect === undefined + ); +} + +function willEmitClose(stream) { + if (!isNodeStream(stream)) return null; + + const wState = stream._writableState; + const rState = stream._readableState; + const state = wState || rState; + + return (!state && isServerResponse(stream)) || !!( + state && + state.autoDestroy && + state.emitClose && + state.closed === false + ); +} + +export default { + isDisturbed, + kIsDisturbed, + isClosed, + isDestroyed, + isDuplexNodeStream, + isFinished, + isReadable, + isReadableNodeStream, + isReadableEnded, + isReadableFinished, + isNodeStream, + isWritable, + isWritableNodeStream, + isWritableEnded, + isWritableFinished, + isServerRequest, + isServerResponse, + willEmitClose, +}; +export { + isClosed, + isDestroyed, + isDisturbed, + isDuplexNodeStream, + isFinished, + isNodeStream, + isReadable, + isReadableEnded, + isReadableFinished, + isReadableNodeStream, + isServerRequest, + isServerResponse, + isWritable, + isWritableEnded, + isWritableFinished, + isWritableNodeStream, + kIsDisturbed, + willEmitClose, +}; diff --git a/ext/node/polyfills/internal/streams/writable.mjs b/ext/node/polyfills/internal/streams/writable.mjs new file mode 100644 index 00000000000000..6f4d77960fef24 --- /dev/null +++ b/ext/node/polyfills/internal/streams/writable.mjs @@ -0,0 +1,9 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// deno-lint-ignore-file + +import { Writable } from "internal:deno_node/polyfills/_stream.mjs"; +const { WritableState, fromWeb, toWeb } = Writable; + +export default Writable; +export { fromWeb, toWeb, WritableState }; diff --git a/ext/node/polyfills/internal/test/binding.ts b/ext/node/polyfills/internal/test/binding.ts new file mode 100644 index 00000000000000..996cc57aaa7644 --- /dev/null +++ b/ext/node/polyfills/internal/test/binding.ts @@ -0,0 +1,16 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. +// Copyright Joyent, Inc. and Node.js contributors. All rights reserved. MIT license. +import { getBinding } from "internal:deno_node/polyfills/internal_binding/mod.ts"; +import type { BindingName } from "internal:deno_node/polyfills/internal_binding/mod.ts"; + +export function internalBinding(name: BindingName) { + return getBinding(name); +} + +// TODO(kt3k): export actual primordials +export const primordials = {}; + +export default { + internalBinding, + primordials, +}; diff --git a/ext/node/polyfills/internal/timers.mjs b/ext/node/polyfills/internal/timers.mjs new file mode 100644 index 00000000000000..6796885ce2cc6b --- /dev/null +++ b/ext/node/polyfills/internal/timers.mjs @@ -0,0 +1,126 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. +// Copyright Joyent and Node contributors. All rights reserved. MIT license. + +import { inspect } from "internal:deno_node/polyfills/internal/util/inspect.mjs"; +import { validateFunction, validateNumber } from "internal:deno_node/polyfills/internal/validators.mjs"; +import { ERR_OUT_OF_RANGE } from "internal:deno_node/polyfills/internal/errors.ts"; +import { emitWarning } from "internal:deno_node/polyfills/process.ts"; +import { + setTimeout as setTimeout_, + clearTimeout as clearTimeout_, + setInterval as setInterval_, +} from "internal:deno_web/02_timers.js"; + +// Timeout values > TIMEOUT_MAX are set to 1. +export const TIMEOUT_MAX = 2 ** 31 - 1; + +export const kTimerId = Symbol("timerId"); +export const kTimeout = Symbol("timeout"); +const kRefed = Symbol("refed"); +const createTimer = Symbol("createTimer"); + +// Timer constructor function. +export function Timeout(callback, after, args, isRepeat, isRefed) { + if (typeof after === "number" && after > TIMEOUT_MAX) { + after = 1; + } + this._idleTimeout = after; + this._onTimeout = callback; + this._timerArgs = args; + this._isRepeat = isRepeat; + this[kRefed] = isRefed; + this[kTimerId] = this[createTimer](); +} + +Timeout.prototype[createTimer] = function () { + const callback = this._onTimeout; + const cb = (...args) => callback.bind(this)(...args); + const id = this._isRepeat + ? setInterval_(cb, this._idleTimeout, ...this._timerArgs) + : setTimeout_(cb, this._idleTimeout, ...this._timerArgs); + if (!this[kRefed]) { + Deno.unrefTimer(id); + } + return id; +}; + +// Make sure the linked list only shows the minimal necessary information. +Timeout.prototype[inspect.custom] = function (_, options) { + return inspect(this, { + ...options, + // Only inspect one level. + depth: 0, + // It should not recurse. + customInspect: false, + }); +}; + +Timeout.prototype.refresh = function () { + clearTimeout_(this[kTimerId]); + this[kTimerId] = this[createTimer](); + return this; +}; + +Timeout.prototype.unref = function () { + if (this[kRefed]) { + this[kRefed] = false; + Deno.unrefTimer(this[kTimerId]); + } + return this; +}; + +Timeout.prototype.ref = function () { + if (!this[kRefed]) { + this[kRefed] = true; + Deno.refTimer(this[kTimerId]); + } + return this; +}; + +Timeout.prototype.hasRef = function () { + return this[kRefed]; +}; + +Timeout.prototype[Symbol.toPrimitive] = function () { + return this[kTimerId]; +}; + +/** + * @param {number} msecs + * @param {string} name + * @returns + */ +export function getTimerDuration(msecs, name) { + validateNumber(msecs, name); + + if (msecs < 0 || !Number.isFinite(msecs)) { + throw new ERR_OUT_OF_RANGE(name, "a non-negative finite number", msecs); + } + + // Ensure that msecs fits into signed int32 + if (msecs > TIMEOUT_MAX) { + emitWarning( + `${msecs} does not fit into a 32-bit signed integer.` + + `\nTimer duration was truncated to ${TIMEOUT_MAX}.`, + "TimeoutOverflowWarning", + ); + + return TIMEOUT_MAX; + } + + return msecs; +} + +export function setUnrefTimeout(callback, timeout, ...args) { + validateFunction(callback, "callback"); + return new Timeout(callback, timeout, args, false, false); +} + +export default { + getTimerDuration, + kTimerId, + kTimeout, + setUnrefTimeout, + Timeout, + TIMEOUT_MAX, +}; diff --git a/ext/node/polyfills/internal/url.ts b/ext/node/polyfills/internal/url.ts new file mode 100644 index 00000000000000..415ad9be6ec365 --- /dev/null +++ b/ext/node/polyfills/internal/url.ts @@ -0,0 +1,49 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. +import { fileURLToPath } from "internal:deno_node/polyfills/url.ts"; +import { Buffer } from "internal:deno_node/polyfills/buffer.ts"; + +const searchParams = Symbol("query"); + +export function toPathIfFileURL( + fileURLOrPath: string | Buffer | URL, +): string | Buffer { + if (!(fileURLOrPath instanceof URL)) { + return fileURLOrPath; + } + return fileURLToPath(fileURLOrPath); +} + +// Utility function that converts a URL object into an ordinary +// options object as expected by the http.request and https.request +// APIs. +// deno-lint-ignore no-explicit-any +export function urlToHttpOptions(url: any): any { + // deno-lint-ignore no-explicit-any + const options: any = { + protocol: url.protocol, + hostname: typeof url.hostname === "string" && + url.hostname.startsWith("[") + ? url.hostname.slice(1, -1) + : url.hostname, + hash: url.hash, + search: url.search, + pathname: url.pathname, + path: `${url.pathname || ""}${url.search || ""}`, + href: url.href, + }; + if (url.port !== "") { + options.port = Number(url.port); + } + if (url.username || url.password) { + options.auth = `${decodeURIComponent(url.username)}:${ + decodeURIComponent(url.password) + }`; + } + return options; +} + +export { searchParams as searchParamsSymbol }; + +export default { + toPathIfFileURL, +}; diff --git a/ext/node/polyfills/internal/util.mjs b/ext/node/polyfills/internal/util.mjs new file mode 100644 index 00000000000000..ba26c6a6a095b8 --- /dev/null +++ b/ext/node/polyfills/internal/util.mjs @@ -0,0 +1,141 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. +import { validateFunction } from "internal:deno_node/polyfills/internal/validators.mjs"; +import { normalizeEncoding, slowCases } from "internal:deno_node/polyfills/internal/normalize_encoding.mjs"; +export { normalizeEncoding, slowCases }; +import { ObjectCreate, StringPrototypeToUpperCase } from "internal:deno_node/polyfills/internal/primordials.mjs"; +import { ERR_UNKNOWN_SIGNAL } from "internal:deno_node/polyfills/internal/errors.ts"; +import { os } from "internal:deno_node/polyfills/internal_binding/constants.ts"; + +export const customInspectSymbol = Symbol.for("nodejs.util.inspect.custom"); +export const kEnumerableProperty = Object.create(null); +kEnumerableProperty.enumerable = true; + +export const kEmptyObject = Object.freeze(Object.create(null)); + +export function once(callback) { + let called = false; + return function (...args) { + if (called) return; + called = true; + Reflect.apply(callback, this, args); + }; +} + +export function createDeferredPromise() { + let resolve; + let reject; + const promise = new Promise((res, rej) => { + resolve = res; + reject = rej; + }); + + return { promise, resolve, reject }; +} + +// In addition to being accessible through util.promisify.custom, +// this symbol is registered globally and can be accessed in any environment as +// Symbol.for('nodejs.util.promisify.custom'). +const kCustomPromisifiedSymbol = Symbol.for("nodejs.util.promisify.custom"); +// This is an internal Node symbol used by functions returning multiple +// arguments, e.g. ['bytesRead', 'buffer'] for fs.read(). +const kCustomPromisifyArgsSymbol = Symbol.for( + "nodejs.util.promisify.customArgs", +); + +export const customPromisifyArgs = kCustomPromisifyArgsSymbol; + +export function promisify( + original, +) { + validateFunction(original, "original"); + if (original[kCustomPromisifiedSymbol]) { + const fn = original[kCustomPromisifiedSymbol]; + + validateFunction(fn, "util.promisify.custom"); + + return Object.defineProperty(fn, kCustomPromisifiedSymbol, { + value: fn, + enumerable: false, + writable: false, + configurable: true, + }); + } + + // Names to create an object from in case the callback receives multiple + // arguments, e.g. ['bytesRead', 'buffer'] for fs.read. + const argumentNames = original[kCustomPromisifyArgsSymbol]; + function fn(...args) { + return new Promise((resolve, reject) => { + args.push((err, ...values) => { + if (err) { + return reject(err); + } + if (argumentNames !== undefined && values.length > 1) { + const obj = {}; + for (let i = 0; i < argumentNames.length; i++) { + obj[argumentNames[i]] = values[i]; + } + resolve(obj); + } else { + resolve(values[0]); + } + }); + Reflect.apply(original, this, args); + }); + } + + Object.setPrototypeOf(fn, Object.getPrototypeOf(original)); + + Object.defineProperty(fn, kCustomPromisifiedSymbol, { + value: fn, + enumerable: false, + writable: false, + configurable: true, + }); + return Object.defineProperties( + fn, + Object.getOwnPropertyDescriptors(original), + ); +} + +let signalsToNamesMapping; +function getSignalsToNamesMapping() { + if (signalsToNamesMapping !== undefined) { + return signalsToNamesMapping; + } + + signalsToNamesMapping = ObjectCreate(null); + for (const key in os.signals) { + signalsToNamesMapping[os.signals[key]] = key; + } + + return signalsToNamesMapping; +} + +export function convertToValidSignal(signal) { + if (typeof signal === "number" && getSignalsToNamesMapping()[signal]) { + return signal; + } + + if (typeof signal === "string") { + const signalName = os.signals[StringPrototypeToUpperCase(signal)]; + if (signalName) return signalName; + } + + throw new ERR_UNKNOWN_SIGNAL(signal); +} + +promisify.custom = kCustomPromisifiedSymbol; + +export default { + convertToValidSignal, + createDeferredPromise, + customInspectSymbol, + customPromisifyArgs, + kEmptyObject, + kEnumerableProperty, + normalizeEncoding, + once, + promisify, + slowCases, +}; diff --git a/ext/node/polyfills/internal/util/comparisons.ts b/ext/node/polyfills/internal/util/comparisons.ts new file mode 100644 index 00000000000000..1620e468b5ba57 --- /dev/null +++ b/ext/node/polyfills/internal/util/comparisons.ts @@ -0,0 +1,681 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. +// Copyright Joyent and Node contributors. All rights reserved. MIT license. + +// deno-lint-ignore-file +import { + isAnyArrayBuffer, + isArrayBufferView, + isBigIntObject, + isBooleanObject, + isBoxedPrimitive, + isDate, + isFloat32Array, + isFloat64Array, + isMap, + isNativeError, + isNumberObject, + isRegExp, + isSet, + isStringObject, + isSymbolObject, + isTypedArray, +} from "internal:deno_node/polyfills/internal/util/types.ts"; + +import { Buffer } from "internal:deno_node/polyfills/buffer.ts"; +import { + getOwnNonIndexProperties, + ONLY_ENUMERABLE, + SKIP_SYMBOLS, +} from "internal:deno_node/polyfills/internal_binding/util.ts"; + +enum valueType { + noIterator, + isArray, + isSet, + isMap, +} + +interface Memo { + val1: Map; + val2: Map; + position: number; +} +let memo: Memo; + +export function isDeepStrictEqual(val1: unknown, val2: unknown): boolean { + return innerDeepEqual(val1, val2, true); +} +export function isDeepEqual(val1: unknown, val2: unknown): boolean { + return innerDeepEqual(val1, val2, false); +} + +function innerDeepEqual( + val1: unknown, + val2: unknown, + strict: boolean, + memos = memo, +): boolean { + // Basic case covered by Strict Equality Comparison + if (val1 === val2) { + if (val1 !== 0) return true; + return strict ? Object.is(val1, val2) : true; + } + if (strict) { + // Cases where the values are not objects + // If both values are Not a Number NaN + if (typeof val1 !== "object") { + return ( + typeof val1 === "number" && Number.isNaN(val1) && Number.isNaN(val2) + ); + } + // If either value is null + if (typeof val2 !== "object" || val1 === null || val2 === null) { + return false; + } + // If the prototype are not the same + if (Object.getPrototypeOf(val1) !== Object.getPrototypeOf(val2)) { + return false; + } + } else { + // Non strict case where values are either null or NaN + if (val1 === null || typeof val1 !== "object") { + if (val2 === null || typeof val2 !== "object") { + return val1 == val2 || (Number.isNaN(val1) && Number.isNaN(val2)); + } + return false; + } + if (val2 === null || typeof val2 !== "object") { + return false; + } + } + + const val1Tag = Object.prototype.toString.call(val1); + const val2Tag = Object.prototype.toString.call(val2); + + // prototype must be Strictly Equal + if ( + val1Tag !== val2Tag + ) { + return false; + } + + // handling when values are array + if (Array.isArray(val1)) { + // quick rejection cases + if (!Array.isArray(val2) || val1.length !== val2.length) { + return false; + } + const filter = strict ? ONLY_ENUMERABLE : ONLY_ENUMERABLE | SKIP_SYMBOLS; + const keys1 = getOwnNonIndexProperties(val1, filter); + const keys2 = getOwnNonIndexProperties(val2, filter); + if (keys1.length !== keys2.length) { + return false; + } + return keyCheck(val1, val2, strict, memos, valueType.isArray, keys1); + } else if (val1Tag === "[object Object]") { + return keyCheck( + val1 as object, + val2 as object, + strict, + memos, + valueType.noIterator, + ); + } else if (val1 instanceof Date) { + if (!(val2 instanceof Date) || val1.getTime() !== val2.getTime()) { + return false; + } + } else if (val1 instanceof RegExp) { + if (!(val2 instanceof RegExp) || !areSimilarRegExps(val1, val2)) { + return false; + } + } else if (isNativeError(val1) || val1 instanceof Error) { + // stack may or may not be same, hence it shouldn't be compared + if ( + // How to handle the type errors here + (!isNativeError(val2) && !(val2 instanceof Error)) || + (val1 as Error).message !== (val2 as Error).message || + (val1 as Error).name !== (val2 as Error).name + ) { + return false; + } + } else if (isArrayBufferView(val1)) { + const TypedArrayPrototypeGetSymbolToStringTag = ( + val: + | BigInt64Array + | BigUint64Array + | Float32Array + | Float64Array + | Int8Array + | Int16Array + | Int32Array + | Uint8Array + | Uint8ClampedArray + | Uint16Array + | Uint32Array, + ) => + Object.getOwnPropertySymbols(val) + .map((item) => item.toString()) + .toString(); + if ( + isTypedArray(val1) && + isTypedArray(val2) && + (TypedArrayPrototypeGetSymbolToStringTag(val1) !== + TypedArrayPrototypeGetSymbolToStringTag(val2)) + ) { + return false; + } + + if (!strict && (isFloat32Array(val1) || isFloat64Array(val1))) { + if (!areSimilarFloatArrays(val1, val2)) { + return false; + } + } else if (!areSimilarTypedArrays(val1, val2)) { + return false; + } + const filter = strict ? ONLY_ENUMERABLE : ONLY_ENUMERABLE | SKIP_SYMBOLS; + const keysVal1 = getOwnNonIndexProperties(val1 as object, filter); + const keysVal2 = getOwnNonIndexProperties(val2 as object, filter); + if (keysVal1.length !== keysVal2.length) { + return false; + } + return keyCheck( + val1 as object, + val2 as object, + strict, + memos, + valueType.noIterator, + keysVal1, + ); + } else if (isSet(val1)) { + if ( + !isSet(val2) || + (val1 as Set).size !== (val2 as Set).size + ) { + return false; + } + return keyCheck( + val1 as object, + val2 as object, + strict, + memos, + valueType.isSet, + ); + } else if (isMap(val1)) { + if ( + !isMap(val2) || val1.size !== val2.size + ) { + return false; + } + return keyCheck( + val1 as object, + val2 as object, + strict, + memos, + valueType.isMap, + ); + } else if (isAnyArrayBuffer(val1)) { + if (!isAnyArrayBuffer(val2) || !areEqualArrayBuffers(val1, val2)) { + return false; + } + } else if (isBoxedPrimitive(val1)) { + if (!isEqualBoxedPrimitive(val1, val2)) { + return false; + } + } else if ( + Array.isArray(val2) || + isArrayBufferView(val2) || + isSet(val2) || + isMap(val2) || + isDate(val2) || + isRegExp(val2) || + isAnyArrayBuffer(val2) || + isBoxedPrimitive(val2) || + isNativeError(val2) || + val2 instanceof Error + ) { + return false; + } + return keyCheck( + val1 as object, + val2 as object, + strict, + memos, + valueType.noIterator, + ); +} + +function keyCheck( + val1: object, + val2: object, + strict: boolean, + memos: Memo, + iterationType: valueType, + aKeys: (string | symbol)[] = [], +) { + if (arguments.length === 5) { + aKeys = Object.keys(val1); + const bKeys = Object.keys(val2); + + // The pair must have the same number of owned properties. + if (aKeys.length !== bKeys.length) { + return false; + } + } + + // Cheap key test + let i = 0; + for (; i < aKeys.length; i++) { + if (!val2.propertyIsEnumerable(aKeys[i])) { + return false; + } + } + + if (strict && arguments.length === 5) { + const symbolKeysA = Object.getOwnPropertySymbols(val1); + if (symbolKeysA.length !== 0) { + let count = 0; + for (i = 0; i < symbolKeysA.length; i++) { + const key = symbolKeysA[i]; + if (val1.propertyIsEnumerable(key)) { + if (!val2.propertyIsEnumerable(key)) { + return false; + } + // added toString here + aKeys.push(key.toString()); + count++; + } else if (val2.propertyIsEnumerable(key)) { + return false; + } + } + const symbolKeysB = Object.getOwnPropertySymbols(val2); + if ( + symbolKeysA.length !== symbolKeysB.length && + getEnumerables(val2, symbolKeysB).length !== count + ) { + return false; + } + } else { + const symbolKeysB = Object.getOwnPropertySymbols(val2); + if ( + symbolKeysB.length !== 0 && + getEnumerables(val2, symbolKeysB).length !== 0 + ) { + return false; + } + } + } + if ( + aKeys.length === 0 && + (iterationType === valueType.noIterator || + (iterationType === valueType.isArray && (val1 as []).length === 0) || + (val1 as Set).size === 0) + ) { + return true; + } + + if (memos === undefined) { + memos = { + val1: new Map(), + val2: new Map(), + position: 0, + }; + } else { + const val2MemoA = memos.val1.get(val1); + if (val2MemoA !== undefined) { + const val2MemoB = memos.val2.get(val2); + if (val2MemoB !== undefined) { + return val2MemoA === val2MemoB; + } + } + memos.position++; + } + + memos.val1.set(val1, memos.position); + memos.val2.set(val2, memos.position); + + const areEq = objEquiv(val1, val2, strict, aKeys, memos, iterationType); + + memos.val1.delete(val1); + memos.val2.delete(val2); + + return areEq; +} + +function areSimilarRegExps(a: RegExp, b: RegExp) { + return a.source === b.source && a.flags === b.flags && + a.lastIndex === b.lastIndex; +} + +// TODO(standvpmnt): add type for arguments +function areSimilarFloatArrays(arr1: any, arr2: any): boolean { + if (arr1.byteLength !== arr2.byteLength) { + return false; + } + for (let i = 0; i < arr1.byteLength; i++) { + if (arr1[i] !== arr2[i]) { + return false; + } + } + return true; +} + +// TODO(standvpmnt): add type for arguments +function areSimilarTypedArrays(arr1: any, arr2: any): boolean { + if (arr1.byteLength !== arr2.byteLength) { + return false; + } + return ( + Buffer.compare( + new Uint8Array(arr1.buffer, arr1.byteOffset, arr1.byteLength), + new Uint8Array(arr2.buffer, arr2.byteOffset, arr2.byteLength), + ) === 0 + ); +} +// TODO(standvpmnt): add type for arguments +function areEqualArrayBuffers(buf1: any, buf2: any): boolean { + return ( + buf1.byteLength === buf2.byteLength && + Buffer.compare(new Uint8Array(buf1), new Uint8Array(buf2)) === 0 + ); +} + +// TODO(standvpmnt): this check of getOwnPropertySymbols and getOwnPropertyNames +// length is sufficient to handle the current test case, however this will fail +// to catch a scenario wherein the getOwnPropertySymbols and getOwnPropertyNames +// length is the same(will be very contrived but a possible shortcoming +function isEqualBoxedPrimitive(a: any, b: any): boolean { + if ( + Object.getOwnPropertyNames(a).length !== + Object.getOwnPropertyNames(b).length + ) { + return false; + } + if ( + Object.getOwnPropertySymbols(a).length !== + Object.getOwnPropertySymbols(b).length + ) { + return false; + } + if (isNumberObject(a)) { + return ( + isNumberObject(b) && + Object.is( + Number.prototype.valueOf.call(a), + Number.prototype.valueOf.call(b), + ) + ); + } + if (isStringObject(a)) { + return ( + isStringObject(b) && + (String.prototype.valueOf.call(a) === String.prototype.valueOf.call(b)) + ); + } + if (isBooleanObject(a)) { + return ( + isBooleanObject(b) && + (Boolean.prototype.valueOf.call(a) === Boolean.prototype.valueOf.call(b)) + ); + } + if (isBigIntObject(a)) { + return ( + isBigIntObject(b) && + (BigInt.prototype.valueOf.call(a) === BigInt.prototype.valueOf.call(b)) + ); + } + if (isSymbolObject(a)) { + return ( + isSymbolObject(b) && + (Symbol.prototype.valueOf.call(a) === + Symbol.prototype.valueOf.call(b)) + ); + } + // assert.fail(`Unknown boxed type ${val1}`); + // return false; + throw Error(`Unknown boxed type`); +} + +function getEnumerables(val: any, keys: any) { + return keys.filter((key: string) => val.propertyIsEnumerable(key)); +} + +function objEquiv( + obj1: any, + obj2: any, + strict: boolean, + keys: any, + memos: Memo, + iterationType: valueType, +): boolean { + let i = 0; + + if (iterationType === valueType.isSet) { + if (!setEquiv(obj1, obj2, strict, memos)) { + return false; + } + } else if (iterationType === valueType.isMap) { + if (!mapEquiv(obj1, obj2, strict, memos)) { + return false; + } + } else if (iterationType === valueType.isArray) { + for (; i < obj1.length; i++) { + if (obj1.hasOwnProperty(i)) { + if ( + !obj2.hasOwnProperty(i) || + !innerDeepEqual(obj1[i], obj2[i], strict, memos) + ) { + return false; + } + } else if (obj2.hasOwnProperty(i)) { + return false; + } else { + const keys1 = Object.keys(obj1); + for (; i < keys1.length; i++) { + const key = keys1[i]; + if ( + !obj2.hasOwnProperty(key) || + !innerDeepEqual(obj1[key], obj2[key], strict, memos) + ) { + return false; + } + } + if (keys1.length !== Object.keys(obj2).length) { + return false; + } + if (keys1.length !== Object.keys(obj2).length) { + return false; + } + return true; + } + } + } + + // Expensive test + for (i = 0; i < keys.length; i++) { + const key = keys[i]; + if (!innerDeepEqual(obj1[key], obj2[key], strict, memos)) { + return false; + } + } + return true; +} + +function findLooseMatchingPrimitives( + primitive: unknown, +): boolean | null | undefined { + switch (typeof primitive) { + case "undefined": + return null; + case "object": + return undefined; + case "symbol": + return false; + case "string": + primitive = +primitive; + case "number": + if (Number.isNaN(primitive)) { + return false; + } + } + return true; +} + +function setMightHaveLoosePrim( + set1: Set, + set2: Set, + primitive: any, +) { + const altValue = findLooseMatchingPrimitives(primitive); + if (altValue != null) return altValue; + + return set2.has(altValue) && !set1.has(altValue); +} + +function setHasEqualElement( + set: any, + val1: any, + strict: boolean, + memos: Memo, +): boolean { + for (const val2 of set) { + if (innerDeepEqual(val1, val2, strict, memos)) { + set.delete(val2); + return true; + } + } + + return false; +} + +function setEquiv(set1: any, set2: any, strict: boolean, memos: Memo): boolean { + let set = null; + for (const item of set1) { + if (typeof item === "object" && item !== null) { + if (set === null) { + // What is SafeSet from primordials? + // set = new SafeSet(); + set = new Set(); + } + set.add(item); + } else if (!set2.has(item)) { + if (strict) return false; + + if (!setMightHaveLoosePrim(set1, set2, item)) { + return false; + } + + if (set === null) { + set = new Set(); + } + set.add(item); + } + } + + if (set !== null) { + for (const item of set2) { + if (typeof item === "object" && item !== null) { + if (!setHasEqualElement(set, item, strict, memos)) return false; + } else if ( + !strict && + !set1.has(item) && + !setHasEqualElement(set, item, strict, memos) + ) { + return false; + } + } + return set.size === 0; + } + + return true; +} + +// TODO(standvpmnt): add types for argument +function mapMightHaveLoosePrimitive( + map1: Map, + map2: Map, + primitive: any, + item: any, + memos: Memo, +): boolean { + const altValue = findLooseMatchingPrimitives(primitive); + if (altValue != null) { + return altValue; + } + const curB = map2.get(altValue); + if ( + (curB === undefined && !map2.has(altValue)) || + !innerDeepEqual(item, curB, false, memo) + ) { + return false; + } + return !map1.has(altValue) && innerDeepEqual(item, curB, false, memos); +} + +function mapEquiv(map1: any, map2: any, strict: boolean, memos: Memo): boolean { + let set = null; + + for (const { 0: key, 1: item1 } of map1) { + if (typeof key === "object" && key !== null) { + if (set === null) { + set = new Set(); + } + set.add(key); + } else { + const item2 = map2.get(key); + if ( + ( + (item2 === undefined && !map2.has(key)) || + !innerDeepEqual(item1, item2, strict, memos) + ) + ) { + if (strict) return false; + if (!mapMightHaveLoosePrimitive(map1, map2, key, item1, memos)) { + return false; + } + if (set === null) { + set = new Set(); + } + set.add(key); + } + } + } + + if (set !== null) { + for (const { 0: key, 1: item } of map2) { + if (typeof key === "object" && key !== null) { + if (!mapHasEqualEntry(set, map1, key, item, strict, memos)) { + return false; + } + } else if ( + !strict && (!map1.has(key) || + !innerDeepEqual(map1.get(key), item, false, memos)) && + !mapHasEqualEntry(set, map1, key, item, false, memos) + ) { + return false; + } + } + return set.size === 0; + } + + return true; +} + +function mapHasEqualEntry( + set: any, + map: any, + key1: any, + item1: any, + strict: boolean, + memos: Memo, +): boolean { + for (const key2 of set) { + if ( + innerDeepEqual(key1, key2, strict, memos) && + innerDeepEqual(item1, map.get(key2), strict, memos) + ) { + set.delete(key2); + return true; + } + } + return false; +} diff --git a/ext/node/polyfills/internal/util/debuglog.ts b/ext/node/polyfills/internal/util/debuglog.ts new file mode 100644 index 00000000000000..498facbd159698 --- /dev/null +++ b/ext/node/polyfills/internal/util/debuglog.ts @@ -0,0 +1,121 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +import { inspect } from "internal:deno_node/polyfills/internal/util/inspect.mjs"; + +// `debugImpls` and `testEnabled` are deliberately not initialized so any call +// to `debuglog()` before `initializeDebugEnv()` is called will throw. +let debugImpls: Record void>; +let testEnabled: (str: string) => boolean; + +// `debugEnv` is initial value of process.env.NODE_DEBUG +function initializeDebugEnv(debugEnv: string) { + debugImpls = Object.create(null); + if (debugEnv) { + // This is run before any user code, it's OK not to use primordials. + debugEnv = debugEnv.replace(/[|\\{}()[\]^$+?.]/g, "\\$&") + .replaceAll("*", ".*") + .replaceAll(",", "$|^"); + const debugEnvRegex = new RegExp(`^${debugEnv}$`, "i"); + testEnabled = (str) => debugEnvRegex.exec(str) !== null; + } else { + testEnabled = () => false; + } +} + +// Emits warning when user sets +// NODE_DEBUG=http or NODE_DEBUG=http2. +function emitWarningIfNeeded(set: string) { + if ("HTTP" === set || "HTTP2" === set) { + console.warn( + "Setting the NODE_DEBUG environment variable " + + "to '" + set.toLowerCase() + "' can expose sensitive " + + "data (such as passwords, tokens and authentication headers) " + + "in the resulting log.", + ); + } +} + +const noop = () => {}; + +function debuglogImpl( + enabled: boolean, + set: string, +): (...args: unknown[]) => void { + if (debugImpls[set] === undefined) { + if (enabled) { + emitWarningIfNeeded(set); + debugImpls[set] = function debug(...args: unknown[]) { + const msg = args.map((arg) => inspect(arg)).join(" "); + console.error("%s %s: %s", set, String(Deno.pid), msg); + }; + } else { + debugImpls[set] = noop; + } + } + + return debugImpls[set]; +} + +// debuglogImpl depends on process.pid and process.env.NODE_DEBUG, +// so it needs to be called lazily in top scopes of internal modules +// that may be loaded before these run time states are allowed to +// be accessed. +export function debuglog( + set: string, + cb?: (debug: (...args: unknown[]) => void) => void, +) { + function init() { + set = set.toUpperCase(); + enabled = testEnabled(set); + } + + let debug = (...args: unknown[]): void => { + init(); + // Only invokes debuglogImpl() when the debug function is + // called for the first time. + debug = debuglogImpl(enabled, set); + + if (typeof cb === "function") { + cb(debug); + } + + return debug(...args); + }; + + let enabled: boolean; + let test = () => { + init(); + test = () => enabled; + return enabled; + }; + + const logger = (...args: unknown[]) => debug(...args); + + Object.defineProperty(logger, "enabled", { + get() { + return test(); + }, + configurable: true, + enumerable: true, + }); + + return logger; +} + +let debugEnv; +/* TODO(kt3k): enable initializing debugEnv. +It's not possible to access env var when snapshotting. + +try { + debugEnv = Deno.env.get("NODE_DEBUG") ?? ""; +} catch (error) { + if (error instanceof Deno.errors.PermissionDenied) { + debugEnv = ""; + } else { + throw error; + } +} +*/ +initializeDebugEnv(debugEnv); + +export default { debuglog }; diff --git a/ext/node/polyfills/internal/util/inspect.mjs b/ext/node/polyfills/internal/util/inspect.mjs new file mode 100644 index 00000000000000..53a878aa3945c2 --- /dev/null +++ b/ext/node/polyfills/internal/util/inspect.mjs @@ -0,0 +1,2237 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. +// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. + +import * as types from "internal:deno_node/polyfills/internal/util/types.ts"; +import { validateObject, validateString } from "internal:deno_node/polyfills/internal/validators.mjs"; +import { codes } from "internal:deno_node/polyfills/internal/error_codes.ts"; + +import { + ALL_PROPERTIES, + getOwnNonIndexProperties, + ONLY_ENUMERABLE, +} from "internal:deno_node/polyfills/internal_binding/util.ts"; + +const kObjectType = 0; +const kArrayType = 1; +const kArrayExtrasType = 2; + +const kMinLineLength = 16; + +// Constants to map the iterator state. +const kWeak = 0; +const kIterator = 1; +const kMapEntries = 2; + +const kPending = 0; +const kRejected = 2; + +// Escaped control characters (plus the single quote and the backslash). Use +// empty strings to fill up unused entries. +// deno-fmt-ignore +const meta = [ + '\\x00', '\\x01', '\\x02', '\\x03', '\\x04', '\\x05', '\\x06', '\\x07', // x07 + '\\b', '\\t', '\\n', '\\x0B', '\\f', '\\r', '\\x0E', '\\x0F', // x0F + '\\x10', '\\x11', '\\x12', '\\x13', '\\x14', '\\x15', '\\x16', '\\x17', // x17 + '\\x18', '\\x19', '\\x1A', '\\x1B', '\\x1C', '\\x1D', '\\x1E', '\\x1F', // x1F + '', '', '', '', '', '', '', "\\'", '', '', '', '', '', '', '', '', // x2F + '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', // x3F + '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', // x4F + '', '', '', '', '', '', '', '', '', '', '', '', '\\\\', '', '', '', // x5F + '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', // x6F + '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '\\x7F', // x7F + '\\x80', '\\x81', '\\x82', '\\x83', '\\x84', '\\x85', '\\x86', '\\x87', // x87 + '\\x88', '\\x89', '\\x8A', '\\x8B', '\\x8C', '\\x8D', '\\x8E', '\\x8F', // x8F + '\\x90', '\\x91', '\\x92', '\\x93', '\\x94', '\\x95', '\\x96', '\\x97', // x97 + '\\x98', '\\x99', '\\x9A', '\\x9B', '\\x9C', '\\x9D', '\\x9E', '\\x9F', // x9F +]; + +// https://tc39.es/ecma262/#sec-IsHTMLDDA-internal-slot +const isUndetectableObject = (v) => typeof v === "undefined" && v !== undefined; + +// deno-lint-ignore no-control-regex +const strEscapeSequencesRegExp = /[\x00-\x1f\x27\x5c\x7f-\x9f]/; +// deno-lint-ignore no-control-regex +const strEscapeSequencesReplacer = /[\x00-\x1f\x27\x5c\x7f-\x9f]/g; +// deno-lint-ignore no-control-regex +const strEscapeSequencesRegExpSingle = /[\x00-\x1f\x5c\x7f-\x9f]/; +// deno-lint-ignore no-control-regex +const strEscapeSequencesReplacerSingle = /[\x00-\x1f\x5c\x7f-\x9f]/g; + +const keyStrRegExp = /^[a-zA-Z_][a-zA-Z_0-9]*$/; +const numberRegExp = /^(0|[1-9][0-9]*)$/; +const nodeModulesRegExp = /[/\\]node_modules[/\\](.+?)(?=[/\\])/g; + +const classRegExp = /^(\s+[^(]*?)\s*{/; +// eslint-disable-next-line node-core/no-unescaped-regexp-dot +const stripCommentsRegExp = /(\/\/.*?\n)|(\/\*(.|\n)*?\*\/)/g; + +const inspectDefaultOptions = { + showHidden: false, + depth: 2, + colors: false, + customInspect: true, + showProxy: false, + maxArrayLength: 100, + maxStringLength: 10000, + breakLength: 80, + compact: 3, + sorted: false, + getters: false, +}; + +function getUserOptions(ctx, isCrossContext) { + const ret = { + stylize: ctx.stylize, + showHidden: ctx.showHidden, + depth: ctx.depth, + colors: ctx.colors, + customInspect: ctx.customInspect, + showProxy: ctx.showProxy, + maxArrayLength: ctx.maxArrayLength, + maxStringLength: ctx.maxStringLength, + breakLength: ctx.breakLength, + compact: ctx.compact, + sorted: ctx.sorted, + getters: ctx.getters, + ...ctx.userOptions, + }; + + // Typically, the target value will be an instance of `Object`. If that is + // *not* the case, the object may come from another vm.Context, and we want + // to avoid passing it objects from this Context in that case, so we remove + // the prototype from the returned object itself + the `stylize()` function, + // and remove all other non-primitives, including non-primitive user options. + if (isCrossContext) { + Object.setPrototypeOf(ret, null); + for (const key of Object.keys(ret)) { + if ( + (typeof ret[key] === "object" || typeof ret[key] === "function") && + ret[key] !== null + ) { + delete ret[key]; + } + } + ret.stylize = Object.setPrototypeOf((value, flavour) => { + let stylized; + try { + stylized = `${ctx.stylize(value, flavour)}`; + } catch { + // noop + } + + if (typeof stylized !== "string") return value; + // `stylized` is a string as it should be, which is safe to pass along. + return stylized; + }, null); + } + + return ret; +} + +/** + * Echos the value of any input. Tries to print the value out + * in the best way possible given the different types. + */ +/* Legacy: value, showHidden, depth, colors */ +export function inspect(value, opts) { + // Default options + const ctx = { + budget: {}, + indentationLvl: 0, + seen: [], + currentDepth: 0, + stylize: stylizeNoColor, + showHidden: inspectDefaultOptions.showHidden, + depth: inspectDefaultOptions.depth, + colors: inspectDefaultOptions.colors, + customInspect: inspectDefaultOptions.customInspect, + showProxy: inspectDefaultOptions.showProxy, + maxArrayLength: inspectDefaultOptions.maxArrayLength, + maxStringLength: inspectDefaultOptions.maxStringLength, + breakLength: inspectDefaultOptions.breakLength, + compact: inspectDefaultOptions.compact, + sorted: inspectDefaultOptions.sorted, + getters: inspectDefaultOptions.getters, + }; + if (arguments.length > 1) { + // Legacy... + if (arguments.length > 2) { + if (arguments[2] !== undefined) { + ctx.depth = arguments[2]; + } + if (arguments.length > 3 && arguments[3] !== undefined) { + ctx.colors = arguments[3]; + } + } + // Set user-specified options + if (typeof opts === "boolean") { + ctx.showHidden = opts; + } else if (opts) { + const optKeys = Object.keys(opts); + for (let i = 0; i < optKeys.length; ++i) { + const key = optKeys[i]; + // TODO(BridgeAR): Find a solution what to do about stylize. Either make + // this function public or add a new API with a similar or better + // functionality. + if ( + // deno-lint-ignore no-prototype-builtins + inspectDefaultOptions.hasOwnProperty(key) || + key === "stylize" + ) { + ctx[key] = opts[key]; + } else if (ctx.userOptions === undefined) { + // This is required to pass through the actual user input. + ctx.userOptions = opts; + } + } + } + } + if (ctx.colors) ctx.stylize = stylizeWithColor; + if (ctx.maxArrayLength === null) ctx.maxArrayLength = Infinity; + if (ctx.maxStringLength === null) ctx.maxStringLength = Infinity; + return formatValue(ctx, value, 0); +} +const customInspectSymbol = Symbol.for("nodejs.util.inspect.custom"); +inspect.custom = customInspectSymbol; + +Object.defineProperty(inspect, "defaultOptions", { + get() { + return inspectDefaultOptions; + }, + set(options) { + validateObject(options, "options"); + return Object.assign(inspectDefaultOptions, options); + }, +}); + +// Set Graphics Rendition https://en.wikipedia.org/wiki/ANSI_escape_code#graphics +// Each color consists of an array with the color code as first entry and the +// reset code as second entry. +const defaultFG = 39; +const defaultBG = 49; +inspect.colors = Object.assign(Object.create(null), { + reset: [0, 0], + bold: [1, 22], + dim: [2, 22], // Alias: faint + italic: [3, 23], + underline: [4, 24], + blink: [5, 25], + // Swap foreground and background colors + inverse: [7, 27], // Alias: swapcolors, swapColors + hidden: [8, 28], // Alias: conceal + strikethrough: [9, 29], // Alias: strikeThrough, crossedout, crossedOut + doubleunderline: [21, 24], // Alias: doubleUnderline + black: [30, defaultFG], + red: [31, defaultFG], + green: [32, defaultFG], + yellow: [33, defaultFG], + blue: [34, defaultFG], + magenta: [35, defaultFG], + cyan: [36, defaultFG], + white: [37, defaultFG], + bgBlack: [40, defaultBG], + bgRed: [41, defaultBG], + bgGreen: [42, defaultBG], + bgYellow: [43, defaultBG], + bgBlue: [44, defaultBG], + bgMagenta: [45, defaultBG], + bgCyan: [46, defaultBG], + bgWhite: [47, defaultBG], + framed: [51, 54], + overlined: [53, 55], + gray: [90, defaultFG], // Alias: grey, blackBright + redBright: [91, defaultFG], + greenBright: [92, defaultFG], + yellowBright: [93, defaultFG], + blueBright: [94, defaultFG], + magentaBright: [95, defaultFG], + cyanBright: [96, defaultFG], + whiteBright: [97, defaultFG], + bgGray: [100, defaultBG], // Alias: bgGrey, bgBlackBright + bgRedBright: [101, defaultBG], + bgGreenBright: [102, defaultBG], + bgYellowBright: [103, defaultBG], + bgBlueBright: [104, defaultBG], + bgMagentaBright: [105, defaultBG], + bgCyanBright: [106, defaultBG], + bgWhiteBright: [107, defaultBG], +}); + +function defineColorAlias(target, alias) { + Object.defineProperty(inspect.colors, alias, { + get() { + return this[target]; + }, + set(value) { + this[target] = value; + }, + configurable: true, + enumerable: false, + }); +} + +defineColorAlias("gray", "grey"); +defineColorAlias("gray", "blackBright"); +defineColorAlias("bgGray", "bgGrey"); +defineColorAlias("bgGray", "bgBlackBright"); +defineColorAlias("dim", "faint"); +defineColorAlias("strikethrough", "crossedout"); +defineColorAlias("strikethrough", "strikeThrough"); +defineColorAlias("strikethrough", "crossedOut"); +defineColorAlias("hidden", "conceal"); +defineColorAlias("inverse", "swapColors"); +defineColorAlias("inverse", "swapcolors"); +defineColorAlias("doubleunderline", "doubleUnderline"); + +// TODO(BridgeAR): Add function style support for more complex styles. +// Don't use 'blue' not visible on cmd.exe +inspect.styles = Object.assign(Object.create(null), { + special: "cyan", + number: "yellow", + bigint: "yellow", + boolean: "yellow", + undefined: "grey", + null: "bold", + string: "green", + symbol: "green", + date: "magenta", + // "name": intentionally not styling + // TODO(BridgeAR): Highlight regular expressions properly. + regexp: "red", + module: "underline", +}); + +function addQuotes(str, quotes) { + if (quotes === -1) { + return `"${str}"`; + } + if (quotes === -2) { + return `\`${str}\``; + } + return `'${str}'`; +} + +// TODO(wafuwafu13): Figure out +const escapeFn = (str) => meta[str.charCodeAt(0)]; + +// Escape control characters, single quotes and the backslash. +// This is similar to JSON stringify escaping. +function strEscape(str) { + let escapeTest = strEscapeSequencesRegExp; + let escapeReplace = strEscapeSequencesReplacer; + let singleQuote = 39; + + // Check for double quotes. If not present, do not escape single quotes and + // instead wrap the text in double quotes. If double quotes exist, check for + // backticks. If they do not exist, use those as fallback instead of the + // double quotes. + if (str.includes("'")) { + // This invalidates the charCode and therefore can not be matched for + // anymore. + if (!str.includes('"')) { + singleQuote = -1; + } else if ( + !str.includes("`") && + !str.includes("${") + ) { + singleQuote = -2; + } + if (singleQuote !== 39) { + escapeTest = strEscapeSequencesRegExpSingle; + escapeReplace = strEscapeSequencesReplacerSingle; + } + } + + // Some magic numbers that worked out fine while benchmarking with v8 6.0 + if (str.length < 5000 && !escapeTest.test(str)) { + return addQuotes(str, singleQuote); + } + if (str.length > 100) { + str = str.replace(escapeReplace, escapeFn); + return addQuotes(str, singleQuote); + } + + let result = ""; + let last = 0; + const lastIndex = str.length; + for (let i = 0; i < lastIndex; i++) { + const point = str.charCodeAt(i); + if ( + point === singleQuote || + point === 92 || + point < 32 || + (point > 126 && point < 160) + ) { + if (last === i) { + result += meta[point]; + } else { + result += `${str.slice(last, i)}${meta[point]}`; + } + last = i + 1; + } + } + + if (last !== lastIndex) { + result += str.slice(last); + } + return addQuotes(result, singleQuote); +} + +function stylizeWithColor(str, styleType) { + const style = inspect.styles[styleType]; + if (style !== undefined) { + const color = inspect.colors[style]; + if (color !== undefined) { + return `\u001b[${color[0]}m${str}\u001b[${color[1]}m`; + } + } + return str; +} + +function stylizeNoColor(str) { + return str; +} + +// Note: using `formatValue` directly requires the indentation level to be +// corrected by setting `ctx.indentationLvL += diff` and then to decrease the +// value afterwards again. +function formatValue( + ctx, + value, + recurseTimes, + typedArray, +) { + // Primitive types cannot have properties. + if ( + typeof value !== "object" && + typeof value !== "function" && + !isUndetectableObject(value) + ) { + return formatPrimitive(ctx.stylize, value, ctx); + } + if (value === null) { + return ctx.stylize("null", "null"); + } + + // Memorize the context for custom inspection on proxies. + const context = value; + // Always check for proxies to prevent side effects and to prevent triggering + // any proxy handlers. + // TODO(wafuwafu13): Set Proxy + const proxy = undefined; + // const proxy = getProxyDetails(value, !!ctx.showProxy); + // if (proxy !== undefined) { + // if (ctx.showProxy) { + // return formatProxy(ctx, proxy, recurseTimes); + // } + // value = proxy; + // } + + // Provide a hook for user-specified inspect functions. + // Check that value is an object with an inspect function on it. + if (ctx.customInspect) { + const maybeCustom = value[customInspectSymbol]; + if ( + typeof maybeCustom === "function" && + // Filter out the util module, its inspect function is special. + maybeCustom !== inspect && + // Also filter out any prototype objects using the circular check. + !(value.constructor && value.constructor.prototype === value) + ) { + // This makes sure the recurseTimes are reported as before while using + // a counter internally. + const depth = ctx.depth === null ? null : ctx.depth - recurseTimes; + const isCrossContext = proxy !== undefined || + !(context instanceof Object); + const ret = maybeCustom.call( + context, + depth, + getUserOptions(ctx, isCrossContext), + ); + // If the custom inspection method returned `this`, don't go into + // infinite recursion. + if (ret !== context) { + if (typeof ret !== "string") { + return formatValue(ctx, ret, recurseTimes); + } + return ret.replace(/\n/g, `\n${" ".repeat(ctx.indentationLvl)}`); + } + } + } + + // Using an array here is actually better for the average case than using + // a Set. `seen` will only check for the depth and will never grow too large. + if (ctx.seen.includes(value)) { + let index = 1; + if (ctx.circular === undefined) { + ctx.circular = new Map(); + ctx.circular.set(value, index); + } else { + index = ctx.circular.get(value); + if (index === undefined) { + index = ctx.circular.size + 1; + ctx.circular.set(value, index); + } + } + return ctx.stylize(`[Circular *${index}]`, "special"); + } + + return formatRaw(ctx, value, recurseTimes, typedArray); +} + +function formatRaw(ctx, value, recurseTimes, typedArray) { + let keys; + let protoProps; + if (ctx.showHidden && (recurseTimes <= ctx.depth || ctx.depth === null)) { + protoProps = []; + } + + const constructor = getConstructorName(value, ctx, recurseTimes, protoProps); + // Reset the variable to check for this later on. + if (protoProps !== undefined && protoProps.length === 0) { + protoProps = undefined; + } + + let tag = value[Symbol.toStringTag]; + // Only list the tag in case it's non-enumerable / not an own property. + // Otherwise we'd print this twice. + if ( + typeof tag !== "string" + // TODO(wafuwafu13): Implement + // (tag !== "" && + // (ctx.showHidden + // ? Object.prototype.hasOwnProperty + // : Object.prototype.propertyIsEnumerable)( + // value, + // Symbol.toStringTag, + // )) + ) { + tag = ""; + } + let base = ""; + let formatter = getEmptyFormatArray; + let braces; + let noIterator = true; + let i = 0; + const filter = ctx.showHidden ? ALL_PROPERTIES : ONLY_ENUMERABLE; + + let extrasType = kObjectType; + + // Iterators and the rest are split to reduce checks. + // We have to check all values in case the constructor is set to null. + // Otherwise it would not possible to identify all types properly. + if (value[Symbol.iterator] || constructor === null) { + noIterator = false; + if (Array.isArray(value)) { + // Only set the constructor for non ordinary ("Array [...]") arrays. + const prefix = (constructor !== "Array" || tag !== "") + ? getPrefix(constructor, tag, "Array", `(${value.length})`) + : ""; + keys = getOwnNonIndexProperties(value, filter); + braces = [`${prefix}[`, "]"]; + if (value.length === 0 && keys.length === 0 && protoProps === undefined) { + return `${braces[0]}]`; + } + extrasType = kArrayExtrasType; + formatter = formatArray; + } else if (types.isSet(value)) { + const size = value.size; + const prefix = getPrefix(constructor, tag, "Set", `(${size})`); + keys = getKeys(value, ctx.showHidden); + formatter = constructor !== null + ? formatSet.bind(null, value) + : formatSet.bind(null, value.values()); + if (size === 0 && keys.length === 0 && protoProps === undefined) { + return `${prefix}{}`; + } + braces = [`${prefix}{`, "}"]; + } else if (types.isMap(value)) { + const size = value.size; + const prefix = getPrefix(constructor, tag, "Map", `(${size})`); + keys = getKeys(value, ctx.showHidden); + formatter = constructor !== null + ? formatMap.bind(null, value) + : formatMap.bind(null, value.entries()); + if (size === 0 && keys.length === 0 && protoProps === undefined) { + return `${prefix}{}`; + } + braces = [`${prefix}{`, "}"]; + } else if (types.isTypedArray(value)) { + keys = getOwnNonIndexProperties(value, filter); + const bound = value; + const fallback = ""; + if (constructor === null) { + // TODO(wafuwafu13): Implement + // fallback = TypedArrayPrototypeGetSymbolToStringTag(value); + // // Reconstruct the array information. + // bound = new primordials[fallback](value); + } + const size = value.length; + const prefix = getPrefix(constructor, tag, fallback, `(${size})`); + braces = [`${prefix}[`, "]"]; + if (value.length === 0 && keys.length === 0 && !ctx.showHidden) { + return `${braces[0]}]`; + } + // Special handle the value. The original value is required below. The + // bound function is required to reconstruct missing information. + (formatter) = formatTypedArray.bind(null, bound, size); + extrasType = kArrayExtrasType; + } else if (types.isMapIterator(value)) { + keys = getKeys(value, ctx.showHidden); + braces = getIteratorBraces("Map", tag); + // Add braces to the formatter parameters. + (formatter) = formatIterator.bind(null, braces); + } else if (types.isSetIterator(value)) { + keys = getKeys(value, ctx.showHidden); + braces = getIteratorBraces("Set", tag); + // Add braces to the formatter parameters. + (formatter) = formatIterator.bind(null, braces); + } else { + noIterator = true; + } + } + if (noIterator) { + keys = getKeys(value, ctx.showHidden); + braces = ["{", "}"]; + if (constructor === "Object") { + if (types.isArgumentsObject(value)) { + braces[0] = "[Arguments] {"; + } else if (tag !== "") { + braces[0] = `${getPrefix(constructor, tag, "Object")}{`; + } + if (keys.length === 0 && protoProps === undefined) { + return `${braces[0]}}`; + } + } else if (typeof value === "function") { + base = getFunctionBase(value, constructor, tag); + if (keys.length === 0 && protoProps === undefined) { + return ctx.stylize(base, "special"); + } + } else if (types.isRegExp(value)) { + // Make RegExps say that they are RegExps + base = RegExp(constructor !== null ? value : new RegExp(value)) + .toString(); + const prefix = getPrefix(constructor, tag, "RegExp"); + if (prefix !== "RegExp ") { + base = `${prefix}${base}`; + } + if ( + (keys.length === 0 && protoProps === undefined) || + (recurseTimes > ctx.depth && ctx.depth !== null) + ) { + return ctx.stylize(base, "regexp"); + } + } else if (types.isDate(value)) { + // Make dates with properties first say the date + base = Number.isNaN(value.getTime()) + ? value.toString() + : value.toISOString(); + const prefix = getPrefix(constructor, tag, "Date"); + if (prefix !== "Date ") { + base = `${prefix}${base}`; + } + if (keys.length === 0 && protoProps === undefined) { + return ctx.stylize(base, "date"); + } + } else if (value instanceof Error) { + base = formatError(value, constructor, tag, ctx, keys); + if (keys.length === 0 && protoProps === undefined) { + return base; + } + } else if (types.isAnyArrayBuffer(value)) { + // Fast path for ArrayBuffer and SharedArrayBuffer. + // Can't do the same for DataView because it has a non-primitive + // .buffer property that we need to recurse for. + const arrayType = types.isArrayBuffer(value) + ? "ArrayBuffer" + : "SharedArrayBuffer"; + const prefix = getPrefix(constructor, tag, arrayType); + if (typedArray === undefined) { + (formatter) = formatArrayBuffer; + } else if (keys.length === 0 && protoProps === undefined) { + return prefix + + `{ byteLength: ${formatNumber(ctx.stylize, value.byteLength)} }`; + } + braces[0] = `${prefix}{`; + Array.prototype.unshift.call(keys, "byteLength"); + } else if (types.isDataView(value)) { + braces[0] = `${getPrefix(constructor, tag, "DataView")}{`; + // .buffer goes last, it's not a primitive like the others. + Array.prototype.unshift.call(keys, "byteLength", "byteOffset", "buffer"); + } else if (types.isPromise(value)) { + braces[0] = `${getPrefix(constructor, tag, "Promise")}{`; + (formatter) = formatPromise; + } else if (types.isWeakSet(value)) { + braces[0] = `${getPrefix(constructor, tag, "WeakSet")}{`; + (formatter) = ctx.showHidden ? formatWeakSet : formatWeakCollection; + } else if (types.isWeakMap(value)) { + braces[0] = `${getPrefix(constructor, tag, "WeakMap")}{`; + (formatter) = ctx.showHidden ? formatWeakMap : formatWeakCollection; + } else if (types.isModuleNamespaceObject(value)) { + braces[0] = `${getPrefix(constructor, tag, "Module")}{`; + // Special handle keys for namespace objects. + (formatter) = formatNamespaceObject.bind(null, keys); + } else if (types.isBoxedPrimitive(value)) { + base = getBoxedBase(value, ctx, keys, constructor, tag); + if (keys.length === 0 && protoProps === undefined) { + return base; + } + } else { + if (keys.length === 0 && protoProps === undefined) { + // TODO(wafuwafu13): Implement + // if (types.isExternal(value)) { + // const address = getExternalValue(value).toString(16); + // return ctx.stylize(`[External: ${address}]`, 'special'); + // } + return `${getCtxStyle(value, constructor, tag)}{}`; + } + braces[0] = `${getCtxStyle(value, constructor, tag)}{`; + } + } + + if (recurseTimes > ctx.depth && ctx.depth !== null) { + let constructorName = getCtxStyle(value, constructor, tag).slice(0, -1); + if (constructor !== null) { + constructorName = `[${constructorName}]`; + } + return ctx.stylize(constructorName, "special"); + } + recurseTimes += 1; + + ctx.seen.push(value); + ctx.currentDepth = recurseTimes; + let output; + const indentationLvl = ctx.indentationLvl; + try { + output = formatter(ctx, value, recurseTimes); + for (i = 0; i < keys.length; i++) { + output.push( + formatProperty(ctx, value, recurseTimes, keys[i], extrasType), + ); + } + if (protoProps !== undefined) { + output.push(...protoProps); + } + } catch (err) { + const constructorName = getCtxStyle(value, constructor, tag).slice(0, -1); + return handleMaxCallStackSize(ctx, err, constructorName, indentationLvl); + } + if (ctx.circular !== undefined) { + const index = ctx.circular.get(value); + if (index !== undefined) { + const reference = ctx.stylize(``, "special"); + // Add reference always to the very beginning of the output. + if (ctx.compact !== true) { + base = base === "" ? reference : `${reference} ${base}`; + } else { + braces[0] = `${reference} ${braces[0]}`; + } + } + } + ctx.seen.pop(); + + if (ctx.sorted) { + const comparator = ctx.sorted === true ? undefined : ctx.sorted; + if (extrasType === kObjectType) { + output = output.sort(comparator); + } else if (keys.length > 1) { + const sorted = output.slice(output.length - keys.length).sort(comparator); + output.splice(output.length - keys.length, keys.length, ...sorted); + } + } + + const res = reduceToSingleString( + ctx, + output, + base, + braces, + extrasType, + recurseTimes, + value, + ); + const budget = ctx.budget[ctx.indentationLvl] || 0; + const newLength = budget + res.length; + ctx.budget[ctx.indentationLvl] = newLength; + // If any indentationLvl exceeds this limit, limit further inspecting to the + // minimum. Otherwise the recursive algorithm might continue inspecting the + // object even though the maximum string size (~2 ** 28 on 32 bit systems and + // ~2 ** 30 on 64 bit systems) exceeded. The actual output is not limited at + // exactly 2 ** 27 but a bit higher. This depends on the object shape. + // This limit also makes sure that huge objects don't block the event loop + // significantly. + if (newLength > 2 ** 27) { + ctx.depth = -1; + } + return res; +} + +const builtInObjects = new Set( + Object.getOwnPropertyNames(globalThis).filter((e) => + /^[A-Z][a-zA-Z0-9]+$/.test(e) + ), +); + +function addPrototypeProperties( + ctx, + main, + obj, + recurseTimes, + output, +) { + let depth = 0; + let keys; + let keySet; + do { + if (depth !== 0 || main === obj) { + obj = Object.getPrototypeOf(obj); + // Stop as soon as a null prototype is encountered. + if (obj === null) { + return; + } + // Stop as soon as a built-in object type is detected. + const descriptor = Object.getOwnPropertyDescriptor(obj, "constructor"); + if ( + descriptor !== undefined && + typeof descriptor.value === "function" && + builtInObjects.has(descriptor.value.name) + ) { + return; + } + } + + if (depth === 0) { + keySet = new Set(); + } else { + Array.prototype.forEach.call(keys, (key) => keySet.add(key)); + } + // Get all own property names and symbols. + keys = Reflect.ownKeys(obj); + Array.prototype.push.call(ctx.seen, main); + for (const key of keys) { + // Ignore the `constructor` property and keys that exist on layers above. + if ( + key === "constructor" || + // deno-lint-ignore no-prototype-builtins + main.hasOwnProperty(key) || + (depth !== 0 && keySet.has(key)) + ) { + continue; + } + const desc = Object.getOwnPropertyDescriptor(obj, key); + if (typeof desc.value === "function") { + continue; + } + const value = formatProperty( + ctx, + obj, + recurseTimes, + key, + kObjectType, + desc, + main, + ); + if (ctx.colors) { + // Faint! + Array.prototype.push.call(output, `\u001b[2m${value}\u001b[22m`); + } else { + Array.prototype.push.call(output, value); + } + } + Array.prototype.pop.call(ctx.seen); + // Limit the inspection to up to three prototype layers. Using `recurseTimes` + // is not a good choice here, because it's as if the properties are declared + // on the current object from the users perspective. + } while (++depth !== 3); +} + +function getConstructorName( + obj, + ctx, + recurseTimes, + protoProps, +) { + let firstProto; + const tmp = obj; + while (obj || isUndetectableObject(obj)) { + const descriptor = Object.getOwnPropertyDescriptor(obj, "constructor"); + if ( + descriptor !== undefined && + typeof descriptor.value === "function" && + descriptor.value.name !== "" && + isInstanceof(tmp, descriptor.value) + ) { + if ( + protoProps !== undefined && + (firstProto !== obj || + !builtInObjects.has(descriptor.value.name)) + ) { + addPrototypeProperties( + ctx, + tmp, + firstProto || tmp, + recurseTimes, + protoProps, + ); + } + return descriptor.value.name; + } + + obj = Object.getPrototypeOf(obj); + if (firstProto === undefined) { + firstProto = obj; + } + } + + if (firstProto === null) { + return null; + } + + // TODO(wafuwafu13): Implement + // const res = internalGetConstructorName(tmp); + const res = undefined; + + if (recurseTimes > ctx.depth && ctx.depth !== null) { + return `${res} `; + } + + const protoConstr = getConstructorName( + firstProto, + ctx, + recurseTimes + 1, + protoProps, + ); + + if (protoConstr === null) { + return `${res} <${ + inspect(firstProto, { + ...ctx, + customInspect: false, + depth: -1, + }) + }>`; + } + + return `${res} <${protoConstr}>`; +} + +function formatPrimitive(fn, value, ctx) { + if (typeof value === "string") { + let trailer = ""; + if (value.length > ctx.maxStringLength) { + const remaining = value.length - ctx.maxStringLength; + value = value.slice(0, ctx.maxStringLength); + trailer = `... ${remaining} more character${remaining > 1 ? "s" : ""}`; + } + if ( + ctx.compact !== true && + // TODO(BridgeAR): Add unicode support. Use the readline getStringWidth + // function. + value.length > kMinLineLength && + value.length > ctx.breakLength - ctx.indentationLvl - 4 + ) { + return value + .split(/(?<=\n)/) + .map((line) => fn(strEscape(line), "string")) + .join(` +\n${" ".repeat(ctx.indentationLvl + 2)}`) + trailer; + } + return fn(strEscape(value), "string") + trailer; + } + if (typeof value === "number") { + return formatNumber(fn, value); + } + if (typeof value === "bigint") { + return formatBigInt(fn, value); + } + if (typeof value === "boolean") { + return fn(`${value}`, "boolean"); + } + if (typeof value === "undefined") { + return fn("undefined", "undefined"); + } + // es6 symbol primitive + return fn(value.toString(), "symbol"); +} + +// Return a new empty array to push in the results of the default formatter. +function getEmptyFormatArray() { + return []; +} + +function isInstanceof(object, proto) { + try { + return object instanceof proto; + } catch { + return false; + } +} + +function getPrefix(constructor, tag, fallback, size = "") { + if (constructor === null) { + if (tag !== "" && fallback !== tag) { + return `[${fallback}${size}: null prototype] [${tag}] `; + } + return `[${fallback}${size}: null prototype] `; + } + + if (tag !== "" && constructor !== tag) { + return `${constructor}${size} [${tag}] `; + } + return `${constructor}${size} `; +} + +function formatArray(ctx, value, recurseTimes) { + const valLen = value.length; + const len = Math.min(Math.max(0, ctx.maxArrayLength), valLen); + + const remaining = valLen - len; + const output = []; + for (let i = 0; i < len; i++) { + // Special handle sparse arrays. + // deno-lint-ignore no-prototype-builtins + if (!value.hasOwnProperty(i)) { + return formatSpecialArray(ctx, value, recurseTimes, len, output, i); + } + output.push(formatProperty(ctx, value, recurseTimes, i, kArrayType)); + } + if (remaining > 0) { + output.push(`... ${remaining} more item${remaining > 1 ? "s" : ""}`); + } + return output; +} + +function getCtxStyle(_value, constructor, tag) { + let fallback = ""; + if (constructor === null) { + // TODO(wafuwafu13): Implement + // fallback = internalGetConstructorName(value); + if (fallback === tag) { + fallback = "Object"; + } + } + return getPrefix(constructor, tag, fallback); +} + +// Look up the keys of the object. +function getKeys(value, showHidden) { + let keys; + const symbols = Object.getOwnPropertySymbols(value); + if (showHidden) { + keys = Object.getOwnPropertyNames(value); + if (symbols.length !== 0) { + Array.prototype.push.apply(keys, symbols); + } + } else { + // This might throw if `value` is a Module Namespace Object from an + // unevaluated module, but we don't want to perform the actual type + // check because it's expensive. + // TODO(devsnek): track https://github.com/tc39/ecma262/issues/1209 + // and modify this logic as needed. + try { + keys = Object.keys(value); + } catch (_err) { + // TODO(wafuwafu13): Implement + // assert(isNativeError(err) && err.name === 'ReferenceError' && + // isModuleNamespaceObject(value)); + keys = Object.getOwnPropertyNames(value); + } + if (symbols.length !== 0) { + // TODO(wafuwafu13): Implement + // const filter = (key: any) => + // + // Object.prototype.propertyIsEnumerable(value, key); + // Array.prototype.push.apply( + // keys, + // symbols.filter(filter), + // ); + } + } + return keys; +} + +function formatSet(value, ctx, _ignored, recurseTimes) { + const output = []; + ctx.indentationLvl += 2; + for (const v of value) { + Array.prototype.push.call(output, formatValue(ctx, v, recurseTimes)); + } + ctx.indentationLvl -= 2; + return output; +} + +function formatMap(value, ctx, _gnored, recurseTimes) { + const output = []; + ctx.indentationLvl += 2; + for (const { 0: k, 1: v } of value) { + output.push( + `${formatValue(ctx, k, recurseTimes)} => ${ + formatValue(ctx, v, recurseTimes) + }`, + ); + } + ctx.indentationLvl -= 2; + return output; +} + +function formatTypedArray( + value, + length, + ctx, + _ignored, + recurseTimes, +) { + const maxLength = Math.min(Math.max(0, ctx.maxArrayLength), length); + const remaining = value.length - maxLength; + const output = new Array(maxLength); + const elementFormatter = value.length > 0 && typeof value[0] === "number" + ? formatNumber + : formatBigInt; + for (let i = 0; i < maxLength; ++i) { + output[i] = elementFormatter(ctx.stylize, value[i]); + } + if (remaining > 0) { + output[maxLength] = `... ${remaining} more item${remaining > 1 ? "s" : ""}`; + } + if (ctx.showHidden) { + // .buffer goes last, it's not a primitive like the others. + // All besides `BYTES_PER_ELEMENT` are actually getters. + ctx.indentationLvl += 2; + for ( + const key of [ + "BYTES_PER_ELEMENT", + "length", + "byteLength", + "byteOffset", + "buffer", + ] + ) { + const str = formatValue(ctx, value[key], recurseTimes, true); + Array.prototype.push.call(output, `[${key}]: ${str}`); + } + ctx.indentationLvl -= 2; + } + return output; +} + +function getIteratorBraces(type, tag) { + if (tag !== `${type} Iterator`) { + if (tag !== "") { + tag += "] ["; + } + tag += `${type} Iterator`; + } + return [`[${tag}] {`, "}"]; +} + +function formatIterator(braces, ctx, value, recurseTimes) { + // TODO(wafuwafu13): Implement + // const { 0: entries, 1: isKeyValue } = previewEntries(value, true); + const { 0: entries, 1: isKeyValue } = value; + if (isKeyValue) { + // Mark entry iterators as such. + braces[0] = braces[0].replace(/ Iterator] {$/, " Entries] {"); + return formatMapIterInner(ctx, recurseTimes, entries, kMapEntries); + } + + return formatSetIterInner(ctx, recurseTimes, entries, kIterator); +} + +function getFunctionBase(value, constructor, tag) { + const stringified = Function.prototype.toString.call(value); + if (stringified.slice(0, 5) === "class" && stringified.endsWith("}")) { + const slice = stringified.slice(5, -1); + const bracketIndex = slice.indexOf("{"); + if ( + bracketIndex !== -1 && + (!slice.slice(0, bracketIndex).includes("(") || + // Slow path to guarantee that it's indeed a class. + classRegExp.test(slice.replace(stripCommentsRegExp))) + ) { + return getClassBase(value, constructor, tag); + } + } + let type = "Function"; + if (types.isGeneratorFunction(value)) { + type = `Generator${type}`; + } + if (types.isAsyncFunction(value)) { + type = `Async${type}`; + } + let base = `[${type}`; + if (constructor === null) { + base += " (null prototype)"; + } + if (value.name === "") { + base += " (anonymous)"; + } else { + base += `: ${value.name}`; + } + base += "]"; + if (constructor !== type && constructor !== null) { + base += ` ${constructor}`; + } + if (tag !== "" && constructor !== tag) { + base += ` [${tag}]`; + } + return base; +} + +function formatError( + err, + constructor, + tag, + ctx, + keys, +) { + const name = err.name != null ? String(err.name) : "Error"; + let len = name.length; + let stack = err.stack ? String(err.stack) : err.toString(); + + // Do not "duplicate" error properties that are already included in the output + // otherwise. + if (!ctx.showHidden && keys.length !== 0) { + for (const name of ["name", "message", "stack"]) { + const index = keys.indexOf(name); + // Only hide the property in case it's part of the original stack + if (index !== -1 && stack.includes(err[name])) { + keys.splice(index, 1); + } + } + } + + // A stack trace may contain arbitrary data. Only manipulate the output + // for "regular errors" (errors that "look normal") for now. + if ( + constructor === null || + (name.endsWith("Error") && + stack.startsWith(name) && + (stack.length === len || stack[len] === ":" || stack[len] === "\n")) + ) { + let fallback = "Error"; + if (constructor === null) { + const start = stack.match(/^([A-Z][a-z_ A-Z0-9[\]()-]+)(?::|\n {4}at)/) || + stack.match(/^([a-z_A-Z0-9-]*Error)$/); + fallback = (start && start[1]) || ""; + len = fallback.length; + fallback = fallback || "Error"; + } + const prefix = getPrefix(constructor, tag, fallback).slice(0, -1); + if (name !== prefix) { + if (prefix.includes(name)) { + if (len === 0) { + stack = `${prefix}: ${stack}`; + } else { + stack = `${prefix}${stack.slice(len)}`; + } + } else { + stack = `${prefix} [${name}]${stack.slice(len)}`; + } + } + } + // Ignore the error message if it's contained in the stack. + let pos = (err.message && stack.indexOf(err.message)) || -1; + if (pos !== -1) { + pos += err.message.length; + } + // Wrap the error in brackets in case it has no stack trace. + const stackStart = stack.indexOf("\n at", pos); + if (stackStart === -1) { + stack = `[${stack}]`; + } else if (ctx.colors) { + // Highlight userland code and node modules. + let newStack = stack.slice(0, stackStart); + const lines = stack.slice(stackStart + 1).split("\n"); + for (const line of lines) { + // const core = line.match(coreModuleRegExp); + // TODO(wafuwafu13): Implement + // if (core !== null && NativeModule.exists(core[1])) { + // newStack += `\n${ctx.stylize(line, 'undefined')}`; + // } else { + // This adds underscores to all node_modules to quickly identify them. + let nodeModule; + newStack += "\n"; + let pos = 0; + // deno-lint-ignore no-cond-assign + while (nodeModule = nodeModulesRegExp.exec(line)) { + // '/node_modules/'.length === 14 + newStack += line.slice(pos, nodeModule.index + 14); + newStack += ctx.stylize(nodeModule[1], "module"); + pos = nodeModule.index + nodeModule[0].length; + } + newStack += pos === 0 ? line : line.slice(pos); + // } + } + stack = newStack; + } + // The message and the stack have to be indented as well! + if (ctx.indentationLvl !== 0) { + const indentation = " ".repeat(ctx.indentationLvl); + stack = stack.replace(/\n/g, `\n${indentation}`); + } + return stack; +} + +let hexSlice; + +function formatArrayBuffer(ctx, value) { + let buffer; + try { + buffer = new Uint8Array(value); + } catch { + return [ctx.stylize("(detached)", "special")]; + } + // TODO(wafuwafu13): Implement + // if (hexSlice === undefined) + // hexSlice = uncurryThis(require('buffer').Buffer.prototype.hexSlice); + let str = hexSlice(buffer, 0, Math.min(ctx.maxArrayLength, buffer.length)) + .replace(/(.{2})/g, "$1 ").trim(); + + const remaining = buffer.length - ctx.maxArrayLength; + if (remaining > 0) { + str += ` ... ${remaining} more byte${remaining > 1 ? "s" : ""}`; + } + return [`${ctx.stylize("[Uint8Contents]", "special")}: <${str}>`]; +} + +function formatNumber(fn, value) { + // Format -0 as '-0'. Checking `value === -0` won't distinguish 0 from -0. + return fn(Object.is(value, -0) ? "-0" : `${value}`, "number"); +} + +function formatPromise(ctx, value, recurseTimes) { + let output; + // TODO(wafuwafu13): Implement + // const { 0: state, 1: result } = getPromiseDetails(value); + const { 0: state, 1: result } = value; + if (state === kPending) { + output = [ctx.stylize("", "special")]; + } else { + ctx.indentationLvl += 2; + const str = formatValue(ctx, result, recurseTimes); + ctx.indentationLvl -= 2; + output = [ + state === kRejected + ? `${ctx.stylize("", "special")} ${str}` + : str, + ]; + } + return output; +} + +function formatWeakCollection(ctx) { + return [ctx.stylize("", "special")]; +} + +function formatWeakSet(ctx, value, recurseTimes) { + // TODO(wafuwafu13): Implement + // const entries = previewEntries(value); + const entries = value; + return formatSetIterInner(ctx, recurseTimes, entries, kWeak); +} + +function formatWeakMap(ctx, value, recurseTimes) { + // TODO(wafuwafu13): Implement + // const entries = previewEntries(value); + const entries = value; + return formatMapIterInner(ctx, recurseTimes, entries, kWeak); +} + +function formatProperty( + ctx, + value, + recurseTimes, + key, + type, + desc, + original = value, +) { + let name, str; + let extra = " "; + desc = desc || Object.getOwnPropertyDescriptor(value, key) || + { value: value[key], enumerable: true }; + if (desc.value !== undefined) { + const diff = (ctx.compact !== true || type !== kObjectType) ? 2 : 3; + ctx.indentationLvl += diff; + str = formatValue(ctx, desc.value, recurseTimes); + if (diff === 3 && ctx.breakLength < getStringWidth(str, ctx.colors)) { + extra = `\n${" ".repeat(ctx.indentationLvl)}`; + } + ctx.indentationLvl -= diff; + } else if (desc.get !== undefined) { + const label = desc.set !== undefined ? "Getter/Setter" : "Getter"; + const s = ctx.stylize; + const sp = "special"; + if ( + ctx.getters && (ctx.getters === true || + (ctx.getters === "get" && desc.set === undefined) || + (ctx.getters === "set" && desc.set !== undefined)) + ) { + try { + const tmp = desc.get.call(original); + ctx.indentationLvl += 2; + if (tmp === null) { + str = `${s(`[${label}:`, sp)} ${s("null", "null")}${s("]", sp)}`; + } else if (typeof tmp === "object") { + str = `${s(`[${label}]`, sp)} ${formatValue(ctx, tmp, recurseTimes)}`; + } else { + const primitive = formatPrimitive(s, tmp, ctx); + str = `${s(`[${label}:`, sp)} ${primitive}${s("]", sp)}`; + } + ctx.indentationLvl -= 2; + } catch (err) { + const message = ``; + str = `${s(`[${label}:`, sp)} ${message}${s("]", sp)}`; + } + } else { + str = ctx.stylize(`[${label}]`, sp); + } + } else if (desc.set !== undefined) { + str = ctx.stylize("[Setter]", "special"); + } else { + str = ctx.stylize("undefined", "undefined"); + } + if (type === kArrayType) { + return str; + } + if (typeof key === "symbol") { + const tmp = key.toString().replace(strEscapeSequencesReplacer, escapeFn); + + name = `[${ctx.stylize(tmp, "symbol")}]`; + } else if (key === "__proto__") { + name = "['__proto__']"; + } else if (desc.enumerable === false) { + const tmp = key.replace(strEscapeSequencesReplacer, escapeFn); + + name = `[${tmp}]`; + } else if (keyStrRegExp.test(key)) { + name = ctx.stylize(key, "name"); + } else { + name = ctx.stylize(strEscape(key), "string"); + } + return `${name}:${extra}${str}`; +} + +function handleMaxCallStackSize( + _ctx, + _err, + _constructorName, + _indentationLvl, +) { + // TODO(wafuwafu13): Implement + // if (types.isStackOverflowError(err)) { + // ctx.seen.pop(); + // ctx.indentationLvl = indentationLvl; + // return ctx.stylize( + // `[${constructorName}: Inspection interrupted ` + + // 'prematurely. Maximum call stack size exceeded.]', + // 'special' + // ); + // } + // /* c8 ignore next */ + // assert.fail(err.stack); +} + +// deno-lint-ignore no-control-regex +const colorRegExp = /\u001b\[\d\d?m/g; +function removeColors(str) { + return str.replace(colorRegExp, ""); +} + +function isBelowBreakLength(ctx, output, start, base) { + // Each entry is separated by at least a comma. Thus, we start with a total + // length of at least `output.length`. In addition, some cases have a + // whitespace in-between each other that is added to the total as well. + // TODO(BridgeAR): Add unicode support. Use the readline getStringWidth + // function. Check the performance overhead and make it an opt-in in case it's + // significant. + let totalLength = output.length + start; + if (totalLength + output.length > ctx.breakLength) { + return false; + } + for (let i = 0; i < output.length; i++) { + if (ctx.colors) { + totalLength += removeColors(output[i]).length; + } else { + totalLength += output[i].length; + } + if (totalLength > ctx.breakLength) { + return false; + } + } + // Do not line up properties on the same line if `base` contains line breaks. + return base === "" || !base.includes("\n"); +} + +function formatBigInt(fn, value) { + return fn(`${value}n`, "bigint"); +} + +function formatNamespaceObject( + keys, + ctx, + value, + recurseTimes, +) { + const output = new Array(keys.length); + for (let i = 0; i < keys.length; i++) { + try { + output[i] = formatProperty( + ctx, + value, + recurseTimes, + keys[i], + kObjectType, + ); + } catch (_err) { + // TODO(wafuwfu13): Implement + // assert(isNativeError(err) && err.name === 'ReferenceError'); + // Use the existing functionality. This makes sure the indentation and + // line breaks are always correct. Otherwise it is very difficult to keep + // this aligned, even though this is a hacky way of dealing with this. + const tmp = { [keys[i]]: "" }; + output[i] = formatProperty(ctx, tmp, recurseTimes, keys[i], kObjectType); + const pos = output[i].lastIndexOf(" "); + // We have to find the last whitespace and have to replace that value as + // it will be visualized as a regular string. + output[i] = output[i].slice(0, pos + 1) + + ctx.stylize("", "special"); + } + } + // Reset the keys to an empty array. This prevents duplicated inspection. + keys.length = 0; + return output; +} + +// The array is sparse and/or has extra keys +function formatSpecialArray( + ctx, + value, + recurseTimes, + maxLength, + output, + i, +) { + const keys = Object.keys(value); + let index = i; + for (; i < keys.length && output.length < maxLength; i++) { + const key = keys[i]; + const tmp = +key; + // Arrays can only have up to 2^32 - 1 entries + if (tmp > 2 ** 32 - 2) { + break; + } + if (`${index}` !== key) { + if (!numberRegExp.test(key)) { + break; + } + const emptyItems = tmp - index; + const ending = emptyItems > 1 ? "s" : ""; + const message = `<${emptyItems} empty item${ending}>`; + output.push(ctx.stylize(message, "undefined")); + index = tmp; + if (output.length === maxLength) { + break; + } + } + output.push(formatProperty(ctx, value, recurseTimes, key, kArrayType)); + index++; + } + const remaining = value.length - index; + if (output.length !== maxLength) { + if (remaining > 0) { + const ending = remaining > 1 ? "s" : ""; + const message = `<${remaining} empty item${ending}>`; + output.push(ctx.stylize(message, "undefined")); + } + } else if (remaining > 0) { + output.push(`... ${remaining} more item${remaining > 1 ? "s" : ""}`); + } + return output; +} + +function getBoxedBase( + value, + ctx, + keys, + constructor, + tag, +) { + let type; + if (types.isNumberObject(value)) { + type = "Number"; + } else if (types.isStringObject(value)) { + type = "String"; + // For boxed Strings, we have to remove the 0-n indexed entries, + // since they just noisy up the output and are redundant + // Make boxed primitive Strings look like such + keys.splice(0, value.length); + } else if (types.isBooleanObject(value)) { + type = "Boolean"; + } else if (types.isBigIntObject(value)) { + type = "BigInt"; + } else { + type = "Symbol"; + } + let base = `[${type}`; + if (type !== constructor) { + if (constructor === null) { + base += " (null prototype)"; + } else { + base += ` (${constructor})`; + } + } + + base += `: ${formatPrimitive(stylizeNoColor, value.valueOf(), ctx)}]`; + if (tag !== "" && tag !== constructor) { + base += ` [${tag}]`; + } + if (keys.length !== 0 || ctx.stylize === stylizeNoColor) { + return base; + } + return ctx.stylize(base, type.toLowerCase()); +} + +function getClassBase(value, constructor, tag) { + // deno-lint-ignore no-prototype-builtins + const hasName = value.hasOwnProperty("name"); + const name = (hasName && value.name) || "(anonymous)"; + let base = `class ${name}`; + if (constructor !== "Function" && constructor !== null) { + base += ` [${constructor}]`; + } + if (tag !== "" && constructor !== tag) { + base += ` [${tag}]`; + } + if (constructor !== null) { + const superName = Object.getPrototypeOf(value).name; + if (superName) { + base += ` extends ${superName}`; + } + } else { + base += " extends [null prototype]"; + } + return `[${base}]`; +} + +function reduceToSingleString( + ctx, + output, + base, + braces, + extrasType, + recurseTimes, + value, +) { + if (ctx.compact !== true) { + if (typeof ctx.compact === "number" && ctx.compact >= 1) { + // Memorize the original output length. In case the output is grouped, + // prevent lining up the entries on a single line. + const entries = output.length; + // Group array elements together if the array contains at least six + // separate entries. + if (extrasType === kArrayExtrasType && entries > 6) { + output = groupArrayElements(ctx, output, value); + } + // `ctx.currentDepth` is set to the most inner depth of the currently + // inspected object part while `recurseTimes` is the actual current depth + // that is inspected. + // + // Example: + // + // const a = { first: [ 1, 2, 3 ], second: { inner: [ 1, 2, 3 ] } } + // + // The deepest depth of `a` is 2 (a.second.inner) and `a.first` has a max + // depth of 1. + // + // Consolidate all entries of the local most inner depth up to + // `ctx.compact`, as long as the properties are smaller than + // `ctx.breakLength`. + if ( + ctx.currentDepth - recurseTimes < ctx.compact && + entries === output.length + ) { + // Line up all entries on a single line in case the entries do not + // exceed `breakLength`. Add 10 as constant to start next to all other + // factors that may reduce `breakLength`. + const start = output.length + ctx.indentationLvl + + braces[0].length + base.length + 10; + if (isBelowBreakLength(ctx, output, start, base)) { + return `${base ? `${base} ` : ""}${braces[0]} ${join(output, ", ")}` + + ` ${braces[1]}`; + } + } + } + // Line up each entry on an individual line. + const indentation = `\n${" ".repeat(ctx.indentationLvl)}`; + return `${base ? `${base} ` : ""}${braces[0]}${indentation} ` + + `${join(output, `,${indentation} `)}${indentation}${braces[1]}`; + } + // Line up all entries on a single line in case the entries do not exceed + // `breakLength`. + if (isBelowBreakLength(ctx, output, 0, base)) { + return `${braces[0]}${base ? ` ${base}` : ""} ${join(output, ", ")} ` + + braces[1]; + } + const indentation = " ".repeat(ctx.indentationLvl); + // If the opening "brace" is too large, like in the case of "Set {", + // we need to force the first item to be on the next line or the + // items will not line up correctly. + const ln = base === "" && braces[0].length === 1 + ? " " + : `${base ? ` ${base}` : ""}\n${indentation} `; + // Line up each entry on an individual line. + return `${braces[0]}${ln}${join(output, `,\n${indentation} `)} ${braces[1]}`; +} + +// The built-in Array#join is slower in v8 6.0 +function join(output, separator) { + let str = ""; + if (output.length !== 0) { + const lastIndex = output.length - 1; + for (let i = 0; i < lastIndex; i++) { + // It is faster not to use a template string here + str += output[i]; + str += separator; + } + str += output[lastIndex]; + } + return str; +} + +function groupArrayElements(ctx, output, value) { + let totalLength = 0; + let maxLength = 0; + let i = 0; + let outputLength = output.length; + if (ctx.maxArrayLength < output.length) { + // This makes sure the "... n more items" part is not taken into account. + outputLength--; + } + const separatorSpace = 2; // Add 1 for the space and 1 for the separator. + const dataLen = new Array(outputLength); + // Calculate the total length of all output entries and the individual max + // entries length of all output entries. We have to remove colors first, + // otherwise the length would not be calculated properly. + for (; i < outputLength; i++) { + const len = getStringWidth(output[i], ctx.colors); + dataLen[i] = len; + totalLength += len + separatorSpace; + if (maxLength < len) { + maxLength = len; + } + } + // Add two to `maxLength` as we add a single whitespace character plus a comma + // in-between two entries. + const actualMax = maxLength + separatorSpace; + // Check if at least three entries fit next to each other and prevent grouping + // of arrays that contains entries of very different length (i.e., if a single + // entry is longer than 1/5 of all other entries combined). Otherwise the + // space in-between small entries would be enormous. + if ( + actualMax * 3 + ctx.indentationLvl < ctx.breakLength && + (totalLength / actualMax > 5 || maxLength <= 6) + ) { + const approxCharHeights = 2.5; + const averageBias = Math.sqrt(actualMax - totalLength / output.length); + const biasedMax = Math.max(actualMax - 3 - averageBias, 1); + // Dynamically check how many columns seem possible. + const columns = Math.min( + // Ideally a square should be drawn. We expect a character to be about 2.5 + // times as high as wide. This is the area formula to calculate a square + // which contains n rectangles of size `actualMax * approxCharHeights`. + // Divide that by `actualMax` to receive the correct number of columns. + // The added bias increases the columns for short entries. + Math.round( + Math.sqrt( + approxCharHeights * biasedMax * outputLength, + ) / biasedMax, + ), + // Do not exceed the breakLength. + Math.floor((ctx.breakLength - ctx.indentationLvl) / actualMax), + // Limit array grouping for small `compact` modes as the user requested + // minimal grouping. + ctx.compact * 4, + // Limit the columns to a maximum of fifteen. + 15, + ); + // Return with the original output if no grouping should happen. + if (columns <= 1) { + return output; + } + const tmp = []; + const maxLineLength = []; + for (let i = 0; i < columns; i++) { + let lineMaxLength = 0; + for (let j = i; j < output.length; j += columns) { + if (dataLen[j] > lineMaxLength) { + lineMaxLength = dataLen[j]; + } + } + lineMaxLength += separatorSpace; + maxLineLength[i] = lineMaxLength; + } + let order = String.prototype.padStart; + if (value !== undefined) { + for (let i = 0; i < output.length; i++) { + if (typeof value[i] !== "number" && typeof value[i] !== "bigint") { + order = String.prototype.padEnd; + break; + } + } + } + // Each iteration creates a single line of grouped entries. + for (let i = 0; i < outputLength; i += columns) { + // The last lines may contain less entries than columns. + const max = Math.min(i + columns, outputLength); + let str = ""; + let j = i; + for (; j < max - 1; j++) { + // Calculate extra color padding in case it's active. This has to be + // done line by line as some lines might contain more colors than + // others. + const padding = maxLineLength[j - i] + output[j].length - dataLen[j]; + str += `${output[j]}, `.padStart(padding, " "); + } + if (order === String.prototype.padStart) { + const padding = maxLineLength[j - i] + + output[j].length - + dataLen[j] - + separatorSpace; + str += output[j].padStart(padding, " "); + } else { + str += output[j]; + } + Array.prototype.push.call(tmp, str); + } + if (ctx.maxArrayLength < output.length) { + Array.prototype.push.call(tmp, output[outputLength]); + } + output = tmp; + } + return output; +} + +function formatMapIterInner( + ctx, + recurseTimes, + entries, + state, +) { + const maxArrayLength = Math.max(ctx.maxArrayLength, 0); + // Entries exist as [key1, val1, key2, val2, ...] + const len = entries.length / 2; + const remaining = len - maxArrayLength; + const maxLength = Math.min(maxArrayLength, len); + let output = new Array(maxLength); + let i = 0; + ctx.indentationLvl += 2; + if (state === kWeak) { + for (; i < maxLength; i++) { + const pos = i * 2; + output[i] = `${formatValue(ctx, entries[pos], recurseTimes)} => ${ + formatValue(ctx, entries[pos + 1], recurseTimes) + }`; + } + // Sort all entries to have a halfway reliable output (if more entries than + // retrieved ones exist, we can not reliably return the same output) if the + // output is not sorted anyway. + if (!ctx.sorted) { + output = output.sort(); + } + } else { + for (; i < maxLength; i++) { + const pos = i * 2; + const res = [ + formatValue(ctx, entries[pos], recurseTimes), + formatValue(ctx, entries[pos + 1], recurseTimes), + ]; + output[i] = reduceToSingleString( + ctx, + res, + "", + ["[", "]"], + kArrayExtrasType, + recurseTimes, + ); + } + } + ctx.indentationLvl -= 2; + if (remaining > 0) { + output.push(`... ${remaining} more item${remaining > 1 ? "s" : ""}`); + } + return output; +} + +function formatSetIterInner( + ctx, + recurseTimes, + entries, + state, +) { + const maxArrayLength = Math.max(ctx.maxArrayLength, 0); + const maxLength = Math.min(maxArrayLength, entries.length); + const output = new Array(maxLength); + ctx.indentationLvl += 2; + for (let i = 0; i < maxLength; i++) { + output[i] = formatValue(ctx, entries[i], recurseTimes); + } + ctx.indentationLvl -= 2; + if (state === kWeak && !ctx.sorted) { + // Sort all entries to have a halfway reliable output (if more entries than + // retrieved ones exist, we can not reliably return the same output) if the + // output is not sorted anyway. + output.sort(); + } + const remaining = entries.length - maxLength; + if (remaining > 0) { + Array.prototype.push.call( + output, + `... ${remaining} more item${remaining > 1 ? "s" : ""}`, + ); + } + return output; +} + +// Regex used for ansi escape code splitting +// Adopted from https://github.com/chalk/ansi-regex/blob/HEAD/index.js +// License: MIT, authors: @sindresorhus, Qix-, arjunmehta and LitoMore +// Matches all ansi escape code sequences in a string +const ansiPattern = "[\\u001B\\u009B][[\\]()#;?]*" + + "(?:(?:(?:(?:;[-a-zA-Z\\d\\/#&.:=?%@~_]+)*" + + "|[a-zA-Z\\d]+(?:;[-a-zA-Z\\d\\/#&.:=?%@~_]*)*)?\\u0007)" + + "|(?:(?:\\d{1,4}(?:;\\d{0,4})*)?[\\dA-PR-TZcf-ntqry=><~]))"; +const ansi = new RegExp(ansiPattern, "g"); + +/** + * Returns the number of columns required to display the given string. + */ +export function getStringWidth(str, removeControlChars = true) { + let width = 0; + + if (removeControlChars) { + str = stripVTControlCharacters(str); + } + str = str.normalize("NFC"); + for (const char of str[Symbol.iterator]()) { + const code = char.codePointAt(0); + if (isFullWidthCodePoint(code)) { + width += 2; + } else if (!isZeroWidthCodePoint(code)) { + width++; + } + } + + return width; +} + +/** + * Returns true if the character represented by a given + * Unicode code point is full-width. Otherwise returns false. + */ +const isFullWidthCodePoint = (code) => { + // Code points are partially derived from: + // https://www.unicode.org/Public/UNIDATA/EastAsianWidth.txt + return code >= 0x1100 && ( + code <= 0x115f || // Hangul Jamo + code === 0x2329 || // LEFT-POINTING ANGLE BRACKET + code === 0x232a || // RIGHT-POINTING ANGLE BRACKET + // CJK Radicals Supplement .. Enclosed CJK Letters and Months + (code >= 0x2e80 && code <= 0x3247 && code !== 0x303f) || + // Enclosed CJK Letters and Months .. CJK Unified Ideographs Extension A + (code >= 0x3250 && code <= 0x4dbf) || + // CJK Unified Ideographs .. Yi Radicals + (code >= 0x4e00 && code <= 0xa4c6) || + // Hangul Jamo Extended-A + (code >= 0xa960 && code <= 0xa97c) || + // Hangul Syllables + (code >= 0xac00 && code <= 0xd7a3) || + // CJK Compatibility Ideographs + (code >= 0xf900 && code <= 0xfaff) || + // Vertical Forms + (code >= 0xfe10 && code <= 0xfe19) || + // CJK Compatibility Forms .. Small Form Variants + (code >= 0xfe30 && code <= 0xfe6b) || + // Halfwidth and Fullwidth Forms + (code >= 0xff01 && code <= 0xff60) || + (code >= 0xffe0 && code <= 0xffe6) || + // Kana Supplement + (code >= 0x1b000 && code <= 0x1b001) || + // Enclosed Ideographic Supplement + (code >= 0x1f200 && code <= 0x1f251) || + // Miscellaneous Symbols and Pictographs 0x1f300 - 0x1f5ff + // Emoticons 0x1f600 - 0x1f64f + (code >= 0x1f300 && code <= 0x1f64f) || + // CJK Unified Ideographs Extension B .. Tertiary Ideographic Plane + (code >= 0x20000 && code <= 0x3fffd) + ); +}; + +const isZeroWidthCodePoint = (code) => { + return code <= 0x1F || // C0 control codes + (code >= 0x7F && code <= 0x9F) || // C1 control codes + (code >= 0x300 && code <= 0x36F) || // Combining Diacritical Marks + (code >= 0x200B && code <= 0x200F) || // Modifying Invisible Characters + // Combining Diacritical Marks for Symbols + (code >= 0x20D0 && code <= 0x20FF) || + (code >= 0xFE00 && code <= 0xFE0F) || // Variation Selectors + (code >= 0xFE20 && code <= 0xFE2F) || // Combining Half Marks + (code >= 0xE0100 && code <= 0xE01EF); // Variation Selectors +}; + +function hasBuiltInToString(value) { + // TODO(wafuwafu13): Implement + // // Prevent triggering proxy traps. + // const getFullProxy = false; + // const proxyTarget = getProxyDetails(value, getFullProxy); + const proxyTarget = undefined; + if (proxyTarget !== undefined) { + value = proxyTarget; + } + + // Count objects that have no `toString` function as built-in. + if (typeof value.toString !== "function") { + return true; + } + + // The object has a own `toString` property. Thus it's not not a built-in one. + if (Object.prototype.hasOwnProperty.call(value, "toString")) { + return false; + } + + // Find the object that has the `toString` property as own property in the + // prototype chain. + let pointer = value; + do { + pointer = Object.getPrototypeOf(pointer); + } while (!Object.prototype.hasOwnProperty.call(pointer, "toString")); + + // Check closer if the object is a built-in. + const descriptor = Object.getOwnPropertyDescriptor(pointer, "constructor"); + return descriptor !== undefined && + typeof descriptor.value === "function" && + builtInObjects.has(descriptor.value.name); +} + +const firstErrorLine = (error) => error.message.split("\n", 1)[0]; +let CIRCULAR_ERROR_MESSAGE; +function tryStringify(arg) { + try { + return JSON.stringify(arg); + } catch (err) { + // Populate the circular error message lazily + if (!CIRCULAR_ERROR_MESSAGE) { + try { + const a = {}; + a.a = a; + JSON.stringify(a); + } catch (circularError) { + CIRCULAR_ERROR_MESSAGE = firstErrorLine(circularError); + } + } + if ( + err.name === "TypeError" && + firstErrorLine(err) === CIRCULAR_ERROR_MESSAGE + ) { + return "[Circular]"; + } + throw err; + } +} + +export function format(...args) { + return formatWithOptionsInternal(undefined, args); +} + +export function formatWithOptions(inspectOptions, ...args) { + if (typeof inspectOptions !== "object" || inspectOptions === null) { + throw new codes.ERR_INVALID_ARG_TYPE( + "inspectOptions", + "object", + inspectOptions, + ); + } + return formatWithOptionsInternal(inspectOptions, args); +} + +function formatNumberNoColor(number, options) { + return formatNumber( + stylizeNoColor, + number, + options?.numericSeparator ?? inspectDefaultOptions.numericSeparator, + ); +} + +function formatBigIntNoColor(bigint, options) { + return formatBigInt( + stylizeNoColor, + bigint, + options?.numericSeparator ?? inspectDefaultOptions.numericSeparator, + ); +} + +function formatWithOptionsInternal(inspectOptions, args) { + const first = args[0]; + let a = 0; + let str = ""; + let join = ""; + + if (typeof first === "string") { + if (args.length === 1) { + return first; + } + let tempStr; + let lastPos = 0; + + for (let i = 0; i < first.length - 1; i++) { + if (first.charCodeAt(i) === 37) { // '%' + const nextChar = first.charCodeAt(++i); + if (a + 1 !== args.length) { + switch (nextChar) { + // deno-lint-ignore no-case-declarations + case 115: // 's' + const tempArg = args[++a]; + if (typeof tempArg === "number") { + tempStr = formatNumberNoColor(tempArg, inspectOptions); + } else if (typeof tempArg === "bigint") { + tempStr = formatBigIntNoColor(tempArg, inspectOptions); + } else if ( + typeof tempArg !== "object" || + tempArg === null || + !hasBuiltInToString(tempArg) + ) { + tempStr = String(tempArg); + } else { + tempStr = inspect(tempArg, { + ...inspectOptions, + compact: 3, + colors: false, + depth: 0, + }); + } + break; + case 106: // 'j' + tempStr = tryStringify(args[++a]); + break; + // deno-lint-ignore no-case-declarations + case 100: // 'd' + const tempNum = args[++a]; + if (typeof tempNum === "bigint") { + tempStr = formatBigIntNoColor(tempNum, inspectOptions); + } else if (typeof tempNum === "symbol") { + tempStr = "NaN"; + } else { + tempStr = formatNumberNoColor(Number(tempNum), inspectOptions); + } + break; + case 79: // 'O' + tempStr = inspect(args[++a], inspectOptions); + break; + case 111: // 'o' + tempStr = inspect(args[++a], { + ...inspectOptions, + showHidden: true, + showProxy: true, + depth: 4, + }); + break; + // deno-lint-ignore no-case-declarations + case 105: // 'i' + const tempInteger = args[++a]; + if (typeof tempInteger === "bigint") { + tempStr = formatBigIntNoColor(tempInteger, inspectOptions); + } else if (typeof tempInteger === "symbol") { + tempStr = "NaN"; + } else { + tempStr = formatNumberNoColor( + Number.parseInt(tempInteger), + inspectOptions, + ); + } + break; + // deno-lint-ignore no-case-declarations + case 102: // 'f' + const tempFloat = args[++a]; + if (typeof tempFloat === "symbol") { + tempStr = "NaN"; + } else { + tempStr = formatNumberNoColor( + Number.parseFloat(tempFloat), + inspectOptions, + ); + } + break; + case 99: // 'c' + a += 1; + tempStr = ""; + break; + case 37: // '%' + str += first.slice(lastPos, i); + lastPos = i + 1; + continue; + default: // Any other character is not a correct placeholder + continue; + } + if (lastPos !== i - 1) { + str += first.slice(lastPos, i - 1); + } + str += tempStr; + lastPos = i + 1; + } else if (nextChar === 37) { + str += first.slice(lastPos, i); + lastPos = i + 1; + } + } + } + if (lastPos !== 0) { + a++; + join = " "; + if (lastPos < first.length) { + str += first.slice(lastPos); + } + } + } + + while (a < args.length) { + const value = args[a]; + str += join; + str += typeof value !== "string" ? inspect(value, inspectOptions) : value; + join = " "; + a++; + } + return str; +} + +/** + * Remove all VT control characters. Use to estimate displayed string width. + */ +export function stripVTControlCharacters(str) { + validateString(str, "str"); + + return str.replace(ansi, ""); +} + +export default { + format, + getStringWidth, + inspect, + stripVTControlCharacters, + formatWithOptions, +}; diff --git a/ext/node/polyfills/internal/util/types.ts b/ext/node/polyfills/internal/util/types.ts new file mode 100644 index 00000000000000..299493bc921bb4 --- /dev/null +++ b/ext/node/polyfills/internal/util/types.ts @@ -0,0 +1,143 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. +// +// Adapted from Node.js. Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. + +import * as bindingTypes from "internal:deno_node/polyfills/internal_binding/types.ts"; +export { + isCryptoKey, + isKeyObject, +} from "internal:deno_node/polyfills/internal/crypto/_keys.ts"; + +// https://tc39.es/ecma262/#sec-get-%typedarray%.prototype-@@tostringtag +const _getTypedArrayToStringTag = Object.getOwnPropertyDescriptor( + Object.getPrototypeOf(Uint8Array).prototype, + Symbol.toStringTag, +)!.get!; + +export function isArrayBufferView( + value: unknown, +): value is + | DataView + | BigInt64Array + | BigUint64Array + | Float32Array + | Float64Array + | Int8Array + | Int16Array + | Int32Array + | Uint8Array + | Uint8ClampedArray + | Uint16Array + | Uint32Array { + return ArrayBuffer.isView(value); +} + +export function isBigInt64Array(value: unknown): value is BigInt64Array { + return _getTypedArrayToStringTag.call(value) === "BigInt64Array"; +} + +export function isBigUint64Array(value: unknown): value is BigUint64Array { + return _getTypedArrayToStringTag.call(value) === "BigUint64Array"; +} + +export function isFloat32Array(value: unknown): value is Float32Array { + return _getTypedArrayToStringTag.call(value) === "Float32Array"; +} + +export function isFloat64Array(value: unknown): value is Float64Array { + return _getTypedArrayToStringTag.call(value) === "Float64Array"; +} + +export function isInt8Array(value: unknown): value is Int8Array { + return _getTypedArrayToStringTag.call(value) === "Int8Array"; +} + +export function isInt16Array(value: unknown): value is Int16Array { + return _getTypedArrayToStringTag.call(value) === "Int16Array"; +} + +export function isInt32Array(value: unknown): value is Int32Array { + return _getTypedArrayToStringTag.call(value) === "Int32Array"; +} + +export function isTypedArray(value: unknown): value is + | BigInt64Array + | BigUint64Array + | Float32Array + | Float64Array + | Int8Array + | Int16Array + | Int32Array + | Uint8Array + | Uint8ClampedArray + | Uint16Array + | Uint32Array { + return _getTypedArrayToStringTag.call(value) !== undefined; +} + +export function isUint8Array(value: unknown): value is Uint8Array { + return _getTypedArrayToStringTag.call(value) === "Uint8Array"; +} + +export function isUint8ClampedArray( + value: unknown, +): value is Uint8ClampedArray { + return _getTypedArrayToStringTag.call(value) === "Uint8ClampedArray"; +} + +export function isUint16Array(value: unknown): value is Uint16Array { + return _getTypedArrayToStringTag.call(value) === "Uint16Array"; +} + +export function isUint32Array(value: unknown): value is Uint32Array { + return _getTypedArrayToStringTag.call(value) === "Uint32Array"; +} + +export const { + // isExternal, + isDate, + isArgumentsObject, + isBigIntObject, + isBooleanObject, + isNumberObject, + isStringObject, + isSymbolObject, + isNativeError, + isRegExp, + isAsyncFunction, + isGeneratorFunction, + isGeneratorObject, + isPromise, + isMap, + isSet, + isMapIterator, + isSetIterator, + isWeakMap, + isWeakSet, + isArrayBuffer, + isDataView, + isSharedArrayBuffer, + isProxy, + isModuleNamespaceObject, + isAnyArrayBuffer, + isBoxedPrimitive, +} = bindingTypes; diff --git a/ext/node/polyfills/internal/validators.mjs b/ext/node/polyfills/internal/validators.mjs new file mode 100644 index 00000000000000..bea9e881ad8876 --- /dev/null +++ b/ext/node/polyfills/internal/validators.mjs @@ -0,0 +1,317 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. +// Copyright Joyent and Node contributors. All rights reserved. MIT license. + +import { codes } from "internal:deno_node/polyfills/internal/error_codes.ts"; +import { hideStackFrames } from "internal:deno_node/polyfills/internal/hide_stack_frames.ts"; +import { isArrayBufferView } from "internal:deno_node/polyfills/internal/util/types.ts"; +import { normalizeEncoding } from "internal:deno_node/polyfills/internal/normalize_encoding.mjs"; + +/** + * @param {number} value + * @returns {boolean} + */ +function isInt32(value) { + return value === (value | 0); +} + +/** + * @param {unknown} value + * @returns {boolean} + */ +function isUint32(value) { + return value === (value >>> 0); +} + +const octalReg = /^[0-7]+$/; +const modeDesc = "must be a 32-bit unsigned integer or an octal string"; + +/** + * Parse and validate values that will be converted into mode_t (the S_* + * constants). Only valid numbers and octal strings are allowed. They could be + * converted to 32-bit unsigned integers or non-negative signed integers in the + * C++ land, but any value higher than 0o777 will result in platform-specific + * behaviors. + * + * @param {*} value Values to be validated + * @param {string} name Name of the argument + * @param {number} [def] If specified, will be returned for invalid values + * @returns {number} + */ +function parseFileMode(value, name, def) { + value ??= def; + if (typeof value === "string") { + if (!octalReg.test(value)) { + throw new codes.ERR_INVALID_ARG_VALUE(name, value, modeDesc); + } + value = Number.parseInt(value, 8); + } + + validateInt32(value, name, 0, 2 ** 32 - 1); + return value; +} + +const validateBuffer = hideStackFrames((buffer, name = "buffer") => { + if (!isArrayBufferView(buffer)) { + throw new codes.ERR_INVALID_ARG_TYPE( + name, + ["Buffer", "TypedArray", "DataView"], + buffer, + ); + } +}); + +const validateInteger = hideStackFrames( + ( + value, + name, + min = Number.MIN_SAFE_INTEGER, + max = Number.MAX_SAFE_INTEGER, + ) => { + if (typeof value !== "number") { + throw new codes.ERR_INVALID_ARG_TYPE(name, "number", value); + } + if (!Number.isInteger(value)) { + throw new codes.ERR_OUT_OF_RANGE(name, "an integer", value); + } + if (value < min || value > max) { + throw new codes.ERR_OUT_OF_RANGE(name, `>= ${min} && <= ${max}`, value); + } + }, +); + +/** + * @param {unknown} value + * @param {string} name + * @param {{ + * allowArray?: boolean, + * allowFunction?: boolean, + * nullable?: boolean + * }} [options] + */ +const validateObject = hideStackFrames((value, name, options) => { + const useDefaultOptions = options == null; + const allowArray = useDefaultOptions ? false : options.allowArray; + const allowFunction = useDefaultOptions ? false : options.allowFunction; + const nullable = useDefaultOptions ? false : options.nullable; + if ( + (!nullable && value === null) || + (!allowArray && Array.isArray(value)) || + (typeof value !== "object" && ( + !allowFunction || typeof value !== "function" + )) + ) { + throw new codes.ERR_INVALID_ARG_TYPE(name, "Object", value); + } +}); + +const validateInt32 = hideStackFrames( + (value, name, min = -2147483648, max = 2147483647) => { + // The defaults for min and max correspond to the limits of 32-bit integers. + if (!isInt32(value)) { + if (typeof value !== "number") { + throw new codes.ERR_INVALID_ARG_TYPE(name, "number", value); + } + + if (!Number.isInteger(value)) { + throw new codes.ERR_OUT_OF_RANGE(name, "an integer", value); + } + + throw new codes.ERR_OUT_OF_RANGE(name, `>= ${min} && <= ${max}`, value); + } + + if (value < min || value > max) { + throw new codes.ERR_OUT_OF_RANGE(name, `>= ${min} && <= ${max}`, value); + } + }, +); + +const validateUint32 = hideStackFrames( + (value, name, positive) => { + if (!isUint32(value)) { + if (typeof value !== "number") { + throw new codes.ERR_INVALID_ARG_TYPE(name, "number", value); + } + if (!Number.isInteger(value)) { + throw new codes.ERR_OUT_OF_RANGE(name, "an integer", value); + } + const min = positive ? 1 : 0; + // 2 ** 32 === 4294967296 + throw new codes.ERR_OUT_OF_RANGE( + name, + `>= ${min} && < 4294967296`, + value, + ); + } + if (positive && value === 0) { + throw new codes.ERR_OUT_OF_RANGE(name, ">= 1 && < 4294967296", value); + } + }, +); + +/** + * @param {unknown} value + * @param {string} name + */ +function validateString(value, name) { + if (typeof value !== "string") { + throw new codes.ERR_INVALID_ARG_TYPE(name, "string", value); + } +} + +/** + * @param {unknown} value + * @param {string} name + */ +function validateNumber(value, name) { + if (typeof value !== "number") { + throw new codes.ERR_INVALID_ARG_TYPE(name, "number", value); + } +} + +/** + * @param {unknown} value + * @param {string} name + */ +function validateBoolean(value, name) { + if (typeof value !== "boolean") { + throw new codes.ERR_INVALID_ARG_TYPE(name, "boolean", value); + } +} + +/** + * @param {unknown} value + * @param {string} name + * @param {unknown[]} oneOf + */ +const validateOneOf = hideStackFrames( + (value, name, oneOf) => { + if (!Array.prototype.includes.call(oneOf, value)) { + const allowed = Array.prototype.join.call( + Array.prototype.map.call( + oneOf, + (v) => (typeof v === "string" ? `'${v}'` : String(v)), + ), + ", ", + ); + const reason = "must be one of: " + allowed; + + throw new codes.ERR_INVALID_ARG_VALUE(name, value, reason); + } + }, +); + +export function validateEncoding(data, encoding) { + const normalizedEncoding = normalizeEncoding(encoding); + const length = data.length; + + if (normalizedEncoding === "hex" && length % 2 !== 0) { + throw new codes.ERR_INVALID_ARG_VALUE( + "encoding", + encoding, + `is invalid for data of length ${length}`, + ); + } +} + +// Check that the port number is not NaN when coerced to a number, +// is an integer and that it falls within the legal range of port numbers. +/** + * @param {string} name + * @returns {number} + */ +function validatePort(port, name = "Port", allowZero = true) { + if ( + (typeof port !== "number" && typeof port !== "string") || + (typeof port === "string" && + String.prototype.trim.call(port).length === 0) || + +port !== (+port >>> 0) || + port > 0xFFFF || + (port === 0 && !allowZero) + ) { + throw new codes.ERR_SOCKET_BAD_PORT(name, port, allowZero); + } + + return port; +} + +/** + * @param {unknown} signal + * @param {string} name + */ +const validateAbortSignal = hideStackFrames( + (signal, name) => { + if ( + signal !== undefined && + (signal === null || + typeof signal !== "object" || + !("aborted" in signal)) + ) { + throw new codes.ERR_INVALID_ARG_TYPE(name, "AbortSignal", signal); + } + }, +); + +/** + * @param {unknown} value + * @param {string} name + */ +const validateFunction = hideStackFrames( + (value, name) => { + if (typeof value !== "function") { + throw new codes.ERR_INVALID_ARG_TYPE(name, "Function", value); + } + }, +); + +/** + * @param {unknown} value + * @param {string} name + */ +const validateArray = hideStackFrames( + (value, name, minLength = 0) => { + if (!Array.isArray(value)) { + throw new codes.ERR_INVALID_ARG_TYPE(name, "Array", value); + } + if (value.length < minLength) { + const reason = `must be longer than ${minLength}`; + throw new codes.ERR_INVALID_ARG_VALUE(name, value, reason); + } + }, +); + +export default { + isInt32, + isUint32, + parseFileMode, + validateAbortSignal, + validateArray, + validateBoolean, + validateBuffer, + validateFunction, + validateInt32, + validateInteger, + validateNumber, + validateObject, + validateOneOf, + validatePort, + validateString, + validateUint32, +}; +export { + isInt32, + isUint32, + parseFileMode, + validateAbortSignal, + validateArray, + validateBoolean, + validateBuffer, + validateFunction, + validateInt32, + validateInteger, + validateNumber, + validateObject, + validateOneOf, + validatePort, + validateString, + validateUint32, +}; diff --git a/ext/node/polyfills/internal_binding/README.md b/ext/node/polyfills/internal_binding/README.md new file mode 100644 index 00000000000000..903d33cd664110 --- /dev/null +++ b/ext/node/polyfills/internal_binding/README.md @@ -0,0 +1,11 @@ +# Internal Bindings + +The modules in this directory implement (simulate) C++ bindings implemented in +the `./src/` directory of the [Node.js](https://github.com/nodejs/node) +repository. + +These bindings are created in the Node.js source code by using +`NODE_MODULE_CONTEXT_AWARE_INTERNAL`. + +Please refer to for +further information. diff --git a/ext/node/polyfills/internal_binding/_libuv_winerror.ts b/ext/node/polyfills/internal_binding/_libuv_winerror.ts new file mode 100644 index 00000000000000..62123c2580a9db --- /dev/null +++ b/ext/node/polyfills/internal_binding/_libuv_winerror.ts @@ -0,0 +1,7 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. + +const { ops } = globalThis.__bootstrap.core; + +export function uvTranslateSysError(sysErrno: number): string { + return ops.op_node_sys_to_uv_error(sysErrno); +} diff --git a/ext/node/polyfills/internal_binding/_listen.ts b/ext/node/polyfills/internal_binding/_listen.ts new file mode 100644 index 00000000000000..d801b0548494c9 --- /dev/null +++ b/ext/node/polyfills/internal_binding/_listen.ts @@ -0,0 +1,16 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. +/** + * @param n Number to act on. + * @return The number rounded up to the nearest power of 2. + */ +export function ceilPowOf2(n: number) { + const roundPowOf2 = 1 << (31 - Math.clz32(n)); + + return roundPowOf2 < n ? roundPowOf2 * 2 : roundPowOf2; +} + +/** Initial backoff delay of 5ms following a temporary accept failure. */ +export const INITIAL_ACCEPT_BACKOFF_DELAY = 5; + +/** Max backoff delay of 1s following a temporary accept failure. */ +export const MAX_ACCEPT_BACKOFF_DELAY = 1000; diff --git a/ext/node/polyfills/internal_binding/_node.ts b/ext/node/polyfills/internal_binding/_node.ts new file mode 100644 index 00000000000000..47a289dda780ef --- /dev/null +++ b/ext/node/polyfills/internal_binding/_node.ts @@ -0,0 +1,18 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. +// This file contains C++ node globals accesed in internal binding calls + +/** + * Adapted from + * https://github.com/nodejs/node/blob/3b72788afb7365e10ae1e97c71d1f60ee29f09f2/src/node.h#L728-L738 + */ +export enum Encodings { + ASCII, // 0 + UTF8, // 1 + BASE64, // 2 + UCS2, // 3 + BINARY, // 4 + HEX, // 5 + BUFFER, // 6 + BASE64URL, // 7 + LATIN1 = 4, // 4 = BINARY +} diff --git a/ext/node/polyfills/internal_binding/_timingSafeEqual.ts b/ext/node/polyfills/internal_binding/_timingSafeEqual.ts new file mode 100644 index 00000000000000..9002300d13ed90 --- /dev/null +++ b/ext/node/polyfills/internal_binding/_timingSafeEqual.ts @@ -0,0 +1,43 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. +import { Buffer } from "internal:deno_node/polyfills/buffer.ts"; + +function assert(cond) { + if (!cond) { + throw new Error("assertion failed"); + } +} + +/** Compare to array buffers or data views in a way that timing based attacks + * cannot gain information about the platform. */ +function stdTimingSafeEqual( + a: ArrayBufferView | ArrayBufferLike | DataView, + b: ArrayBufferView | ArrayBufferLike | DataView, +): boolean { + if (a.byteLength !== b.byteLength) { + return false; + } + if (!(a instanceof DataView)) { + a = new DataView(ArrayBuffer.isView(a) ? a.buffer : a); + } + if (!(b instanceof DataView)) { + b = new DataView(ArrayBuffer.isView(b) ? b.buffer : b); + } + assert(a instanceof DataView); + assert(b instanceof DataView); + const length = a.byteLength; + let out = 0; + let i = -1; + while (++i < length) { + out |= a.getUint8(i) ^ b.getUint8(i); + } + return out === 0; +} + +export const timingSafeEqual = ( + a: Buffer | DataView | ArrayBuffer, + b: Buffer | DataView | ArrayBuffer, +): boolean => { + if (a instanceof Buffer) a = new DataView(a.buffer); + if (a instanceof Buffer) b = new DataView(a.buffer); + return stdTimingSafeEqual(a, b); +}; diff --git a/ext/node/polyfills/internal_binding/_utils.ts b/ext/node/polyfills/internal_binding/_utils.ts new file mode 100644 index 00000000000000..4d545df0e26c88 --- /dev/null +++ b/ext/node/polyfills/internal_binding/_utils.ts @@ -0,0 +1,88 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. +import { + forgivingBase64Decode, + forgivingBase64UrlEncode, +} from "internal:deno_web/00_infra.js"; + +export function asciiToBytes(str: string) { + const byteArray = []; + for (let i = 0; i < str.length; ++i) { + byteArray.push(str.charCodeAt(i) & 255); + } + return new Uint8Array(byteArray); +} + +export function base64ToBytes(str: string) { + str = base64clean(str); + str = str.replaceAll("-", "+").replaceAll("_", "/"); + return forgivingBase64Decode(str); +} + +const INVALID_BASE64_RE = /[^+/0-9A-Za-z-_]/g; +function base64clean(str: string) { + // Node takes equal signs as end of the Base64 encoding + str = str.split("=")[0]; + // Node strips out invalid characters like \n and \t from the string, std/base64 does not + str = str.trim().replace(INVALID_BASE64_RE, ""); + // Node converts strings with length < 2 to '' + if (str.length < 2) return ""; + // Node allows for non-padded base64 strings (missing trailing ===), std/base64 does not + while (str.length % 4 !== 0) { + str = str + "="; + } + return str; +} + +export function base64UrlToBytes(str: string) { + str = base64clean(str); + str = str.replaceAll("+", "-").replaceAll("/", "_"); + return forgivingBase64UrlEncode(str); +} + +export function hexToBytes(str: string) { + const byteArray = new Uint8Array(Math.floor((str || "").length / 2)); + let i; + for (i = 0; i < byteArray.length; i++) { + const a = Number.parseInt(str[i * 2], 16); + const b = Number.parseInt(str[i * 2 + 1], 16); + if (Number.isNaN(a) && Number.isNaN(b)) { + break; + } + byteArray[i] = (a << 4) | b; + } + return new Uint8Array( + i === byteArray.length ? byteArray : byteArray.slice(0, i), + ); +} + +export function utf16leToBytes(str: string, units: number) { + let c, hi, lo; + const byteArray = []; + for (let i = 0; i < str.length; ++i) { + if ((units -= 2) < 0) { + break; + } + c = str.charCodeAt(i); + hi = c >> 8; + lo = c % 256; + byteArray.push(lo); + byteArray.push(hi); + } + return new Uint8Array(byteArray); +} + +export function bytesToAscii(bytes: Uint8Array) { + let ret = ""; + for (let i = 0; i < bytes.length; ++i) { + ret += String.fromCharCode(bytes[i] & 127); + } + return ret; +} + +export function bytesToUtf16le(bytes: Uint8Array) { + let res = ""; + for (let i = 0; i < bytes.length - 1; i += 2) { + res += String.fromCharCode(bytes[i] + bytes[i + 1] * 256); + } + return res; +} diff --git a/ext/node/polyfills/internal_binding/ares.ts b/ext/node/polyfills/internal_binding/ares.ts new file mode 100644 index 00000000000000..1c9abb03816f67 --- /dev/null +++ b/ext/node/polyfills/internal_binding/ares.ts @@ -0,0 +1,67 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. +/* Copyright 1998 by the Massachusetts Institute of Technology. + * + * Permission to use, copy, modify, and distribute this + * software and its documentation for any purpose and without + * fee is hereby granted, provided that the above copyright + * notice appear in all copies and that both that copyright + * notice and this permission notice appear in supporting + * documentation, and that the name of M.I.T. not be used in + * advertising or publicity pertaining to distribution of the + * software without specific, written prior permission. + * M.I.T. makes no representations about the suitability of + * this software for any purpose. It is provided "as is" + * without express or implied warranty. + */ + +// REF: https://github.com/nodejs/node/blob/master/deps/cares/include/ares.h#L190 + +export const ARES_AI_CANONNAME = 1 << 0; +export const ARES_AI_NUMERICHOST = 1 << 1; +export const ARES_AI_PASSIVE = 1 << 2; +export const ARES_AI_NUMERICSERV = 1 << 3; +export const AI_V4MAPPED = 1 << 4; +export const AI_ALL = 1 << 5; +export const AI_ADDRCONFIG = 1 << 6; +export const ARES_AI_NOSORT = 1 << 7; +export const ARES_AI_ENVHOSTS = 1 << 8; + +// REF: https://github.com/nodejs/node/blob/master/deps/cares/src/lib/ares_strerror.c + +// deno-lint-ignore camelcase +export function ares_strerror(code: number) { + /* Return a string literal from a table. */ + const errorText = [ + "Successful completion", + "DNS server returned answer with no data", + "DNS server claims query was misformatted", + "DNS server returned general failure", + "Domain name not found", + "DNS server does not implement requested operation", + "DNS server refused query", + "Misformatted DNS query", + "Misformatted domain name", + "Unsupported address family", + "Misformatted DNS reply", + "Could not contact DNS servers", + "Timeout while contacting DNS servers", + "End of file", + "Error reading file", + "Out of memory", + "Channel is being destroyed", + "Misformatted string", + "Illegal flags specified", + "Given hostname is not numeric", + "Illegal hints flags specified", + "c-ares library initialization not yet performed", + "Error loading iphlpapi.dll", + "Could not find GetNetworkParams function", + "DNS query cancelled", + ]; + + if (code >= 0 && code < errorText.length) { + return errorText[code]; + } else { + return "unknown"; + } +} diff --git a/ext/node/polyfills/internal_binding/async_wrap.ts b/ext/node/polyfills/internal_binding/async_wrap.ts new file mode 100644 index 00000000000000..c8df985cda68a9 --- /dev/null +++ b/ext/node/polyfills/internal_binding/async_wrap.ts @@ -0,0 +1,152 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. +// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. + +// This module ports: +// - https://github.com/nodejs/node/blob/master/src/async_wrap-inl.h +// - https://github.com/nodejs/node/blob/master/src/async_wrap.cc +// - https://github.com/nodejs/node/blob/master/src/async_wrap.h + +export function registerDestroyHook( + // deno-lint-ignore no-explicit-any + _target: any, + _asyncId: number, + _prop: { destroyed: boolean }, +) { + // TODO(kt3k): implement actual procedures +} + +export enum constants { + kInit, + kBefore, + kAfter, + kDestroy, + kPromiseResolve, + kTotals, + kCheck, + kExecutionAsyncId, + kTriggerAsyncId, + kAsyncIdCounter, + kDefaultTriggerAsyncId, + kUsesExecutionAsyncResource, + kStackLength, +} + +const asyncHookFields = new Uint32Array(Object.keys(constants).length); + +export { asyncHookFields as async_hook_fields }; + +// Increment the internal id counter and return the value. +export function newAsyncId() { + return ++asyncIdFields[constants.kAsyncIdCounter]; +} + +export enum UidFields { + kExecutionAsyncId, + kTriggerAsyncId, + kAsyncIdCounter, + kDefaultTriggerAsyncId, + kUidFieldsCount, +} + +const asyncIdFields = new Float64Array(Object.keys(UidFields).length); + +// `kAsyncIdCounter` should start at `1` because that'll be the id the execution +// context during bootstrap. +asyncIdFields[UidFields.kAsyncIdCounter] = 1; + +// `kDefaultTriggerAsyncId` should be `-1`, this indicates that there is no +// specified default value and it should fallback to the executionAsyncId. +// 0 is not used as the magic value, because that indicates a missing +// context which is different from a default context. +asyncIdFields[UidFields.kDefaultTriggerAsyncId] = -1; + +export { asyncIdFields }; + +export enum providerType { + NONE, + DIRHANDLE, + DNSCHANNEL, + ELDHISTOGRAM, + FILEHANDLE, + FILEHANDLECLOSEREQ, + FIXEDSIZEBLOBCOPY, + FSEVENTWRAP, + FSREQCALLBACK, + FSREQPROMISE, + GETADDRINFOREQWRAP, + GETNAMEINFOREQWRAP, + HEAPSNAPSHOT, + HTTP2SESSION, + HTTP2STREAM, + HTTP2PING, + HTTP2SETTINGS, + HTTPINCOMINGMESSAGE, + HTTPCLIENTREQUEST, + JSSTREAM, + JSUDPWRAP, + MESSAGEPORT, + PIPECONNECTWRAP, + PIPESERVERWRAP, + PIPEWRAP, + PROCESSWRAP, + PROMISE, + QUERYWRAP, + SHUTDOWNWRAP, + SIGNALWRAP, + STATWATCHER, + STREAMPIPE, + TCPCONNECTWRAP, + TCPSERVERWRAP, + TCPWRAP, + TTYWRAP, + UDPSENDWRAP, + UDPWRAP, + SIGINTWATCHDOG, + WORKER, + WORKERHEAPSNAPSHOT, + WRITEWRAP, + ZLIB, +} + +const kInvalidAsyncId = -1; + +export class AsyncWrap { + provider: providerType = providerType.NONE; + asyncId = kInvalidAsyncId; + + constructor(provider: providerType) { + this.provider = provider; + this.getAsyncId(); + } + + getAsyncId(): number { + this.asyncId = this.asyncId === kInvalidAsyncId + ? newAsyncId() + : this.asyncId; + + return this.asyncId; + } + + getProviderType() { + return this.provider; + } +} diff --git a/ext/node/polyfills/internal_binding/buffer.ts b/ext/node/polyfills/internal_binding/buffer.ts new file mode 100644 index 00000000000000..58e104481de9dc --- /dev/null +++ b/ext/node/polyfills/internal_binding/buffer.ts @@ -0,0 +1,160 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. +import { Encodings } from "internal:deno_node/polyfills/internal_binding/_node.ts"; + +export function indexOfNeedle( + source: Uint8Array, + needle: Uint8Array, + start = 0, +): number { + if (start >= source.length) { + return -1; + } + if (start < 0) { + start = Math.max(0, source.length + start); + } + const s = needle[0]; + for (let i = start; i < source.length; i++) { + if (source[i] !== s) continue; + const pin = i; + let matched = 1; + let j = i; + while (matched < needle.length) { + j++; + if (source[j] !== needle[j - pin]) { + break; + } + matched++; + } + if (matched === needle.length) { + return pin; + } + } + return -1; +} + +export function numberToBytes(n: number): Uint8Array { + if (n === 0) return new Uint8Array([0]); + + const bytes = []; + bytes.unshift(n & 255); + while (n >= 256) { + n = n >>> 8; + bytes.unshift(n & 255); + } + return new Uint8Array(bytes); +} + +// TODO(Soremwar) +// Check if offset or buffer can be transform in order to just use std's lastIndexOf directly +// This implementation differs from std's lastIndexOf in the fact that +// it also includes items outside of the offset as long as part of the +// set is contained inside of the offset +// Probably way slower too +function findLastIndex( + targetBuffer: Uint8Array, + buffer: Uint8Array, + offset: number, +) { + offset = offset > targetBuffer.length ? targetBuffer.length : offset; + + const searchableBuffer = targetBuffer.slice(0, offset + buffer.length); + const searchableBufferLastIndex = searchableBuffer.length - 1; + const bufferLastIndex = buffer.length - 1; + + // Important to keep track of the last match index in order to backtrack after an incomplete match + // Not doing this will cause the search to skip all possible matches that happened in the + // last match range + let lastMatchIndex = -1; + let matches = 0; + let index = -1; + for (let x = 0; x <= searchableBufferLastIndex; x++) { + if ( + searchableBuffer[searchableBufferLastIndex - x] === + buffer[bufferLastIndex - matches] + ) { + if (lastMatchIndex === -1) { + lastMatchIndex = x; + } + matches++; + } else { + matches = 0; + if (lastMatchIndex !== -1) { + // Restart the search right after the last index was ignored + x = lastMatchIndex + 1; + lastMatchIndex = -1; + } + continue; + } + + if (matches === buffer.length) { + index = x; + break; + } + } + + if (index === -1) return index; + + return searchableBufferLastIndex - index; +} + +// TODO(@bartlomieju): +// Take encoding into account when evaluating index +function indexOfBuffer( + targetBuffer: Uint8Array, + buffer: Uint8Array, + byteOffset: number, + encoding: Encodings, + forwardDirection: boolean, +) { + if (!Encodings[encoding] === undefined) { + throw new Error(`Unknown encoding code ${encoding}`); + } + + if (!forwardDirection) { + // If negative the offset is calculated from the end of the buffer + + if (byteOffset < 0) { + byteOffset = targetBuffer.length + byteOffset; + } + + if (buffer.length === 0) { + return byteOffset <= targetBuffer.length + ? byteOffset + : targetBuffer.length; + } + + return findLastIndex(targetBuffer, buffer, byteOffset); + } + + if (buffer.length === 0) { + return byteOffset <= targetBuffer.length ? byteOffset : targetBuffer.length; + } + + return indexOfNeedle(targetBuffer, buffer, byteOffset); +} + +// TODO(Soremwar) +// Node's implementation is a very obscure algorithm that I haven't been able to crack just yet +function indexOfNumber( + targetBuffer: Uint8Array, + number: number, + byteOffset: number, + forwardDirection: boolean, +) { + const bytes = numberToBytes(number); + + if (bytes.length > 1) { + throw new Error("Multi byte number search is not supported"); + } + + return indexOfBuffer( + targetBuffer, + numberToBytes(number), + byteOffset, + Encodings.UTF8, + forwardDirection, + ); +} + +export default { indexOfBuffer, indexOfNumber }; +export { indexOfBuffer, indexOfNumber }; diff --git a/ext/node/polyfills/internal_binding/cares_wrap.ts b/ext/node/polyfills/internal_binding/cares_wrap.ts new file mode 100644 index 00000000000000..1345e946388156 --- /dev/null +++ b/ext/node/polyfills/internal_binding/cares_wrap.ts @@ -0,0 +1,545 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. +// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. + +// This module ports: +// - https://github.com/nodejs/node/blob/master/src/cares_wrap.cc +// - https://github.com/nodejs/node/blob/master/src/cares_wrap.h + +import type { ErrnoException } from "internal:deno_node/polyfills/internal/errors.ts"; +import { isIPv4 } from "internal:deno_node/polyfills/internal/net.ts"; +import { codeMap } from "internal:deno_node/polyfills/internal_binding/uv.ts"; +import { + AsyncWrap, + providerType, +} from "internal:deno_node/polyfills/internal_binding/async_wrap.ts"; +// deno-lint-ignore camelcase +import { ares_strerror } from "internal:deno_node/polyfills/internal_binding/ares.ts"; +import { notImplemented } from "internal:deno_node/polyfills/_utils.ts"; +import { isWindows } from "internal:deno_node/polyfills/_util/os.ts"; + +interface LookupAddress { + address: string; + family: number; +} + +export class GetAddrInfoReqWrap extends AsyncWrap { + family!: number; + hostname!: string; + + callback!: ( + err: ErrnoException | null, + addressOrAddresses?: string | LookupAddress[] | null, + family?: number, + ) => void; + resolve!: (addressOrAddresses: LookupAddress | LookupAddress[]) => void; + reject!: (err: ErrnoException | null) => void; + oncomplete!: (err: number | null, addresses: string[]) => void; + + constructor() { + super(providerType.GETADDRINFOREQWRAP); + } +} + +export function getaddrinfo( + req: GetAddrInfoReqWrap, + hostname: string, + family: number, + _hints: number, + verbatim: boolean, +): number { + let addresses: string[] = []; + + // TODO(cmorten): use hints + // REF: https://nodejs.org/api/dns.html#dns_supported_getaddrinfo_flags + + const recordTypes: ("A" | "AAAA")[] = []; + + if (family === 0 || family === 4) { + recordTypes.push("A"); + } + if (family === 0 || family === 6) { + recordTypes.push("AAAA"); + } + + (async () => { + await Promise.allSettled( + recordTypes.map((recordType) => + Deno.resolveDns(hostname, recordType).then((records) => { + records.forEach((record) => addresses.push(record)); + }) + ), + ); + + const error = addresses.length ? 0 : codeMap.get("EAI_NODATA")!; + + // TODO(cmorten): needs work + // REF: https://github.com/nodejs/node/blob/master/src/cares_wrap.cc#L1444 + if (!verbatim) { + addresses.sort((a: string, b: string): number => { + if (isIPv4(a)) { + return -1; + } else if (isIPv4(b)) { + return 1; + } + + return 0; + }); + } + + // TODO(@bartlomieju): Forces IPv4 as a workaround for Deno not + // aligning with Node on implicit binding on Windows + // REF: https://github.com/denoland/deno/issues/10762 + if (isWindows && hostname === "localhost") { + addresses = addresses.filter((address) => isIPv4(address)); + } + + req.oncomplete(error, addresses); + })(); + + return 0; +} + +export class QueryReqWrap extends AsyncWrap { + bindingName!: string; + hostname!: string; + ttl!: boolean; + + callback!: ( + err: ErrnoException | null, + // deno-lint-ignore no-explicit-any + records?: any, + ) => void; + // deno-lint-ignore no-explicit-any + resolve!: (records: any) => void; + reject!: (err: ErrnoException | null) => void; + oncomplete!: ( + err: number, + // deno-lint-ignore no-explicit-any + records: any, + ttls?: number[], + ) => void; + + constructor() { + super(providerType.QUERYWRAP); + } +} + +export interface ChannelWrapQuery { + queryAny(req: QueryReqWrap, name: string): number; + queryA(req: QueryReqWrap, name: string): number; + queryAaaa(req: QueryReqWrap, name: string): number; + queryCaa(req: QueryReqWrap, name: string): number; + queryCname(req: QueryReqWrap, name: string): number; + queryMx(req: QueryReqWrap, name: string): number; + queryNs(req: QueryReqWrap, name: string): number; + queryTxt(req: QueryReqWrap, name: string): number; + querySrv(req: QueryReqWrap, name: string): number; + queryPtr(req: QueryReqWrap, name: string): number; + queryNaptr(req: QueryReqWrap, name: string): number; + querySoa(req: QueryReqWrap, name: string): number; + getHostByAddr(req: QueryReqWrap, name: string): number; +} + +function fqdnToHostname(fqdn: string): string { + return fqdn.replace(/\.$/, ""); +} + +function compressIPv6(address: string): string { + const formatted = address.replace(/\b(?:0+:){2,}/, ":"); + const finalAddress = formatted + .split(":") + .map((octet) => { + if (octet.match(/^\d+\.\d+\.\d+\.\d+$/)) { + // decimal + return Number(octet.replaceAll(".", "")).toString(16); + } + + return octet.replace(/\b0+/g, ""); + }) + .join(":"); + + return finalAddress; +} + +export class ChannelWrap extends AsyncWrap implements ChannelWrapQuery { + #servers: [string, number][] = []; + #timeout: number; + #tries: number; + + constructor(timeout: number, tries: number) { + super(providerType.DNSCHANNEL); + + this.#timeout = timeout; + this.#tries = tries; + } + + async #query(query: string, recordType: Deno.RecordType) { + // TODO(@bartlomieju): TTL logic. + + let code: number; + let ret: Awaited>; + + if (this.#servers.length) { + for (const [ipAddr, port] of this.#servers) { + const resolveOptions = { + nameServer: { + ipAddr, + port, + }, + }; + + ({ code, ret } = await this.#resolve( + query, + recordType, + resolveOptions, + )); + + if (code === 0 || code === codeMap.get("EAI_NODATA")!) { + break; + } + } + } else { + ({ code, ret } = await this.#resolve(query, recordType)); + } + + return { code: code!, ret: ret! }; + } + + async #resolve( + query: string, + recordType: Deno.RecordType, + resolveOptions?: Deno.ResolveDnsOptions, + ): Promise<{ + code: number; + ret: Awaited>; + }> { + let ret: Awaited> = []; + let code = 0; + + try { + ret = await Deno.resolveDns(query, recordType, resolveOptions); + } catch (e) { + if (e instanceof Deno.errors.NotFound) { + code = codeMap.get("EAI_NODATA")!; + } else { + // TODO(cmorten): map errors to appropriate error codes. + code = codeMap.get("UNKNOWN")!; + } + } + + return { code, ret }; + } + + queryAny(req: QueryReqWrap, name: string): number { + // TODO(@bartlomieju): implemented temporary measure to allow limited usage of + // `resolveAny` like APIs. + // + // Ideally we move to using the "ANY" / "*" DNS query in future + // REF: https://github.com/denoland/deno/issues/14492 + (async () => { + const records: { type: Deno.RecordType; [key: string]: unknown }[] = []; + + await Promise.allSettled([ + this.#query(name, "A").then(({ ret }) => { + ret.forEach((record) => records.push({ type: "A", address: record })); + }), + this.#query(name, "AAAA").then(({ ret }) => { + (ret as string[]).forEach((record) => + records.push({ type: "AAAA", address: compressIPv6(record) }) + ); + }), + this.#query(name, "CAA").then(({ ret }) => { + (ret as Deno.CAARecord[]).forEach(({ critical, tag, value }) => + records.push({ + type: "CAA", + [tag]: value, + critical: +critical && 128, + }) + ); + }), + this.#query(name, "CNAME").then(({ ret }) => { + ret.forEach((record) => + records.push({ type: "CNAME", value: record }) + ); + }), + this.#query(name, "MX").then(({ ret }) => { + (ret as Deno.MXRecord[]).forEach(({ preference, exchange }) => + records.push({ + type: "MX", + priority: preference, + exchange: fqdnToHostname(exchange), + }) + ); + }), + this.#query(name, "NAPTR").then(({ ret }) => { + (ret as Deno.NAPTRRecord[]).forEach( + ({ order, preference, flags, services, regexp, replacement }) => + records.push({ + type: "NAPTR", + order, + preference, + flags, + service: services, + regexp, + replacement, + }), + ); + }), + this.#query(name, "NS").then(({ ret }) => { + (ret as string[]).forEach((record) => + records.push({ type: "NS", value: fqdnToHostname(record) }) + ); + }), + this.#query(name, "PTR").then(({ ret }) => { + (ret as string[]).forEach((record) => + records.push({ type: "PTR", value: fqdnToHostname(record) }) + ); + }), + this.#query(name, "SOA").then(({ ret }) => { + (ret as Deno.SOARecord[]).forEach( + ({ mname, rname, serial, refresh, retry, expire, minimum }) => + records.push({ + type: "SOA", + nsname: fqdnToHostname(mname), + hostmaster: fqdnToHostname(rname), + serial, + refresh, + retry, + expire, + minttl: minimum, + }), + ); + }), + this.#query(name, "SRV").then(({ ret }) => { + (ret as Deno.SRVRecord[]).forEach( + ({ priority, weight, port, target }) => + records.push({ + type: "SRV", + priority, + weight, + port, + name: target, + }), + ); + }), + this.#query(name, "TXT").then(({ ret }) => { + ret.forEach((record) => + records.push({ type: "TXT", entries: record }) + ); + }), + ]); + + const err = records.length ? 0 : codeMap.get("EAI_NODATA")!; + + req.oncomplete(err, records); + })(); + + return 0; + } + + queryA(req: QueryReqWrap, name: string): number { + this.#query(name, "A").then(({ code, ret }) => { + req.oncomplete(code, ret); + }); + + return 0; + } + + queryAaaa(req: QueryReqWrap, name: string): number { + this.#query(name, "AAAA").then(({ code, ret }) => { + const records = (ret as string[]).map((record) => compressIPv6(record)); + + req.oncomplete(code, records); + }); + + return 0; + } + + queryCaa(req: QueryReqWrap, name: string): number { + this.#query(name, "CAA").then(({ code, ret }) => { + const records = (ret as Deno.CAARecord[]).map( + ({ critical, tag, value }) => ({ + [tag]: value, + critical: +critical && 128, + }), + ); + + req.oncomplete(code, records); + }); + + return 0; + } + + queryCname(req: QueryReqWrap, name: string): number { + this.#query(name, "CNAME").then(({ code, ret }) => { + req.oncomplete(code, ret); + }); + + return 0; + } + + queryMx(req: QueryReqWrap, name: string): number { + this.#query(name, "MX").then(({ code, ret }) => { + const records = (ret as Deno.MXRecord[]).map( + ({ preference, exchange }) => ({ + priority: preference, + exchange: fqdnToHostname(exchange), + }), + ); + + req.oncomplete(code, records); + }); + + return 0; + } + + queryNaptr(req: QueryReqWrap, name: string): number { + this.#query(name, "NAPTR").then(({ code, ret }) => { + const records = (ret as Deno.NAPTRRecord[]).map( + ({ order, preference, flags, services, regexp, replacement }) => ({ + flags, + service: services, + regexp, + replacement, + order, + preference, + }), + ); + + req.oncomplete(code, records); + }); + + return 0; + } + + queryNs(req: QueryReqWrap, name: string): number { + this.#query(name, "NS").then(({ code, ret }) => { + const records = (ret as string[]).map((record) => fqdnToHostname(record)); + + req.oncomplete(code, records); + }); + + return 0; + } + + queryPtr(req: QueryReqWrap, name: string): number { + this.#query(name, "PTR").then(({ code, ret }) => { + const records = (ret as string[]).map((record) => fqdnToHostname(record)); + + req.oncomplete(code, records); + }); + + return 0; + } + + querySoa(req: QueryReqWrap, name: string): number { + this.#query(name, "SOA").then(({ code, ret }) => { + let record = {}; + + if (ret.length) { + const { mname, rname, serial, refresh, retry, expire, minimum } = + ret[0] as Deno.SOARecord; + + record = { + nsname: fqdnToHostname(mname), + hostmaster: fqdnToHostname(rname), + serial, + refresh, + retry, + expire, + minttl: minimum, + }; + } + + req.oncomplete(code, record); + }); + + return 0; + } + + querySrv(req: QueryReqWrap, name: string): number { + this.#query(name, "SRV").then(({ code, ret }) => { + const records = (ret as Deno.SRVRecord[]).map( + ({ priority, weight, port, target }) => ({ + priority, + weight, + port, + name: target, + }), + ); + + req.oncomplete(code, records); + }); + + return 0; + } + + queryTxt(req: QueryReqWrap, name: string): number { + this.#query(name, "TXT").then(({ code, ret }) => { + req.oncomplete(code, ret); + }); + + return 0; + } + + getHostByAddr(_req: QueryReqWrap, _name: string): number { + // TODO(@bartlomieju): https://github.com/denoland/deno/issues/14432 + notImplemented("cares.ChannelWrap.prototype.getHostByAddr"); + } + + getServers(): [string, number][] { + return this.#servers; + } + + setServers(servers: string | [number, string, number][]): number { + if (typeof servers === "string") { + const tuples: [string, number][] = []; + + for (let i = 0; i < servers.length; i += 2) { + tuples.push([servers[i], parseInt(servers[i + 1])]); + } + + this.#servers = tuples; + } else { + this.#servers = servers.map(([_ipVersion, ip, port]) => [ip, port]); + } + + return 0; + } + + setLocalAddress(_addr0: string, _addr1?: string) { + notImplemented("cares.ChannelWrap.prototype.setLocalAddress"); + } + + cancel() { + notImplemented("cares.ChannelWrap.prototype.cancel"); + } +} + +const DNS_ESETSRVPENDING = -1000; +const EMSG_ESETSRVPENDING = "There are pending queries."; + +export function strerror(code: number) { + return code === DNS_ESETSRVPENDING + ? EMSG_ESETSRVPENDING + : ares_strerror(code); +} diff --git a/ext/node/polyfills/internal_binding/config.ts b/ext/node/polyfills/internal_binding/config.ts new file mode 100644 index 00000000000000..93e2042b0d4343 --- /dev/null +++ b/ext/node/polyfills/internal_binding/config.ts @@ -0,0 +1,3 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. + +export {}; diff --git a/ext/node/polyfills/internal_binding/connection_wrap.ts b/ext/node/polyfills/internal_binding/connection_wrap.ts new file mode 100644 index 00000000000000..1ce95cc1bf2a33 --- /dev/null +++ b/ext/node/polyfills/internal_binding/connection_wrap.ts @@ -0,0 +1,94 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. +// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. + +// This module ports: +// - https://github.com/nodejs/node/blob/master/src/connection_wrap.cc +// - https://github.com/nodejs/node/blob/master/src/connection_wrap.h + +import { LibuvStreamWrap } from "internal:deno_node/polyfills/internal_binding/stream_wrap.ts"; +import { + AsyncWrap, + providerType, +} from "internal:deno_node/polyfills/internal_binding/async_wrap.ts"; + +interface Reader { + read(p: Uint8Array): Promise; +} + +interface Writer { + write(p: Uint8Array): Promise; +} + +export interface Closer { + close(): void; +} + +type Ref = { ref(): void; unref(): void }; + +export class ConnectionWrap extends LibuvStreamWrap { + /** Optional connection callback. */ + onconnection: ((status: number, handle?: ConnectionWrap) => void) | null = + null; + + /** + * Creates a new ConnectionWrap class instance. + * @param provider Provider type. + * @param object Optional stream object. + */ + constructor( + provider: providerType, + object?: Reader & Writer & Closer & Ref, + ) { + super(provider, object); + } + + /** + * @param req A connect request. + * @param status An error status code. + */ + afterConnect< + T extends AsyncWrap & { + oncomplete( + status: number, + handle: ConnectionWrap, + req: T, + readable: boolean, + writeable: boolean, + ): void; + }, + >( + req: T, + status: number, + ) { + const isSuccessStatus = !status; + const readable = isSuccessStatus; + const writable = isSuccessStatus; + + try { + req.oncomplete(status, this, req, readable, writable); + } catch { + // swallow callback errors. + } + + return; + } +} diff --git a/ext/node/polyfills/internal_binding/constants.ts b/ext/node/polyfills/internal_binding/constants.ts new file mode 100644 index 00000000000000..e6fd2d3ea0d50c --- /dev/null +++ b/ext/node/polyfills/internal_binding/constants.ts @@ -0,0 +1,903 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. +let os: { + dlopen: { + RTLD_DEEPBIND?: number; + RTLD_GLOBAL?: number; + RTLD_LAZY?: number; + RTLD_LOCAL?: number; + RTLD_NOW?: number; + }; + errno: { + E2BIG: number; + EACCES: number; + EADDRINUSE: number; + EADDRNOTAVAIL: number; + EAFNOSUPPORT: number; + EAGAIN: number; + EALREADY: number; + EBADF: number; + EBADMSG: number; + EBUSY: number; + ECANCELED: number; + ECHILD: number; + ECONNABORTED: number; + ECONNREFUSED: number; + ECONNRESET: number; + EDEADLK: number; + EDESTADDRREQ: number; + EDOM: number; + EDQUOT?: number; + EEXIST: number; + EFAULT: number; + EFBIG: number; + EHOSTUNREACH: number; + EIDRM: number; + EILSEQ: number; + EINPROGRESS: number; + EINTR: number; + EINVAL: number; + EIO: number; + EISCONN: number; + EISDIR: number; + ELOOP: number; + EMFILE: number; + EMLINK: number; + EMSGSIZE: number; + EMULTIHOP?: number; + ENAMETOOLONG: number; + ENETDOWN: number; + ENETRESET: number; + ENETUNREACH: number; + ENFILE: number; + ENOBUFS: number; + ENODATA: number; + ENODEV: number; + ENOENT: number; + ENOEXEC: number; + ENOLCK: number; + ENOLINK: number; + ENOMEM: number; + ENOMSG: number; + ENOPROTOOPT: number; + ENOSPC: number; + ENOSR: number; + ENOSTR: number; + ENOSYS: number; + ENOTCONN: number; + ENOTDIR: number; + ENOTEMPTY: number; + ENOTSOCK: number; + ENOTSUP: number; + ENOTTY: number; + ENXIO: number; + EOPNOTSUPP: number; + EOVERFLOW: number; + EPERM: number; + EPIPE: number; + EPROTO: number; + EPROTONOSUPPORT: number; + EPROTOTYPE: number; + ERANGE: number; + EROFS: number; + ESPIPE: number; + ESRCH: number; + ESTALE?: number; + ETIME: number; + ETIMEDOUT: number; + ETXTBSY: number; + EWOULDBLOCK: number; + EXDEV: number; + WSA_E_CANCELLED?: number; + WSA_E_NO_MORE?: number; + WSAEACCES?: number; + WSAEADDRINUSE?: number; + WSAEADDRNOTAVAIL?: number; + WSAEAFNOSUPPORT?: number; + WSAEALREADY?: number; + WSAEBADF?: number; + WSAECANCELLED?: number; + WSAECONNABORTED?: number; + WSAECONNREFUSED?: number; + WSAECONNRESET?: number; + WSAEDESTADDRREQ?: number; + WSAEDISCON?: number; + WSAEDQUOT?: number; + WSAEFAULT?: number; + WSAEHOSTDOWN?: number; + WSAEHOSTUNREACH?: number; + WSAEINPROGRESS?: number; + WSAEINTR?: number; + WSAEINVAL?: number; + WSAEINVALIDPROCTABLE?: number; + WSAEINVALIDPROVIDER?: number; + WSAEISCONN?: number; + WSAELOOP?: number; + WSAEMFILE?: number; + WSAEMSGSIZE?: number; + WSAENAMETOOLONG?: number; + WSAENETDOWN?: number; + WSAENETRESET?: number; + WSAENETUNREACH?: number; + WSAENOBUFS?: number; + WSAENOMORE?: number; + WSAENOPROTOOPT?: number; + WSAENOTCONN?: number; + WSAENOTEMPTY?: number; + WSAENOTSOCK?: number; + WSAEOPNOTSUPP?: number; + WSAEPFNOSUPPORT?: number; + WSAEPROCLIM?: number; + WSAEPROTONOSUPPORT?: number; + WSAEPROTOTYPE?: number; + WSAEPROVIDERFAILEDINIT?: number; + WSAEREFUSED?: number; + WSAEREMOTE?: number; + WSAESHUTDOWN?: number; + WSAESOCKTNOSUPPORT?: number; + WSAESTALE?: number; + WSAETIMEDOUT?: number; + WSAETOOMANYREFS?: number; + WSAEUSERS?: number; + WSAEWOULDBLOCK?: number; + WSANOTINITIALISED?: number; + WSASERVICE_NOT_FOUND?: number; + WSASYSCALLFAILURE?: number; + WSASYSNOTREADY?: number; + WSATYPE_NOT_FOUND?: number; + WSAVERNOTSUPPORTED?: number; + }; + priority: { + PRIORITY_ABOVE_NORMAL: number; + PRIORITY_BELOW_NORMAL: number; + PRIORITY_HIGH: number; + PRIORITY_HIGHEST: number; + PRIORITY_LOW: number; + PRIORITY_NORMAL: number; + }; + signals: { + SIGABRT: number; + SIGALRM?: number; + SIGBREAK?: number; + SIGBUS?: number; + SIGCHLD?: number; + SIGCONT?: number; + SIGFPE: number; + SIGHUP: number; + SIGILL: number; + SIGINFO?: number; + SIGINT: number; + SIGIO?: number; + SIGIOT?: number; + SIGKILL: number; + SIGPIPE?: number; + SIGPOLL?: number; + SIGPROF?: number; + SIGPWR?: number; + SIGQUIT?: number; + SIGSEGV: number; + SIGSTKFLT?: number; + SIGSTOP?: number; + SIGSYS?: number; + SIGTERM: number; + SIGTRAP?: number; + SIGTSTP?: number; + SIGTTIN?: number; + SIGTTOU?: number; + SIGUNUSED?: number; + SIGURG?: number; + SIGUSR1?: number; + SIGUSR2?: number; + SIGVTALRM?: number; + SIGWINCH: number; + SIGXCPU?: number; + SIGXFSZ?: number; + }; + UV_UDP_IPV6ONLY?: number; + UV_UDP_REUSEADDR: number; +}; + +const core = globalThis.__bootstrap.core; +const buildOs = core.ops.op_node_build_os(); +if (buildOs === "darwin") { + os = { + UV_UDP_REUSEADDR: 4, + dlopen: { + RTLD_LAZY: 1, + RTLD_NOW: 2, + RTLD_GLOBAL: 8, + RTLD_LOCAL: 4, + }, + errno: { + E2BIG: 7, + EACCES: 13, + EADDRINUSE: 48, + EADDRNOTAVAIL: 49, + EAFNOSUPPORT: 47, + EAGAIN: 35, + EALREADY: 37, + EBADF: 9, + EBADMSG: 94, + EBUSY: 16, + ECANCELED: 89, + ECHILD: 10, + ECONNABORTED: 53, + ECONNREFUSED: 61, + ECONNRESET: 54, + EDEADLK: 11, + EDESTADDRREQ: 39, + EDOM: 33, + EDQUOT: 69, + EEXIST: 17, + EFAULT: 14, + EFBIG: 27, + EHOSTUNREACH: 65, + EIDRM: 90, + EILSEQ: 92, + EINPROGRESS: 36, + EINTR: 4, + EINVAL: 22, + EIO: 5, + EISCONN: 56, + EISDIR: 21, + ELOOP: 62, + EMFILE: 24, + EMLINK: 31, + EMSGSIZE: 40, + EMULTIHOP: 95, + ENAMETOOLONG: 63, + ENETDOWN: 50, + ENETRESET: 52, + ENETUNREACH: 51, + ENFILE: 23, + ENOBUFS: 55, + ENODATA: 96, + ENODEV: 19, + ENOENT: 2, + ENOEXEC: 8, + ENOLCK: 77, + ENOLINK: 97, + ENOMEM: 12, + ENOMSG: 91, + ENOPROTOOPT: 42, + ENOSPC: 28, + ENOSR: 98, + ENOSTR: 99, + ENOSYS: 78, + ENOTCONN: 57, + ENOTDIR: 20, + ENOTEMPTY: 66, + ENOTSOCK: 38, + ENOTSUP: 45, + ENOTTY: 25, + ENXIO: 6, + EOPNOTSUPP: 102, + EOVERFLOW: 84, + EPERM: 1, + EPIPE: 32, + EPROTO: 100, + EPROTONOSUPPORT: 43, + EPROTOTYPE: 41, + ERANGE: 34, + EROFS: 30, + ESPIPE: 29, + ESRCH: 3, + ESTALE: 70, + ETIME: 101, + ETIMEDOUT: 60, + ETXTBSY: 26, + EWOULDBLOCK: 35, + EXDEV: 18, + }, + signals: { + SIGHUP: 1, + SIGINT: 2, + SIGQUIT: 3, + SIGILL: 4, + SIGTRAP: 5, + SIGABRT: 6, + SIGIOT: 6, + SIGBUS: 10, + SIGFPE: 8, + SIGKILL: 9, + SIGUSR1: 30, + SIGSEGV: 11, + SIGUSR2: 31, + SIGPIPE: 13, + SIGALRM: 14, + SIGTERM: 15, + SIGCHLD: 20, + SIGCONT: 19, + SIGSTOP: 17, + SIGTSTP: 18, + SIGTTIN: 21, + SIGTTOU: 22, + SIGURG: 16, + SIGXCPU: 24, + SIGXFSZ: 25, + SIGVTALRM: 26, + SIGPROF: 27, + SIGWINCH: 28, + SIGIO: 23, + SIGINFO: 29, + SIGSYS: 12, + }, + priority: { + PRIORITY_LOW: 19, + PRIORITY_BELOW_NORMAL: 10, + PRIORITY_NORMAL: 0, + PRIORITY_ABOVE_NORMAL: -7, + PRIORITY_HIGH: -14, + PRIORITY_HIGHEST: -20, + }, + }; +} else if (buildOs === "linux") { + os = { + UV_UDP_REUSEADDR: 4, + dlopen: { + RTLD_LAZY: 1, + RTLD_NOW: 2, + RTLD_GLOBAL: 256, + RTLD_LOCAL: 0, + RTLD_DEEPBIND: 8, + }, + errno: { + E2BIG: 7, + EACCES: 13, + EADDRINUSE: 98, + EADDRNOTAVAIL: 99, + EAFNOSUPPORT: 97, + EAGAIN: 11, + EALREADY: 114, + EBADF: 9, + EBADMSG: 74, + EBUSY: 16, + ECANCELED: 125, + ECHILD: 10, + ECONNABORTED: 103, + ECONNREFUSED: 111, + ECONNRESET: 104, + EDEADLK: 35, + EDESTADDRREQ: 89, + EDOM: 33, + EDQUOT: 122, + EEXIST: 17, + EFAULT: 14, + EFBIG: 27, + EHOSTUNREACH: 113, + EIDRM: 43, + EILSEQ: 84, + EINPROGRESS: 115, + EINTR: 4, + EINVAL: 22, + EIO: 5, + EISCONN: 106, + EISDIR: 21, + ELOOP: 40, + EMFILE: 24, + EMLINK: 31, + EMSGSIZE: 90, + EMULTIHOP: 72, + ENAMETOOLONG: 36, + ENETDOWN: 100, + ENETRESET: 102, + ENETUNREACH: 101, + ENFILE: 23, + ENOBUFS: 105, + ENODATA: 61, + ENODEV: 19, + ENOENT: 2, + ENOEXEC: 8, + ENOLCK: 37, + ENOLINK: 67, + ENOMEM: 12, + ENOMSG: 42, + ENOPROTOOPT: 92, + ENOSPC: 28, + ENOSR: 63, + ENOSTR: 60, + ENOSYS: 38, + ENOTCONN: 107, + ENOTDIR: 20, + ENOTEMPTY: 39, + ENOTSOCK: 88, + ENOTSUP: 95, + ENOTTY: 25, + ENXIO: 6, + EOPNOTSUPP: 95, + EOVERFLOW: 75, + EPERM: 1, + EPIPE: 32, + EPROTO: 71, + EPROTONOSUPPORT: 93, + EPROTOTYPE: 91, + ERANGE: 34, + EROFS: 30, + ESPIPE: 29, + ESRCH: 3, + ESTALE: 116, + ETIME: 62, + ETIMEDOUT: 110, + ETXTBSY: 26, + EWOULDBLOCK: 11, + EXDEV: 18, + }, + signals: { + SIGHUP: 1, + SIGINT: 2, + SIGQUIT: 3, + SIGILL: 4, + SIGTRAP: 5, + SIGABRT: 6, + SIGIOT: 6, + SIGBUS: 7, + SIGFPE: 8, + SIGKILL: 9, + SIGUSR1: 10, + SIGSEGV: 11, + SIGUSR2: 12, + SIGPIPE: 13, + SIGALRM: 14, + SIGTERM: 15, + SIGCHLD: 17, + SIGSTKFLT: 16, + SIGCONT: 18, + SIGSTOP: 19, + SIGTSTP: 20, + SIGTTIN: 21, + SIGTTOU: 22, + SIGURG: 23, + SIGXCPU: 24, + SIGXFSZ: 25, + SIGVTALRM: 26, + SIGPROF: 27, + SIGWINCH: 28, + SIGIO: 29, + SIGPOLL: 29, + SIGPWR: 30, + SIGSYS: 31, + SIGUNUSED: 31, + }, + priority: { + PRIORITY_LOW: 19, + PRIORITY_BELOW_NORMAL: 10, + PRIORITY_NORMAL: 0, + PRIORITY_ABOVE_NORMAL: -7, + PRIORITY_HIGH: -14, + PRIORITY_HIGHEST: -20, + }, + }; +} else { + os = { + UV_UDP_REUSEADDR: 4, + dlopen: {}, + errno: { + E2BIG: 7, + EACCES: 13, + EADDRINUSE: 100, + EADDRNOTAVAIL: 101, + EAFNOSUPPORT: 102, + EAGAIN: 11, + EALREADY: 103, + EBADF: 9, + EBADMSG: 104, + EBUSY: 16, + ECANCELED: 105, + ECHILD: 10, + ECONNABORTED: 106, + ECONNREFUSED: 107, + ECONNRESET: 108, + EDEADLK: 36, + EDESTADDRREQ: 109, + EDOM: 33, + EEXIST: 17, + EFAULT: 14, + EFBIG: 27, + EHOSTUNREACH: 110, + EIDRM: 111, + EILSEQ: 42, + EINPROGRESS: 112, + EINTR: 4, + EINVAL: 22, + EIO: 5, + EISCONN: 113, + EISDIR: 21, + ELOOP: 114, + EMFILE: 24, + EMLINK: 31, + EMSGSIZE: 115, + ENAMETOOLONG: 38, + ENETDOWN: 116, + ENETRESET: 117, + ENETUNREACH: 118, + ENFILE: 23, + ENOBUFS: 119, + ENODATA: 120, + ENODEV: 19, + ENOENT: 2, + ENOEXEC: 8, + ENOLCK: 39, + ENOLINK: 121, + ENOMEM: 12, + ENOMSG: 122, + ENOPROTOOPT: 123, + ENOSPC: 28, + ENOSR: 124, + ENOSTR: 125, + ENOSYS: 40, + ENOTCONN: 126, + ENOTDIR: 20, + ENOTEMPTY: 41, + ENOTSOCK: 128, + ENOTSUP: 129, + ENOTTY: 25, + ENXIO: 6, + EOPNOTSUPP: 130, + EOVERFLOW: 132, + EPERM: 1, + EPIPE: 32, + EPROTO: 134, + EPROTONOSUPPORT: 135, + EPROTOTYPE: 136, + ERANGE: 34, + EROFS: 30, + ESPIPE: 29, + ESRCH: 3, + ETIME: 137, + ETIMEDOUT: 138, + ETXTBSY: 139, + EWOULDBLOCK: 140, + EXDEV: 18, + WSAEINTR: 10004, + WSAEBADF: 10009, + WSAEACCES: 10013, + WSAEFAULT: 10014, + WSAEINVAL: 10022, + WSAEMFILE: 10024, + WSAEWOULDBLOCK: 10035, + WSAEINPROGRESS: 10036, + WSAEALREADY: 10037, + WSAENOTSOCK: 10038, + WSAEDESTADDRREQ: 10039, + WSAEMSGSIZE: 10040, + WSAEPROTOTYPE: 10041, + WSAENOPROTOOPT: 10042, + WSAEPROTONOSUPPORT: 10043, + WSAESOCKTNOSUPPORT: 10044, + WSAEOPNOTSUPP: 10045, + WSAEPFNOSUPPORT: 10046, + WSAEAFNOSUPPORT: 10047, + WSAEADDRINUSE: 10048, + WSAEADDRNOTAVAIL: 10049, + WSAENETDOWN: 10050, + WSAENETUNREACH: 10051, + WSAENETRESET: 10052, + WSAECONNABORTED: 10053, + WSAECONNRESET: 10054, + WSAENOBUFS: 10055, + WSAEISCONN: 10056, + WSAENOTCONN: 10057, + WSAESHUTDOWN: 10058, + WSAETOOMANYREFS: 10059, + WSAETIMEDOUT: 10060, + WSAECONNREFUSED: 10061, + WSAELOOP: 10062, + WSAENAMETOOLONG: 10063, + WSAEHOSTDOWN: 10064, + WSAEHOSTUNREACH: 10065, + WSAENOTEMPTY: 10066, + WSAEPROCLIM: 10067, + WSAEUSERS: 10068, + WSAEDQUOT: 10069, + WSAESTALE: 10070, + WSAEREMOTE: 10071, + WSASYSNOTREADY: 10091, + WSAVERNOTSUPPORTED: 10092, + WSANOTINITIALISED: 10093, + WSAEDISCON: 10101, + WSAENOMORE: 10102, + WSAECANCELLED: 10103, + WSAEINVALIDPROCTABLE: 10104, + WSAEINVALIDPROVIDER: 10105, + WSAEPROVIDERFAILEDINIT: 10106, + WSASYSCALLFAILURE: 10107, + WSASERVICE_NOT_FOUND: 10108, + WSATYPE_NOT_FOUND: 10109, + WSA_E_NO_MORE: 10110, + WSA_E_CANCELLED: 10111, + WSAEREFUSED: 10112, + }, + signals: { + SIGHUP: 1, + SIGINT: 2, + SIGILL: 4, + SIGABRT: 22, + SIGFPE: 8, + SIGKILL: 9, + SIGSEGV: 11, + SIGTERM: 15, + SIGBREAK: 21, + SIGWINCH: 28, + }, + priority: { + PRIORITY_LOW: 19, + PRIORITY_BELOW_NORMAL: 10, + PRIORITY_NORMAL: 0, + PRIORITY_ABOVE_NORMAL: -7, + PRIORITY_HIGH: -14, + PRIORITY_HIGHEST: -20, + }, + }; +} + +export { os }; + +export const fs = { + UV_FS_SYMLINK_DIR: 1, + UV_FS_SYMLINK_JUNCTION: 2, + O_RDONLY: 0, + O_WRONLY: 1, + O_RDWR: 2, + UV_DIRENT_UNKNOWN: 0, + UV_DIRENT_FILE: 1, + UV_DIRENT_DIR: 2, + UV_DIRENT_LINK: 3, + UV_DIRENT_FIFO: 4, + UV_DIRENT_SOCKET: 5, + UV_DIRENT_CHAR: 6, + UV_DIRENT_BLOCK: 7, + S_IFMT: 61440, + S_IFREG: 32768, + S_IFDIR: 16384, + S_IFCHR: 8192, + S_IFBLK: 24576, + S_IFIFO: 4096, + S_IFLNK: 40960, + S_IFSOCK: 49152, + O_CREAT: 512, + O_EXCL: 2048, + UV_FS_O_FILEMAP: 0, + O_NOCTTY: 131072, + O_TRUNC: 1024, + O_APPEND: 8, + O_DIRECTORY: 1048576, + O_NOFOLLOW: 256, + O_SYNC: 128, + O_DSYNC: 4194304, + O_SYMLINK: 2097152, + O_NONBLOCK: 4, + S_IRWXU: 448, + S_IRUSR: 256, + S_IWUSR: 128, + S_IXUSR: 64, + S_IRWXG: 56, + S_IRGRP: 32, + S_IWGRP: 16, + S_IXGRP: 8, + S_IRWXO: 7, + S_IROTH: 4, + S_IWOTH: 2, + S_IXOTH: 1, + F_OK: 0, + R_OK: 4, + W_OK: 2, + X_OK: 1, + UV_FS_COPYFILE_EXCL: 1, + COPYFILE_EXCL: 1, + UV_FS_COPYFILE_FICLONE: 2, + COPYFILE_FICLONE: 2, + UV_FS_COPYFILE_FICLONE_FORCE: 4, + COPYFILE_FICLONE_FORCE: 4, +} as const; +export const crypto = { + OPENSSL_VERSION_NUMBER: 269488319, + SSL_OP_ALL: 2147485780, + SSL_OP_ALLOW_NO_DHE_KEX: 1024, + SSL_OP_ALLOW_UNSAFE_LEGACY_RENEGOTIATION: 262144, + SSL_OP_CIPHER_SERVER_PREFERENCE: 4194304, + SSL_OP_CISCO_ANYCONNECT: 32768, + SSL_OP_COOKIE_EXCHANGE: 8192, + SSL_OP_CRYPTOPRO_TLSEXT_BUG: 2147483648, + SSL_OP_DONT_INSERT_EMPTY_FRAGMENTS: 2048, + SSL_OP_EPHEMERAL_RSA: 0, + SSL_OP_LEGACY_SERVER_CONNECT: 4, + SSL_OP_MICROSOFT_BIG_SSLV3_BUFFER: 0, + SSL_OP_MICROSOFT_SESS_ID_BUG: 0, + SSL_OP_MSIE_SSLV2_RSA_PADDING: 0, + SSL_OP_NETSCAPE_CA_DN_BUG: 0, + SSL_OP_NETSCAPE_CHALLENGE_BUG: 0, + SSL_OP_NETSCAPE_DEMO_CIPHER_CHANGE_BUG: 0, + SSL_OP_NETSCAPE_REUSE_CIPHER_CHANGE_BUG: 0, + SSL_OP_NO_COMPRESSION: 131072, + SSL_OP_NO_ENCRYPT_THEN_MAC: 524288, + SSL_OP_NO_QUERY_MTU: 4096, + SSL_OP_NO_RENEGOTIATION: 1073741824, + SSL_OP_NO_SESSION_RESUMPTION_ON_RENEGOTIATION: 65536, + SSL_OP_NO_SSLv2: 0, + SSL_OP_NO_SSLv3: 33554432, + SSL_OP_NO_TICKET: 16384, + SSL_OP_NO_TLSv1: 67108864, + SSL_OP_NO_TLSv1_1: 268435456, + SSL_OP_NO_TLSv1_2: 134217728, + SSL_OP_NO_TLSv1_3: 536870912, + SSL_OP_PKCS1_CHECK_1: 0, + SSL_OP_PKCS1_CHECK_2: 0, + SSL_OP_PRIORITIZE_CHACHA: 2097152, + SSL_OP_SINGLE_DH_USE: 0, + SSL_OP_SINGLE_ECDH_USE: 0, + SSL_OP_SSLEAY_080_CLIENT_DH_BUG: 0, + SSL_OP_SSLREF2_REUSE_CERT_TYPE_BUG: 0, + SSL_OP_TLS_BLOCK_PADDING_BUG: 0, + SSL_OP_TLS_D5_BUG: 0, + SSL_OP_TLS_ROLLBACK_BUG: 8388608, + ENGINE_METHOD_RSA: 1, + ENGINE_METHOD_DSA: 2, + ENGINE_METHOD_DH: 4, + ENGINE_METHOD_RAND: 8, + ENGINE_METHOD_EC: 2048, + ENGINE_METHOD_CIPHERS: 64, + ENGINE_METHOD_DIGESTS: 128, + ENGINE_METHOD_PKEY_METHS: 512, + ENGINE_METHOD_PKEY_ASN1_METHS: 1024, + ENGINE_METHOD_ALL: 65535, + ENGINE_METHOD_NONE: 0, + DH_CHECK_P_NOT_SAFE_PRIME: 2, + DH_CHECK_P_NOT_PRIME: 1, + DH_UNABLE_TO_CHECK_GENERATOR: 4, + DH_NOT_SUITABLE_GENERATOR: 8, + ALPN_ENABLED: 1, + RSA_PKCS1_PADDING: 1, + RSA_SSLV23_PADDING: 2, + RSA_NO_PADDING: 3, + RSA_PKCS1_OAEP_PADDING: 4, + RSA_X931_PADDING: 5, + RSA_PKCS1_PSS_PADDING: 6, + RSA_PSS_SALTLEN_DIGEST: -1, + RSA_PSS_SALTLEN_MAX_SIGN: -2, + RSA_PSS_SALTLEN_AUTO: -2, + defaultCoreCipherList: + "TLS_AES_256_GCM_SHA384:TLS_CHACHA20_POLY1305_SHA256:TLS_AES_128_GCM_SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-SHA256:DHE-RSA-AES128-SHA256:ECDHE-RSA-AES256-SHA384:DHE-RSA-AES256-SHA384:ECDHE-RSA-AES256-SHA256:DHE-RSA-AES256-SHA256:HIGH:!aNULL:!eNULL:!EXPORT:!DES:!RC4:!MD5:!PSK:!SRP:!CAMELLIA", + TLS1_VERSION: 769, + TLS1_1_VERSION: 770, + TLS1_2_VERSION: 771, + TLS1_3_VERSION: 772, + POINT_CONVERSION_COMPRESSED: 2, + POINT_CONVERSION_UNCOMPRESSED: 4, + POINT_CONVERSION_HYBRID: 6, +} as const; +export const zlib = { + Z_NO_FLUSH: 0, + Z_PARTIAL_FLUSH: 1, + Z_SYNC_FLUSH: 2, + Z_FULL_FLUSH: 3, + Z_FINISH: 4, + Z_BLOCK: 5, + Z_OK: 0, + Z_STREAM_END: 1, + Z_NEED_DICT: 2, + Z_ERRNO: -1, + Z_STREAM_ERROR: -2, + Z_DATA_ERROR: -3, + Z_MEM_ERROR: -4, + Z_BUF_ERROR: -5, + Z_VERSION_ERROR: -6, + Z_NO_COMPRESSION: 0, + Z_BEST_SPEED: 1, + Z_BEST_COMPRESSION: 9, + Z_DEFAULT_COMPRESSION: -1, + Z_FILTERED: 1, + Z_HUFFMAN_ONLY: 2, + Z_RLE: 3, + Z_FIXED: 4, + Z_DEFAULT_STRATEGY: 0, + ZLIB_VERNUM: 4784, + DEFLATE: 1, + INFLATE: 2, + GZIP: 3, + GUNZIP: 4, + DEFLATERAW: 5, + INFLATERAW: 6, + UNZIP: 7, + BROTLI_DECODE: 8, + BROTLI_ENCODE: 9, + Z_MIN_WINDOWBITS: 8, + Z_MAX_WINDOWBITS: 15, + Z_DEFAULT_WINDOWBITS: 15, + Z_MIN_CHUNK: 64, + Z_MAX_CHUNK: Infinity, + Z_DEFAULT_CHUNK: 16384, + Z_MIN_MEMLEVEL: 1, + Z_MAX_MEMLEVEL: 9, + Z_DEFAULT_MEMLEVEL: 8, + Z_MIN_LEVEL: -1, + Z_MAX_LEVEL: 9, + Z_DEFAULT_LEVEL: -1, + BROTLI_OPERATION_PROCESS: 0, + BROTLI_OPERATION_FLUSH: 1, + BROTLI_OPERATION_FINISH: 2, + BROTLI_OPERATION_EMIT_METADATA: 3, + BROTLI_PARAM_MODE: 0, + BROTLI_MODE_GENERIC: 0, + BROTLI_MODE_TEXT: 1, + BROTLI_MODE_FONT: 2, + BROTLI_DEFAULT_MODE: 0, + BROTLI_PARAM_QUALITY: 1, + BROTLI_MIN_QUALITY: 0, + BROTLI_MAX_QUALITY: 11, + BROTLI_DEFAULT_QUALITY: 11, + BROTLI_PARAM_LGWIN: 2, + BROTLI_MIN_WINDOW_BITS: 10, + BROTLI_MAX_WINDOW_BITS: 24, + BROTLI_LARGE_MAX_WINDOW_BITS: 30, + BROTLI_DEFAULT_WINDOW: 22, + BROTLI_PARAM_LGBLOCK: 3, + BROTLI_MIN_INPUT_BLOCK_BITS: 16, + BROTLI_MAX_INPUT_BLOCK_BITS: 24, + BROTLI_PARAM_DISABLE_LITERAL_CONTEXT_MODELING: 4, + BROTLI_PARAM_SIZE_HINT: 5, + BROTLI_PARAM_LARGE_WINDOW: 6, + BROTLI_PARAM_NPOSTFIX: 7, + BROTLI_PARAM_NDIRECT: 8, + BROTLI_DECODER_RESULT_ERROR: 0, + BROTLI_DECODER_RESULT_SUCCESS: 1, + BROTLI_DECODER_RESULT_NEEDS_MORE_INPUT: 2, + BROTLI_DECODER_RESULT_NEEDS_MORE_OUTPUT: 3, + BROTLI_DECODER_PARAM_DISABLE_RING_BUFFER_REALLOCATION: 0, + BROTLI_DECODER_PARAM_LARGE_WINDOW: 1, + BROTLI_DECODER_NO_ERROR: 0, + BROTLI_DECODER_SUCCESS: 1, + BROTLI_DECODER_NEEDS_MORE_INPUT: 2, + BROTLI_DECODER_NEEDS_MORE_OUTPUT: 3, + BROTLI_DECODER_ERROR_FORMAT_EXUBERANT_NIBBLE: -1, + BROTLI_DECODER_ERROR_FORMAT_RESERVED: -2, + BROTLI_DECODER_ERROR_FORMAT_EXUBERANT_META_NIBBLE: -3, + BROTLI_DECODER_ERROR_FORMAT_SIMPLE_HUFFMAN_ALPHABET: -4, + BROTLI_DECODER_ERROR_FORMAT_SIMPLE_HUFFMAN_SAME: -5, + BROTLI_DECODER_ERROR_FORMAT_CL_SPACE: -6, + BROTLI_DECODER_ERROR_FORMAT_HUFFMAN_SPACE: -7, + BROTLI_DECODER_ERROR_FORMAT_CONTEXT_MAP_REPEAT: -8, + BROTLI_DECODER_ERROR_FORMAT_BLOCK_LENGTH_1: -9, + BROTLI_DECODER_ERROR_FORMAT_BLOCK_LENGTH_2: -10, + BROTLI_DECODER_ERROR_FORMAT_TRANSFORM: -11, + BROTLI_DECODER_ERROR_FORMAT_DICTIONARY: -12, + BROTLI_DECODER_ERROR_FORMAT_WINDOW_BITS: -13, + BROTLI_DECODER_ERROR_FORMAT_PADDING_1: -14, + BROTLI_DECODER_ERROR_FORMAT_PADDING_2: -15, + BROTLI_DECODER_ERROR_FORMAT_DISTANCE: -16, + BROTLI_DECODER_ERROR_DICTIONARY_NOT_SET: -19, + BROTLI_DECODER_ERROR_INVALID_ARGUMENTS: -20, + BROTLI_DECODER_ERROR_ALLOC_CONTEXT_MODES: -21, + BROTLI_DECODER_ERROR_ALLOC_TREE_GROUPS: -22, + BROTLI_DECODER_ERROR_ALLOC_CONTEXT_MAP: -25, + BROTLI_DECODER_ERROR_ALLOC_RING_BUFFER_1: -26, + BROTLI_DECODER_ERROR_ALLOC_RING_BUFFER_2: -27, + BROTLI_DECODER_ERROR_ALLOC_BLOCK_TYPE_TREES: -30, + BROTLI_DECODER_ERROR_UNREACHABLE: -31, +} as const; +export const trace = { + TRACE_EVENT_PHASE_BEGIN: 66, + TRACE_EVENT_PHASE_END: 69, + TRACE_EVENT_PHASE_COMPLETE: 88, + TRACE_EVENT_PHASE_INSTANT: 73, + TRACE_EVENT_PHASE_ASYNC_BEGIN: 83, + TRACE_EVENT_PHASE_ASYNC_STEP_INTO: 84, + TRACE_EVENT_PHASE_ASYNC_STEP_PAST: 112, + TRACE_EVENT_PHASE_ASYNC_END: 70, + TRACE_EVENT_PHASE_NESTABLE_ASYNC_BEGIN: 98, + TRACE_EVENT_PHASE_NESTABLE_ASYNC_END: 101, + TRACE_EVENT_PHASE_NESTABLE_ASYNC_INSTANT: 110, + TRACE_EVENT_PHASE_FLOW_BEGIN: 115, + TRACE_EVENT_PHASE_FLOW_STEP: 116, + TRACE_EVENT_PHASE_FLOW_END: 102, + TRACE_EVENT_PHASE_METADATA: 77, + TRACE_EVENT_PHASE_COUNTER: 67, + TRACE_EVENT_PHASE_SAMPLE: 80, + TRACE_EVENT_PHASE_CREATE_OBJECT: 78, + TRACE_EVENT_PHASE_SNAPSHOT_OBJECT: 79, + TRACE_EVENT_PHASE_DELETE_OBJECT: 68, + TRACE_EVENT_PHASE_MEMORY_DUMP: 118, + TRACE_EVENT_PHASE_MARK: 82, + TRACE_EVENT_PHASE_CLOCK_SYNC: 99, + TRACE_EVENT_PHASE_ENTER_CONTEXT: 40, + TRACE_EVENT_PHASE_LEAVE_CONTEXT: 41, + TRACE_EVENT_PHASE_LINK_IDS: 61, +} as const; diff --git a/ext/node/polyfills/internal_binding/contextify.ts b/ext/node/polyfills/internal_binding/contextify.ts new file mode 100644 index 00000000000000..93e2042b0d4343 --- /dev/null +++ b/ext/node/polyfills/internal_binding/contextify.ts @@ -0,0 +1,3 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. + +export {}; diff --git a/ext/node/polyfills/internal_binding/credentials.ts b/ext/node/polyfills/internal_binding/credentials.ts new file mode 100644 index 00000000000000..93e2042b0d4343 --- /dev/null +++ b/ext/node/polyfills/internal_binding/credentials.ts @@ -0,0 +1,3 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. + +export {}; diff --git a/ext/node/polyfills/internal_binding/crypto.ts b/ext/node/polyfills/internal_binding/crypto.ts new file mode 100644 index 00000000000000..ce4739b3daa78e --- /dev/null +++ b/ext/node/polyfills/internal_binding/crypto.ts @@ -0,0 +1,14 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. +// Copyright Joyent, Inc. and Node.js contributors. All rights reserved. MIT license. + +import { notImplemented } from "internal:deno_node/polyfills/_utils.ts"; + +export { timingSafeEqual } from "internal:deno_node/polyfills/internal_binding/_timingSafeEqual.ts"; + +export function getFipsCrypto(): boolean { + notImplemented("crypto.getFipsCrypto"); +} + +export function setFipsCrypto(_fips: boolean) { + notImplemented("crypto.setFipsCrypto"); +} diff --git a/ext/node/polyfills/internal_binding/errors.ts b/ext/node/polyfills/internal_binding/errors.ts new file mode 100644 index 00000000000000..93e2042b0d4343 --- /dev/null +++ b/ext/node/polyfills/internal_binding/errors.ts @@ -0,0 +1,3 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. + +export {}; diff --git a/ext/node/polyfills/internal_binding/fs.ts b/ext/node/polyfills/internal_binding/fs.ts new file mode 100644 index 00000000000000..93e2042b0d4343 --- /dev/null +++ b/ext/node/polyfills/internal_binding/fs.ts @@ -0,0 +1,3 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. + +export {}; diff --git a/ext/node/polyfills/internal_binding/fs_dir.ts b/ext/node/polyfills/internal_binding/fs_dir.ts new file mode 100644 index 00000000000000..93e2042b0d4343 --- /dev/null +++ b/ext/node/polyfills/internal_binding/fs_dir.ts @@ -0,0 +1,3 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. + +export {}; diff --git a/ext/node/polyfills/internal_binding/fs_event_wrap.ts b/ext/node/polyfills/internal_binding/fs_event_wrap.ts new file mode 100644 index 00000000000000..93e2042b0d4343 --- /dev/null +++ b/ext/node/polyfills/internal_binding/fs_event_wrap.ts @@ -0,0 +1,3 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. + +export {}; diff --git a/ext/node/polyfills/internal_binding/handle_wrap.ts b/ext/node/polyfills/internal_binding/handle_wrap.ts new file mode 100644 index 00000000000000..98c6a9f168e116 --- /dev/null +++ b/ext/node/polyfills/internal_binding/handle_wrap.ts @@ -0,0 +1,53 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. +// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. + +// This module ports: +// - https://github.com/nodejs/node/blob/master/src/handle_wrap.cc +// - https://github.com/nodejs/node/blob/master/src/handle_wrap.h + +import { unreachable } from "internal:deno_node/polyfills/_util/asserts.ts"; +import { + AsyncWrap, + providerType, +} from "internal:deno_node/polyfills/internal_binding/async_wrap.ts"; + +export class HandleWrap extends AsyncWrap { + constructor(provider: providerType) { + super(provider); + } + + close(cb: () => void = () => {}) { + this._onClose(); + queueMicrotask(cb); + } + + ref() { + unreachable(); + } + + unref() { + unreachable(); + } + + // deno-lint-ignore no-explicit-any + _onClose(): any {} +} diff --git a/ext/node/polyfills/internal_binding/heap_utils.ts b/ext/node/polyfills/internal_binding/heap_utils.ts new file mode 100644 index 00000000000000..93e2042b0d4343 --- /dev/null +++ b/ext/node/polyfills/internal_binding/heap_utils.ts @@ -0,0 +1,3 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. + +export {}; diff --git a/ext/node/polyfills/internal_binding/http_parser.ts b/ext/node/polyfills/internal_binding/http_parser.ts new file mode 100644 index 00000000000000..93e2042b0d4343 --- /dev/null +++ b/ext/node/polyfills/internal_binding/http_parser.ts @@ -0,0 +1,3 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. + +export {}; diff --git a/ext/node/polyfills/internal_binding/icu.ts b/ext/node/polyfills/internal_binding/icu.ts new file mode 100644 index 00000000000000..93e2042b0d4343 --- /dev/null +++ b/ext/node/polyfills/internal_binding/icu.ts @@ -0,0 +1,3 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. + +export {}; diff --git a/ext/node/polyfills/internal_binding/inspector.ts b/ext/node/polyfills/internal_binding/inspector.ts new file mode 100644 index 00000000000000..93e2042b0d4343 --- /dev/null +++ b/ext/node/polyfills/internal_binding/inspector.ts @@ -0,0 +1,3 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. + +export {}; diff --git a/ext/node/polyfills/internal_binding/js_stream.ts b/ext/node/polyfills/internal_binding/js_stream.ts new file mode 100644 index 00000000000000..93e2042b0d4343 --- /dev/null +++ b/ext/node/polyfills/internal_binding/js_stream.ts @@ -0,0 +1,3 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. + +export {}; diff --git a/ext/node/polyfills/internal_binding/messaging.ts b/ext/node/polyfills/internal_binding/messaging.ts new file mode 100644 index 00000000000000..93e2042b0d4343 --- /dev/null +++ b/ext/node/polyfills/internal_binding/messaging.ts @@ -0,0 +1,3 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. + +export {}; diff --git a/ext/node/polyfills/internal_binding/mod.ts b/ext/node/polyfills/internal_binding/mod.ts new file mode 100644 index 00000000000000..6273b263b9fdf8 --- /dev/null +++ b/ext/node/polyfills/internal_binding/mod.ts @@ -0,0 +1,108 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. +import * as asyncWrap from "internal:deno_node/polyfills/internal_binding/async_wrap.ts"; +import * as buffer from "internal:deno_node/polyfills/internal_binding/buffer.ts"; +import * as config from "internal:deno_node/polyfills/internal_binding/config.ts"; +import * as caresWrap from "internal:deno_node/polyfills/internal_binding/cares_wrap.ts"; +import * as constants from "internal:deno_node/polyfills/internal_binding/constants.ts"; +import * as contextify from "internal:deno_node/polyfills/internal_binding/contextify.ts"; +import * as crypto from "internal:deno_node/polyfills/internal_binding/crypto.ts"; +import * as credentials from "internal:deno_node/polyfills/internal_binding/credentials.ts"; +import * as errors from "internal:deno_node/polyfills/internal_binding/errors.ts"; +import * as fs from "internal:deno_node/polyfills/internal_binding/fs.ts"; +import * as fsDir from "internal:deno_node/polyfills/internal_binding/fs_dir.ts"; +import * as fsEventWrap from "internal:deno_node/polyfills/internal_binding/fs_event_wrap.ts"; +import * as heapUtils from "internal:deno_node/polyfills/internal_binding/heap_utils.ts"; +import * as httpParser from "internal:deno_node/polyfills/internal_binding/http_parser.ts"; +import * as icu from "internal:deno_node/polyfills/internal_binding/icu.ts"; +import * as inspector from "internal:deno_node/polyfills/internal_binding/inspector.ts"; +import * as jsStream from "internal:deno_node/polyfills/internal_binding/js_stream.ts"; +import * as messaging from "internal:deno_node/polyfills/internal_binding/messaging.ts"; +import * as moduleWrap from "internal:deno_node/polyfills/internal_binding/module_wrap.ts"; +import * as nativeModule from "internal:deno_node/polyfills/internal_binding/native_module.ts"; +import * as natives from "internal:deno_node/polyfills/internal_binding/natives.ts"; +import * as options from "internal:deno_node/polyfills/internal_binding/options.ts"; +import * as os from "internal:deno_node/polyfills/internal_binding/os.ts"; +import * as pipeWrap from "internal:deno_node/polyfills/internal_binding/pipe_wrap.ts"; +import * as performance from "internal:deno_node/polyfills/internal_binding/performance.ts"; +import * as processMethods from "internal:deno_node/polyfills/internal_binding/process_methods.ts"; +import * as report from "internal:deno_node/polyfills/internal_binding/report.ts"; +import * as serdes from "internal:deno_node/polyfills/internal_binding/serdes.ts"; +import * as signalWrap from "internal:deno_node/polyfills/internal_binding/signal_wrap.ts"; +import * as spawnSync from "internal:deno_node/polyfills/internal_binding/spawn_sync.ts"; +import * as streamWrap from "internal:deno_node/polyfills/internal_binding/stream_wrap.ts"; +import * as stringDecoder from "internal:deno_node/polyfills/internal_binding/string_decoder.ts"; +import * as symbols from "internal:deno_node/polyfills/internal_binding/symbols.ts"; +import * as taskQueue from "internal:deno_node/polyfills/internal_binding/task_queue.ts"; +import * as tcpWrap from "internal:deno_node/polyfills/internal_binding/tcp_wrap.ts"; +import * as timers from "internal:deno_node/polyfills/internal_binding/timers.ts"; +import * as tlsWrap from "internal:deno_node/polyfills/internal_binding/tls_wrap.ts"; +import * as traceEvents from "internal:deno_node/polyfills/internal_binding/trace_events.ts"; +import * as ttyWrap from "internal:deno_node/polyfills/internal_binding/tty_wrap.ts"; +import * as types from "internal:deno_node/polyfills/internal_binding/types.ts"; +import * as udpWrap from "internal:deno_node/polyfills/internal_binding/udp_wrap.ts"; +import * as url from "internal:deno_node/polyfills/internal_binding/url.ts"; +import * as util from "internal:deno_node/polyfills/internal_binding/util.ts"; +import * as uv from "internal:deno_node/polyfills/internal_binding/uv.ts"; +import * as v8 from "internal:deno_node/polyfills/internal_binding/v8.ts"; +import * as worker from "internal:deno_node/polyfills/internal_binding/worker.ts"; +import * as zlib from "internal:deno_node/polyfills/internal_binding/zlib.ts"; + +const modules = { + "async_wrap": asyncWrap, + buffer, + "cares_wrap": caresWrap, + config, + constants, + contextify, + credentials, + crypto, + errors, + fs, + "fs_dir": fsDir, + "fs_event_wrap": fsEventWrap, + "heap_utils": heapUtils, + "http_parser": httpParser, + icu, + inspector, + "js_stream": jsStream, + messaging, + "module_wrap": moduleWrap, + "native_module": nativeModule, + natives, + options, + os, + performance, + "pipe_wrap": pipeWrap, + "process_methods": processMethods, + report, + serdes, + "signal_wrap": signalWrap, + "spawn_sync": spawnSync, + "stream_wrap": streamWrap, + "string_decoder": stringDecoder, + symbols, + "task_queue": taskQueue, + "tcp_wrap": tcpWrap, + timers, + "tls_wrap": tlsWrap, + "trace_events": traceEvents, + "tty_wrap": ttyWrap, + types, + "udp_wrap": udpWrap, + url, + util, + uv, + v8, + worker, + zlib, +}; + +export type BindingName = keyof typeof modules; + +export function getBinding(name: BindingName) { + const mod = modules[name]; + if (!mod) { + throw new Error(`No such module: ${name}`); + } + return mod; +} diff --git a/ext/node/polyfills/internal_binding/module_wrap.ts b/ext/node/polyfills/internal_binding/module_wrap.ts new file mode 100644 index 00000000000000..93e2042b0d4343 --- /dev/null +++ b/ext/node/polyfills/internal_binding/module_wrap.ts @@ -0,0 +1,3 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. + +export {}; diff --git a/ext/node/polyfills/internal_binding/native_module.ts b/ext/node/polyfills/internal_binding/native_module.ts new file mode 100644 index 00000000000000..93e2042b0d4343 --- /dev/null +++ b/ext/node/polyfills/internal_binding/native_module.ts @@ -0,0 +1,3 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. + +export {}; diff --git a/ext/node/polyfills/internal_binding/natives.ts b/ext/node/polyfills/internal_binding/natives.ts new file mode 100644 index 00000000000000..93e2042b0d4343 --- /dev/null +++ b/ext/node/polyfills/internal_binding/natives.ts @@ -0,0 +1,3 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. + +export {}; diff --git a/ext/node/polyfills/internal_binding/node_file.ts b/ext/node/polyfills/internal_binding/node_file.ts new file mode 100644 index 00000000000000..742217b1993476 --- /dev/null +++ b/ext/node/polyfills/internal_binding/node_file.ts @@ -0,0 +1,84 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. +// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. + +// This module ports: +// - https://github.com/nodejs/node/blob/master/src/node_file-inl.h +// - https://github.com/nodejs/node/blob/master/src/node_file.cc +// - https://github.com/nodejs/node/blob/master/src/node_file.h + +import { assert } from "internal:deno_node/polyfills/_util/asserts.ts"; + +/** + * Write to the given file from the given buffer synchronously. + * + * Implements sync part of WriteBuffer in src/node_file.cc + * See: https://github.com/nodejs/node/blob/e9ed113/src/node_file.cc#L1818 + * + * @param fs file descriptor + * @param buffer the data to write + * @param offset where in the buffer to start from + * @param length how much to write + * @param position if integer, position to write at in the file. if null, write from the current position + * @param context context object for passing error number + */ +export function writeBuffer( + fd: number, + buffer: Uint8Array, + offset: number, + length: number, + position: number | null, + ctx: { errno?: number }, +) { + assert(offset >= 0, "offset should be greater or equal to 0"); + assert( + offset + length <= buffer.byteLength, + `buffer doesn't have enough data: byteLength = ${buffer.byteLength}, offset + length = ${ + offset + + length + }`, + ); + + if (position) { + Deno.seekSync(fd, position, Deno.SeekMode.Current); + } + + const subarray = buffer.subarray(offset, offset + length); + + try { + return Deno.writeSync(fd, subarray); + } catch (e) { + ctx.errno = extractOsErrorNumberFromErrorMessage(e); + return 0; + } +} + +function extractOsErrorNumberFromErrorMessage(e: unknown): number { + const match = e instanceof Error + ? e.message.match(/\(os error (\d+)\)/) + : false; + + if (match) { + return +match[1]; + } + + return 255; // Unknown error +} diff --git a/ext/node/polyfills/internal_binding/node_options.ts b/ext/node/polyfills/internal_binding/node_options.ts new file mode 100644 index 00000000000000..6109cf6db32a65 --- /dev/null +++ b/ext/node/polyfills/internal_binding/node_options.ts @@ -0,0 +1,39 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. +// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. + +// This module ports: +// - https://github.com/nodejs/node/blob/master/src/node_options-inl.h +// - https://github.com/nodejs/node/blob/master/src/node_options.cc +// - https://github.com/nodejs/node/blob/master/src/node_options.h + +export function getOptions() { + // TODO(kt3k): Return option arguments as parsed object + return { options: new Map() }; + + // const { Deno } = globalThis as any; + // const args = parse(Deno?.args ?? []); + // const options = new Map( + // Object.entries(args).map(([key, value]) => [key, { value }]), + // ); + // + // return { options }; +} diff --git a/ext/node/polyfills/internal_binding/options.ts b/ext/node/polyfills/internal_binding/options.ts new file mode 100644 index 00000000000000..93e2042b0d4343 --- /dev/null +++ b/ext/node/polyfills/internal_binding/options.ts @@ -0,0 +1,3 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. + +export {}; diff --git a/ext/node/polyfills/internal_binding/os.ts b/ext/node/polyfills/internal_binding/os.ts new file mode 100644 index 00000000000000..93e2042b0d4343 --- /dev/null +++ b/ext/node/polyfills/internal_binding/os.ts @@ -0,0 +1,3 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. + +export {}; diff --git a/ext/node/polyfills/internal_binding/performance.ts b/ext/node/polyfills/internal_binding/performance.ts new file mode 100644 index 00000000000000..93e2042b0d4343 --- /dev/null +++ b/ext/node/polyfills/internal_binding/performance.ts @@ -0,0 +1,3 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. + +export {}; diff --git a/ext/node/polyfills/internal_binding/pipe_wrap.ts b/ext/node/polyfills/internal_binding/pipe_wrap.ts new file mode 100644 index 00000000000000..1e0d551a4801c9 --- /dev/null +++ b/ext/node/polyfills/internal_binding/pipe_wrap.ts @@ -0,0 +1,397 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. +// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. + +// This module ports: +// - https://github.com/nodejs/node/blob/master/src/pipe_wrap.cc +// - https://github.com/nodejs/node/blob/master/src/pipe_wrap.h + +import { notImplemented } from "internal:deno_node/polyfills/_utils.ts"; +import { unreachable } from "internal:deno_node/polyfills/_util/asserts.ts"; +import { ConnectionWrap } from "internal:deno_node/polyfills/internal_binding/connection_wrap.ts"; +import { + AsyncWrap, + providerType, +} from "internal:deno_node/polyfills/internal_binding/async_wrap.ts"; +import { LibuvStreamWrap } from "internal:deno_node/polyfills/internal_binding/stream_wrap.ts"; +import { codeMap } from "internal:deno_node/polyfills/internal_binding/uv.ts"; +import { delay } from "internal:deno_node/polyfills/_util/async.ts"; +import { kStreamBaseField } from "internal:deno_node/polyfills/internal_binding/stream_wrap.ts"; +import { + ceilPowOf2, + INITIAL_ACCEPT_BACKOFF_DELAY, + MAX_ACCEPT_BACKOFF_DELAY, +} from "internal:deno_node/polyfills/internal_binding/_listen.ts"; +import { isWindows } from "internal:deno_node/polyfills/_util/os.ts"; +import { fs } from "internal:deno_node/polyfills/internal_binding/constants.ts"; + +export enum socketType { + SOCKET, + SERVER, + IPC, +} + +export class Pipe extends ConnectionWrap { + override reading = false; + ipc: boolean; + + // REF: https://github.com/nodejs/node/blob/master/deps/uv/src/win/pipe.c#L48 + #pendingInstances = 4; + + #address?: string; + + #backlog?: number; + #listener!: Deno.Listener; + #connections = 0; + + #closed = false; + #acceptBackoffDelay?: number; + + constructor(type: number, conn?: Deno.UnixConn) { + let provider: providerType; + let ipc: boolean; + + switch (type) { + case socketType.SOCKET: { + provider = providerType.PIPEWRAP; + ipc = false; + + break; + } + case socketType.SERVER: { + provider = providerType.PIPESERVERWRAP; + ipc = false; + + break; + } + case socketType.IPC: { + provider = providerType.PIPEWRAP; + ipc = true; + + break; + } + default: { + unreachable(); + } + } + + super(provider, conn); + + this.ipc = ipc; + + if (conn && provider === providerType.PIPEWRAP) { + const localAddr = conn.localAddr as Deno.UnixAddr; + this.#address = localAddr.path; + } + } + + open(_fd: number): number { + // REF: https://github.com/denoland/deno/issues/6529 + notImplemented("Pipe.prototype.open"); + } + + /** + * Bind to a Unix domain or Windows named pipe. + * @param name Unix domain or Windows named pipe the server should listen to. + * @return An error status code. + */ + bind(name: string) { + // Deno doesn't currently separate bind from connect. For now we noop under + // the assumption we will connect shortly. + // REF: https://doc.deno.land/deno/unstable/~/Deno.connect + + this.#address = name; + + return 0; + } + + /** + * Connect to a Unix domain or Windows named pipe. + * @param req A PipeConnectWrap instance. + * @param address Unix domain or Windows named pipe the server should connect to. + * @return An error status code. + */ + connect(req: PipeConnectWrap, address: string) { + if (isWindows) { + // REF: https://github.com/denoland/deno/issues/10244 + notImplemented("Pipe.prototype.connect - Windows"); + } + + const connectOptions: Deno.UnixConnectOptions = { + path: address, + transport: "unix", + }; + + Deno.connect(connectOptions).then( + (conn: Deno.UnixConn) => { + const localAddr = conn.localAddr as Deno.UnixAddr; + + this.#address = req.address = localAddr.path; + this[kStreamBaseField] = conn; + + try { + this.afterConnect(req, 0); + } catch { + // swallow callback errors. + } + }, + (e) => { + // TODO(cmorten): correct mapping of connection error to status code. + let code: number; + + if (e instanceof Deno.errors.NotFound) { + code = codeMap.get("ENOENT")!; + } else if (e instanceof Deno.errors.PermissionDenied) { + code = codeMap.get("EACCES")!; + } else { + code = codeMap.get("ECONNREFUSED")!; + } + + try { + this.afterConnect(req, code); + } catch { + // swallow callback errors. + } + }, + ); + + return 0; + } + + /** + * Listen for new connections. + * @param backlog The maximum length of the queue of pending connections. + * @return An error status code. + */ + listen(backlog: number): number { + if (isWindows) { + // REF: https://github.com/denoland/deno/issues/10244 + notImplemented("Pipe.prototype.listen - Windows"); + } + + this.#backlog = isWindows + ? this.#pendingInstances + : ceilPowOf2(backlog + 1); + + const listenOptions = { + path: this.#address!, + transport: "unix" as const, + }; + + let listener; + + try { + listener = Deno.listen(listenOptions); + } catch (e) { + if (e instanceof Deno.errors.AddrInUse) { + return codeMap.get("EADDRINUSE")!; + } else if (e instanceof Deno.errors.AddrNotAvailable) { + return codeMap.get("EADDRNOTAVAIL")!; + } else if (e instanceof Deno.errors.PermissionDenied) { + throw e; + } + + // TODO(cmorten): map errors to appropriate error codes. + return codeMap.get("UNKNOWN")!; + } + + const address = listener.addr as Deno.UnixAddr; + this.#address = address.path; + + this.#listener = listener; + this.#accept(); + + return 0; + } + + override ref() { + if (this.#listener) { + this.#listener.ref(); + } + } + + override unref() { + if (this.#listener) { + this.#listener.unref(); + } + } + + /** + * Set the number of pending pipe instance handles when the pipe server is + * waiting for connections. This setting applies to Windows only. + * @param instances Number of pending pipe instances. + */ + setPendingInstances(instances: number) { + this.#pendingInstances = instances; + } + + /** + * Alters pipe permissions, allowing it to be accessed from processes run by + * different users. Makes the pipe writable or readable by all users. Mode + * can be `UV_WRITABLE`, `UV_READABLE` or `UV_WRITABLE | UV_READABLE`. This + * function is blocking. + * @param mode Pipe permissions mode. + * @return An error status code. + */ + fchmod(mode: number) { + if ( + mode != constants.UV_READABLE && + mode != constants.UV_WRITABLE && + mode != (constants.UV_WRITABLE | constants.UV_READABLE) + ) { + return codeMap.get("EINVAL"); + } + + let desiredMode = 0; + + if (mode & constants.UV_READABLE) { + desiredMode |= fs.S_IRUSR | fs.S_IRGRP | fs.S_IROTH; + } + if (mode & constants.UV_WRITABLE) { + desiredMode |= fs.S_IWUSR | fs.S_IWGRP | fs.S_IWOTH; + } + + // TODO(cmorten): this will incorrectly throw on Windows + // REF: https://github.com/denoland/deno/issues/4357 + try { + Deno.chmodSync(this.#address!, desiredMode); + } catch { + // TODO(cmorten): map errors to appropriate error codes. + return codeMap.get("UNKNOWN")!; + } + + return 0; + } + + /** Handle backoff delays following an unsuccessful accept. */ + async #acceptBackoff() { + // Backoff after transient errors to allow time for the system to + // recover, and avoid blocking up the event loop with a continuously + // running loop. + if (!this.#acceptBackoffDelay) { + this.#acceptBackoffDelay = INITIAL_ACCEPT_BACKOFF_DELAY; + } else { + this.#acceptBackoffDelay *= 2; + } + + if (this.#acceptBackoffDelay >= MAX_ACCEPT_BACKOFF_DELAY) { + this.#acceptBackoffDelay = MAX_ACCEPT_BACKOFF_DELAY; + } + + await delay(this.#acceptBackoffDelay); + + this.#accept(); + } + + /** Accept new connections. */ + async #accept(): Promise { + if (this.#closed) { + return; + } + + if (this.#connections > this.#backlog!) { + this.#acceptBackoff(); + + return; + } + + let connection: Deno.Conn; + + try { + connection = await this.#listener.accept(); + } catch (e) { + if (e instanceof Deno.errors.BadResource && this.#closed) { + // Listener and server has closed. + return; + } + + try { + // TODO(cmorten): map errors to appropriate error codes. + this.onconnection!(codeMap.get("UNKNOWN")!, undefined); + } catch { + // swallow callback errors. + } + + this.#acceptBackoff(); + + return; + } + + // Reset the backoff delay upon successful accept. + this.#acceptBackoffDelay = undefined; + + const connectionHandle = new Pipe(socketType.SOCKET, connection); + this.#connections++; + + try { + this.onconnection!(0, connectionHandle); + } catch { + // swallow callback errors. + } + + return this.#accept(); + } + + /** Handle server closure. */ + override _onClose(): number { + this.#closed = true; + this.reading = false; + + this.#address = undefined; + + this.#backlog = undefined; + this.#connections = 0; + this.#acceptBackoffDelay = undefined; + + if (this.provider === providerType.PIPESERVERWRAP) { + try { + this.#listener.close(); + } catch { + // listener already closed + } + } + + return LibuvStreamWrap.prototype._onClose.call(this); + } +} + +export class PipeConnectWrap extends AsyncWrap { + oncomplete!: ( + status: number, + handle: ConnectionWrap, + req: PipeConnectWrap, + readable: boolean, + writeable: boolean, + ) => void; + address!: string; + + constructor() { + super(providerType.PIPECONNECTWRAP); + } +} + +export enum constants { + SOCKET = socketType.SOCKET, + SERVER = socketType.SERVER, + IPC = socketType.IPC, + UV_READABLE = 1, + UV_WRITABLE = 2, +} diff --git a/ext/node/polyfills/internal_binding/process_methods.ts b/ext/node/polyfills/internal_binding/process_methods.ts new file mode 100644 index 00000000000000..93e2042b0d4343 --- /dev/null +++ b/ext/node/polyfills/internal_binding/process_methods.ts @@ -0,0 +1,3 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. + +export {}; diff --git a/ext/node/polyfills/internal_binding/report.ts b/ext/node/polyfills/internal_binding/report.ts new file mode 100644 index 00000000000000..93e2042b0d4343 --- /dev/null +++ b/ext/node/polyfills/internal_binding/report.ts @@ -0,0 +1,3 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. + +export {}; diff --git a/ext/node/polyfills/internal_binding/serdes.ts b/ext/node/polyfills/internal_binding/serdes.ts new file mode 100644 index 00000000000000..93e2042b0d4343 --- /dev/null +++ b/ext/node/polyfills/internal_binding/serdes.ts @@ -0,0 +1,3 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. + +export {}; diff --git a/ext/node/polyfills/internal_binding/signal_wrap.ts b/ext/node/polyfills/internal_binding/signal_wrap.ts new file mode 100644 index 00000000000000..93e2042b0d4343 --- /dev/null +++ b/ext/node/polyfills/internal_binding/signal_wrap.ts @@ -0,0 +1,3 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. + +export {}; diff --git a/ext/node/polyfills/internal_binding/spawn_sync.ts b/ext/node/polyfills/internal_binding/spawn_sync.ts new file mode 100644 index 00000000000000..93e2042b0d4343 --- /dev/null +++ b/ext/node/polyfills/internal_binding/spawn_sync.ts @@ -0,0 +1,3 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. + +export {}; diff --git a/ext/node/polyfills/internal_binding/stream_wrap.ts b/ext/node/polyfills/internal_binding/stream_wrap.ts new file mode 100644 index 00000000000000..3aee3b9daf2c23 --- /dev/null +++ b/ext/node/polyfills/internal_binding/stream_wrap.ts @@ -0,0 +1,374 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. +// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. + +// This module ports: +// - https://github.com/nodejs/node/blob/master/src/stream_base-inl.h +// - https://github.com/nodejs/node/blob/master/src/stream_base.h +// - https://github.com/nodejs/node/blob/master/src/stream_base.cc +// - https://github.com/nodejs/node/blob/master/src/stream_wrap.h +// - https://github.com/nodejs/node/blob/master/src/stream_wrap.cc + +import { TextEncoder } from "internal:deno_web/08_text_encoding.js"; +import { Buffer } from "internal:deno_node/polyfills/buffer.ts"; +import { notImplemented } from "internal:deno_node/polyfills/_utils.ts"; +import { HandleWrap } from "internal:deno_node/polyfills/internal_binding/handle_wrap.ts"; +import { + AsyncWrap, + providerType, +} from "internal:deno_node/polyfills/internal_binding/async_wrap.ts"; +import { codeMap } from "internal:deno_node/polyfills/internal_binding/uv.ts"; + +interface Reader { + read(p: Uint8Array): Promise; +} + +interface Writer { + write(p: Uint8Array): Promise; +} + +export interface Closer { + close(): void; +} + +type Ref = { ref(): void; unref(): void }; + +enum StreamBaseStateFields { + kReadBytesOrError, + kArrayBufferOffset, + kBytesWritten, + kLastWriteWasAsync, + kNumStreamBaseStateFields, +} + +export const kReadBytesOrError = StreamBaseStateFields.kReadBytesOrError; +export const kArrayBufferOffset = StreamBaseStateFields.kArrayBufferOffset; +export const kBytesWritten = StreamBaseStateFields.kBytesWritten; +export const kLastWriteWasAsync = StreamBaseStateFields.kLastWriteWasAsync; +export const kNumStreamBaseStateFields = + StreamBaseStateFields.kNumStreamBaseStateFields; + +export const streamBaseState = new Uint8Array(5); + +// This is Deno, it always will be async. +streamBaseState[kLastWriteWasAsync] = 1; + +export class WriteWrap extends AsyncWrap { + handle!: H; + oncomplete!: (status: number) => void; + async!: boolean; + bytes!: number; + buffer!: unknown; + callback!: unknown; + _chunks!: unknown[]; + + constructor() { + super(providerType.WRITEWRAP); + } +} + +export class ShutdownWrap extends AsyncWrap { + handle!: H; + oncomplete!: (status: number) => void; + callback!: () => void; + + constructor() { + super(providerType.SHUTDOWNWRAP); + } +} + +export const kStreamBaseField = Symbol("kStreamBaseField"); + +const SUGGESTED_SIZE = 64 * 1024; + +export class LibuvStreamWrap extends HandleWrap { + [kStreamBaseField]?: Reader & Writer & Closer & Ref; + + reading!: boolean; + #reading = false; + destroyed = false; + writeQueueSize = 0; + bytesRead = 0; + bytesWritten = 0; + + onread!: (_arrayBuffer: Uint8Array, _nread: number) => Uint8Array | undefined; + + constructor( + provider: providerType, + stream?: Reader & Writer & Closer & Ref, + ) { + super(provider); + this.#attachToObject(stream); + } + + /** + * Start the reading of the stream. + * @return An error status code. + */ + readStart(): number { + if (!this.#reading) { + this.#reading = true; + this.#read(); + } + + return 0; + } + + /** + * Stop the reading of the stream. + * @return An error status code. + */ + readStop(): number { + this.#reading = false; + + return 0; + } + + /** + * Shutdown the stream. + * @param req A shutdown request wrapper. + * @return An error status code. + */ + shutdown(req: ShutdownWrap): number { + const status = this._onClose(); + + try { + req.oncomplete(status); + } catch { + // swallow callback error. + } + + return 0; + } + + /** + * @param userBuf + * @return An error status code. + */ + useUserBuffer(_userBuf: unknown): number { + // TODO(cmorten) + notImplemented("LibuvStreamWrap.prototype.useUserBuffer"); + } + + /** + * Write a buffer to the stream. + * @param req A write request wrapper. + * @param data The Uint8Array buffer to write to the stream. + * @return An error status code. + */ + writeBuffer(req: WriteWrap, data: Uint8Array): number { + this.#write(req, data); + + return 0; + } + + /** + * Write multiple chunks at once. + * @param req A write request wrapper. + * @param chunks + * @param allBuffers + * @return An error status code. + */ + writev( + req: WriteWrap, + chunks: Buffer[] | (string | Buffer)[], + allBuffers: boolean, + ): number { + const count = allBuffers ? chunks.length : chunks.length >> 1; + const buffers: Buffer[] = new Array(count); + + if (!allBuffers) { + for (let i = 0; i < count; i++) { + const chunk = chunks[i * 2]; + + if (Buffer.isBuffer(chunk)) { + buffers[i] = chunk; + } + + // String chunk + const encoding: string = chunks[i * 2 + 1] as string; + buffers[i] = Buffer.from(chunk as string, encoding); + } + } else { + for (let i = 0; i < count; i++) { + buffers[i] = chunks[i] as Buffer; + } + } + + return this.writeBuffer(req, Buffer.concat(buffers)); + } + + /** + * Write an ASCII string to the stream. + * @return An error status code. + */ + writeAsciiString(req: WriteWrap, data: string): number { + const buffer = new TextEncoder().encode(data); + + return this.writeBuffer(req, buffer); + } + + /** + * Write an UTF8 string to the stream. + * @return An error status code. + */ + writeUtf8String(req: WriteWrap, data: string): number { + const buffer = new TextEncoder().encode(data); + + return this.writeBuffer(req, buffer); + } + + /** + * Write an UCS2 string to the stream. + * @return An error status code. + */ + writeUcs2String(_req: WriteWrap, _data: string): number { + notImplemented("LibuvStreamWrap.prototype.writeUcs2String"); + } + + /** + * Write an LATIN1 string to the stream. + * @return An error status code. + */ + writeLatin1String(req: WriteWrap, data: string): number { + const buffer = Buffer.from(data, "latin1"); + return this.writeBuffer(req, buffer); + } + + override _onClose(): number { + let status = 0; + this.#reading = false; + + try { + this[kStreamBaseField]?.close(); + } catch { + status = codeMap.get("ENOTCONN")!; + } + + return status; + } + + /** + * Attaches the class to the underlying stream. + * @param stream The stream to attach to. + */ + #attachToObject(stream?: Reader & Writer & Closer & Ref) { + this[kStreamBaseField] = stream; + } + + /** Internal method for reading from the attached stream. */ + async #read() { + let buf = new Uint8Array(SUGGESTED_SIZE); + + let nread: number | null; + try { + nread = await this[kStreamBaseField]!.read(buf); + } catch (e) { + if ( + e instanceof Deno.errors.Interrupted || + e instanceof Deno.errors.BadResource + ) { + nread = codeMap.get("EOF")!; + } else if ( + e instanceof Deno.errors.ConnectionReset || + e instanceof Deno.errors.ConnectionAborted + ) { + nread = codeMap.get("ECONNRESET")!; + } else { + nread = codeMap.get("UNKNOWN")!; + } + + buf = new Uint8Array(0); + } + + nread ??= codeMap.get("EOF")!; + + streamBaseState[kReadBytesOrError] = nread; + + if (nread > 0) { + this.bytesRead += nread; + } + + buf = buf.slice(0, nread); + + streamBaseState[kArrayBufferOffset] = 0; + + try { + this.onread!(buf, nread); + } catch { + // swallow callback errors. + } + + if (nread >= 0 && this.#reading) { + this.#read(); + } + } + + /** + * Internal method for writing to the attached stream. + * @param req A write request wrapper. + * @param data The Uint8Array buffer to write to the stream. + */ + async #write(req: WriteWrap, data: Uint8Array) { + const { byteLength } = data; + + try { + // TODO(crowlKats): duplicate from runtime/js/13_buffer.js + let nwritten = 0; + while (nwritten < data.length) { + nwritten += await this[kStreamBaseField]!.write( + data.subarray(nwritten), + ); + } + } catch (e) { + let status: number; + + // TODO(cmorten): map err to status codes + if ( + e instanceof Deno.errors.BadResource || + e instanceof Deno.errors.BrokenPipe + ) { + status = codeMap.get("EBADF")!; + } else { + status = codeMap.get("UNKNOWN")!; + } + + try { + req.oncomplete(status); + } catch { + // swallow callback errors. + } + + return; + } + + streamBaseState[kBytesWritten] = byteLength; + this.bytesWritten += byteLength; + + try { + req.oncomplete(0); + } catch { + // swallow callback errors. + } + + return; + } +} diff --git a/ext/node/polyfills/internal_binding/string_decoder.ts b/ext/node/polyfills/internal_binding/string_decoder.ts new file mode 100644 index 00000000000000..3df230aee605d0 --- /dev/null +++ b/ext/node/polyfills/internal_binding/string_decoder.ts @@ -0,0 +1,15 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. +import { Encodings } from "internal:deno_node/polyfills/internal_binding/_node.ts"; + +const encodings = []; +encodings[Encodings.ASCII] = "ascii"; +encodings[Encodings.BASE64] = "base64"; +encodings[Encodings.BASE64URL] = "base64url"; +encodings[Encodings.BUFFER] = "buffer"; +encodings[Encodings.HEX] = "hex"; +encodings[Encodings.LATIN1] = "latin1"; +encodings[Encodings.UCS2] = "utf16le"; +encodings[Encodings.UTF8] = "utf8"; + +export default { encodings }; +export { encodings }; diff --git a/ext/node/polyfills/internal_binding/symbols.ts b/ext/node/polyfills/internal_binding/symbols.ts new file mode 100644 index 00000000000000..affa86fbe6ecc8 --- /dev/null +++ b/ext/node/polyfills/internal_binding/symbols.ts @@ -0,0 +1,27 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. +// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. + +// This module ports: +// - https://github.com/nodejs/node/blob/master/src/node_symbols.cc + +export const asyncIdSymbol: unique symbol = Symbol("asyncIdSymbol"); +export const ownerSymbol: unique symbol = Symbol("ownerSymbol"); diff --git a/ext/node/polyfills/internal_binding/task_queue.ts b/ext/node/polyfills/internal_binding/task_queue.ts new file mode 100644 index 00000000000000..93e2042b0d4343 --- /dev/null +++ b/ext/node/polyfills/internal_binding/task_queue.ts @@ -0,0 +1,3 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. + +export {}; diff --git a/ext/node/polyfills/internal_binding/tcp_wrap.ts b/ext/node/polyfills/internal_binding/tcp_wrap.ts new file mode 100644 index 00000000000000..d0da3e10c940c4 --- /dev/null +++ b/ext/node/polyfills/internal_binding/tcp_wrap.ts @@ -0,0 +1,493 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. +// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. + +// This module ports: +// - https://github.com/nodejs/node/blob/master/src/tcp_wrap.cc +// - https://github.com/nodejs/node/blob/master/src/tcp_wrap.h + +import { notImplemented } from "internal:deno_node/polyfills/_utils.ts"; +import { unreachable } from "internal:deno_node/polyfills/_util/asserts.ts"; +import { ConnectionWrap } from "internal:deno_node/polyfills/internal_binding/connection_wrap.ts"; +import { + AsyncWrap, + providerType, +} from "internal:deno_node/polyfills/internal_binding/async_wrap.ts"; +import { LibuvStreamWrap } from "internal:deno_node/polyfills/internal_binding/stream_wrap.ts"; +import { ownerSymbol } from "internal:deno_node/polyfills/internal_binding/symbols.ts"; +import { codeMap } from "internal:deno_node/polyfills/internal_binding/uv.ts"; +import { delay } from "internal:deno_node/polyfills/_util/async.ts"; +import { kStreamBaseField } from "internal:deno_node/polyfills/internal_binding/stream_wrap.ts"; +import { isIP } from "internal:deno_node/polyfills/internal/net.ts"; +import { + ceilPowOf2, + INITIAL_ACCEPT_BACKOFF_DELAY, + MAX_ACCEPT_BACKOFF_DELAY, +} from "internal:deno_node/polyfills/internal_binding/_listen.ts"; + +/** The type of TCP socket. */ +enum socketType { + SOCKET, + SERVER, +} + +interface AddressInfo { + address: string; + family?: number; + port: number; +} + +export class TCPConnectWrap extends AsyncWrap { + oncomplete!: ( + status: number, + handle: ConnectionWrap, + req: TCPConnectWrap, + readable: boolean, + writeable: boolean, + ) => void; + address!: string; + port!: number; + localAddress!: string; + localPort!: number; + + constructor() { + super(providerType.TCPCONNECTWRAP); + } +} + +export enum constants { + SOCKET = socketType.SOCKET, + SERVER = socketType.SERVER, + UV_TCP_IPV6ONLY, +} + +export class TCP extends ConnectionWrap { + [ownerSymbol]: unknown = null; + override reading = false; + + #address?: string; + #port?: number; + + #remoteAddress?: string; + #remoteFamily?: number; + #remotePort?: number; + + #backlog?: number; + #listener!: Deno.Listener; + #connections = 0; + + #closed = false; + #acceptBackoffDelay?: number; + + /** + * Creates a new TCP class instance. + * @param type The socket type. + * @param conn Optional connection object to wrap. + */ + constructor(type: number, conn?: Deno.Conn) { + let provider: providerType; + + switch (type) { + case socketType.SOCKET: { + provider = providerType.TCPWRAP; + + break; + } + case socketType.SERVER: { + provider = providerType.TCPSERVERWRAP; + + break; + } + default: { + unreachable(); + } + } + + super(provider, conn); + + // TODO(cmorten): the handling of new connections and construction feels + // a little off. Suspect duplicating in some fashion. + if (conn && provider === providerType.TCPWRAP) { + const localAddr = conn.localAddr as Deno.NetAddr; + this.#address = localAddr.hostname; + this.#port = localAddr.port; + + const remoteAddr = conn.remoteAddr as Deno.NetAddr; + this.#remoteAddress = remoteAddr.hostname; + this.#remotePort = remoteAddr.port; + this.#remoteFamily = isIP(remoteAddr.hostname); + } + } + + /** + * Opens a file descriptor. + * @param fd The file descriptor to open. + * @return An error status code. + */ + open(_fd: number): number { + // REF: https://github.com/denoland/deno/issues/6529 + notImplemented("TCP.prototype.open"); + } + + /** + * Bind to an IPv4 address. + * @param address The hostname to bind to. + * @param port The port to bind to + * @return An error status code. + */ + bind(address: string, port: number): number { + return this.#bind(address, port, 0); + } + + /** + * Bind to an IPv6 address. + * @param address The hostname to bind to. + * @param port The port to bind to + * @return An error status code. + */ + bind6(address: string, port: number, flags: number): number { + return this.#bind(address, port, flags); + } + + /** + * Connect to an IPv4 address. + * @param req A TCPConnectWrap instance. + * @param address The hostname to connect to. + * @param port The port to connect to. + * @return An error status code. + */ + connect(req: TCPConnectWrap, address: string, port: number): number { + return this.#connect(req, address, port); + } + + /** + * Connect to an IPv6 address. + * @param req A TCPConnectWrap instance. + * @param address The hostname to connect to. + * @param port The port to connect to. + * @return An error status code. + */ + connect6(req: TCPConnectWrap, address: string, port: number): number { + return this.#connect(req, address, port); + } + + /** + * Listen for new connections. + * @param backlog The maximum length of the queue of pending connections. + * @return An error status code. + */ + listen(backlog: number): number { + this.#backlog = ceilPowOf2(backlog + 1); + + const listenOptions = { + hostname: this.#address!, + port: this.#port!, + transport: "tcp" as const, + }; + + let listener; + + try { + listener = Deno.listen(listenOptions); + } catch (e) { + if (e instanceof Deno.errors.AddrInUse) { + return codeMap.get("EADDRINUSE")!; + } else if (e instanceof Deno.errors.AddrNotAvailable) { + return codeMap.get("EADDRNOTAVAIL")!; + } else if (e instanceof Deno.errors.PermissionDenied) { + throw e; + } + + // TODO(cmorten): map errors to appropriate error codes. + return codeMap.get("UNKNOWN")!; + } + + const address = listener.addr as Deno.NetAddr; + this.#address = address.hostname; + this.#port = address.port; + + this.#listener = listener; + this.#accept(); + + return 0; + } + + override ref() { + if (this.#listener) { + this.#listener.ref(); + } + + if (this[kStreamBaseField]) { + this[kStreamBaseField].ref(); + } + } + + override unref() { + if (this.#listener) { + this.#listener.unref(); + } + + if (this[kStreamBaseField]) { + this[kStreamBaseField].unref(); + } + } + + /** + * Populates the provided object with local address entries. + * @param sockname An object to add the local address entries to. + * @return An error status code. + */ + getsockname(sockname: Record | AddressInfo): number { + if ( + typeof this.#address === "undefined" || + typeof this.#port === "undefined" + ) { + return codeMap.get("EADDRNOTAVAIL")!; + } + + sockname.address = this.#address; + sockname.port = this.#port; + sockname.family = isIP(this.#address); + + return 0; + } + + /** + * Populates the provided object with remote address entries. + * @param peername An object to add the remote address entries to. + * @return An error status code. + */ + getpeername(peername: Record | AddressInfo): number { + if ( + typeof this.#remoteAddress === "undefined" || + typeof this.#remotePort === "undefined" + ) { + return codeMap.get("EADDRNOTAVAIL")!; + } + + peername.address = this.#remoteAddress; + peername.port = this.#remotePort; + peername.family = this.#remoteFamily; + + return 0; + } + + /** + * @param noDelay + * @return An error status code. + */ + setNoDelay(_noDelay: boolean): number { + // TODO(bnoordhuis) https://github.com/denoland/deno/pull/13103 + return 0; + } + + /** + * @param enable + * @param initialDelay + * @return An error status code. + */ + setKeepAlive(_enable: boolean, _initialDelay: number): number { + // TODO(bnoordhuis) https://github.com/denoland/deno/pull/13103 + return 0; + } + + /** + * Windows only. + * + * Deprecated by Node. + * REF: https://github.com/nodejs/node/blob/master/lib/net.js#L1731 + * + * @param enable + * @return An error status code. + * @deprecated + */ + setSimultaneousAccepts(_enable: boolean) { + // Low priority to implement owing to it being deprecated in Node. + notImplemented("TCP.prototype.setSimultaneousAccepts"); + } + + /** + * Bind to an IPv4 or IPv6 address. + * @param address The hostname to bind to. + * @param port The port to bind to + * @param _flags + * @return An error status code. + */ + #bind(address: string, port: number, _flags: number): number { + // Deno doesn't currently separate bind from connect etc. + // REF: + // - https://doc.deno.land/deno/stable/~/Deno.connect + // - https://doc.deno.land/deno/stable/~/Deno.listen + // + // This also means we won't be connecting from the specified local address + // and port as providing these is not an option in Deno. + // REF: + // - https://doc.deno.land/deno/stable/~/Deno.ConnectOptions + // - https://doc.deno.land/deno/stable/~/Deno.ListenOptions + + this.#address = address; + this.#port = port; + + return 0; + } + + /** + * Connect to an IPv4 or IPv6 address. + * @param req A TCPConnectWrap instance. + * @param address The hostname to connect to. + * @param port The port to connect to. + * @return An error status code. + */ + #connect(req: TCPConnectWrap, address: string, port: number): number { + this.#remoteAddress = address; + this.#remotePort = port; + this.#remoteFamily = isIP(address); + + const connectOptions: Deno.ConnectOptions = { + hostname: address, + port, + transport: "tcp", + }; + + Deno.connect(connectOptions).then( + (conn: Deno.Conn) => { + // Incorrect / backwards, but correcting the local address and port with + // what was actually used given we can't actually specify these in Deno. + const localAddr = conn.localAddr as Deno.NetAddr; + this.#address = req.localAddress = localAddr.hostname; + this.#port = req.localPort = localAddr.port; + this[kStreamBaseField] = conn; + + try { + this.afterConnect(req, 0); + } catch { + // swallow callback errors. + } + }, + () => { + try { + // TODO(cmorten): correct mapping of connection error to status code. + this.afterConnect(req, codeMap.get("ECONNREFUSED")!); + } catch { + // swallow callback errors. + } + }, + ); + + return 0; + } + + /** Handle backoff delays following an unsuccessful accept. */ + async #acceptBackoff() { + // Backoff after transient errors to allow time for the system to + // recover, and avoid blocking up the event loop with a continuously + // running loop. + if (!this.#acceptBackoffDelay) { + this.#acceptBackoffDelay = INITIAL_ACCEPT_BACKOFF_DELAY; + } else { + this.#acceptBackoffDelay *= 2; + } + + if (this.#acceptBackoffDelay >= MAX_ACCEPT_BACKOFF_DELAY) { + this.#acceptBackoffDelay = MAX_ACCEPT_BACKOFF_DELAY; + } + + await delay(this.#acceptBackoffDelay); + + this.#accept(); + } + + /** Accept new connections. */ + async #accept(): Promise { + if (this.#closed) { + return; + } + + if (this.#connections > this.#backlog!) { + this.#acceptBackoff(); + + return; + } + + let connection: Deno.Conn; + + try { + connection = await this.#listener.accept(); + } catch (e) { + if (e instanceof Deno.errors.BadResource && this.#closed) { + // Listener and server has closed. + return; + } + + try { + // TODO(cmorten): map errors to appropriate error codes. + this.onconnection!(codeMap.get("UNKNOWN")!, undefined); + } catch { + // swallow callback errors. + } + + this.#acceptBackoff(); + + return; + } + + // Reset the backoff delay upon successful accept. + this.#acceptBackoffDelay = undefined; + + const connectionHandle = new TCP(socketType.SOCKET, connection); + this.#connections++; + + try { + this.onconnection!(0, connectionHandle); + } catch { + // swallow callback errors. + } + + return this.#accept(); + } + + /** Handle server closure. */ + override _onClose(): number { + this.#closed = true; + this.reading = false; + + this.#address = undefined; + this.#port = undefined; + + this.#remoteAddress = undefined; + this.#remoteFamily = undefined; + this.#remotePort = undefined; + + this.#backlog = undefined; + this.#connections = 0; + this.#acceptBackoffDelay = undefined; + + if (this.provider === providerType.TCPSERVERWRAP) { + try { + this.#listener.close(); + } catch { + // listener already closed + } + } + + return LibuvStreamWrap.prototype._onClose.call(this); + } +} diff --git a/ext/node/polyfills/internal_binding/timers.ts b/ext/node/polyfills/internal_binding/timers.ts new file mode 100644 index 00000000000000..93e2042b0d4343 --- /dev/null +++ b/ext/node/polyfills/internal_binding/timers.ts @@ -0,0 +1,3 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. + +export {}; diff --git a/ext/node/polyfills/internal_binding/tls_wrap.ts b/ext/node/polyfills/internal_binding/tls_wrap.ts new file mode 100644 index 00000000000000..93e2042b0d4343 --- /dev/null +++ b/ext/node/polyfills/internal_binding/tls_wrap.ts @@ -0,0 +1,3 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. + +export {}; diff --git a/ext/node/polyfills/internal_binding/trace_events.ts b/ext/node/polyfills/internal_binding/trace_events.ts new file mode 100644 index 00000000000000..93e2042b0d4343 --- /dev/null +++ b/ext/node/polyfills/internal_binding/trace_events.ts @@ -0,0 +1,3 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. + +export {}; diff --git a/ext/node/polyfills/internal_binding/tty_wrap.ts b/ext/node/polyfills/internal_binding/tty_wrap.ts new file mode 100644 index 00000000000000..93e2042b0d4343 --- /dev/null +++ b/ext/node/polyfills/internal_binding/tty_wrap.ts @@ -0,0 +1,3 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. + +export {}; diff --git a/ext/node/polyfills/internal_binding/types.ts b/ext/node/polyfills/internal_binding/types.ts new file mode 100644 index 00000000000000..348d5553fd6623 --- /dev/null +++ b/ext/node/polyfills/internal_binding/types.ts @@ -0,0 +1,377 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. +// +// Adapted from Node.js. Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. + +import { core } from "internal:deno_node/polyfills/_core.ts"; + +// https://tc39.es/ecma262/#sec-object.prototype.tostring +const _toString = Object.prototype.toString; + +// https://tc39.es/ecma262/#sec-bigint.prototype.valueof +const _bigIntValueOf = BigInt.prototype.valueOf; + +// https://tc39.es/ecma262/#sec-boolean.prototype.valueof +const _booleanValueOf = Boolean.prototype.valueOf; + +// https://tc39.es/ecma262/#sec-date.prototype.valueof +const _dateValueOf = Date.prototype.valueOf; + +// https://tc39.es/ecma262/#sec-number.prototype.valueof +const _numberValueOf = Number.prototype.valueOf; + +// https://tc39.es/ecma262/#sec-string.prototype.valueof +const _stringValueOf = String.prototype.valueOf; + +// https://tc39.es/ecma262/#sec-symbol.prototype.valueof +const _symbolValueOf = Symbol.prototype.valueOf; + +// https://tc39.es/ecma262/#sec-weakmap.prototype.has +const _weakMapHas = WeakMap.prototype.has; + +// https://tc39.es/ecma262/#sec-weakset.prototype.has +const _weakSetHas = WeakSet.prototype.has; + +// https://tc39.es/ecma262/#sec-get-arraybuffer.prototype.bytelength +const _getArrayBufferByteLength = Object.getOwnPropertyDescriptor( + ArrayBuffer.prototype, + "byteLength", +)!.get!; + +// https://tc39.es/ecma262/#sec-get-sharedarraybuffer.prototype.bytelength +const _getSharedArrayBufferByteLength = globalThis.SharedArrayBuffer + ? Object.getOwnPropertyDescriptor( + SharedArrayBuffer.prototype, + "byteLength", + )!.get! + : undefined; + +// https://tc39.es/ecma262/#sec-get-%typedarray%.prototype-@@tostringtag +const _getTypedArrayToStringTag = Object.getOwnPropertyDescriptor( + Object.getPrototypeOf(Uint8Array).prototype, + Symbol.toStringTag, +)!.get!; + +// https://tc39.es/ecma262/#sec-get-set.prototype.size +const _getSetSize = Object.getOwnPropertyDescriptor( + Set.prototype, + "size", +)!.get!; + +// https://tc39.es/ecma262/#sec-get-map.prototype.size +const _getMapSize = Object.getOwnPropertyDescriptor( + Map.prototype, + "size", +)!.get!; + +function isObjectLike( + value: unknown, +): value is Record { + return value !== null && typeof value === "object"; +} + +export function isAnyArrayBuffer( + value: unknown, +): value is ArrayBuffer | SharedArrayBuffer { + return isArrayBuffer(value) || isSharedArrayBuffer(value); +} + +export function isArgumentsObject(value: unknown): value is IArguments { + return ( + isObjectLike(value) && + value[Symbol.toStringTag] === undefined && + _toString.call(value) === "[object Arguments]" + ); +} + +export function isArrayBuffer(value: unknown): value is ArrayBuffer { + try { + _getArrayBufferByteLength.call(value); + return true; + } catch { + return false; + } +} + +export function isAsyncFunction( + value: unknown, +): value is (...args: unknown[]) => Promise { + return ( + typeof value === "function" && + // @ts-ignore: function is a kind of object + value[Symbol.toStringTag] === "AsyncFunction" + ); +} + +// deno-lint-ignore ban-types +export function isBooleanObject(value: unknown): value is Boolean { + if (!isObjectLike(value)) { + return false; + } + + try { + _booleanValueOf.call(value); + return true; + } catch { + return false; + } +} + +export function isBoxedPrimitive( + value: unknown, + // deno-lint-ignore ban-types +): value is Boolean | String | Number | Symbol | BigInt { + return ( + isBooleanObject(value) || + isStringObject(value) || + isNumberObject(value) || + isSymbolObject(value) || + isBigIntObject(value) + ); +} + +export function isDataView(value: unknown): value is DataView { + return ( + ArrayBuffer.isView(value) && + _getTypedArrayToStringTag.call(value) === undefined + ); +} + +export function isDate(value: unknown): value is Date { + try { + _dateValueOf.call(value); + return true; + } catch { + return false; + } +} + +export function isGeneratorFunction( + value: unknown, +): value is GeneratorFunction { + return ( + typeof value === "function" && + // @ts-ignore: function is a kind of object + value[Symbol.toStringTag] === "GeneratorFunction" + ); +} + +export function isGeneratorObject(value: unknown): value is Generator { + return ( + isObjectLike(value) && + value[Symbol.toStringTag] === "Generator" + ); +} + +export function isMap(value: unknown): value is Map { + try { + _getMapSize.call(value); + return true; + } catch { + return false; + } +} + +export function isMapIterator( + value: unknown, +): value is IterableIterator<[unknown, unknown]> { + return ( + isObjectLike(value) && + value[Symbol.toStringTag] === "Map Iterator" + ); +} + +export function isModuleNamespaceObject( + value: unknown, +): value is Record { + return ( + isObjectLike(value) && + value[Symbol.toStringTag] === "Module" + ); +} + +export function isNativeError(value: unknown): value is Error { + return ( + isObjectLike(value) && + value[Symbol.toStringTag] === undefined && + _toString.call(value) === "[object Error]" + ); +} + +// deno-lint-ignore ban-types +export function isNumberObject(value: unknown): value is Number { + if (!isObjectLike(value)) { + return false; + } + + try { + _numberValueOf.call(value); + return true; + } catch { + return false; + } +} + +export function isBigIntObject(value: unknown): value is bigint { + if (!isObjectLike(value)) { + return false; + } + + try { + _bigIntValueOf.call(value); + return true; + } catch { + return false; + } +} + +export function isPromise(value: unknown): value is Promise { + return ( + isObjectLike(value) && + value[Symbol.toStringTag] === "Promise" + ); +} + +export function isProxy( + value: unknown, +): value is Record { + return core.isProxy(value); +} + +export function isRegExp(value: unknown): value is RegExp { + return ( + isObjectLike(value) && + value[Symbol.toStringTag] === undefined && + _toString.call(value) === "[object RegExp]" + ); +} + +export function isSet(value: unknown): value is Set { + try { + _getSetSize.call(value); + return true; + } catch { + return false; + } +} + +export function isSetIterator( + value: unknown, +): value is IterableIterator { + return ( + isObjectLike(value) && + value[Symbol.toStringTag] === "Set Iterator" + ); +} + +export function isSharedArrayBuffer( + value: unknown, +): value is SharedArrayBuffer { + // SharedArrayBuffer is not available on this runtime + if (_getSharedArrayBufferByteLength === undefined) { + return false; + } + + try { + _getSharedArrayBufferByteLength.call(value); + return true; + } catch { + return false; + } +} + +// deno-lint-ignore ban-types +export function isStringObject(value: unknown): value is String { + if (!isObjectLike(value)) { + return false; + } + + try { + _stringValueOf.call(value); + return true; + } catch { + return false; + } +} + +// deno-lint-ignore ban-types +export function isSymbolObject(value: unknown): value is Symbol { + if (!isObjectLike(value)) { + return false; + } + + try { + _symbolValueOf.call(value); + return true; + } catch { + return false; + } +} + +export function isWeakMap( + value: unknown, +): value is WeakMap, unknown> { + try { + // deno-lint-ignore no-explicit-any + _weakMapHas.call(value, null as any); + return true; + } catch { + return false; + } +} + +export function isWeakSet( + value: unknown, +): value is WeakSet> { + try { + // deno-lint-ignore no-explicit-any + _weakSetHas.call(value, null as any); + return true; + } catch { + return false; + } +} + +export default { + isAsyncFunction, + isGeneratorFunction, + isAnyArrayBuffer, + isArrayBuffer, + isArgumentsObject, + isBoxedPrimitive, + isDataView, + // isExternal, + isMap, + isMapIterator, + isModuleNamespaceObject, + isNativeError, + isPromise, + isSet, + isSetIterator, + isWeakMap, + isWeakSet, + isRegExp, + isDate, + isStringObject, + isNumberObject, + isBooleanObject, + isBigIntObject, +}; diff --git a/ext/node/polyfills/internal_binding/udp_wrap.ts b/ext/node/polyfills/internal_binding/udp_wrap.ts new file mode 100644 index 00000000000000..54173e3baca251 --- /dev/null +++ b/ext/node/polyfills/internal_binding/udp_wrap.ts @@ -0,0 +1,504 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. +// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. + +import { + AsyncWrap, + providerType, +} from "internal:deno_node/polyfills/internal_binding/async_wrap.ts"; +import { GetAddrInfoReqWrap } from "internal:deno_node/polyfills/internal_binding/cares_wrap.ts"; +import { HandleWrap } from "internal:deno_node/polyfills/internal_binding/handle_wrap.ts"; +import { ownerSymbol } from "internal:deno_node/polyfills/internal_binding/symbols.ts"; +import { + codeMap, + errorMap, +} from "internal:deno_node/polyfills/internal_binding/uv.ts"; +import { notImplemented } from "internal:deno_node/polyfills/_utils.ts"; +import { Buffer } from "internal:deno_node/polyfills/buffer.ts"; +import type { ErrnoException } from "internal:deno_node/polyfills/internal/errors.ts"; +import { isIP } from "internal:deno_node/polyfills/internal/net.ts"; + +import { isLinux, isWindows } from "internal:deno_node/polyfills/_util/os.ts"; + +// @ts-ignore Deno[Deno.internal] is used on purpose here +const DenoListenDatagram = Deno[Deno.internal]?.nodeUnstable?.listenDatagram || + Deno.listenDatagram; + +type MessageType = string | Uint8Array | Buffer | DataView; + +const AF_INET = 2; +const AF_INET6 = 10; + +const UDP_DGRAM_MAXSIZE = 64 * 1024; + +export class SendWrap extends AsyncWrap { + list!: MessageType[]; + address!: string; + port!: number; + + callback!: (error: ErrnoException | null, bytes?: number) => void; + oncomplete!: (err: number | null, sent?: number) => void; + + constructor() { + super(providerType.UDPSENDWRAP); + } +} + +export class UDP extends HandleWrap { + [ownerSymbol]: unknown = null; + + #address?: string; + #family?: string; + #port?: number; + + #remoteAddress?: string; + #remoteFamily?: string; + #remotePort?: number; + + #listener?: Deno.DatagramConn; + #receiving = false; + + #recvBufferSize = UDP_DGRAM_MAXSIZE; + #sendBufferSize = UDP_DGRAM_MAXSIZE; + + onmessage!: ( + nread: number, + handle: UDP, + buf?: Buffer, + rinfo?: { + address: string; + family: "IPv4" | "IPv6"; + port: number; + size?: number; + }, + ) => void; + + lookup!: ( + address: string, + callback: ( + err: ErrnoException | null, + address: string, + family: number, + ) => void, + ) => GetAddrInfoReqWrap | Record; + + constructor() { + super(providerType.UDPWRAP); + } + + addMembership(_multicastAddress: string, _interfaceAddress?: string): number { + notImplemented("udp.UDP.prototype.addMembership"); + } + + addSourceSpecificMembership( + _sourceAddress: string, + _groupAddress: string, + _interfaceAddress?: string, + ): number { + notImplemented("udp.UDP.prototype.addSourceSpecificMembership"); + } + + /** + * Bind to an IPv4 address. + * @param ip The hostname to bind to. + * @param port The port to bind to + * @return An error status code. + */ + bind(ip: string, port: number, flags: number): number { + return this.#doBind(ip, port, flags, AF_INET); + } + + /** + * Bind to an IPv6 address. + * @param ip The hostname to bind to. + * @param port The port to bind to + * @return An error status code. + */ + bind6(ip: string, port: number, flags: number): number { + return this.#doBind(ip, port, flags, AF_INET6); + } + + bufferSize( + size: number, + buffer: boolean, + ctx: Record, + ): number | undefined { + let err: string | undefined; + + if (size > UDP_DGRAM_MAXSIZE) { + err = "EINVAL"; + } else if (!this.#address) { + err = isWindows ? "ENOTSOCK" : "EBADF"; + } + + if (err) { + ctx.errno = codeMap.get(err)!; + ctx.code = err; + ctx.message = errorMap.get(ctx.errno)![1]; + ctx.syscall = buffer ? "uv_recv_buffer_size" : "uv_send_buffer_size"; + + return; + } + + if (size !== 0) { + size = isLinux ? size * 2 : size; + + if (buffer) { + return (this.#recvBufferSize = size); + } + + return (this.#sendBufferSize = size); + } + + return buffer ? this.#recvBufferSize : this.#sendBufferSize; + } + + connect(ip: string, port: number): number { + return this.#doConnect(ip, port, AF_INET); + } + + connect6(ip: string, port: number): number { + return this.#doConnect(ip, port, AF_INET6); + } + + disconnect(): number { + this.#remoteAddress = undefined; + this.#remotePort = undefined; + this.#remoteFamily = undefined; + + return 0; + } + + dropMembership( + _multicastAddress: string, + _interfaceAddress?: string, + ): number { + notImplemented("udp.UDP.prototype.dropMembership"); + } + + dropSourceSpecificMembership( + _sourceAddress: string, + _groupAddress: string, + _interfaceAddress?: string, + ): number { + notImplemented("udp.UDP.prototype.dropSourceSpecificMembership"); + } + + /** + * Populates the provided object with remote address entries. + * @param peername An object to add the remote address entries to. + * @return An error status code. + */ + getpeername(peername: Record): number { + if (this.#remoteAddress === undefined) { + return codeMap.get("EBADF")!; + } + + peername.address = this.#remoteAddress; + peername.port = this.#remotePort!; + peername.family = this.#remoteFamily!; + + return 0; + } + + /** + * Populates the provided object with local address entries. + * @param sockname An object to add the local address entries to. + * @return An error status code. + */ + getsockname(sockname: Record): number { + if (this.#address === undefined) { + return codeMap.get("EBADF")!; + } + + sockname.address = this.#address; + sockname.port = this.#port!; + sockname.family = this.#family!; + + return 0; + } + + /** + * Opens a file descriptor. + * @param fd The file descriptor to open. + * @return An error status code. + */ + open(_fd: number): number { + // REF: https://github.com/denoland/deno/issues/6529 + notImplemented("udp.UDP.prototype.open"); + } + + /** + * Start receiving on the connection. + * @return An error status code. + */ + recvStart(): number { + if (!this.#receiving) { + this.#receiving = true; + this.#receive(); + } + + return 0; + } + + /** + * Stop receiving on the connection. + * @return An error status code. + */ + recvStop(): number { + this.#receiving = false; + + return 0; + } + + override ref() { + notImplemented("udp.UDP.prototype.ref"); + } + + send( + req: SendWrap, + bufs: MessageType[], + count: number, + ...args: [number, string, boolean] | [boolean] + ): number { + return this.#doSend(req, bufs, count, args, AF_INET); + } + + send6( + req: SendWrap, + bufs: MessageType[], + count: number, + ...args: [number, string, boolean] | [boolean] + ): number { + return this.#doSend(req, bufs, count, args, AF_INET6); + } + + setBroadcast(_bool: 0 | 1): number { + notImplemented("udp.UDP.prototype.setBroadcast"); + } + + setMulticastInterface(_interfaceAddress: string): number { + notImplemented("udp.UDP.prototype.setMulticastInterface"); + } + + setMulticastLoopback(_bool: 0 | 1): number { + notImplemented("udp.UDP.prototype.setMulticastLoopback"); + } + + setMulticastTTL(_ttl: number): number { + notImplemented("udp.UDP.prototype.setMulticastTTL"); + } + + setTTL(_ttl: number): number { + notImplemented("udp.UDP.prototype.setTTL"); + } + + override unref() { + notImplemented("udp.UDP.prototype.unref"); + } + + #doBind(ip: string, port: number, _flags: number, family: number): number { + // TODO(cmorten): use flags to inform socket reuse etc. + const listenOptions = { + port, + hostname: ip, + transport: "udp" as const, + }; + + let listener; + + try { + listener = DenoListenDatagram(listenOptions); + } catch (e) { + if (e instanceof Deno.errors.AddrInUse) { + return codeMap.get("EADDRINUSE")!; + } else if (e instanceof Deno.errors.AddrNotAvailable) { + return codeMap.get("EADDRNOTAVAIL")!; + } else if (e instanceof Deno.errors.PermissionDenied) { + throw e; + } + + // TODO(cmorten): map errors to appropriate error codes. + return codeMap.get("UNKNOWN")!; + } + + const address = listener.addr as Deno.NetAddr; + this.#address = address.hostname; + this.#port = address.port; + this.#family = family === AF_INET6 ? ("IPv6" as const) : ("IPv4" as const); + this.#listener = listener; + + return 0; + } + + #doConnect(ip: string, port: number, family: number): number { + this.#remoteAddress = ip; + this.#remotePort = port; + this.#remoteFamily = family === AF_INET6 + ? ("IPv6" as const) + : ("IPv4" as const); + + return 0; + } + + #doSend( + req: SendWrap, + bufs: MessageType[], + _count: number, + args: [number, string, boolean] | [boolean], + _family: number, + ): number { + let hasCallback: boolean; + + if (args.length === 3) { + this.#remotePort = args[0] as number; + this.#remoteAddress = args[1] as string; + hasCallback = args[2] as boolean; + } else { + hasCallback = args[0] as boolean; + } + + const addr: Deno.NetAddr = { + hostname: this.#remoteAddress!, + port: this.#remotePort!, + transport: "udp", + }; + + // Deno.DatagramConn.prototype.send accepts only one Uint8Array + const payload = new Uint8Array( + Buffer.concat( + bufs.map((buf) => { + if (typeof buf === "string") { + return Buffer.from(buf); + } + + return Buffer.from(buf.buffer, buf.byteOffset, buf.byteLength); + }), + ), + ); + + (async () => { + let sent: number; + let err: number | null = null; + + try { + sent = await this.#listener!.send(payload, addr); + } catch (e) { + // TODO(cmorten): map errors to appropriate error codes. + if (e instanceof Deno.errors.BadResource) { + err = codeMap.get("EBADF")!; + } else if ( + e instanceof Error && + e.message.match(/os error (40|90|10040)/) + ) { + err = codeMap.get("EMSGSIZE")!; + } else { + err = codeMap.get("UNKNOWN")!; + } + + sent = 0; + } + + if (hasCallback) { + try { + req.oncomplete(err, sent); + } catch { + // swallow callback errors + } + } + })(); + + return 0; + } + + async #receive() { + if (!this.#receiving) { + return; + } + + const p = new Uint8Array(this.#recvBufferSize); + + let buf: Uint8Array; + let remoteAddr: Deno.NetAddr | null; + let nread: number | null; + + try { + [buf, remoteAddr] = (await this.#listener!.receive(p)) as [ + Uint8Array, + Deno.NetAddr, + ]; + + nread = buf.length; + } catch (e) { + // TODO(cmorten): map errors to appropriate error codes. + if ( + e instanceof Deno.errors.Interrupted || + e instanceof Deno.errors.BadResource + ) { + nread = 0; + } else { + nread = codeMap.get("UNKNOWN")!; + } + + buf = new Uint8Array(0); + remoteAddr = null; + } + + nread ??= 0; + + const rinfo = remoteAddr + ? { + address: remoteAddr.hostname, + port: remoteAddr.port, + family: isIP(remoteAddr.hostname) === 6 + ? ("IPv6" as const) + : ("IPv4" as const), + } + : undefined; + + try { + this.onmessage(nread, this, Buffer.from(buf), rinfo); + } catch { + // swallow callback errors. + } + + this.#receive(); + } + + /** Handle socket closure. */ + override _onClose(): number { + this.#receiving = false; + + this.#address = undefined; + this.#port = undefined; + this.#family = undefined; + + try { + this.#listener!.close(); + } catch { + // listener already closed + } + + this.#listener = undefined; + + return 0; + } +} diff --git a/ext/node/polyfills/internal_binding/url.ts b/ext/node/polyfills/internal_binding/url.ts new file mode 100644 index 00000000000000..93e2042b0d4343 --- /dev/null +++ b/ext/node/polyfills/internal_binding/url.ts @@ -0,0 +1,3 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. + +export {}; diff --git a/ext/node/polyfills/internal_binding/util.ts b/ext/node/polyfills/internal_binding/util.ts new file mode 100644 index 00000000000000..21d3cb3dd41d78 --- /dev/null +++ b/ext/node/polyfills/internal_binding/util.ts @@ -0,0 +1,126 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. +// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. + +// This module ports: +// - https://github.com/nodejs/node/blob/master/src/util-inl.h +// - https://github.com/nodejs/node/blob/master/src/util.cc +// - https://github.com/nodejs/node/blob/master/src/util.h + +import { notImplemented } from "internal:deno_node/polyfills/_utils.ts"; + +export function guessHandleType(_fd: number): string { + notImplemented("util.guessHandleType"); +} + +export const ALL_PROPERTIES = 0; +export const ONLY_WRITABLE = 1; +export const ONLY_ENUMERABLE = 2; +export const ONLY_CONFIGURABLE = 4; +export const ONLY_ENUM_WRITABLE = 6; +export const SKIP_STRINGS = 8; +export const SKIP_SYMBOLS = 16; + +/** + * Efficiently determine whether the provided property key is numeric + * (and thus could be an array indexer) or not. + * + * Always returns true for values of type `'number'`. + * + * Otherwise, only returns true for strings that consist only of positive integers. + * + * Results are cached. + */ +const isNumericLookup: Record = {}; +export function isArrayIndex(value: unknown): value is number | string { + switch (typeof value) { + case "number": + return value >= 0 && (value | 0) === value; + case "string": { + const result = isNumericLookup[value]; + if (result !== void 0) { + return result; + } + const length = value.length; + if (length === 0) { + return isNumericLookup[value] = false; + } + let ch = 0; + let i = 0; + for (; i < length; ++i) { + ch = value.charCodeAt(i); + if ( + i === 0 && ch === 0x30 && length > 1 /* must not start with 0 */ || + ch < 0x30 /* 0 */ || ch > 0x39 /* 9 */ + ) { + return isNumericLookup[value] = false; + } + } + return isNumericLookup[value] = true; + } + default: + return false; + } +} + +export function getOwnNonIndexProperties( + // deno-lint-ignore ban-types + obj: object, + filter: number, +): (string | symbol)[] { + let allProperties = [ + ...Object.getOwnPropertyNames(obj), + ...Object.getOwnPropertySymbols(obj), + ]; + + if (Array.isArray(obj)) { + allProperties = allProperties.filter((k) => !isArrayIndex(k)); + } + + if (filter === ALL_PROPERTIES) { + return allProperties; + } + + const result: (string | symbol)[] = []; + for (const key of allProperties) { + const desc = Object.getOwnPropertyDescriptor(obj, key); + if (desc === undefined) { + continue; + } + if (filter & ONLY_WRITABLE && !desc.writable) { + continue; + } + if (filter & ONLY_ENUMERABLE && !desc.enumerable) { + continue; + } + if (filter & ONLY_CONFIGURABLE && !desc.configurable) { + continue; + } + if (filter & SKIP_STRINGS && typeof key === "string") { + continue; + } + if (filter & SKIP_SYMBOLS && typeof key === "symbol") { + continue; + } + result.push(key); + } + return result; +} diff --git a/ext/node/polyfills/internal_binding/uv.ts b/ext/node/polyfills/internal_binding/uv.ts new file mode 100644 index 00000000000000..4ef1b9c4114912 --- /dev/null +++ b/ext/node/polyfills/internal_binding/uv.ts @@ -0,0 +1,437 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. +// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. + +// This module ports: +// - https://github.com/nodejs/node/blob/master/src/uv.cc +// - https://github.com/nodejs/node/blob/master/deps/uv +// +// See also: http://docs.libuv.org/en/v1.x/errors.html#error-constants + +import { unreachable } from "internal:deno_node/polyfills/_util/asserts.ts"; +import { osType } from "internal:deno_node/polyfills/_util/os.ts"; +import { uvTranslateSysError } from "internal:deno_node/polyfills/internal_binding/_libuv_winerror.ts"; + +// In Node these values are coming from libuv: +// Ref: https://github.com/libuv/libuv/blob/v1.x/include/uv/errno.h +// Ref: https://github.com/nodejs/node/blob/524123fbf064ff64bb6fcd83485cfc27db932f68/lib/internal/errors.js#L383 +// Since there is no easy way to port code from libuv and these maps are +// changing very rarely, we simply extract them from Node and store here. + +// Note +// Run the following to get the map: +// $ node -e "console.log(process.binding('uv').getErrorMap())" +// This setup automatically exports maps from both "win", "linux" & darwin: +// https://github.com/schwarzkopfb/node_errno_map + +type ErrorMapData = Array<[number, [string, string]]>; +type CodeMapData = Array<[string, number]>; + +const codeToErrorWindows: ErrorMapData = [ + [-4093, ["E2BIG", "argument list too long"]], + [-4092, ["EACCES", "permission denied"]], + [-4091, ["EADDRINUSE", "address already in use"]], + [-4090, ["EADDRNOTAVAIL", "address not available"]], + [-4089, ["EAFNOSUPPORT", "address family not supported"]], + [-4088, ["EAGAIN", "resource temporarily unavailable"]], + [-3000, ["EAI_ADDRFAMILY", "address family not supported"]], + [-3001, ["EAI_AGAIN", "temporary failure"]], + [-3002, ["EAI_BADFLAGS", "bad ai_flags value"]], + [-3013, ["EAI_BADHINTS", "invalid value for hints"]], + [-3003, ["EAI_CANCELED", "request canceled"]], + [-3004, ["EAI_FAIL", "permanent failure"]], + [-3005, ["EAI_FAMILY", "ai_family not supported"]], + [-3006, ["EAI_MEMORY", "out of memory"]], + [-3007, ["EAI_NODATA", "no address"]], + [-3008, ["EAI_NONAME", "unknown node or service"]], + [-3009, ["EAI_OVERFLOW", "argument buffer overflow"]], + [-3014, ["EAI_PROTOCOL", "resolved protocol is unknown"]], + [-3010, ["EAI_SERVICE", "service not available for socket type"]], + [-3011, ["EAI_SOCKTYPE", "socket type not supported"]], + [-4084, ["EALREADY", "connection already in progress"]], + [-4083, ["EBADF", "bad file descriptor"]], + [-4082, ["EBUSY", "resource busy or locked"]], + [-4081, ["ECANCELED", "operation canceled"]], + [-4080, ["ECHARSET", "invalid Unicode character"]], + [-4079, ["ECONNABORTED", "software caused connection abort"]], + [-4078, ["ECONNREFUSED", "connection refused"]], + [-4077, ["ECONNRESET", "connection reset by peer"]], + [-4076, ["EDESTADDRREQ", "destination address required"]], + [-4075, ["EEXIST", "file already exists"]], + [-4074, ["EFAULT", "bad address in system call argument"]], + [-4036, ["EFBIG", "file too large"]], + [-4073, ["EHOSTUNREACH", "host is unreachable"]], + [-4072, ["EINTR", "interrupted system call"]], + [-4071, ["EINVAL", "invalid argument"]], + [-4070, ["EIO", "i/o error"]], + [-4069, ["EISCONN", "socket is already connected"]], + [-4068, ["EISDIR", "illegal operation on a directory"]], + [-4067, ["ELOOP", "too many symbolic links encountered"]], + [-4066, ["EMFILE", "too many open files"]], + [-4065, ["EMSGSIZE", "message too long"]], + [-4064, ["ENAMETOOLONG", "name too long"]], + [-4063, ["ENETDOWN", "network is down"]], + [-4062, ["ENETUNREACH", "network is unreachable"]], + [-4061, ["ENFILE", "file table overflow"]], + [-4060, ["ENOBUFS", "no buffer space available"]], + [-4059, ["ENODEV", "no such device"]], + [-4058, ["ENOENT", "no such file or directory"]], + [-4057, ["ENOMEM", "not enough memory"]], + [-4056, ["ENONET", "machine is not on the network"]], + [-4035, ["ENOPROTOOPT", "protocol not available"]], + [-4055, ["ENOSPC", "no space left on device"]], + [-4054, ["ENOSYS", "function not implemented"]], + [-4053, ["ENOTCONN", "socket is not connected"]], + [-4052, ["ENOTDIR", "not a directory"]], + [-4051, ["ENOTEMPTY", "directory not empty"]], + [-4050, ["ENOTSOCK", "socket operation on non-socket"]], + [-4049, ["ENOTSUP", "operation not supported on socket"]], + [-4048, ["EPERM", "operation not permitted"]], + [-4047, ["EPIPE", "broken pipe"]], + [-4046, ["EPROTO", "protocol error"]], + [-4045, ["EPROTONOSUPPORT", "protocol not supported"]], + [-4044, ["EPROTOTYPE", "protocol wrong type for socket"]], + [-4034, ["ERANGE", "result too large"]], + [-4043, ["EROFS", "read-only file system"]], + [-4042, ["ESHUTDOWN", "cannot send after transport endpoint shutdown"]], + [-4041, ["ESPIPE", "invalid seek"]], + [-4040, ["ESRCH", "no such process"]], + [-4039, ["ETIMEDOUT", "connection timed out"]], + [-4038, ["ETXTBSY", "text file is busy"]], + [-4037, ["EXDEV", "cross-device link not permitted"]], + [-4094, ["UNKNOWN", "unknown error"]], + [-4095, ["EOF", "end of file"]], + [-4033, ["ENXIO", "no such device or address"]], + [-4032, ["EMLINK", "too many links"]], + [-4031, ["EHOSTDOWN", "host is down"]], + [-4030, ["EREMOTEIO", "remote I/O error"]], + [-4029, ["ENOTTY", "inappropriate ioctl for device"]], + [-4028, ["EFTYPE", "inappropriate file type or format"]], + [-4027, ["EILSEQ", "illegal byte sequence"]], +]; + +const errorToCodeWindows: CodeMapData = codeToErrorWindows.map(( + [status, [error]], +) => [error, status]); + +const codeToErrorDarwin: ErrorMapData = [ + [-7, ["E2BIG", "argument list too long"]], + [-13, ["EACCES", "permission denied"]], + [-48, ["EADDRINUSE", "address already in use"]], + [-49, ["EADDRNOTAVAIL", "address not available"]], + [-47, ["EAFNOSUPPORT", "address family not supported"]], + [-35, ["EAGAIN", "resource temporarily unavailable"]], + [-3000, ["EAI_ADDRFAMILY", "address family not supported"]], + [-3001, ["EAI_AGAIN", "temporary failure"]], + [-3002, ["EAI_BADFLAGS", "bad ai_flags value"]], + [-3013, ["EAI_BADHINTS", "invalid value for hints"]], + [-3003, ["EAI_CANCELED", "request canceled"]], + [-3004, ["EAI_FAIL", "permanent failure"]], + [-3005, ["EAI_FAMILY", "ai_family not supported"]], + [-3006, ["EAI_MEMORY", "out of memory"]], + [-3007, ["EAI_NODATA", "no address"]], + [-3008, ["EAI_NONAME", "unknown node or service"]], + [-3009, ["EAI_OVERFLOW", "argument buffer overflow"]], + [-3014, ["EAI_PROTOCOL", "resolved protocol is unknown"]], + [-3010, ["EAI_SERVICE", "service not available for socket type"]], + [-3011, ["EAI_SOCKTYPE", "socket type not supported"]], + [-37, ["EALREADY", "connection already in progress"]], + [-9, ["EBADF", "bad file descriptor"]], + [-16, ["EBUSY", "resource busy or locked"]], + [-89, ["ECANCELED", "operation canceled"]], + [-4080, ["ECHARSET", "invalid Unicode character"]], + [-53, ["ECONNABORTED", "software caused connection abort"]], + [-61, ["ECONNREFUSED", "connection refused"]], + [-54, ["ECONNRESET", "connection reset by peer"]], + [-39, ["EDESTADDRREQ", "destination address required"]], + [-17, ["EEXIST", "file already exists"]], + [-14, ["EFAULT", "bad address in system call argument"]], + [-27, ["EFBIG", "file too large"]], + [-65, ["EHOSTUNREACH", "host is unreachable"]], + [-4, ["EINTR", "interrupted system call"]], + [-22, ["EINVAL", "invalid argument"]], + [-5, ["EIO", "i/o error"]], + [-56, ["EISCONN", "socket is already connected"]], + [-21, ["EISDIR", "illegal operation on a directory"]], + [-62, ["ELOOP", "too many symbolic links encountered"]], + [-24, ["EMFILE", "too many open files"]], + [-40, ["EMSGSIZE", "message too long"]], + [-63, ["ENAMETOOLONG", "name too long"]], + [-50, ["ENETDOWN", "network is down"]], + [-51, ["ENETUNREACH", "network is unreachable"]], + [-23, ["ENFILE", "file table overflow"]], + [-55, ["ENOBUFS", "no buffer space available"]], + [-19, ["ENODEV", "no such device"]], + [-2, ["ENOENT", "no such file or directory"]], + [-12, ["ENOMEM", "not enough memory"]], + [-4056, ["ENONET", "machine is not on the network"]], + [-42, ["ENOPROTOOPT", "protocol not available"]], + [-28, ["ENOSPC", "no space left on device"]], + [-78, ["ENOSYS", "function not implemented"]], + [-57, ["ENOTCONN", "socket is not connected"]], + [-20, ["ENOTDIR", "not a directory"]], + [-66, ["ENOTEMPTY", "directory not empty"]], + [-38, ["ENOTSOCK", "socket operation on non-socket"]], + [-45, ["ENOTSUP", "operation not supported on socket"]], + [-1, ["EPERM", "operation not permitted"]], + [-32, ["EPIPE", "broken pipe"]], + [-100, ["EPROTO", "protocol error"]], + [-43, ["EPROTONOSUPPORT", "protocol not supported"]], + [-41, ["EPROTOTYPE", "protocol wrong type for socket"]], + [-34, ["ERANGE", "result too large"]], + [-30, ["EROFS", "read-only file system"]], + [-58, ["ESHUTDOWN", "cannot send after transport endpoint shutdown"]], + [-29, ["ESPIPE", "invalid seek"]], + [-3, ["ESRCH", "no such process"]], + [-60, ["ETIMEDOUT", "connection timed out"]], + [-26, ["ETXTBSY", "text file is busy"]], + [-18, ["EXDEV", "cross-device link not permitted"]], + [-4094, ["UNKNOWN", "unknown error"]], + [-4095, ["EOF", "end of file"]], + [-6, ["ENXIO", "no such device or address"]], + [-31, ["EMLINK", "too many links"]], + [-64, ["EHOSTDOWN", "host is down"]], + [-4030, ["EREMOTEIO", "remote I/O error"]], + [-25, ["ENOTTY", "inappropriate ioctl for device"]], + [-79, ["EFTYPE", "inappropriate file type or format"]], + [-92, ["EILSEQ", "illegal byte sequence"]], +]; + +const errorToCodeDarwin: CodeMapData = codeToErrorDarwin.map(( + [status, [code]], +) => [code, status]); + +const codeToErrorLinux: ErrorMapData = [ + [-7, ["E2BIG", "argument list too long"]], + [-13, ["EACCES", "permission denied"]], + [-98, ["EADDRINUSE", "address already in use"]], + [-99, ["EADDRNOTAVAIL", "address not available"]], + [-97, ["EAFNOSUPPORT", "address family not supported"]], + [-11, ["EAGAIN", "resource temporarily unavailable"]], + [-3000, ["EAI_ADDRFAMILY", "address family not supported"]], + [-3001, ["EAI_AGAIN", "temporary failure"]], + [-3002, ["EAI_BADFLAGS", "bad ai_flags value"]], + [-3013, ["EAI_BADHINTS", "invalid value for hints"]], + [-3003, ["EAI_CANCELED", "request canceled"]], + [-3004, ["EAI_FAIL", "permanent failure"]], + [-3005, ["EAI_FAMILY", "ai_family not supported"]], + [-3006, ["EAI_MEMORY", "out of memory"]], + [-3007, ["EAI_NODATA", "no address"]], + [-3008, ["EAI_NONAME", "unknown node or service"]], + [-3009, ["EAI_OVERFLOW", "argument buffer overflow"]], + [-3014, ["EAI_PROTOCOL", "resolved protocol is unknown"]], + [-3010, ["EAI_SERVICE", "service not available for socket type"]], + [-3011, ["EAI_SOCKTYPE", "socket type not supported"]], + [-114, ["EALREADY", "connection already in progress"]], + [-9, ["EBADF", "bad file descriptor"]], + [-16, ["EBUSY", "resource busy or locked"]], + [-125, ["ECANCELED", "operation canceled"]], + [-4080, ["ECHARSET", "invalid Unicode character"]], + [-103, ["ECONNABORTED", "software caused connection abort"]], + [-111, ["ECONNREFUSED", "connection refused"]], + [-104, ["ECONNRESET", "connection reset by peer"]], + [-89, ["EDESTADDRREQ", "destination address required"]], + [-17, ["EEXIST", "file already exists"]], + [-14, ["EFAULT", "bad address in system call argument"]], + [-27, ["EFBIG", "file too large"]], + [-113, ["EHOSTUNREACH", "host is unreachable"]], + [-4, ["EINTR", "interrupted system call"]], + [-22, ["EINVAL", "invalid argument"]], + [-5, ["EIO", "i/o error"]], + [-106, ["EISCONN", "socket is already connected"]], + [-21, ["EISDIR", "illegal operation on a directory"]], + [-40, ["ELOOP", "too many symbolic links encountered"]], + [-24, ["EMFILE", "too many open files"]], + [-90, ["EMSGSIZE", "message too long"]], + [-36, ["ENAMETOOLONG", "name too long"]], + [-100, ["ENETDOWN", "network is down"]], + [-101, ["ENETUNREACH", "network is unreachable"]], + [-23, ["ENFILE", "file table overflow"]], + [-105, ["ENOBUFS", "no buffer space available"]], + [-19, ["ENODEV", "no such device"]], + [-2, ["ENOENT", "no such file or directory"]], + [-12, ["ENOMEM", "not enough memory"]], + [-64, ["ENONET", "machine is not on the network"]], + [-92, ["ENOPROTOOPT", "protocol not available"]], + [-28, ["ENOSPC", "no space left on device"]], + [-38, ["ENOSYS", "function not implemented"]], + [-107, ["ENOTCONN", "socket is not connected"]], + [-20, ["ENOTDIR", "not a directory"]], + [-39, ["ENOTEMPTY", "directory not empty"]], + [-88, ["ENOTSOCK", "socket operation on non-socket"]], + [-95, ["ENOTSUP", "operation not supported on socket"]], + [-1, ["EPERM", "operation not permitted"]], + [-32, ["EPIPE", "broken pipe"]], + [-71, ["EPROTO", "protocol error"]], + [-93, ["EPROTONOSUPPORT", "protocol not supported"]], + [-91, ["EPROTOTYPE", "protocol wrong type for socket"]], + [-34, ["ERANGE", "result too large"]], + [-30, ["EROFS", "read-only file system"]], + [-108, ["ESHUTDOWN", "cannot send after transport endpoint shutdown"]], + [-29, ["ESPIPE", "invalid seek"]], + [-3, ["ESRCH", "no such process"]], + [-110, ["ETIMEDOUT", "connection timed out"]], + [-26, ["ETXTBSY", "text file is busy"]], + [-18, ["EXDEV", "cross-device link not permitted"]], + [-4094, ["UNKNOWN", "unknown error"]], + [-4095, ["EOF", "end of file"]], + [-6, ["ENXIO", "no such device or address"]], + [-31, ["EMLINK", "too many links"]], + [-112, ["EHOSTDOWN", "host is down"]], + [-121, ["EREMOTEIO", "remote I/O error"]], + [-25, ["ENOTTY", "inappropriate ioctl for device"]], + [-4028, ["EFTYPE", "inappropriate file type or format"]], + [-84, ["EILSEQ", "illegal byte sequence"]], +]; + +const errorToCodeLinux: CodeMapData = codeToErrorLinux.map(( + [status, [code]], +) => [code, status]); + +const codeToErrorFreebsd: ErrorMapData = [ + [-7, ["E2BIG", "argument list too long"]], + [-13, ["EACCES", "permission denied"]], + [-48, ["EADDRINUSE", "address already in use"]], + [-49, ["EADDRNOTAVAIL", "address not available"]], + [-47, ["EAFNOSUPPORT", "address family not supported"]], + [-35, ["EAGAIN", "resource temporarily unavailable"]], + [-3000, ["EAI_ADDRFAMILY", "address family not supported"]], + [-3001, ["EAI_AGAIN", "temporary failure"]], + [-3002, ["EAI_BADFLAGS", "bad ai_flags value"]], + [-3013, ["EAI_BADHINTS", "invalid value for hints"]], + [-3003, ["EAI_CANCELED", "request canceled"]], + [-3004, ["EAI_FAIL", "permanent failure"]], + [-3005, ["EAI_FAMILY", "ai_family not supported"]], + [-3006, ["EAI_MEMORY", "out of memory"]], + [-3007, ["EAI_NODATA", "no address"]], + [-3008, ["EAI_NONAME", "unknown node or service"]], + [-3009, ["EAI_OVERFLOW", "argument buffer overflow"]], + [-3014, ["EAI_PROTOCOL", "resolved protocol is unknown"]], + [-3010, ["EAI_SERVICE", "service not available for socket type"]], + [-3011, ["EAI_SOCKTYPE", "socket type not supported"]], + [-37, ["EALREADY", "connection already in progress"]], + [-9, ["EBADF", "bad file descriptor"]], + [-16, ["EBUSY", "resource busy or locked"]], + [-85, ["ECANCELED", "operation canceled"]], + [-4080, ["ECHARSET", "invalid Unicode character"]], + [-53, ["ECONNABORTED", "software caused connection abort"]], + [-61, ["ECONNREFUSED", "connection refused"]], + [-54, ["ECONNRESET", "connection reset by peer"]], + [-39, ["EDESTADDRREQ", "destination address required"]], + [-17, ["EEXIST", "file already exists"]], + [-14, ["EFAULT", "bad address in system call argument"]], + [-27, ["EFBIG", "file too large"]], + [-65, ["EHOSTUNREACH", "host is unreachable"]], + [-4, ["EINTR", "interrupted system call"]], + [-22, ["EINVAL", "invalid argument"]], + [-5, ["EIO", "i/o error"]], + [-56, ["EISCONN", "socket is already connected"]], + [-21, ["EISDIR", "illegal operation on a directory"]], + [-62, ["ELOOP", "too many symbolic links encountered"]], + [-24, ["EMFILE", "too many open files"]], + [-40, ["EMSGSIZE", "message too long"]], + [-63, ["ENAMETOOLONG", "name too long"]], + [-50, ["ENETDOWN", "network is down"]], + [-51, ["ENETUNREACH", "network is unreachable"]], + [-23, ["ENFILE", "file table overflow"]], + [-55, ["ENOBUFS", "no buffer space available"]], + [-19, ["ENODEV", "no such device"]], + [-2, ["ENOENT", "no such file or directory"]], + [-12, ["ENOMEM", "not enough memory"]], + [-4056, ["ENONET", "machine is not on the network"]], + [-42, ["ENOPROTOOPT", "protocol not available"]], + [-28, ["ENOSPC", "no space left on device"]], + [-78, ["ENOSYS", "function not implemented"]], + [-57, ["ENOTCONN", "socket is not connected"]], + [-20, ["ENOTDIR", "not a directory"]], + [-66, ["ENOTEMPTY", "directory not empty"]], + [-38, ["ENOTSOCK", "socket operation on non-socket"]], + [-45, ["ENOTSUP", "operation not supported on socket"]], + [-84, ["EOVERFLOW", "value too large for defined data type"]], + [-1, ["EPERM", "operation not permitted"]], + [-32, ["EPIPE", "broken pipe"]], + [-92, ["EPROTO", "protocol error"]], + [-43, ["EPROTONOSUPPORT", "protocol not supported"]], + [-41, ["EPROTOTYPE", "protocol wrong type for socket"]], + [-34, ["ERANGE", "result too large"]], + [-30, ["EROFS", "read-only file system"]], + [-58, ["ESHUTDOWN", "cannot send after transport endpoint shutdown"]], + [-29, ["ESPIPE", "invalid seek"]], + [-3, ["ESRCH", "no such process"]], + [-60, ["ETIMEDOUT", "connection timed out"]], + [-26, ["ETXTBSY", "text file is busy"]], + [-18, ["EXDEV", "cross-device link not permitted"]], + [-4094, ["UNKNOWN", "unknown error"]], + [-4095, ["EOF", "end of file"]], + [-6, ["ENXIO", "no such device or address"]], + [-31, ["EMLINK", "too many links"]], + [-64, ["EHOSTDOWN", "host is down"]], + [-4030, ["EREMOTEIO", "remote I/O error"]], + [-25, ["ENOTTY", "inappropriate ioctl for device"]], + [-79, ["EFTYPE", "inappropriate file type or format"]], + [-86, ["EILSEQ", "illegal byte sequence"]], + [-44, ["ESOCKTNOSUPPORT", "socket type not supported"]], +]; + +const errorToCodeFreebsd: CodeMapData = codeToErrorFreebsd.map(( + [status, [code]], +) => [code, status]); + +export const errorMap = new Map( + osType === "windows" + ? codeToErrorWindows + : osType === "darwin" + ? codeToErrorDarwin + : osType === "linux" + ? codeToErrorLinux + : osType === "freebsd" + ? codeToErrorFreebsd + : unreachable(), +); + +export const codeMap = new Map( + osType === "windows" + ? errorToCodeWindows + : osType === "darwin" + ? errorToCodeDarwin + : osType === "linux" + ? errorToCodeLinux + : osType === "freebsd" + ? errorToCodeFreebsd + : unreachable(), +); + +export function mapSysErrnoToUvErrno(sysErrno: number): number { + if (osType === "windows") { + const code = uvTranslateSysError(sysErrno); + return codeMap.get(code) ?? -sysErrno; + } else { + return -sysErrno; + } +} + +export const UV_EAI_MEMORY = codeMap.get("EAI_MEMORY")!; +export const UV_EBADF = codeMap.get("EBADF")!; +export const UV_EEXIST = codeMap.get("EEXIST"); +export const UV_EINVAL = codeMap.get("EINVAL")!; +export const UV_ENOENT = codeMap.get("ENOENT"); +export const UV_ENOTSOCK = codeMap.get("ENOTSOCK")!; +export const UV_UNKNOWN = codeMap.get("UNKNOWN")!; diff --git a/ext/node/polyfills/internal_binding/v8.ts b/ext/node/polyfills/internal_binding/v8.ts new file mode 100644 index 00000000000000..93e2042b0d4343 --- /dev/null +++ b/ext/node/polyfills/internal_binding/v8.ts @@ -0,0 +1,3 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. + +export {}; diff --git a/ext/node/polyfills/internal_binding/worker.ts b/ext/node/polyfills/internal_binding/worker.ts new file mode 100644 index 00000000000000..93e2042b0d4343 --- /dev/null +++ b/ext/node/polyfills/internal_binding/worker.ts @@ -0,0 +1,3 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. + +export {}; diff --git a/ext/node/polyfills/internal_binding/zlib.ts b/ext/node/polyfills/internal_binding/zlib.ts new file mode 100644 index 00000000000000..93e2042b0d4343 --- /dev/null +++ b/ext/node/polyfills/internal_binding/zlib.ts @@ -0,0 +1,3 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. + +export {}; diff --git a/ext/node/polyfills/module.js b/ext/node/polyfills/module.js new file mode 100644 index 00000000000000..2b7c20e26da3b2 --- /dev/null +++ b/ext/node/polyfills/module.js @@ -0,0 +1,20 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. + +const internals = globalThis.__bootstrap.internals; +const m = internals.require.Module; +export const _cache = m._cache; +export const _extensions = m._extensions; +export const _findPath = m._findPath; +export const _initPaths = m._initPaths; +export const _load = m._load; +export const _nodeModulePaths = m._nodeModulePaths; +export const _pathCache = m._pathCache; +export const _preloadModules = m._preloadModules; +export const _resolveFilename = m._resolveFilename; +export const _resolveLookupPaths = m._resolveLookupPaths; +export const builtinModules = m.builtinModules; +export const createRequire = m.createRequire; +export const globalPaths = m.globalPaths; +export const Module = m.Module; +export const wrap = m.wrap; +export default m; diff --git a/ext/node/polyfills/module_all.ts b/ext/node/polyfills/module_all.ts new file mode 100644 index 00000000000000..989ce55a888268 --- /dev/null +++ b/ext/node/polyfills/module_all.ts @@ -0,0 +1,191 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. +const internals = globalThis.__bootstrap.internals; +import _httpAgent from "internal:deno_node/polyfills/_http_agent.mjs"; +import _httpOutgoing from "internal:deno_node/polyfills/_http_outgoing.ts"; +import _streamDuplex from "internal:deno_node/polyfills/internal/streams/duplex.mjs"; +import _streamPassthrough from "internal:deno_node/polyfills/internal/streams/passthrough.mjs"; +import _streamReadable from "internal:deno_node/polyfills/internal/streams/readable.mjs"; +import _streamTransform from "internal:deno_node/polyfills/internal/streams/transform.mjs"; +import _streamWritable from "internal:deno_node/polyfills/internal/streams/writable.mjs"; +import assert from "internal:deno_node/polyfills/assert.ts"; +import assertStrict from "internal:deno_node/polyfills/assert/strict.ts"; +import asyncHooks from "internal:deno_node/polyfills/async_hooks.ts"; +import buffer from "internal:deno_node/polyfills/buffer.ts"; +import childProcess from "internal:deno_node/polyfills/child_process.ts"; +import cluster from "internal:deno_node/polyfills/cluster.ts"; +import console from "internal:deno_node/polyfills/console.ts"; +import constants from "internal:deno_node/polyfills/constants.ts"; +import crypto from "internal:deno_node/polyfills/crypto.ts"; +import dgram from "internal:deno_node/polyfills/dgram.ts"; +import diagnosticsChannel from "internal:deno_node/polyfills/diagnostics_channel.ts"; +import dns from "internal:deno_node/polyfills/dns.ts"; +import dnsPromises from "internal:deno_node/polyfills/dns/promises.ts"; +import domain from "internal:deno_node/polyfills/domain.ts"; +import events from "internal:deno_node/polyfills/events.ts"; +import fs from "internal:deno_node/polyfills/fs.ts"; +import fsPromises from "internal:deno_node/polyfills/fs/promises.ts"; +import http from "internal:deno_node/polyfills/http.ts"; +import http2 from "internal:deno_node/polyfills/http2.ts"; +import https from "internal:deno_node/polyfills/https.ts"; +import inspector from "internal:deno_node/polyfills/inspector.ts"; +import internalCp from "internal:deno_node/polyfills/internal/child_process.ts"; +import internalCryptoCertificate from "internal:deno_node/polyfills/internal/crypto/certificate.ts"; +import internalCryptoCipher from "internal:deno_node/polyfills/internal/crypto/cipher.ts"; +import internalCryptoDiffiehellman from "internal:deno_node/polyfills/internal/crypto/diffiehellman.ts"; +import internalCryptoHash from "internal:deno_node/polyfills/internal/crypto/hash.ts"; +import internalCryptoHkdf from "internal:deno_node/polyfills/internal/crypto/hkdf.ts"; +import internalCryptoKeygen from "internal:deno_node/polyfills/internal/crypto/keygen.ts"; +import internalCryptoKeys from "internal:deno_node/polyfills/internal/crypto/keys.ts"; +import internalCryptoPbkdf2 from "internal:deno_node/polyfills/internal/crypto/pbkdf2.ts"; +import internalCryptoRandom from "internal:deno_node/polyfills/internal/crypto/random.ts"; +import internalCryptoScrypt from "internal:deno_node/polyfills/internal/crypto/scrypt.ts"; +import internalCryptoSig from "internal:deno_node/polyfills/internal/crypto/sig.ts"; +import internalCryptoUtil from "internal:deno_node/polyfills/internal/crypto/util.ts"; +import internalCryptoX509 from "internal:deno_node/polyfills/internal/crypto/x509.ts"; +import internalDgram from "internal:deno_node/polyfills/internal/dgram.ts"; +import internalDnsPromises from "internal:deno_node/polyfills/internal/dns/promises.ts"; +import internalErrors from "internal:deno_node/polyfills/internal/errors.ts"; +import internalEventTarget from "internal:deno_node/polyfills/internal/event_target.mjs"; +import internalFsUtils from "internal:deno_node/polyfills/internal/fs/utils.mjs"; +import internalHttp from "internal:deno_node/polyfills/internal/http.ts"; +import internalReadlineUtils from "internal:deno_node/polyfills/internal/readline/utils.mjs"; +import internalStreamsAddAbortSignal from "internal:deno_node/polyfills/internal/streams/add-abort-signal.mjs"; +import internalStreamsBufferList from "internal:deno_node/polyfills/internal/streams/buffer_list.mjs"; +import internalStreamsLazyTransform from "internal:deno_node/polyfills/internal/streams/lazy_transform.mjs"; +import internalStreamsState from "internal:deno_node/polyfills/internal/streams/state.mjs"; +import internalTestBinding from "internal:deno_node/polyfills/internal/test/binding.ts"; +import internalTimers from "internal:deno_node/polyfills/internal/timers.mjs"; +import internalUtil from "internal:deno_node/polyfills/internal/util.mjs"; +import internalUtilInspect from "internal:deno_node/polyfills/internal/util/inspect.mjs"; +import net from "internal:deno_node/polyfills/net.ts"; +import os from "internal:deno_node/polyfills/os.ts"; +import pathPosix from "internal:deno_node/polyfills/path/posix.ts"; +import pathWin32 from "internal:deno_node/polyfills/path/win32.ts"; +import path from "internal:deno_node/polyfills/path.ts"; +import perfHooks from "internal:deno_node/polyfills/perf_hooks.ts"; +import punycode from "internal:deno_node/polyfills/punycode.ts"; +import process from "internal:deno_node/polyfills/process.ts"; +import querystring from "internal:deno_node/polyfills/querystring.ts"; +import readline from "internal:deno_node/polyfills/readline.ts"; +import readlinePromises from "internal:deno_node/polyfills/readline/promises.ts"; +import repl from "internal:deno_node/polyfills/repl.ts"; +import stream from "internal:deno_node/polyfills/stream.ts"; +import streamConsumers from "internal:deno_node/polyfills/stream/consumers.mjs"; +import streamPromises from "internal:deno_node/polyfills/stream/promises.mjs"; +import streamWeb from "internal:deno_node/polyfills/stream/web.ts"; +import stringDecoder from "internal:deno_node/polyfills/string_decoder.ts"; +import sys from "internal:deno_node/polyfills/sys.ts"; +import timers from "internal:deno_node/polyfills/timers.ts"; +import timersPromises from "internal:deno_node/polyfills/timers/promises.ts"; +import tls from "internal:deno_node/polyfills/tls.ts"; +import tty from "internal:deno_node/polyfills/tty.ts"; +import url from "internal:deno_node/polyfills/url.ts"; +import utilTypes from "internal:deno_node/polyfills/util/types.ts"; +import util from "internal:deno_node/polyfills/util.ts"; +import v8 from "internal:deno_node/polyfills/v8.ts"; +import vm from "internal:deno_node/polyfills/vm.ts"; +import workerThreads from "internal:deno_node/polyfills/worker_threads.ts"; +import wasi from "internal:deno_node/polyfills/wasi.ts"; +import zlib from "internal:deno_node/polyfills/zlib.ts"; + +// Canonical mapping of supported modules +const moduleAll = { + "_http_agent": _httpAgent, + "_http_outgoing": _httpOutgoing, + "_stream_duplex": _streamDuplex, + "_stream_passthrough": _streamPassthrough, + "_stream_readable": _streamReadable, + "_stream_transform": _streamTransform, + "_stream_writable": _streamWritable, + assert, + "assert/strict": assertStrict, + "async_hooks": asyncHooks, + buffer, + crypto, + console, + constants, + child_process: childProcess, + cluster, + dgram, + diagnostics_channel: diagnosticsChannel, + dns, + "dns/promises": dnsPromises, + domain, + events, + fs, + "fs/promises": fsPromises, + http, + http2, + https, + inspector, + "internal/child_process": internalCp, + "internal/crypto/certificate": internalCryptoCertificate, + "internal/crypto/cipher": internalCryptoCipher, + "internal/crypto/diffiehellman": internalCryptoDiffiehellman, + "internal/crypto/hash": internalCryptoHash, + "internal/crypto/hkdf": internalCryptoHkdf, + "internal/crypto/keygen": internalCryptoKeygen, + "internal/crypto/keys": internalCryptoKeys, + "internal/crypto/pbkdf2": internalCryptoPbkdf2, + "internal/crypto/random": internalCryptoRandom, + "internal/crypto/scrypt": internalCryptoScrypt, + "internal/crypto/sig": internalCryptoSig, + "internal/crypto/util": internalCryptoUtil, + "internal/crypto/x509": internalCryptoX509, + "internal/dgram": internalDgram, + "internal/dns/promises": internalDnsPromises, + "internal/errors": internalErrors, + "internal/event_target": internalEventTarget, + "internal/fs/utils": internalFsUtils, + "internal/http": internalHttp, + "internal/readline/utils": internalReadlineUtils, + "internal/streams/add-abort-signal": internalStreamsAddAbortSignal, + "internal/streams/buffer_list": internalStreamsBufferList, + "internal/streams/lazy_transform": internalStreamsLazyTransform, + "internal/streams/state": internalStreamsState, + "internal/test/binding": internalTestBinding, + "internal/timers": internalTimers, + "internal/util/inspect": internalUtilInspect, + "internal/util": internalUtil, + net, + os, + "path/posix": pathPosix, + "path/win32": pathWin32, + path, + perf_hooks: perfHooks, + process, + get punycode() { + process.emitWarning( + "The `punycode` module is deprecated. Please use a userland " + + "alternative instead.", + "DeprecationWarning", + "DEP0040", + ); + return punycode; + }, + querystring, + readline, + "readline/promises": readlinePromises, + repl, + stream, + "stream/consumers": streamConsumers, + "stream/promises": streamPromises, + "stream/web": streamWeb, + string_decoder: stringDecoder, + sys, + timers, + "timers/promises": timersPromises, + tls, + tty, + url, + util, + "util/types": utilTypes, + v8, + vm, + wasi, + worker_threads: workerThreads, + zlib, +} as Record; + +internals.nodeModuleAll = moduleAll; +export default moduleAll; diff --git a/ext/node/polyfills/module_esm.ts b/ext/node/polyfills/module_esm.ts new file mode 100644 index 00000000000000..e9cb38ff1fd721 --- /dev/null +++ b/ext/node/polyfills/module_esm.ts @@ -0,0 +1,845 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. +// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. + +/** + * NOTE(bartlomieju): + * Functionality of this file is ported in Rust in `cli/compat/esm_resolver.ts`. + * Unfortunately we have no way to call ESM resolution in Rust from TypeScript code. + */ + +import { + fileURLToPath, + pathToFileURL, +} from "internal:deno_node/polyfills/url.ts"; +import { + ERR_INVALID_MODULE_SPECIFIER, + ERR_INVALID_PACKAGE_CONFIG, + ERR_INVALID_PACKAGE_TARGET, + ERR_MODULE_NOT_FOUND, + ERR_PACKAGE_IMPORT_NOT_DEFINED, + ERR_PACKAGE_PATH_NOT_EXPORTED, + NodeError, +} from "internal:deno_node/polyfills/internal/errors.ts"; + +const { hasOwn } = Object; + +export const encodedSepRegEx = /%2F|%2C/i; + +function throwInvalidSubpath( + subpath: string, + packageJSONUrl: string, + internal: boolean, + base: string, +) { + const reason = `request is not a valid subpath for the "${ + internal ? "imports" : "exports" + }" resolution of ${fileURLToPath(packageJSONUrl)}`; + throw new ERR_INVALID_MODULE_SPECIFIER( + subpath, + reason, + base && fileURLToPath(base), + ); +} + +function throwInvalidPackageTarget( + subpath: string, + // deno-lint-ignore no-explicit-any + target: any, + packageJSONUrl: string, + internal: boolean, + base: string, +) { + if (typeof target === "object" && target !== null) { + target = JSON.stringify(target, null, ""); + } else { + target = `${target}`; + } + throw new ERR_INVALID_PACKAGE_TARGET( + fileURLToPath(new URL(".", packageJSONUrl)), + subpath, + target, + internal, + base && fileURLToPath(base), + ); +} + +function throwImportNotDefined( + specifier: string, + packageJSONUrl: URL | undefined, + base: string | URL, +): TypeError & { code: string } { + throw new ERR_PACKAGE_IMPORT_NOT_DEFINED( + specifier, + packageJSONUrl && fileURLToPath(new URL(".", packageJSONUrl)), + fileURLToPath(base), + ); +} + +function throwExportsNotFound( + subpath: string, + packageJSONUrl: string, + base?: string, +): Error & { code: string } { + throw new ERR_PACKAGE_PATH_NOT_EXPORTED( + subpath, + fileURLToPath(new URL(".", packageJSONUrl)), + base && fileURLToPath(base), + ); +} + +function patternKeyCompare(a: string, b: string): number { + const aPatternIndex = a.indexOf("*"); + const bPatternIndex = b.indexOf("*"); + const baseLenA = aPatternIndex === -1 ? a.length : aPatternIndex + 1; + const baseLenB = bPatternIndex === -1 ? b.length : bPatternIndex + 1; + if (baseLenA > baseLenB) return -1; + if (baseLenB > baseLenA) return 1; + if (aPatternIndex === -1) return 1; + if (bPatternIndex === -1) return -1; + if (a.length > b.length) return -1; + if (b.length > a.length) return 1; + return 0; +} + +function fileExists(url: string | URL): boolean { + try { + const info = Deno.statSync(url); + return info.isFile; + } catch { + return false; + } +} + +function tryStatSync(path: string): { isDirectory: boolean } { + try { + const info = Deno.statSync(path); + return { isDirectory: info.isDirectory }; + } catch { + return { isDirectory: false }; + } +} + +/** + * Legacy CommonJS main resolution: + * 1. let M = pkg_url + (json main field) + * 2. TRY(M, M.js, M.json, M.node) + * 3. TRY(M/index.js, M/index.json, M/index.node) + * 4. TRY(pkg_url/index.js, pkg_url/index.json, pkg_url/index.node) + * 5. NOT_FOUND + */ +function legacyMainResolve( + packageJSONUrl: URL, + packageConfig: PackageConfig, + base: string | URL, +): URL { + let guess; + if (packageConfig.main !== undefined) { + // Note: fs check redundances will be handled by Descriptor cache here. + if ( + fileExists(guess = new URL(`./${packageConfig.main}`, packageJSONUrl)) + ) { + return guess; + } else if ( + fileExists(guess = new URL(`./${packageConfig.main}.js`, packageJSONUrl)) + ) { + // pass + } else if ( + fileExists( + guess = new URL(`./${packageConfig.main}.json`, packageJSONUrl), + ) + ) { + // pass + } else if ( + fileExists( + guess = new URL(`./${packageConfig.main}.node`, packageJSONUrl), + ) + ) { + // pass + } else if ( + fileExists( + guess = new URL(`./${packageConfig.main}/index.js`, packageJSONUrl), + ) + ) { + // pass + } else if ( + fileExists( + guess = new URL(`./${packageConfig.main}/index.json`, packageJSONUrl), + ) + ) { + // pass + } else if ( + fileExists( + guess = new URL(`./${packageConfig.main}/index.node`, packageJSONUrl), + ) + ) { + // pass + } else guess = undefined; + if (guess) { + // TODO(bartlomieju): + // emitLegacyIndexDeprecation(guess, packageJSONUrl, base, + // packageConfig.main); + return guess; + } + // Fallthrough. + } + if (fileExists(guess = new URL("./index.js", packageJSONUrl))) { + // pass + } // So fs. + else if (fileExists(guess = new URL("./index.json", packageJSONUrl))) { + // pass + } else if (fileExists(guess = new URL("./index.node", packageJSONUrl))) { + // pass + } else guess = undefined; + if (guess) { + // TODO(bartlomieju): + // emitLegacyIndexDeprecation(guess, packageJSONUrl, base, packageConfig.main); + return guess; + } + // Not found. + throw new ERR_MODULE_NOT_FOUND( + fileURLToPath(new URL(".", packageJSONUrl)), + fileURLToPath(base), + ); +} + +function parsePackageName( + specifier: string, + base: string | URL, +): { packageName: string; packageSubpath: string; isScoped: boolean } { + let separatorIndex = specifier.indexOf("/"); + let validPackageName = true; + let isScoped = false; + if (specifier[0] === "@") { + isScoped = true; + if (separatorIndex === -1 || specifier.length === 0) { + validPackageName = false; + } else { + separatorIndex = specifier.indexOf("/", separatorIndex + 1); + } + } + + const packageName = separatorIndex === -1 + ? specifier + : specifier.slice(0, separatorIndex); + + // Package name cannot have leading . and cannot have percent-encoding or + // separators. + for (let i = 0; i < packageName.length; i++) { + if (packageName[i] === "%" || packageName[i] === "\\") { + validPackageName = false; + break; + } + } + + if (!validPackageName) { + throw new ERR_INVALID_MODULE_SPECIFIER( + specifier, + "is not a valid package name", + fileURLToPath(base), + ); + } + + const packageSubpath = "." + + (separatorIndex === -1 ? "" : specifier.slice(separatorIndex)); + + return { packageName, packageSubpath, isScoped }; +} + +function packageResolve( + specifier: string, + base: string, + conditions: Set, +): URL | undefined { + const { packageName, packageSubpath, isScoped } = parsePackageName( + specifier, + base, + ); + + // ResolveSelf + const packageConfig = getPackageScopeConfig(base); + if (packageConfig.exists) { + const packageJSONUrl = pathToFileURL(packageConfig.pjsonPath); + if ( + packageConfig.name === packageName && + packageConfig.exports !== undefined && packageConfig.exports !== null + ) { + return packageExportsResolve( + packageJSONUrl.toString(), + packageSubpath, + packageConfig, + base, + conditions, + ); + } + } + + let packageJSONUrl = new URL( + "./node_modules/" + packageName + "/package.json", + base, + ); + let packageJSONPath = fileURLToPath(packageJSONUrl); + let lastPath; + do { + const stat = tryStatSync( + packageJSONPath.slice(0, packageJSONPath.length - 13), + ); + if (!stat.isDirectory) { + lastPath = packageJSONPath; + packageJSONUrl = new URL( + (isScoped ? "../../../../node_modules/" : "../../../node_modules/") + + packageName + "/package.json", + packageJSONUrl, + ); + packageJSONPath = fileURLToPath(packageJSONUrl); + continue; + } + + // Package match. + const packageConfig = getPackageConfig(packageJSONPath, specifier, base); + if (packageConfig.exports !== undefined && packageConfig.exports !== null) { + return packageExportsResolve( + packageJSONUrl.toString(), + packageSubpath, + packageConfig, + base, + conditions, + ); + } + if (packageSubpath === ".") { + return legacyMainResolve(packageJSONUrl, packageConfig, base); + } + return new URL(packageSubpath, packageJSONUrl); + // Cross-platform root check. + } while (packageJSONPath.length !== lastPath.length); + + // TODO(bartlomieju): this is false positive + // deno-lint-ignore no-unreachable + throw new ERR_MODULE_NOT_FOUND(packageName, fileURLToPath(base)); +} + +const invalidSegmentRegEx = /(^|\\|\/)(\.\.?|node_modules)(\\|\/|$)/; +const patternRegEx = /\*/g; + +function resolvePackageTargetString( + target: string, + subpath: string, + match: string, + packageJSONUrl: string, + base: string, + pattern: boolean, + internal: boolean, + conditions: Set, +): URL | undefined { + if (subpath !== "" && !pattern && target[target.length - 1] !== "/") { + throwInvalidPackageTarget(match, target, packageJSONUrl, internal, base); + } + + if (!target.startsWith("./")) { + if ( + internal && !target.startsWith("../") && + !target.startsWith("/") + ) { + let isURL = false; + try { + new URL(target); + isURL = true; + } catch { + // pass + } + if (!isURL) { + const exportTarget = pattern + ? target.replace(patternRegEx, () => subpath) + : target + subpath; + return packageResolve(exportTarget, packageJSONUrl, conditions); + } + } + throwInvalidPackageTarget(match, target, packageJSONUrl, internal, base); + } + + if (invalidSegmentRegEx.test(target.slice(2))) { + throwInvalidPackageTarget(match, target, packageJSONUrl, internal, base); + } + + const resolved = new URL(target, packageJSONUrl); + const resolvedPath = resolved.pathname; + const packagePath = new URL(".", packageJSONUrl).pathname; + + if (!resolvedPath.startsWith(packagePath)) { + throwInvalidPackageTarget(match, target, packageJSONUrl, internal, base); + } + + if (subpath === "") return resolved; + + if (invalidSegmentRegEx.test(subpath)) { + const request = pattern + ? match.replace("*", () => subpath) + : match + subpath; + throwInvalidSubpath(request, packageJSONUrl, internal, base); + } + + if (pattern) { + return new URL(resolved.href.replace(patternRegEx, () => subpath)); + } + return new URL(subpath, resolved); +} + +function isArrayIndex(key: string): boolean { + const keyNum = +key; + if (`${keyNum}` !== key) return false; + return keyNum >= 0 && keyNum < 0xFFFF_FFFF; +} + +function resolvePackageTarget( + packageJSONUrl: string, + // deno-lint-ignore no-explicit-any + target: any, + subpath: string, + packageSubpath: string, + base: string, + pattern: boolean, + internal: boolean, + conditions: Set, +): URL | undefined { + if (typeof target === "string") { + return resolvePackageTargetString( + target, + subpath, + packageSubpath, + packageJSONUrl, + base, + pattern, + internal, + conditions, + ); + } else if (Array.isArray(target)) { + if (target.length === 0) { + return undefined; + } + + let lastException; + for (let i = 0; i < target.length; i++) { + const targetItem = target[i]; + let resolved; + try { + resolved = resolvePackageTarget( + packageJSONUrl, + targetItem, + subpath, + packageSubpath, + base, + pattern, + internal, + conditions, + ); + } catch (e: unknown) { + lastException = e; + if (e instanceof NodeError && e.code === "ERR_INVALID_PACKAGE_TARGET") { + continue; + } + throw e; + } + if (resolved === undefined) { + continue; + } + if (resolved === null) { + lastException = null; + continue; + } + return resolved; + } + if (lastException === undefined || lastException === null) { + return undefined; + } + throw lastException; + } else if (typeof target === "object" && target !== null) { + const keys = Object.getOwnPropertyNames(target); + for (let i = 0; i < keys.length; i++) { + const key = keys[i]; + if (isArrayIndex(key)) { + throw new ERR_INVALID_PACKAGE_CONFIG( + fileURLToPath(packageJSONUrl), + base, + '"exports" cannot contain numeric property keys.', + ); + } + } + for (let i = 0; i < keys.length; i++) { + const key = keys[i]; + if (key === "default" || conditions.has(key)) { + const conditionalTarget = target[key]; + const resolved = resolvePackageTarget( + packageJSONUrl, + conditionalTarget, + subpath, + packageSubpath, + base, + pattern, + internal, + conditions, + ); + if (resolved === undefined) { + continue; + } + return resolved; + } + } + return undefined; + } else if (target === null) { + return undefined; + } + throwInvalidPackageTarget( + packageSubpath, + target, + packageJSONUrl, + internal, + base, + ); +} + +export function packageExportsResolve( + packageJSONUrl: string, + packageSubpath: string, + packageConfig: PackageConfig, + base: string, + conditions: Set, + // @ts-ignore `URL` needs to be forced due to control flow +): URL { + let exports = packageConfig.exports; + if (isConditionalExportsMainSugar(exports, packageJSONUrl, base)) { + exports = { ".": exports }; + } + + if ( + hasOwn(exports, packageSubpath) && + !packageSubpath.includes("*") && + !packageSubpath.endsWith("/") + ) { + const target = exports[packageSubpath]; + const resolved = resolvePackageTarget( + packageJSONUrl, + target, + "", + packageSubpath, + base, + false, + false, + conditions, + ); + if (resolved === null || resolved === undefined) { + throwExportsNotFound(packageSubpath, packageJSONUrl, base); + } + return resolved!; + } + + let bestMatch = ""; + let bestMatchSubpath = ""; + const keys = Object.getOwnPropertyNames(exports); + for (let i = 0; i < keys.length; i++) { + const key = keys[i]; + const patternIndex = key.indexOf("*"); + if ( + patternIndex !== -1 && + packageSubpath.startsWith(key.slice(0, patternIndex)) + ) { + // When this reaches EOL, this can throw at the top of the whole function: + // + // if (StringPrototypeEndsWith(packageSubpath, '/')) + // throwInvalidSubpath(packageSubpath) + // + // To match "imports" and the spec. + if (packageSubpath.endsWith("/")) { + // TODO(@bartlomieju): + // emitTrailingSlashPatternDeprecation( + // packageSubpath, + // packageJSONUrl, + // base, + // ); + } + const patternTrailer = key.slice(patternIndex + 1); + if ( + packageSubpath.length >= key.length && + packageSubpath.endsWith(patternTrailer) && + patternKeyCompare(bestMatch, key) === 1 && + key.lastIndexOf("*") === patternIndex + ) { + bestMatch = key; + bestMatchSubpath = packageSubpath.slice( + patternIndex, + packageSubpath.length - patternTrailer.length, + ); + } + } + } + + if (bestMatch) { + const target = exports[bestMatch]; + const resolved = resolvePackageTarget( + packageJSONUrl, + target, + bestMatchSubpath, + bestMatch, + base, + true, + false, + conditions, + ); + if (resolved === null || resolved === undefined) { + throwExportsNotFound(packageSubpath, packageJSONUrl, base); + } + return resolved!; + } + + throwExportsNotFound(packageSubpath, packageJSONUrl, base); +} + +export interface PackageConfig { + pjsonPath: string; + exists: boolean; + name?: string; + main?: string; + // deno-lint-ignore no-explicit-any + exports?: any; + // deno-lint-ignore no-explicit-any + imports?: any; + type?: string; +} + +const packageJSONCache = new Map(); /* string -> PackageConfig */ + +function getPackageConfig( + path: string, + specifier: string | URL, + base?: string | URL, +): PackageConfig { + const existing = packageJSONCache.get(path); + if (existing !== undefined) { + return existing; + } + + let source: string | undefined; + try { + source = new TextDecoder().decode( + Deno.readFileSync(path), + ); + } catch { + // pass + } + + if (source === undefined) { + const packageConfig = { + pjsonPath: path, + exists: false, + main: undefined, + name: undefined, + type: "none", + exports: undefined, + imports: undefined, + }; + packageJSONCache.set(path, packageConfig); + return packageConfig; + } + + let packageJSON; + try { + packageJSON = JSON.parse(source); + } catch (error) { + throw new ERR_INVALID_PACKAGE_CONFIG( + path, + (base ? `"${specifier}" from ` : "") + fileURLToPath(base || specifier), + // @ts-ignore there's no assertion for type and `error` is thus `unknown` + error.message, + ); + } + + let { imports, main, name, type } = packageJSON; + const { exports } = packageJSON; + if (typeof imports !== "object" || imports === null) imports = undefined; + if (typeof main !== "string") main = undefined; + if (typeof name !== "string") name = undefined; + // Ignore unknown types for forwards compatibility + if (type !== "module" && type !== "commonjs") type = "none"; + + const packageConfig = { + pjsonPath: path, + exists: true, + main, + name, + type, + exports, + imports, + }; + packageJSONCache.set(path, packageConfig); + return packageConfig; +} + +function getPackageScopeConfig(resolved: URL | string): PackageConfig { + let packageJSONUrl = new URL("./package.json", resolved); + while (true) { + const packageJSONPath = packageJSONUrl.pathname; + if (packageJSONPath.endsWith("node_modules/package.json")) { + break; + } + const packageConfig = getPackageConfig( + fileURLToPath(packageJSONUrl), + resolved, + ); + if (packageConfig.exists) return packageConfig; + + const lastPackageJSONUrl = packageJSONUrl; + packageJSONUrl = new URL("../package.json", packageJSONUrl); + + // Terminates at root where ../package.json equals ../../package.json + // (can't just check "/package.json" for Windows support). + if (packageJSONUrl.pathname === lastPackageJSONUrl.pathname) break; + } + const packageJSONPath = fileURLToPath(packageJSONUrl); + const packageConfig = { + pjsonPath: packageJSONPath, + exists: false, + main: undefined, + name: undefined, + type: "none", + exports: undefined, + imports: undefined, + }; + packageJSONCache.set(packageJSONPath, packageConfig); + return packageConfig; +} + +export function packageImportsResolve( + name: string, + base: string, + conditions: Set, + // @ts-ignore `URL` needs to be forced due to control flow +): URL { + if ( + name === "#" || name.startsWith("#/") || + name.startsWith("/") + ) { + const reason = "is not a valid internal imports specifier name"; + throw new ERR_INVALID_MODULE_SPECIFIER(name, reason, fileURLToPath(base)); + } + let packageJSONUrl; + const packageConfig = getPackageScopeConfig(base); + if (packageConfig.exists) { + packageJSONUrl = pathToFileURL(packageConfig.pjsonPath); + const imports = packageConfig.imports; + if (imports) { + if ( + hasOwn(imports, name) && + !name.includes("*") + ) { + const resolved = resolvePackageTarget( + packageJSONUrl.toString(), + imports[name], + "", + name, + base, + false, + true, + conditions, + ); + if (resolved !== null && resolved !== undefined) { + return resolved; + } + } else { + let bestMatch = ""; + let bestMatchSubpath = ""; + const keys = Object.getOwnPropertyNames(imports); + for (let i = 0; i < keys.length; i++) { + const key = keys[i]; + const patternIndex = key.indexOf("*"); + if ( + patternIndex !== -1 && + name.startsWith( + key.slice(0, patternIndex), + ) + ) { + const patternTrailer = key.slice(patternIndex + 1); + if ( + name.length >= key.length && + name.endsWith(patternTrailer) && + patternKeyCompare(bestMatch, key) === 1 && + key.lastIndexOf("*") === patternIndex + ) { + bestMatch = key; + bestMatchSubpath = name.slice( + patternIndex, + name.length - patternTrailer.length, + ); + } + } + } + + if (bestMatch) { + const target = imports[bestMatch]; + const resolved = resolvePackageTarget( + packageJSONUrl.toString(), + target, + bestMatchSubpath, + bestMatch, + base, + true, + true, + conditions, + ); + if (resolved !== null && resolved !== undefined) { + return resolved; + } + } + } + } + } + throwImportNotDefined(name, packageJSONUrl, base); +} + +function isConditionalExportsMainSugar( + // deno-lint-ignore no-explicit-any + exports: any, + packageJSONUrl: string, + base: string, +): boolean { + if (typeof exports === "string" || Array.isArray(exports)) return true; + if (typeof exports !== "object" || exports === null) return false; + + const keys = Object.getOwnPropertyNames(exports); + let isConditionalSugar = false; + let i = 0; + for (let j = 0; j < keys.length; j++) { + const key = keys[j]; + const curIsConditionalSugar = key === "" || key[0] !== "."; + if (i++ === 0) { + isConditionalSugar = curIsConditionalSugar; + } else if (isConditionalSugar !== curIsConditionalSugar) { + const message = + "\"exports\" cannot contain some keys starting with '.' and some not." + + " The exports object must either be an object of package subpath keys" + + " or an object of main entry condition name keys only."; + throw new ERR_INVALID_PACKAGE_CONFIG( + fileURLToPath(packageJSONUrl), + base, + message, + ); + } + } + return isConditionalSugar; +} diff --git a/ext/node/polyfills/net.ts b/ext/node/polyfills/net.ts new file mode 100644 index 00000000000000..e5f157f099171c --- /dev/null +++ b/ext/node/polyfills/net.ts @@ -0,0 +1,2483 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. +// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. + +import { notImplemented } from "internal:deno_node/polyfills/_utils.ts"; +import { EventEmitter } from "internal:deno_node/polyfills/events.ts"; +import { + isIP, + isIPv4, + isIPv6, + normalizedArgsSymbol, +} from "internal:deno_node/polyfills/internal/net.ts"; +import { Duplex } from "internal:deno_node/polyfills/stream.ts"; +import { + asyncIdSymbol, + defaultTriggerAsyncIdScope, + newAsyncId, + ownerSymbol, +} from "internal:deno_node/polyfills/internal/async_hooks.ts"; +import { + ERR_INVALID_ADDRESS_FAMILY, + ERR_INVALID_ARG_TYPE, + ERR_INVALID_ARG_VALUE, + ERR_INVALID_FD_TYPE, + ERR_INVALID_IP_ADDRESS, + ERR_MISSING_ARGS, + ERR_SERVER_ALREADY_LISTEN, + ERR_SERVER_NOT_RUNNING, + ERR_SOCKET_CLOSED, + errnoException, + exceptionWithHostPort, + genericNodeError, + uvExceptionWithHostPort, +} from "internal:deno_node/polyfills/internal/errors.ts"; +import type { ErrnoException } from "internal:deno_node/polyfills/internal/errors.ts"; +import { Encodings } from "internal:deno_node/polyfills/_utils.ts"; +import { isUint8Array } from "internal:deno_node/polyfills/internal/util/types.ts"; +import { + kAfterAsyncWrite, + kBuffer, + kBufferCb, + kBufferGen, + kHandle, + kUpdateTimer, + onStreamRead, + setStreamTimeout, + writeGeneric, + writevGeneric, +} from "internal:deno_node/polyfills/internal/stream_base_commons.ts"; +import { kTimeout } from "internal:deno_node/polyfills/internal/timers.mjs"; +import { nextTick } from "internal:deno_node/polyfills/_next_tick.ts"; +import { + DTRACE_NET_SERVER_CONNECTION, + DTRACE_NET_STREAM_END, +} from "internal:deno_node/polyfills/internal/dtrace.ts"; +import { Buffer } from "internal:deno_node/polyfills/buffer.ts"; +import type { LookupOneOptions } from "internal:deno_node/polyfills/internal/dns/utils.ts"; +import { + validateAbortSignal, + validateFunction, + validateInt32, + validateNumber, + validatePort, + validateString, +} from "internal:deno_node/polyfills/internal/validators.mjs"; +import { + constants as TCPConstants, + TCP, + TCPConnectWrap, +} from "internal:deno_node/polyfills/internal_binding/tcp_wrap.ts"; +import { + constants as PipeConstants, + Pipe, + PipeConnectWrap, +} from "internal:deno_node/polyfills/internal_binding/pipe_wrap.ts"; +import { ShutdownWrap } from "internal:deno_node/polyfills/internal_binding/stream_wrap.ts"; +import { assert } from "internal:deno_node/polyfills/_util/asserts.ts"; +import { isWindows } from "internal:deno_node/polyfills/_util/os.ts"; +import { + ADDRCONFIG, + lookup as dnsLookup, +} from "internal:deno_node/polyfills/dns.ts"; +import { codeMap } from "internal:deno_node/polyfills/internal_binding/uv.ts"; +import { guessHandleType } from "internal:deno_node/polyfills/internal_binding/util.ts"; +import { debuglog } from "internal:deno_node/polyfills/internal/util/debuglog.ts"; +import type { DuplexOptions } from "internal:deno_node/polyfills/_stream.d.ts"; +import type { BufferEncoding } from "internal:deno_node/polyfills/_global.d.ts"; +import type { Abortable } from "internal:deno_node/polyfills/_events.d.ts"; +import { channel } from "internal:deno_node/polyfills/diagnostics_channel.ts"; + +let debug = debuglog("net", (fn) => { + debug = fn; +}); + +const kLastWriteQueueSize = Symbol("lastWriteQueueSize"); +const kSetNoDelay = Symbol("kSetNoDelay"); +const kBytesRead = Symbol("kBytesRead"); +const kBytesWritten = Symbol("kBytesWritten"); + +const DEFAULT_IPV4_ADDR = "0.0.0.0"; +const DEFAULT_IPV6_ADDR = "::"; + +type Handle = TCP | Pipe; + +interface HandleOptions { + pauseOnCreate?: boolean; + manualStart?: boolean; + handle?: Handle; +} + +interface OnReadOptions { + buffer: Uint8Array | (() => Uint8Array); + /** + * This function is called for every chunk of incoming data. + * + * Two arguments are passed to it: the number of bytes written to buffer and + * a reference to buffer. + * + * Return `false` from this function to implicitly `pause()` the socket. + */ + callback(bytesWritten: number, buf: Uint8Array): boolean; +} + +interface ConnectOptions { + /** + * If specified, incoming data is stored in a single buffer and passed to the + * supplied callback when data arrives on the socket. + * + * Note: this will cause the streaming functionality to not provide any data, + * however events like `"error"`, `"end"`, and `"close"` will still be + * emitted as normal and methods like `pause()` and `resume()` will also + * behave as expected. + */ + onread?: OnReadOptions; +} + +interface SocketOptions extends ConnectOptions, HandleOptions, DuplexOptions { + /** + * If specified, wrap around an existing socket with the given file + * descriptor, otherwise a new socket will be created. + */ + fd?: number; + /** + * If set to `false`, then the socket will automatically end the writable + * side when the readable side ends. See `net.createServer()` and the `"end"` + * event for details. Default: `false`. + */ + allowHalfOpen?: boolean; + /** + * Allow reads on the socket when an fd is passed, otherwise ignored. + * Default: `false`. + */ + readable?: boolean; + /** + * Allow writes on the socket when an fd is passed, otherwise ignored. + * Default: `false`. + */ + writable?: boolean; + /** An Abort signal that may be used to destroy the socket. */ + signal?: AbortSignal; +} + +interface TcpNetConnectOptions extends TcpSocketConnectOptions, SocketOptions { + timeout?: number; +} + +interface IpcNetConnectOptions extends IpcSocketConnectOptions, SocketOptions { + timeout?: number; +} + +type NetConnectOptions = TcpNetConnectOptions | IpcNetConnectOptions; + +interface AddressInfo { + address: string; + family?: string; + port: number; +} + +type LookupFunction = ( + hostname: string, + options: LookupOneOptions, + callback: ( + err: ErrnoException | null, + address: string, + family: number, + ) => void, +) => void; + +interface TcpSocketConnectOptions extends ConnectOptions { + port: number; + host?: string; + localAddress?: string; + localPort?: number; + hints?: number; + family?: number; + lookup?: LookupFunction; +} + +interface IpcSocketConnectOptions extends ConnectOptions { + path: string; +} + +type SocketConnectOptions = TcpSocketConnectOptions | IpcSocketConnectOptions; + +function _getNewAsyncId(handle?: Handle): number { + return !handle || typeof handle.getAsyncId !== "function" + ? newAsyncId() + : handle.getAsyncId(); +} + +interface NormalizedArgs { + 0: Partial; + 1: ConnectionListener | null; + [normalizedArgsSymbol]?: boolean; +} + +const _noop = (_arrayBuffer: Uint8Array, _nread: number): undefined => { + return; +}; + +const netClientSocketChannel = channel("net.client.socket"); +const netServerSocketChannel = channel("net.server.socket"); + +function _toNumber(x: unknown): number | false { + return (x = Number(x)) >= 0 ? (x as number) : false; +} + +function _isPipeName(s: unknown): s is string { + return typeof s === "string" && _toNumber(s) === false; +} + +function _createHandle(fd: number, isServer: boolean): Handle { + validateInt32(fd, "fd", 0); + + const type = guessHandleType(fd); + + if (type === "PIPE") { + return new Pipe(isServer ? PipeConstants.SERVER : PipeConstants.SOCKET); + } + + if (type === "TCP") { + return new TCP(isServer ? TCPConstants.SERVER : TCPConstants.SOCKET); + } + + throw new ERR_INVALID_FD_TYPE(type); +} + +// Returns an array [options, cb], where options is an object, +// cb is either a function or null. +// Used to normalize arguments of `Socket.prototype.connect()` and +// `Server.prototype.listen()`. Possible combinations of parameters: +// - (options[...][, cb]) +// - (path[...][, cb]) +// - ([port][, host][...][, cb]) +// For `Socket.prototype.connect()`, the [...] part is ignored +// For `Server.prototype.listen()`, the [...] part is [, backlog] +// but will not be handled here (handled in listen()) +export function _normalizeArgs(args: unknown[]): NormalizedArgs { + let arr: NormalizedArgs; + + if (args.length === 0) { + arr = [{}, null]; + arr[normalizedArgsSymbol] = true; + + return arr; + } + + const arg0 = args[0] as Partial | number | string; + let options: Partial = {}; + + if (typeof arg0 === "object" && arg0 !== null) { + // (options[...][, cb]) + options = arg0; + } else if (_isPipeName(arg0)) { + // (path[...][, cb]) + (options as IpcSocketConnectOptions).path = arg0; + } else { + // ([port][, host][...][, cb]) + (options as TcpSocketConnectOptions).port = arg0; + + if (args.length > 1 && typeof args[1] === "string") { + (options as TcpSocketConnectOptions).host = args[1]; + } + } + + const cb = args[args.length - 1]; + + if (!_isConnectionListener(cb)) { + arr = [options, null]; + } else { + arr = [options, cb]; + } + + arr[normalizedArgsSymbol] = true; + + return arr; +} + +function _isTCPConnectWrap( + req: TCPConnectWrap | PipeConnectWrap, +): req is TCPConnectWrap { + return "localAddress" in req && "localPort" in req; +} + +function _afterConnect( + status: number, + // deno-lint-ignore no-explicit-any + handle: any, + req: PipeConnectWrap | TCPConnectWrap, + readable: boolean, + writable: boolean, +) { + let socket = handle[ownerSymbol]; + + if (socket.constructor.name === "ReusedHandle") { + socket = socket.handle; + } + + // Callback may come after call to destroy + if (socket.destroyed) { + return; + } + + debug("afterConnect"); + + assert(socket.connecting); + + socket.connecting = false; + socket._sockname = null; + + if (status === 0) { + if (socket.readable && !readable) { + socket.push(null); + socket.read(); + } + + if (socket.writable && !writable) { + socket.end(); + } + + socket._unrefTimer(); + + socket.emit("connect"); + socket.emit("ready"); + + // Start the first read, or get an immediate EOF. + // this doesn't actually consume any bytes, because len=0. + if (readable && !socket.isPaused()) { + socket.read(0); + } + } else { + socket.connecting = false; + let details; + + if (_isTCPConnectWrap(req)) { + details = req.localAddress + ":" + req.localPort; + } + + const ex = exceptionWithHostPort( + status, + "connect", + req.address, + (req as TCPConnectWrap).port, + details, + ); + + if (_isTCPConnectWrap(req)) { + ex.localAddress = req.localAddress; + ex.localPort = req.localPort; + } + + socket.destroy(ex); + } +} + +function _checkBindError(err: number, port: number, handle: TCP) { + // EADDRINUSE may not be reported until we call `listen()` or `connect()`. + // To complicate matters, a failed `bind()` followed by `listen()` or `connect()` + // will implicitly bind to a random port. Ergo, check that the socket is + // bound to the expected port before calling `listen()` or `connect()`. + if (err === 0 && port > 0 && handle.getsockname) { + const out: AddressInfo | Record = {}; + err = handle.getsockname(out); + + if (err === 0 && port !== out.port) { + err = codeMap.get("EADDRINUSE")!; + } + } + + return err; +} + +function _isPipe( + options: Partial, +): options is IpcSocketConnectOptions { + return "path" in options && !!options.path; +} + +function _connectErrorNT(socket: Socket, err: Error) { + socket.destroy(err); +} + +function _internalConnect( + socket: Socket, + address: string, + port: number, + addressType: number, + localAddress: string, + localPort: number, + flags: number, +) { + assert(socket.connecting); + + let err; + + if (localAddress || localPort) { + if (addressType === 4) { + localAddress = localAddress || DEFAULT_IPV4_ADDR; + err = (socket._handle as TCP).bind(localAddress, localPort); + } else { + // addressType === 6 + localAddress = localAddress || DEFAULT_IPV6_ADDR; + err = (socket._handle as TCP).bind6(localAddress, localPort, flags); + } + + debug( + "binding to localAddress: %s and localPort: %d (addressType: %d)", + localAddress, + localPort, + addressType, + ); + + err = _checkBindError(err, localPort, socket._handle as TCP); + + if (err) { + const ex = exceptionWithHostPort(err, "bind", localAddress, localPort); + socket.destroy(ex); + + return; + } + } + + if (addressType === 6 || addressType === 4) { + const req = new TCPConnectWrap(); + req.oncomplete = _afterConnect; + req.address = address; + req.port = port; + req.localAddress = localAddress; + req.localPort = localPort; + + if (addressType === 4) { + err = (socket._handle as TCP).connect(req, address, port); + } else { + err = (socket._handle as TCP).connect6(req, address, port); + } + } else { + const req = new PipeConnectWrap(); + req.oncomplete = _afterConnect; + req.address = address; + + err = (socket._handle as Pipe).connect(req, address); + } + + if (err) { + let details = ""; + + const sockname = socket._getsockname(); + + if (sockname) { + details = `${sockname.address}:${sockname.port}`; + } + + const ex = exceptionWithHostPort(err, "connect", address, port, details); + socket.destroy(ex); + } +} + +// Provide a better error message when we call end() as a result +// of the other side sending a FIN. The standard "write after end" +// is overly vague, and makes it seem like the user's code is to blame. +function _writeAfterFIN( + this: Socket, + // deno-lint-ignore no-explicit-any + chunk: any, + encoding?: + | BufferEncoding + | null + | ((error: Error | null | undefined) => void), + cb?: (error: Error | null | undefined) => void, +): boolean { + if (!this.writableEnded) { + return Duplex.prototype.write.call( + this, + chunk, + encoding as BufferEncoding | null, + // @ts-expect-error Using `call` seem to be interfering with the overload for write + cb, + ); + } + + if (typeof encoding === "function") { + cb = encoding; + encoding = null; + } + + const err = genericNodeError( + "This socket has been ended by the other party", + { code: "EPIPE" }, + ); + + if (typeof cb === "function") { + defaultTriggerAsyncIdScope(this[asyncIdSymbol], nextTick, cb, err); + } + + if (this._server) { + nextTick(() => this.destroy(err)); + } else { + this.destroy(err); + } + + return false; +} + +function _tryReadStart(socket: Socket) { + // Not already reading, start the flow. + debug("Socket._handle.readStart"); + socket._handle!.reading = true; + const err = socket._handle!.readStart(); + + if (err) { + socket.destroy(errnoException(err, "read")); + } +} + +// Called when the "end" event is emitted. +function _onReadableStreamEnd(this: Socket) { + if (!this.allowHalfOpen) { + this.write = _writeAfterFIN; + } +} + +// Called when creating new Socket, or when re-using a closed Socket +function _initSocketHandle(socket: Socket) { + socket._undestroy(); + socket._sockname = undefined; + + // Handle creation may be deferred to bind() or connect() time. + if (socket._handle) { + // deno-lint-ignore no-explicit-any + (socket._handle as any)[ownerSymbol] = socket; + socket._handle.onread = onStreamRead; + socket[asyncIdSymbol] = _getNewAsyncId(socket._handle); + + let userBuf = socket[kBuffer]; + + if (userBuf) { + const bufGen = socket[kBufferGen]; + + if (bufGen !== null) { + userBuf = bufGen(); + + if (!isUint8Array(userBuf)) { + return; + } + + socket[kBuffer] = userBuf; + } + + socket._handle.useUserBuffer(userBuf); + } + } +} + +function _lookupAndConnect( + self: Socket, + options: TcpSocketConnectOptions, +) { + const { localAddress, localPort } = options; + const host = options.host || "localhost"; + let { port } = options; + + if (localAddress && !isIP(localAddress)) { + throw new ERR_INVALID_IP_ADDRESS(localAddress); + } + + if (localPort) { + validateNumber(localPort, "options.localPort"); + } + + if (typeof port !== "undefined") { + if (typeof port !== "number" && typeof port !== "string") { + throw new ERR_INVALID_ARG_TYPE( + "options.port", + ["number", "string"], + port, + ); + } + + validatePort(port); + } + + port |= 0; + + // If host is an IP, skip performing a lookup + const addressType = isIP(host); + if (addressType) { + defaultTriggerAsyncIdScope(self[asyncIdSymbol], nextTick, () => { + if (self.connecting) { + defaultTriggerAsyncIdScope( + self[asyncIdSymbol], + _internalConnect, + self, + host, + port, + addressType, + localAddress, + localPort, + ); + } + }); + + return; + } + + if (options.lookup !== undefined) { + validateFunction(options.lookup, "options.lookup"); + } + + const dnsOpts = { + family: options.family, + hints: options.hints || 0, + }; + + if ( + !isWindows && + dnsOpts.family !== 4 && + dnsOpts.family !== 6 && + dnsOpts.hints === 0 + ) { + dnsOpts.hints = ADDRCONFIG; + } + + debug("connect: find host", host); + debug("connect: dns options", dnsOpts); + self._host = host; + const lookup = options.lookup || dnsLookup; + + defaultTriggerAsyncIdScope(self[asyncIdSymbol], function () { + lookup( + host, + dnsOpts, + function emitLookup( + err: ErrnoException | null, + ip: string, + addressType: number, + ) { + self.emit("lookup", err, ip, addressType, host); + + // It's possible we were destroyed while looking this up. + // XXX it would be great if we could cancel the promise returned by + // the look up. + if (!self.connecting) { + return; + } + + if (err) { + // net.createConnection() creates a net.Socket object and immediately + // calls net.Socket.connect() on it (that's us). There are no event + // listeners registered yet so defer the error event to the next tick. + nextTick(_connectErrorNT, self, err); + } else if (!isIP(ip)) { + err = new ERR_INVALID_IP_ADDRESS(ip); + + nextTick(_connectErrorNT, self, err); + } else if (addressType !== 4 && addressType !== 6) { + err = new ERR_INVALID_ADDRESS_FAMILY( + `${addressType}`, + options.host!, + options.port, + ); + + nextTick(_connectErrorNT, self, err); + } else { + self._unrefTimer(); + + defaultTriggerAsyncIdScope( + self[asyncIdSymbol], + _internalConnect, + self, + ip, + port, + addressType, + localAddress, + localPort, + ); + } + }, + ); + }); +} + +function _afterShutdown(this: ShutdownWrap) { + // deno-lint-ignore no-explicit-any + const self: any = this.handle[ownerSymbol]; + + debug("afterShutdown destroyed=%j", self.destroyed, self._readableState); + + this.callback(); +} + +function _emitCloseNT(s: Socket | Server) { + debug("SERVER: emit close"); + s.emit("close"); +} + +/** + * This class is an abstraction of a TCP socket or a streaming `IPC` endpoint + * (uses named pipes on Windows, and Unix domain sockets otherwise). It is also + * an `EventEmitter`. + * + * A `net.Socket` can be created by the user and used directly to interact with + * a server. For example, it is returned by `createConnection`, + * so the user can use it to talk to the server. + * + * It can also be created by Node.js and passed to the user when a connection + * is received. For example, it is passed to the listeners of a `"connection"` event emitted on a `Server`, so the user can use + * it to interact with the client. + */ +export class Socket extends Duplex { + // Problem with this is that users can supply their own handle, that may not + // have `handle.getAsyncId()`. In this case an `[asyncIdSymbol]` should + // probably be supplied by `async_hooks`. + [asyncIdSymbol] = -1; + + [kHandle]: Handle | null = null; + [kSetNoDelay] = false; + [kLastWriteQueueSize] = 0; + // deno-lint-ignore no-explicit-any + [kTimeout]: any = null; + [kBuffer]: Uint8Array | boolean | null = null; + [kBufferCb]: OnReadOptions["callback"] | null = null; + [kBufferGen]: (() => Uint8Array) | null = null; + + // Used after `.destroy()` + [kBytesRead] = 0; + [kBytesWritten] = 0; + + // Reserved properties + server = null; + // deno-lint-ignore no-explicit-any + _server: any = null; + + _peername?: AddressInfo | Record; + _sockname?: AddressInfo | Record; + _pendingData: Uint8Array | string | null = null; + _pendingEncoding = ""; + _host: string | null = null; + // deno-lint-ignore no-explicit-any + _parent: any = null; + + constructor(options: SocketOptions | number) { + if (typeof options === "number") { + // Legacy interface. + options = { fd: options }; + } else { + options = { ...options }; + } + + // Default to *not* allowing half open sockets. + options.allowHalfOpen = Boolean(options.allowHalfOpen); + // For backwards compat do not emit close on destroy. + options.emitClose = false; + options.autoDestroy = true; + // Handle strings directly. + options.decodeStrings = false; + + super(options); + + if (options.handle) { + this._handle = options.handle; + this[asyncIdSymbol] = _getNewAsyncId(this._handle); + } else if (options.fd !== undefined) { + // REF: https://github.com/denoland/deno/issues/6529 + notImplemented("net.Socket.prototype.constructor with fd option"); + } + + const onread = options.onread; + + if ( + onread !== null && + typeof onread === "object" && + (isUint8Array(onread.buffer) || typeof onread.buffer === "function") && + typeof onread.callback === "function" + ) { + if (typeof onread.buffer === "function") { + this[kBuffer] = true; + this[kBufferGen] = onread.buffer; + } else { + this[kBuffer] = onread.buffer; + } + + this[kBufferCb] = onread.callback; + } + + this.on("end", _onReadableStreamEnd); + + _initSocketHandle(this); + + // If we have a handle, then start the flow of data into the + // buffer. If not, then this will happen when we connect. + if (this._handle && options.readable !== false) { + if (options.pauseOnCreate) { + // Stop the handle from reading and pause the stream + this._handle.reading = false; + this._handle.readStop(); + // @ts-expect-error This property shouldn't be modified + this.readableFlowing = false; + } else if (!options.manualStart) { + this.read(0); + } + } + } + + /** + * Initiate a connection on a given socket. + * + * Possible signatures: + * + * - `socket.connect(options[, connectListener])` + * - `socket.connect(path[, connectListener])` for `IPC` connections. + * - `socket.connect(port[, host][, connectListener])` for TCP connections. + * - Returns: `net.Socket` The socket itself. + * + * This function is asynchronous. When the connection is established, the `"connect"` event will be emitted. If there is a problem connecting, + * instead of a `"connect"` event, an `"error"` event will be emitted with + * the error passed to the `"error"` listener. + * The last parameter `connectListener`, if supplied, will be added as a listener + * for the `"connect"` event **once**. + * + * This function should only be used for reconnecting a socket after `"close"` has been emitted or otherwise it may lead to undefined + * behavior. + */ + connect( + options: SocketConnectOptions | NormalizedArgs, + connectionListener?: ConnectionListener, + ): this; + connect( + port: number, + host: string, + connectionListener?: ConnectionListener, + ): this; + connect(port: number, connectionListener?: ConnectionListener): this; + connect(path: string, connectionListener?: ConnectionListener): this; + connect(...args: unknown[]): this { + let normalized: NormalizedArgs; + + // If passed an array, it's treated as an array of arguments that have + // already been normalized (so we don't normalize more than once). This has + // been solved before in https://github.com/nodejs/node/pull/12342, but was + // reverted as it had unintended side effects. + if ( + Array.isArray(args[0]) && + (args[0] as unknown as NormalizedArgs)[normalizedArgsSymbol] + ) { + normalized = args[0] as unknown as NormalizedArgs; + } else { + normalized = _normalizeArgs(args); + } + + const options = normalized[0]; + const cb = normalized[1]; + + // `options.port === null` will be checked later. + if ( + (options as TcpSocketConnectOptions).port === undefined && + (options as IpcSocketConnectOptions).path == null + ) { + throw new ERR_MISSING_ARGS(["options", "port", "path"]); + } + + if (this.write !== Socket.prototype.write) { + this.write = Socket.prototype.write; + } + + if (this.destroyed) { + this._handle = null; + this._peername = undefined; + this._sockname = undefined; + } + + const { path } = options as IpcNetConnectOptions; + const pipe = _isPipe(options); + debug("pipe", pipe, path); + + if (!this._handle) { + this._handle = pipe + ? new Pipe(PipeConstants.SOCKET) + : new TCP(TCPConstants.SOCKET); + + _initSocketHandle(this); + } + + if (cb !== null) { + this.once("connect", cb); + } + + this._unrefTimer(); + + this.connecting = true; + + if (pipe) { + validateString(path, "options.path"); + defaultTriggerAsyncIdScope( + this[asyncIdSymbol], + _internalConnect, + this, + path, + ); + } else { + _lookupAndConnect(this, options as TcpSocketConnectOptions); + } + + return this; + } + + /** + * Pauses the reading of data. That is, `"data"` events will not be emitted. + * Useful to throttle back an upload. + * + * @return The socket itself. + */ + override pause(): this { + if ( + this[kBuffer] && + !this.connecting && + this._handle && + this._handle.reading + ) { + this._handle.reading = false; + + if (!this.destroyed) { + const err = this._handle.readStop(); + + if (err) { + this.destroy(errnoException(err, "read")); + } + } + } + + return Duplex.prototype.pause.call(this) as unknown as this; + } + + /** + * Resumes reading after a call to `socket.pause()`. + * + * @return The socket itself. + */ + override resume(): this { + if ( + this[kBuffer] && + !this.connecting && + this._handle && + !this._handle.reading + ) { + _tryReadStart(this); + } + + return Duplex.prototype.resume.call(this) as this; + } + + /** + * Sets the socket to timeout after `timeout` milliseconds of inactivity on + * the socket. By default `net.Socket` do not have a timeout. + * + * When an idle timeout is triggered the socket will receive a `"timeout"` event but the connection will not be severed. The user must manually call `socket.end()` or `socket.destroy()` to + * end the connection. + * + * If `timeout` is `0`, then the existing idle timeout is disabled. + * + * The optional `callback` parameter will be added as a one-time listener for the `"timeout"` event. + * @return The socket itself. + */ + setTimeout = setStreamTimeout; + + /** + * Enable/disable the use of Nagle's algorithm. + * + * When a TCP connection is created, it will have Nagle's algorithm enabled. + * + * Nagle's algorithm delays data before it is sent via the network. It attempts + * to optimize throughput at the expense of latency. + * + * Passing `true` for `noDelay` or not passing an argument will disable Nagle's + * algorithm for the socket. Passing `false` for `noDelay` will enable Nagle's + * algorithm. + * + * @param noDelay + * @return The socket itself. + */ + setNoDelay(noDelay?: boolean): this { + if (!this._handle) { + this.once( + "connect", + noDelay ? this.setNoDelay : () => this.setNoDelay(noDelay), + ); + + return this; + } + + // Backwards compatibility: assume true when `noDelay` is omitted + const newValue = noDelay === undefined ? true : !!noDelay; + + if ( + "setNoDelay" in this._handle && + this._handle.setNoDelay && + newValue !== this[kSetNoDelay] + ) { + this[kSetNoDelay] = newValue; + this._handle.setNoDelay(newValue); + } + + return this; + } + + /** + * Enable/disable keep-alive functionality, and optionally set the initial + * delay before the first keepalive probe is sent on an idle socket. + * + * Set `initialDelay` (in milliseconds) to set the delay between the last + * data packet received and the first keepalive probe. Setting `0` for`initialDelay` will leave the value unchanged from the default + * (or previous) setting. + * + * Enabling the keep-alive functionality will set the following socket options: + * + * - `SO_KEEPALIVE=1` + * - `TCP_KEEPIDLE=initialDelay` + * - `TCP_KEEPCNT=10` + * - `TCP_KEEPINTVL=1` + * + * @param enable + * @param initialDelay + * @return The socket itself. + */ + setKeepAlive(enable: boolean, initialDelay?: number): this { + if (!this._handle) { + this.once("connect", () => this.setKeepAlive(enable, initialDelay)); + + return this; + } + + if ("setKeepAlive" in this._handle) { + this._handle.setKeepAlive(enable, ~~(initialDelay! / 1000)); + } + + return this; + } + + /** + * Returns the bound `address`, the address `family` name and `port` of the + * socket as reported by the operating system:`{ port: 12346, family: "IPv4", address: "127.0.0.1" }` + */ + address(): AddressInfo | Record { + return this._getsockname(); + } + + /** + * Calling `unref()` on a socket will allow the program to exit if this is the only + * active socket in the event system. If the socket is already `unref`ed calling`unref()` again will have no effect. + * + * @return The socket itself. + */ + unref(): this { + if (!this._handle) { + this.once("connect", this.unref); + + return this; + } + + if (typeof this._handle.unref === "function") { + this._handle.unref(); + } + + return this; + } + + /** + * Opposite of `unref()`, calling `ref()` on a previously `unref`ed socket will_not_ let the program exit if it's the only socket left (the default behavior). + * If the socket is `ref`ed calling `ref` again will have no effect. + * + * @return The socket itself. + */ + ref(): this { + if (!this._handle) { + this.once("connect", this.ref); + + return this; + } + + if (typeof this._handle.ref === "function") { + this._handle.ref(); + } + + return this; + } + + /** + * This property shows the number of characters buffered for writing. The buffer + * may contain strings whose length after encoding is not yet known. So this number + * is only an approximation of the number of bytes in the buffer. + * + * `net.Socket` has the property that `socket.write()` always works. This is to + * help users get up and running quickly. The computer cannot always keep up + * with the amount of data that is written to a socket. The network connection + * simply might be too slow. Node.js will internally queue up the data written to a + * socket and send it out over the wire when it is possible. + * + * The consequence of this internal buffering is that memory may grow. + * Users who experience large or growing `bufferSize` should attempt to + * "throttle" the data flows in their program with `socket.pause()` and `socket.resume()`. + * + * @deprecated Use `writableLength` instead. + */ + get bufferSize(): number { + if (this._handle) { + return this.writableLength; + } + + return 0; + } + + /** + * The amount of received bytes. + */ + get bytesRead(): number { + return this._handle ? this._handle.bytesRead : this[kBytesRead]; + } + + /** + * The amount of bytes sent. + */ + get bytesWritten(): number | undefined { + let bytes = this._bytesDispatched; + const data = this._pendingData; + const encoding = this._pendingEncoding; + const writableBuffer = this.writableBuffer; + + if (!writableBuffer) { + return undefined; + } + + for (const el of writableBuffer) { + bytes += el!.chunk instanceof Buffer + ? el!.chunk.length + : Buffer.byteLength(el!.chunk, el!.encoding); + } + + if (Array.isArray(data)) { + // Was a writev, iterate over chunks to get total length + for (let i = 0; i < data.length; i++) { + const chunk = data[i]; + + // deno-lint-ignore no-explicit-any + if ((data as any).allBuffers || chunk instanceof Buffer) { + bytes += chunk.length; + } else { + bytes += Buffer.byteLength(chunk.chunk, chunk.encoding); + } + } + } else if (data) { + // Writes are either a string or a Buffer. + if (typeof data !== "string") { + bytes += (data as Buffer).length; + } else { + bytes += Buffer.byteLength(data, encoding); + } + } + + return bytes; + } + + /** + * If `true`,`socket.connect(options[, connectListener])` was + * called and has not yet finished. It will stay `true` until the socket becomes + * connected, then it is set to `false` and the `"connect"` event is emitted. Note + * that the `socket.connect(options[, connectListener])` callback is a listener for the `"connect"` event. + */ + connecting = false; + + /** + * The string representation of the local IP address the remote client is + * connecting on. For example, in a server listening on `"0.0.0.0"`, if a client + * connects on `"192.168.1.1"`, the value of `socket.localAddress` would be`"192.168.1.1"`. + */ + get localAddress(): string { + return this._getsockname().address; + } + + /** + * The numeric representation of the local port. For example, `80` or `21`. + */ + get localPort(): number { + return this._getsockname().port; + } + + /** + * The string representation of the local IP family. `"IPv4"` or `"IPv6"`. + */ + get localFamily(): string | undefined { + return this._getsockname().family; + } + + /** + * The string representation of the remote IP address. For example,`"74.125.127.100"` or `"2001:4860:a005::68"`. Value may be `undefined` if + * the socket is destroyed (for example, if the client disconnected). + */ + get remoteAddress(): string | undefined { + return this._getpeername().address; + } + + /** + * The string representation of the remote IP family. `"IPv4"` or `"IPv6"`. + */ + get remoteFamily(): string | undefined { + const { family } = this._getpeername(); + + return family ? `IPv${family}` : family; + } + + /** + * The numeric representation of the remote port. For example, `80` or `21`. + */ + get remotePort(): number | undefined { + return this._getpeername().port; + } + + get pending(): boolean { + return !this._handle || this.connecting; + } + + get readyState(): string { + if (this.connecting) { + return "opening"; + } else if (this.readable && this.writable) { + return "open"; + } else if (this.readable && !this.writable) { + return "readOnly"; + } else if (!this.readable && this.writable) { + return "writeOnly"; + } + return "closed"; + } + + /** + * Half-closes the socket. i.e., it sends a FIN packet. It is possible the + * server will still send some data. + * + * See `writable.end()` for further details. + * + * @param encoding Only used when data is `string`. + * @param cb Optional callback for when the socket is finished. + * @return The socket itself. + */ + override end(cb?: () => void): this; + override end(buffer: Uint8Array | string, cb?: () => void): this; + override end( + data: Uint8Array | string, + encoding?: Encodings, + cb?: () => void, + ): this; + override end( + data?: Uint8Array | string | (() => void), + encoding?: Encodings | (() => void), + cb?: () => void, + ): this { + Duplex.prototype.end.call(this, data, encoding as Encodings, cb); + DTRACE_NET_STREAM_END(this); + + return this; + } + + /** + * @param size Optional argument to specify how much data to read. + */ + override read( + size?: number, + ): string | Uint8Array | Buffer | null | undefined { + if ( + this[kBuffer] && + !this.connecting && + this._handle && + !this._handle.reading + ) { + _tryReadStart(this); + } + + return Duplex.prototype.read.call(this, size); + } + + destroySoon() { + if (this.writable) { + this.end(); + } + + if (this.writableFinished) { + this.destroy(); + } else { + this.once("finish", this.destroy); + } + } + + _unrefTimer() { + // deno-lint-ignore no-this-alias + for (let s = this; s !== null; s = s._parent) { + if (s[kTimeout]) { + s[kTimeout].refresh(); + } + } + } + + // The user has called .end(), and all the bytes have been + // sent out to the other side. + // deno-lint-ignore no-explicit-any + override _final(cb: any): any { + // If still connecting - defer handling `_final` until 'connect' will happen + if (this.pending) { + debug("_final: not yet connected"); + return this.once("connect", () => this._final(cb)); + } + + if (!this._handle) { + return cb(); + } + + debug("_final: not ended, call shutdown()"); + + const req = new ShutdownWrap(); + req.oncomplete = _afterShutdown; + req.handle = this._handle; + req.callback = cb; + const err = this._handle.shutdown(req); + + if (err === 1 || err === codeMap.get("ENOTCONN")) { + // synchronous finish + return cb(); + } else if (err !== 0) { + return cb(errnoException(err, "shutdown")); + } + } + + _onTimeout() { + const handle = this._handle; + const lastWriteQueueSize = this[kLastWriteQueueSize]; + + if (lastWriteQueueSize > 0 && handle) { + // `lastWriteQueueSize !== writeQueueSize` means there is + // an active write in progress, so we suppress the timeout. + const { writeQueueSize } = handle; + + if (lastWriteQueueSize !== writeQueueSize) { + this[kLastWriteQueueSize] = writeQueueSize; + this._unrefTimer(); + + return; + } + } + + debug("_onTimeout"); + this.emit("timeout"); + } + + override _read(size?: number) { + debug("_read"); + if (this.connecting || !this._handle) { + debug("_read wait for connection"); + this.once("connect", () => this._read(size)); + } else if (!this._handle.reading) { + _tryReadStart(this); + } + } + + override _destroy(exception: Error | null, cb: (err: Error | null) => void) { + debug("destroy"); + this.connecting = false; + + // deno-lint-ignore no-this-alias + for (let s = this; s !== null; s = s._parent) { + clearTimeout(s[kTimeout]); + } + + debug("close"); + if (this._handle) { + debug("close handle"); + const isException = exception ? true : false; + // `bytesRead` and `kBytesWritten` should be accessible after `.destroy()` + this[kBytesRead] = this._handle.bytesRead; + this[kBytesWritten] = this._handle.bytesWritten; + + this._handle.close(() => { + this._handle!.onread = _noop; + this._handle = null; + this._sockname = undefined; + + debug("emit close"); + this.emit("close", isException); + }); + cb(exception); + } else { + cb(exception); + nextTick(_emitCloseNT, this); + } + + if (this._server) { + debug("has server"); + this._server._connections--; + + if (this._server._emitCloseIfDrained) { + this._server._emitCloseIfDrained(); + } + } + } + + _getpeername(): AddressInfo | Record { + if (!this._handle || !("getpeername" in this._handle) || this.connecting) { + return this._peername || {}; + } else if (!this._peername) { + this._peername = {}; + this._handle.getpeername(this._peername); + } + + return this._peername; + } + + _getsockname(): AddressInfo | Record { + if (!this._handle || !("getsockname" in this._handle)) { + return {}; + } else if (!this._sockname) { + this._sockname = {}; + this._handle.getsockname(this._sockname); + } + + return this._sockname; + } + + _writeGeneric( + writev: boolean, + // deno-lint-ignore no-explicit-any + data: any, + encoding: string, + cb: (error?: Error | null) => void, + ) { + // If we are still connecting, then buffer this for later. + // The Writable logic will buffer up any more writes while + // waiting for this one to be done. + if (this.connecting) { + this._pendingData = data; + this._pendingEncoding = encoding; + this.once("connect", function connect(this: Socket) { + this._writeGeneric(writev, data, encoding, cb); + }); + + return; + } + + this._pendingData = null; + this._pendingEncoding = ""; + + if (!this._handle) { + cb(new ERR_SOCKET_CLOSED()); + + return false; + } + + this._unrefTimer(); + + let req; + + if (writev) { + req = writevGeneric(this, data, cb); + } else { + req = writeGeneric(this, data, encoding, cb); + } + if (req.async) { + this[kLastWriteQueueSize] = req.bytes; + } + } + + // @ts-ignore Duplex defining as a property when want a method. + _writev( + // deno-lint-ignore no-explicit-any + chunks: Array<{ chunk: any; encoding: string }>, + cb: (error?: Error | null) => void, + ) { + this._writeGeneric(true, chunks, "", cb); + } + + override _write( + // deno-lint-ignore no-explicit-any + data: any, + encoding: string, + cb: (error?: Error | null) => void, + ) { + this._writeGeneric(false, data, encoding, cb); + } + + [kAfterAsyncWrite]() { + this[kLastWriteQueueSize] = 0; + } + + get [kUpdateTimer]() { + return this._unrefTimer; + } + + get _connecting(): boolean { + return this.connecting; + } + + // Legacy alias. Having this is probably being overly cautious, but it doesn't + // really hurt anyone either. This can probably be removed safely if desired. + get _bytesDispatched(): number { + return this._handle ? this._handle.bytesWritten : this[kBytesWritten]; + } + + get _handle(): Handle | null { + return this[kHandle]; + } + + set _handle(v: Handle | null) { + this[kHandle] = v; + } +} + +export const Stream = Socket; + +// Target API: +// +// let s = net.connect({port: 80, host: 'google.com'}, function() { +// ... +// }); +// +// There are various forms: +// +// connect(options, [cb]) +// connect(port, [host], [cb]) +// connect(path, [cb]); +// +export function connect( + options: NetConnectOptions, + connectionListener?: () => void, +): Socket; +export function connect( + port: number, + host?: string, + connectionListener?: () => void, +): Socket; +export function connect(path: string, connectionListener?: () => void): Socket; +export function connect(...args: unknown[]) { + const normalized = _normalizeArgs(args); + const options = normalized[0] as Partial; + debug("createConnection", normalized); + const socket = new Socket(options); + + if (netClientSocketChannel.hasSubscribers) { + netClientSocketChannel.publish({ + socket, + }); + } + + if (options.timeout) { + socket.setTimeout(options.timeout); + } + + return socket.connect(normalized); +} + +export const createConnection = connect; + +export interface ListenOptions extends Abortable { + fd?: number; + port?: number | undefined; + host?: string | undefined; + backlog?: number | undefined; + path?: string | undefined; + exclusive?: boolean | undefined; + readableAll?: boolean | undefined; + writableAll?: boolean | undefined; + /** + * Default: `false` + */ + ipv6Only?: boolean | undefined; +} + +type ConnectionListener = (socket: Socket) => void; + +interface ServerOptions { + /** + * Indicates whether half-opened TCP connections are allowed. + * Default: false + */ + allowHalfOpen?: boolean | undefined; + /** + * Indicates whether the socket should be paused on incoming connections. + * Default: false + */ + pauseOnConnect?: boolean | undefined; +} + +function _isServerSocketOptions( + options: unknown, +): options is null | undefined | ServerOptions { + return ( + options === null || + typeof options === "undefined" || + typeof options === "object" + ); +} + +function _isConnectionListener( + connectionListener: unknown, +): connectionListener is ConnectionListener { + return typeof connectionListener === "function"; +} + +function _getFlags(ipv6Only?: boolean): number { + return ipv6Only === true ? TCPConstants.UV_TCP_IPV6ONLY : 0; +} + +function _listenInCluster( + server: Server, + address: string | null, + port: number | null, + addressType: number | null, + backlog: number, + fd?: number | null, + exclusive?: boolean, + flags?: number, +) { + exclusive = !!exclusive; + + // TODO(cmorten): here we deviate somewhat from the Node implementation which + // makes use of the https://nodejs.org/api/cluster.html module to run servers + // across a "cluster" of Node processes to take advantage of multi-core + // systems. + // + // Though Deno has has a Worker capability from which we could simulate this, + // for now we assert that we are _always_ on the primary process. + const isPrimary = true; + + if (isPrimary || exclusive) { + // Will create a new handle + // _listen2 sets up the listened handle, it is still named like this + // to avoid breaking code that wraps this method + server._listen2(address, port, addressType, backlog, fd, flags); + + return; + } +} + +function _lookupAndListen( + server: Server, + port: number, + address: string, + backlog: number, + exclusive: boolean, + flags: number, +) { + dnsLookup(address, function doListen(err, ip, addressType) { + if (err) { + server.emit("error", err); + } else { + addressType = ip ? addressType : 4; + + _listenInCluster( + server, + ip, + port, + addressType, + backlog, + null, + exclusive, + flags, + ); + } + }); +} + +function _addAbortSignalOption(server: Server, options: ListenOptions) { + if (options?.signal === undefined) { + return; + } + + validateAbortSignal(options.signal, "options.signal"); + + const { signal } = options; + + const onAborted = () => { + server.close(); + }; + + if (signal.aborted) { + nextTick(onAborted); + } else { + signal.addEventListener("abort", onAborted); + server.once("close", () => signal.removeEventListener("abort", onAborted)); + } +} + +// Returns handle if it can be created, or error code if it can't +export function _createServerHandle( + address: string | null, + port: number | null, + addressType: number | null, + fd?: number | null, + flags?: number, +): Handle | number { + let err = 0; + // Assign handle in listen, and clean up if bind or listen fails + let handle; + let isTCP = false; + + if (typeof fd === "number" && fd >= 0) { + try { + handle = _createHandle(fd, true); + } catch (e) { + // Not a fd we can listen on. This will trigger an error. + debug("listen invalid fd=%d:", fd, (e as Error).message); + + return codeMap.get("EINVAL")!; + } + + err = handle.open(fd); + + if (err) { + return err; + } + + assert(!address && !port); + } else if (port === -1 && addressType === -1) { + handle = new Pipe(PipeConstants.SERVER); + + if (isWindows) { + const instances = Number.parseInt( + Deno.env.get("NODE_PENDING_PIPE_INSTANCES") ?? "", + ); + + if (!Number.isNaN(instances)) { + handle.setPendingInstances!(instances); + } + } + } else { + handle = new TCP(TCPConstants.SERVER); + isTCP = true; + } + + if (address || port || isTCP) { + debug("bind to", address || "any"); + + if (!address) { + // TODO(@bartlomieju): differs from Node which tries to bind to IPv6 first when no + // address is provided. + // + // Forcing IPv4 as a workaround for Deno not aligning with Node on + // implicit binding on Windows. + // + // REF: https://github.com/denoland/deno/issues/10762 + + // Try binding to ipv6 first + // err = (handle as TCP).bind6(DEFAULT_IPV6_ADDR, port ?? 0, flags ?? 0); + + // if (err) { + // handle.close(); + + // Fallback to ipv4 + return _createServerHandle(DEFAULT_IPV4_ADDR, port, 4, null, flags); + // } + } else if (addressType === 6) { + err = (handle as TCP).bind6(address, port ?? 0, flags ?? 0); + } else { + err = (handle as TCP).bind(address, port ?? 0); + } + } + + if (err) { + handle.close(); + + return err; + } + + return handle; +} + +function _emitErrorNT(server: Server, err: Error) { + server.emit("error", err); +} + +function _emitListeningNT(server: Server) { + // Ensure handle hasn't closed + if (server._handle) { + server.emit("listening"); + } +} + +// deno-lint-ignore no-explicit-any +function _onconnection(this: any, err: number, clientHandle?: Handle) { + // deno-lint-ignore no-this-alias + const handle = this; + const self = handle[ownerSymbol]; + + debug("onconnection"); + + if (err) { + self.emit("error", errnoException(err, "accept")); + + return; + } + + if (self.maxConnections && self._connections >= self.maxConnections) { + clientHandle!.close(); + + return; + } + + const socket = new Socket({ + handle: clientHandle, + allowHalfOpen: self.allowHalfOpen, + pauseOnCreate: self.pauseOnConnect, + readable: true, + writable: true, + }); + + // TODO(@bartlomieju): implement noDelay and setKeepAlive + + self._connections++; + socket.server = self; + socket._server = self; + + DTRACE_NET_SERVER_CONNECTION(socket); + self.emit("connection", socket); + + if (netServerSocketChannel.hasSubscribers) { + netServerSocketChannel.publish({ + socket, + }); + } +} + +function _setupListenHandle( + this: Server, + address: string | null, + port: number | null, + addressType: number | null, + backlog: number, + fd?: number | null, + flags?: number, +) { + debug("setupListenHandle", address, port, addressType, backlog, fd); + + // If there is not yet a handle, we need to create one and bind. + // In the case of a server sent via IPC, we don't need to do this. + if (this._handle) { + debug("setupListenHandle: have a handle already"); + } else { + debug("setupListenHandle: create a handle"); + + let rval = null; + + // Try to bind to the unspecified IPv6 address, see if IPv6 is available + if (!address && typeof fd !== "number") { + // TODO(@bartlomieju): differs from Node which tries to bind to IPv6 first + // when no address is provided. + // + // Forcing IPv4 as a workaround for Deno not aligning with Node on + // implicit binding on Windows. + // + // REF: https://github.com/denoland/deno/issues/10762 + // rval = _createServerHandle(DEFAULT_IPV6_ADDR, port, 6, fd, flags); + + // if (typeof rval === "number") { + // rval = null; + address = DEFAULT_IPV4_ADDR; + addressType = 4; + // } else { + // address = DEFAULT_IPV6_ADDR; + // addressType = 6; + // } + } + + if (rval === null) { + rval = _createServerHandle(address, port, addressType, fd, flags); + } + + if (typeof rval === "number") { + const error = uvExceptionWithHostPort(rval, "listen", address, port); + nextTick(_emitErrorNT, this, error); + + return; + } + + this._handle = rval; + } + + this[asyncIdSymbol] = _getNewAsyncId(this._handle); + this._handle.onconnection = _onconnection; + this._handle[ownerSymbol] = this; + + // Use a backlog of 512 entries. We pass 511 to the listen() call because + // the kernel does: backlogsize = roundup_pow_of_two(backlogsize + 1); + // which will thus give us a backlog of 512 entries. + const err = this._handle.listen(backlog || 511); + + if (err) { + const ex = uvExceptionWithHostPort(err, "listen", address, port); + this._handle.close(); + this._handle = null; + + defaultTriggerAsyncIdScope( + this[asyncIdSymbol], + nextTick, + _emitErrorNT, + this, + ex, + ); + + return; + } + + // Generate connection key, this should be unique to the connection + this._connectionKey = addressType + ":" + address + ":" + port; + + // Unref the handle if the server was unref'ed prior to listening + if (this._unref) { + this.unref(); + } + + defaultTriggerAsyncIdScope( + this[asyncIdSymbol], + nextTick, + _emitListeningNT, + this, + ); +} + +/** This class is used to create a TCP or IPC server. */ +export class Server extends EventEmitter { + [asyncIdSymbol] = -1; + + allowHalfOpen = false; + pauseOnConnect = false; + + // deno-lint-ignore no-explicit-any + _handle: any = null; + _connections = 0; + _usingWorkers = false; + // deno-lint-ignore no-explicit-any + _workers: any[] = []; + _unref = false; + _pipeName?: string; + _connectionKey?: string; + + /** + * `net.Server` is an `EventEmitter` with the following events: + * + * - `"close"` - Emitted when the server closes. If connections exist, this + * event is not emitted until all connections are ended. + * - `"connection"` - Emitted when a new connection is made. `socket` is an + * instance of `net.Socket`. + * - `"error"` - Emitted when an error occurs. Unlike `net.Socket`, the + * `"close"` event will not be emitted directly following this event unless + * `server.close()` is manually called. See the example in discussion of + * `server.listen()`. + * - `"listening"` - Emitted when the server has been bound after calling + * `server.listen()`. + */ + constructor(connectionListener?: ConnectionListener); + constructor(options?: ServerOptions, connectionListener?: ConnectionListener); + constructor( + options?: ServerOptions | ConnectionListener, + connectionListener?: ConnectionListener, + ) { + super(); + + if (_isConnectionListener(options)) { + this.on("connection", options); + } else if (_isServerSocketOptions(options)) { + this.allowHalfOpen = options?.allowHalfOpen || false; + this.pauseOnConnect = !!options?.pauseOnConnect; + + if (_isConnectionListener(connectionListener)) { + this.on("connection", connectionListener); + } + } else { + throw new ERR_INVALID_ARG_TYPE("options", "Object", options); + } + } + + /** + * Start a server listening for connections. A `net.Server` can be a TCP or + * an `IPC` server depending on what it listens to. + * + * Possible signatures: + * + * - `server.listen(handle[, backlog][, callback])` + * - `server.listen(options[, callback])` + * - `server.listen(path[, backlog][, callback])` for `IPC` servers + * - `server.listen([port[, host[, backlog]]][, callback])` for TCP servers + * + * This function is asynchronous. When the server starts listening, the `'listening'` event will be emitted. The last parameter `callback`will be added as a listener for the `'listening'` + * event. + * + * All `listen()` methods can take a `backlog` parameter to specify the maximum + * length of the queue of pending connections. The actual length will be determined + * by the OS through sysctl settings such as `tcp_max_syn_backlog` and `somaxconn` on Linux. The default value of this parameter is 511 (not 512). + * + * All `Socket` are set to `SO_REUSEADDR` (see [`socket(7)`](https://man7.org/linux/man-pages/man7/socket.7.html) for + * details). + * + * The `server.listen()` method can be called again if and only if there was an + * error during the first `server.listen()` call or `server.close()` has been + * called. Otherwise, an `ERR_SERVER_ALREADY_LISTEN` error will be thrown. + * + * One of the most common errors raised when listening is `EADDRINUSE`. + * This happens when another server is already listening on the requested`port`/`path`/`handle`. One way to handle this would be to retry + * after a certain amount of time: + */ + listen( + port?: number, + hostname?: string, + backlog?: number, + listeningListener?: () => void, + ): this; + listen( + port?: number, + hostname?: string, + listeningListener?: () => void, + ): this; + listen(port?: number, backlog?: number, listeningListener?: () => void): this; + listen(port?: number, listeningListener?: () => void): this; + listen(path: string, backlog?: number, listeningListener?: () => void): this; + listen(path: string, listeningListener?: () => void): this; + listen(options: ListenOptions, listeningListener?: () => void): this; + // deno-lint-ignore no-explicit-any + listen(handle: any, backlog?: number, listeningListener?: () => void): this; + // deno-lint-ignore no-explicit-any + listen(handle: any, listeningListener?: () => void): this; + listen(...args: unknown[]): this { + const normalized = _normalizeArgs(args); + let options = normalized[0] as Partial; + const cb = normalized[1]; + + if (this._handle) { + throw new ERR_SERVER_ALREADY_LISTEN(); + } + + if (cb !== null) { + this.once("listening", cb); + } + + const backlogFromArgs: number = + // (handle, backlog) or (path, backlog) or (port, backlog) + _toNumber(args.length > 1 && args[1]) || + (_toNumber(args.length > 2 && args[2]) as number); // (port, host, backlog) + + // deno-lint-ignore no-explicit-any + options = (options as any)._handle || (options as any).handle || options; + const flags = _getFlags(options.ipv6Only); + + // (handle[, backlog][, cb]) where handle is an object with a handle + if (options instanceof TCP) { + this._handle = options; + this[asyncIdSymbol] = this._handle.getAsyncId(); + + _listenInCluster(this, null, -1, -1, backlogFromArgs); + + return this; + } + + _addAbortSignalOption(this, options); + + // (handle[, backlog][, cb]) where handle is an object with a fd + if (typeof options.fd === "number" && options.fd >= 0) { + _listenInCluster(this, null, null, null, backlogFromArgs, options.fd); + + return this; + } + + // ([port][, host][, backlog][, cb]) where port is omitted, + // that is, listen(), listen(null), listen(cb), or listen(null, cb) + // or (options[, cb]) where options.port is explicitly set as undefined or + // null, bind to an arbitrary unused port + if ( + args.length === 0 || + typeof args[0] === "function" || + (typeof options.port === "undefined" && "port" in options) || + options.port === null + ) { + options.port = 0; + } + + // ([port][, host][, backlog][, cb]) where port is specified + // or (options[, cb]) where options.port is specified + // or if options.port is normalized as 0 before + let backlog; + + if (typeof options.port === "number" || typeof options.port === "string") { + validatePort(options.port, "options.port"); + backlog = options.backlog || backlogFromArgs; + + // start TCP server listening on host:port + if (options.host) { + _lookupAndListen( + this, + options.port | 0, + options.host, + backlog, + !!options.exclusive, + flags, + ); + } else { + // Undefined host, listens on unspecified address + // Default addressType 4 will be used to search for primary server + _listenInCluster( + this, + null, + options.port | 0, + 4, + backlog, + undefined, + options.exclusive, + ); + } + + return this; + } + + // (path[, backlog][, cb]) or (options[, cb]) + // where path or options.path is a UNIX domain socket or Windows pipe + if (options.path && _isPipeName(options.path)) { + const pipeName = (this._pipeName = options.path); + backlog = options.backlog || backlogFromArgs; + + _listenInCluster( + this, + pipeName, + -1, + -1, + backlog, + undefined, + options.exclusive, + ); + + if (!this._handle) { + // Failed and an error shall be emitted in the next tick. + // Therefore, we directly return. + return this; + } + + let mode = 0; + + if (options.readableAll === true) { + mode |= PipeConstants.UV_READABLE; + } + + if (options.writableAll === true) { + mode |= PipeConstants.UV_WRITABLE; + } + + if (mode !== 0) { + const err = this._handle.fchmod(mode); + + if (err) { + this._handle.close(); + this._handle = null; + + throw errnoException(err, "uv_pipe_chmod"); + } + } + + return this; + } + + if (!("port" in options || "path" in options)) { + throw new ERR_INVALID_ARG_VALUE( + "options", + options, + 'must have the property "port" or "path"', + ); + } + + throw new ERR_INVALID_ARG_VALUE("options", options); + } + + /** + * Stops the server from accepting new connections and keeps existing + * connections. This function is asynchronous, the server is finally closed + * when all connections are ended and the server emits a `"close"` event. + * The optional `callback` will be called once the `"close"` event occurs. Unlike + * that event, it will be called with an `Error` as its only argument if the server + * was not open when it was closed. + * + * @param cb Called when the server is closed. + */ + close(cb?: (err?: Error) => void): this { + if (typeof cb === "function") { + if (!this._handle) { + this.once("close", function close() { + cb(new ERR_SERVER_NOT_RUNNING()); + }); + } else { + this.once("close", cb); + } + } + + if (this._handle) { + (this._handle as TCP).close(); + this._handle = null; + } + + if (this._usingWorkers) { + let left = this._workers.length; + const onWorkerClose = () => { + if (--left !== 0) { + return; + } + + this._connections = 0; + this._emitCloseIfDrained(); + }; + + // Increment connections to be sure that, even if all sockets will be closed + // during polling of workers, `close` event will be emitted only once. + this._connections++; + + // Poll workers + for (let n = 0; n < this._workers.length; n++) { + this._workers[n].close(onWorkerClose); + } + } else { + this._emitCloseIfDrained(); + } + + return this; + } + + /** + * Returns the bound `address`, the address `family` name, and `port` of the server + * as reported by the operating system if listening on an IP socket + * (useful to find which port was assigned when getting an OS-assigned address):`{ port: 12346, family: "IPv4", address: "127.0.0.1" }`. + * + * For a server listening on a pipe or Unix domain socket, the name is returned + * as a string. + * + * `server.address()` returns `null` before the `"listening"` event has been + * emitted or after calling `server.close()`. + */ + address(): AddressInfo | string | null { + if (this._handle && this._handle.getsockname) { + const out = {}; + const err = this._handle.getsockname(out); + + if (err) { + throw errnoException(err, "address"); + } + + return out as AddressInfo; + } else if (this._pipeName) { + return this._pipeName; + } + + return null; + } + + /** + * Asynchronously get the number of concurrent connections on the server. Works + * when sockets were sent to forks. + * + * Callback should take two arguments `err` and `count`. + */ + getConnections(cb: (err: Error | null, count: number) => void): this { + // deno-lint-ignore no-this-alias + const server = this; + + function end(err: Error | null, connections?: number) { + defaultTriggerAsyncIdScope( + server[asyncIdSymbol], + nextTick, + cb, + err, + connections, + ); + } + + if (!this._usingWorkers) { + end(null, this._connections); + + return this; + } + + // Poll workers + let left = this._workers.length; + let total = this._connections; + + function oncount(err: Error, count: number) { + if (err) { + left = -1; + + return end(err); + } + + total += count; + + if (--left === 0) { + return end(null, total); + } + } + + for (let n = 0; n < this._workers.length; n++) { + this._workers[n].getConnections(oncount); + } + + return this; + } + + /** + * Calling `unref()` on a server will allow the program to exit if this is the only + * active server in the event system. If the server is already `unref`ed calling `unref()` again will have no effect. + */ + unref(): this { + this._unref = true; + + if (this._handle) { + this._handle.unref(); + } + + return this; + } + + /** + * Opposite of `unref()`, calling `ref()` on a previously `unref`ed server will _not_ let the program exit if it's the only server left (the default behavior). + * If the server is `ref`ed calling `ref()` again will have no effect. + */ + ref(): this { + this._unref = false; + + if (this._handle) { + this._handle.ref(); + } + + return this; + } + + /** + * Indicates whether or not the server is listening for connections. + */ + get listening(): boolean { + return !!this._handle; + } + + _listen2 = _setupListenHandle; + + _emitCloseIfDrained() { + debug("SERVER _emitCloseIfDrained"); + if (this._handle || this._connections) { + debug( + `SERVER handle? ${!!this._handle} connections? ${this._connections}`, + ); + return; + } + + // We use setTimeout instead of nextTick here to avoid EADDRINUSE error + // when the same port listened immediately after the 'close' event. + // ref: https://github.com/denoland/deno_std/issues/2788 + defaultTriggerAsyncIdScope( + this[asyncIdSymbol], + setTimeout, + _emitCloseNT, + 0, + this, + ); + } + + _setupWorker(socketList: EventEmitter) { + this._usingWorkers = true; + this._workers.push(socketList); + + // deno-lint-ignore no-explicit-any + socketList.once("exit", (socketList: any) => { + const index = this._workers.indexOf(socketList); + this._workers.splice(index, 1); + }); + } + + [EventEmitter.captureRejectionSymbol]( + err: Error, + event: string, + sock: Socket, + ) { + switch (event) { + case "connection": { + sock.destroy(err); + break; + } + default: { + this.emit("error", err); + } + } + } +} + +/** + * Creates a new TCP or IPC server. + * + * Accepts an `options` object with properties `allowHalfOpen` (default `false`) + * and `pauseOnConnect` (default `false`). + * + * If `allowHalfOpen` is set to `false`, then the socket will + * automatically end the writable side when the readable side ends. + * + * If `allowHalfOpen` is set to `true`, when the other end of the socket + * signals the end of transmission, the server will only send back the end of + * transmission when `socket.end()` is explicitly called. For example, in the + * context of TCP, when a FIN packed is received, a FIN packed is sent back + * only when `socket.end()` is explicitly called. Until then the connection is + * half-closed (non-readable but still writable). See `"end"` event and RFC 1122 + * (section 4.2.2.13) for more information. + * + * `pauseOnConnect` indicates whether the socket should be paused on incoming + * connections. + * + * If `pauseOnConnect` is set to `true`, then the socket associated with each + * incoming connection will be paused, and no data will be read from its + * handle. This allows connections to be passed between processes without any + * data being read by the original process. To begin reading data from a paused + * socket, call `socket.resume()`. + * + * The server can be a TCP server or an IPC server, depending on what it + * `listen()` to. + * + * Here is an example of an TCP echo server which listens for connections on + * port 8124: + * + * @param options Socket options. + * @param connectionListener Automatically set as a listener for the `"connection"` event. + * @return A `net.Server`. + */ +export function createServer( + options?: ServerOptions, + connectionListener?: ConnectionListener, +): Server { + return new Server(options, connectionListener); +} + +export { isIP, isIPv4, isIPv6 }; + +export default { + _createServerHandle, + _normalizeArgs, + isIP, + isIPv4, + isIPv6, + connect, + createConnection, + createServer, + Server, + Socket, + Stream, +}; diff --git a/ext/node/polyfills/os.ts b/ext/node/polyfills/os.ts new file mode 100644 index 00000000000000..94ca944c8f3786 --- /dev/null +++ b/ext/node/polyfills/os.ts @@ -0,0 +1,349 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. +// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. + +import { notImplemented } from "internal:deno_node/polyfills/_utils.ts"; +import { validateIntegerRange } from "internal:deno_node/polyfills/_utils.ts"; +import process from "internal:deno_node/polyfills/process.ts"; +import { isWindows, osType } from "internal:deno_node/polyfills/_util/os.ts"; +import { os } from "internal:deno_node/polyfills/internal_binding/constants.ts"; + +export const constants = os; + +const SEE_GITHUB_ISSUE = "See https://github.com/denoland/deno_std/issues/1436"; + +// @ts-ignore Deno[Deno.internal] is used on purpose here +const DenoOsUptime = Deno[Deno.internal]?.nodeUnstable?.osUptime || + Deno.osUptime; + +interface CPUTimes { + /** The number of milliseconds the CPU has spent in user mode */ + user: number; + + /** The number of milliseconds the CPU has spent in nice mode */ + nice: number; + + /** The number of milliseconds the CPU has spent in sys mode */ + sys: number; + + /** The number of milliseconds the CPU has spent in idle mode */ + idle: number; + + /** The number of milliseconds the CPU has spent in irq mode */ + irq: number; +} + +interface CPUCoreInfo { + model: string; + + /** in MHz */ + speed: number; + + times: CPUTimes; +} + +interface NetworkAddress { + /** The assigned IPv4 or IPv6 address */ + address: string; + + /** The IPv4 or IPv6 network mask */ + netmask: string; + + family: "IPv4" | "IPv6"; + + /** The MAC address of the network interface */ + mac: string; + + /** true if the network interface is a loopback or similar interface that is not remotely accessible; otherwise false */ + internal: boolean; + + /** The numeric IPv6 scope ID (only specified when family is IPv6) */ + scopeid?: number; + + /** The assigned IPv4 or IPv6 address with the routing prefix in CIDR notation. If the netmask is invalid, this property is set to null. */ + cidr: string; +} + +interface NetworkInterfaces { + [key: string]: NetworkAddress[]; +} + +export interface UserInfoOptions { + encoding: string; +} + +interface UserInfo { + username: string; + uid: number; + gid: number; + shell: string; + homedir: string; +} + +export function arch(): string { + return process.arch; +} + +// deno-lint-ignore no-explicit-any +(arch as any)[Symbol.toPrimitive] = (): string => process.arch; +// deno-lint-ignore no-explicit-any +(endianness as any)[Symbol.toPrimitive] = (): string => endianness(); +// deno-lint-ignore no-explicit-any +(freemem as any)[Symbol.toPrimitive] = (): number => freemem(); +// deno-lint-ignore no-explicit-any +(homedir as any)[Symbol.toPrimitive] = (): string | null => homedir(); +// deno-lint-ignore no-explicit-any +(hostname as any)[Symbol.toPrimitive] = (): string | null => hostname(); +// deno-lint-ignore no-explicit-any +(platform as any)[Symbol.toPrimitive] = (): string => platform(); +// deno-lint-ignore no-explicit-any +(release as any)[Symbol.toPrimitive] = (): string => release(); +// deno-lint-ignore no-explicit-any +(version as any)[Symbol.toPrimitive] = (): string => version(); +// deno-lint-ignore no-explicit-any +(totalmem as any)[Symbol.toPrimitive] = (): number => totalmem(); +// deno-lint-ignore no-explicit-any +(type as any)[Symbol.toPrimitive] = (): string => type(); +// deno-lint-ignore no-explicit-any +(uptime as any)[Symbol.toPrimitive] = (): number => uptime(); + +export function cpus(): CPUCoreInfo[] { + return Array.from(Array(navigator.hardwareConcurrency)).map(() => { + return { + model: "", + speed: 0, + times: { + user: 0, + nice: 0, + sys: 0, + idle: 0, + irq: 0, + }, + }; + }); +} + +/** + * Returns a string identifying the endianness of the CPU for which the Deno + * binary was compiled. Possible values are 'BE' for big endian and 'LE' for + * little endian. + */ +export function endianness(): "BE" | "LE" { + // Source: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/DataView#Endianness + const buffer = new ArrayBuffer(2); + new DataView(buffer).setInt16(0, 256, true /* littleEndian */); + // Int16Array uses the platform's endianness. + return new Int16Array(buffer)[0] === 256 ? "LE" : "BE"; +} + +/** Return free memory amount */ +export function freemem(): number { + return Deno.systemMemoryInfo().free; +} + +/** Not yet implemented */ +export function getPriority(pid = 0): number { + validateIntegerRange(pid, "pid"); + notImplemented(SEE_GITHUB_ISSUE); +} + +/** Returns the string path of the current user's home directory. */ +export function homedir(): string | null { + // Note: Node/libuv calls getpwuid() / GetUserProfileDirectory() when the + // environment variable isn't set but that's the (very uncommon) fallback + // path. IMO, it's okay to punt on that for now. + switch (osType) { + case "windows": + return Deno.env.get("USERPROFILE") || null; + case "linux": + case "darwin": + case "freebsd": + return Deno.env.get("HOME") || null; + default: + throw Error("unreachable"); + } +} + +/** Returns the host name of the operating system as a string. */ +export function hostname(): string { + return Deno.hostname(); +} + +/** Returns an array containing the 1, 5, and 15 minute load averages */ +export function loadavg(): number[] { + if (isWindows) { + return [0, 0, 0]; + } + return Deno.loadavg(); +} + +/** Returns an object containing network interfaces that have been assigned a network address. + * Each key on the returned object identifies a network interface. The associated value is an array of objects that each describe an assigned network address. */ +export function networkInterfaces(): NetworkInterfaces { + const interfaces: NetworkInterfaces = {}; + for ( + const { name, address, netmask, family, mac, scopeid, cidr } of Deno + .networkInterfaces() + ) { + const addresses = interfaces[name] ||= []; + const networkAddress: NetworkAddress = { + address, + netmask, + family, + mac, + internal: (family === "IPv4" && isIPv4LoopbackAddr(address)) || + (family === "IPv6" && isIPv6LoopbackAddr(address)), + cidr, + }; + if (family === "IPv6") { + networkAddress.scopeid = scopeid!; + } + addresses.push(networkAddress); + } + return interfaces; +} + +function isIPv4LoopbackAddr(addr: string) { + return addr.startsWith("127"); +} + +function isIPv6LoopbackAddr(addr: string) { + return addr === "::1" || addr === "fe80::1"; +} + +/** Returns the a string identifying the operating system platform. The value is set at compile time. Possible values are 'darwin', 'linux', and 'win32'. */ +export function platform(): string { + return process.platform; +} + +/** Returns the operating system as a string */ +export function release(): string { + return Deno.osRelease(); +} + +/** Returns a string identifying the kernel version */ +export function version(): string { + // TODO(kt3k): Temporarily uses Deno.osRelease(). + // Revisit this if this implementation is insufficient for any npm module + return Deno.osRelease(); +} + +/** Not yet implemented */ +export function setPriority(pid: number, priority?: number) { + /* The node API has the 'pid' as the first parameter and as optional. + This makes for a problematic implementation in Typescript. */ + if (priority === undefined) { + priority = pid; + pid = 0; + } + validateIntegerRange(pid, "pid"); + validateIntegerRange(priority, "priority", -20, 19); + + notImplemented(SEE_GITHUB_ISSUE); +} + +/** Returns the operating system's default directory for temporary files as a string. */ +export function tmpdir(): string | null { + /* This follows the node js implementation, but has a few + differences: + * On windows, if none of the environment variables are defined, + we return null. + * On unix we use a plain Deno.env.get, instead of safeGetenv, + which special cases setuid binaries. + * Node removes a single trailing / or \, we remove all. + */ + if (isWindows) { + const temp = Deno.env.get("TEMP") || Deno.env.get("TMP"); + if (temp) { + return temp.replace(/(? */ +export const CHAR_LEFT_CURLY_BRACKET = 123; /* { */ +export const CHAR_RIGHT_CURLY_BRACKET = 125; /* } */ +export const CHAR_HYPHEN_MINUS = 45; /* - */ +export const CHAR_PLUS = 43; /* + */ +export const CHAR_DOUBLE_QUOTE = 34; /* " */ +export const CHAR_SINGLE_QUOTE = 39; /* ' */ +export const CHAR_PERCENT = 37; /* % */ +export const CHAR_SEMICOLON = 59; /* ; */ +export const CHAR_CIRCUMFLEX_ACCENT = 94; /* ^ */ +export const CHAR_GRAVE_ACCENT = 96; /* ` */ +export const CHAR_AT = 64; /* @ */ +export const CHAR_AMPERSAND = 38; /* & */ +export const CHAR_EQUAL = 61; /* = */ + +// Digits +export const CHAR_0 = 48; /* 0 */ +export const CHAR_9 = 57; /* 9 */ diff --git a/ext/node/polyfills/path/_interface.ts b/ext/node/polyfills/path/_interface.ts new file mode 100644 index 00000000000000..46d6d00f8e7e8b --- /dev/null +++ b/ext/node/polyfills/path/_interface.ts @@ -0,0 +1,29 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. + +/** + * A parsed path object generated by path.parse() or consumed by path.format(). + */ +export interface ParsedPath { + /** + * The root of the path such as '/' or 'c:\' + */ + root: string; + /** + * The full directory path such as '/home/user/dir' or 'c:\path\dir' + */ + dir: string; + /** + * The file name including extension (if any) such as 'index.html' + */ + base: string; + /** + * The file extension (if any) such as '.html' + */ + ext: string; + /** + * The file name without extension (if any) such as 'index' + */ + name: string; +} + +export type FormatInputPathObject = Partial; diff --git a/ext/node/polyfills/path/_util.ts b/ext/node/polyfills/path/_util.ts new file mode 100644 index 00000000000000..ccc12abc9be988 --- /dev/null +++ b/ext/node/polyfills/path/_util.ts @@ -0,0 +1,131 @@ +// Copyright the Browserify authors. MIT License. +// Ported from https://github.com/browserify/path-browserify/ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. + +import type { FormatInputPathObject } from "internal:deno_node/polyfills/path/_interface.ts"; +import { + CHAR_BACKWARD_SLASH, + CHAR_DOT, + CHAR_FORWARD_SLASH, + CHAR_LOWERCASE_A, + CHAR_LOWERCASE_Z, + CHAR_UPPERCASE_A, + CHAR_UPPERCASE_Z, +} from "internal:deno_node/polyfills/path/_constants.ts"; +import { ERR_INVALID_ARG_TYPE } from "internal:deno_node/polyfills/internal/errors.ts"; + +export function assertPath(path: string) { + if (typeof path !== "string") { + throw new ERR_INVALID_ARG_TYPE("path", ["string"], path); + } +} + +export function isPosixPathSeparator(code: number): boolean { + return code === CHAR_FORWARD_SLASH; +} + +export function isPathSeparator(code: number): boolean { + return isPosixPathSeparator(code) || code === CHAR_BACKWARD_SLASH; +} + +export function isWindowsDeviceRoot(code: number): boolean { + return ( + (code >= CHAR_LOWERCASE_A && code <= CHAR_LOWERCASE_Z) || + (code >= CHAR_UPPERCASE_A && code <= CHAR_UPPERCASE_Z) + ); +} + +// Resolves . and .. elements in a path with directory names +export function normalizeString( + path: string, + allowAboveRoot: boolean, + separator: string, + isPathSeparator: (code: number) => boolean, +): string { + let res = ""; + let lastSegmentLength = 0; + let lastSlash = -1; + let dots = 0; + let code: number | undefined; + for (let i = 0, len = path.length; i <= len; ++i) { + if (i < len) code = path.charCodeAt(i); + else if (isPathSeparator(code!)) break; + else code = CHAR_FORWARD_SLASH; + + if (isPathSeparator(code!)) { + if (lastSlash === i - 1 || dots === 1) { + // NOOP + } else if (lastSlash !== i - 1 && dots === 2) { + if ( + res.length < 2 || + lastSegmentLength !== 2 || + res.charCodeAt(res.length - 1) !== CHAR_DOT || + res.charCodeAt(res.length - 2) !== CHAR_DOT + ) { + if (res.length > 2) { + const lastSlashIndex = res.lastIndexOf(separator); + if (lastSlashIndex === -1) { + res = ""; + lastSegmentLength = 0; + } else { + res = res.slice(0, lastSlashIndex); + lastSegmentLength = res.length - 1 - res.lastIndexOf(separator); + } + lastSlash = i; + dots = 0; + continue; + } else if (res.length === 2 || res.length === 1) { + res = ""; + lastSegmentLength = 0; + lastSlash = i; + dots = 0; + continue; + } + } + if (allowAboveRoot) { + if (res.length > 0) res += `${separator}..`; + else res = ".."; + lastSegmentLength = 2; + } + } else { + if (res.length > 0) res += separator + path.slice(lastSlash + 1, i); + else res = path.slice(lastSlash + 1, i); + lastSegmentLength = i - lastSlash - 1; + } + lastSlash = i; + dots = 0; + } else if (code === CHAR_DOT && dots !== -1) { + ++dots; + } else { + dots = -1; + } + } + return res; +} + +export function _format( + sep: string, + pathObject: FormatInputPathObject, +): string { + const dir: string | undefined = pathObject.dir || pathObject.root; + const base: string = pathObject.base || + (pathObject.name || "") + (pathObject.ext || ""); + if (!dir) return base; + if (dir === pathObject.root) return dir + base; + return dir + sep + base; +} + +const WHITESPACE_ENCODINGS: Record = { + "\u0009": "%09", + "\u000A": "%0A", + "\u000B": "%0B", + "\u000C": "%0C", + "\u000D": "%0D", + "\u0020": "%20", +}; + +export function encodeWhitespace(string: string): string { + return string.replaceAll(/[\s]/g, (c) => { + return WHITESPACE_ENCODINGS[c] ?? c; + }); +} diff --git a/ext/node/polyfills/path/common.ts b/ext/node/polyfills/path/common.ts new file mode 100644 index 00000000000000..e4efe7cc4a30af --- /dev/null +++ b/ext/node/polyfills/path/common.ts @@ -0,0 +1,39 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. +// This module is browser compatible. + +import { SEP } from "internal:deno_node/polyfills/path/separator.ts"; + +/** Determines the common path from a set of paths, using an optional separator, + * which defaults to the OS default separator. + * + * ```ts + * const p = common([ + * "./deno/std/path/mod.ts", + * "./deno/std/fs/mod.ts", + * ]); + * console.log(p); // "./deno/std/" + * ``` + */ +export function common(paths: string[], sep = SEP): string { + const [first = "", ...remaining] = paths; + if (first === "" || remaining.length === 0) { + return first.substring(0, first.lastIndexOf(sep) + 1); + } + const parts = first.split(sep); + + let endOfPrefix = parts.length; + for (const path of remaining) { + const compare = path.split(sep); + for (let i = 0; i < endOfPrefix; i++) { + if (compare[i] !== parts[i]) { + endOfPrefix = i; + } + } + + if (endOfPrefix === 0) { + return ""; + } + } + const prefix = parts.slice(0, endOfPrefix).join(sep); + return prefix.endsWith(sep) ? prefix : `${prefix}${sep}`; +} diff --git a/ext/node/polyfills/path/glob.ts b/ext/node/polyfills/path/glob.ts new file mode 100644 index 00000000000000..c0da29b9f08d6c --- /dev/null +++ b/ext/node/polyfills/path/glob.ts @@ -0,0 +1,420 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. + +import { isWindows, osType } from "internal:deno_node/polyfills/_util/os.ts"; +import { + SEP, + SEP_PATTERN, +} from "internal:deno_node/polyfills/path/separator.ts"; +import * as _win32 from "internal:deno_node/polyfills/path/win32.ts"; +import * as _posix from "internal:deno_node/polyfills/path/posix.ts"; +import type { OSType } from "internal:deno_node/polyfills/_util/os.ts"; + +const path = isWindows ? _win32 : _posix; +const { join, normalize } = path; + +export interface GlobOptions { + /** Extended glob syntax. + * See https://www.linuxjournal.com/content/bash-extended-globbing. + * + * @default {true} + */ + extended?: boolean; + /** Globstar syntax. + * See https://www.linuxjournal.com/content/globstar-new-bash-globbing-option. + * If false, `**` is treated like `*`. + * + * @default {true} + */ + globstar?: boolean; + /** Whether globstar should be case-insensitive. */ + caseInsensitive?: boolean; + /** Operating system. Defaults to the native OS. */ + os?: OSType; +} + +export type GlobToRegExpOptions = GlobOptions; + +const regExpEscapeChars = [ + "!", + "$", + "(", + ")", + "*", + "+", + ".", + "=", + "?", + "[", + "\\", + "^", + "{", + "|", +]; +const rangeEscapeChars = ["-", "\\", "]"]; + +/** Convert a glob string to a regular expression. + * + * Tries to match bash glob expansion as closely as possible. + * + * Basic glob syntax: + * - `*` - Matches everything without leaving the path segment. + * - `?` - Matches any single character. + * - `{foo,bar}` - Matches `foo` or `bar`. + * - `[abcd]` - Matches `a`, `b`, `c` or `d`. + * - `[a-d]` - Matches `a`, `b`, `c` or `d`. + * - `[!abcd]` - Matches any single character besides `a`, `b`, `c` or `d`. + * - `[[::]]` - Matches any character belonging to ``. + * - `[[:alnum:]]` - Matches any digit or letter. + * - `[[:digit:]abc]` - Matches any digit, `a`, `b` or `c`. + * - See https://facelessuser.github.io/wcmatch/glob/#posix-character-classes + * for a complete list of supported character classes. + * - `\` - Escapes the next character for an `os` other than `"windows"`. + * - \` - Escapes the next character for `os` set to `"windows"`. + * - `/` - Path separator. + * - `\` - Additional path separator only for `os` set to `"windows"`. + * + * Extended syntax: + * - Requires `{ extended: true }`. + * - `?(foo|bar)` - Matches 0 or 1 instance of `{foo,bar}`. + * - `@(foo|bar)` - Matches 1 instance of `{foo,bar}`. They behave the same. + * - `*(foo|bar)` - Matches _n_ instances of `{foo,bar}`. + * - `+(foo|bar)` - Matches _n > 0_ instances of `{foo,bar}`. + * - `!(foo|bar)` - Matches anything other than `{foo,bar}`. + * - See https://www.linuxjournal.com/content/bash-extended-globbing. + * + * Globstar syntax: + * - Requires `{ globstar: true }`. + * - `**` - Matches any number of any path segments. + * - Must comprise its entire path segment in the provided glob. + * - See https://www.linuxjournal.com/content/globstar-new-bash-globbing-option. + * + * Note the following properties: + * - The generated `RegExp` is anchored at both start and end. + * - Repeating and trailing separators are tolerated. Trailing separators in the + * provided glob have no meaning and are discarded. + * - Absolute globs will only match absolute paths, etc. + * - Empty globs will match nothing. + * - Any special glob syntax must be contained to one path segment. For example, + * `?(foo|bar/baz)` is invalid. The separator will take precedence and the + * first segment ends with an unclosed group. + * - If a path segment ends with unclosed groups or a dangling escape prefix, a + * parse error has occurred. Every character for that segment is taken + * literally in this event. + * + * Limitations: + * - A negative group like `!(foo|bar)` will wrongly be converted to a negative + * look-ahead followed by a wildcard. This means that `!(foo).js` will wrongly + * fail to match `foobar.js`, even though `foobar` is not `foo`. Effectively, + * `!(foo|bar)` is treated like `!(@(foo|bar)*)`. This will work correctly if + * the group occurs not nested at the end of the segment. */ +export function globToRegExp( + glob: string, + { + extended = true, + globstar: globstarOption = true, + os = osType, + caseInsensitive = false, + }: GlobToRegExpOptions = {}, +): RegExp { + if (glob == "") { + return /(?!)/; + } + + const sep = os == "windows" ? "(?:\\\\|/)+" : "/+"; + const sepMaybe = os == "windows" ? "(?:\\\\|/)*" : "/*"; + const seps = os == "windows" ? ["\\", "/"] : ["/"]; + const globstar = os == "windows" + ? "(?:[^\\\\/]*(?:\\\\|/|$)+)*" + : "(?:[^/]*(?:/|$)+)*"; + const wildcard = os == "windows" ? "[^\\\\/]*" : "[^/]*"; + const escapePrefix = os == "windows" ? "`" : "\\"; + + // Remove trailing separators. + let newLength = glob.length; + for (; newLength > 1 && seps.includes(glob[newLength - 1]); newLength--); + glob = glob.slice(0, newLength); + + let regExpString = ""; + + // Terminates correctly. Trust that `j` is incremented every iteration. + for (let j = 0; j < glob.length;) { + let segment = ""; + const groupStack: string[] = []; + let inRange = false; + let inEscape = false; + let endsWithSep = false; + let i = j; + + // Terminates with `i` at the non-inclusive end of the current segment. + for (; i < glob.length && !seps.includes(glob[i]); i++) { + if (inEscape) { + inEscape = false; + const escapeChars = inRange ? rangeEscapeChars : regExpEscapeChars; + segment += escapeChars.includes(glob[i]) ? `\\${glob[i]}` : glob[i]; + continue; + } + + if (glob[i] == escapePrefix) { + inEscape = true; + continue; + } + + if (glob[i] == "[") { + if (!inRange) { + inRange = true; + segment += "["; + if (glob[i + 1] == "!") { + i++; + segment += "^"; + } else if (glob[i + 1] == "^") { + i++; + segment += "\\^"; + } + continue; + } else if (glob[i + 1] == ":") { + let k = i + 1; + let value = ""; + while (glob[k + 1] != null && glob[k + 1] != ":") { + value += glob[k + 1]; + k++; + } + if (glob[k + 1] == ":" && glob[k + 2] == "]") { + i = k + 2; + if (value == "alnum") segment += "\\dA-Za-z"; + else if (value == "alpha") segment += "A-Za-z"; + else if (value == "ascii") segment += "\x00-\x7F"; + else if (value == "blank") segment += "\t "; + else if (value == "cntrl") segment += "\x00-\x1F\x7F"; + else if (value == "digit") segment += "\\d"; + else if (value == "graph") segment += "\x21-\x7E"; + else if (value == "lower") segment += "a-z"; + else if (value == "print") segment += "\x20-\x7E"; + else if (value == "punct") { + segment += "!\"#$%&'()*+,\\-./:;<=>?@[\\\\\\]^_‘{|}~"; + } else if (value == "space") segment += "\\s\v"; + else if (value == "upper") segment += "A-Z"; + else if (value == "word") segment += "\\w"; + else if (value == "xdigit") segment += "\\dA-Fa-f"; + continue; + } + } + } + + if (glob[i] == "]" && inRange) { + inRange = false; + segment += "]"; + continue; + } + + if (inRange) { + if (glob[i] == "\\") { + segment += `\\\\`; + } else { + segment += glob[i]; + } + continue; + } + + if ( + glob[i] == ")" && groupStack.length > 0 && + groupStack[groupStack.length - 1] != "BRACE" + ) { + segment += ")"; + const type = groupStack.pop()!; + if (type == "!") { + segment += wildcard; + } else if (type != "@") { + segment += type; + } + continue; + } + + if ( + glob[i] == "|" && groupStack.length > 0 && + groupStack[groupStack.length - 1] != "BRACE" + ) { + segment += "|"; + continue; + } + + if (glob[i] == "+" && extended && glob[i + 1] == "(") { + i++; + groupStack.push("+"); + segment += "(?:"; + continue; + } + + if (glob[i] == "@" && extended && glob[i + 1] == "(") { + i++; + groupStack.push("@"); + segment += "(?:"; + continue; + } + + if (glob[i] == "?") { + if (extended && glob[i + 1] == "(") { + i++; + groupStack.push("?"); + segment += "(?:"; + } else { + segment += "."; + } + continue; + } + + if (glob[i] == "!" && extended && glob[i + 1] == "(") { + i++; + groupStack.push("!"); + segment += "(?!"; + continue; + } + + if (glob[i] == "{") { + groupStack.push("BRACE"); + segment += "(?:"; + continue; + } + + if (glob[i] == "}" && groupStack[groupStack.length - 1] == "BRACE") { + groupStack.pop(); + segment += ")"; + continue; + } + + if (glob[i] == "," && groupStack[groupStack.length - 1] == "BRACE") { + segment += "|"; + continue; + } + + if (glob[i] == "*") { + if (extended && glob[i + 1] == "(") { + i++; + groupStack.push("*"); + segment += "(?:"; + } else { + const prevChar = glob[i - 1]; + let numStars = 1; + while (glob[i + 1] == "*") { + i++; + numStars++; + } + const nextChar = glob[i + 1]; + if ( + globstarOption && numStars == 2 && + [...seps, undefined].includes(prevChar) && + [...seps, undefined].includes(nextChar) + ) { + segment += globstar; + endsWithSep = true; + } else { + segment += wildcard; + } + } + continue; + } + + segment += regExpEscapeChars.includes(glob[i]) ? `\\${glob[i]}` : glob[i]; + } + + // Check for unclosed groups or a dangling backslash. + if (groupStack.length > 0 || inRange || inEscape) { + // Parse failure. Take all characters from this segment literally. + segment = ""; + for (const c of glob.slice(j, i)) { + segment += regExpEscapeChars.includes(c) ? `\\${c}` : c; + endsWithSep = false; + } + } + + regExpString += segment; + if (!endsWithSep) { + regExpString += i < glob.length ? sep : sepMaybe; + endsWithSep = true; + } + + // Terminates with `i` at the start of the next segment. + while (seps.includes(glob[i])) i++; + + // Check that the next value of `j` is indeed higher than the current value. + if (!(i > j)) { + throw new Error("Assertion failure: i > j (potential infinite loop)"); + } + j = i; + } + + regExpString = `^${regExpString}$`; + return new RegExp(regExpString, caseInsensitive ? "i" : ""); +} + +/** Test whether the given string is a glob */ +export function isGlob(str: string): boolean { + const chars: Record = { "{": "}", "(": ")", "[": "]" }; + const regex = + /\\(.)|(^!|\*|\?|[\].+)]\?|\[[^\\\]]+\]|\{[^\\}]+\}|\(\?[:!=][^\\)]+\)|\([^|]+\|[^\\)]+\))/; + + if (str === "") { + return false; + } + + let match: RegExpExecArray | null; + + while ((match = regex.exec(str))) { + if (match[2]) return true; + let idx = match.index + match[0].length; + + // if an open bracket/brace/paren is escaped, + // set the index to the next closing character + const open = match[1]; + const close = open ? chars[open] : null; + if (open && close) { + const n = str.indexOf(close, idx); + if (n !== -1) { + idx = n + 1; + } + } + + str = str.slice(idx); + } + + return false; +} + +/** Like normalize(), but doesn't collapse "**\/.." when `globstar` is true. */ +export function normalizeGlob( + glob: string, + { globstar = false }: GlobOptions = {}, +): string { + if (glob.match(/\0/g)) { + throw new Error(`Glob contains invalid characters: "${glob}"`); + } + if (!globstar) { + return normalize(glob); + } + const s = SEP_PATTERN.source; + const badParentPattern = new RegExp( + `(?<=(${s}|^)\\*\\*${s})\\.\\.(?=${s}|$)`, + "g", + ); + return normalize(glob.replace(badParentPattern, "\0")).replace(/\0/g, ".."); +} + +/** Like join(), but doesn't collapse "**\/.." when `globstar` is true. */ +export function joinGlobs( + globs: string[], + { extended = true, globstar = false }: GlobOptions = {}, +): string { + if (!globstar || globs.length == 0) { + return join(...globs); + } + if (globs.length === 0) return "."; + let joined: string | undefined; + for (const glob of globs) { + const path = glob; + if (path.length > 0) { + if (!joined) joined = path; + else joined += `${SEP}${path}`; + } + } + if (!joined) return "."; + return normalizeGlob(joined, { extended, globstar }); +} diff --git a/ext/node/polyfills/path/mod.ts b/ext/node/polyfills/path/mod.ts new file mode 100644 index 00000000000000..4b4de056be8521 --- /dev/null +++ b/ext/node/polyfills/path/mod.ts @@ -0,0 +1,37 @@ +// Copyright the Browserify authors. MIT License. +// Ported mostly from https://github.com/browserify/path-browserify/ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. + +import { isWindows } from "internal:deno_node/polyfills/_util/os.ts"; +import _win32 from "internal:deno_node/polyfills/path/win32.ts"; +import _posix from "internal:deno_node/polyfills/path/posix.ts"; + +const path = isWindows ? _win32 : _posix; + +export const win32 = _win32; +export const posix = _posix; +export const { + basename, + delimiter, + dirname, + extname, + format, + fromFileUrl, + isAbsolute, + join, + normalize, + parse, + relative, + resolve, + sep, + toFileUrl, + toNamespacedPath, +} = path; + +export * from "internal:deno_node/polyfills/path/common.ts"; +export { + SEP, + SEP_PATTERN, +} from "internal:deno_node/polyfills/path/separator.ts"; +export * from "internal:deno_node/polyfills/path/_interface.ts"; +export * from "internal:deno_node/polyfills/path/glob.ts"; diff --git a/ext/node/polyfills/path/posix.ts b/ext/node/polyfills/path/posix.ts new file mode 100644 index 00000000000000..8ebf646290a7d1 --- /dev/null +++ b/ext/node/polyfills/path/posix.ts @@ -0,0 +1,536 @@ +// Copyright the Browserify authors. MIT License. +// Ported from https://github.com/browserify/path-browserify/ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. + +import type { + FormatInputPathObject, + ParsedPath, +} from "internal:deno_node/polyfills/path/_interface.ts"; +import { + CHAR_DOT, + CHAR_FORWARD_SLASH, +} from "internal:deno_node/polyfills/path/_constants.ts"; +import { ERR_INVALID_ARG_TYPE } from "internal:deno_node/polyfills/internal/errors.ts"; + +import { + _format, + assertPath, + encodeWhitespace, + isPosixPathSeparator, + normalizeString, +} from "internal:deno_node/polyfills/path/_util.ts"; + +export const sep = "/"; +export const delimiter = ":"; + +// path.resolve([from ...], to) +/** + * Resolves `pathSegments` into an absolute path. + * @param pathSegments an array of path segments + */ +export function resolve(...pathSegments: string[]): string { + let resolvedPath = ""; + let resolvedAbsolute = false; + + for (let i = pathSegments.length - 1; i >= -1 && !resolvedAbsolute; i--) { + let path: string; + + if (i >= 0) path = pathSegments[i]; + else { + // deno-lint-ignore no-explicit-any + const { Deno } = globalThis as any; + if (typeof Deno?.cwd !== "function") { + throw new TypeError("Resolved a relative path without a CWD."); + } + path = Deno.cwd(); + } + + assertPath(path); + + // Skip empty entries + if (path.length === 0) { + continue; + } + + resolvedPath = `${path}/${resolvedPath}`; + resolvedAbsolute = path.charCodeAt(0) === CHAR_FORWARD_SLASH; + } + + // At this point the path should be resolved to a full absolute path, but + // handle relative paths to be safe (might happen when process.cwd() fails) + + // Normalize the path + resolvedPath = normalizeString( + resolvedPath, + !resolvedAbsolute, + "/", + isPosixPathSeparator, + ); + + if (resolvedAbsolute) { + if (resolvedPath.length > 0) return `/${resolvedPath}`; + else return "/"; + } else if (resolvedPath.length > 0) return resolvedPath; + else return "."; +} + +/** + * Normalize the `path`, resolving `'..'` and `'.'` segments. + * @param path to be normalized + */ +export function normalize(path: string): string { + assertPath(path); + + if (path.length === 0) return "."; + + const isAbsolute = path.charCodeAt(0) === CHAR_FORWARD_SLASH; + const trailingSeparator = + path.charCodeAt(path.length - 1) === CHAR_FORWARD_SLASH; + + // Normalize the path + path = normalizeString(path, !isAbsolute, "/", isPosixPathSeparator); + + if (path.length === 0 && !isAbsolute) path = "."; + if (path.length > 0 && trailingSeparator) path += "/"; + + if (isAbsolute) return `/${path}`; + return path; +} + +/** + * Verifies whether provided path is absolute + * @param path to be verified as absolute + */ +export function isAbsolute(path: string): boolean { + assertPath(path); + return path.length > 0 && path.charCodeAt(0) === CHAR_FORWARD_SLASH; +} + +/** + * Join all given a sequence of `paths`,then normalizes the resulting path. + * @param paths to be joined and normalized + */ +export function join(...paths: string[]): string { + if (paths.length === 0) return "."; + let joined: string | undefined; + for (let i = 0, len = paths.length; i < len; ++i) { + const path = paths[i]; + assertPath(path); + if (path.length > 0) { + if (!joined) joined = path; + else joined += `/${path}`; + } + } + if (!joined) return "."; + return normalize(joined); +} + +/** + * Return the relative path from `from` to `to` based on current working directory. + * @param from path in current working directory + * @param to path in current working directory + */ +export function relative(from: string, to: string): string { + assertPath(from); + assertPath(to); + + if (from === to) return ""; + + from = resolve(from); + to = resolve(to); + + if (from === to) return ""; + + // Trim any leading backslashes + let fromStart = 1; + const fromEnd = from.length; + for (; fromStart < fromEnd; ++fromStart) { + if (from.charCodeAt(fromStart) !== CHAR_FORWARD_SLASH) break; + } + const fromLen = fromEnd - fromStart; + + // Trim any leading backslashes + let toStart = 1; + const toEnd = to.length; + for (; toStart < toEnd; ++toStart) { + if (to.charCodeAt(toStart) !== CHAR_FORWARD_SLASH) break; + } + const toLen = toEnd - toStart; + + // Compare paths to find the longest common path from root + const length = fromLen < toLen ? fromLen : toLen; + let lastCommonSep = -1; + let i = 0; + for (; i <= length; ++i) { + if (i === length) { + if (toLen > length) { + if (to.charCodeAt(toStart + i) === CHAR_FORWARD_SLASH) { + // We get here if `from` is the exact base path for `to`. + // For example: from='/foo/bar'; to='/foo/bar/baz' + return to.slice(toStart + i + 1); + } else if (i === 0) { + // We get here if `from` is the root + // For example: from='/'; to='/foo' + return to.slice(toStart + i); + } + } else if (fromLen > length) { + if (from.charCodeAt(fromStart + i) === CHAR_FORWARD_SLASH) { + // We get here if `to` is the exact base path for `from`. + // For example: from='/foo/bar/baz'; to='/foo/bar' + lastCommonSep = i; + } else if (i === 0) { + // We get here if `to` is the root. + // For example: from='/foo'; to='/' + lastCommonSep = 0; + } + } + break; + } + const fromCode = from.charCodeAt(fromStart + i); + const toCode = to.charCodeAt(toStart + i); + if (fromCode !== toCode) break; + else if (fromCode === CHAR_FORWARD_SLASH) lastCommonSep = i; + } + + let out = ""; + // Generate the relative path based on the path difference between `to` + // and `from` + for (i = fromStart + lastCommonSep + 1; i <= fromEnd; ++i) { + if (i === fromEnd || from.charCodeAt(i) === CHAR_FORWARD_SLASH) { + if (out.length === 0) out += ".."; + else out += "/.."; + } + } + + // Lastly, append the rest of the destination (`to`) path that comes after + // the common path parts + if (out.length > 0) return out + to.slice(toStart + lastCommonSep); + else { + toStart += lastCommonSep; + if (to.charCodeAt(toStart) === CHAR_FORWARD_SLASH) ++toStart; + return to.slice(toStart); + } +} + +/** + * Resolves path to a namespace path + * @param path to resolve to namespace + */ +export function toNamespacedPath(path: string): string { + // Non-op on posix systems + return path; +} + +/** + * Return the directory name of a `path`. + * @param path to determine name for + */ +export function dirname(path: string): string { + assertPath(path); + if (path.length === 0) return "."; + const hasRoot = path.charCodeAt(0) === CHAR_FORWARD_SLASH; + let end = -1; + let matchedSlash = true; + for (let i = path.length - 1; i >= 1; --i) { + if (path.charCodeAt(i) === CHAR_FORWARD_SLASH) { + if (!matchedSlash) { + end = i; + break; + } + } else { + // We saw the first non-path separator + matchedSlash = false; + } + } + + if (end === -1) return hasRoot ? "/" : "."; + if (hasRoot && end === 1) return "//"; + return path.slice(0, end); +} + +/** + * Return the last portion of a `path`. Trailing directory separators are ignored. + * @param path to process + * @param ext of path directory + */ +export function basename(path: string, ext = ""): string { + if (ext !== undefined && typeof ext !== "string") { + throw new ERR_INVALID_ARG_TYPE("ext", ["string"], ext); + } + assertPath(path); + + let start = 0; + let end = -1; + let matchedSlash = true; + let i: number; + + if (ext !== undefined && ext.length > 0 && ext.length <= path.length) { + if (ext.length === path.length && ext === path) return ""; + let extIdx = ext.length - 1; + let firstNonSlashEnd = -1; + for (i = path.length - 1; i >= 0; --i) { + const code = path.charCodeAt(i); + if (code === CHAR_FORWARD_SLASH) { + // If we reached a path separator that was not part of a set of path + // separators at the end of the string, stop now + if (!matchedSlash) { + start = i + 1; + break; + } + } else { + if (firstNonSlashEnd === -1) { + // We saw the first non-path separator, remember this index in case + // we need it if the extension ends up not matching + matchedSlash = false; + firstNonSlashEnd = i + 1; + } + if (extIdx >= 0) { + // Try to match the explicit extension + if (code === ext.charCodeAt(extIdx)) { + if (--extIdx === -1) { + // We matched the extension, so mark this as the end of our path + // component + end = i; + } + } else { + // Extension does not match, so our result is the entire path + // component + extIdx = -1; + end = firstNonSlashEnd; + } + } + } + } + + if (start === end) end = firstNonSlashEnd; + else if (end === -1) end = path.length; + return path.slice(start, end); + } else { + for (i = path.length - 1; i >= 0; --i) { + if (path.charCodeAt(i) === CHAR_FORWARD_SLASH) { + // If we reached a path separator that was not part of a set of path + // separators at the end of the string, stop now + if (!matchedSlash) { + start = i + 1; + break; + } + } else if (end === -1) { + // We saw the first non-path separator, mark this as the end of our + // path component + matchedSlash = false; + end = i + 1; + } + } + + if (end === -1) return ""; + return path.slice(start, end); + } +} + +/** + * Return the extension of the `path`. + * @param path with extension + */ +export function extname(path: string): string { + assertPath(path); + let startDot = -1; + let startPart = 0; + let end = -1; + let matchedSlash = true; + // Track the state of characters (if any) we see before our first dot and + // after any path separator we find + let preDotState = 0; + for (let i = path.length - 1; i >= 0; --i) { + const code = path.charCodeAt(i); + if (code === CHAR_FORWARD_SLASH) { + // If we reached a path separator that was not part of a set of path + // separators at the end of the string, stop now + if (!matchedSlash) { + startPart = i + 1; + break; + } + continue; + } + if (end === -1) { + // We saw the first non-path separator, mark this as the end of our + // extension + matchedSlash = false; + end = i + 1; + } + if (code === CHAR_DOT) { + // If this is our first dot, mark it as the start of our extension + if (startDot === -1) startDot = i; + else if (preDotState !== 1) preDotState = 1; + } else if (startDot !== -1) { + // We saw a non-dot and non-path separator before our dot, so we should + // have a good chance at having a non-empty extension + preDotState = -1; + } + } + + if ( + startDot === -1 || + end === -1 || + // We saw a non-dot character immediately before the dot + preDotState === 0 || + // The (right-most) trimmed path component is exactly '..' + (preDotState === 1 && startDot === end - 1 && startDot === startPart + 1) + ) { + return ""; + } + return path.slice(startDot, end); +} + +/** + * Generate a path from `FormatInputPathObject` object. + * @param pathObject with path + */ +export function format(pathObject: FormatInputPathObject): string { + if (pathObject === null || typeof pathObject !== "object") { + throw new ERR_INVALID_ARG_TYPE("pathObject", ["Object"], pathObject); + } + return _format("/", pathObject); +} + +/** + * Return a `ParsedPath` object of the `path`. + * @param path to process + */ +export function parse(path: string): ParsedPath { + assertPath(path); + + const ret: ParsedPath = { root: "", dir: "", base: "", ext: "", name: "" }; + if (path.length === 0) return ret; + const isAbsolute = path.charCodeAt(0) === CHAR_FORWARD_SLASH; + let start: number; + if (isAbsolute) { + ret.root = "/"; + start = 1; + } else { + start = 0; + } + let startDot = -1; + let startPart = 0; + let end = -1; + let matchedSlash = true; + let i = path.length - 1; + + // Track the state of characters (if any) we see before our first dot and + // after any path separator we find + let preDotState = 0; + + // Get non-dir info + for (; i >= start; --i) { + const code = path.charCodeAt(i); + if (code === CHAR_FORWARD_SLASH) { + // If we reached a path separator that was not part of a set of path + // separators at the end of the string, stop now + if (!matchedSlash) { + startPart = i + 1; + break; + } + continue; + } + if (end === -1) { + // We saw the first non-path separator, mark this as the end of our + // extension + matchedSlash = false; + end = i + 1; + } + if (code === CHAR_DOT) { + // If this is our first dot, mark it as the start of our extension + if (startDot === -1) startDot = i; + else if (preDotState !== 1) preDotState = 1; + } else if (startDot !== -1) { + // We saw a non-dot and non-path separator before our dot, so we should + // have a good chance at having a non-empty extension + preDotState = -1; + } + } + + if ( + startDot === -1 || + end === -1 || + // We saw a non-dot character immediately before the dot + preDotState === 0 || + // The (right-most) trimmed path component is exactly '..' + (preDotState === 1 && startDot === end - 1 && startDot === startPart + 1) + ) { + if (end !== -1) { + if (startPart === 0 && isAbsolute) { + ret.base = ret.name = path.slice(1, end); + } else { + ret.base = ret.name = path.slice(startPart, end); + } + } + } else { + if (startPart === 0 && isAbsolute) { + ret.name = path.slice(1, startDot); + ret.base = path.slice(1, end); + } else { + ret.name = path.slice(startPart, startDot); + ret.base = path.slice(startPart, end); + } + ret.ext = path.slice(startDot, end); + } + + if (startPart > 0) ret.dir = path.slice(0, startPart - 1); + else if (isAbsolute) ret.dir = "/"; + + return ret; +} + +/** + * Converts a file URL to a path string. + * + * ```ts + * fromFileUrl("file:///home/foo"); // "/home/foo" + * ``` + * @param url of a file URL + */ +export function fromFileUrl(url: string | URL): string { + url = url instanceof URL ? url : new URL(url); + if (url.protocol != "file:") { + throw new TypeError("Must be a file URL."); + } + return decodeURIComponent( + url.pathname.replace(/%(?![0-9A-Fa-f]{2})/g, "%25"), + ); +} + +/** + * Converts a path string to a file URL. + * + * ```ts + * toFileUrl("/home/foo"); // new URL("file:///home/foo") + * ``` + * @param path to convert to file URL + */ +export function toFileUrl(path: string): URL { + if (!isAbsolute(path)) { + throw new TypeError("Must be an absolute path."); + } + const url = new URL("file:///"); + url.pathname = encodeWhitespace( + path.replace(/%/g, "%25").replace(/\\/g, "%5C"), + ); + return url; +} + +export default { + basename, + delimiter, + dirname, + extname, + format, + fromFileUrl, + isAbsolute, + join, + normalize, + parse, + relative, + resolve, + sep, + toFileUrl, + toNamespacedPath, +}; diff --git a/ext/node/polyfills/path/separator.ts b/ext/node/polyfills/path/separator.ts new file mode 100644 index 00000000000000..2cfde31d028e9f --- /dev/null +++ b/ext/node/polyfills/path/separator.ts @@ -0,0 +1,6 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. + +import { isWindows } from "internal:deno_node/polyfills/_util/os.ts"; + +export const SEP = isWindows ? "\\" : "/"; +export const SEP_PATTERN = isWindows ? /[\\/]+/ : /\/+/; diff --git a/ext/node/polyfills/path/win32.ts b/ext/node/polyfills/path/win32.ts new file mode 100644 index 00000000000000..4b30e34304fa80 --- /dev/null +++ b/ext/node/polyfills/path/win32.ts @@ -0,0 +1,1025 @@ +// Copyright the Browserify authors. MIT License. +// Ported from https://github.com/browserify/path-browserify/ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. + +import type { + FormatInputPathObject, + ParsedPath, +} from "internal:deno_node/polyfills/path/_interface.ts"; +import { + CHAR_BACKWARD_SLASH, + CHAR_COLON, + CHAR_DOT, + CHAR_QUESTION_MARK, +} from "internal:deno_node/polyfills/path/_constants.ts"; +import { ERR_INVALID_ARG_TYPE } from "internal:deno_node/polyfills/internal/errors.ts"; + +import { + _format, + assertPath, + encodeWhitespace, + isPathSeparator, + isWindowsDeviceRoot, + normalizeString, +} from "internal:deno_node/polyfills/path/_util.ts"; +import { assert } from "internal:deno_node/polyfills/_util/asserts.ts"; + +export const sep = "\\"; +export const delimiter = ";"; + +/** + * Resolves path segments into a `path` + * @param pathSegments to process to path + */ +export function resolve(...pathSegments: string[]): string { + let resolvedDevice = ""; + let resolvedTail = ""; + let resolvedAbsolute = false; + + for (let i = pathSegments.length - 1; i >= -1; i--) { + let path: string; + // deno-lint-ignore no-explicit-any + const { Deno } = globalThis as any; + if (i >= 0) { + path = pathSegments[i]; + } else if (!resolvedDevice) { + if (typeof Deno?.cwd !== "function") { + throw new TypeError("Resolved a drive-letter-less path without a CWD."); + } + path = Deno.cwd(); + } else { + if ( + typeof Deno?.env?.get !== "function" || typeof Deno?.cwd !== "function" + ) { + throw new TypeError("Resolved a relative path without a CWD."); + } + path = Deno.cwd(); + + // Verify that a cwd was found and that it actually points + // to our drive. If not, default to the drive's root. + if ( + path === undefined || + path.slice(0, 3).toLowerCase() !== `${resolvedDevice.toLowerCase()}\\` + ) { + path = `${resolvedDevice}\\`; + } + } + + assertPath(path); + + const len = path.length; + + // Skip empty entries + if (len === 0) continue; + + let rootEnd = 0; + let device = ""; + let isAbsolute = false; + const code = path.charCodeAt(0); + + // Try to match a root + if (len > 1) { + if (isPathSeparator(code)) { + // Possible UNC root + + // If we started with a separator, we know we at least have an + // absolute path of some kind (UNC or otherwise) + isAbsolute = true; + + if (isPathSeparator(path.charCodeAt(1))) { + // Matched double path separator at beginning + let j = 2; + let last = j; + // Match 1 or more non-path separators + for (; j < len; ++j) { + if (isPathSeparator(path.charCodeAt(j))) break; + } + if (j < len && j !== last) { + const firstPart = path.slice(last, j); + // Matched! + last = j; + // Match 1 or more path separators + for (; j < len; ++j) { + if (!isPathSeparator(path.charCodeAt(j))) break; + } + if (j < len && j !== last) { + // Matched! + last = j; + // Match 1 or more non-path separators + for (; j < len; ++j) { + if (isPathSeparator(path.charCodeAt(j))) break; + } + if (j === len) { + // We matched a UNC root only + device = `\\\\${firstPart}\\${path.slice(last)}`; + rootEnd = j; + } else if (j !== last) { + // We matched a UNC root with leftovers + + device = `\\\\${firstPart}\\${path.slice(last, j)}`; + rootEnd = j; + } + } + } + } else { + rootEnd = 1; + } + } else if (isWindowsDeviceRoot(code)) { + // Possible device root + + if (path.charCodeAt(1) === CHAR_COLON) { + device = path.slice(0, 2); + rootEnd = 2; + if (len > 2) { + if (isPathSeparator(path.charCodeAt(2))) { + // Treat separator following drive name as an absolute path + // indicator + isAbsolute = true; + rootEnd = 3; + } + } + } + } + } else if (isPathSeparator(code)) { + // `path` contains just a path separator + rootEnd = 1; + isAbsolute = true; + } + + if ( + device.length > 0 && + resolvedDevice.length > 0 && + device.toLowerCase() !== resolvedDevice.toLowerCase() + ) { + // This path points to another device so it is not applicable + continue; + } + + if (resolvedDevice.length === 0 && device.length > 0) { + resolvedDevice = device; + } + if (!resolvedAbsolute) { + resolvedTail = `${path.slice(rootEnd)}\\${resolvedTail}`; + resolvedAbsolute = isAbsolute; + } + + if (resolvedAbsolute && resolvedDevice.length > 0) break; + } + + // At this point the path should be resolved to a full absolute path, + // but handle relative paths to be safe (might happen when process.cwd() + // fails) + + // Normalize the tail path + resolvedTail = normalizeString( + resolvedTail, + !resolvedAbsolute, + "\\", + isPathSeparator, + ); + + return resolvedDevice + (resolvedAbsolute ? "\\" : "") + resolvedTail || "."; +} + +/** + * Normalizes a `path` + * @param path to normalize + */ +export function normalize(path: string): string { + assertPath(path); + const len = path.length; + if (len === 0) return "."; + let rootEnd = 0; + let device: string | undefined; + let isAbsolute = false; + const code = path.charCodeAt(0); + + // Try to match a root + if (len > 1) { + if (isPathSeparator(code)) { + // Possible UNC root + + // If we started with a separator, we know we at least have an absolute + // path of some kind (UNC or otherwise) + isAbsolute = true; + + if (isPathSeparator(path.charCodeAt(1))) { + // Matched double path separator at beginning + let j = 2; + let last = j; + // Match 1 or more non-path separators + for (; j < len; ++j) { + if (isPathSeparator(path.charCodeAt(j))) break; + } + if (j < len && j !== last) { + const firstPart = path.slice(last, j); + // Matched! + last = j; + // Match 1 or more path separators + for (; j < len; ++j) { + if (!isPathSeparator(path.charCodeAt(j))) break; + } + if (j < len && j !== last) { + // Matched! + last = j; + // Match 1 or more non-path separators + for (; j < len; ++j) { + if (isPathSeparator(path.charCodeAt(j))) break; + } + if (j === len) { + // We matched a UNC root only + // Return the normalized version of the UNC root since there + // is nothing left to process + + return `\\\\${firstPart}\\${path.slice(last)}\\`; + } else if (j !== last) { + // We matched a UNC root with leftovers + + device = `\\\\${firstPart}\\${path.slice(last, j)}`; + rootEnd = j; + } + } + } + } else { + rootEnd = 1; + } + } else if (isWindowsDeviceRoot(code)) { + // Possible device root + + if (path.charCodeAt(1) === CHAR_COLON) { + device = path.slice(0, 2); + rootEnd = 2; + if (len > 2) { + if (isPathSeparator(path.charCodeAt(2))) { + // Treat separator following drive name as an absolute path + // indicator + isAbsolute = true; + rootEnd = 3; + } + } + } + } + } else if (isPathSeparator(code)) { + // `path` contains just a path separator, exit early to avoid unnecessary + // work + return "\\"; + } + + let tail: string; + if (rootEnd < len) { + tail = normalizeString( + path.slice(rootEnd), + !isAbsolute, + "\\", + isPathSeparator, + ); + } else { + tail = ""; + } + if (tail.length === 0 && !isAbsolute) tail = "."; + if (tail.length > 0 && isPathSeparator(path.charCodeAt(len - 1))) { + tail += "\\"; + } + if (device === undefined) { + if (isAbsolute) { + if (tail.length > 0) return `\\${tail}`; + else return "\\"; + } else if (tail.length > 0) { + return tail; + } else { + return ""; + } + } else if (isAbsolute) { + if (tail.length > 0) return `${device}\\${tail}`; + else return `${device}\\`; + } else if (tail.length > 0) { + return device + tail; + } else { + return device; + } +} + +/** + * Verifies whether path is absolute + * @param path to verify + */ +export function isAbsolute(path: string): boolean { + assertPath(path); + const len = path.length; + if (len === 0) return false; + + const code = path.charCodeAt(0); + if (isPathSeparator(code)) { + return true; + } else if (isWindowsDeviceRoot(code)) { + // Possible device root + + if (len > 2 && path.charCodeAt(1) === CHAR_COLON) { + if (isPathSeparator(path.charCodeAt(2))) return true; + } + } + return false; +} + +/** + * Join all given a sequence of `paths`,then normalizes the resulting path. + * @param paths to be joined and normalized + */ +export function join(...paths: string[]): string { + const pathsCount = paths.length; + if (pathsCount === 0) return "."; + + let joined: string | undefined; + let firstPart: string | null = null; + for (let i = 0; i < pathsCount; ++i) { + const path = paths[i]; + assertPath(path); + if (path.length > 0) { + if (joined === undefined) joined = firstPart = path; + else joined += `\\${path}`; + } + } + + if (joined === undefined) return "."; + + // Make sure that the joined path doesn't start with two slashes, because + // normalize() will mistake it for an UNC path then. + // + // This step is skipped when it is very clear that the user actually + // intended to point at an UNC path. This is assumed when the first + // non-empty string arguments starts with exactly two slashes followed by + // at least one more non-slash character. + // + // Note that for normalize() to treat a path as an UNC path it needs to + // have at least 2 components, so we don't filter for that here. + // This means that the user can use join to construct UNC paths from + // a server name and a share name; for example: + // path.join('//server', 'share') -> '\\\\server\\share\\') + let needsReplace = true; + let slashCount = 0; + assert(firstPart != null); + if (isPathSeparator(firstPart.charCodeAt(0))) { + ++slashCount; + const firstLen = firstPart.length; + if (firstLen > 1) { + if (isPathSeparator(firstPart.charCodeAt(1))) { + ++slashCount; + if (firstLen > 2) { + if (isPathSeparator(firstPart.charCodeAt(2))) ++slashCount; + else { + // We matched a UNC path in the first part + needsReplace = false; + } + } + } + } + } + if (needsReplace) { + // Find any more consecutive slashes we need to replace + for (; slashCount < joined.length; ++slashCount) { + if (!isPathSeparator(joined.charCodeAt(slashCount))) break; + } + + // Replace the slashes if needed + if (slashCount >= 2) joined = `\\${joined.slice(slashCount)}`; + } + + return normalize(joined); +} + +/** + * It will solve the relative path from `from` to `to`, for instance: + * from = 'C:\\orandea\\test\\aaa' + * to = 'C:\\orandea\\impl\\bbb' + * The output of the function should be: '..\\..\\impl\\bbb' + * @param from relative path + * @param to relative path + */ +export function relative(from: string, to: string): string { + assertPath(from); + assertPath(to); + + if (from === to) return ""; + + const fromOrig = resolve(from); + const toOrig = resolve(to); + + if (fromOrig === toOrig) return ""; + + from = fromOrig.toLowerCase(); + to = toOrig.toLowerCase(); + + if (from === to) return ""; + + // Trim any leading backslashes + let fromStart = 0; + let fromEnd = from.length; + for (; fromStart < fromEnd; ++fromStart) { + if (from.charCodeAt(fromStart) !== CHAR_BACKWARD_SLASH) break; + } + // Trim trailing backslashes (applicable to UNC paths only) + for (; fromEnd - 1 > fromStart; --fromEnd) { + if (from.charCodeAt(fromEnd - 1) !== CHAR_BACKWARD_SLASH) break; + } + const fromLen = fromEnd - fromStart; + + // Trim any leading backslashes + let toStart = 0; + let toEnd = to.length; + for (; toStart < toEnd; ++toStart) { + if (to.charCodeAt(toStart) !== CHAR_BACKWARD_SLASH) break; + } + // Trim trailing backslashes (applicable to UNC paths only) + for (; toEnd - 1 > toStart; --toEnd) { + if (to.charCodeAt(toEnd - 1) !== CHAR_BACKWARD_SLASH) break; + } + const toLen = toEnd - toStart; + + // Compare paths to find the longest common path from root + const length = fromLen < toLen ? fromLen : toLen; + let lastCommonSep = -1; + let i = 0; + for (; i <= length; ++i) { + if (i === length) { + if (toLen > length) { + if (to.charCodeAt(toStart + i) === CHAR_BACKWARD_SLASH) { + // We get here if `from` is the exact base path for `to`. + // For example: from='C:\\foo\\bar'; to='C:\\foo\\bar\\baz' + return toOrig.slice(toStart + i + 1); + } else if (i === 2) { + // We get here if `from` is the device root. + // For example: from='C:\\'; to='C:\\foo' + return toOrig.slice(toStart + i); + } + } + if (fromLen > length) { + if (from.charCodeAt(fromStart + i) === CHAR_BACKWARD_SLASH) { + // We get here if `to` is the exact base path for `from`. + // For example: from='C:\\foo\\bar'; to='C:\\foo' + lastCommonSep = i; + } else if (i === 2) { + // We get here if `to` is the device root. + // For example: from='C:\\foo\\bar'; to='C:\\' + lastCommonSep = 3; + } + } + break; + } + const fromCode = from.charCodeAt(fromStart + i); + const toCode = to.charCodeAt(toStart + i); + if (fromCode !== toCode) break; + else if (fromCode === CHAR_BACKWARD_SLASH) lastCommonSep = i; + } + + // We found a mismatch before the first common path separator was seen, so + // return the original `to`. + if (i !== length && lastCommonSep === -1) { + return toOrig; + } + + let out = ""; + if (lastCommonSep === -1) lastCommonSep = 0; + // Generate the relative path based on the path difference between `to` and + // `from` + for (i = fromStart + lastCommonSep + 1; i <= fromEnd; ++i) { + if (i === fromEnd || from.charCodeAt(i) === CHAR_BACKWARD_SLASH) { + if (out.length === 0) out += ".."; + else out += "\\.."; + } + } + + // Lastly, append the rest of the destination (`to`) path that comes after + // the common path parts + if (out.length > 0) { + return out + toOrig.slice(toStart + lastCommonSep, toEnd); + } else { + toStart += lastCommonSep; + if (toOrig.charCodeAt(toStart) === CHAR_BACKWARD_SLASH) ++toStart; + return toOrig.slice(toStart, toEnd); + } +} + +/** + * Resolves path to a namespace path + * @param path to resolve to namespace + */ +export function toNamespacedPath(path: string): string { + // Note: this will *probably* throw somewhere. + if (typeof path !== "string") return path; + if (path.length === 0) return ""; + + const resolvedPath = resolve(path); + + if (resolvedPath.length >= 3) { + if (resolvedPath.charCodeAt(0) === CHAR_BACKWARD_SLASH) { + // Possible UNC root + + if (resolvedPath.charCodeAt(1) === CHAR_BACKWARD_SLASH) { + const code = resolvedPath.charCodeAt(2); + if (code !== CHAR_QUESTION_MARK && code !== CHAR_DOT) { + // Matched non-long UNC root, convert the path to a long UNC path + return `\\\\?\\UNC\\${resolvedPath.slice(2)}`; + } + } + } else if (isWindowsDeviceRoot(resolvedPath.charCodeAt(0))) { + // Possible device root + + if ( + resolvedPath.charCodeAt(1) === CHAR_COLON && + resolvedPath.charCodeAt(2) === CHAR_BACKWARD_SLASH + ) { + // Matched device root, convert the path to a long UNC path + return `\\\\?\\${resolvedPath}`; + } + } + } + + return path; +} + +/** + * Return the directory name of a `path`. + * @param path to determine name for + */ +export function dirname(path: string): string { + assertPath(path); + const len = path.length; + if (len === 0) return "."; + let rootEnd = -1; + let end = -1; + let matchedSlash = true; + let offset = 0; + const code = path.charCodeAt(0); + + // Try to match a root + if (len > 1) { + if (isPathSeparator(code)) { + // Possible UNC root + + rootEnd = offset = 1; + + if (isPathSeparator(path.charCodeAt(1))) { + // Matched double path separator at beginning + let j = 2; + let last = j; + // Match 1 or more non-path separators + for (; j < len; ++j) { + if (isPathSeparator(path.charCodeAt(j))) break; + } + if (j < len && j !== last) { + // Matched! + last = j; + // Match 1 or more path separators + for (; j < len; ++j) { + if (!isPathSeparator(path.charCodeAt(j))) break; + } + if (j < len && j !== last) { + // Matched! + last = j; + // Match 1 or more non-path separators + for (; j < len; ++j) { + if (isPathSeparator(path.charCodeAt(j))) break; + } + if (j === len) { + // We matched a UNC root only + return path; + } + if (j !== last) { + // We matched a UNC root with leftovers + + // Offset by 1 to include the separator after the UNC root to + // treat it as a "normal root" on top of a (UNC) root + rootEnd = offset = j + 1; + } + } + } + } + } else if (isWindowsDeviceRoot(code)) { + // Possible device root + + if (path.charCodeAt(1) === CHAR_COLON) { + rootEnd = offset = 2; + if (len > 2) { + if (isPathSeparator(path.charCodeAt(2))) rootEnd = offset = 3; + } + } + } + } else if (isPathSeparator(code)) { + // `path` contains just a path separator, exit early to avoid + // unnecessary work + return path; + } + + for (let i = len - 1; i >= offset; --i) { + if (isPathSeparator(path.charCodeAt(i))) { + if (!matchedSlash) { + end = i; + break; + } + } else { + // We saw the first non-path separator + matchedSlash = false; + } + } + + if (end === -1) { + if (rootEnd === -1) return "."; + else end = rootEnd; + } + return path.slice(0, end); +} + +/** + * Return the last portion of a `path`. Trailing directory separators are ignored. + * @param path to process + * @param ext of path directory + */ +export function basename(path: string, ext = ""): string { + if (ext !== undefined && typeof ext !== "string") { + throw new ERR_INVALID_ARG_TYPE("ext", ["string"], ext); + } + + assertPath(path); + + let start = 0; + let end = -1; + let matchedSlash = true; + let i: number; + + // Check for a drive letter prefix so as not to mistake the following + // path separator as an extra separator at the end of the path that can be + // disregarded + if (path.length >= 2) { + const drive = path.charCodeAt(0); + if (isWindowsDeviceRoot(drive)) { + if (path.charCodeAt(1) === CHAR_COLON) start = 2; + } + } + + if (ext !== undefined && ext.length > 0 && ext.length <= path.length) { + if (ext.length === path.length && ext === path) return ""; + let extIdx = ext.length - 1; + let firstNonSlashEnd = -1; + for (i = path.length - 1; i >= start; --i) { + const code = path.charCodeAt(i); + if (isPathSeparator(code)) { + // If we reached a path separator that was not part of a set of path + // separators at the end of the string, stop now + if (!matchedSlash) { + start = i + 1; + break; + } + } else { + if (firstNonSlashEnd === -1) { + // We saw the first non-path separator, remember this index in case + // we need it if the extension ends up not matching + matchedSlash = false; + firstNonSlashEnd = i + 1; + } + if (extIdx >= 0) { + // Try to match the explicit extension + if (code === ext.charCodeAt(extIdx)) { + if (--extIdx === -1) { + // We matched the extension, so mark this as the end of our path + // component + end = i; + } + } else { + // Extension does not match, so our result is the entire path + // component + extIdx = -1; + end = firstNonSlashEnd; + } + } + } + } + + if (start === end) end = firstNonSlashEnd; + else if (end === -1) end = path.length; + return path.slice(start, end); + } else { + for (i = path.length - 1; i >= start; --i) { + if (isPathSeparator(path.charCodeAt(i))) { + // If we reached a path separator that was not part of a set of path + // separators at the end of the string, stop now + if (!matchedSlash) { + start = i + 1; + break; + } + } else if (end === -1) { + // We saw the first non-path separator, mark this as the end of our + // path component + matchedSlash = false; + end = i + 1; + } + } + + if (end === -1) return ""; + return path.slice(start, end); + } +} + +/** + * Return the extension of the `path`. + * @param path with extension + */ +export function extname(path: string): string { + assertPath(path); + let start = 0; + let startDot = -1; + let startPart = 0; + let end = -1; + let matchedSlash = true; + // Track the state of characters (if any) we see before our first dot and + // after any path separator we find + let preDotState = 0; + + // Check for a drive letter prefix so as not to mistake the following + // path separator as an extra separator at the end of the path that can be + // disregarded + + if ( + path.length >= 2 && + path.charCodeAt(1) === CHAR_COLON && + isWindowsDeviceRoot(path.charCodeAt(0)) + ) { + start = startPart = 2; + } + + for (let i = path.length - 1; i >= start; --i) { + const code = path.charCodeAt(i); + if (isPathSeparator(code)) { + // If we reached a path separator that was not part of a set of path + // separators at the end of the string, stop now + if (!matchedSlash) { + startPart = i + 1; + break; + } + continue; + } + if (end === -1) { + // We saw the first non-path separator, mark this as the end of our + // extension + matchedSlash = false; + end = i + 1; + } + if (code === CHAR_DOT) { + // If this is our first dot, mark it as the start of our extension + if (startDot === -1) startDot = i; + else if (preDotState !== 1) preDotState = 1; + } else if (startDot !== -1) { + // We saw a non-dot and non-path separator before our dot, so we should + // have a good chance at having a non-empty extension + preDotState = -1; + } + } + + if ( + startDot === -1 || + end === -1 || + // We saw a non-dot character immediately before the dot + preDotState === 0 || + // The (right-most) trimmed path component is exactly '..' + (preDotState === 1 && startDot === end - 1 && startDot === startPart + 1) + ) { + return ""; + } + return path.slice(startDot, end); +} + +/** + * Generate a path from `FormatInputPathObject` object. + * @param pathObject with path + */ +export function format(pathObject: FormatInputPathObject): string { + if (pathObject === null || typeof pathObject !== "object") { + throw new ERR_INVALID_ARG_TYPE("pathObject", ["Object"], pathObject); + } + return _format("\\", pathObject); +} + +/** + * Return a `ParsedPath` object of the `path`. + * @param path to process + */ +export function parse(path: string): ParsedPath { + assertPath(path); + + const ret: ParsedPath = { root: "", dir: "", base: "", ext: "", name: "" }; + + const len = path.length; + if (len === 0) return ret; + + let rootEnd = 0; + let code = path.charCodeAt(0); + + // Try to match a root + if (len > 1) { + if (isPathSeparator(code)) { + // Possible UNC root + + rootEnd = 1; + if (isPathSeparator(path.charCodeAt(1))) { + // Matched double path separator at beginning + let j = 2; + let last = j; + // Match 1 or more non-path separators + for (; j < len; ++j) { + if (isPathSeparator(path.charCodeAt(j))) break; + } + if (j < len && j !== last) { + // Matched! + last = j; + // Match 1 or more path separators + for (; j < len; ++j) { + if (!isPathSeparator(path.charCodeAt(j))) break; + } + if (j < len && j !== last) { + // Matched! + last = j; + // Match 1 or more non-path separators + for (; j < len; ++j) { + if (isPathSeparator(path.charCodeAt(j))) break; + } + if (j === len) { + // We matched a UNC root only + + rootEnd = j; + } else if (j !== last) { + // We matched a UNC root with leftovers + + rootEnd = j + 1; + } + } + } + } + } else if (isWindowsDeviceRoot(code)) { + // Possible device root + + if (path.charCodeAt(1) === CHAR_COLON) { + rootEnd = 2; + if (len > 2) { + if (isPathSeparator(path.charCodeAt(2))) { + if (len === 3) { + // `path` contains just a drive root, exit early to avoid + // unnecessary work + ret.root = ret.dir = path; + return ret; + } + rootEnd = 3; + } + } else { + // `path` contains just a drive root, exit early to avoid + // unnecessary work + ret.root = ret.dir = path; + return ret; + } + } + } + } else if (isPathSeparator(code)) { + // `path` contains just a path separator, exit early to avoid + // unnecessary work + ret.root = ret.dir = path; + return ret; + } + + if (rootEnd > 0) ret.root = path.slice(0, rootEnd); + + let startDot = -1; + let startPart = rootEnd; + let end = -1; + let matchedSlash = true; + let i = path.length - 1; + + // Track the state of characters (if any) we see before our first dot and + // after any path separator we find + let preDotState = 0; + + // Get non-dir info + for (; i >= rootEnd; --i) { + code = path.charCodeAt(i); + if (isPathSeparator(code)) { + // If we reached a path separator that was not part of a set of path + // separators at the end of the string, stop now + if (!matchedSlash) { + startPart = i + 1; + break; + } + continue; + } + if (end === -1) { + // We saw the first non-path separator, mark this as the end of our + // extension + matchedSlash = false; + end = i + 1; + } + if (code === CHAR_DOT) { + // If this is our first dot, mark it as the start of our extension + if (startDot === -1) startDot = i; + else if (preDotState !== 1) preDotState = 1; + } else if (startDot !== -1) { + // We saw a non-dot and non-path separator before our dot, so we should + // have a good chance at having a non-empty extension + preDotState = -1; + } + } + + if ( + startDot === -1 || + end === -1 || + // We saw a non-dot character immediately before the dot + preDotState === 0 || + // The (right-most) trimmed path component is exactly '..' + (preDotState === 1 && startDot === end - 1 && startDot === startPart + 1) + ) { + if (end !== -1) { + ret.base = ret.name = path.slice(startPart, end); + } + } else { + ret.name = path.slice(startPart, startDot); + ret.base = path.slice(startPart, end); + ret.ext = path.slice(startDot, end); + } + + // If the directory is the root, use the entire root as the `dir` including + // the trailing slash if any (`C:\abc` -> `C:\`). Otherwise, strip out the + // trailing slash (`C:\abc\def` -> `C:\abc`). + if (startPart > 0 && startPart !== rootEnd) { + ret.dir = path.slice(0, startPart - 1); + } else ret.dir = ret.root; + + return ret; +} + +/** + * Converts a file URL to a path string. + * + * ```ts + * fromFileUrl("file:///home/foo"); // "\\home\\foo" + * fromFileUrl("file:///C:/Users/foo"); // "C:\\Users\\foo" + * fromFileUrl("file://localhost/home/foo"); // "\\\\localhost\\home\\foo" + * ``` + * @param url of a file URL + */ +export function fromFileUrl(url: string | URL): string { + url = url instanceof URL ? url : new URL(url); + if (url.protocol != "file:") { + throw new TypeError("Must be a file URL."); + } + let path = decodeURIComponent( + url.pathname.replace(/\//g, "\\").replace(/%(?![0-9A-Fa-f]{2})/g, "%25"), + ).replace(/^\\*([A-Za-z]:)(\\|$)/, "$1\\"); + if (url.hostname != "") { + // Note: The `URL` implementation guarantees that the drive letter and + // hostname are mutually exclusive. Otherwise it would not have been valid + // to append the hostname and path like this. + path = `\\\\${url.hostname}${path}`; + } + return path; +} + +/** + * Converts a path string to a file URL. + * + * ```ts + * toFileUrl("\\home\\foo"); // new URL("file:///home/foo") + * toFileUrl("C:\\Users\\foo"); // new URL("file:///C:/Users/foo") + * toFileUrl("\\\\127.0.0.1\\home\\foo"); // new URL("file://127.0.0.1/home/foo") + * ``` + * @param path to convert to file URL + */ +export function toFileUrl(path: string): URL { + if (!isAbsolute(path)) { + throw new TypeError("Must be an absolute path."); + } + const [, hostname, pathname] = path.match( + /^(?:[/\\]{2}([^/\\]+)(?=[/\\](?:[^/\\]|$)))?(.*)/, + )!; + const url = new URL("file:///"); + url.pathname = encodeWhitespace(pathname.replace(/%/g, "%25")); + if (hostname != null && hostname != "localhost") { + url.hostname = hostname; + if (!url.hostname) { + throw new TypeError("Invalid hostname."); + } + } + return url; +} + +export default { + basename, + delimiter, + dirname, + extname, + format, + fromFileUrl, + isAbsolute, + join, + normalize, + parse, + relative, + resolve, + sep, + toFileUrl, + toNamespacedPath, +}; diff --git a/ext/node/polyfills/perf_hooks.ts b/ext/node/polyfills/perf_hooks.ts new file mode 100644 index 00000000000000..3888ec2c541e20 --- /dev/null +++ b/ext/node/polyfills/perf_hooks.ts @@ -0,0 +1,86 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. +import { notImplemented } from "internal:deno_node/polyfills/_utils.ts"; +import { + performance as shimPerformance, + PerformanceEntry, +} from "internal:deno_web/15_performance.js"; + +// FIXME(bartlomieju) +const PerformanceObserver = undefined; +const constants = {}; + +const performance: + & Omit< + Performance, + "clearMeasures" | "getEntries" | "getEntriesByName" | "getEntriesByType" + > + & { + // deno-lint-ignore no-explicit-any + eventLoopUtilization: any; + nodeTiming: Record; + // deno-lint-ignore no-explicit-any + timerify: any; + // deno-lint-ignore no-explicit-any + timeOrigin: any; + } = { + clearMarks: (markName: string) => shimPerformance.clearMarks(markName), + eventLoopUtilization: () => + notImplemented("eventLoopUtilization from performance"), + mark: (markName: string) => shimPerformance.mark(markName), + measure: ( + measureName: string, + startMark?: string | PerformanceMeasureOptions, + endMark?: string, + ): PerformanceMeasure => { + if (endMark) { + return shimPerformance.measure( + measureName, + startMark as string, + endMark, + ); + } else { + return shimPerformance.measure( + measureName, + startMark as PerformanceMeasureOptions, + ); + } + }, + nodeTiming: {}, + now: () => shimPerformance.now(), + timerify: () => notImplemented("timerify from performance"), + // deno-lint-ignore no-explicit-any + timeOrigin: (shimPerformance as any).timeOrigin, + // @ts-ignore waiting on update in `deno`, but currently this is + // a circular dependency + toJSON: () => shimPerformance.toJSON(), + addEventListener: ( + ...args: Parameters + ) => shimPerformance.addEventListener(...args), + removeEventListener: ( + ...args: Parameters + ) => shimPerformance.removeEventListener(...args), + dispatchEvent: ( + ...args: Parameters + ) => shimPerformance.dispatchEvent(...args), + }; + +const monitorEventLoopDelay = () => + notImplemented( + "monitorEventLoopDelay from performance", + ); + +export default { + performance, + PerformanceObserver, + PerformanceEntry, + monitorEventLoopDelay, + constants, +}; + +export { + constants, + monitorEventLoopDelay, + performance, + PerformanceEntry, + PerformanceObserver, +}; diff --git a/ext/node/polyfills/process.ts b/ext/node/polyfills/process.ts new file mode 100644 index 00000000000000..779eefb8c2d2cd --- /dev/null +++ b/ext/node/polyfills/process.ts @@ -0,0 +1,775 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. +// Copyright Joyent, Inc. and Node.js contributors. All rights reserved. MIT license. + +const internals = globalThis.__bootstrap.internals; +import { core } from "internal:deno_node/polyfills/_core.ts"; +import { + notImplemented, + warnNotImplemented, +} from "internal:deno_node/polyfills/_utils.ts"; +import { EventEmitter } from "internal:deno_node/polyfills/events.ts"; +import { validateString } from "internal:deno_node/polyfills/internal/validators.mjs"; +import { + ERR_INVALID_ARG_TYPE, + ERR_UNKNOWN_SIGNAL, + errnoException, +} from "internal:deno_node/polyfills/internal/errors.ts"; +import { getOptionValue } from "internal:deno_node/polyfills/internal/options.ts"; +import { assert } from "internal:deno_node/polyfills/_util/asserts.ts"; +import { fromFileUrl, join } from "internal:deno_node/polyfills/path.ts"; +import { + arch as arch_, + chdir, + cwd, + env, + nextTick as _nextTick, + version, + versions, +} from "internal:deno_node/polyfills/_process/process.ts"; +import { _exiting } from "internal:deno_node/polyfills/_process/exiting.ts"; +export { _nextTick as nextTick, chdir, cwd, env, version, versions }; +import { + createWritableStdioStream, + initStdin, +} from "internal:deno_node/polyfills/_process/streams.mjs"; +import { stdio } from "internal:deno_node/polyfills/_process/stdio.mjs"; +import { + enableNextTick, + processTicksAndRejections, + runNextTicks, +} from "internal:deno_node/polyfills/_next_tick.ts"; +import { isWindows } from "internal:deno_node/polyfills/_util/os.ts"; +import * as files from "internal:runtime/js/40_files.js"; + +// TODO(kt3k): This should be set at start up time +export let arch = ""; + +// TODO(kt3k): This should be set at start up time +export let platform = ""; + +// TODO(kt3k): This should be set at start up time +export let pid = 0; + +// TODO(kt3k): Give better types to stdio objects +// deno-lint-ignore no-explicit-any +let stderr = null as any; +// deno-lint-ignore no-explicit-any +let stdin = null as any; +// deno-lint-ignore no-explicit-any +let stdout = null as any; + +export { stderr, stdin, stdout }; +import { getBinding } from "internal:deno_node/polyfills/internal_binding/mod.ts"; +import * as constants from "internal:deno_node/polyfills/internal_binding/constants.ts"; +import * as uv from "internal:deno_node/polyfills/internal_binding/uv.ts"; +import type { BindingName } from "internal:deno_node/polyfills/internal_binding/mod.ts"; +import { buildAllowedFlags } from "internal:deno_node/polyfills/internal/process/per_thread.mjs"; + +// @ts-ignore Deno[Deno.internal] is used on purpose here +const DenoCommand = Deno[Deno.internal]?.nodeUnstable?.Command || + Deno.Command; + +const notImplementedEvents = [ + "disconnect", + "message", + "multipleResolves", + "rejectionHandled", + "worker", +]; + +export const argv: string[] = []; + +// Overwrites the 1st item with getter. +// TODO(bartlomieju): added "configurable: true" to make this work for binary +// commands, but that is probably a wrong solution +// TODO(bartlomieju): move the configuration for all "argv" to +// "internals.__bootstrapNodeProcess" +Object.defineProperty(argv, "0", { + get: () => { + return Deno.execPath(); + }, + configurable: true, +}); +// Overwrites the 2st item with getter. +Object.defineProperty(argv, "1", { + get: () => { + if (Deno.mainModule.startsWith("file:")) { + return fromFileUrl(Deno.mainModule); + } else { + return join(Deno.cwd(), "$deno$node.js"); + } + }, +}); + +/** https://nodejs.org/api/process.html#process_process_exit_code */ +export const exit = (code?: number | string) => { + if (code || code === 0) { + if (typeof code === "string") { + const parsedCode = parseInt(code); + process.exitCode = isNaN(parsedCode) ? undefined : parsedCode; + } else { + process.exitCode = code; + } + } + + if (!process._exiting) { + process._exiting = true; + // FIXME(bartlomieju): this is wrong, we won't be using syscall to exit + // and thus the `unload` event will not be emitted to properly trigger "emit" + // event on `process`. + process.emit("exit", process.exitCode || 0); + } + + Deno.exit(process.exitCode || 0); +}; + +function addReadOnlyProcessAlias( + name: string, + option: string, + enumerable = true, +) { + const value = getOptionValue(option); + + if (value) { + Object.defineProperty(process, name, { + writable: false, + configurable: true, + enumerable, + value, + }); + } +} + +function createWarningObject( + warning: string, + type: string, + code?: string, + // deno-lint-ignore ban-types + ctor?: Function, + detail?: string, +): Error { + assert(typeof warning === "string"); + + // deno-lint-ignore no-explicit-any + const warningErr: any = new Error(warning); + warningErr.name = String(type || "Warning"); + + if (code !== undefined) { + warningErr.code = code; + } + if (detail !== undefined) { + warningErr.detail = detail; + } + + // @ts-ignore this function is not available in lib.dom.d.ts + Error.captureStackTrace(warningErr, ctor || process.emitWarning); + + return warningErr; +} + +function doEmitWarning(warning: Error) { + process.emit("warning", warning); +} + +/** https://nodejs.org/api/process.html#process_process_emitwarning_warning_options */ +export function emitWarning( + warning: string | Error, + type: + // deno-lint-ignore ban-types + | { type: string; detail: string; code: string; ctor: Function } + | string + | null, + code?: string, + // deno-lint-ignore ban-types + ctor?: Function, +) { + let detail; + + if (type !== null && typeof type === "object" && !Array.isArray(type)) { + ctor = type.ctor; + code = type.code; + + if (typeof type.detail === "string") { + detail = type.detail; + } + + type = type.type || "Warning"; + } else if (typeof type === "function") { + ctor = type; + code = undefined; + type = "Warning"; + } + + if (type !== undefined) { + validateString(type, "type"); + } + + if (typeof code === "function") { + ctor = code; + code = undefined; + } else if (code !== undefined) { + validateString(code, "code"); + } + + if (typeof warning === "string") { + warning = createWarningObject(warning, type as string, code, ctor, detail); + } else if (!(warning instanceof Error)) { + throw new ERR_INVALID_ARG_TYPE("warning", ["Error", "string"], warning); + } + + if (warning.name === "DeprecationWarning") { + // deno-lint-ignore no-explicit-any + if ((process as any).noDeprecation) { + return; + } + + // deno-lint-ignore no-explicit-any + if ((process as any).throwDeprecation) { + // Delay throwing the error to guarantee that all former warnings were + // properly logged. + return process.nextTick(() => { + throw warning; + }); + } + } + + process.nextTick(doEmitWarning, warning); +} + +export function hrtime(time?: [number, number]): [number, number] { + const milli = performance.now(); + const sec = Math.floor(milli / 1000); + const nano = Math.floor(milli * 1_000_000 - sec * 1_000_000_000); + if (!time) { + return [sec, nano]; + } + const [prevSec, prevNano] = time; + return [sec - prevSec, nano - prevNano]; +} + +hrtime.bigint = function (): bigint { + const [sec, nano] = hrtime(); + return BigInt(sec) * 1_000_000_000n + BigInt(nano); +}; + +export function memoryUsage(): { + rss: number; + heapTotal: number; + heapUsed: number; + external: number; + arrayBuffers: number; +} { + return { + ...Deno.memoryUsage(), + arrayBuffers: 0, + }; +} + +memoryUsage.rss = function (): number { + return memoryUsage().rss; +}; + +// Returns a negative error code than can be recognized by errnoException +function _kill(pid: number, sig: number): number { + let errCode; + + if (sig === 0) { + let status; + if (Deno.build.os === "windows") { + status = (new DenoCommand("powershell.exe", { + args: ["Get-Process", "-pid", pid], + })).outputSync(); + } else { + status = (new DenoCommand("kill", { + args: ["-0", pid], + })).outputSync(); + } + + if (!status.success) { + errCode = uv.codeMap.get("ESRCH"); + } + } else { + // Reverse search the shortname based on the numeric code + const maybeSignal = Object.entries(constants.os.signals).find(( + [_, numericCode], + ) => numericCode === sig); + + if (!maybeSignal) { + errCode = uv.codeMap.get("EINVAL"); + } else { + try { + Deno.kill(pid, maybeSignal[0] as Deno.Signal); + } catch (e) { + if (e instanceof TypeError) { + throw notImplemented(maybeSignal[0]); + } + + throw e; + } + } + } + + if (!errCode) { + return 0; + } else { + return errCode; + } +} + +export function kill(pid: number, sig: string | number = "SIGTERM") { + if (pid != (pid | 0)) { + throw new ERR_INVALID_ARG_TYPE("pid", "number", pid); + } + + let err; + if (typeof sig === "number") { + err = process._kill(pid, sig); + } else { + if (sig in constants.os.signals) { + // @ts-ignore Index previously checked + err = process._kill(pid, constants.os.signals[sig]); + } else { + throw new ERR_UNKNOWN_SIGNAL(sig); + } + } + + if (err) { + throw errnoException(err, "kill"); + } + + return true; +} + +// deno-lint-ignore no-explicit-any +function uncaughtExceptionHandler(err: any, origin: string) { + // The origin parameter can be 'unhandledRejection' or 'uncaughtException' + // depending on how the uncaught exception was created. In Node.js, + // exceptions thrown from the top level of a CommonJS module are reported as + // 'uncaughtException', while exceptions thrown from the top level of an ESM + // module are reported as 'unhandledRejection'. Deno does not have a true + // CommonJS implementation, so all exceptions thrown from the top level are + // reported as 'uncaughtException'. + process.emit("uncaughtExceptionMonitor", err, origin); + process.emit("uncaughtException", err, origin); +} + +let execPath: string | null = null; + +class Process extends EventEmitter { + constructor() { + super(); + } + + /** https://nodejs.org/api/process.html#process_process_arch */ + get arch() { + if (!arch) { + arch = arch_(); + } + return arch; + } + + /** + * https://nodejs.org/api/process.html#process_process_argv + * Read permissions are required in order to get the executable route + */ + argv = argv; + + /** https://nodejs.org/api/process.html#process_process_chdir_directory */ + chdir = chdir; + + /** https://nodejs.org/api/process.html#processconfig */ + config = { + target_defaults: {}, + variables: {}, + }; + + /** https://nodejs.org/api/process.html#process_process_cwd */ + cwd = cwd; + + /** + * https://nodejs.org/api/process.html#process_process_env + * Requires env permissions + */ + env = env; + + /** https://nodejs.org/api/process.html#process_process_execargv */ + execArgv: string[] = []; + + /** https://nodejs.org/api/process.html#process_process_exit_code */ + exit = exit; + + _exiting = _exiting; + + /** https://nodejs.org/api/process.html#processexitcode_1 */ + exitCode: undefined | number = undefined; + + // Typed as any to avoid importing "module" module for types + // deno-lint-ignore no-explicit-any + mainModule: any = undefined; + + /** https://nodejs.org/api/process.html#process_process_nexttick_callback_args */ + nextTick = _nextTick; + + /** https://nodejs.org/api/process.html#process_process_events */ + override on(event: "exit", listener: (code: number) => void): this; + override on( + event: typeof notImplementedEvents[number], + // deno-lint-ignore ban-types + listener: Function, + ): this; + // deno-lint-ignore no-explicit-any + override on(event: string, listener: (...args: any[]) => void): this { + if (notImplementedEvents.includes(event)) { + warnNotImplemented(`process.on("${event}")`); + super.on(event, listener); + } else if (event.startsWith("SIG")) { + if (event === "SIGBREAK" && Deno.build.os !== "windows") { + // Ignores SIGBREAK if the platform is not windows. + } else if (event === "SIGTERM" && Deno.build.os === "windows") { + // Ignores SIGTERM on windows. + } else { + Deno.addSignalListener(event as Deno.Signal, listener); + } + } else { + super.on(event, listener); + } + + return this; + } + + override off(event: "exit", listener: (code: number) => void): this; + override off( + event: typeof notImplementedEvents[number], + // deno-lint-ignore ban-types + listener: Function, + ): this; + // deno-lint-ignore no-explicit-any + override off(event: string, listener: (...args: any[]) => void): this { + if (notImplementedEvents.includes(event)) { + warnNotImplemented(`process.off("${event}")`); + super.off(event, listener); + } else if (event.startsWith("SIG")) { + if (event === "SIGBREAK" && Deno.build.os !== "windows") { + // Ignores SIGBREAK if the platform is not windows. + } else if (event === "SIGTERM" && Deno.build.os === "windows") { + // Ignores SIGTERM on windows. + } else { + Deno.removeSignalListener(event as Deno.Signal, listener); + } + } else { + super.off(event, listener); + } + + return this; + } + + // deno-lint-ignore no-explicit-any + override emit(event: string, ...args: any[]): boolean { + if (event.startsWith("SIG")) { + if (event === "SIGBREAK" && Deno.build.os !== "windows") { + // Ignores SIGBREAK if the platform is not windows. + } else { + Deno.kill(Deno.pid, event as Deno.Signal); + } + } else { + return super.emit(event, ...args); + } + + return true; + } + + override prependListener( + event: "exit", + listener: (code: number) => void, + ): this; + override prependListener( + event: typeof notImplementedEvents[number], + // deno-lint-ignore ban-types + listener: Function, + ): this; + override prependListener( + event: string, + // deno-lint-ignore no-explicit-any + listener: (...args: any[]) => void, + ): this { + if (notImplementedEvents.includes(event)) { + warnNotImplemented(`process.prependListener("${event}")`); + super.prependListener(event, listener); + } else if (event.startsWith("SIG")) { + if (event === "SIGBREAK" && Deno.build.os !== "windows") { + // Ignores SIGBREAK if the platform is not windows. + } else { + Deno.addSignalListener(event as Deno.Signal, listener); + } + } else { + super.prependListener(event, listener); + } + + return this; + } + + /** https://nodejs.org/api/process.html#process_process_pid */ + get pid() { + if (!pid) { + pid = Deno.pid; + } + return pid; + } + + /** https://nodejs.org/api/process.html#process_process_platform */ + get platform() { + if (!platform) { + platform = isWindows ? "win32" : Deno.build.os; + } + return platform; + } + + override addListener(event: "exit", listener: (code: number) => void): this; + override addListener( + event: typeof notImplementedEvents[number], + // deno-lint-ignore ban-types + listener: Function, + ): this; + override addListener( + event: string, + // deno-lint-ignore no-explicit-any + listener: (...args: any[]) => void, + ): this { + if (notImplementedEvents.includes(event)) { + warnNotImplemented(`process.addListener("${event}")`); + } + + return this.on(event, listener); + } + + override removeListener( + event: "exit", + listener: (code: number) => void, + ): this; + override removeListener( + event: typeof notImplementedEvents[number], + // deno-lint-ignore ban-types + listener: Function, + ): this; + override removeListener( + event: string, + // deno-lint-ignore no-explicit-any + listener: (...args: any[]) => void, + ): this { + if (notImplementedEvents.includes(event)) { + warnNotImplemented(`process.removeListener("${event}")`); + } + + return this.off(event, listener); + } + + /** + * Returns the current high-resolution real time in a [seconds, nanoseconds] + * tuple. + * + * Note: You need to give --allow-hrtime permission to Deno to actually get + * nanoseconds precision values. If you don't give 'hrtime' permission, the returned + * values only have milliseconds precision. + * + * `time` is an optional parameter that must be the result of a previous process.hrtime() call to diff with the current time. + * + * These times are relative to an arbitrary time in the past, and not related to the time of day and therefore not subject to clock drift. The primary use is for measuring performance between intervals. + * https://nodejs.org/api/process.html#process_process_hrtime_time + */ + hrtime = hrtime; + + /** + * @private + * + * NodeJS internal, use process.kill instead + */ + _kill = _kill; + + /** https://nodejs.org/api/process.html#processkillpid-signal */ + kill = kill; + + memoryUsage = memoryUsage; + + /** https://nodejs.org/api/process.html#process_process_stderr */ + stderr = stderr; + + /** https://nodejs.org/api/process.html#process_process_stdin */ + stdin = stdin; + + /** https://nodejs.org/api/process.html#process_process_stdout */ + stdout = stdout; + + /** https://nodejs.org/api/process.html#process_process_version */ + version = version; + + /** https://nodejs.org/api/process.html#process_process_versions */ + versions = versions; + + /** https://nodejs.org/api/process.html#process_process_emitwarning_warning_options */ + emitWarning = emitWarning; + + binding(name: BindingName) { + return getBinding(name); + } + + /** https://nodejs.org/api/process.html#processumaskmask */ + umask() { + // Always return the system default umask value. + // We don't use Deno.umask here because it has a race + // condition bug. + // See https://github.com/denoland/deno_std/issues/1893#issuecomment-1032897779 + return 0o22; + } + + /** This method is removed on Windows */ + getgid?(): number { + return Deno.gid()!; + } + + /** This method is removed on Windows */ + getuid?(): number { + return Deno.uid()!; + } + + // TODO(kt3k): Implement this when we added -e option to node compat mode + _eval: string | undefined = undefined; + + /** https://nodejs.org/api/process.html#processexecpath */ + get execPath() { + if (execPath) { + return execPath; + } + execPath = Deno.execPath(); + return execPath; + } + + set execPath(path: string) { + execPath = path; + } + + setStartTime(t: number) { + this.#startTime = t; + } + + #startTime = 0; + /** https://nodejs.org/api/process.html#processuptime */ + uptime() { + return (Date.now() - this.#startTime) / 1000; + } + + #allowedFlags = buildAllowedFlags(); + /** https://nodejs.org/api/process.html#processallowednodeenvironmentflags */ + get allowedNodeEnvironmentFlags() { + return this.#allowedFlags; + } + + features = { inspector: false }; + + // TODO(kt3k): Get the value from --no-deprecation flag. + noDeprecation = false; +} + +if (isWindows) { + delete Process.prototype.getgid; + delete Process.prototype.getuid; +} + +/** https://nodejs.org/api/process.html#process_process */ +const process = new Process(); + +Object.defineProperty(process, Symbol.toStringTag, { + enumerable: false, + writable: true, + configurable: false, + value: "process", +}); + +addReadOnlyProcessAlias("noDeprecation", "--no-deprecation"); +addReadOnlyProcessAlias("throwDeprecation", "--throw-deprecation"); + +export const removeListener = process.removeListener; +export const removeAllListeners = process.removeAllListeners; + +// Should be called only once, in `runtime/js/99_main.js` when the runtime is +// bootstrapped. +internals.__bootstrapNodeProcess = function ( + args: string[], + denoVersions: Record, +) { + for (let i = 0; i < args.length; i++) { + argv[i + 2] = args[i]; + } + + for (const [key, value] of Object.entries(denoVersions)) { + versions[key] = value; + } + + core.setNextTickCallback(processTicksAndRejections); + core.setMacrotaskCallback(runNextTicks); + enableNextTick(); + + // TODO(bartlomieju): this is buggy, see https://github.com/denoland/deno/issues/16928 + // We should use a specialized API in 99_main.js instead + globalThis.addEventListener("unhandledrejection", (event) => { + if (process.listenerCount("unhandledRejection") === 0) { + // The Node.js default behavior is to raise an uncaught exception if + // an unhandled rejection occurs and there are no unhandledRejection + // listeners. + if (process.listenerCount("uncaughtException") === 0) { + throw event.reason; + } + + event.preventDefault(); + uncaughtExceptionHandler(event.reason, "unhandledRejection"); + return; + } + + event.preventDefault(); + process.emit("unhandledRejection", event.reason, event.promise); + }); + + globalThis.addEventListener("error", (event) => { + if (process.listenerCount("uncaughtException") > 0) { + event.preventDefault(); + } + + uncaughtExceptionHandler(event.error, "uncaughtException"); + }); + + globalThis.addEventListener("beforeunload", (e) => { + process.emit("beforeExit", process.exitCode || 0); + processTicksAndRejections(); + if (core.eventLoopHasMoreWork()) { + e.preventDefault(); + } + }); + + globalThis.addEventListener("unload", () => { + if (!process._exiting) { + process._exiting = true; + process.emit("exit", process.exitCode || 0); + } + }); + + // Initializes stdin + stdin = stdio.stdin = process.stdin = initStdin(); + + /** https://nodejs.org/api/process.html#process_process_stderr */ + stderr = stdio.stderr = process.stderr = createWritableStdioStream( + files.stderr, + "stderr", + ); + + /** https://nodejs.org/api/process.html#process_process_stdout */ + stdout = stdio.stdout = process.stdout = createWritableStdioStream( + files.stdout, + "stdout", + ); + + process.setStartTime(Date.now()); + // @ts-ignore Remove setStartTime and #startTime is not modifiable + delete process.setStartTime; + delete internals.__bootstrapNodeProcess; +}; + +export default process; diff --git a/ext/node/polyfills/punycode.ts b/ext/node/polyfills/punycode.ts new file mode 100644 index 00000000000000..30fb727c2f87b8 --- /dev/null +++ b/ext/node/polyfills/punycode.ts @@ -0,0 +1,31 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. + +import { ucs2 } from "internal:deno_node/polyfills/internal/idna.ts"; + +const { ops } = globalThis.__bootstrap.core; + +function toASCII(domain) { + return ops.op_node_idna_domain_to_ascii(domain); +} + +function toUnicode(domain) { + return ops.op_node_idna_domain_to_unicode(domain); +} + +function decode(domain) { + return ops.op_node_idna_punycode_decode(domain); +} + +function encode(domain) { + return ops.op_node_idna_punycode_encode(domain); +} + +export { decode, encode, toASCII, toUnicode, ucs2 }; + +export default { + decode, + encode, + toASCII, + toUnicode, + ucs2, +}; diff --git a/ext/node/polyfills/querystring.ts b/ext/node/polyfills/querystring.ts new file mode 100644 index 00000000000000..d8fdfbcc8fb67a --- /dev/null +++ b/ext/node/polyfills/querystring.ts @@ -0,0 +1,517 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. +import { Buffer } from "internal:deno_node/polyfills/buffer.ts"; +import { + encodeStr, + hexTable, +} from "internal:deno_node/polyfills/internal/querystring.ts"; + +/** + * Alias of querystring.parse() + * @legacy + */ +export const decode = parse; + +/** + * Alias of querystring.stringify() + * @legacy + */ +export const encode = stringify; + +/** + * replaces encodeURIComponent() + * @see https://www.ecma-international.org/ecma-262/5.1/#sec-15.1.3.4 + */ +function qsEscape(str: unknown): string { + if (typeof str !== "string") { + if (typeof str === "object") { + str = String(str); + } else { + str += ""; + } + } + return encodeStr(str as string, noEscape, hexTable); +} + +/** + * Performs URL percent-encoding on the given `str` in a manner that is optimized for the specific requirements of URL query strings. + * Used by `querystring.stringify()` and is generally not expected to be used directly. + * It is exported primarily to allow application code to provide a replacement percent-encoding implementation if necessary by assigning `querystring.escape` to an alternative function. + * @legacy + * @see Tested in `test-querystring-escape.js` + */ +export const escape = qsEscape; + +export interface ParsedUrlQuery { + [key: string]: string | string[] | undefined; +} + +export interface ParsedUrlQueryInput { + [key: string]: + | string + | number + | boolean + | ReadonlyArray + | ReadonlyArray + | ReadonlyArray + | null + | undefined; +} + +interface ParseOptions { + /** The function to use when decoding percent-encoded characters in the query string. */ + decodeURIComponent?: (string: string) => string; + /** Specifies the maximum number of keys to parse. */ + maxKeys?: number; +} + +// deno-fmt-ignore +const isHexTable = new Int8Array([ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 0 - 15 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 16 - 31 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 32 - 47 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, // 48 - 63 + 0, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 64 - 79 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 80 - 95 + 0, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 96 - 111 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 112 - 127 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 128 ... + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // ... 256 +]); + +function charCodes(str: string): number[] { + const ret = new Array(str.length); + for (let i = 0; i < str.length; ++i) { + ret[i] = str.charCodeAt(i); + } + return ret; +} + +function addKeyVal( + obj: ParsedUrlQuery, + key: string, + value: string, + keyEncoded: boolean, + valEncoded: boolean, + decode: (encodedURIComponent: string) => string, +) { + if (key.length > 0 && keyEncoded) { + key = decode(key); + } + if (value.length > 0 && valEncoded) { + value = decode(value); + } + + if (obj[key] === undefined) { + obj[key] = value; + } else { + const curValue = obj[key]; + // A simple Array-specific property check is enough here to + // distinguish from a string value and is faster and still safe + // since we are generating all of the values being assigned. + if ((curValue as string[]).pop) { + (curValue as string[])[curValue!.length] = value; + } else { + obj[key] = [curValue as string, value]; + } + } +} + +/** + * Parses a URL query string into a collection of key and value pairs. + * @param str The URL query string to parse + * @param sep The substring used to delimit key and value pairs in the query string. Default: '&'. + * @param eq The substring used to delimit keys and values in the query string. Default: '='. + * @param options The parse options + * @param options.decodeURIComponent The function to use when decoding percent-encoded characters in the query string. Default: `querystring.unescape()`. + * @param options.maxKeys Specifies the maximum number of keys to parse. Specify `0` to remove key counting limitations. Default: `1000`. + * @legacy + * @see Tested in test-querystring.js + */ +export function parse( + str: string, + sep = "&", + eq = "=", + { decodeURIComponent = unescape, maxKeys = 1000 }: ParseOptions = {}, +): ParsedUrlQuery { + const obj: ParsedUrlQuery = Object.create(null); + + if (typeof str !== "string" || str.length === 0) { + return obj; + } + + const sepCodes = !sep ? [38] /* & */ : charCodes(String(sep)); + const eqCodes = !eq ? [61] /* = */ : charCodes(String(eq)); + const sepLen = sepCodes.length; + const eqLen = eqCodes.length; + + let pairs = 1000; + if (typeof maxKeys === "number") { + // -1 is used in place of a value like Infinity for meaning + // "unlimited pairs" because of additional checks V8 (at least as of v5.4) + // has to do when using variables that contain values like Infinity. Since + // `pairs` is always decremented and checked explicitly for 0, -1 works + // effectively the same as Infinity, while providing a significant + // performance boost. + pairs = maxKeys > 0 ? maxKeys : -1; + } + + let decode = unescape; + if (decodeURIComponent) { + decode = decodeURIComponent; + } + const customDecode = decode !== unescape; + + let lastPos = 0; + let sepIdx = 0; + let eqIdx = 0; + let key = ""; + let value = ""; + let keyEncoded = customDecode; + let valEncoded = customDecode; + const plusChar = customDecode ? "%20" : " "; + let encodeCheck = 0; + for (let i = 0; i < str.length; ++i) { + const code = str.charCodeAt(i); + + // Try matching key/value pair separator (e.g. '&') + if (code === sepCodes[sepIdx]) { + if (++sepIdx === sepLen) { + // Key/value pair separator match! + const end = i - sepIdx + 1; + if (eqIdx < eqLen) { + // We didn't find the (entire) key/value separator + if (lastPos < end) { + // Treat the substring as part of the key instead of the value + key += str.slice(lastPos, end); + } else if (key.length === 0) { + // We saw an empty substring between separators + if (--pairs === 0) { + return obj; + } + lastPos = i + 1; + sepIdx = eqIdx = 0; + continue; + } + } else if (lastPos < end) { + value += str.slice(lastPos, end); + } + + addKeyVal(obj, key, value, keyEncoded, valEncoded, decode); + + if (--pairs === 0) { + return obj; + } + key = value = ""; + encodeCheck = 0; + lastPos = i + 1; + sepIdx = eqIdx = 0; + } + } else { + sepIdx = 0; + // Try matching key/value separator (e.g. '=') if we haven't already + if (eqIdx < eqLen) { + if (code === eqCodes[eqIdx]) { + if (++eqIdx === eqLen) { + // Key/value separator match! + const end = i - eqIdx + 1; + if (lastPos < end) { + key += str.slice(lastPos, end); + } + encodeCheck = 0; + lastPos = i + 1; + } + continue; + } else { + eqIdx = 0; + if (!keyEncoded) { + // Try to match an (valid) encoded byte once to minimize unnecessary + // calls to string decoding functions + if (code === 37 /* % */) { + encodeCheck = 1; + continue; + } else if (encodeCheck > 0) { + if (isHexTable[code] === 1) { + if (++encodeCheck === 3) { + keyEncoded = true; + } + continue; + } else { + encodeCheck = 0; + } + } + } + } + if (code === 43 /* + */) { + if (lastPos < i) { + key += str.slice(lastPos, i); + } + key += plusChar; + lastPos = i + 1; + continue; + } + } + if (code === 43 /* + */) { + if (lastPos < i) { + value += str.slice(lastPos, i); + } + value += plusChar; + lastPos = i + 1; + } else if (!valEncoded) { + // Try to match an (valid) encoded byte (once) to minimize unnecessary + // calls to string decoding functions + if (code === 37 /* % */) { + encodeCheck = 1; + } else if (encodeCheck > 0) { + if (isHexTable[code] === 1) { + if (++encodeCheck === 3) { + valEncoded = true; + } + } else { + encodeCheck = 0; + } + } + } + } + } + + // Deal with any leftover key or value data + if (lastPos < str.length) { + if (eqIdx < eqLen) { + key += str.slice(lastPos); + } else if (sepIdx < sepLen) { + value += str.slice(lastPos); + } + } else if (eqIdx === 0 && key.length === 0) { + // We ended on an empty substring + return obj; + } + + addKeyVal(obj, key, value, keyEncoded, valEncoded, decode); + + return obj; +} + +interface StringifyOptions { + /** The function to use when converting URL-unsafe characters to percent-encoding in the query string. */ + encodeURIComponent: (string: string) => string; +} + +/** + * These characters do not need escaping when generating query strings: + * ! - . _ ~ + * ' ( ) * + * digits + * alpha (uppercase) + * alpha (lowercase) + */ +// deno-fmt-ignore +const noEscape = new Int8Array([ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 0 - 15 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 16 - 31 + 0, 1, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 1, 1, 0, // 32 - 47 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, // 48 - 63 + 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 64 - 79 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1, // 80 - 95 + 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 96 - 111 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 1, 0, // 112 - 127 +]); + +// deno-lint-ignore no-explicit-any +function stringifyPrimitive(v: any): string { + if (typeof v === "string") { + return v; + } + if (typeof v === "number" && isFinite(v)) { + return "" + v; + } + if (typeof v === "bigint") { + return "" + v; + } + if (typeof v === "boolean") { + return v ? "true" : "false"; + } + return ""; +} + +function encodeStringifiedCustom( + // deno-lint-ignore no-explicit-any + v: any, + encode: (string: string) => string, +): string { + return encode(stringifyPrimitive(v)); +} + +// deno-lint-ignore no-explicit-any +function encodeStringified(v: any, encode: (string: string) => string): string { + if (typeof v === "string") { + return (v.length ? encode(v) : ""); + } + if (typeof v === "number" && isFinite(v)) { + // Values >= 1e21 automatically switch to scientific notation which requires + // escaping due to the inclusion of a '+' in the output + return (Math.abs(v) < 1e21 ? "" + v : encode("" + v)); + } + if (typeof v === "bigint") { + return "" + v; + } + if (typeof v === "boolean") { + return v ? "true" : "false"; + } + return ""; +} + +/** + * Produces a URL query string from a given obj by iterating through the object's "own properties". + * @param obj The object to serialize into a URL query string. + * @param sep The substring used to delimit key and value pairs in the query string. Default: '&'. + * @param eq The substring used to delimit keys and values in the query string. Default: '='. + * @param options The stringify options + * @param options.encodeURIComponent The function to use when converting URL-unsafe characters to percent-encoding in the query string. Default: `querystring.escape()`. + * @legacy + * @see Tested in `test-querystring.js` + */ +export function stringify( + // deno-lint-ignore no-explicit-any + obj: Record, + sep?: string, + eq?: string, + options?: StringifyOptions, +): string { + sep ||= "&"; + eq ||= "="; + const encode = options ? options.encodeURIComponent : qsEscape; + const convert = options ? encodeStringifiedCustom : encodeStringified; + + if (obj !== null && typeof obj === "object") { + const keys = Object.keys(obj); + const len = keys.length; + let fields = ""; + for (let i = 0; i < len; ++i) { + const k = keys[i]; + const v = obj[k]; + let ks = convert(k, encode); + ks += eq; + + if (Array.isArray(v)) { + const vlen = v.length; + if (vlen === 0) continue; + if (fields) { + fields += sep; + } + for (let j = 0; j < vlen; ++j) { + if (j) { + fields += sep; + } + fields += ks; + fields += convert(v[j], encode); + } + } else { + if (fields) { + fields += sep; + } + fields += ks; + fields += convert(v, encode); + } + } + return fields; + } + return ""; +} + +// deno-fmt-ignore +const unhexTable = new Int8Array([ + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // 0 - 15 + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // 16 - 31 + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // 32 - 47 + +0, +1, +2, +3, +4, +5, +6, +7, +8, +9, -1, -1, -1, -1, -1, -1, // 48 - 63 + -1, 10, 11, 12, 13, 14, 15, -1, -1, -1, -1, -1, -1, -1, -1, -1, // 64 - 79 + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // 80 - 95 + -1, 10, 11, 12, 13, 14, 15, -1, -1, -1, -1, -1, -1, -1, -1, -1, // 96 - 111 + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // 112 - 127 + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // 128 ... + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // ... 255 +]); + +/** + * A safe fast alternative to decodeURIComponent + */ +export function unescapeBuffer(s: string, decodeSpaces = false): Buffer { + const out = Buffer.alloc(s.length); + let index = 0; + let outIndex = 0; + let currentChar; + let nextChar; + let hexHigh; + let hexLow; + const maxLength = s.length - 2; + // Flag to know if some hex chars have been decoded + let hasHex = false; + while (index < s.length) { + currentChar = s.charCodeAt(index); + if (currentChar === 43 /* '+' */ && decodeSpaces) { + out[outIndex++] = 32; // ' ' + index++; + continue; + } + if (currentChar === 37 /* '%' */ && index < maxLength) { + currentChar = s.charCodeAt(++index); + hexHigh = unhexTable[currentChar]; + if (!(hexHigh >= 0)) { + out[outIndex++] = 37; // '%' + continue; + } else { + nextChar = s.charCodeAt(++index); + hexLow = unhexTable[nextChar]; + if (!(hexLow >= 0)) { + out[outIndex++] = 37; // '%' + index--; + } else { + hasHex = true; + currentChar = hexHigh * 16 + hexLow; + } + } + } + out[outIndex++] = currentChar; + index++; + } + return hasHex ? out.slice(0, outIndex) : out; +} + +function qsUnescape(s: string): string { + try { + return decodeURIComponent(s); + } catch { + return unescapeBuffer(s).toString(); + } +} + +/** + * Performs decoding of URL percent-encoded characters on the given `str`. + * Used by `querystring.parse()` and is generally not expected to be used directly. + * It is exported primarily to allow application code to provide a replacement decoding implementation if necessary by assigning `querystring.unescape` to an alternative function. + * @legacy + * @see Tested in `test-querystring-escape.js` + */ +export const unescape = qsUnescape; + +export default { + parse, + stringify, + decode, + encode, + unescape, + escape, + unescapeBuffer, +}; diff --git a/ext/node/polyfills/readline.ts b/ext/node/polyfills/readline.ts new file mode 100644 index 00000000000000..99b73177d74d39 --- /dev/null +++ b/ext/node/polyfills/readline.ts @@ -0,0 +1,35 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. +// @deno-types="./_readline.d.ts" + +import { + clearLine, + clearScreenDown, + createInterface, + cursorTo, + emitKeypressEvents, + Interface, + moveCursor, + promises, +} from "internal:deno_node/polyfills/_readline.mjs"; + +export { + clearLine, + clearScreenDown, + createInterface, + cursorTo, + emitKeypressEvents, + Interface, + moveCursor, + promises, +}; + +export default { + Interface, + clearLine, + clearScreenDown, + createInterface, + cursorTo, + emitKeypressEvents, + moveCursor, + promises, +}; diff --git a/ext/node/polyfills/readline/promises.ts b/ext/node/polyfills/readline/promises.ts new file mode 100644 index 00000000000000..47e3b5b22dabbd --- /dev/null +++ b/ext/node/polyfills/readline/promises.ts @@ -0,0 +1,178 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. +// Copyright Joyent and Node contributors. All rights reserved. MIT license. + +import { Readline } from "internal:deno_node/polyfills/internal/readline/promises.mjs"; + +import { + Interface as _Interface, + kQuestion, + kQuestionCancel, +} from "internal:deno_node/polyfills/internal/readline/interface.mjs"; +import { AbortError } from "internal:deno_node/polyfills/internal/errors.ts"; +import { validateAbortSignal } from "internal:deno_node/polyfills/internal/validators.mjs"; + +import { kEmptyObject } from "internal:deno_node/polyfills/internal/util.mjs"; +import type { Abortable } from "internal:deno_node/polyfills/_events.d.ts"; +import type { + AsyncCompleter, + Completer, + ReadLineOptions, +} from "internal:deno_node/polyfills/_readline_shared_types.d.ts"; + +import type { + ReadableStream, + WritableStream, +} from "internal:deno_node/polyfills/_global.d.ts"; + +/** + * The `readline/promise` module provides an API for reading lines of input from a Readable stream one line at a time. + * + * @see [source](https://github.com/nodejs/node/blob/v18.0.0/lib/readline/promises.js) + * @since v17.0.0 + */ +export interface Interface extends _Interface { + /** + * The rl.question() method displays the query by writing it to the output, waits for user input to be provided on input, + * then invokes the callback function passing the provided input as the first argument. + * + * When called, rl.question() will resume the input stream if it has been paused. + * + * If the readlinePromises.Interface was created with output set to null or undefined the query is not written. + * + * If the question is called after rl.close(), it returns a rejected promise. + * + * Example usage: + * + * ```js + * const answer = await rl.question('What is your favorite food? '); + * console.log(`Oh, so your favorite food is ${answer}`); + * ``` + * + * Using an AbortSignal to cancel a question. + * + * ```js + * const signal = AbortSignal.timeout(10_000); + * + * signal.addEventListener('abort', () => { + * console.log('The food question timed out'); + * }, { once: true }); + * + * const answer = await rl.question('What is your favorite food? ', { signal }); + * console.log(`Oh, so your favorite food is ${answer}`); + * ``` + * + * @since v17.0.0 + * @param query A statement or query to write to output, prepended to the prompt. + */ + question(query: string, options?: Abortable): Promise; +} + +export class Interface extends _Interface { + constructor( + input: ReadableStream | ReadLineOptions, + output?: WritableStream, + completer?: Completer | AsyncCompleter, + terminal?: boolean, + ) { + super(input, output, completer, terminal); + } + question(query: string, options: Abortable = kEmptyObject): Promise { + return new Promise((resolve, reject) => { + let cb = resolve; + + if (options?.signal) { + validateAbortSignal(options.signal, "options.signal"); + if (options.signal.aborted) { + return reject( + new AbortError(undefined, { cause: options.signal.reason }), + ); + } + + const onAbort = () => { + this[kQuestionCancel](); + reject(new AbortError(undefined, { cause: options!.signal!.reason })); + }; + options.signal.addEventListener("abort", onAbort, { once: true }); + cb = (answer) => { + options!.signal!.removeEventListener("abort", onAbort); + resolve(answer); + }; + } + + this[kQuestion](query, cb); + }); + } +} + +/** + * The `readlinePromises.createInterface()` method creates a new `readlinePromises.Interface` instance. + * + * ```js + * const readlinePromises = require('node:readline/promises'); + * const rl = readlinePromises.createInterface({ + * input: process.stdin, + * output: process.stdout + * }); + * ``` + * + * Once the `readlinePromises.Interface` instance is created, the most common case is to listen for the `'line'` event: + * + * ```js + * rl.on('line', (line) => { + * console.log(`Received: ${line}`); + * }); + * ``` + * + * If `terminal` is `true` for this instance then the `output` stream will get the best compatibility if it defines an `output.columns` property, + * and emits a `'resize'` event on the `output`, if or when the columns ever change (`process.stdout` does this automatically when it is a TTY). + * + * ## Use of the `completer` function + * + * The `completer` function takes the current line entered by the user as an argument, and returns an `Array` with 2 entries: + * + * - An Array with matching entries for the completion. + * - The substring that was used for the matching. + * + * For instance: `[[substr1, substr2, ...], originalsubstring]`. + * + * ```js + * function completer(line) { + * const completions = '.help .error .exit .quit .q'.split(' '); + * const hits = completions.filter((c) => c.startsWith(line)); + * // Show all completions if none found + * return [hits.length ? hits : completions, line]; + * } + * ``` + * + * The `completer` function can also returns a `Promise`, or be asynchronous: + * + * ```js + * async function completer(linePartial) { + * await someAsyncWork(); + * return [['123'], linePartial]; + * } + * ``` + */ +export function createInterface(options: ReadLineOptions): Interface; +export function createInterface( + input: ReadableStream, + output?: WritableStream, + completer?: Completer | AsyncCompleter, + terminal?: boolean, +): Interface; +export function createInterface( + inputOrOptions: ReadableStream | ReadLineOptions, + output?: WritableStream, + completer?: Completer | AsyncCompleter, + terminal?: boolean, +): Interface { + return new Interface(inputOrOptions, output, completer, terminal); +} + +export { Readline }; + +export default { + Interface, + Readline, + createInterface, +}; diff --git a/ext/node/polyfills/repl.ts b/ext/node/polyfills/repl.ts new file mode 100644 index 00000000000000..33d904de3beb26 --- /dev/null +++ b/ext/node/polyfills/repl.ts @@ -0,0 +1,62 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. +// Copyright Joyent and Node contributors. All rights reserved. MIT license. + +import { notImplemented } from "internal:deno_node/polyfills/_utils.ts"; + +export class REPLServer { + constructor() { + notImplemented("REPLServer.prototype.constructor"); + } +} +export const builtinModules = [ + "assert", + "async_hooks", + "buffer", + "child_process", + "cluster", + "console", + "constants", + "crypto", + "dgram", + "diagnostics_channel", + "dns", + "domain", + "events", + "fs", + "http", + "http2", + "https", + "inspector", + "module", + "net", + "os", + "path", + "perf_hooks", + "process", + "punycode", + "querystring", + "readline", + "repl", + "stream", + "string_decoder", + "sys", + "timers", + "tls", + "trace_events", + "tty", + "url", + "util", + "v8", + "vm", + "wasi", + "worker_threads", + "zlib", +]; +export function start() { + notImplemented("repl.start"); +} +export default { + REPLServer, + builtinModules, + start, +}; diff --git a/ext/node/polyfills/stream.ts b/ext/node/polyfills/stream.ts new file mode 100644 index 00000000000000..aac96a76ee10f0 --- /dev/null +++ b/ext/node/polyfills/stream.ts @@ -0,0 +1,37 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. +// compose, destroy and isDisturbed are experimental APIs without +// typings. They can be exposed once they are released as stable in Node + +// @deno-types="./_stream.d.ts" +import { + _isUint8Array, + _uint8ArrayToBuffer, + addAbortSignal, + // compose, + // destroy, + Duplex, + finished, + // isDisturbed, + PassThrough, + pipeline, + Readable, + Stream, + Transform, + Writable, +} from "internal:deno_node/polyfills/_stream.mjs"; + +export { + _isUint8Array, + _uint8ArrayToBuffer, + addAbortSignal, + Duplex, + finished, + PassThrough, + pipeline, + Readable, + Stream, + Transform, + Writable, +}; + +export default Stream; diff --git a/ext/node/polyfills/stream/consumers.mjs b/ext/node/polyfills/stream/consumers.mjs new file mode 100644 index 00000000000000..61fe27020b632b --- /dev/null +++ b/ext/node/polyfills/stream/consumers.mjs @@ -0,0 +1,78 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. +// Copyright Joyent and Node contributors. All rights reserved. MIT license. + +import { TextDecoder } from "internal:deno_web/08_text_encoding.js"; +import { Buffer } from "internal:deno_node/polyfills/buffer.ts"; + +/** + * @typedef {import('../_global.d.ts').ReadableStream + * } ReadableStream + * @typedef {import('../_stream.d.ts')} Readable + */ + +/** + * @param {AsyncIterable|ReadableStream|Readable} stream + * @returns {Promise} + */ +async function blob(stream) { + const chunks = []; + for await (const chunk of stream) { + chunks.push(chunk); + } + return new Blob(chunks); +} + +/** + * @param {AsyncIterable|ReadableStream|Readable} stream + * @returns {Promise} + */ +async function arrayBuffer(stream) { + const ret = await blob(stream); + return ret.arrayBuffer(); +} + +/** + * @param {AsyncIterable|ReadableStream|Readable} stream + * @returns {Promise} + */ +async function buffer(stream) { + return Buffer.from(await arrayBuffer(stream)); +} + +/** + * @param {AsyncIterable|ReadableStream|Readable} stream + * @returns {Promise} + */ +async function text(stream) { + const dec = new TextDecoder(); + let str = ""; + for await (const chunk of stream) { + if (typeof chunk === "string") { + str += chunk; + } else { + str += dec.decode(chunk, { stream: true }); + } + } + // Flush the streaming TextDecoder so that any pending + // incomplete multibyte characters are handled. + str += dec.decode(undefined, { stream: false }); + return str; +} + +/** + * @param {AsyncIterable|ReadableStream|Readable} stream + * @returns {Promise} + */ +async function json(stream) { + const str = await text(stream); + return JSON.parse(str); +} + +export default { + arrayBuffer, + blob, + buffer, + json, + text, +}; +export { arrayBuffer, blob, buffer, json, text }; diff --git a/ext/node/polyfills/stream/promises.mjs b/ext/node/polyfills/stream/promises.mjs new file mode 100644 index 00000000000000..8c1f7439bef4d2 --- /dev/null +++ b/ext/node/polyfills/stream/promises.mjs @@ -0,0 +1,12 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. +// Copyright Joyent and Node contributors. All rights reserved. MIT license. + +import stream from "internal:deno_node/polyfills/_stream.mjs"; + +const { finished, pipeline } = stream.promises; + +export default { + finished, + pipeline, +}; +export { finished, pipeline }; diff --git a/ext/node/polyfills/stream/web.ts b/ext/node/polyfills/stream/web.ts new file mode 100644 index 00000000000000..b92e7b55b4ed1d --- /dev/null +++ b/ext/node/polyfills/stream/web.ts @@ -0,0 +1,57 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. + +import { + ByteLengthQueuingStrategy, + CountQueuingStrategy, + ReadableByteStreamController, + ReadableStream, + ReadableStreamBYOBReader, + ReadableStreamBYOBRequest, + ReadableStreamDefaultController, + ReadableStreamDefaultReader, + TransformStream, + TransformStreamDefaultController, + WritableStream, + WritableStreamDefaultController, + WritableStreamDefaultWriter, +} from "internal:deno_web/06_streams.js"; +import { + TextDecoderStream, + TextEncoderStream, +} from "internal:deno_web/08_text_encoding.js"; + +export { + ByteLengthQueuingStrategy, + CountQueuingStrategy, + ReadableByteStreamController, + ReadableStream, + ReadableStreamBYOBReader, + ReadableStreamBYOBRequest, + ReadableStreamDefaultController, + ReadableStreamDefaultReader, + TextDecoderStream, + TextEncoderStream, + TransformStream, + TransformStreamDefaultController, + WritableStream, + WritableStreamDefaultController, + WritableStreamDefaultWriter, +}; + +export default { + ReadableStream, + ReadableStreamBYOBReader, + ReadableStreamBYOBRequest, + ReadableStreamDefaultReader, + ReadableByteStreamController, + ReadableStreamDefaultController, + TransformStream, + TransformStreamDefaultController, + WritableStream, + WritableStreamDefaultWriter, + WritableStreamDefaultController, + ByteLengthQueuingStrategy, + CountQueuingStrategy, + TextEncoderStream, + TextDecoderStream, +}; diff --git a/ext/node/polyfills/string_decoder.ts b/ext/node/polyfills/string_decoder.ts new file mode 100644 index 00000000000000..33ff6a2f00e9e1 --- /dev/null +++ b/ext/node/polyfills/string_decoder.ts @@ -0,0 +1,340 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. +// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. + +import { Buffer } from "internal:deno_node/polyfills/buffer.ts"; +import { + normalizeEncoding as castEncoding, + notImplemented, +} from "internal:deno_node/polyfills/_utils.ts"; + +enum NotImplemented { + "ascii", + "latin1", + "utf16le", +} + +function normalizeEncoding(enc?: string): string { + const encoding = castEncoding(enc ?? null); + if (encoding && encoding in NotImplemented) notImplemented(encoding); + if (!encoding && typeof enc === "string" && enc.toLowerCase() !== "raw") { + throw new Error(`Unknown encoding: ${enc}`); + } + return String(encoding); +} + +/** + * Check is `ArrayBuffer` and not `TypedArray`. Typescript allowed `TypedArray` to be passed as `ArrayBuffer` and does not do a deep check + */ + +function isBufferType(buf: Buffer) { + return buf instanceof ArrayBuffer && buf.BYTES_PER_ELEMENT; +} + +/* + * Checks the type of a UTF-8 byte, whether it's ASCII, a leading byte, or a + * continuation byte. If an invalid byte is detected, -2 is returned. + */ +function utf8CheckByte(byte: number): number { + if (byte <= 0x7f) return 0; + else if (byte >> 5 === 0x06) return 2; + else if (byte >> 4 === 0x0e) return 3; + else if (byte >> 3 === 0x1e) return 4; + return byte >> 6 === 0x02 ? -1 : -2; +} + +/* + * Checks at most 3 bytes at the end of a Buffer in order to detect an + * incomplete multi-byte UTF-8 character. The total number of bytes (2, 3, or 4) + * needed to complete the UTF-8 character (if applicable) are returned. + */ +function utf8CheckIncomplete( + self: StringDecoderBase, + buf: Buffer, + i: number, +): number { + let j = buf.length - 1; + if (j < i) return 0; + let nb = utf8CheckByte(buf[j]); + if (nb >= 0) { + if (nb > 0) self.lastNeed = nb - 1; + return nb; + } + if (--j < i || nb === -2) return 0; + nb = utf8CheckByte(buf[j]); + if (nb >= 0) { + if (nb > 0) self.lastNeed = nb - 2; + return nb; + } + if (--j < i || nb === -2) return 0; + nb = utf8CheckByte(buf[j]); + if (nb >= 0) { + if (nb > 0) { + if (nb === 2) nb = 0; + else self.lastNeed = nb - 3; + } + return nb; + } + return 0; +} + +/* + * Validates as many continuation bytes for a multi-byte UTF-8 character as + * needed or are available. If we see a non-continuation byte where we expect + * one, we "replace" the validated continuation bytes we've seen so far with + * a single UTF-8 replacement character ('\ufffd'), to match v8's UTF-8 decoding + * behavior. The continuation byte check is included three times in the case + * where all of the continuation bytes for a character exist in the same buffer. + * It is also done this way as a slight performance increase instead of using a + * loop. + */ +function utf8CheckExtraBytes( + self: StringDecoderBase, + buf: Buffer, +): string | undefined { + if ((buf[0] & 0xc0) !== 0x80) { + self.lastNeed = 0; + return "\ufffd"; + } + if (self.lastNeed > 1 && buf.length > 1) { + if ((buf[1] & 0xc0) !== 0x80) { + self.lastNeed = 1; + return "\ufffd"; + } + if (self.lastNeed > 2 && buf.length > 2) { + if ((buf[2] & 0xc0) !== 0x80) { + self.lastNeed = 2; + return "\ufffd"; + } + } + } +} + +/* + * Attempts to complete a multi-byte UTF-8 character using bytes from a Buffer. + */ +function utf8FillLastComplete( + this: StringDecoderBase, + buf: Buffer, +): string | undefined { + const p = this.lastTotal - this.lastNeed; + const r = utf8CheckExtraBytes(this, buf); + if (r !== undefined) return r; + if (this.lastNeed <= buf.length) { + buf.copy(this.lastChar, p, 0, this.lastNeed); + return this.lastChar.toString(this.encoding, 0, this.lastTotal); + } + buf.copy(this.lastChar, p, 0, buf.length); + this.lastNeed -= buf.length; +} + +/* + * Attempts to complete a partial non-UTF-8 character using bytes from a Buffer + */ +function utf8FillLastIncomplete( + this: StringDecoderBase, + buf: Buffer, +): string | undefined { + if (this.lastNeed <= buf.length) { + buf.copy(this.lastChar, this.lastTotal - this.lastNeed, 0, this.lastNeed); + return this.lastChar.toString(this.encoding, 0, this.lastTotal); + } + buf.copy(this.lastChar, this.lastTotal - this.lastNeed, 0, buf.length); + this.lastNeed -= buf.length; +} + +/* + * Returns all complete UTF-8 characters in a Buffer. If the Buffer ended on a + * partial character, the character's bytes are buffered until the required + * number of bytes are available. + */ +function utf8Text(this: StringDecoderBase, buf: Buffer, i: number): string { + const total = utf8CheckIncomplete(this, buf, i); + if (!this.lastNeed) return buf.toString("utf8", i); + this.lastTotal = total; + const end = buf.length - (total - this.lastNeed); + buf.copy(this.lastChar, 0, end); + return buf.toString("utf8", i, end); +} + +/* + * For UTF-8, a replacement character is added when ending on a partial + * character. + */ +function utf8End(this: Utf8Decoder, buf?: Buffer): string { + const r = buf && buf.length ? this.write(buf) : ""; + if (this.lastNeed) return r + "\ufffd"; + return r; +} + +function utf8Write( + this: Utf8Decoder | Base64Decoder, + buf: Buffer | string, +): string { + if (typeof buf === "string") { + return buf; + } + if (buf.length === 0) return ""; + let r; + let i; + // Because `TypedArray` is recognized as `ArrayBuffer` but in the reality, there are some fundamental difference. We would need to cast it properly + const normalizedBuffer: Buffer = isBufferType(buf) ? buf : Buffer.from(buf); + if (this.lastNeed) { + r = this.fillLast(normalizedBuffer); + if (r === undefined) return ""; + i = this.lastNeed; + this.lastNeed = 0; + } else { + i = 0; + } + if (i < buf.length) { + return r + ? r + this.text(normalizedBuffer, i) + : this.text(normalizedBuffer, i); + } + return r || ""; +} + +function base64Text(this: StringDecoderBase, buf: Buffer, i: number): string { + const n = (buf.length - i) % 3; + if (n === 0) return buf.toString("base64", i); + this.lastNeed = 3 - n; + this.lastTotal = 3; + if (n === 1) { + this.lastChar[0] = buf[buf.length - 1]; + } else { + this.lastChar[0] = buf[buf.length - 2]; + this.lastChar[1] = buf[buf.length - 1]; + } + return buf.toString("base64", i, buf.length - n); +} + +function base64End(this: Base64Decoder, buf?: Buffer): string { + const r = buf && buf.length ? this.write(buf) : ""; + if (this.lastNeed) { + return r + this.lastChar.toString("base64", 0, 3 - this.lastNeed); + } + return r; +} + +function simpleWrite( + this: StringDecoderBase, + buf: Buffer | string, +): string { + if (typeof buf === "string") { + return buf; + } + return buf.toString(this.encoding); +} + +function simpleEnd(this: GenericDecoder, buf?: Buffer): string { + return buf && buf.length ? this.write(buf) : ""; +} + +class StringDecoderBase { + public lastChar: Buffer; + public lastNeed = 0; + public lastTotal = 0; + constructor(public encoding: string, nb: number) { + this.lastChar = Buffer.allocUnsafe(nb); + } +} + +class Base64Decoder extends StringDecoderBase { + public end = base64End; + public fillLast = utf8FillLastIncomplete; + public text = base64Text; + public write = utf8Write; + + constructor(encoding?: string) { + super(normalizeEncoding(encoding), 3); + } +} + +class GenericDecoder extends StringDecoderBase { + public end = simpleEnd; + public fillLast = undefined; + public text = utf8Text; + public write = simpleWrite; + + constructor(encoding?: string) { + super(normalizeEncoding(encoding), 4); + } +} + +class Utf8Decoder extends StringDecoderBase { + public end = utf8End; + public fillLast = utf8FillLastComplete; + public text = utf8Text; + public write = utf8Write; + + constructor(encoding?: string) { + super(normalizeEncoding(encoding), 4); + } +} + +/* + * StringDecoder provides an interface for efficiently splitting a series of + * buffers into a series of JS strings without breaking apart multi-byte + * characters. + */ +export class StringDecoder { + public encoding: string; + public end: (buf?: Buffer) => string; + public fillLast: ((buf: Buffer) => string | undefined) | undefined; + public lastChar: Buffer; + public lastNeed: number; + public lastTotal: number; + public text: (buf: Buffer, n: number) => string; + public write: (buf: Buffer) => string; + + constructor(encoding?: string) { + const normalizedEncoding = normalizeEncoding(encoding); + let decoder: Utf8Decoder | Base64Decoder | GenericDecoder; + switch (normalizedEncoding) { + case "utf8": + decoder = new Utf8Decoder(encoding); + break; + case "base64": + decoder = new Base64Decoder(encoding); + break; + default: + decoder = new GenericDecoder(encoding); + } + this.encoding = decoder.encoding; + this.end = decoder.end; + this.fillLast = decoder.fillLast; + this.lastChar = decoder.lastChar; + this.lastNeed = decoder.lastNeed; + this.lastTotal = decoder.lastTotal; + this.text = decoder.text; + this.write = decoder.write; + } +} +// Allow calling StringDecoder() without new +const PStringDecoder = new Proxy(StringDecoder, { + apply(_target, thisArg, args) { + // @ts-ignore tedious to replicate types ... + return Object.assign(thisArg, new StringDecoder(...args)); + }, +}); + +export default { StringDecoder: PStringDecoder }; diff --git a/ext/node/polyfills/sys.ts b/ext/node/polyfills/sys.ts new file mode 100644 index 00000000000000..e6297ca1627dcf --- /dev/null +++ b/ext/node/polyfills/sys.ts @@ -0,0 +1,3 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. +export * from "internal:deno_node/polyfills/util.ts"; +export { default } from "internal:deno_node/polyfills/util.ts"; diff --git a/ext/node/polyfills/timers.ts b/ext/node/polyfills/timers.ts new file mode 100644 index 00000000000000..5a650c1cc29ff5 --- /dev/null +++ b/ext/node/polyfills/timers.ts @@ -0,0 +1,66 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. + +import { + setUnrefTimeout, + Timeout, +} from "internal:deno_node/polyfills/internal/timers.mjs"; +import { validateFunction } from "internal:deno_node/polyfills/internal/validators.mjs"; +import { promisify } from "internal:deno_node/polyfills/internal/util.mjs"; +export { setUnrefTimeout } from "internal:deno_node/polyfills/internal/timers.mjs"; +import * as timers from "internal:deno_web/02_timers.js"; + +const clearTimeout_ = timers.clearTimeout; +const clearInterval_ = timers.clearInterval; + +export function setTimeout( + callback: (...args: unknown[]) => void, + timeout?: number, + ...args: unknown[] +) { + validateFunction(callback, "callback"); + return new Timeout(callback, timeout, args, false, true); +} + +Object.defineProperty(setTimeout, promisify.custom, { + value: (timeout: number, ...args: unknown[]) => { + return new Promise((cb) => setTimeout(cb, timeout, ...args)); + }, + enumerable: true, +}); +export function clearTimeout(timeout?: Timeout | number) { + if (timeout == null) { + return; + } + clearTimeout_(+timeout); +} +export function setInterval( + callback: (...args: unknown[]) => void, + timeout?: number, + ...args: unknown[] +) { + validateFunction(callback, "callback"); + return new Timeout(callback, timeout, args, true, true); +} +export function clearInterval(timeout?: Timeout | number | string) { + if (timeout == null) { + return; + } + clearInterval_(+timeout); +} +// TODO(bartlomieju): implement the 'NodeJS.Immediate' versions of the timers. +// https://github.com/DefinitelyTyped/DefinitelyTyped/blob/1163ead296d84e7a3c80d71e7c81ecbd1a130e9a/types/node/v12/globals.d.ts#L1120-L1131 +export const setImmediate = ( + cb: (...args: unknown[]) => void, + ...args: unknown[] +): Timeout => setTimeout(cb, 0, ...args); +export const clearImmediate = clearTimeout; + +export default { + setTimeout, + clearTimeout, + setInterval, + clearInterval, + setImmediate, + setUnrefTimeout, + clearImmediate, +}; diff --git a/ext/node/polyfills/timers/promises.ts b/ext/node/polyfills/timers/promises.ts new file mode 100644 index 00000000000000..4700f43ec035c3 --- /dev/null +++ b/ext/node/polyfills/timers/promises.ts @@ -0,0 +1,13 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. +import { promisify } from "internal:deno_node/polyfills/util.ts"; +import timers from "internal:deno_node/polyfills/timers.ts"; + +export const setTimeout = promisify(timers.setTimeout), + setImmediate = promisify(timers.setImmediate), + setInterval = promisify(timers.setInterval); + +export default { + setTimeout, + setImmediate, + setInterval, +}; diff --git a/ext/node/polyfills/tls.ts b/ext/node/polyfills/tls.ts new file mode 100644 index 00000000000000..b920ffc7d45054 --- /dev/null +++ b/ext/node/polyfills/tls.ts @@ -0,0 +1,65 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. +// Copyright Joyent and Node contributors. All rights reserved. MIT license. + +import { notImplemented } from "internal:deno_node/polyfills/_utils.ts"; +import tlsCommon from "internal:deno_node/polyfills/_tls_common.ts"; +import tlsWrap from "internal:deno_node/polyfills/_tls_wrap.ts"; + +// openssl -> rustls +const cipherMap = { + "__proto__": null, + "AES128-GCM-SHA256": "TLS13_AES_128_GCM_SHA256", + "AES256-GCM-SHA384": "TLS13_AES_256_GCM_SHA384", + "ECDHE-ECDSA-AES128-GCM-SHA256": "TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256", + "ECDHE-ECDSA-AES256-GCM-SHA384": "TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384", + "ECDHE-ECDSA-CHACHA20-POLY1305": + "TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256", + "ECDHE-RSA-AES128-GCM-SHA256": "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256", + "ECDHE-RSA-AES256-GCM-SHA384": "TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384", + "ECDHE-RSA-CHACHA20-POLY1305": "TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256", + "TLS_AES_128_GCM_SHA256": "TLS13_AES_128_GCM_SHA256", + "TLS_AES_256_GCM_SHA384": "TLS13_AES_256_GCM_SHA384", + "TLS_CHACHA20_POLY1305_SHA256": "TLS13_CHACHA20_POLY1305_SHA256", +}; + +export function getCiphers() { + // TODO(bnoordhuis) Use locale-insensitive toLowerCase() + return Object.keys(cipherMap).map((name) => name.toLowerCase()); +} + +export const rootCertificates = undefined; +export const DEFAULT_ECDH_CURVE = "auto"; +export const DEFAULT_MAX_VERSION = "TLSv1.3"; +export const DEFAULT_MIN_VERSION = "TLSv1.2"; + +export class CryptoStream {} +export class SecurePair {} +export const Server = tlsWrap.Server; +export function createSecurePair() { + notImplemented("tls.createSecurePair"); +} + +export default { + CryptoStream, + SecurePair, + Server, + TLSSocket: tlsWrap.TLSSocket, + checkServerIdentity: tlsWrap.checkServerIdentity, + connect: tlsWrap.connect, + createSecureContext: tlsCommon.createSecureContext, + createSecurePair, + createServer: tlsWrap.createServer, + getCiphers, + rootCertificates, + DEFAULT_CIPHERS: tlsWrap.DEFAULT_CIPHERS, + DEFAULT_ECDH_CURVE, + DEFAULT_MAX_VERSION, + DEFAULT_MIN_VERSION, +}; + +export const checkServerIdentity = tlsWrap.checkServerIdentity; +export const connect = tlsWrap.connect; +export const createSecureContext = tlsCommon.createSecureContext; +export const createServer = tlsWrap.createServer; +export const DEFAULT_CIPHERS = tlsWrap.DEFAULT_CIPHERS; +export const TLSSocket = tlsWrap.TLSSocket; diff --git a/ext/node/polyfills/tty.ts b/ext/node/polyfills/tty.ts new file mode 100644 index 00000000000000..b3b9b62da6260e --- /dev/null +++ b/ext/node/polyfills/tty.ts @@ -0,0 +1,25 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. + +import { Socket } from "internal:deno_node/polyfills/net.ts"; + +// Returns true when the given numeric fd is associated with a TTY and false otherwise. +function isatty(fd: number) { + if (typeof fd !== "number") { + return false; + } + try { + return Deno.isatty(fd); + } catch (_) { + return false; + } +} + +// TODO(kt3k): Implement tty.ReadStream class +export class ReadStream extends Socket { +} +// TODO(kt3k): Implement tty.WriteStream class +export class WriteStream extends Socket { +} + +export { isatty }; +export default { isatty, WriteStream, ReadStream }; diff --git a/ext/node/polyfills/upstream_modules.ts b/ext/node/polyfills/upstream_modules.ts new file mode 100644 index 00000000000000..ed8d6faa0037dd --- /dev/null +++ b/ext/node/polyfills/upstream_modules.ts @@ -0,0 +1,39 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. +// Upstream modules +const callerPath = `const callerCallsite = require("caller-callsite"); +const re = /^file:/; + +module.exports = () => { + const fileUrl = callerCallsite().getFileName(); + return fileUrl.replace(re, ""); +}; +`; + +// From: https://github.com/stefanpenner/get-caller-file/blob/2383bf9e98ed3c568ff69d7586cf59c0f1dcb9d3/index.ts +const getCallerFile = ` +const re = /^file:\\/\\//; + +module.exports = function getCallerFile(position = 2) { + if (position >= Error.stackTraceLimit) { + throw new TypeError('getCallerFile(position) requires position be less then Error.stackTraceLimit but position was: "' + position + '" and Error.stackTraceLimit was: "' + Error.stackTraceLimit + '"'); + } + + const oldPrepareStackTrace = Error.prepareStackTrace; + Error.prepareStackTrace = (_, stack) => stack; + const stack = new Error().stack; + Error.prepareStackTrace = oldPrepareStackTrace; + + + if (stack !== null && typeof stack === 'object') { + // stack[0] holds this file + // stack[1] holds where this function was called + // stack[2] holds the file we're interested in + return stack[position] ? stack[position].getFileName().replace(re, "") : undefined; + } +}; +`; + +export default { + "caller-path": callerPath, + "get-caller-file": getCallerFile, +} as Record; diff --git a/ext/node/polyfills/url.ts b/ext/node/polyfills/url.ts new file mode 100644 index 00000000000000..6d38fd1ffc7514 --- /dev/null +++ b/ext/node/polyfills/url.ts @@ -0,0 +1,1493 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. +// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. + +import { + ERR_INVALID_ARG_TYPE, + ERR_INVALID_ARG_VALUE, + ERR_INVALID_FILE_URL_HOST, + ERR_INVALID_FILE_URL_PATH, + ERR_INVALID_URL, + ERR_INVALID_URL_SCHEME, +} from "internal:deno_node/polyfills/internal/errors.ts"; +import { validateString } from "internal:deno_node/polyfills/internal/validators.mjs"; +import { + CHAR_0, + CHAR_9, + CHAR_AT, + CHAR_BACKWARD_SLASH, + CHAR_CARRIAGE_RETURN, + CHAR_CIRCUMFLEX_ACCENT, + CHAR_DOT, + CHAR_DOUBLE_QUOTE, + CHAR_FORM_FEED, + CHAR_FORWARD_SLASH, + CHAR_GRAVE_ACCENT, + CHAR_HASH, + CHAR_HYPHEN_MINUS, + CHAR_LEFT_ANGLE_BRACKET, + CHAR_LEFT_CURLY_BRACKET, + CHAR_LEFT_SQUARE_BRACKET, + CHAR_LINE_FEED, + CHAR_LOWERCASE_A, + CHAR_LOWERCASE_Z, + CHAR_NO_BREAK_SPACE, + CHAR_PERCENT, + CHAR_PLUS, + CHAR_QUESTION_MARK, + CHAR_RIGHT_ANGLE_BRACKET, + CHAR_RIGHT_CURLY_BRACKET, + CHAR_RIGHT_SQUARE_BRACKET, + CHAR_SEMICOLON, + CHAR_SINGLE_QUOTE, + CHAR_SPACE, + CHAR_TAB, + CHAR_UNDERSCORE, + CHAR_UPPERCASE_A, + CHAR_UPPERCASE_Z, + CHAR_VERTICAL_LINE, + CHAR_ZERO_WIDTH_NOBREAK_SPACE, +} from "internal:deno_node/polyfills/path/_constants.ts"; +import * as path from "internal:deno_node/polyfills/path.ts"; +import { toASCII, toUnicode } from "internal:deno_node/polyfills/punycode.ts"; +import { isWindows, osType } from "internal:deno_node/polyfills/_util/os.ts"; +import { + encodeStr, + hexTable, +} from "internal:deno_node/polyfills/internal/querystring.ts"; +import querystring from "internal:deno_node/polyfills/querystring.ts"; +import type { + ParsedUrlQuery, + ParsedUrlQueryInput, +} from "internal:deno_node/polyfills/querystring.ts"; +import { URL, URLSearchParams } from "internal:deno_url/00_url.js"; + +const forwardSlashRegEx = /\//g; +const percentRegEx = /%/g; +const backslashRegEx = /\\/g; +const newlineRegEx = /\n/g; +const carriageReturnRegEx = /\r/g; +const tabRegEx = /\t/g; +// Reference: RFC 3986, RFC 1808, RFC 2396 + +// define these here so at least they only have to be +// compiled once on the first module load. +const protocolPattern = /^[a-z0-9.+-]+:/i; +const portPattern = /:[0-9]*$/; +const hostPattern = /^\/\/[^@/]+@[^@/]+/; +// Special case for a simple path URL +const simplePathPattern = /^(\/\/?(?!\/)[^?\s]*)(\?[^\s]*)?$/; +// Protocols that can allow "unsafe" and "unwise" chars. +const unsafeProtocol = new Set(["javascript", "javascript:"]); +// Protocols that never have a hostname. +const hostlessProtocol = new Set(["javascript", "javascript:"]); +// Protocols that always contain a // bit. +const slashedProtocol = new Set([ + "http", + "http:", + "https", + "https:", + "ftp", + "ftp:", + "gopher", + "gopher:", + "file", + "file:", + "ws", + "ws:", + "wss", + "wss:", +]); + +const hostnameMaxLen = 255; + +// These characters do not need escaping: +// ! - . _ ~ +// ' ( ) * : +// digits +// alpha (uppercase) +// alpha (lowercase) +// deno-fmt-ignore +const noEscapeAuth = new Int8Array([ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 0x00 - 0x0F + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 0x10 - 0x1F + 0, 1, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 1, 1, 0, // 0x20 - 0x2F + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, // 0x30 - 0x3F + 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 0x40 - 0x4F + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1, // 0x50 - 0x5F + 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 0x60 - 0x6F + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 1, 0, // 0x70 - 0x7F +]); + +// This prevents some common spoofing bugs due to our use of IDNA toASCII. For +// compatibility, the set of characters we use here is the *intersection* of +// "forbidden host code point" in the WHATWG URL Standard [1] and the +// characters in the host parsing loop in Url.prototype.parse, with the +// following additions: +// +// - ':' since this could cause a "protocol spoofing" bug +// - '@' since this could cause parts of the hostname to be confused with auth +// - '[' and ']' since this could cause a non-IPv6 hostname to be interpreted +// as IPv6 by isIpv6Hostname above +// +// [1]: https://url.spec.whatwg.org/#forbidden-host-code-point +const forbiddenHostChars = /[\0\t\n\r #%/:<>?@[\\\]^|]/; +// For IPv6, permit '[', ']', and ':'. +const forbiddenHostCharsIpv6 = /[\0\t\n\r #%/<>?@\\^|]/; + +const _url = URL; +export { _url as URL }; + +// Legacy URL API +export class Url { + public protocol: string | null; + public slashes: boolean | null; + public auth: string | null; + public host: string | null; + public port: string | null; + public hostname: string | null; + public hash: string | null; + public search: string | null; + public query: string | ParsedUrlQuery | null; + public pathname: string | null; + public path: string | null; + public href: string | null; + [key: string]: unknown; + + constructor() { + this.protocol = null; + this.slashes = null; + this.auth = null; + this.host = null; + this.port = null; + this.hostname = null; + this.hash = null; + this.search = null; + this.query = null; + this.pathname = null; + this.path = null; + this.href = null; + } + + #parseHost() { + let host = this.host || ""; + let port: RegExpExecArray | null | string = portPattern.exec(host); + if (port) { + port = port[0]; + if (port !== ":") { + this.port = port.slice(1); + } + host = host.slice(0, host.length - port.length); + } + if (host) this.hostname = host; + } + + public resolve(relative: string) { + return this.resolveObject(parse(relative, false, true)).format(); + } + + public resolveObject(relative: string | Url) { + if (typeof relative === "string") { + const rel = new Url(); + rel.urlParse(relative, false, true); + relative = rel; + } + + const result = new Url(); + const tkeys = Object.keys(this); + for (let tk = 0; tk < tkeys.length; tk++) { + const tkey = tkeys[tk]; + result[tkey] = this[tkey]; + } + + // Hash is always overridden, no matter what. + // even href="" will remove it. + result.hash = relative.hash; + + // If the relative url is empty, then there's nothing left to do here. + if (relative.href === "") { + result.href = result.format(); + return result; + } + + // Hrefs like //foo/bar always cut to the protocol. + if (relative.slashes && !relative.protocol) { + // Take everything except the protocol from relative + const rkeys = Object.keys(relative); + for (let rk = 0; rk < rkeys.length; rk++) { + const rkey = rkeys[rk]; + if (rkey !== "protocol") result[rkey] = relative[rkey]; + } + + // urlParse appends trailing / to urls like http://www.example.com + if ( + result.protocol && + slashedProtocol.has(result.protocol) && + result.hostname && + !result.pathname + ) { + result.path = result.pathname = "/"; + } + + result.href = result.format(); + return result; + } + + if (relative.protocol && relative.protocol !== result.protocol) { + // If it's a known url protocol, then changing + // the protocol does weird things + // first, if it's not file:, then we MUST have a host, + // and if there was a path + // to begin with, then we MUST have a path. + // if it is file:, then the host is dropped, + // because that's known to be hostless. + // anything else is assumed to be absolute. + if (!slashedProtocol.has(relative.protocol)) { + const keys = Object.keys(relative); + for (let v = 0; v < keys.length; v++) { + const k = keys[v]; + result[k] = relative[k]; + } + result.href = result.format(); + return result; + } + + result.protocol = relative.protocol; + if ( + !relative.host && + !/^file:?$/.test(relative.protocol) && + !hostlessProtocol.has(relative.protocol) + ) { + const relPath = (relative.pathname || "").split("/"); + while (relPath.length && !(relative.host = relPath.shift() || null)); + if (!relative.host) relative.host = ""; + if (!relative.hostname) relative.hostname = ""; + if (relPath[0] !== "") relPath.unshift(""); + if (relPath.length < 2) relPath.unshift(""); + result.pathname = relPath.join("/"); + } else { + result.pathname = relative.pathname; + } + result.search = relative.search; + result.query = relative.query; + result.host = relative.host || ""; + result.auth = relative.auth; + result.hostname = relative.hostname || relative.host; + result.port = relative.port; + // To support http.request + if (result.pathname || result.search) { + const p = result.pathname || ""; + const s = result.search || ""; + result.path = p + s; + } + result.slashes = result.slashes || relative.slashes; + result.href = result.format(); + return result; + } + + const isSourceAbs = result.pathname && result.pathname.charAt(0) === "/"; + const isRelAbs = relative.host || + (relative.pathname && relative.pathname.charAt(0) === "/"); + let mustEndAbs: string | boolean | number | null = isRelAbs || + isSourceAbs || (result.host && relative.pathname); + const removeAllDots = mustEndAbs; + let srcPath = (result.pathname && result.pathname.split("/")) || []; + const relPath = (relative.pathname && relative.pathname.split("/")) || []; + const noLeadingSlashes = result.protocol && + !slashedProtocol.has(result.protocol); + + // If the url is a non-slashed url, then relative + // links like ../.. should be able + // to crawl up to the hostname, as well. This is strange. + // result.protocol has already been set by now. + // Later on, put the first path part into the host field. + if (noLeadingSlashes) { + result.hostname = ""; + result.port = null; + if (result.host) { + if (srcPath[0] === "") srcPath[0] = result.host; + else srcPath.unshift(result.host); + } + result.host = ""; + if (relative.protocol) { + relative.hostname = null; + relative.port = null; + result.auth = null; + if (relative.host) { + if (relPath[0] === "") relPath[0] = relative.host; + else relPath.unshift(relative.host); + } + relative.host = null; + } + mustEndAbs = mustEndAbs && (relPath[0] === "" || srcPath[0] === ""); + } + + if (isRelAbs) { + // it's absolute. + if (relative.host || relative.host === "") { + if (result.host !== relative.host) result.auth = null; + result.host = relative.host; + result.port = relative.port; + } + if (relative.hostname || relative.hostname === "") { + if (result.hostname !== relative.hostname) result.auth = null; + result.hostname = relative.hostname; + } + result.search = relative.search; + result.query = relative.query; + srcPath = relPath; + // Fall through to the dot-handling below. + } else if (relPath.length) { + // it's relative + // throw away the existing file, and take the new path instead. + if (!srcPath) srcPath = []; + srcPath.pop(); + srcPath = srcPath.concat(relPath); + result.search = relative.search; + result.query = relative.query; + } else if (relative.search !== null && relative.search !== undefined) { + // Just pull out the search. + // like href='?foo'. + // Put this after the other two cases because it simplifies the booleans + if (noLeadingSlashes) { + result.hostname = result.host = srcPath.shift() || null; + // Occasionally the auth can get stuck only in host. + // This especially happens in cases like + // url.resolveObject('mailto:local1@domain1', 'local2@domain2') + const authInHost = result.host && result.host.indexOf("@") > 0 && + result.host.split("@"); + if (authInHost) { + result.auth = authInHost.shift() || null; + result.host = result.hostname = authInHost.shift() || null; + } + } + result.search = relative.search; + result.query = relative.query; + // To support http.request + if (result.pathname !== null || result.search !== null) { + result.path = (result.pathname ? result.pathname : "") + + (result.search ? result.search : ""); + } + result.href = result.format(); + return result; + } + + if (!srcPath.length) { + // No path at all. All other things were already handled above. + result.pathname = null; + // To support http.request + if (result.search) { + result.path = "/" + result.search; + } else { + result.path = null; + } + result.href = result.format(); + return result; + } + + // If a url ENDs in . or .., then it must get a trailing slash. + // however, if it ends in anything else non-slashy, + // then it must NOT get a trailing slash. + let last = srcPath.slice(-1)[0]; + const hasTrailingSlash = + ((result.host || relative.host || srcPath.length > 1) && + (last === "." || last === "..")) || + last === ""; + + // Strip single dots, resolve double dots to parent dir + // if the path tries to go above the root, `up` ends up > 0 + let up = 0; + for (let i = srcPath.length - 1; i >= 0; i--) { + last = srcPath[i]; + if (last === ".") { + srcPath.splice(i, 1); + } else if (last === "..") { + srcPath.splice(i, 1); + up++; + } else if (up) { + srcPath.splice(i, 1); + up--; + } + } + + // If the path is allowed to go above the root, restore leading ..s + if (!mustEndAbs && !removeAllDots) { + while (up--) { + srcPath.unshift(".."); + } + } + + if ( + mustEndAbs && + srcPath[0] !== "" && + (!srcPath[0] || srcPath[0].charAt(0) !== "/") + ) { + srcPath.unshift(""); + } + + if (hasTrailingSlash && srcPath.join("/").slice(-1) !== "/") { + srcPath.push(""); + } + + const isAbsolute = srcPath[0] === "" || + (srcPath[0] && srcPath[0].charAt(0) === "/"); + + // put the host back + if (noLeadingSlashes) { + result.hostname = result.host = isAbsolute + ? "" + : srcPath.length + ? srcPath.shift() || null + : ""; + // Occasionally the auth can get stuck only in host. + // This especially happens in cases like + // url.resolveObject('mailto:local1@domain1', 'local2@domain2') + const authInHost = result.host && result.host.indexOf("@") > 0 + ? result.host.split("@") + : false; + if (authInHost) { + result.auth = authInHost.shift() || null; + result.host = result.hostname = authInHost.shift() || null; + } + } + + mustEndAbs = mustEndAbs || (result.host && srcPath.length); + + if (mustEndAbs && !isAbsolute) { + srcPath.unshift(""); + } + + if (!srcPath.length) { + result.pathname = null; + result.path = null; + } else { + result.pathname = srcPath.join("/"); + } + + // To support request.http + if (result.pathname !== null || result.search !== null) { + result.path = (result.pathname ? result.pathname : "") + + (result.search ? result.search : ""); + } + result.auth = relative.auth || result.auth; + result.slashes = result.slashes || relative.slashes; + result.href = result.format(); + return result; + } + + format() { + let auth = this.auth || ""; + if (auth) { + auth = encodeStr(auth, noEscapeAuth, hexTable); + auth += "@"; + } + + let protocol = this.protocol || ""; + let pathname = this.pathname || ""; + let hash = this.hash || ""; + let host = ""; + let query = ""; + + if (this.host) { + host = auth + this.host; + } else if (this.hostname) { + host = auth + + (this.hostname.includes(":") && !isIpv6Hostname(this.hostname) + ? "[" + this.hostname + "]" + : this.hostname); + if (this.port) { + host += ":" + this.port; + } + } + + if (this.query !== null && typeof this.query === "object") { + query = querystring.stringify(this.query); + } + + let search = this.search || (query && "?" + query) || ""; + + if (protocol && protocol.charCodeAt(protocol.length - 1) !== 58 /* : */) { + protocol += ":"; + } + + let newPathname = ""; + let lastPos = 0; + for (let i = 0; i < pathname.length; ++i) { + switch (pathname.charCodeAt(i)) { + case CHAR_HASH: + if (i - lastPos > 0) { + newPathname += pathname.slice(lastPos, i); + } + newPathname += "%23"; + lastPos = i + 1; + break; + case CHAR_QUESTION_MARK: + if (i - lastPos > 0) { + newPathname += pathname.slice(lastPos, i); + } + newPathname += "%3F"; + lastPos = i + 1; + break; + } + } + if (lastPos > 0) { + if (lastPos !== pathname.length) { + pathname = newPathname + pathname.slice(lastPos); + } else pathname = newPathname; + } + + // Only the slashedProtocols get the //. Not mailto:, xmpp:, etc. + // unless they had them to begin with. + if (this.slashes || slashedProtocol.has(protocol)) { + if (this.slashes || host) { + if (pathname && pathname.charCodeAt(0) !== CHAR_FORWARD_SLASH) { + pathname = "/" + pathname; + } + host = "//" + host; + } else if ( + protocol.length >= 4 && + protocol.charCodeAt(0) === 102 /* f */ && + protocol.charCodeAt(1) === 105 /* i */ && + protocol.charCodeAt(2) === 108 /* l */ && + protocol.charCodeAt(3) === 101 /* e */ + ) { + host = "//"; + } + } + + search = search.replace(/#/g, "%23"); + + if (hash && hash.charCodeAt(0) !== CHAR_HASH) { + hash = "#" + hash; + } + if (search && search.charCodeAt(0) !== CHAR_QUESTION_MARK) { + search = "?" + search; + } + + return protocol + host + pathname + search + hash; + } + + public urlParse( + url: string, + parseQueryString: boolean, + slashesDenoteHost: boolean, + ) { + validateString(url, "url"); + + // Copy chrome, IE, opera backslash-handling behavior. + // Back slashes before the query string get converted to forward slashes + // See: https://code.google.com/p/chromium/issues/detail?id=25916 + let hasHash = false; + let start = -1; + let end = -1; + let rest = ""; + let lastPos = 0; + for (let i = 0, inWs = false, split = false; i < url.length; ++i) { + const code = url.charCodeAt(i); + + // Find first and last non-whitespace characters for trimming + const isWs = code === CHAR_SPACE || + code === CHAR_TAB || + code === CHAR_CARRIAGE_RETURN || + code === CHAR_LINE_FEED || + code === CHAR_FORM_FEED || + code === CHAR_NO_BREAK_SPACE || + code === CHAR_ZERO_WIDTH_NOBREAK_SPACE; + if (start === -1) { + if (isWs) continue; + lastPos = start = i; + } else if (inWs) { + if (!isWs) { + end = -1; + inWs = false; + } + } else if (isWs) { + end = i; + inWs = true; + } + + // Only convert backslashes while we haven't seen a split character + if (!split) { + switch (code) { + case CHAR_HASH: + hasHash = true; + // Fall through + case CHAR_QUESTION_MARK: + split = true; + break; + case CHAR_BACKWARD_SLASH: + if (i - lastPos > 0) rest += url.slice(lastPos, i); + rest += "/"; + lastPos = i + 1; + break; + } + } else if (!hasHash && code === CHAR_HASH) { + hasHash = true; + } + } + + // Check if string was non-empty (including strings with only whitespace) + if (start !== -1) { + if (lastPos === start) { + // We didn't convert any backslashes + + if (end === -1) { + if (start === 0) rest = url; + else rest = url.slice(start); + } else { + rest = url.slice(start, end); + } + } else if (end === -1 && lastPos < url.length) { + // We converted some backslashes and have only part of the entire string + rest += url.slice(lastPos); + } else if (end !== -1 && lastPos < end) { + // We converted some backslashes and have only part of the entire string + rest += url.slice(lastPos, end); + } + } + + if (!slashesDenoteHost && !hasHash) { + // Try fast path regexp + const simplePath = simplePathPattern.exec(rest); + if (simplePath) { + this.path = rest; + this.href = rest; + this.pathname = simplePath[1]; + if (simplePath[2]) { + this.search = simplePath[2]; + if (parseQueryString) { + this.query = querystring.parse(this.search.slice(1)); + } else { + this.query = this.search.slice(1); + } + } else if (parseQueryString) { + this.search = null; + this.query = Object.create(null); + } + return this; + } + } + + let proto: RegExpExecArray | null | string = protocolPattern.exec(rest); + let lowerProto = ""; + if (proto) { + proto = proto[0]; + lowerProto = proto.toLowerCase(); + this.protocol = lowerProto; + rest = rest.slice(proto.length); + } + + // Figure out if it's got a host + // user@server is *always* interpreted as a hostname, and url + // resolution will treat //foo/bar as host=foo,path=bar because that's + // how the browser resolves relative URLs. + let slashes; + if (slashesDenoteHost || proto || hostPattern.test(rest)) { + slashes = rest.charCodeAt(0) === CHAR_FORWARD_SLASH && + rest.charCodeAt(1) === CHAR_FORWARD_SLASH; + if (slashes && !(proto && hostlessProtocol.has(lowerProto))) { + rest = rest.slice(2); + this.slashes = true; + } + } + + if ( + !hostlessProtocol.has(lowerProto) && + (slashes || (proto && !slashedProtocol.has(proto))) + ) { + // there's a hostname. + // the first instance of /, ?, ;, or # ends the host. + // + // If there is an @ in the hostname, then non-host chars *are* allowed + // to the left of the last @ sign, unless some host-ending character + // comes *before* the @-sign. + // URLs are obnoxious. + // + // ex: + // http://a@b@c/ => user:a@b host:c + // http://a@b?@c => user:a host:b path:/?@c + + let hostEnd = -1; + let atSign = -1; + let nonHost = -1; + for (let i = 0; i < rest.length; ++i) { + switch (rest.charCodeAt(i)) { + case CHAR_TAB: + case CHAR_LINE_FEED: + case CHAR_CARRIAGE_RETURN: + case CHAR_SPACE: + case CHAR_DOUBLE_QUOTE: + case CHAR_PERCENT: + case CHAR_SINGLE_QUOTE: + case CHAR_SEMICOLON: + case CHAR_LEFT_ANGLE_BRACKET: + case CHAR_RIGHT_ANGLE_BRACKET: + case CHAR_BACKWARD_SLASH: + case CHAR_CIRCUMFLEX_ACCENT: + case CHAR_GRAVE_ACCENT: + case CHAR_LEFT_CURLY_BRACKET: + case CHAR_VERTICAL_LINE: + case CHAR_RIGHT_CURLY_BRACKET: + // Characters that are never ever allowed in a hostname from RFC 2396 + if (nonHost === -1) nonHost = i; + break; + case CHAR_HASH: + case CHAR_FORWARD_SLASH: + case CHAR_QUESTION_MARK: + // Find the first instance of any host-ending characters + if (nonHost === -1) nonHost = i; + hostEnd = i; + break; + case CHAR_AT: + // At this point, either we have an explicit point where the + // auth portion cannot go past, or the last @ char is the decider. + atSign = i; + nonHost = -1; + break; + } + if (hostEnd !== -1) break; + } + start = 0; + if (atSign !== -1) { + this.auth = decodeURIComponent(rest.slice(0, atSign)); + start = atSign + 1; + } + if (nonHost === -1) { + this.host = rest.slice(start); + rest = ""; + } else { + this.host = rest.slice(start, nonHost); + rest = rest.slice(nonHost); + } + + // pull out port. + this.#parseHost(); + + // We've indicated that there is a hostname, + // so even if it's empty, it has to be present. + if (typeof this.hostname !== "string") this.hostname = ""; + + const hostname = this.hostname; + + // If hostname begins with [ and ends with ] + // assume that it's an IPv6 address. + const ipv6Hostname = isIpv6Hostname(hostname); + + // validate a little. + if (!ipv6Hostname) { + rest = getHostname(this, rest, hostname); + } + + if (this.hostname.length > hostnameMaxLen) { + this.hostname = ""; + } else { + // Hostnames are always lower case. + this.hostname = this.hostname.toLowerCase(); + } + + if (this.hostname !== "") { + if (ipv6Hostname) { + if (forbiddenHostCharsIpv6.test(this.hostname)) { + throw new ERR_INVALID_URL(url); + } + } else { + // IDNA Support: Returns a punycoded representation of "domain". + // It only converts parts of the domain name that + // have non-ASCII characters, i.e. it doesn't matter if + // you call it with a domain that already is ASCII-only. + + // Use lenient mode (`true`) to try to support even non-compliant + // URLs. + this.hostname = toASCII(this.hostname); + + // Prevent two potential routes of hostname spoofing. + // 1. If this.hostname is empty, it must have become empty due to toASCII + // since we checked this.hostname above. + // 2. If any of forbiddenHostChars appears in this.hostname, it must have + // also gotten in due to toASCII. This is since getHostname would have + // filtered them out otherwise. + // Rather than trying to correct this by moving the non-host part into + // the pathname as we've done in getHostname, throw an exception to + // convey the severity of this issue. + if (this.hostname === "" || forbiddenHostChars.test(this.hostname)) { + throw new ERR_INVALID_URL(url); + } + } + } + + const p = this.port ? ":" + this.port : ""; + const h = this.hostname || ""; + this.host = h + p; + + // strip [ and ] from the hostname + // the host field still retains them, though + if (ipv6Hostname) { + this.hostname = this.hostname.slice(1, -1); + if (rest[0] !== "/") { + rest = "/" + rest; + } + } + } + + // Now rest is set to the post-host stuff. + // Chop off any delim chars. + if (!unsafeProtocol.has(lowerProto)) { + // First, make 100% sure that any "autoEscape" chars get + // escaped, even if encodeURIComponent doesn't think they + // need to be. + rest = autoEscapeStr(rest); + } + + let questionIdx = -1; + let hashIdx = -1; + for (let i = 0; i < rest.length; ++i) { + const code = rest.charCodeAt(i); + if (code === CHAR_HASH) { + this.hash = rest.slice(i); + hashIdx = i; + break; + } else if (code === CHAR_QUESTION_MARK && questionIdx === -1) { + questionIdx = i; + } + } + + if (questionIdx !== -1) { + if (hashIdx === -1) { + this.search = rest.slice(questionIdx); + this.query = rest.slice(questionIdx + 1); + } else { + this.search = rest.slice(questionIdx, hashIdx); + this.query = rest.slice(questionIdx + 1, hashIdx); + } + if (parseQueryString) { + this.query = querystring.parse(this.query); + } + } else if (parseQueryString) { + // No query string, but parseQueryString still requested + this.search = null; + this.query = Object.create(null); + } + + const useQuestionIdx = questionIdx !== -1 && + (hashIdx === -1 || questionIdx < hashIdx); + const firstIdx = useQuestionIdx ? questionIdx : hashIdx; + if (firstIdx === -1) { + if (rest.length > 0) this.pathname = rest; + } else if (firstIdx > 0) { + this.pathname = rest.slice(0, firstIdx); + } + if (slashedProtocol.has(lowerProto) && this.hostname && !this.pathname) { + this.pathname = "/"; + } + + // To support http.request + if (this.pathname || this.search) { + const p = this.pathname || ""; + const s = this.search || ""; + this.path = p + s; + } + + // Finally, reconstruct the href based on what has been validated. + this.href = this.format(); + return this; + } +} + +interface UrlObject { + auth?: string | null | undefined; + hash?: string | null | undefined; + host?: string | null | undefined; + hostname?: string | null | undefined; + href?: string | null | undefined; + pathname?: string | null | undefined; + protocol?: string | null | undefined; + search?: string | null | undefined; + slashes?: boolean | null | undefined; + port?: string | number | null | undefined; + query?: string | null | ParsedUrlQueryInput | undefined; +} + +export function format( + urlObject: string | URL | Url | UrlObject, + options?: { + auth: boolean; + fragment: boolean; + search: boolean; + unicode: boolean; + }, +): string { + if (typeof urlObject === "string") { + urlObject = parse(urlObject, true, false); + } else if (typeof urlObject !== "object" || urlObject === null) { + throw new ERR_INVALID_ARG_TYPE( + "urlObject", + ["Object", "string"], + urlObject, + ); + } else if (!(urlObject instanceof Url)) { + if (urlObject instanceof URL) { + return formatWhatwg(urlObject, options); + } + return Url.prototype.format.call(urlObject); + } + + return (urlObject as Url).format(); +} + +/** + * The URL object has both a `toString()` method and `href` property that return string serializations of the URL. + * These are not, however, customizable in any way. + * This method allows for basic customization of the output. + * @see Tested in `parallel/test-url-format-whatwg.js`. + * @param urlObject + * @param options + * @param options.auth `true` if the serialized URL string should include the username and password, `false` otherwise. **Default**: `true`. + * @param options.fragment `true` if the serialized URL string should include the fragment, `false` otherwise. **Default**: `true`. + * @param options.search `true` if the serialized URL string should include the search query, **Default**: `true`. + * @param options.unicode `true` if Unicode characters appearing in the host component of the URL string should be encoded directly as opposed to being Punycode encoded. **Default**: `false`. + * @returns a customizable serialization of a URL `String` representation of a `WHATWG URL` object. + */ +function formatWhatwg( + urlObject: string | URL, + options?: { + auth: boolean; + fragment: boolean; + search: boolean; + unicode: boolean; + }, +): string { + if (typeof urlObject === "string") { + urlObject = new URL(urlObject); + } + if (options) { + if (typeof options !== "object") { + throw new ERR_INVALID_ARG_TYPE("options", "object", options); + } + } + + options = { + auth: true, + fragment: true, + search: true, + unicode: false, + ...options, + }; + + let ret = urlObject.protocol; + if (urlObject.host !== null) { + ret += "//"; + const hasUsername = !!urlObject.username; + const hasPassword = !!urlObject.password; + if (options.auth && (hasUsername || hasPassword)) { + if (hasUsername) { + ret += urlObject.username; + } + if (hasPassword) { + ret += `:${urlObject.password}`; + } + ret += "@"; + } + // TODO(wafuwfu13): Support unicode option + // ret += options.unicode ? + // domainToUnicode(urlObject.host) : urlObject.host; + ret += urlObject.host; + if (urlObject.port) { + ret += `:${urlObject.port}`; + } + } + + ret += urlObject.pathname; + + if (options.search && urlObject.search) { + ret += urlObject.search; + } + if (options.fragment && urlObject.hash) { + ret += urlObject.hash; + } + + return ret; +} + +function isIpv6Hostname(hostname: string) { + return ( + hostname.charCodeAt(0) === CHAR_LEFT_SQUARE_BRACKET && + hostname.charCodeAt(hostname.length - 1) === CHAR_RIGHT_SQUARE_BRACKET + ); +} + +function getHostname(self: Url, rest: string, hostname: string) { + for (let i = 0; i < hostname.length; ++i) { + const code = hostname.charCodeAt(i); + const isValid = (code >= CHAR_LOWERCASE_A && code <= CHAR_LOWERCASE_Z) || + code === CHAR_DOT || + (code >= CHAR_UPPERCASE_A && code <= CHAR_UPPERCASE_Z) || + (code >= CHAR_0 && code <= CHAR_9) || + code === CHAR_HYPHEN_MINUS || + code === CHAR_PLUS || + code === CHAR_UNDERSCORE || + code > 127; + + // Invalid host character + if (!isValid) { + self.hostname = hostname.slice(0, i); + return `/${hostname.slice(i)}${rest}`; + } + } + return rest; +} + +// Escaped characters. Use empty strings to fill up unused entries. +// Using Array is faster than Object/Map +// deno-fmt-ignore +const escapedCodes = [ + /* 0 - 9 */ "", + "", + "", + "", + "", + "", + "", + "", + "", + "%09", + /* 10 - 19 */ "%0A", + "", + "", + "%0D", + "", + "", + "", + "", + "", + "", + /* 20 - 29 */ "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + /* 30 - 39 */ "", + "", + "%20", + "", + "%22", + "", + "", + "", + "", + "%27", + /* 40 - 49 */ "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + /* 50 - 59 */ "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + /* 60 - 69 */ "%3C", + "", + "%3E", + "", + "", + "", + "", + "", + "", + "", + /* 70 - 79 */ "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + /* 80 - 89 */ "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + /* 90 - 99 */ "", + "", + "%5C", + "", + "%5E", + "", + "%60", + "", + "", + "", + /* 100 - 109 */ "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + /* 110 - 119 */ "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + /* 120 - 125 */ "", + "", + "", + "%7B", + "%7C", + "%7D" +]; + +// Automatically escape all delimiters and unwise characters from RFC 2396. +// Also escape single quotes in case of an XSS attack. +// Return the escaped string. +function autoEscapeStr(rest: string) { + let escaped = ""; + let lastEscapedPos = 0; + for (let i = 0; i < rest.length; ++i) { + // `escaped` contains substring up to the last escaped character. + const escapedChar = escapedCodes[rest.charCodeAt(i)]; + if (escapedChar) { + // Concat if there are ordinary characters in the middle. + if (i > lastEscapedPos) { + escaped += rest.slice(lastEscapedPos, i); + } + escaped += escapedChar; + lastEscapedPos = i + 1; + } + } + if (lastEscapedPos === 0) { + // Nothing has been escaped. + return rest; + } + + // There are ordinary characters at the end. + if (lastEscapedPos < rest.length) { + escaped += rest.slice(lastEscapedPos); + } + + return escaped; +} + +/** + * The url.urlParse() method takes a URL string, parses it, and returns a URL object. + * + * @see Tested in `parallel/test-url-parse-format.js`. + * @param url The URL string to parse. + * @param parseQueryString If `true`, the query property will always be set to an object returned by the querystring module's parse() method. If false, + * the query property on the returned URL object will be an unparsed, undecoded string. Default: false. + * @param slashesDenoteHost If `true`, the first token after the literal string // and preceding the next / will be interpreted as the host + */ +export function parse( + url: string | Url, + parseQueryString: boolean, + slashesDenoteHost: boolean, +) { + if (url instanceof Url) return url; + + const urlObject = new Url(); + urlObject.urlParse(url, parseQueryString, slashesDenoteHost); + return urlObject; +} + +/** The url.resolve() method resolves a target URL relative to a base URL in a manner similar to that of a Web browser resolving an anchor tag HREF. + * @see https://nodejs.org/api/url.html#urlresolvefrom-to + * @legacy + */ +export function resolve(from: string, to: string) { + return parse(from, false, true).resolve(to); +} + +export function resolveObject(source: string | Url, relative: string) { + if (!source) return relative; + return parse(source, false, true).resolveObject(relative); +} + +/** + * The url.domainToASCII() takes an arbitrary domain and attempts to convert it into an IDN + * + * @param domain The domain to convert to an IDN + * @see https://www.rfc-editor.org/rfc/rfc3490#section-4 + */ +export function domainToASCII(domain: string) { + return toASCII(domain); +} + +/** + * The url.domainToUnicode() takes an IDN and attempts to convert it into unicode + * + * @param domain The IDN to convert to Unicode + * @see https://www.rfc-editor.org/rfc/rfc3490#section-4 + */ +export function domainToUnicode(domain: string) { + return toUnicode(domain); +} + +/** + * This function ensures the correct decodings of percent-encoded characters as well as ensuring a cross-platform valid absolute path string. + * @see Tested in `parallel/test-fileurltopath.js`. + * @param path The file URL string or URL object to convert to a path. + * @returns The fully-resolved platform-specific Node.js file path. + */ +export function fileURLToPath(path: string | URL): string { + if (typeof path === "string") path = new URL(path); + else if (!(path instanceof URL)) { + throw new ERR_INVALID_ARG_TYPE("path", ["string", "URL"], path); + } + if (path.protocol !== "file:") { + throw new ERR_INVALID_URL_SCHEME("file"); + } + return isWindows ? getPathFromURLWin(path) : getPathFromURLPosix(path); +} + +function getPathFromURLWin(url: URL): string { + const hostname = url.hostname; + let pathname = url.pathname; + for (let n = 0; n < pathname.length; n++) { + if (pathname[n] === "%") { + const third = pathname.codePointAt(n + 2)! | 0x20; + if ( + (pathname[n + 1] === "2" && third === 102) || // 2f 2F / + (pathname[n + 1] === "5" && third === 99) // 5c 5C \ + ) { + throw new ERR_INVALID_FILE_URL_PATH( + "must not include encoded \\ or / characters", + ); + } + } + } + + pathname = pathname.replace(forwardSlashRegEx, "\\"); + pathname = decodeURIComponent(pathname); + if (hostname !== "") { + // TODO(bartlomieju): add support for punycode encodings + return `\\\\${hostname}${pathname}`; + } else { + // Otherwise, it's a local path that requires a drive letter + const letter = pathname.codePointAt(1)! | 0x20; + const sep = pathname[2]; + if ( + letter < CHAR_LOWERCASE_A || + letter > CHAR_LOWERCASE_Z || // a..z A..Z + sep !== ":" + ) { + throw new ERR_INVALID_FILE_URL_PATH("must be absolute"); + } + return pathname.slice(1); + } +} + +function getPathFromURLPosix(url: URL): string { + if (url.hostname !== "") { + throw new ERR_INVALID_FILE_URL_HOST(osType); + } + const pathname = url.pathname; + for (let n = 0; n < pathname.length; n++) { + if (pathname[n] === "%") { + const third = pathname.codePointAt(n + 2)! | 0x20; + if (pathname[n + 1] === "2" && third === 102) { + throw new ERR_INVALID_FILE_URL_PATH( + "must not include encoded / characters", + ); + } + } + } + return decodeURIComponent(pathname); +} + +/** + * The following characters are percent-encoded when converting from file path + * to URL: + * - %: The percent character is the only character not encoded by the + * `pathname` setter. + * - \: Backslash is encoded on non-windows platforms since it's a valid + * character but the `pathname` setters replaces it by a forward slash. + * - LF: The newline character is stripped out by the `pathname` setter. + * (See whatwg/url#419) + * - CR: The carriage return character is also stripped out by the `pathname` + * setter. + * - TAB: The tab character is also stripped out by the `pathname` setter. + */ +function encodePathChars(filepath: string): string { + if (filepath.includes("%")) { + filepath = filepath.replace(percentRegEx, "%25"); + } + // In posix, backslash is a valid character in paths: + if (!isWindows && filepath.includes("\\")) { + filepath = filepath.replace(backslashRegEx, "%5C"); + } + if (filepath.includes("\n")) { + filepath = filepath.replace(newlineRegEx, "%0A"); + } + if (filepath.includes("\r")) { + filepath = filepath.replace(carriageReturnRegEx, "%0D"); + } + if (filepath.includes("\t")) { + filepath = filepath.replace(tabRegEx, "%09"); + } + return filepath; +} + +/** + * This function ensures that `filepath` is resolved absolutely, and that the URL control characters are correctly encoded when converting into a File URL. + * @see Tested in `parallel/test-url-pathtofileurl.js`. + * @param filepath The file path string to convert to a file URL. + * @returns The file URL object. + */ +export function pathToFileURL(filepath: string): URL { + const outURL = new URL("file://"); + if (isWindows && filepath.startsWith("\\\\")) { + // UNC path format: \\server\share\resource + const paths = filepath.split("\\"); + if (paths.length <= 3) { + throw new ERR_INVALID_ARG_VALUE( + "filepath", + filepath, + "Missing UNC resource path", + ); + } + const hostname = paths[2]; + if (hostname.length === 0) { + throw new ERR_INVALID_ARG_VALUE( + "filepath", + filepath, + "Empty UNC servername", + ); + } + + outURL.hostname = domainToASCII(hostname); + outURL.pathname = encodePathChars(paths.slice(3).join("/")); + } else { + let resolved = path.resolve(filepath); + // path.resolve strips trailing slashes so we must add them back + const filePathLast = filepath.charCodeAt(filepath.length - 1); + if ( + (filePathLast === CHAR_FORWARD_SLASH || + (isWindows && filePathLast === CHAR_BACKWARD_SLASH)) && + resolved[resolved.length - 1] !== path.sep + ) { + resolved += "/"; + } + + outURL.pathname = encodePathChars(resolved); + } + return outURL; +} + +interface HttpOptions { + protocol: string; + hostname: string; + hash: string; + search: string; + pathname: string; + path: string; + href: string; + port?: number; + auth?: string; +} + +/** + * This utility function converts a URL object into an ordinary options object as expected by the `http.request()` and `https.request()` APIs. + * @see Tested in `parallel/test-url-urltooptions.js`. + * @param url The `WHATWG URL` object to convert to an options object. + * @returns HttpOptions + * @returns HttpOptions.protocol Protocol to use. + * @returns HttpOptions.hostname A domain name or IP address of the server to issue the request to. + * @returns HttpOptions.hash The fragment portion of the URL. + * @returns HttpOptions.search The serialized query portion of the URL. + * @returns HttpOptions.pathname The path portion of the URL. + * @returns HttpOptions.path Request path. Should include query string if any. E.G. `'/index.html?page=12'`. An exception is thrown when the request path contains illegal characters. Currently, only spaces are rejected but that may change in the future. + * @returns HttpOptions.href The serialized URL. + * @returns HttpOptions.port Port of remote server. + * @returns HttpOptions.auth Basic authentication i.e. `'user:password'` to compute an Authorization header. + */ +export function urlToHttpOptions(url: URL): HttpOptions { + const options: HttpOptions = { + protocol: url.protocol, + hostname: typeof url.hostname === "string" && url.hostname.startsWith("[") + ? url.hostname.slice(1, -1) + : url.hostname, + hash: url.hash, + search: url.search, + pathname: url.pathname, + path: `${url.pathname || ""}${url.search || ""}`, + href: url.href, + }; + if (url.port !== "") { + options.port = Number(url.port); + } + if (url.username || url.password) { + options.auth = `${decodeURIComponent(url.username)}:${ + decodeURIComponent( + url.password, + ) + }`; + } + return options; +} + +const URLSearchParams_ = URLSearchParams; +export { URLSearchParams_ as URLSearchParams }; + +export default { + parse, + format, + resolve, + resolveObject, + domainToASCII, + domainToUnicode, + fileURLToPath, + pathToFileURL, + urlToHttpOptions, + Url, + URL, + URLSearchParams, +}; diff --git a/ext/node/polyfills/util.ts b/ext/node/polyfills/util.ts new file mode 100644 index 00000000000000..32dfcae74860ae --- /dev/null +++ b/ext/node/polyfills/util.ts @@ -0,0 +1,293 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. +import { promisify } from "internal:deno_node/polyfills/internal/util.mjs"; +import { callbackify } from "internal:deno_node/polyfills/_util/_util_callbackify.ts"; +import { debuglog } from "internal:deno_node/polyfills/internal/util/debuglog.ts"; +import { + format, + formatWithOptions, + inspect, + stripVTControlCharacters, +} from "internal:deno_node/polyfills/internal/util/inspect.mjs"; +import { codes } from "internal:deno_node/polyfills/internal/error_codes.ts"; +import types from "internal:deno_node/polyfills/util/types.ts"; +import { Buffer } from "internal:deno_node/polyfills/buffer.ts"; +import { isDeepStrictEqual } from "internal:deno_node/polyfills/internal/util/comparisons.ts"; +import process from "internal:deno_node/polyfills/process.ts"; +import { validateString } from "internal:deno_node/polyfills/internal/validators.mjs"; + +export { + callbackify, + debuglog, + format, + formatWithOptions, + inspect, + promisify, + stripVTControlCharacters, + types, +}; + +/** @deprecated - use `Array.isArray()` instead. */ +export function isArray(value: unknown): boolean { + return Array.isArray(value); +} + +/** @deprecated - use `typeof value === "boolean" || value instanceof Boolean` instead. */ +export function isBoolean(value: unknown): boolean { + return typeof value === "boolean" || value instanceof Boolean; +} + +/** @deprecated - use `value === null` instead. */ +export function isNull(value: unknown): boolean { + return value === null; +} + +/** @deprecated - use `value === null || value === undefined` instead. */ +export function isNullOrUndefined(value: unknown): boolean { + return value === null || value === undefined; +} + +/** @deprecated - use `typeof value === "number" || value instanceof Number` instead. */ +export function isNumber(value: unknown): boolean { + return typeof value === "number" || value instanceof Number; +} + +/** @deprecated - use `typeof value === "string" || value instanceof String` instead. */ +export function isString(value: unknown): boolean { + return typeof value === "string" || value instanceof String; +} + +/** @deprecated - use `typeof value === "symbol"` instead. */ +export function isSymbol(value: unknown): boolean { + return typeof value === "symbol"; +} + +/** @deprecated - use `value === undefined` instead. */ +export function isUndefined(value: unknown): boolean { + return value === undefined; +} + +/** @deprecated - use `value !== null && typeof value === "object"` instead. */ +export function isObject(value: unknown): boolean { + return value !== null && typeof value === "object"; +} + +/** @deprecated - use `e instanceof Error` instead. */ +export function isError(e: unknown): boolean { + return e instanceof Error; +} + +/** @deprecated - use `typeof value === "function"` instead. */ +export function isFunction(value: unknown): boolean { + return typeof value === "function"; +} + +/** @deprecated Use util.types.RegExp() instead. */ +export function isRegExp(value: unknown): boolean { + return types.isRegExp(value); +} + +/** @deprecated Use util.types.isDate() instead. */ +export function isDate(value: unknown): boolean { + return types.isDate(value); +} + +/** @deprecated - use `value === null || (typeof value !== "object" && typeof value !== "function")` instead. */ +export function isPrimitive(value: unknown): boolean { + return ( + value === null || (typeof value !== "object" && typeof value !== "function") + ); +} + +/** @deprecated Use Buffer.isBuffer() instead. */ +export function isBuffer(value: unknown): boolean { + return Buffer.isBuffer(value); +} + +/** @deprecated Use Object.assign() instead. */ +export function _extend( + target: Record, + source: unknown, +): Record { + // Don't do anything if source isn't an object + if (source === null || typeof source !== "object") return target; + + const keys = Object.keys(source!); + let i = keys.length; + while (i--) { + target[keys[i]] = (source as Record)[keys[i]]; + } + return target; +} + +/** + * https://nodejs.org/api/util.html#util_util_inherits_constructor_superconstructor + * @param ctor Constructor function which needs to inherit the prototype. + * @param superCtor Constructor function to inherit prototype from. + */ +export function inherits( + ctor: new (...args: unknown[]) => T, + superCtor: new (...args: unknown[]) => U, +) { + if (ctor === undefined || ctor === null) { + throw new codes.ERR_INVALID_ARG_TYPE("ctor", "Function", ctor); + } + + if (superCtor === undefined || superCtor === null) { + throw new codes.ERR_INVALID_ARG_TYPE("superCtor", "Function", superCtor); + } + + if (superCtor.prototype === undefined) { + throw new codes.ERR_INVALID_ARG_TYPE( + "superCtor.prototype", + "Object", + superCtor.prototype, + ); + } + Object.defineProperty(ctor, "super_", { + value: superCtor, + writable: true, + configurable: true, + }); + Object.setPrototypeOf(ctor.prototype, superCtor.prototype); +} + +import { + _TextDecoder, + _TextEncoder, + getSystemErrorName, +} from "internal:deno_node/polyfills/_utils.ts"; + +/** The global TextDecoder */ +export type TextDecoder = import("./_utils.ts")._TextDecoder; +export const TextDecoder = _TextDecoder; + +/** The global TextEncoder */ +export type TextEncoder = import("./_utils.ts")._TextEncoder; +export const TextEncoder = _TextEncoder; + +function pad(n: number) { + return n.toString().padStart(2, "0"); +} + +const months = [ + "Jan", + "Feb", + "Mar", + "Apr", + "May", + "Jun", + "Jul", + "Aug", + "Sep", + "Oct", + "Nov", + "Dec", +]; + +/** + * @returns 26 Feb 16:19:34 + */ +function timestamp(): string { + const d = new Date(); + const t = [ + pad(d.getHours()), + pad(d.getMinutes()), + pad(d.getSeconds()), + ].join(":"); + return `${(d.getDate())} ${months[(d).getMonth()]} ${t}`; +} + +/** + * Log is just a thin wrapper to console.log that prepends a timestamp + * @deprecated + */ +// deno-lint-ignore no-explicit-any +export function log(...args: any[]) { + console.log("%s - %s", timestamp(), format(...args)); +} + +// Keep a list of deprecation codes that have been warned on so we only warn on +// each one once. +const codesWarned = new Set(); + +// Mark that a method should not be used. +// Returns a modified function which warns once by default. +// If --no-deprecation is set, then it is a no-op. +// deno-lint-ignore no-explicit-any +export function deprecate(fn: any, msg: string, code?: any) { + if (process.noDeprecation === true) { + return fn; + } + + if (code !== undefined) { + validateString(code, "code"); + } + + let warned = false; + // deno-lint-ignore no-explicit-any + function deprecated(this: any, ...args: any[]) { + if (!warned) { + warned = true; + if (code !== undefined) { + if (!codesWarned.has(code)) { + process.emitWarning(msg, "DeprecationWarning", code, deprecated); + codesWarned.add(code); + } + } else { + // deno-lint-ignore no-explicit-any + process.emitWarning(msg, "DeprecationWarning", deprecated as any); + } + } + if (new.target) { + return Reflect.construct(fn, args, new.target); + } + return Reflect.apply(fn, this, args); + } + + // The wrapper will keep the same prototype as fn to maintain prototype chain + Object.setPrototypeOf(deprecated, fn); + if (fn.prototype) { + // Setting this (rather than using Object.setPrototype, as above) ensures + // that calling the unwrapped constructor gives an instanceof the wrapped + // constructor. + deprecated.prototype = fn.prototype; + } + + return deprecated; +} + +export { getSystemErrorName, isDeepStrictEqual }; + +export default { + format, + formatWithOptions, + inspect, + isArray, + isBoolean, + isNull, + isNullOrUndefined, + isNumber, + isString, + isSymbol, + isUndefined, + isObject, + isError, + isFunction, + isRegExp, + isDate, + isPrimitive, + isBuffer, + _extend, + getSystemErrorName, + deprecate, + callbackify, + promisify, + inherits, + types, + stripVTControlCharacters, + TextDecoder, + TextEncoder, + log, + debuglog, + isDeepStrictEqual, +}; diff --git a/ext/node/polyfills/util/types.ts b/ext/node/polyfills/util/types.ts new file mode 100644 index 00000000000000..2ed7d16914c0ce --- /dev/null +++ b/ext/node/polyfills/util/types.ts @@ -0,0 +1,4 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. +import * as types from "internal:deno_node/polyfills/internal/util/types.ts"; +export * from "internal:deno_node/polyfills/internal/util/types.ts"; +export default { ...types }; diff --git a/ext/node/polyfills/v8.ts b/ext/node/polyfills/v8.ts new file mode 100644 index 00000000000000..c7875e6545c9a9 --- /dev/null +++ b/ext/node/polyfills/v8.ts @@ -0,0 +1,113 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. +// Copyright Joyent and Node contributors. All rights reserved. MIT license. + +import { notImplemented } from "internal:deno_node/polyfills/_utils.ts"; + +const { ops } = globalThis.__bootstrap.core; + +export function cachedDataVersionTag() { + return ops.op_v8_cached_data_version_tag(); +} +export function getHeapCodeStatistics() { + notImplemented("v8.getHeapCodeStatistics"); +} +export function getHeapSnapshot() { + notImplemented("v8.getHeapSnapshot"); +} +export function getHeapSpaceStatistics() { + notImplemented("v8.getHeapSpaceStatistics"); +} + +const buffer = new Float64Array(14); + +export function getHeapStatistics() { + ops.op_v8_get_heap_statistics(buffer); + + return { + total_heap_size: buffer[0], + total_heap_size_executable: buffer[1], + total_physical_size: buffer[2], + total_available_size: buffer[3], + used_heap_size: buffer[4], + heap_size_limit: buffer[5], + malloced_memory: buffer[6], + peak_malloced_memory: buffer[7], + does_zap_garbage: buffer[8], + number_of_native_contexts: buffer[9], + number_of_detached_contexts: buffer[10], + total_global_handles_size: buffer[11], + used_global_handles_size: buffer[12], + external_memory: buffer[13], + }; +} + +export function setFlagsFromString() { + notImplemented("v8.setFlagsFromString"); +} +export function stopCoverage() { + notImplemented("v8.stopCoverage"); +} +export function takeCoverage() { + notImplemented("v8.takeCoverage"); +} +export function writeHeapSnapshot() { + notImplemented("v8.writeHeapSnapshot"); +} +export function serialize() { + notImplemented("v8.serialize"); +} +export function deserialize() { + notImplemented("v8.deserialize"); +} +export class Serializer { + constructor() { + notImplemented("v8.Serializer.prototype.constructor"); + } +} +export class Deserializer { + constructor() { + notImplemented("v8.Deserializer.prototype.constructor"); + } +} +export class DefaultSerializer { + constructor() { + notImplemented("v8.DefaultSerializer.prototype.constructor"); + } +} +export class DefaultDeserializer { + constructor() { + notImplemented("v8.DefaultDeserializer.prototype.constructor"); + } +} +export const promiseHooks = { + onInit() { + notImplemented("v8.promiseHooks.onInit"); + }, + onSettled() { + notImplemented("v8.promiseHooks.onSetttled"); + }, + onBefore() { + notImplemented("v8.promiseHooks.onBefore"); + }, + createHook() { + notImplemented("v8.promiseHooks.createHook"); + }, +}; +export default { + cachedDataVersionTag, + getHeapCodeStatistics, + getHeapSnapshot, + getHeapSpaceStatistics, + getHeapStatistics, + setFlagsFromString, + stopCoverage, + takeCoverage, + writeHeapSnapshot, + serialize, + deserialize, + Serializer, + Deserializer, + DefaultSerializer, + DefaultDeserializer, + promiseHooks, +}; diff --git a/ext/node/polyfills/vm.ts b/ext/node/polyfills/vm.ts new file mode 100644 index 00000000000000..0d5de72c60da6a --- /dev/null +++ b/ext/node/polyfills/vm.ts @@ -0,0 +1,83 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. + +// deno-lint-ignore-file no-explicit-any + +import { notImplemented } from "internal:deno_node/polyfills/_utils.ts"; + +export class Script { + code: string; + constructor(code: string, _options = {}) { + this.code = `${code}`; + } + + runInThisContext(_options: any) { + return eval.call(globalThis, this.code); + } + + runInContext(_contextifiedObject: any, _options: any) { + notImplemented("Script.prototype.runInContext"); + } + + runInNewContext(_contextObject: any, _options: any) { + notImplemented("Script.prototype.runInNewContext"); + } + + createCachedData() { + notImplemented("Script.prototyp.createCachedData"); + } +} + +export function createContext(_contextObject: any, _options: any) { + notImplemented("createContext"); +} + +export function createScript(code: string, options: any) { + return new Script(code, options); +} + +export function runInContext( + _code: string, + _contextifiedObject: any, + _options: any, +) { + notImplemented("runInContext"); +} + +export function runInNewContext( + _code: string, + _contextObject: any, + _options: any, +) { + notImplemented("runInNewContext"); +} + +export function runInThisContext( + code: string, + options: any, +) { + return createScript(code, options).runInThisContext(options); +} + +export function isContext(_maybeContext: any) { + notImplemented("isContext"); +} + +export function compileFunction(_code: string, _params: any, _options: any) { + notImplemented("compileFunction"); +} + +export function measureMemory(_options: any) { + notImplemented("measureMemory"); +} + +export default { + Script, + createContext, + createScript, + runInContext, + runInNewContext, + runInThisContext, + isContext, + compileFunction, + measureMemory, +}; diff --git a/ext/node/polyfills/wasi.ts b/ext/node/polyfills/wasi.ts new file mode 100644 index 00000000000000..a68063ba90e8e2 --- /dev/null +++ b/ext/node/polyfills/wasi.ts @@ -0,0 +1,11 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. + +class Context { + constructor() { + throw new Error("Context is currently not supported"); + } +} + +export const WASI = Context; + +export default { WASI }; diff --git a/ext/node/polyfills/worker_threads.ts b/ext/node/polyfills/worker_threads.ts new file mode 100644 index 00000000000000..7bca5fc4efa011 --- /dev/null +++ b/ext/node/polyfills/worker_threads.ts @@ -0,0 +1,248 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. +// Copyright Joyent and Node contributors. All rights reserved. MIT license. + +import { resolve, toFileUrl } from "internal:deno_node/polyfills/path.ts"; +import { notImplemented } from "internal:deno_node/polyfills/_utils.ts"; +import { EventEmitter } from "internal:deno_node/polyfills/events.ts"; + +const environmentData = new Map(); +let threads = 0; + +export interface WorkerOptions { + // only for typings + argv?: unknown[]; + env?: Record; + execArgv?: string[]; + stdin?: boolean; + stdout?: boolean; + stderr?: boolean; + trackUnmanagedFds?: boolean; + resourceLimits?: { + maxYoungGenerationSizeMb?: number; + maxOldGenerationSizeMb?: number; + codeRangeSizeMb?: number; + stackSizeMb?: number; + }; + + eval?: boolean; + transferList?: Transferable[]; + workerData?: unknown; +} + +const kHandle = Symbol("kHandle"); +const PRIVATE_WORKER_THREAD_NAME = "$DENO_STD_NODE_WORKER_THREAD"; +class _Worker extends EventEmitter { + readonly threadId: number; + readonly resourceLimits: Required< + NonNullable + > = { + maxYoungGenerationSizeMb: -1, + maxOldGenerationSizeMb: -1, + codeRangeSizeMb: -1, + stackSizeMb: 4, + }; + private readonly [kHandle]: Worker; + + postMessage: Worker["postMessage"]; + + constructor(specifier: URL | string, options?: WorkerOptions) { + notImplemented("Worker"); + super(); + if (options?.eval === true) { + specifier = `data:text/javascript,${specifier}`; + } else if (typeof specifier === "string") { + // @ts-ignore This API is temporarily disabled + specifier = toFileUrl(resolve(specifier)); + } + const handle = this[kHandle] = new Worker( + specifier, + { + name: PRIVATE_WORKER_THREAD_NAME, + type: "module", + } as globalThis.WorkerOptions, // bypass unstable type error + ); + handle.addEventListener( + "error", + (event) => this.emit("error", event.error || event.message), + ); + handle.addEventListener( + "messageerror", + (event) => this.emit("messageerror", event.data), + ); + handle.addEventListener( + "message", + (event) => this.emit("message", event.data), + ); + handle.postMessage({ + environmentData, + threadId: (this.threadId = ++threads), + workerData: options?.workerData, + }, options?.transferList || []); + this.postMessage = handle.postMessage.bind(handle); + this.emit("online"); + } + + terminate() { + this[kHandle].terminate(); + this.emit("exit", 0); + } + + readonly getHeapSnapshot = () => + notImplemented("Worker.prototype.getHeapSnapshot"); + // fake performance + readonly performance = globalThis.performance; +} + +export const isMainThread = + // deno-lint-ignore no-explicit-any + (globalThis as any).name !== PRIVATE_WORKER_THREAD_NAME; + +// fake resourceLimits +export const resourceLimits = isMainThread ? {} : { + maxYoungGenerationSizeMb: 48, + maxOldGenerationSizeMb: 2048, + codeRangeSizeMb: 0, + stackSizeMb: 4, +}; + +const threadId = 0; +const workerData: unknown = null; + +// Like https://github.com/nodejs/node/blob/48655e17e1d84ba5021d7a94b4b88823f7c9c6cf/lib/internal/event_target.js#L611 +interface NodeEventTarget extends + Pick< + EventEmitter, + "eventNames" | "listenerCount" | "emit" | "removeAllListeners" + > { + setMaxListeners(n: number): void; + getMaxListeners(): number; + // deno-lint-ignore no-explicit-any + off(eventName: string, listener: (...args: any[]) => void): NodeEventTarget; + // deno-lint-ignore no-explicit-any + on(eventName: string, listener: (...args: any[]) => void): NodeEventTarget; + // deno-lint-ignore no-explicit-any + once(eventName: string, listener: (...args: any[]) => void): NodeEventTarget; + addListener: NodeEventTarget["on"]; + removeListener: NodeEventTarget["off"]; +} + +type ParentPort = typeof self & NodeEventTarget; + +// deno-lint-ignore no-explicit-any +const parentPort: ParentPort = null as any; + +/* +if (!isMainThread) { + // deno-lint-ignore no-explicit-any + delete (globalThis as any).name; + // deno-lint-ignore no-explicit-any + const listeners = new WeakMap<(...args: any[]) => void, (ev: any) => any>(); + + parentPort = self as ParentPort; + parentPort.off = parentPort.removeListener = function ( + this: ParentPort, + name, + listener, + ) { + this.removeEventListener(name, listeners.get(listener)!); + listeners.delete(listener); + return this; + }; + parentPort.on = parentPort.addListener = function ( + this: ParentPort, + name, + listener, + ) { + // deno-lint-ignore no-explicit-any + const _listener = (ev: any) => listener(ev.data); + listeners.set(listener, _listener); + this.addEventListener(name, _listener); + return this; + }; + parentPort.once = function (this: ParentPort, name, listener) { + // deno-lint-ignore no-explicit-any + const _listener = (ev: any) => listener(ev.data); + listeners.set(listener, _listener); + this.addEventListener(name, _listener); + return this; + }; + + // mocks + parentPort.setMaxListeners = () => {}; + parentPort.getMaxListeners = () => Infinity; + parentPort.eventNames = () => [""]; + parentPort.listenerCount = () => 0; + + parentPort.emit = () => notImplemented("parentPort.emit"); + parentPort.removeAllListeners = () => + notImplemented("parentPort.removeAllListeners"); + + // Receive startup message + [{ threadId, workerData, environmentData }] = await once( + parentPort, + "message", + ); + + // alias + parentPort.addEventListener("offline", () => { + parentPort.emit("close"); + }); +} +*/ + +export function getEnvironmentData(key: unknown) { + notImplemented("getEnvironmentData"); + return environmentData.get(key); +} + +export function setEnvironmentData(key: unknown, value?: unknown) { + notImplemented("setEnvironmentData"); + if (value === undefined) { + environmentData.delete(key); + } else { + environmentData.set(key, value); + } +} + +// deno-lint-ignore no-explicit-any +const _MessagePort: typeof MessagePort = (globalThis as any).MessagePort; +const _MessageChannel: typeof MessageChannel = + // deno-lint-ignore no-explicit-any + (globalThis as any).MessageChannel; +export const BroadcastChannel = globalThis.BroadcastChannel; +export const SHARE_ENV = Symbol.for("nodejs.worker_threads.SHARE_ENV"); +export function markAsUntransferable() { + notImplemented("markAsUntransferable"); +} +export function moveMessagePortToContext() { + notImplemented("moveMessagePortToContext"); +} +export function receiveMessageOnPort() { + notImplemented("receiveMessageOnPort"); +} +export { + _MessageChannel as MessageChannel, + _MessagePort as MessagePort, + _Worker as Worker, + parentPort, + threadId, + workerData, +}; + +export default { + markAsUntransferable, + moveMessagePortToContext, + receiveMessageOnPort, + MessagePort: _MessagePort, + MessageChannel: _MessageChannel, + BroadcastChannel, + Worker: _Worker, + getEnvironmentData, + setEnvironmentData, + SHARE_ENV, + threadId, + workerData, + resourceLimits, + parentPort, + isMainThread, +}; diff --git a/ext/node/polyfills/zlib.ts b/ext/node/polyfills/zlib.ts new file mode 100644 index 00000000000000..ac52b4d4a54ee3 --- /dev/null +++ b/ext/node/polyfills/zlib.ts @@ -0,0 +1,154 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. +import { notImplemented } from "internal:deno_node/polyfills/_utils.ts"; +import { zlib as constants } from "internal:deno_node/polyfills/internal_binding/constants.ts"; +import { + codes, + createDeflate, + createDeflateRaw, + createGunzip, + createGzip, + createInflate, + createInflateRaw, + createUnzip, + Deflate, + deflate, + DeflateRaw, + deflateRaw, + deflateRawSync, + deflateSync, + Gunzip, + gunzip, + gunzipSync, + Gzip, + gzip, + gzipSync, + Inflate, + inflate, + InflateRaw, + inflateRaw, + inflateRawSync, + inflateSync, + Unzip, + unzip, + unzipSync, +} from "internal:deno_node/polyfills/_zlib.mjs"; +export class Options { + constructor() { + notImplemented("Options.prototype.constructor"); + } +} +export class BrotliOptions { + constructor() { + notImplemented("BrotliOptions.prototype.constructor"); + } +} +export class BrotliCompress { + constructor() { + notImplemented("BrotliCompress.prototype.constructor"); + } +} +export class BrotliDecompress { + constructor() { + notImplemented("BrotliDecompress.prototype.constructor"); + } +} +export class ZlibBase { + constructor() { + notImplemented("ZlibBase.prototype.constructor"); + } +} +export { constants }; +export function createBrotliCompress() { + notImplemented("createBrotliCompress"); +} +export function createBrotliDecompress() { + notImplemented("createBrotliDecompress"); +} +export function brotliCompress() { + notImplemented("brotliCompress"); +} +export function brotliCompressSync() { + notImplemented("brotliCompressSync"); +} +export function brotliDecompress() { + notImplemented("brotliDecompress"); +} +export function brotliDecompressSync() { + notImplemented("brotliDecompressSync"); +} + +export default { + Options, + BrotliOptions, + BrotliCompress, + BrotliDecompress, + Deflate, + DeflateRaw, + Gunzip, + Gzip, + Inflate, + InflateRaw, + Unzip, + ZlibBase, + constants, + codes, + createBrotliCompress, + createBrotliDecompress, + createDeflate, + createDeflateRaw, + createGunzip, + createGzip, + createInflate, + createInflateRaw, + createUnzip, + brotliCompress, + brotliCompressSync, + brotliDecompress, + brotliDecompressSync, + deflate, + deflateSync, + deflateRaw, + deflateRawSync, + gunzip, + gunzipSync, + gzip, + gzipSync, + inflate, + inflateSync, + inflateRaw, + inflateRawSync, + unzip, + unzipSync, +}; + +export { + codes, + createDeflate, + createDeflateRaw, + createGunzip, + createGzip, + createInflate, + createInflateRaw, + createUnzip, + Deflate, + deflate, + DeflateRaw, + deflateRaw, + deflateRawSync, + deflateSync, + Gunzip, + gunzip, + gunzipSync, + Gzip, + gzip, + gzipSync, + Inflate, + inflate, + InflateRaw, + inflateRaw, + inflateRawSync, + inflateSync, + Unzip, + unzip, + unzipSync, +}; diff --git a/ext/node/v8.rs b/ext/node/v8.rs new file mode 100644 index 00000000000000..5307f71077bb97 --- /dev/null +++ b/ext/node/v8.rs @@ -0,0 +1,29 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. +use deno_core::op; +use deno_core::v8; + +#[op] +fn op_v8_cached_data_version_tag() -> u32 { + v8::script_compiler::cached_data_version_tag() +} + +#[op(v8)] +fn op_v8_get_heap_statistics(scope: &mut v8::HandleScope, buffer: &mut [f64]) { + let mut stats = v8::HeapStatistics::default(); + scope.get_heap_statistics(&mut stats); + + buffer[0] = stats.total_heap_size() as f64; + buffer[1] = stats.total_heap_size_executable() as f64; + buffer[2] = stats.total_physical_size() as f64; + buffer[3] = stats.total_available_size() as f64; + buffer[4] = stats.used_heap_size() as f64; + buffer[5] = stats.heap_size_limit() as f64; + buffer[6] = stats.malloced_memory() as f64; + buffer[7] = stats.peak_malloced_memory() as f64; + buffer[8] = stats.does_zap_garbage() as f64; + buffer[9] = stats.number_of_native_contexts() as f64; + buffer[10] = stats.number_of_detached_contexts() as f64; + buffer[11] = stats.total_global_handles_size() as f64; + buffer[12] = stats.used_global_handles_size() as f64; + buffer[13] = stats.external_memory() as f64; +} diff --git a/ext/node/winerror.rs b/ext/node/winerror.rs new file mode 100644 index 00000000000000..3a1f5be201ba9e --- /dev/null +++ b/ext/node/winerror.rs @@ -0,0 +1,17005 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. +/* Copyright Joyent, Inc. and other Node contributors. All rights reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to + * deal in the Software without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + * IN THE SOFTWARE. + */ + +// This module ports: +// - https://github.com/libuv/libuv/blob/master/src/win/error.c +#![allow(unused)] + +use deno_core::op; + +#[op] +fn op_node_sys_to_uv_error(err: i32) -> String { + let uv_err = match err { + ERROR_ACCESS_DENIED => "EACCES", + ERROR_NOACCESS => "EACCES", + WSAEACCES => "EACCES", + ERROR_CANT_ACCESS_FILE => "EACCES", + ERROR_ADDRESS_ALREADY_ASSOCIATED => "EADDRINUSE", + WSAEADDRINUSE => "EADDRINUSE", + WSAEADDRNOTAVAIL => "EADDRNOTAVAIL", + WSAEAFNOSUPPORT => "EAFNOSUPPORT", + WSAEWOULDBLOCK => "EAGAIN", + WSAEALREADY => "EALREADY", + ERROR_INVALID_FLAGS => "EBADF", + ERROR_INVALID_HANDLE => "EBADF", + ERROR_LOCK_VIOLATION => "EBUSY", + ERROR_PIPE_BUSY => "EBUSY", + ERROR_SHARING_VIOLATION => "EBUSY", + ERROR_OPERATION_ABORTED => "ECANCELED", + WSAEINTR => "ECANCELED", + ERROR_NO_UNICODE_TRANSLATION => "ECHARSET", + ERROR_CONNECTION_ABORTED => "ECONNABORTED", + WSAECONNABORTED => "ECONNABORTED", + ERROR_CONNECTION_REFUSED => "ECONNREFUSED", + WSAECONNREFUSED => "ECONNREFUSED", + ERROR_NETNAME_DELETED => "ECONNRESET", + WSAECONNRESET => "ECONNRESET", + ERROR_ALREADY_EXISTS => "EEXIST", + ERROR_FILE_EXISTS => "EEXIST", + ERROR_BUFFER_OVERFLOW => "EFAULT", + WSAEFAULT => "EFAULT", + ERROR_HOST_UNREACHABLE => "EHOSTUNREACH", + WSAEHOSTUNREACH => "EHOSTUNREACH", + ERROR_INSUFFICIENT_BUFFER => "EINVAL", + ERROR_INVALID_DATA => "EINVAL", + ERROR_INVALID_NAME => "EINVAL", + ERROR_INVALID_PARAMETER => "EINVAL", + WSAEINVAL => "EINVAL", + WSAEPFNOSUPPORT => "EINVAL", + ERROR_BEGINNING_OF_MEDIA => "EIO", + ERROR_BUS_RESET => "EIO", + ERROR_CRC => "EIO", + ERROR_DEVICE_DOOR_OPEN => "EIO", + ERROR_DEVICE_REQUIRES_CLEANING => "EIO", + ERROR_DISK_CORRUPT => "EIO", + ERROR_EOM_OVERFLOW => "EIO", + ERROR_FILEMARK_DETECTED => "EIO", + ERROR_GEN_FAILURE => "EIO", + ERROR_INVALID_BLOCK_LENGTH => "EIO", + ERROR_IO_DEVICE => "EIO", + ERROR_NO_DATA_DETECTED => "EIO", + ERROR_NO_SIGNAL_SENT => "EIO", + ERROR_OPEN_FAILED => "EIO", + ERROR_SETMARK_DETECTED => "EIO", + ERROR_SIGNAL_REFUSED => "EIO", + WSAEISCONN => "EISCONN", + ERROR_CANT_RESOLVE_FILENAME => "ELOOP", + ERROR_TOO_MANY_OPEN_FILES => "EMFILE", + WSAEMFILE => "EMFILE", + WSAEMSGSIZE => "EMSGSIZE", + ERROR_FILENAME_EXCED_RANGE => "ENAMETOOLONG", + ERROR_NETWORK_UNREACHABLE => "ENETUNREACH", + WSAENETUNREACH => "ENETUNREACH", + WSAENOBUFS => "ENOBUFS", + ERROR_BAD_PATHNAME => "ENOENT", + ERROR_DIRECTORY => "ENOTDIR", + ERROR_ENVVAR_NOT_FOUND => "ENOENT", + ERROR_FILE_NOT_FOUND => "ENOENT", + ERROR_INVALID_DRIVE => "ENOENT", + ERROR_INVALID_REPARSE_DATA => "ENOENT", + ERROR_MOD_NOT_FOUND => "ENOENT", + ERROR_PATH_NOT_FOUND => "ENOENT", + WSAHOST_NOT_FOUND => "ENOENT", + WSANO_DATA => "ENOENT", + ERROR_NOT_ENOUGH_MEMORY => "ENOMEM", + ERROR_OUTOFMEMORY => "ENOMEM", + ERROR_CANNOT_MAKE => "ENOSPC", + ERROR_DISK_FULL => "ENOSPC", + ERROR_EA_TABLE_FULL => "ENOSPC", + ERROR_END_OF_MEDIA => "ENOSPC", + ERROR_HANDLE_DISK_FULL => "ENOSPC", + ERROR_NOT_CONNECTED => "ENOTCONN", + WSAENOTCONN => "ENOTCONN", + ERROR_DIR_NOT_EMPTY => "ENOTEMPTY", + WSAENOTSOCK => "ENOTSOCK", + ERROR_NOT_SUPPORTED => "ENOTSUP", + ERROR_BROKEN_PIPE => "EOF", + ERROR_PRIVILEGE_NOT_HELD => "EPERM", + ERROR_BAD_PIPE => "EPIPE", + ERROR_NO_DATA => "EPIPE", + ERROR_PIPE_NOT_CONNECTED => "EPIPE", + WSAESHUTDOWN => "EPIPE", + WSAEPROTONOSUPPORT => "EPROTONOSUPPORT", + ERROR_WRITE_PROTECT => "EROFS", + ERROR_SEM_TIMEOUT => "ETIMEDOUT", + WSAETIMEDOUT => "ETIMEDOUT", + ERROR_NOT_SAME_DEVICE => "EXDEV", + ERROR_INVALID_FUNCTION => "EISDIR", + ERROR_META_EXPANSION_TOO_LONG => "E2BIG", + WSAESOCKTNOSUPPORT => "ESOCKTNOSUPPORT", + _ => "UNKNOWN", + }; + uv_err.to_string() +} + +/*++ + +Copyright (c) Microsoft Corporation. All rights reserved. + +You may only use this code if you agree to the terms of the Windows Research Kernel Source Code License agreement (see License.txt). +If you do not agree to the terms, do not use the code. + +Module: + + winderror.h + +Abstract: + + Win32 API functions + +--*/ + +// This module ports: +// - https://raw.githubusercontent.com/mic101/windows/master/WRK-v1.2/public/sdk/inc/winerror.h + +// MessageId: ERROR_SUCCESS +// +// MessageText: +// +// The operation completed successfully. +// +pub const ERROR_SUCCESS: i32 = 0; + +// +// MessageId: ERROR_INVALID_FUNCTION +// +// MessageText: +// +// Incorrect function. +// +pub const ERROR_INVALID_FUNCTION: i32 = 1; // dderror + +// +// MessageId: ERROR_FILE_NOT_FOUND +// +// MessageText: +// +// The system cannot find the file specified. +// +pub const ERROR_FILE_NOT_FOUND: i32 = 2; + +// +// MessageId: ERROR_PATH_NOT_FOUND +// +// MessageText: +// +// The system cannot find the path specified. +// +pub const ERROR_PATH_NOT_FOUND: i32 = 3; + +// +// MessageId: ERROR_TOO_MANY_OPEN_FILES +// +// MessageText: +// +// The system cannot open the file. +// +pub const ERROR_TOO_MANY_OPEN_FILES: i32 = 4; + +// +// MessageId: ERROR_ACCESS_DENIED +// +// MessageText: +// +// Access is denied. +// +pub const ERROR_ACCESS_DENIED: i32 = 5; + +// +// MessageId: ERROR_INVALID_HANDLE +// +// MessageText: +// +// The handle is invalid. +// +pub const ERROR_INVALID_HANDLE: i32 = 6; + +// +// MessageId: ERROR_ARENA_TRASHED +// +// MessageText: +// +// The storage control blocks were destroyed. +// +pub const ERROR_ARENA_TRASHED: i32 = 7; + +// +// MessageId: ERROR_NOT_ENOUGH_MEMORY +// +// MessageText: +// +// Not enough storage is available to process this command. +// +pub const ERROR_NOT_ENOUGH_MEMORY: i32 = 8; // dderror + +// +// MessageId: ERROR_INVALID_BLOCK +// +// MessageText: +// +// The storage control block address is invalid. +// +pub const ERROR_INVALID_BLOCK: i32 = 9; + +// +// MessageId: ERROR_BAD_ENVIRONMENT +// +// MessageText: +// +// The environment is incorrect. +// +pub const ERROR_BAD_ENVIRONMENT: i32 = 10; + +// +// MessageId: ERROR_BAD_FORMAT +// +// MessageText: +// +// An attempt was made to load a program with an incorrect format. +// +pub const ERROR_BAD_FORMAT: i32 = 11; + +// +// MessageId: ERROR_INVALID_ACCESS +// +// MessageText: +// +// The access code is invalid. +// +pub const ERROR_INVALID_ACCESS: i32 = 12; + +// +// MessageId: ERROR_INVALID_DATA +// +// MessageText: +// +// The data is invalid. +// +pub const ERROR_INVALID_DATA: i32 = 13; + +// +// MessageId: ERROR_OUTOFMEMORY +// +// MessageText: +// +// Not enough storage is available to complete this operation. +// +pub const ERROR_OUTOFMEMORY: i32 = 14; + +// +// MessageId: ERROR_INVALID_DRIVE +// +// MessageText: +// +// The system cannot find the drive specified. +// +pub const ERROR_INVALID_DRIVE: i32 = 15; + +// +// MessageId: ERROR_CURRENT_DIRECTORY +// +// MessageText: +// +// The directory cannot be removed. +// +pub const ERROR_CURRENT_DIRECTORY: i32 = 16; + +// +// MessageId: ERROR_NOT_SAME_DEVICE +// +// MessageText: +// +// The system cannot move the file to a different disk drive. +// +pub const ERROR_NOT_SAME_DEVICE: i32 = 17; + +// +// MessageId: ERROR_NO_MORE_FILES +// +// MessageText: +// +// There are no more files. +// +pub const ERROR_NO_MORE_FILES: i32 = 18; + +// +// MessageId: ERROR_WRITE_PROTECT +// +// MessageText: +// +// The media is write protected. +// +pub const ERROR_WRITE_PROTECT: i32 = 19; + +// +// MessageId: ERROR_BAD_UNIT +// +// MessageText: +// +// The system cannot find the device specified. +// +pub const ERROR_BAD_UNIT: i32 = 20; + +// +// MessageId: ERROR_NOT_READY +// +// MessageText: +// +// The device is not ready. +// +pub const ERROR_NOT_READY: i32 = 21; + +// +// MessageId: ERROR_BAD_COMMAND +// +// MessageText: +// +// The device does not recognize the command. +// +pub const ERROR_BAD_COMMAND: i32 = 22; + +// +// MessageId: ERROR_CRC +// +// MessageText: +// +// Data error (cyclic redundancy check). +// +pub const ERROR_CRC: i32 = 23; + +// +// MessageId: ERROR_BAD_LENGTH +// +// MessageText: +// +// The program issued a command but the command length is incorrect. +// +pub const ERROR_BAD_LENGTH: i32 = 24; + +// +// MessageId: ERROR_SEEK +// +// MessageText: +// +// The drive cannot locate a specific area or track on the disk. +// +pub const ERROR_SEEK: i32 = 25; + +// +// MessageId: ERROR_NOT_DOS_DISK +// +// MessageText: +// +// The specified disk or diskette cannot be accessed. +// +pub const ERROR_NOT_DOS_DISK: i32 = 26; + +// +// MessageId: ERROR_SECTOR_NOT_FOUND +// +// MessageText: +// +// The drive cannot find the sector requested. +// +pub const ERROR_SECTOR_NOT_FOUND: i32 = 27; + +// +// MessageId: ERROR_OUT_OF_PAPER +// +// MessageText: +// +// The printer is out of paper. +// +pub const ERROR_OUT_OF_PAPER: i32 = 28; + +// +// MessageId: ERROR_WRITE_FAULT +// +// MessageText: +// +// The system cannot write to the specified device. +// +pub const ERROR_WRITE_FAULT: i32 = 29; + +// +// MessageId: ERROR_READ_FAULT +// +// MessageText: +// +// The system cannot read from the specified device. +// +pub const ERROR_READ_FAULT: i32 = 30; + +// +// MessageId: ERROR_GEN_FAILURE +// +// MessageText: +// +// A device attached to the system is not functioning. +// +pub const ERROR_GEN_FAILURE: i32 = 31; + +// +// MessageId: ERROR_SHARING_VIOLATION +// +// MessageText: +// +// The process cannot access the file because it is being used by another process. +// +pub const ERROR_SHARING_VIOLATION: i32 = 32; + +// +// MessageId: ERROR_LOCK_VIOLATION +// +// MessageText: +// +// The process cannot access the file because another process has locked a portion of the file. +// +pub const ERROR_LOCK_VIOLATION: i32 = 33; + +// +// MessageId: ERROR_WRONG_DISK +// +// MessageText: +// +// The wrong diskette is in the drive. +// Insert %2 (Volume Serial Number: %3) into drive %1. +// +pub const ERROR_WRONG_DISK: i32 = 34; + +// +// MessageId: ERROR_SHARING_BUFFER_EXCEEDED +// +// MessageText: +// +// Too many files opened for sharing. +// +pub const ERROR_SHARING_BUFFER_EXCEEDED: i32 = 36; + +// +// MessageId: ERROR_HANDLE_EOF +// +// MessageText: +// +// Reached the end of the file. +// +pub const ERROR_HANDLE_EOF: i32 = 38; + +// +// MessageId: ERROR_HANDLE_DISK_FULL +// +// MessageText: +// +// The disk is full. +// +pub const ERROR_HANDLE_DISK_FULL: i32 = 39; + +// +// MessageId: ERROR_NOT_SUPPORTED +// +// MessageText: +// +// The request is not supported. +// +pub const ERROR_NOT_SUPPORTED: i32 = 50; + +// +// MessageId: ERROR_REM_NOT_LIST +// +// MessageText: +// +// Windows cannot find the network path. Verify that the network path is correct and the destination computer is not busy or turned off. If Windows still cannot find the network path, contact your network administrator. +// +pub const ERROR_REM_NOT_LIST: i32 = 51; + +// +// MessageId: ERROR_DUP_NAME +// +// MessageText: +// +// You were not connected because a duplicate name exists on the network. Go to System in Control Panel to change the computer name and try again. +// +pub const ERROR_DUP_NAME: i32 = 52; + +// +// MessageId: ERROR_BAD_NETPATH +// +// MessageText: +// +// The network path was not found. +// +pub const ERROR_BAD_NETPATH: i32 = 53; + +// +// MessageId: ERROR_NETWORK_BUSY +// +// MessageText: +// +// The network is busy. +// +pub const ERROR_NETWORK_BUSY: i32 = 54; + +// +// MessageId: ERROR_DEV_NOT_EXIST +// +// MessageText: +// +// The specified network resource or device is no longer available. +// +pub const ERROR_DEV_NOT_EXIST: i32 = 55; // dderror + +// +// MessageId: ERROR_TOO_MANY_CMDS +// +// MessageText: +// +// The network BIOS command limit has been reached. +// +pub const ERROR_TOO_MANY_CMDS: i32 = 56; + +// +// MessageId: ERROR_ADAP_HDW_ERR +// +// MessageText: +// +// A network adapter hardware error occurred. +// +pub const ERROR_ADAP_HDW_ERR: i32 = 57; + +// +// MessageId: ERROR_BAD_NET_RESP +// +// MessageText: +// +// The specified server cannot perform the requested operation. +// +pub const ERROR_BAD_NET_RESP: i32 = 58; + +// +// MessageId: ERROR_UNEXP_NET_ERR +// +// MessageText: +// +// An unexpected network error occurred. +// +pub const ERROR_UNEXP_NET_ERR: i32 = 59; + +// +// MessageId: ERROR_BAD_REM_ADAP +// +// MessageText: +// +// The remote adapter is not compatible. +// +pub const ERROR_BAD_REM_ADAP: i32 = 60; + +// +// MessageId: ERROR_PRINTQ_FULL +// +// MessageText: +// +// The printer queue is full. +// +pub const ERROR_PRINTQ_FULL: i32 = 61; + +// +// MessageId: ERROR_NO_SPOOL_SPACE +// +// MessageText: +// +// Space to store the file waiting to be printed is not available on the server. +// +pub const ERROR_NO_SPOOL_SPACE: i32 = 62; + +// +// MessageId: ERROR_PRINT_CANCELLED +// +// MessageText: +// +// Your file waiting to be printed was deleted. +// +pub const ERROR_PRINT_CANCELLED: i32 = 63; + +// +// MessageId: ERROR_NETNAME_DELETED +// +// MessageText: +// +// The specified network name is no longer available. +// +pub const ERROR_NETNAME_DELETED: i32 = 64; + +// +// MessageId: ERROR_NETWORK_ACCESS_DENIED +// +// MessageText: +// +// Network access is denied. +// +pub const ERROR_NETWORK_ACCESS_DENIED: i32 = 65; + +// +// MessageId: ERROR_BAD_DEV_TYPE +// +// MessageText: +// +// The network resource type is not correct. +// +pub const ERROR_BAD_DEV_TYPE: i32 = 66; + +// +// MessageId: ERROR_BAD_NET_NAME +// +// MessageText: +// +// The network name cannot be found. +// +pub const ERROR_BAD_NET_NAME: i32 = 67; + +// +// MessageId: ERROR_TOO_MANY_NAMES +// +// MessageText: +// +// The name limit for the local computer network adapter card was exceeded. +// +pub const ERROR_TOO_MANY_NAMES: i32 = 68; + +// +// MessageId: ERROR_TOO_MANY_SESS +// +// MessageText: +// +// The network BIOS session limit was exceeded. +// +pub const ERROR_TOO_MANY_SESS: i32 = 69; + +// +// MessageId: ERROR_SHARING_PAUSED +// +// MessageText: +// +// The remote server has been paused or is in the process of being started. +// +pub const ERROR_SHARING_PAUSED: i32 = 70; + +// +// MessageId: ERROR_REQ_NOT_ACCEP +// +// MessageText: +// +// No more connections can be made to this remote computer at this time because there are already as many connections as the computer can accept. +// +pub const ERROR_REQ_NOT_ACCEP: i32 = 71; + +// +// MessageId: ERROR_REDIR_PAUSED +// +// MessageText: +// +// The specified printer or disk device has been paused. +// +pub const ERROR_REDIR_PAUSED: i32 = 72; + +// +// MessageId: ERROR_FILE_EXISTS +// +// MessageText: +// +// The file exists. +// +pub const ERROR_FILE_EXISTS: i32 = 80; + +// +// MessageId: ERROR_CANNOT_MAKE +// +// MessageText: +// +// The directory or file cannot be created. +// +pub const ERROR_CANNOT_MAKE: i32 = 82; + +// +// MessageId: ERROR_FAIL_I24 +// +// MessageText: +// +// Fail on INT 24. +// +pub const ERROR_FAIL_I24: i32 = 83; + +// +// MessageId: ERROR_OUT_OF_STRUCTURES +// +// MessageText: +// +// Storage to process this request is not available. +// +pub const ERROR_OUT_OF_STRUCTURES: i32 = 84; + +// +// MessageId: ERROR_ALREADY_ASSIGNED +// +// MessageText: +// +// The local device name is already in use. +// +pub const ERROR_ALREADY_ASSIGNED: i32 = 85; + +// +// MessageId: ERROR_INVALID_PASSWORD +// +// MessageText: +// +// The specified network password is not correct. +// +pub const ERROR_INVALID_PASSWORD: i32 = 86; + +// +// MessageId: ERROR_INVALID_PARAMETER +// +// MessageText: +// +// The parameter is incorrect. +// +pub const ERROR_INVALID_PARAMETER: i32 = 87; // dderror + +// +// MessageId: ERROR_NET_WRITE_FAULT +// +// MessageText: +// +// A write fault occurred on the network. +// +pub const ERROR_NET_WRITE_FAULT: i32 = 88; + +// +// MessageId: ERROR_NO_PROC_SLOTS +// +// MessageText: +// +// The system cannot start another process at this time. +// +pub const ERROR_NO_PROC_SLOTS: i32 = 89; + +// +// MessageId: ERROR_TOO_MANY_SEMAPHORES +// +// MessageText: +// +// Cannot create another system semaphore. +// +pub const ERROR_TOO_MANY_SEMAPHORES: i32 = 100; + +// +// MessageId: ERROR_EXCL_SEM_ALREADY_OWNED +// +// MessageText: +// +// The exclusive semaphore is owned by another process. +// +pub const ERROR_EXCL_SEM_ALREADY_OWNED: i32 = 101; + +// +// MessageId: ERROR_SEM_IS_SET +// +// MessageText: +// +// The semaphore is set and cannot be closed. +// +pub const ERROR_SEM_IS_SET: i32 = 102; + +// +// MessageId: ERROR_TOO_MANY_SEM_REQUESTS +// +// MessageText: +// +// The semaphore cannot be set again. +// +pub const ERROR_TOO_MANY_SEM_REQUESTS: i32 = 103; + +// +// MessageId: ERROR_INVALID_AT_INTERRUPT_TIME +// +// MessageText: +// +// Cannot request exclusive semaphores at interrupt time. +// +pub const ERROR_INVALID_AT_INTERRUPT_TIME: i32 = 104; + +// +// MessageId: ERROR_SEM_OWNER_DIED +// +// MessageText: +// +// The previous ownership of this semaphore has ended. +// +pub const ERROR_SEM_OWNER_DIED: i32 = 105; + +// +// MessageId: ERROR_SEM_USER_LIMIT +// +// MessageText: +// +// Insert the diskette for drive %1. +// +pub const ERROR_SEM_USER_LIMIT: i32 = 106; + +// +// MessageId: ERROR_DISK_CHANGE +// +// MessageText: +// +// The program stopped because an alternate diskette was not inserted. +// +pub const ERROR_DISK_CHANGE: i32 = 107; + +// +// MessageId: ERROR_DRIVE_LOCKED +// +// MessageText: +// +// The disk is in use or locked by another process. +// +pub const ERROR_DRIVE_LOCKED: i32 = 108; + +// +// MessageId: ERROR_BROKEN_PIPE +// +// MessageText: +// +// The pipe has been ended. +// +pub const ERROR_BROKEN_PIPE: i32 = 109; + +// +// MessageId: ERROR_OPEN_FAILED +// +// MessageText: +// +// The system cannot open the device or file specified. +// +pub const ERROR_OPEN_FAILED: i32 = 110; + +// +// MessageId: ERROR_BUFFER_OVERFLOW +// +// MessageText: +// +// The file name is too long. +// +pub const ERROR_BUFFER_OVERFLOW: i32 = 111; + +// +// MessageId: ERROR_DISK_FULL +// +// MessageText: +// +// There is not enough space on the disk. +// +pub const ERROR_DISK_FULL: i32 = 112; + +// +// MessageId: ERROR_NO_MORE_SEARCH_HANDLES +// +// MessageText: +// +// No more internal file identifiers available. +// +pub const ERROR_NO_MORE_SEARCH_HANDLES: i32 = 113; + +// +// MessageId: ERROR_INVALID_TARGET_HANDLE +// +// MessageText: +// +// The target internal file identifier is incorrect. +// +pub const ERROR_INVALID_TARGET_HANDLE: i32 = 114; + +// +// MessageId: ERROR_INVALID_CATEGORY +// +// MessageText: +// +// The IOCTL call made by the application program is not correct. +// +pub const ERROR_INVALID_CATEGORY: i32 = 117; + +// +// MessageId: ERROR_INVALID_VERIFY_SWITCH +// +// MessageText: +// +// The verify-on-write switch parameter value is not correct. +// +pub const ERROR_INVALID_VERIFY_SWITCH: i32 = 118; + +// +// MessageId: ERROR_BAD_DRIVER_LEVEL +// +// MessageText: +// +// The system does not support the command requested. +// +pub const ERROR_BAD_DRIVER_LEVEL: i32 = 119; + +// +// MessageId: ERROR_CALL_NOT_IMPLEMENTED +// +// MessageText: +// +// This function is not supported on this system. +// +pub const ERROR_CALL_NOT_IMPLEMENTED: i32 = 120; + +// +// MessageId: ERROR_SEM_TIMEOUT +// +// MessageText: +// +// The semaphore timeout period has expired. +// +pub const ERROR_SEM_TIMEOUT: i32 = 121; + +// +// MessageId: ERROR_INSUFFICIENT_BUFFER +// +// MessageText: +// +// The data area passed to a system call is too small. +// +pub const ERROR_INSUFFICIENT_BUFFER: i32 = 122; // dderror + +// +// MessageId: ERROR_INVALID_NAME +// +// MessageText: +// +// The filename, directory name, or volume label syntax is incorrect. +// +pub const ERROR_INVALID_NAME: i32 = 123; // dderror + +// +// MessageId: ERROR_INVALID_LEVEL +// +// MessageText: +// +// The system call level is not correct. +// +pub const ERROR_INVALID_LEVEL: i32 = 124; + +// +// MessageId: ERROR_NO_VOLUME_LABEL +// +// MessageText: +// +// The disk has no volume label. +// +pub const ERROR_NO_VOLUME_LABEL: i32 = 125; + +// +// MessageId: ERROR_MOD_NOT_FOUND +// +// MessageText: +// +// The specified module could not be found. +// +pub const ERROR_MOD_NOT_FOUND: i32 = 126; + +// +// MessageId: ERROR_PROC_NOT_FOUND +// +// MessageText: +// +// The specified procedure could not be found. +// +pub const ERROR_PROC_NOT_FOUND: i32 = 127; + +// +// MessageId: ERROR_WAIT_NO_CHILDREN +// +// MessageText: +// +// There are no child processes to wait for. +// +pub const ERROR_WAIT_NO_CHILDREN: i32 = 128; + +// +// MessageId: ERROR_CHILD_NOT_COMPLETE +// +// MessageText: +// +// The %1 application cannot be run in Win32 mode. +// +pub const ERROR_CHILD_NOT_COMPLETE: i32 = 129; + +// +// MessageId: ERROR_DIRECT_ACCESS_HANDLE +// +// MessageText: +// +// Attempt to use a file handle to an open disk partition for an operation other than raw disk I/O. +// +pub const ERROR_DIRECT_ACCESS_HANDLE: i32 = 130; + +// +// MessageId: ERROR_NEGATIVE_SEEK +// +// MessageText: +// +// An attempt was made to move the file pointer before the beginning of the file. +// +pub const ERROR_NEGATIVE_SEEK: i32 = 131; + +// +// MessageId: ERROR_SEEK_ON_DEVICE +// +// MessageText: +// +// The file pointer cannot be set on the specified device or file. +// +pub const ERROR_SEEK_ON_DEVICE: i32 = 132; + +// +// MessageId: ERROR_IS_JOIN_TARGET +// +// MessageText: +// +// A JOIN or SUBST command cannot be used for a drive that contains previously joined drives. +// +pub const ERROR_IS_JOIN_TARGET: i32 = 133; + +// +// MessageId: ERROR_IS_JOINED +// +// MessageText: +// +// An attempt was made to use a JOIN or SUBST command on a drive that has already been joined. +// +pub const ERROR_IS_JOINED: i32 = 134; + +// +// MessageId: ERROR_IS_SUBSTED +// +// MessageText: +// +// An attempt was made to use a JOIN or SUBST command on a drive that has already been substituted. +// +pub const ERROR_IS_SUBSTED: i32 = 135; + +// +// MessageId: ERROR_NOT_JOINED +// +// MessageText: +// +// The system tried to delete the JOIN of a drive that is not joined. +// +pub const ERROR_NOT_JOINED: i32 = 136; + +// +// MessageId: ERROR_NOT_SUBSTED +// +// MessageText: +// +// The system tried to delete the substitution of a drive that is not substituted. +// +pub const ERROR_NOT_SUBSTED: i32 = 137; + +// +// MessageId: ERROR_JOIN_TO_JOIN +// +// MessageText: +// +// The system tried to join a drive to a directory on a joined drive. +// +pub const ERROR_JOIN_TO_JOIN: i32 = 138; + +// +// MessageId: ERROR_SUBST_TO_SUBST +// +// MessageText: +// +// The system tried to substitute a drive to a directory on a substituted drive. +// +pub const ERROR_SUBST_TO_SUBST: i32 = 139; + +// +// MessageId: ERROR_JOIN_TO_SUBST +// +// MessageText: +// +// The system tried to join a drive to a directory on a substituted drive. +// +pub const ERROR_JOIN_TO_SUBST: i32 = 140; + +// +// MessageId: ERROR_SUBST_TO_JOIN +// +// MessageText: +// +// The system tried to SUBST a drive to a directory on a joined drive. +// +pub const ERROR_SUBST_TO_JOIN: i32 = 141; + +// +// MessageId: ERROR_BUSY_DRIVE +// +// MessageText: +// +// The system cannot perform a JOIN or SUBST at this time. +// +pub const ERROR_BUSY_DRIVE: i32 = 142; + +// +// MessageId: ERROR_SAME_DRIVE +// +// MessageText: +// +// The system cannot join or substitute a drive to or for a directory on the same drive. +// +pub const ERROR_SAME_DRIVE: i32 = 143; + +// +// MessageId: ERROR_DIR_NOT_ROOT +// +// MessageText: +// +// The directory is not a subdirectory of the root directory. +// +pub const ERROR_DIR_NOT_ROOT: i32 = 144; + +// +// MessageId: ERROR_DIR_NOT_EMPTY +// +// MessageText: +// +// The directory is not empty. +// +pub const ERROR_DIR_NOT_EMPTY: i32 = 145; + +// +// MessageId: ERROR_IS_SUBST_PATH +// +// MessageText: +// +// The path specified is being used in a substitute. +// +pub const ERROR_IS_SUBST_PATH: i32 = 146; + +// +// MessageId: ERROR_IS_JOIN_PATH +// +// MessageText: +// +// Not enough resources are available to process this command. +// +pub const ERROR_IS_JOIN_PATH: i32 = 147; + +// +// MessageId: ERROR_PATH_BUSY +// +// MessageText: +// +// The path specified cannot be used at this time. +// +pub const ERROR_PATH_BUSY: i32 = 148; + +// +// MessageId: ERROR_IS_SUBST_TARGET +// +// MessageText: +// +// An attempt was made to join or substitute a drive for which a directory on the drive is the target of a previous substitute. +// +pub const ERROR_IS_SUBST_TARGET: i32 = 149; + +// +// MessageId: ERROR_SYSTEM_TRACE +// +// MessageText: +// +// System trace information was not specified in your CONFIG.SYS file, or tracing is disallowed. +// +pub const ERROR_SYSTEM_TRACE: i32 = 150; + +// +// MessageId: ERROR_INVALID_EVENT_COUNT +// +// MessageText: +// +// The number of specified semaphore events for DosMuxSemWait is not correct. +// +pub const ERROR_INVALID_EVENT_COUNT: i32 = 151; + +// +// MessageId: ERROR_TOO_MANY_MUXWAITERS +// +// MessageText: +// +// DosMuxSemWait did not execute; too many semaphores are already set. +// +pub const ERROR_TOO_MANY_MUXWAITERS: i32 = 152; + +// +// MessageId: ERROR_INVALID_LIST_FORMAT +// +// MessageText: +// +// The DosMuxSemWait list is not correct. +// +pub const ERROR_INVALID_LIST_FORMAT: i32 = 153; + +// +// MessageId: ERROR_LABEL_TOO_LONG +// +// MessageText: +// +// The volume label you entered exceeds the label character limit of the target file system. +// +pub const ERROR_LABEL_TOO_LONG: i32 = 154; + +// +// MessageId: ERROR_TOO_MANY_TCBS +// +// MessageText: +// +// Cannot create another thread. +// +pub const ERROR_TOO_MANY_TCBS: i32 = 155; + +// +// MessageId: ERROR_SIGNAL_REFUSED +// +// MessageText: +// +// The recipient process has refused the signal. +// +pub const ERROR_SIGNAL_REFUSED: i32 = 156; + +// +// MessageId: ERROR_DISCARDED +// +// MessageText: +// +// The segment is already discarded and cannot be locked. +// +pub const ERROR_DISCARDED: i32 = 157; + +// +// MessageId: ERROR_NOT_LOCKED +// +// MessageText: +// +// The segment is already unlocked. +// +pub const ERROR_NOT_LOCKED: i32 = 158; + +// +// MessageId: ERROR_BAD_THREADID_ADDR +// +// MessageText: +// +// The address for the thread ID is not correct. +// +pub const ERROR_BAD_THREADID_ADDR: i32 = 159; + +// +// MessageId: ERROR_BAD_ARGUMENTS +// +// MessageText: +// +// One or more arguments are not correct. +// +pub const ERROR_BAD_ARGUMENTS: i32 = 160; + +// +// MessageId: ERROR_BAD_PATHNAME +// +// MessageText: +// +// The specified path is invalid. +// +pub const ERROR_BAD_PATHNAME: i32 = 161; + +// +// MessageId: ERROR_SIGNAL_PENDING +// +// MessageText: +// +// A signal is already pending. +// +pub const ERROR_SIGNAL_PENDING: i32 = 162; + +// +// MessageId: ERROR_MAX_THRDS_REACHED +// +// MessageText: +// +// No more threads can be created in the system. +// +pub const ERROR_MAX_THRDS_REACHED: i32 = 164; + +// +// MessageId: ERROR_LOCK_FAILED +// +// MessageText: +// +// Unable to lock a region of a file. +// +pub const ERROR_LOCK_FAILED: i32 = 167; + +// +// MessageId: ERROR_BUSY +// +// MessageText: +// +// The requested resource is in use. +// +pub const ERROR_BUSY: i32 = 170; // dderror + +// +// MessageId: ERROR_CANCEL_VIOLATION +// +// MessageText: +// +// A lock request was not outstanding for the supplied cancel region. +// +pub const ERROR_CANCEL_VIOLATION: i32 = 173; + +// +// MessageId: ERROR_ATOMIC_LOCKS_NOT_SUPPORTED +// +// MessageText: +// +// The file system does not support atomic changes to the lock type. +// +pub const ERROR_ATOMIC_LOCKS_NOT_SUPPORTED: i32 = 174; + +// +// MessageId: ERROR_INVALID_SEGMENT_NUMBER +// +// MessageText: +// +// The system detected a segment number that was not correct. +// +pub const ERROR_INVALID_SEGMENT_NUMBER: i32 = 180; + +// +// MessageId: ERROR_INVALID_ORDINAL +// +// MessageText: +// +// The operating system cannot run %1. +// +pub const ERROR_INVALID_ORDINAL: i32 = 182; + +// +// MessageId: ERROR_ALREADY_EXISTS +// +// MessageText: +// +// Cannot create a file when that file already exists. +// +pub const ERROR_ALREADY_EXISTS: i32 = 183; + +// +// MessageId: ERROR_INVALID_FLAG_NUMBER +// +// MessageText: +// +// The flag passed is not correct. +// +pub const ERROR_INVALID_FLAG_NUMBER: i32 = 186; + +// +// MessageId: ERROR_SEM_NOT_FOUND +// +// MessageText: +// +// The specified system semaphore name was not found. +// +pub const ERROR_SEM_NOT_FOUND: i32 = 187; + +// +// MessageId: ERROR_INVALID_STARTING_CODESEG +// +// MessageText: +// +// The operating system cannot run %1. +// +pub const ERROR_INVALID_STARTING_CODESEG: i32 = 188; + +// +// MessageId: ERROR_INVALID_STACKSEG +// +// MessageText: +// +// The operating system cannot run %1. +// +pub const ERROR_INVALID_STACKSEG: i32 = 189; + +// +// MessageId: ERROR_INVALID_MODULETYPE +// +// MessageText: +// +// The operating system cannot run %1. +// +pub const ERROR_INVALID_MODULETYPE: i32 = 190; + +// +// MessageId: ERROR_INVALID_EXE_SIGNATURE +// +// MessageText: +// +// Cannot run %1 in Win32 mode. +// +pub const ERROR_INVALID_EXE_SIGNATURE: i32 = 191; + +// +// MessageId: ERROR_EXE_MARKED_INVALID +// +// MessageText: +// +// The operating system cannot run %1. +// +pub const ERROR_EXE_MARKED_INVALID: i32 = 192; + +// +// MessageId: ERROR_BAD_EXE_FORMAT +// +// MessageText: +// +// %1 is not a valid Win32 application. +// +pub const ERROR_BAD_EXE_FORMAT: i32 = 193; + +// +// MessageId: ERROR_ITERATED_DATA_EXCEEDS_64k +// +// MessageText: +// +// The operating system cannot run %1. +// +// deno-lint-ignore camelcase +pub const ERROR_ITERATED_DATA_EXCEEDS_64K: i32 = 194; + +// +// MessageId: ERROR_INVALID_MINALLOCSIZE +// +// MessageText: +// +// The operating system cannot run %1. +// +pub const ERROR_INVALID_MINALLOCSIZE: i32 = 195; + +// +// MessageId: ERROR_DYNLINK_FROM_INVALID_RING +// +// MessageText: +// +// The operating system cannot run this application program. +// +pub const ERROR_DYNLINK_FROM_INVALID_RING: i32 = 196; + +// +// MessageId: ERROR_IOPL_NOT_ENABLED +// +// MessageText: +// +// The operating system is not presently configured to run this application. +// +pub const ERROR_IOPL_NOT_ENABLED: i32 = 197; + +// +// MessageId: ERROR_INVALID_SEGDPL +// +// MessageText: +// +// The operating system cannot run %1. +// +pub const ERROR_INVALID_SEGDPL: i32 = 198; + +// +// MessageId: ERROR_AUTODATASEG_EXCEEDS_64k +// +// MessageText: +// +// The operating system cannot run this application program. +// +// deno-lint-ignore camelcase +pub const ERROR_AUTODATASEG_EXCEEDS_64K: i32 = 199; + +// +// MessageId: ERROR_RING2SEG_MUST_BE_MOVABLE +// +// MessageText: +// +// The code segment cannot be greater than or equal to 64K. +// +pub const ERROR_RING2SEG_MUST_BE_MOVABLE: i32 = 200; + +// +// MessageId: ERROR_RELOC_CHAIN_XEEDS_SEGLIM +// +// MessageText: +// +// The operating system cannot run %1. +// +pub const ERROR_RELOC_CHAIN_XEEDS_SEGLIM: i32 = 201; + +// +// MessageId: ERROR_INFLOOP_IN_RELOC_CHAIN +// +// MessageText: +// +// The operating system cannot run %1. +// +pub const ERROR_INFLOOP_IN_RELOC_CHAIN: i32 = 202; + +// +// MessageId: ERROR_ENVVAR_NOT_FOUND +// +// MessageText: +// +// The system could not find the environment option that was entered. +// +pub const ERROR_ENVVAR_NOT_FOUND: i32 = 203; + +// +// MessageId: ERROR_NO_SIGNAL_SENT +// +// MessageText: +// +// No process in the command subtree has a signal handler. +// +pub const ERROR_NO_SIGNAL_SENT: i32 = 205; + +// +// MessageId: ERROR_FILENAME_EXCED_RANGE +// +// MessageText: +// +// The filename or extension is too long. +// +pub const ERROR_FILENAME_EXCED_RANGE: i32 = 206; + +// +// MessageId: ERROR_RING2_STACK_IN_USE +// +// MessageText: +// +// The ring 2 stack is in use. +// +pub const ERROR_RING2_STACK_IN_USE: i32 = 207; + +// +// MessageId: ERROR_META_EXPANSION_TOO_LONG +// +// MessageText: +// +// The global filename characters, * or ?, are entered incorrectly or too many global filename characters are specified. +// +pub const ERROR_META_EXPANSION_TOO_LONG: i32 = 208; + +// +// MessageId: ERROR_INVALID_SIGNAL_NUMBER +// +// MessageText: +// +// The signal being posted is not correct. +// +pub const ERROR_INVALID_SIGNAL_NUMBER: i32 = 209; + +// +// MessageId: ERROR_THREAD_1_INACTIVE +// +// MessageText: +// +// The signal handler cannot be set. +// +pub const ERROR_THREAD_1_INACTIVE: i32 = 210; + +// +// MessageId: ERROR_LOCKED +// +// MessageText: +// +// The segment is locked and cannot be reallocated. +// +pub const ERROR_LOCKED: i32 = 212; + +// +// MessageId: ERROR_TOO_MANY_MODULES +// +// MessageText: +// +// Too many dynamic-link modules are attached to this program or dynamic-link module. +// +pub const ERROR_TOO_MANY_MODULES: i32 = 214; + +// +// MessageId: ERROR_NESTING_NOT_ALLOWED +// +// MessageText: +// +// Cannot nest calls to LoadModule. +// +pub const ERROR_NESTING_NOT_ALLOWED: i32 = 215; + +// +// MessageId: ERROR_EXE_MACHINE_TYPE_MISMATCH +// +// MessageText: +// +// The image file %1 is valid, but is for a machine type other than the current machine. +// +pub const ERROR_EXE_MACHINE_TYPE_MISMATCH: i32 = 216; + +// +// MessageId: ERROR_EXE_CANNOT_MODIFY_SIGNED_BINARY +// +// MessageText: +// +// The image file %1 is signed, unable to modify. +// +pub const ERROR_EXE_CANNOT_MODIFY_SIGNED_BINARY: i32 = 217; + +// +// MessageId: ERROR_EXE_CANNOT_MODIFY_STRONG_SIGNED_BINARY +// +// MessageText: +// +// The image file %1 is strong signed, unable to modify. +// +pub const ERROR_EXE_CANNOT_MODIFY_STRONG_SIGNED_BINARY: i32 = 218; + +// +// MessageId: ERROR_BAD_PIPE +// +// MessageText: +// +// The pipe state is invalid. +// +pub const ERROR_BAD_PIPE: i32 = 230; + +// +// MessageId: ERROR_PIPE_BUSY +// +// MessageText: +// +// All pipe instances are busy. +// +pub const ERROR_PIPE_BUSY: i32 = 231; + +// +// MessageId: ERROR_NO_DATA +// +// MessageText: +// +// The pipe is being closed. +// +pub const ERROR_NO_DATA: i32 = 232; + +// +// MessageId: ERROR_PIPE_NOT_CONNECTED +// +// MessageText: +// +// No process is on the other end of the pipe. +// +pub const ERROR_PIPE_NOT_CONNECTED: i32 = 233; + +// +// MessageId: ERROR_MORE_DATA +// +// MessageText: +// +// More data is available. +// +pub const ERROR_MORE_DATA: i32 = 234; // dderror + +// +// MessageId: ERROR_VC_DISCONNECTED +// +// MessageText: +// +// The session was canceled. +// +pub const ERROR_VC_DISCONNECTED: i32 = 240; + +// +// MessageId: ERROR_INVALID_EA_NAME +// +// MessageText: +// +// The specified extended attribute name was invalid. +// +pub const ERROR_INVALID_EA_NAME: i32 = 254; + +// +// MessageId: ERROR_EA_LIST_INCONSISTENT +// +// MessageText: +// +// The extended attributes are inconsistent. +// +pub const ERROR_EA_LIST_INCONSISTENT: i32 = 255; + +// +// MessageId: WAIT_TIMEOUT +// +// MessageText: +// +// The wait operation timed out. +// +pub const WAIT_TIMEOUT: i32 = 258; // dderror + +// +// MessageId: ERROR_NO_MORE_ITEMS +// +// MessageText: +// +// No more data is available. +// +pub const ERROR_NO_MORE_ITEMS: i32 = 259; + +// +// MessageId: ERROR_CANNOT_COPY +// +// MessageText: +// +// The copy functions cannot be used. +// +pub const ERROR_CANNOT_COPY: i32 = 266; + +// +// MessageId: ERROR_DIRECTORY +// +// MessageText: +// +// The directory name is invalid. +// +pub const ERROR_DIRECTORY: i32 = 267; + +// +// MessageId: ERROR_EAS_DIDNT_FIT +// +// MessageText: +// +// The extended attributes did not fit in the buffer. +// +pub const ERROR_EAS_DIDNT_FIT: i32 = 275; + +// +// MessageId: ERROR_EA_FILE_CORRUPT +// +// MessageText: +// +// The extended attribute file on the mounted file system is corrupt. +// +pub const ERROR_EA_FILE_CORRUPT: i32 = 276; + +// +// MessageId: ERROR_EA_TABLE_FULL +// +// MessageText: +// +// The extended attribute table file is full. +// +pub const ERROR_EA_TABLE_FULL: i32 = 277; + +// +// MessageId: ERROR_INVALID_EA_HANDLE +// +// MessageText: +// +// The specified extended attribute handle is invalid. +// +pub const ERROR_INVALID_EA_HANDLE: i32 = 278; + +// +// MessageId: ERROR_EAS_NOT_SUPPORTED +// +// MessageText: +// +// The mounted file system does not support extended attributes. +// +pub const ERROR_EAS_NOT_SUPPORTED: i32 = 282; + +// +// MessageId: ERROR_NOT_OWNER +// +// MessageText: +// +// Attempt to release mutex not owned by caller. +// +pub const ERROR_NOT_OWNER: i32 = 288; + +// +// MessageId: ERROR_TOO_MANY_POSTS +// +// MessageText: +// +// Too many posts were made to a semaphore. +// +pub const ERROR_TOO_MANY_POSTS: i32 = 298; + +// +// MessageId: ERROR_PARTIAL_COPY +// +// MessageText: +// +// Only part of a ReadProcessMemory or WriteProcessMemory request was completed. +// +pub const ERROR_PARTIAL_COPY: i32 = 299; + +// +// MessageId: ERROR_OPLOCK_NOT_GRANTED +// +// MessageText: +// +// The oplock request is denied. +// +pub const ERROR_OPLOCK_NOT_GRANTED: i32 = 300; + +// +// MessageId: ERROR_INVALID_OPLOCK_PROTOCOL +// +// MessageText: +// +// An invalid oplock acknowledgment was received by the system. +// +pub const ERROR_INVALID_OPLOCK_PROTOCOL: i32 = 301; + +// +// MessageId: ERROR_DISK_TOO_FRAGMENTED +// +// MessageText: +// +// The volume is too fragmented to complete this operation. +// +pub const ERROR_DISK_TOO_FRAGMENTED: i32 = 302; + +// +// MessageId: ERROR_DELETE_PENDING +// +// MessageText: +// +// The file cannot be opened because it is in the process of being deleted. +// +pub const ERROR_DELETE_PENDING: i32 = 303; + +// +// MessageId: ERROR_MR_MID_NOT_FOUND +// +// MessageText: +// +// The system cannot find message text for message number 0x%1 in the message file for %2. +// +pub const ERROR_MR_MID_NOT_FOUND: i32 = 317; + +// +// MessageId: ERROR_SCOPE_NOT_FOUND +// +// MessageText: +// +// The scope specified was not found. +// +pub const ERROR_SCOPE_NOT_FOUND: i32 = 318; + +// +// MessageId: ERROR_INVALID_ADDRESS +// +// MessageText: +// +// Attempt to access invalid address. +// +pub const ERROR_INVALID_ADDRESS: i32 = 487; + +// +// MessageId: ERROR_ARITHMETIC_OVERFLOW +// +// MessageText: +// +// Arithmetic result exceeded 32 bits. +// +pub const ERROR_ARITHMETIC_OVERFLOW: i32 = 534; + +// +// MessageId: ERROR_PIPE_CONNECTED +// +// MessageText: +// +// There is a process on other end of the pipe. +// +pub const ERROR_PIPE_CONNECTED: i32 = 535; + +// +// MessageId: ERROR_PIPE_LISTENING +// +// MessageText: +// +// Waiting for a process to open the other end of the pipe. +// +pub const ERROR_PIPE_LISTENING: i32 = 536; + +// +// MessageId: ERROR_EA_ACCESS_DENIED +// +// MessageText: +// +// Access to the extended attribute was denied. +// +pub const ERROR_EA_ACCESS_DENIED: i32 = 994; + +// +// MessageId: ERROR_OPERATION_ABORTED +// +// MessageText: +// +// The I/O operation has been aborted because of either a thread exit or an application request. +// +pub const ERROR_OPERATION_ABORTED: i32 = 995; + +// +// MessageId: ERROR_IO_INCOMPLETE +// +// MessageText: +// +// Overlapped I/O event is not in a signaled state. +// +pub const ERROR_IO_INCOMPLETE: i32 = 996; + +// +// MessageId: ERROR_IO_PENDING +// +// MessageText: +// +// Overlapped I/O operation is in progress. +// +pub const ERROR_IO_PENDING: i32 = 997; // dderror + +// +// MessageId: ERROR_NOACCESS +// +// MessageText: +// +// Invalid access to memory location. +// +pub const ERROR_NOACCESS: i32 = 998; + +// +// MessageId: ERROR_SWAPERROR +// +// MessageText: +// +// Error performing inpage operation. +// +pub const ERROR_SWAPERROR: i32 = 999; + +// +// MessageId: ERROR_STACK_OVERFLOW +// +// MessageText: +// +// Recursion too deep; the stack overflowed. +// +pub const ERROR_STACK_OVERFLOW: i32 = 1001; + +// +// MessageId: ERROR_INVALID_MESSAGE +// +// MessageText: +// +// The window cannot act on the sent message. +// +pub const ERROR_INVALID_MESSAGE: i32 = 1002; + +// +// MessageId: ERROR_CAN_NOT_COMPLETE +// +// MessageText: +// +// Cannot complete this function. +// +pub const ERROR_CAN_NOT_COMPLETE: i32 = 1003; + +// +// MessageId: ERROR_INVALID_FLAGS +// +// MessageText: +// +// Invalid flags. +// +pub const ERROR_INVALID_FLAGS: i32 = 1004; + +// +// MessageId: ERROR_UNRECOGNIZED_VOLUME +// +// MessageText: +// +// The volume does not contain a recognized file system. +// Please make sure that all required file system drivers are loaded and that the volume is not corrupted. +// +pub const ERROR_UNRECOGNIZED_VOLUME: i32 = 1005; + +// +// MessageId: ERROR_FILE_INVALID +// +// MessageText: +// +// The volume for a file has been externally altered so that the opened file is no longer valid. +// +pub const ERROR_FILE_INVALID: i32 = 1006; + +// +// MessageId: ERROR_FULLSCREEN_MODE +// +// MessageText: +// +// The requested operation cannot be performed in full-screen mode. +// +pub const ERROR_FULLSCREEN_MODE: i32 = 1007; + +// +// MessageId: ERROR_NO_TOKEN +// +// MessageText: +// +// An attempt was made to reference a token that does not exist. +// +pub const ERROR_NO_TOKEN: i32 = 1008; + +// +// MessageId: ERROR_BADDB +// +// MessageText: +// +// The configuration registry database is corrupt. +// +pub const ERROR_BADDB: i32 = 1009; + +// +// MessageId: ERROR_BADKEY +// +// MessageText: +// +// The configuration registry key is invalid. +// +pub const ERROR_BADKEY: i32 = 1010; + +// +// MessageId: ERROR_CANTOPEN +// +// MessageText: +// +// The configuration registry key could not be opened. +// +pub const ERROR_CANTOPEN: i32 = 1011; + +// +// MessageId: ERROR_CANTREAD +// +// MessageText: +// +// The configuration registry key could not be read. +// +pub const ERROR_CANTREAD: i32 = 1012; + +// +// MessageId: ERROR_CANTWRITE +// +// MessageText: +// +// The configuration registry key could not be written. +// +pub const ERROR_CANTWRITE: i32 = 1013; + +// +// MessageId: ERROR_REGISTRY_RECOVERED +// +// MessageText: +// +// One of the files in the registry database had to be recovered by use of a log or alternate copy. The recovery was successful. +// +pub const ERROR_REGISTRY_RECOVERED: i32 = 1014; + +// +// MessageId: ERROR_REGISTRY_CORRUPT +// +// MessageText: +// +// The registry is corrupted. The structure of one of the files containing registry data is corrupted, or the system's memory image of the file is corrupted, or the file could not be recovered because the alternate copy or log was absent or corrupted. +// +pub const ERROR_REGISTRY_CORRUPT: i32 = 1015; + +// +// MessageId: ERROR_REGISTRY_IO_FAILED +// +// MessageText: +// +// An I/O operation initiated by the registry failed unrecoverably. The registry could not read in, or write out, or flush, one of the files that contain the system's image of the registry. +// +pub const ERROR_REGISTRY_IO_FAILED: i32 = 1016; + +// +// MessageId: ERROR_NOT_REGISTRY_FILE +// +// MessageText: +// +// The system has attempted to load or restore a file into the registry, but the specified file is not in a registry file format. +// +pub const ERROR_NOT_REGISTRY_FILE: i32 = 1017; + +// +// MessageId: ERROR_KEY_DELETED +// +// MessageText: +// +// Illegal operation attempted on a registry key that has been marked for deletion. +// +pub const ERROR_KEY_DELETED: i32 = 1018; + +// +// MessageId: ERROR_NO_LOG_SPACE +// +// MessageText: +// +// System could not allocate the required space in a registry log. +// +pub const ERROR_NO_LOG_SPACE: i32 = 1019; + +// +// MessageId: ERROR_KEY_HAS_CHILDREN +// +// MessageText: +// +// Cannot create a symbolic link in a registry key that already has subkeys or values. +// +pub const ERROR_KEY_HAS_CHILDREN: i32 = 1020; + +// +// MessageId: ERROR_CHILD_MUST_BE_VOLATILE +// +// MessageText: +// +// Cannot create a stable subkey under a volatile parent key. +// +pub const ERROR_CHILD_MUST_BE_VOLATILE: i32 = 1021; + +// +// MessageId: ERROR_NOTIFY_ENUM_DIR +// +// MessageText: +// +// A notify change request is being completed and the information is not being returned in the caller's buffer. The caller now needs to enumerate the files to find the changes. +// +pub const ERROR_NOTIFY_ENUM_DIR: i32 = 1022; + +// +// MessageId: ERROR_DEPENDENT_SERVICES_RUNNING +// +// MessageText: +// +// A stop control has been sent to a service that other running services are dependent on. +// +pub const ERROR_DEPENDENT_SERVICES_RUNNING: i32 = 1051; + +// +// MessageId: ERROR_INVALID_SERVICE_CONTROL +// +// MessageText: +// +// The requested control is not valid for this service. +// +pub const ERROR_INVALID_SERVICE_CONTROL: i32 = 1052; + +// +// MessageId: ERROR_SERVICE_REQUEST_TIMEOUT +// +// MessageText: +// +// The service did not respond to the start or control request in a timely fashion. +// +pub const ERROR_SERVICE_REQUEST_TIMEOUT: i32 = 1053; + +// +// MessageId: ERROR_SERVICE_NO_THREAD +// +// MessageText: +// +// A thread could not be created for the service. +// +pub const ERROR_SERVICE_NO_THREAD: i32 = 1054; + +// +// MessageId: ERROR_SERVICE_DATABASE_LOCKED +// +// MessageText: +// +// The service database is locked. +// +pub const ERROR_SERVICE_DATABASE_LOCKED: i32 = 1055; + +// +// MessageId: ERROR_SERVICE_ALREADY_RUNNING +// +// MessageText: +// +// An instance of the service is already running. +// +pub const ERROR_SERVICE_ALREADY_RUNNING: i32 = 1056; + +// +// MessageId: ERROR_INVALID_SERVICE_ACCOUNT +// +// MessageText: +// +// The account name is invalid or does not exist, or the password is invalid for the account name specified. +// +pub const ERROR_INVALID_SERVICE_ACCOUNT: i32 = 1057; + +// +// MessageId: ERROR_SERVICE_DISABLED +// +// MessageText: +// +// The service cannot be started, either because it is disabled or because it has no enabled devices associated with it. +// +pub const ERROR_SERVICE_DISABLED: i32 = 1058; + +// +// MessageId: ERROR_CIRCULAR_DEPENDENCY +// +// MessageText: +// +// Circular service dependency was specified. +// +pub const ERROR_CIRCULAR_DEPENDENCY: i32 = 1059; + +// +// MessageId: ERROR_SERVICE_DOES_NOT_EXIST +// +// MessageText: +// +// The specified service does not exist as an installed service. +// +pub const ERROR_SERVICE_DOES_NOT_EXIST: i32 = 1060; + +// +// MessageId: ERROR_SERVICE_CANNOT_ACCEPT_CTRL +// +// MessageText: +// +// The service cannot accept control messages at this time. +// +pub const ERROR_SERVICE_CANNOT_ACCEPT_CTRL: i32 = 1061; + +// +// MessageId: ERROR_SERVICE_NOT_ACTIVE +// +// MessageText: +// +// The service has not been started. +// +pub const ERROR_SERVICE_NOT_ACTIVE: i32 = 1062; + +// +// MessageId: ERROR_FAILED_SERVICE_CONTROLLER_CONNECT +// +// MessageText: +// +// The service process could not connect to the service controller. +// +pub const ERROR_FAILED_SERVICE_CONTROLLER_CONNECT: i32 = 1063; + +// +// MessageId: ERROR_EXCEPTION_IN_SERVICE +// +// MessageText: +// +// An exception occurred in the service when handling the control request. +// +pub const ERROR_EXCEPTION_IN_SERVICE: i32 = 1064; + +// +// MessageId: ERROR_DATABASE_DOES_NOT_EXIST +// +// MessageText: +// +// The database specified does not exist. +// +pub const ERROR_DATABASE_DOES_NOT_EXIST: i32 = 1065; + +// +// MessageId: ERROR_SERVICE_SPECIFIC_ERROR +// +// MessageText: +// +// The service has returned a service-specific error code. +// +pub const ERROR_SERVICE_SPECIFIC_ERROR: i32 = 1066; + +// +// MessageId: ERROR_PROCESS_ABORTED +// +// MessageText: +// +// The process terminated unexpectedly. +// +pub const ERROR_PROCESS_ABORTED: i32 = 1067; + +// +// MessageId: ERROR_SERVICE_DEPENDENCY_FAIL +// +// MessageText: +// +// The dependency service or group failed to start. +// +pub const ERROR_SERVICE_DEPENDENCY_FAIL: i32 = 1068; + +// +// MessageId: ERROR_SERVICE_LOGON_FAILED +// +// MessageText: +// +// The service did not start due to a logon failure. +// +pub const ERROR_SERVICE_LOGON_FAILED: i32 = 1069; + +// +// MessageId: ERROR_SERVICE_START_HANG +// +// MessageText: +// +// After starting, the service hung in a start-pending state. +// +pub const ERROR_SERVICE_START_HANG: i32 = 1070; + +// +// MessageId: ERROR_INVALID_SERVICE_LOCK +// +// MessageText: +// +// The specified service database lock is invalid. +// +pub const ERROR_INVALID_SERVICE_LOCK: i32 = 1071; + +// +// MessageId: ERROR_SERVICE_MARKED_FOR_DELETE +// +// MessageText: +// +// The specified service has been marked for deletion. +// +pub const ERROR_SERVICE_MARKED_FOR_DELETE: i32 = 1072; + +// +// MessageId: ERROR_SERVICE_EXISTS +// +// MessageText: +// +// The specified service already exists. +// +pub const ERROR_SERVICE_EXISTS: i32 = 1073; + +// +// MessageId: ERROR_ALREADY_RUNNING_LKG +// +// MessageText: +// +// The system is currently running with the last-known-good configuration. +// +pub const ERROR_ALREADY_RUNNING_LKG: i32 = 1074; + +// +// MessageId: ERROR_SERVICE_DEPENDENCY_DELETED +// +// MessageText: +// +// The dependency service does not exist or has been marked for deletion. +// +pub const ERROR_SERVICE_DEPENDENCY_DELETED: i32 = 1075; + +// +// MessageId: ERROR_BOOT_ALREADY_ACCEPTED +// +// MessageText: +// +// The current boot has already been accepted for use as the last-known-good control set. +// +pub const ERROR_BOOT_ALREADY_ACCEPTED: i32 = 1076; + +// +// MessageId: ERROR_SERVICE_NEVER_STARTED +// +// MessageText: +// +// No attempts to start the service have been made since the last boot. +// +pub const ERROR_SERVICE_NEVER_STARTED: i32 = 1077; + +// +// MessageId: ERROR_DUPLICATE_SERVICE_NAME +// +// MessageText: +// +// The name is already in use as either a service name or a service display name. +// +pub const ERROR_DUPLICATE_SERVICE_NAME: i32 = 1078; + +// +// MessageId: ERROR_DIFFERENT_SERVICE_ACCOUNT +// +// MessageText: +// +// The account specified for this service is different from the account specified for other services running in the same process. +// +pub const ERROR_DIFFERENT_SERVICE_ACCOUNT: i32 = 1079; + +// +// MessageId: ERROR_CANNOT_DETECT_DRIVER_FAILURE +// +// MessageText: +// +// Failure actions can only be set for Win32 services, not for drivers. +// +pub const ERROR_CANNOT_DETECT_DRIVER_FAILURE: i32 = 1080; + +// +// MessageId: ERROR_CANNOT_DETECT_PROCESS_ABORT +// +// MessageText: +// +// This service runs in the same process as the service control manager. +// Therefore, the service control manager cannot take action if this service's process terminates unexpectedly. +// +pub const ERROR_CANNOT_DETECT_PROCESS_ABORT: i32 = 1081; + +// +// MessageId: ERROR_NO_RECOVERY_PROGRAM +// +// MessageText: +// +// No recovery program has been configured for this service. +// +pub const ERROR_NO_RECOVERY_PROGRAM: i32 = 1082; + +// +// MessageId: ERROR_SERVICE_NOT_IN_EXE +// +// MessageText: +// +// The executable program that this service is configured to run in does not implement the service. +// +pub const ERROR_SERVICE_NOT_IN_EXE: i32 = 1083; + +// +// MessageId: ERROR_NOT_SAFEBOOT_SERVICE +// +// MessageText: +// +// This service cannot be started in Safe Mode +// +pub const ERROR_NOT_SAFEBOOT_SERVICE: i32 = 1084; + +// +// MessageId: ERROR_END_OF_MEDIA +// +// MessageText: +// +// The physical end of the tape has been reached. +// +pub const ERROR_END_OF_MEDIA: i32 = 1100; + +// +// MessageId: ERROR_FILEMARK_DETECTED +// +// MessageText: +// +// A tape access reached a filemark. +// +pub const ERROR_FILEMARK_DETECTED: i32 = 1101; + +// +// MessageId: ERROR_BEGINNING_OF_MEDIA +// +// MessageText: +// +// The beginning of the tape or a partition was encountered. +// +pub const ERROR_BEGINNING_OF_MEDIA: i32 = 1102; + +// +// MessageId: ERROR_SETMARK_DETECTED +// +// MessageText: +// +// A tape access reached the end of a set of files. +// +pub const ERROR_SETMARK_DETECTED: i32 = 1103; + +// +// MessageId: ERROR_NO_DATA_DETECTED +// +// MessageText: +// +// No more data is on the tape. +// +pub const ERROR_NO_DATA_DETECTED: i32 = 1104; + +// +// MessageId: ERROR_PARTITION_FAILURE +// +// MessageText: +// +// Tape could not be partitioned. +// +pub const ERROR_PARTITION_FAILURE: i32 = 1105; + +// +// MessageId: ERROR_INVALID_BLOCK_LENGTH +// +// MessageText: +// +// When accessing a new tape of a multivolume partition, the current block size is incorrect. +// +pub const ERROR_INVALID_BLOCK_LENGTH: i32 = 1106; + +// +// MessageId: ERROR_DEVICE_NOT_PARTITIONED +// +// MessageText: +// +// Tape partition information could not be found when loading a tape. +// +pub const ERROR_DEVICE_NOT_PARTITIONED: i32 = 1107; + +// +// MessageId: ERROR_UNABLE_TO_LOCK_MEDIA +// +// MessageText: +// +// Unable to lock the media eject mechanism. +// +pub const ERROR_UNABLE_TO_LOCK_MEDIA: i32 = 1108; + +// +// MessageId: ERROR_UNABLE_TO_UNLOAD_MEDIA +// +// MessageText: +// +// Unable to unload the media. +// +pub const ERROR_UNABLE_TO_UNLOAD_MEDIA: i32 = 1109; + +// +// MessageId: ERROR_MEDIA_CHANGED +// +// MessageText: +// +// The media in the drive may have changed. +// +pub const ERROR_MEDIA_CHANGED: i32 = 1110; + +// +// MessageId: ERROR_BUS_RESET +// +// MessageText: +// +// The I/O bus was reset. +// +pub const ERROR_BUS_RESET: i32 = 1111; + +// +// MessageId: ERROR_NO_MEDIA_IN_DRIVE +// +// MessageText: +// +// No media in drive. +// +pub const ERROR_NO_MEDIA_IN_DRIVE: i32 = 1112; + +// +// MessageId: ERROR_NO_UNICODE_TRANSLATION +// +// MessageText: +// +// No mapping for the Unicode character exists in the target multi-byte code page. +// +pub const ERROR_NO_UNICODE_TRANSLATION: i32 = 1113; + +// +// MessageId: ERROR_DLL_INIT_FAILED +// +// MessageText: +// +// A dynamic link library (DLL) initialization routine failed. +// +pub const ERROR_DLL_INIT_FAILED: i32 = 1114; + +// +// MessageId: ERROR_SHUTDOWN_IN_PROGRESS +// +// MessageText: +// +// A system shutdown is in progress. +// +pub const ERROR_SHUTDOWN_IN_PROGRESS: i32 = 1115; + +// +// MessageId: ERROR_NO_SHUTDOWN_IN_PROGRESS +// +// MessageText: +// +// Unable to abort the system shutdown because no shutdown was in progress. +// +pub const ERROR_NO_SHUTDOWN_IN_PROGRESS: i32 = 1116; + +// +// MessageId: ERROR_IO_DEVICE +// +// MessageText: +// +// The request could not be performed because of an I/O device error. +// +pub const ERROR_IO_DEVICE: i32 = 1117; + +// +// MessageId: ERROR_SERIAL_NO_DEVICE +// +// MessageText: +// +// No serial device was successfully initialized. The serial driver will unload. +// +pub const ERROR_SERIAL_NO_DEVICE: i32 = 1118; + +// +// MessageId: ERROR_IRQ_BUSY +// +// MessageText: +// +// Unable to open a device that was sharing an interrupt request (IRQ) with other devices. At least one other device that uses that IRQ was already opened. +// +pub const ERROR_IRQ_BUSY: i32 = 1119; + +// +// MessageId: ERROR_MORE_WRITES +// +// MessageText: +// +// A serial I/O operation was completed by another write to the serial port. +// (The IOCTL_SERIAL_XOFF_COUNTER reached zero.) +// +pub const ERROR_MORE_WRITES: i32 = 1120; + +// +// MessageId: ERROR_COUNTER_TIMEOUT +// +// MessageText: +// +// A serial I/O operation completed because the timeout period expired. +// (The IOCTL_SERIAL_XOFF_COUNTER did not reach zero.) +// +pub const ERROR_COUNTER_TIMEOUT: i32 = 1121; + +// +// MessageId: ERROR_FLOPPY_ID_MARK_NOT_FOUND +// +// MessageText: +// +// No ID address mark was found on the floppy disk. +// +pub const ERROR_FLOPPY_ID_MARK_NOT_FOUND: i32 = 1122; + +// +// MessageId: ERROR_FLOPPY_WRONG_CYLINDER +// +// MessageText: +// +// Mismatch between the floppy disk sector ID field and the floppy disk controller track address. +// +pub const ERROR_FLOPPY_WRONG_CYLINDER: i32 = 1123; + +// +// MessageId: ERROR_FLOPPY_UNKNOWN_ERROR +// +// MessageText: +// +// The floppy disk controller reported an error that is not recognized by the floppy disk driver. +// +pub const ERROR_FLOPPY_UNKNOWN_ERROR: i32 = 1124; + +// +// MessageId: ERROR_FLOPPY_BAD_REGISTERS +// +// MessageText: +// +// The floppy disk controller returned inconsistent results in its registers. +// +pub const ERROR_FLOPPY_BAD_REGISTERS: i32 = 1125; + +// +// MessageId: ERROR_DISK_RECALIBRATE_FAILED +// +// MessageText: +// +// While accessing the hard disk, a recalibrate operation failed, even after retries. +// +pub const ERROR_DISK_RECALIBRATE_FAILED: i32 = 1126; + +// +// MessageId: ERROR_DISK_OPERATION_FAILED +// +// MessageText: +// +// While accessing the hard disk, a disk operation failed even after retries. +// +pub const ERROR_DISK_OPERATION_FAILED: i32 = 1127; + +// +// MessageId: ERROR_DISK_RESET_FAILED +// +// MessageText: +// +// While accessing the hard disk, a disk controller reset was needed, but even that failed. +// +pub const ERROR_DISK_RESET_FAILED: i32 = 1128; + +// +// MessageId: ERROR_EOM_OVERFLOW +// +// MessageText: +// +// Physical end of tape encountered. +// +pub const ERROR_EOM_OVERFLOW: i32 = 1129; + +// +// MessageId: ERROR_NOT_ENOUGH_SERVER_MEMORY +// +// MessageText: +// +// Not enough server storage is available to process this command. +// +pub const ERROR_NOT_ENOUGH_SERVER_MEMORY: i32 = 1130; + +// +// MessageId: ERROR_POSSIBLE_DEADLOCK +// +// MessageText: +// +// A potential deadlock condition has been detected. +// +pub const ERROR_POSSIBLE_DEADLOCK: i32 = 1131; + +// +// MessageId: ERROR_MAPPED_ALIGNMENT +// +// MessageText: +// +// The base address or the file offset specified does not have the proper alignment. +// +pub const ERROR_MAPPED_ALIGNMENT: i32 = 1132; + +// +// MessageId: ERROR_SET_POWER_STATE_VETOED +// +// MessageText: +// +// An attempt to change the system power state was vetoed by another application or driver. +// +pub const ERROR_SET_POWER_STATE_VETOED: i32 = 1140; + +// +// MessageId: ERROR_SET_POWER_STATE_FAILED +// +// MessageText: +// +// The system BIOS failed an attempt to change the system power state. +// +pub const ERROR_SET_POWER_STATE_FAILED: i32 = 1141; + +// +// MessageId: ERROR_TOO_MANY_LINKS +// +// MessageText: +// +// An attempt was made to create more links on a file than the file system supports. +// +pub const ERROR_TOO_MANY_LINKS: i32 = 1142; + +// +// MessageId: ERROR_OLD_WIN_VERSION +// +// MessageText: +// +// The specified program requires a newer version of Windows. +// +pub const ERROR_OLD_WIN_VERSION: i32 = 1150; + +// +// MessageId: ERROR_APP_WRONG_OS +// +// MessageText: +// +// The specified program is not a Windows or MS-DOS program. +// +pub const ERROR_APP_WRONG_OS: i32 = 1151; + +// +// MessageId: ERROR_SINGLE_INSTANCE_APP +// +// MessageText: +// +// Cannot start more than one instance of the specified program. +// +pub const ERROR_SINGLE_INSTANCE_APP: i32 = 1152; + +// +// MessageId: ERROR_RMODE_APP +// +// MessageText: +// +// The specified program was written for an earlier version of Windows. +// +pub const ERROR_RMODE_APP: i32 = 1153; + +// +// MessageId: ERROR_INVALID_DLL +// +// MessageText: +// +// One of the library files needed to run this application is damaged. +// +pub const ERROR_INVALID_DLL: i32 = 1154; + +// +// MessageId: ERROR_NO_ASSOCIATION +// +// MessageText: +// +// No application is associated with the specified file for this operation. +// +pub const ERROR_NO_ASSOCIATION: i32 = 1155; + +// +// MessageId: ERROR_DDE_FAIL +// +// MessageText: +// +// An error occurred in sending the command to the application. +// +pub const ERROR_DDE_FAIL: i32 = 1156; + +// +// MessageId: ERROR_DLL_NOT_FOUND +// +// MessageText: +// +// One of the library files needed to run this application cannot be found. +// +pub const ERROR_DLL_NOT_FOUND: i32 = 1157; + +// +// MessageId: ERROR_NO_MORE_USER_HANDLES +// +// MessageText: +// +// The current process has used all of its system allowance of handles for Window Manager objects. +// +pub const ERROR_NO_MORE_USER_HANDLES: i32 = 1158; + +// +// MessageId: ERROR_MESSAGE_SYNC_ONLY +// +// MessageText: +// +// The message can be used only with synchronous operations. +// +pub const ERROR_MESSAGE_SYNC_ONLY: i32 = 1159; + +// +// MessageId: ERROR_SOURCE_ELEMENT_EMPTY +// +// MessageText: +// +// The indicated source element has no media. +// +pub const ERROR_SOURCE_ELEMENT_EMPTY: i32 = 1160; + +// +// MessageId: ERROR_DESTINATION_ELEMENT_FULL +// +// MessageText: +// +// The indicated destination element already contains media. +// +pub const ERROR_DESTINATION_ELEMENT_FULL: i32 = 1161; + +// +// MessageId: ERROR_ILLEGAL_ELEMENT_ADDRESS +// +// MessageText: +// +// The indicated element does not exist. +// +pub const ERROR_ILLEGAL_ELEMENT_ADDRESS: i32 = 1162; + +// +// MessageId: ERROR_MAGAZINE_NOT_PRESENT +// +// MessageText: +// +// The indicated element is part of a magazine that is not present. +// +pub const ERROR_MAGAZINE_NOT_PRESENT: i32 = 1163; + +// +// MessageId: ERROR_DEVICE_REINITIALIZATION_NEEDED +// +// MessageText: +// +// The indicated device requires reinitialization due to hardware errors. +// +pub const ERROR_DEVICE_REINITIALIZATION_NEEDED: i32 = 1164; // dderror + +// +// MessageId: ERROR_DEVICE_REQUIRES_CLEANING +// +// MessageText: +// +// The device has indicated that cleaning is required before further operations are attempted. +// +pub const ERROR_DEVICE_REQUIRES_CLEANING: i32 = 1165; + +// +// MessageId: ERROR_DEVICE_DOOR_OPEN +// +// MessageText: +// +// The device has indicated that its door is open. +// +pub const ERROR_DEVICE_DOOR_OPEN: i32 = 1166; + +// +// MessageId: ERROR_DEVICE_NOT_CONNECTED +// +// MessageText: +// +// The device is not connected. +// +pub const ERROR_DEVICE_NOT_CONNECTED: i32 = 1167; + +// +// MessageId: ERROR_NOT_FOUND +// +// MessageText: +// +// Element not found. +// +pub const ERROR_NOT_FOUND: i32 = 1168; + +// +// MessageId: ERROR_NO_MATCH +// +// MessageText: +// +// There was no match for the specified key in the index. +// +pub const ERROR_NO_MATCH: i32 = 1169; + +// +// MessageId: ERROR_SET_NOT_FOUND +// +// MessageText: +// +// The property set specified does not exist on the object. +// +pub const ERROR_SET_NOT_FOUND: i32 = 1170; + +// +// MessageId: ERROR_POINT_NOT_FOUND +// +// MessageText: +// +// The point passed to GetMouseMovePoints is not in the buffer. +// +pub const ERROR_POINT_NOT_FOUND: i32 = 1171; + +// +// MessageId: ERROR_NO_TRACKING_SERVICE +// +// MessageText: +// +// The tracking (workstation) service is not running. +// +pub const ERROR_NO_TRACKING_SERVICE: i32 = 1172; + +// +// MessageId: ERROR_NO_VOLUME_ID +// +// MessageText: +// +// The Volume ID could not be found. +// +pub const ERROR_NO_VOLUME_ID: i32 = 1173; + +// +// MessageId: ERROR_UNABLE_TO_REMOVE_REPLACED +// +// MessageText: +// +// Unable to remove the file to be replaced. +// +pub const ERROR_UNABLE_TO_REMOVE_REPLACED: i32 = 1175; + +// +// MessageId: ERROR_UNABLE_TO_MOVE_REPLACEMENT +// +// MessageText: +// +// Unable to move the replacement file to the file to be replaced. The file to be replaced has retained its original name. +// +pub const ERROR_UNABLE_TO_MOVE_REPLACEMENT: i32 = 1176; + +// +// MessageId: ERROR_UNABLE_TO_MOVE_REPLACEMENT_2 +// +// MessageText: +// +// Unable to move the replacement file to the file to be replaced. The file to be replaced has been renamed using the backup name. +// +pub const ERROR_UNABLE_TO_MOVE_REPLACEMENT_2: i32 = 1177; + +// +// MessageId: ERROR_JOURNAL_DELETE_IN_PROGRESS +// +// MessageText: +// +// The volume change journal is being deleted. +// +pub const ERROR_JOURNAL_DELETE_IN_PROGRESS: i32 = 1178; + +// +// MessageId: ERROR_JOURNAL_NOT_ACTIVE +// +// MessageText: +// +// The volume change journal is not active. +// +pub const ERROR_JOURNAL_NOT_ACTIVE: i32 = 1179; + +// +// MessageId: ERROR_POTENTIAL_FILE_FOUND +// +// MessageText: +// +// A file was found, but it may not be the correct file. +// +pub const ERROR_POTENTIAL_FILE_FOUND: i32 = 1180; + +// +// MessageId: ERROR_JOURNAL_ENTRY_DELETED +// +// MessageText: +// +// The journal entry has been deleted from the journal. +// +pub const ERROR_JOURNAL_ENTRY_DELETED: i32 = 1181; + +// +// MessageId: ERROR_BAD_DEVICE +// +// MessageText: +// +// The specified device name is invalid. +// +pub const ERROR_BAD_DEVICE: i32 = 1200; + +// +// MessageId: ERROR_CONNECTION_UNAVAIL +// +// MessageText: +// +// The device is not currently connected but it is a remembered connection. +// +pub const ERROR_CONNECTION_UNAVAIL: i32 = 1201; + +// +// MessageId: ERROR_DEVICE_ALREADY_REMEMBERED +// +// MessageText: +// +// The local device name has a remembered connection to another network resource. +// +pub const ERROR_DEVICE_ALREADY_REMEMBERED: i32 = 1202; + +// +// MessageId: ERROR_NO_NET_OR_BAD_PATH +// +// MessageText: +// +// No network provider accepted the given network path. +// +pub const ERROR_NO_NET_OR_BAD_PATH: i32 = 1203; + +// +// MessageId: ERROR_BAD_PROVIDER +// +// MessageText: +// +// The specified network provider name is invalid. +// +pub const ERROR_BAD_PROVIDER: i32 = 1204; + +// +// MessageId: ERROR_CANNOT_OPEN_PROFILE +// +// MessageText: +// +// Unable to open the network connection profile. +// +pub const ERROR_CANNOT_OPEN_PROFILE: i32 = 1205; + +// +// MessageId: ERROR_BAD_PROFILE +// +// MessageText: +// +// The network connection profile is corrupted. +// +pub const ERROR_BAD_PROFILE: i32 = 1206; + +// +// MessageId: ERROR_NOT_CONTAINER +// +// MessageText: +// +// Cannot enumerate a noncontainer. +// +pub const ERROR_NOT_CONTAINER: i32 = 1207; + +// +// MessageId: ERROR_EXTENDED_ERROR +// +// MessageText: +// +// An extended error has occurred. +// +pub const ERROR_EXTENDED_ERROR: i32 = 1208; + +// +// MessageId: ERROR_INVALID_GROUPNAME +// +// MessageText: +// +// The format of the specified group name is invalid. +// +pub const ERROR_INVALID_GROUPNAME: i32 = 1209; + +// +// MessageId: ERROR_INVALID_COMPUTERNAME +// +// MessageText: +// +// The format of the specified computer name is invalid. +// +pub const ERROR_INVALID_COMPUTERNAME: i32 = 1210; + +// +// MessageId: ERROR_INVALID_EVENTNAME +// +// MessageText: +// +// The format of the specified event name is invalid. +// +pub const ERROR_INVALID_EVENTNAME: i32 = 1211; + +// +// MessageId: ERROR_INVALID_DOMAINNAME +// +// MessageText: +// +// The format of the specified domain name is invalid. +// +pub const ERROR_INVALID_DOMAINNAME: i32 = 1212; + +// +// MessageId: ERROR_INVALID_SERVICENAME +// +// MessageText: +// +// The format of the specified service name is invalid. +// +pub const ERROR_INVALID_SERVICENAME: i32 = 1213; + +// +// MessageId: ERROR_INVALID_NETNAME +// +// MessageText: +// +// The format of the specified network name is invalid. +// +pub const ERROR_INVALID_NETNAME: i32 = 1214; + +// +// MessageId: ERROR_INVALID_SHARENAME +// +// MessageText: +// +// The format of the specified share name is invalid. +// +pub const ERROR_INVALID_SHARENAME: i32 = 1215; + +// +// MessageId: ERROR_INVALID_PASSWORDNAME +// +// MessageText: +// +// The format of the specified password is invalid. +// +pub const ERROR_INVALID_PASSWORDNAME: i32 = 1216; + +// +// MessageId: ERROR_INVALID_MESSAGENAME +// +// MessageText: +// +// The format of the specified message name is invalid. +// +pub const ERROR_INVALID_MESSAGENAME: i32 = 1217; + +// +// MessageId: ERROR_INVALID_MESSAGEDEST +// +// MessageText: +// +// The format of the specified message destination is invalid. +// +pub const ERROR_INVALID_MESSAGEDEST: i32 = 1218; + +// +// MessageId: ERROR_SESSION_CREDENTIAL_CONFLICT +// +// MessageText: +// +// Multiple connections to a server or shared resource by the same user, using more than one user name, are not allowed. Disconnect all previous connections to the server or shared resource and try again. +// +pub const ERROR_SESSION_CREDENTIAL_CONFLICT: i32 = 1219; + +// +// MessageId: ERROR_REMOTE_SESSION_LIMIT_EXCEEDED +// +// MessageText: +// +// An attempt was made to establish a session to a network server, but there are already too many sessions established to that server. +// +pub const ERROR_REMOTE_SESSION_LIMIT_EXCEEDED: i32 = 1220; + +// +// MessageId: ERROR_DUP_DOMAINNAME +// +// MessageText: +// +// The workgroup or domain name is already in use by another computer on the network. +// +pub const ERROR_DUP_DOMAINNAME: i32 = 1221; + +// +// MessageId: ERROR_NO_NETWORK +// +// MessageText: +// +// The network is not present or not started. +// +pub const ERROR_NO_NETWORK: i32 = 1222; + +// +// MessageId: ERROR_CANCELLED +// +// MessageText: +// +// The operation was canceled by the user. +// +pub const ERROR_CANCELLED: i32 = 1223; + +// +// MessageId: ERROR_USER_MAPPED_FILE +// +// MessageText: +// +// The requested operation cannot be performed on a file with a user-mapped section open. +// +pub const ERROR_USER_MAPPED_FILE: i32 = 1224; + +// +// MessageId: ERROR_CONNECTION_REFUSED +// +// MessageText: +// +// The remote system refused the network connection. +// +pub const ERROR_CONNECTION_REFUSED: i32 = 1225; + +// +// MessageId: ERROR_GRACEFUL_DISCONNECT +// +// MessageText: +// +// The network connection was gracefully closed. +// +pub const ERROR_GRACEFUL_DISCONNECT: i32 = 1226; + +// +// MessageId: ERROR_ADDRESS_ALREADY_ASSOCIATED +// +// MessageText: +// +// The network transport endpoint already has an address associated with it. +// +pub const ERROR_ADDRESS_ALREADY_ASSOCIATED: i32 = 1227; + +// +// MessageId: ERROR_ADDRESS_NOT_ASSOCIATED +// +// MessageText: +// +// An address has not yet been associated with the network endpoint. +// +pub const ERROR_ADDRESS_NOT_ASSOCIATED: i32 = 1228; + +// +// MessageId: ERROR_CONNECTION_INVALID +// +// MessageText: +// +// An operation was attempted on a nonexistent network connection. +// +pub const ERROR_CONNECTION_INVALID: i32 = 1229; + +// +// MessageId: ERROR_CONNECTION_ACTIVE +// +// MessageText: +// +// An invalid operation was attempted on an active network connection. +// +pub const ERROR_CONNECTION_ACTIVE: i32 = 1230; + +// +// MessageId: ERROR_NETWORK_UNREACHABLE +// +// MessageText: +// +// The network location cannot be reached. For information about network troubleshooting, see Windows Help. +// +pub const ERROR_NETWORK_UNREACHABLE: i32 = 1231; + +// +// MessageId: ERROR_HOST_UNREACHABLE +// +// MessageText: +// +// The network location cannot be reached. For information about network troubleshooting, see Windows Help. +// +pub const ERROR_HOST_UNREACHABLE: i32 = 1232; + +// +// MessageId: ERROR_PROTOCOL_UNREACHABLE +// +// MessageText: +// +// The network location cannot be reached. For information about network troubleshooting, see Windows Help. +// +pub const ERROR_PROTOCOL_UNREACHABLE: i32 = 1233; + +// +// MessageId: ERROR_PORT_UNREACHABLE +// +// MessageText: +// +// No service is operating at the destination network endpoint on the remote system. +// +pub const ERROR_PORT_UNREACHABLE: i32 = 1234; + +// +// MessageId: ERROR_REQUEST_ABORTED +// +// MessageText: +// +// The request was aborted. +// +pub const ERROR_REQUEST_ABORTED: i32 = 1235; + +// +// MessageId: ERROR_CONNECTION_ABORTED +// +// MessageText: +// +// The network connection was aborted by the local system. +// +pub const ERROR_CONNECTION_ABORTED: i32 = 1236; + +// +// MessageId: ERROR_RETRY +// +// MessageText: +// +// The operation could not be completed. A retry should be performed. +// +pub const ERROR_RETRY: i32 = 1237; + +// +// MessageId: ERROR_CONNECTION_COUNT_LIMIT +// +// MessageText: +// +// A connection to the server could not be made because the limit on the number of concurrent connections for this account has been reached. +// +pub const ERROR_CONNECTION_COUNT_LIMIT: i32 = 1238; + +// +// MessageId: ERROR_LOGIN_TIME_RESTRICTION +// +// MessageText: +// +// Attempting to log in during an unauthorized time of day for this account. +// +pub const ERROR_LOGIN_TIME_RESTRICTION: i32 = 1239; + +// +// MessageId: ERROR_LOGIN_WKSTA_RESTRICTION +// +// MessageText: +// +// The account is not authorized to log in from this station. +// +pub const ERROR_LOGIN_WKSTA_RESTRICTION: i32 = 1240; + +// +// MessageId: ERROR_INCORRECT_ADDRESS +// +// MessageText: +// +// The network address could not be used for the operation requested. +// +pub const ERROR_INCORRECT_ADDRESS: i32 = 1241; + +// +// MessageId: ERROR_ALREADY_REGISTERED +// +// MessageText: +// +// The service is already registered. +// +pub const ERROR_ALREADY_REGISTERED: i32 = 1242; + +// +// MessageId: ERROR_SERVICE_NOT_FOUND +// +// MessageText: +// +// The specified service does not exist. +// +pub const ERROR_SERVICE_NOT_FOUND: i32 = 1243; + +// +// MessageId: ERROR_NOT_AUTHENTICATED +// +// MessageText: +// +// The operation being requested was not performed because the user has not been authenticated. +// +pub const ERROR_NOT_AUTHENTICATED: i32 = 1244; + +// +// MessageId: ERROR_NOT_LOGGED_ON +// +// MessageText: +// +// The operation being requested was not performed because the user has not logged on to the network. +// The specified service does not exist. +// +pub const ERROR_NOT_LOGGED_ON: i32 = 1245; + +// +// MessageId: ERROR_CONTINUE +// +// MessageText: +// +// Continue with work in progress. +// +pub const ERROR_CONTINUE: i32 = 1246; // dderror + +// +// MessageId: ERROR_ALREADY_INITIALIZED +// +// MessageText: +// +// An attempt was made to perform an initialization operation when initialization has already been completed. +// +pub const ERROR_ALREADY_INITIALIZED: i32 = 1247; + +// +// MessageId: ERROR_NO_MORE_DEVICES +// +// MessageText: +// +// No more local devices. +// +pub const ERROR_NO_MORE_DEVICES: i32 = 1248; // dderror + +// +// MessageId: ERROR_NO_SUCH_SITE +// +// MessageText: +// +// The specified site does not exist. +// +pub const ERROR_NO_SUCH_SITE: i32 = 1249; + +// +// MessageId: ERROR_DOMAIN_CONTROLLER_EXISTS +// +// MessageText: +// +// A domain controller with the specified name already exists. +// +pub const ERROR_DOMAIN_CONTROLLER_EXISTS: i32 = 1250; + +// +// MessageId: ERROR_ONLY_IF_CONNECTED +// +// MessageText: +// +// This operation is supported only when you are connected to the server. +// +pub const ERROR_ONLY_IF_CONNECTED: i32 = 1251; + +// +// MessageId: ERROR_OVERRIDE_NOCHANGES +// +// MessageText: +// +// The group policy framework should call the extension even if there are no changes. +// +pub const ERROR_OVERRIDE_NOCHANGES: i32 = 1252; + +// +// MessageId: ERROR_BAD_USER_PROFILE +// +// MessageText: +// +// The specified user does not have a valid profile. +// +pub const ERROR_BAD_USER_PROFILE: i32 = 1253; + +// +// MessageId: ERROR_NOT_SUPPORTED_ON_SBS +// +// MessageText: +// +// This operation is not supported on a computer running Windows Server 2003 for Small Business Server +// +pub const ERROR_NOT_SUPPORTED_ON_SBS: i32 = 1254; + +// +// MessageId: ERROR_SERVER_SHUTDOWN_IN_PROGRESS +// +// MessageText: +// +// The server machine is shutting down. +// +pub const ERROR_SERVER_SHUTDOWN_IN_PROGRESS: i32 = 1255; + +// +// MessageId: ERROR_HOST_DOWN +// +// MessageText: +// +// The remote system is not available. For information about network troubleshooting, see Windows Help. +// +pub const ERROR_HOST_DOWN: i32 = 1256; + +// +// MessageId: ERROR_NON_ACCOUNT_SID +// +// MessageText: +// +// The security identifier provided is not from an account domain. +// +pub const ERROR_NON_ACCOUNT_SID: i32 = 1257; + +// +// MessageId: ERROR_NON_DOMAIN_SID +// +// MessageText: +// +// The security identifier provided does not have a domain component. +// +pub const ERROR_NON_DOMAIN_SID: i32 = 1258; + +// +// MessageId: ERROR_APPHELP_BLOCK +// +// MessageText: +// +// AppHelp dialog canceled thus preventing the application from starting. +// +pub const ERROR_APPHELP_BLOCK: i32 = 1259; + +// +// MessageId: ERROR_ACCESS_DISABLED_BY_POLICY +// +// MessageText: +// +// Windows cannot open this program because it has been prevented by a software restriction policy. For more information, open Event Viewer or contact your system administrator. +// +pub const ERROR_ACCESS_DISABLED_BY_POLICY: i32 = 1260; + +// +// MessageId: ERROR_REG_NAT_CONSUMPTION +// +// MessageText: +// +// A program attempt to use an invalid register value. Normally caused by an uninitialized register. This error is Itanium specific. +// +pub const ERROR_REG_NAT_CONSUMPTION: i32 = 1261; + +// +// MessageId: ERROR_CSCSHARE_OFFLINE +// +// MessageText: +// +// The share is currently offline or does not exist. +// +pub const ERROR_CSCSHARE_OFFLINE: i32 = 1262; + +// +// MessageId: ERROR_PKINIT_FAILURE +// +// MessageText: +// +// The kerberos protocol encountered an error while validating the +// KDC certificate during smartcard logon. There is more information in the +// system event log. +// +pub const ERROR_PKINIT_FAILURE: i32 = 1263; + +// +// MessageId: ERROR_SMARTCARD_SUBSYSTEM_FAILURE +// +// MessageText: +// +// The kerberos protocol encountered an error while attempting to utilize +// the smartcard subsystem. +// +pub const ERROR_SMARTCARD_SUBSYSTEM_FAILURE: i32 = 1264; + +// +// MessageId: ERROR_DOWNGRADE_DETECTED +// +// MessageText: +// +// The system detected a possible attempt to compromise security. Please ensure that you can contact the server that authenticated you. +// +pub const ERROR_DOWNGRADE_DETECTED: i32 = 1265; + +// +// Do not use ID's 1266 - 1270 as the symbolicNames have been moved to SEC_E_* +// +// +// MessageId: ERROR_MACHINE_LOCKED +// +// MessageText: +// +// The machine is locked and can not be shut down without the force option. +// +pub const ERROR_MACHINE_LOCKED: i32 = 1271; + +// +// MessageId: ERROR_CALLBACK_SUPPLIED_INVALID_DATA +// +// MessageText: +// +// An application-defined callback gave invalid data when called. +// +pub const ERROR_CALLBACK_SUPPLIED_INVALID_DATA: i32 = 1273; + +// +// MessageId: ERROR_SYNC_FOREGROUND_REFRESH_REQUIRED +// +// MessageText: +// +// The group policy framework should call the extension in the synchronous foreground policy refresh. +// +pub const ERROR_SYNC_FOREGROUND_REFRESH_REQUIRED: i32 = 1274; + +// +// MessageId: ERROR_DRIVER_BLOCKED +// +// MessageText: +// +// This driver has been blocked from loading +// +pub const ERROR_DRIVER_BLOCKED: i32 = 1275; + +// +// MessageId: ERROR_INVALID_IMPORT_OF_NON_DLL +// +// MessageText: +// +// A dynamic link library (DLL) referenced a module that was neither a DLL nor the process's executable image. +// +pub const ERROR_INVALID_IMPORT_OF_NON_DLL: i32 = 1276; + +// +// MessageId: ERROR_ACCESS_DISABLED_WEBBLADE +// +// MessageText: +// +// Windows cannot open this program since it has been disabled. +// +pub const ERROR_ACCESS_DISABLED_WEBBLADE: i32 = 1277; + +// +// MessageId: ERROR_ACCESS_DISABLED_WEBBLADE_TAMPER +// +// MessageText: +// +// Windows cannot open this program because the license enforcement system has been tampered with or become corrupted. +// +pub const ERROR_ACCESS_DISABLED_WEBBLADE_TAMPER: i32 = 1278; + +// +// MessageId: ERROR_RECOVERY_FAILURE +// +// MessageText: +// +// A transaction recover failed. +// +pub const ERROR_RECOVERY_FAILURE: i32 = 1279; + +// +// MessageId: ERROR_ALREADY_FIBER +// +// MessageText: +// +// The current thread has already been converted to a fiber. +// +pub const ERROR_ALREADY_FIBER: i32 = 1280; + +// +// MessageId: ERROR_ALREADY_THREAD +// +// MessageText: +// +// The current thread has already been converted from a fiber. +// +pub const ERROR_ALREADY_THREAD: i32 = 1281; + +// +// MessageId: ERROR_STACK_BUFFER_OVERRUN +// +// MessageText: +// +// The system detected an overrun of a stack-based buffer in this application. This +// overrun could potentially allow a malicious user to gain control of this application. +// +pub const ERROR_STACK_BUFFER_OVERRUN: i32 = 1282; + +// +// MessageId: ERROR_PARAMETER_QUOTA_EXCEEDED +// +// MessageText: +// +// Data present in one of the parameters is more than the function can operate on. +// +pub const ERROR_PARAMETER_QUOTA_EXCEEDED: i32 = 1283; + +// +// MessageId: ERROR_DEBUGGER_INACTIVE +// +// MessageText: +// +// An attempt to do an operation on a debug object failed because the object is in the process of being deleted. +// +pub const ERROR_DEBUGGER_INACTIVE: i32 = 1284; + +// +// MessageId: ERROR_DELAY_LOAD_FAILED +// +// MessageText: +// +// An attempt to delay-load a .dll or get a function address in a delay-loaded .dll failed. +// +pub const ERROR_DELAY_LOAD_FAILED: i32 = 1285; + +// +// MessageId: ERROR_VDM_DISALLOWED +// +// MessageText: +// +// %1 is a 16-bit application. You do not have permissions to execute 16-bit applications. Check your permissions with your system administrator. +// +pub const ERROR_VDM_DISALLOWED: i32 = 1286; + +// +// MessageId: ERROR_UNIDENTIFIED_ERROR +// +// MessageText: +// +// Insufficient information exists to identify the cause of failure. +// +pub const ERROR_UNIDENTIFIED_ERROR: i32 = 1287; + +/////////////////////////// +// +// Add new status codes before this point unless there is a component specific section below. +// +/////////////////////////// + +/////////////////////////// +// // +// Security Status Codes // +// // +/////////////////////////// + +// +// MessageId: ERROR_NOT_ALL_ASSIGNED +// +// MessageText: +// +// Not all privileges referenced are assigned to the caller. +// +pub const ERROR_NOT_ALL_ASSIGNED: i32 = 1300; + +// +// MessageId: ERROR_SOME_NOT_MAPPED +// +// MessageText: +// +// Some mapping between account names and security IDs was not done. +// +pub const ERROR_SOME_NOT_MAPPED: i32 = 1301; + +// +// MessageId: ERROR_NO_QUOTAS_FOR_ACCOUNT +// +// MessageText: +// +// No system quota limits are specifically set for this account. +// +pub const ERROR_NO_QUOTAS_FOR_ACCOUNT: i32 = 1302; + +// +// MessageId: ERROR_LOCAL_USER_SESSION_KEY +// +// MessageText: +// +// No encryption key is available. A well-known encryption key was returned. +// +pub const ERROR_LOCAL_USER_SESSION_KEY: i32 = 1303; + +// +// MessageId: ERROR_NULL_LM_PASSWORD +// +// MessageText: +// +// The password is too complex to be converted to a LAN Manager password. The LAN Manager password returned is a NULL string. +// +pub const ERROR_NULL_LM_PASSWORD: i32 = 1304; + +// +// MessageId: ERROR_UNKNOWN_REVISION +// +// MessageText: +// +// The revision level is unknown. +// +pub const ERROR_UNKNOWN_REVISION: i32 = 1305; + +// +// MessageId: ERROR_REVISION_MISMATCH +// +// MessageText: +// +// Indicates two revision levels are incompatible. +// +pub const ERROR_REVISION_MISMATCH: i32 = 1306; + +// +// MessageId: ERROR_INVALID_OWNER +// +// MessageText: +// +// This security ID may not be assigned as the owner of this object. +// +pub const ERROR_INVALID_OWNER: i32 = 1307; + +// +// MessageId: ERROR_INVALID_PRIMARY_GROUP +// +// MessageText: +// +// This security ID may not be assigned as the primary group of an object. +// +pub const ERROR_INVALID_PRIMARY_GROUP: i32 = 1308; + +// +// MessageId: ERROR_NO_IMPERSONATION_TOKEN +// +// MessageText: +// +// An attempt has been made to operate on an impersonation token by a thread that is not currently impersonating a client. +// +pub const ERROR_NO_IMPERSONATION_TOKEN: i32 = 1309; + +// +// MessageId: ERROR_CANT_DISABLE_MANDATORY +// +// MessageText: +// +// The group may not be disabled. +// +pub const ERROR_CANT_DISABLE_MANDATORY: i32 = 1310; + +// +// MessageId: ERROR_NO_LOGON_SERVERS +// +// MessageText: +// +// There are currently no logon servers available to service the logon request. +// +pub const ERROR_NO_LOGON_SERVERS: i32 = 1311; + +// +// MessageId: ERROR_NO_SUCH_LOGON_SESSION +// +// MessageText: +// +// A specified logon session does not exist. It may already have been terminated. +// +pub const ERROR_NO_SUCH_LOGON_SESSION: i32 = 1312; + +// +// MessageId: ERROR_NO_SUCH_PRIVILEGE +// +// MessageText: +// +// A specified privilege does not exist. +// +pub const ERROR_NO_SUCH_PRIVILEGE: i32 = 1313; + +// +// MessageId: ERROR_PRIVILEGE_NOT_HELD +// +// MessageText: +// +// A required privilege is not held by the client. +// +pub const ERROR_PRIVILEGE_NOT_HELD: i32 = 1314; + +// +// MessageId: ERROR_INVALID_ACCOUNT_NAME +// +// MessageText: +// +// The name provided is not a properly formed account name. +// +pub const ERROR_INVALID_ACCOUNT_NAME: i32 = 1315; + +// +// MessageId: ERROR_USER_EXISTS +// +// MessageText: +// +// The specified user already exists. +// +pub const ERROR_USER_EXISTS: i32 = 1316; + +// +// MessageId: ERROR_NO_SUCH_USER +// +// MessageText: +// +// The specified user does not exist. +// +pub const ERROR_NO_SUCH_USER: i32 = 1317; + +// +// MessageId: ERROR_GROUP_EXISTS +// +// MessageText: +// +// The specified group already exists. +// +pub const ERROR_GROUP_EXISTS: i32 = 1318; + +// +// MessageId: ERROR_NO_SUCH_GROUP +// +// MessageText: +// +// The specified group does not exist. +// +pub const ERROR_NO_SUCH_GROUP: i32 = 1319; + +// +// MessageId: ERROR_MEMBER_IN_GROUP +// +// MessageText: +// +// Either the specified user account is already a member of the specified group, or the specified group cannot be deleted because it contains a member. +// +pub const ERROR_MEMBER_IN_GROUP: i32 = 1320; + +// +// MessageId: ERROR_MEMBER_NOT_IN_GROUP +// +// MessageText: +// +// The specified user account is not a member of the specified group account. +// +pub const ERROR_MEMBER_NOT_IN_GROUP: i32 = 1321; + +// +// MessageId: ERROR_LAST_ADMIN +// +// MessageText: +// +// The last remaining administration account cannot be disabled or deleted. +// +pub const ERROR_LAST_ADMIN: i32 = 1322; + +// +// MessageId: ERROR_WRONG_PASSWORD +// +// MessageText: +// +// Unable to update the password. The value provided as the current password is incorrect. +// +pub const ERROR_WRONG_PASSWORD: i32 = 1323; + +// +// MessageId: ERROR_ILL_FORMED_PASSWORD +// +// MessageText: +// +// Unable to update the password. The value provided for the new password contains values that are not allowed in passwords. +// +pub const ERROR_ILL_FORMED_PASSWORD: i32 = 1324; + +// +// MessageId: ERROR_PASSWORD_RESTRICTION +// +// MessageText: +// +// Unable to update the password. The value provided for the new password does not meet the length, complexity, or history requirement of the domain. +// +pub const ERROR_PASSWORD_RESTRICTION: i32 = 1325; + +// +// MessageId: ERROR_LOGON_FAILURE +// +// MessageText: +// +// Logon failure: unknown user name or bad password. +// +pub const ERROR_LOGON_FAILURE: i32 = 1326; + +// +// MessageId: ERROR_ACCOUNT_RESTRICTION +// +// MessageText: +// +// Logon failure: user account restriction. Possible reasons are blank passwords not allowed, logon hour restrictions, or a policy restriction has been enforced. +// +pub const ERROR_ACCOUNT_RESTRICTION: i32 = 1327; + +// +// MessageId: ERROR_INVALID_LOGON_HOURS +// +// MessageText: +// +// Logon failure: account logon time restriction violation. +// +pub const ERROR_INVALID_LOGON_HOURS: i32 = 1328; + +// +// MessageId: ERROR_INVALID_WORKSTATION +// +// MessageText: +// +// Logon failure: user not allowed to log on to this computer. +// +pub const ERROR_INVALID_WORKSTATION: i32 = 1329; + +// +// MessageId: ERROR_PASSWORD_EXPIRED +// +// MessageText: +// +// Logon failure: the specified account password has expired. +// +pub const ERROR_PASSWORD_EXPIRED: i32 = 1330; + +// +// MessageId: ERROR_ACCOUNT_DISABLED +// +// MessageText: +// +// Logon failure: account currently disabled. +// +pub const ERROR_ACCOUNT_DISABLED: i32 = 1331; + +// +// MessageId: ERROR_NONE_MAPPED +// +// MessageText: +// +// No mapping between account names and security IDs was done. +// +pub const ERROR_NONE_MAPPED: i32 = 1332; + +// +// MessageId: ERROR_TOO_MANY_LUIDS_REQUESTED +// +// MessageText: +// +// Too many local user identifiers (LUIDs) were requested at one time. +// +pub const ERROR_TOO_MANY_LUIDS_REQUESTED: i32 = 1333; + +// +// MessageId: ERROR_LUIDS_EXHAUSTED +// +// MessageText: +// +// No more local user identifiers (LUIDs) are available. +// +pub const ERROR_LUIDS_EXHAUSTED: i32 = 1334; + +// +// MessageId: ERROR_INVALID_SUB_AUTHORITY +// +// MessageText: +// +// The subauthority part of a security ID is invalid for this particular use. +// +pub const ERROR_INVALID_SUB_AUTHORITY: i32 = 1335; + +// +// MessageId: ERROR_INVALID_ACL +// +// MessageText: +// +// The access control list (ACL) structure is invalid. +// +pub const ERROR_INVALID_ACL: i32 = 1336; + +// +// MessageId: ERROR_INVALID_SID +// +// MessageText: +// +// The security ID structure is invalid. +// +pub const ERROR_INVALID_SID: i32 = 1337; + +// +// MessageId: ERROR_INVALID_SECURITY_DESCR +// +// MessageText: +// +// The security descriptor structure is invalid. +// +pub const ERROR_INVALID_SECURITY_DESCR: i32 = 1338; + +// +// MessageId: ERROR_BAD_INHERITANCE_ACL +// +// MessageText: +// +// The inherited access control list (ACL) or access control entry (ACE) could not be built. +// +pub const ERROR_BAD_INHERITANCE_ACL: i32 = 1340; + +// +// MessageId: ERROR_SERVER_DISABLED +// +// MessageText: +// +// The server is currently disabled. +// +pub const ERROR_SERVER_DISABLED: i32 = 1341; + +// +// MessageId: ERROR_SERVER_NOT_DISABLED +// +// MessageText: +// +// The server is currently enabled. +// +pub const ERROR_SERVER_NOT_DISABLED: i32 = 1342; + +// +// MessageId: ERROR_INVALID_ID_AUTHORITY +// +// MessageText: +// +// The value provided was an invalid value for an identifier authority. +// +pub const ERROR_INVALID_ID_AUTHORITY: i32 = 1343; + +// +// MessageId: ERROR_ALLOTTED_SPACE_EXCEEDED +// +// MessageText: +// +// No more memory is available for security information updates. +// +pub const ERROR_ALLOTTED_SPACE_EXCEEDED: i32 = 1344; + +// +// MessageId: ERROR_INVALID_GROUP_ATTRIBUTES +// +// MessageText: +// +// The specified attributes are invalid, or incompatible with the attributes for the group as a whole. +// +pub const ERROR_INVALID_GROUP_ATTRIBUTES: i32 = 1345; + +// +// MessageId: ERROR_BAD_IMPERSONATION_LEVEL +// +// MessageText: +// +// Either a required impersonation level was not provided, or the provided impersonation level is invalid. +// +pub const ERROR_BAD_IMPERSONATION_LEVEL: i32 = 1346; + +// +// MessageId: ERROR_CANT_OPEN_ANONYMOUS +// +// MessageText: +// +// Cannot open an anonymous level security token. +// +pub const ERROR_CANT_OPEN_ANONYMOUS: i32 = 1347; + +// +// MessageId: ERROR_BAD_VALIDATION_CLASS +// +// MessageText: +// +// The validation information class requested was invalid. +// +pub const ERROR_BAD_VALIDATION_CLASS: i32 = 1348; + +// +// MessageId: ERROR_BAD_TOKEN_TYPE +// +// MessageText: +// +// The type of the token is inappropriate for its attempted use. +// +pub const ERROR_BAD_TOKEN_TYPE: i32 = 1349; + +// +// MessageId: ERROR_NO_SECURITY_ON_OBJECT +// +// MessageText: +// +// Unable to perform a security operation on an object that has no associated security. +// +pub const ERROR_NO_SECURITY_ON_OBJECT: i32 = 1350; + +// +// MessageId: ERROR_CANT_ACCESS_DOMAIN_INFO +// +// MessageText: +// +// Configuration information could not be read from the domain controller, either because the machine is unavailable, or access has been denied. +// +pub const ERROR_CANT_ACCESS_DOMAIN_INFO: i32 = 1351; + +// +// MessageId: ERROR_INVALID_SERVER_STATE +// +// MessageText: +// +// The security account manager (SAM) or local security authority (LSA) server was in the wrong state to perform the security operation. +// +pub const ERROR_INVALID_SERVER_STATE: i32 = 1352; + +// +// MessageId: ERROR_INVALID_DOMAIN_STATE +// +// MessageText: +// +// The domain was in the wrong state to perform the security operation. +// +pub const ERROR_INVALID_DOMAIN_STATE: i32 = 1353; + +// +// MessageId: ERROR_INVALID_DOMAIN_ROLE +// +// MessageText: +// +// This operation is only allowed for the Primary Domain Controller of the domain. +// +pub const ERROR_INVALID_DOMAIN_ROLE: i32 = 1354; + +// +// MessageId: ERROR_NO_SUCH_DOMAIN +// +// MessageText: +// +// The specified domain either does not exist or could not be contacted. +// +pub const ERROR_NO_SUCH_DOMAIN: i32 = 1355; + +// +// MessageId: ERROR_DOMAIN_EXISTS +// +// MessageText: +// +// The specified domain already exists. +// +pub const ERROR_DOMAIN_EXISTS: i32 = 1356; + +// +// MessageId: ERROR_DOMAIN_LIMIT_EXCEEDED +// +// MessageText: +// +// An attempt was made to exceed the limit on the number of domains per server. +// +pub const ERROR_DOMAIN_LIMIT_EXCEEDED: i32 = 1357; + +// +// MessageId: ERROR_INTERNAL_DB_CORRUPTION +// +// MessageText: +// +// Unable to complete the requested operation because of either a catastrophic media failure or a data structure corruption on the disk. +// +pub const ERROR_INTERNAL_DB_CORRUPTION: i32 = 1358; + +// +// MessageId: ERROR_INTERNAL_ERROR +// +// MessageText: +// +// An internal error occurred. +// +pub const ERROR_INTERNAL_ERROR: i32 = 1359; + +// +// MessageId: ERROR_GENERIC_NOT_MAPPED +// +// MessageText: +// +// Generic access types were contained in an access mask which should already be mapped to nongeneric types. +// +pub const ERROR_GENERIC_NOT_MAPPED: i32 = 1360; + +// +// MessageId: ERROR_BAD_DESCRIPTOR_FORMAT +// +// MessageText: +// +// A security descriptor is not in the right format (absolute or self-relative). +// +pub const ERROR_BAD_DESCRIPTOR_FORMAT: i32 = 1361; + +// +// MessageId: ERROR_NOT_LOGON_PROCESS +// +// MessageText: +// +// The requested action is restricted for use by logon processes only. The calling process has not registered as a logon process. +// +pub const ERROR_NOT_LOGON_PROCESS: i32 = 1362; + +// +// MessageId: ERROR_LOGON_SESSION_EXISTS +// +// MessageText: +// +// Cannot start a new logon session with an ID that is already in use. +// +pub const ERROR_LOGON_SESSION_EXISTS: i32 = 1363; + +// +// MessageId: ERROR_NO_SUCH_PACKAGE +// +// MessageText: +// +// A specified authentication package is unknown. +// +pub const ERROR_NO_SUCH_PACKAGE: i32 = 1364; + +// +// MessageId: ERROR_BAD_LOGON_SESSION_STATE +// +// MessageText: +// +// The logon session is not in a state that is consistent with the requested operation. +// +pub const ERROR_BAD_LOGON_SESSION_STATE: i32 = 1365; + +// +// MessageId: ERROR_LOGON_SESSION_COLLISION +// +// MessageText: +// +// The logon session ID is already in use. +// +pub const ERROR_LOGON_SESSION_COLLISION: i32 = 1366; + +// +// MessageId: ERROR_INVALID_LOGON_TYPE +// +// MessageText: +// +// A logon request contained an invalid logon type value. +// +pub const ERROR_INVALID_LOGON_TYPE: i32 = 1367; + +// +// MessageId: ERROR_CANNOT_IMPERSONATE +// +// MessageText: +// +// Unable to impersonate using a named pipe until data has been read from that pipe. +// +pub const ERROR_CANNOT_IMPERSONATE: i32 = 1368; + +// +// MessageId: ERROR_RXACT_INVALID_STATE +// +// MessageText: +// +// The transaction state of a registry subtree is incompatible with the requested operation. +// +pub const ERROR_RXACT_INVALID_STATE: i32 = 1369; + +// +// MessageId: ERROR_RXACT_COMMIT_FAILURE +// +// MessageText: +// +// An internal security database corruption has been encountered. +// +pub const ERROR_RXACT_COMMIT_FAILURE: i32 = 1370; + +// +// MessageId: ERROR_SPECIAL_ACCOUNT +// +// MessageText: +// +// Cannot perform this operation on built-in accounts. +// +pub const ERROR_SPECIAL_ACCOUNT: i32 = 1371; + +// +// MessageId: ERROR_SPECIAL_GROUP +// +// MessageText: +// +// Cannot perform this operation on this built-in special group. +// +pub const ERROR_SPECIAL_GROUP: i32 = 1372; + +// +// MessageId: ERROR_SPECIAL_USER +// +// MessageText: +// +// Cannot perform this operation on this built-in special user. +// +pub const ERROR_SPECIAL_USER: i32 = 1373; + +// +// MessageId: ERROR_MEMBERS_PRIMARY_GROUP +// +// MessageText: +// +// The user cannot be removed from a group because the group is currently the user's primary group. +// +pub const ERROR_MEMBERS_PRIMARY_GROUP: i32 = 1374; + +// +// MessageId: ERROR_TOKEN_ALREADY_IN_USE +// +// MessageText: +// +// The token is already in use as a primary token. +// +pub const ERROR_TOKEN_ALREADY_IN_USE: i32 = 1375; + +// +// MessageId: ERROR_NO_SUCH_ALIAS +// +// MessageText: +// +// The specified local group does not exist. +// +pub const ERROR_NO_SUCH_ALIAS: i32 = 1376; + +// +// MessageId: ERROR_MEMBER_NOT_IN_ALIAS +// +// MessageText: +// +// The specified account name is not a member of the local group. +// +pub const ERROR_MEMBER_NOT_IN_ALIAS: i32 = 1377; + +// +// MessageId: ERROR_MEMBER_IN_ALIAS +// +// MessageText: +// +// The specified account name is already a member of the local group. +// +pub const ERROR_MEMBER_IN_ALIAS: i32 = 1378; + +// +// MessageId: ERROR_ALIAS_EXISTS +// +// MessageText: +// +// The specified local group already exists. +// +pub const ERROR_ALIAS_EXISTS: i32 = 1379; + +// +// MessageId: ERROR_LOGON_NOT_GRANTED +// +// MessageText: +// +// Logon failure: the user has not been granted the requested logon type at this computer. +// +pub const ERROR_LOGON_NOT_GRANTED: i32 = 1380; + +// +// MessageId: ERROR_TOO_MANY_SECRETS +// +// MessageText: +// +// The maximum number of secrets that may be stored in a single system has been exceeded. +// +pub const ERROR_TOO_MANY_SECRETS: i32 = 1381; + +// +// MessageId: ERROR_SECRET_TOO_LONG +// +// MessageText: +// +// The length of a secret exceeds the maximum length allowed. +// +pub const ERROR_SECRET_TOO_LONG: i32 = 1382; + +// +// MessageId: ERROR_INTERNAL_DB_ERROR +// +// MessageText: +// +// The local security authority database contains an internal inconsistency. +// +pub const ERROR_INTERNAL_DB_ERROR: i32 = 1383; + +// +// MessageId: ERROR_TOO_MANY_CONTEXT_IDS +// +// MessageText: +// +// During a logon attempt, the user's security context accumulated too many security IDs. +// +pub const ERROR_TOO_MANY_CONTEXT_IDS: i32 = 1384; + +// +// MessageId: ERROR_LOGON_TYPE_NOT_GRANTED +// +// MessageText: +// +// Logon failure: the user has not been granted the requested logon type at this computer. +// +pub const ERROR_LOGON_TYPE_NOT_GRANTED: i32 = 1385; + +// +// MessageId: ERROR_NT_CROSS_ENCRYPTION_REQUIRED +// +// MessageText: +// +// A cross-encrypted password is necessary to change a user password. +// +pub const ERROR_NT_CROSS_ENCRYPTION_REQUIRED: i32 = 1386; + +// +// MessageId: ERROR_NO_SUCH_MEMBER +// +// MessageText: +// +// A member could not be added to or removed from the local group because the member does not exist. +// +pub const ERROR_NO_SUCH_MEMBER: i32 = 1387; + +// +// MessageId: ERROR_INVALID_MEMBER +// +// MessageText: +// +// A new member could not be added to a local group because the member has the wrong account type. +// +pub const ERROR_INVALID_MEMBER: i32 = 1388; + +// +// MessageId: ERROR_TOO_MANY_SIDS +// +// MessageText: +// +// Too many security IDs have been specified. +// +pub const ERROR_TOO_MANY_SIDS: i32 = 1389; + +// +// MessageId: ERROR_LM_CROSS_ENCRYPTION_REQUIRED +// +// MessageText: +// +// A cross-encrypted password is necessary to change this user password. +// +pub const ERROR_LM_CROSS_ENCRYPTION_REQUIRED: i32 = 1390; + +// +// MessageId: ERROR_NO_INHERITANCE +// +// MessageText: +// +// Indicates an ACL contains no inheritable components. +// +pub const ERROR_NO_INHERITANCE: i32 = 1391; + +// +// MessageId: ERROR_FILE_CORRUPT +// +// MessageText: +// +// The file or directory is corrupted and unreadable. +// +pub const ERROR_FILE_CORRUPT: i32 = 1392; + +// +// MessageId: ERROR_DISK_CORRUPT +// +// MessageText: +// +// The disk structure is corrupted and unreadable. +// +pub const ERROR_DISK_CORRUPT: i32 = 1393; + +// +// MessageId: ERROR_NO_USER_SESSION_KEY +// +// MessageText: +// +// There is no user session key for the specified logon session. +// +pub const ERROR_NO_USER_SESSION_KEY: i32 = 1394; + +// +// MessageId: ERROR_LICENSE_QUOTA_EXCEEDED +// +// MessageText: +// +// The service being accessed is licensed for a particular number of connections. +// No more connections can be made to the service at this time because there are already as many connections as the service can accept. +// +pub const ERROR_LICENSE_QUOTA_EXCEEDED: i32 = 1395; + +// +// MessageId: ERROR_WRONG_TARGET_NAME +// +// MessageText: +// +// Logon Failure: The target account name is incorrect. +// +pub const ERROR_WRONG_TARGET_NAME: i32 = 1396; + +// +// MessageId: ERROR_MUTUAL_AUTH_FAILED +// +// MessageText: +// +// Mutual Authentication failed. The server's password is out of date at the domain controller. +// +pub const ERROR_MUTUAL_AUTH_FAILED: i32 = 1397; + +// +// MessageId: ERROR_TIME_SKEW +// +// MessageText: +// +// There is a time and/or date difference between the client and server. +// +pub const ERROR_TIME_SKEW: i32 = 1398; + +// +// MessageId: ERROR_CURRENT_DOMAIN_NOT_ALLOWED +// +// MessageText: +// +// This operation can not be performed on the current domain. +// +pub const ERROR_CURRENT_DOMAIN_NOT_ALLOWED: i32 = 1399; + +// End of security error codes + +/////////////////////////// +// // +// WinUser Error Codes // +// // +/////////////////////////// + +// +// MessageId: ERROR_INVALID_WINDOW_HANDLE +// +// MessageText: +// +// Invalid window handle. +// +pub const ERROR_INVALID_WINDOW_HANDLE: i32 = 1400; + +// +// MessageId: ERROR_INVALID_MENU_HANDLE +// +// MessageText: +// +// Invalid menu handle. +// +pub const ERROR_INVALID_MENU_HANDLE: i32 = 1401; + +// +// MessageId: ERROR_INVALID_CURSOR_HANDLE +// +// MessageText: +// +// Invalid cursor handle. +// +pub const ERROR_INVALID_CURSOR_HANDLE: i32 = 1402; + +// +// MessageId: ERROR_INVALID_ACCEL_HANDLE +// +// MessageText: +// +// Invalid accelerator table handle. +// +pub const ERROR_INVALID_ACCEL_HANDLE: i32 = 1403; + +// +// MessageId: ERROR_INVALID_HOOK_HANDLE +// +// MessageText: +// +// Invalid hook handle. +// +pub const ERROR_INVALID_HOOK_HANDLE: i32 = 1404; + +// +// MessageId: ERROR_INVALID_DWP_HANDLE +// +// MessageText: +// +// Invalid handle to a multiple-window position structure. +// +pub const ERROR_INVALID_DWP_HANDLE: i32 = 1405; + +// +// MessageId: ERROR_TLW_WITH_WSCHILD +// +// MessageText: +// +// Cannot create a top-level child window. +// +pub const ERROR_TLW_WITH_WSCHILD: i32 = 1406; + +// +// MessageId: ERROR_CANNOT_FIND_WND_CLASS +// +// MessageText: +// +// Cannot find window class. +// +pub const ERROR_CANNOT_FIND_WND_CLASS: i32 = 1407; + +// +// MessageId: ERROR_WINDOW_OF_OTHER_THREAD +// +// MessageText: +// +// Invalid window; it belongs to other thread. +// +pub const ERROR_WINDOW_OF_OTHER_THREAD: i32 = 1408; + +// +// MessageId: ERROR_HOTKEY_ALREADY_REGISTERED +// +// MessageText: +// +// Hot key is already registered. +// +pub const ERROR_HOTKEY_ALREADY_REGISTERED: i32 = 1409; + +// +// MessageId: ERROR_CLASS_ALREADY_EXISTS +// +// MessageText: +// +// Class already exists. +// +pub const ERROR_CLASS_ALREADY_EXISTS: i32 = 1410; + +// +// MessageId: ERROR_CLASS_DOES_NOT_EXIST +// +// MessageText: +// +// Class does not exist. +// +pub const ERROR_CLASS_DOES_NOT_EXIST: i32 = 1411; + +// +// MessageId: ERROR_CLASS_HAS_WINDOWS +// +// MessageText: +// +// Class still has open windows. +// +pub const ERROR_CLASS_HAS_WINDOWS: i32 = 1412; + +// +// MessageId: ERROR_INVALID_INDEX +// +// MessageText: +// +// Invalid index. +// +pub const ERROR_INVALID_INDEX: i32 = 1413; + +// +// MessageId: ERROR_INVALID_ICON_HANDLE +// +// MessageText: +// +// Invalid icon handle. +// +pub const ERROR_INVALID_ICON_HANDLE: i32 = 1414; + +// +// MessageId: ERROR_PRIVATE_DIALOG_INDEX +// +// MessageText: +// +// Using private DIALOG window words. +// +pub const ERROR_PRIVATE_DIALOG_INDEX: i32 = 1415; + +// +// MessageId: ERROR_LISTBOX_ID_NOT_FOUND +// +// MessageText: +// +// The list box identifier was not found. +// +pub const ERROR_LISTBOX_ID_NOT_FOUND: i32 = 1416; + +// +// MessageId: ERROR_NO_WILDCARD_CHARACTERS +// +// MessageText: +// +// No wildcards were found. +// +pub const ERROR_NO_WILDCARD_CHARACTERS: i32 = 1417; + +// +// MessageId: ERROR_CLIPBOARD_NOT_OPEN +// +// MessageText: +// +// Thread does not have a clipboard open. +// +pub const ERROR_CLIPBOARD_NOT_OPEN: i32 = 1418; + +// +// MessageId: ERROR_HOTKEY_NOT_REGISTERED +// +// MessageText: +// +// Hot key is not registered. +// +pub const ERROR_HOTKEY_NOT_REGISTERED: i32 = 1419; + +// +// MessageId: ERROR_WINDOW_NOT_DIALOG +// +// MessageText: +// +// The window is not a valid dialog window. +// +pub const ERROR_WINDOW_NOT_DIALOG: i32 = 1420; + +// +// MessageId: ERROR_CONTROL_ID_NOT_FOUND +// +// MessageText: +// +// Control ID not found. +// +pub const ERROR_CONTROL_ID_NOT_FOUND: i32 = 1421; + +// +// MessageId: ERROR_INVALID_COMBOBOX_MESSAGE +// +// MessageText: +// +// Invalid message for a combo box because it does not have an edit control. +// +pub const ERROR_INVALID_COMBOBOX_MESSAGE: i32 = 1422; + +// +// MessageId: ERROR_WINDOW_NOT_COMBOBOX +// +// MessageText: +// +// The window is not a combo box. +// +pub const ERROR_WINDOW_NOT_COMBOBOX: i32 = 1423; + +// +// MessageId: ERROR_INVALID_EDIT_HEIGHT +// +// MessageText: +// +// Height must be less than 256. +// +pub const ERROR_INVALID_EDIT_HEIGHT: i32 = 1424; + +// +// MessageId: ERROR_DC_NOT_FOUND +// +// MessageText: +// +// Invalid device context (DC) handle. +// +pub const ERROR_DC_NOT_FOUND: i32 = 1425; + +// +// MessageId: ERROR_INVALID_HOOK_FILTER +// +// MessageText: +// +// Invalid hook procedure type. +// +pub const ERROR_INVALID_HOOK_FILTER: i32 = 1426; + +// +// MessageId: ERROR_INVALID_FILTER_PROC +// +// MessageText: +// +// Invalid hook procedure. +// +pub const ERROR_INVALID_FILTER_PROC: i32 = 1427; + +// +// MessageId: ERROR_HOOK_NEEDS_HMOD +// +// MessageText: +// +// Cannot set nonlocal hook without a module handle. +// +pub const ERROR_HOOK_NEEDS_HMOD: i32 = 1428; + +// +// MessageId: ERROR_GLOBAL_ONLY_HOOK +// +// MessageText: +// +// This hook procedure can only be set globally. +// +pub const ERROR_GLOBAL_ONLY_HOOK: i32 = 1429; + +// +// MessageId: ERROR_JOURNAL_HOOK_SET +// +// MessageText: +// +// The journal hook procedure is already installed. +// +pub const ERROR_JOURNAL_HOOK_SET: i32 = 1430; + +// +// MessageId: ERROR_HOOK_NOT_INSTALLED +// +// MessageText: +// +// The hook procedure is not installed. +// +pub const ERROR_HOOK_NOT_INSTALLED: i32 = 1431; + +// +// MessageId: ERROR_INVALID_LB_MESSAGE +// +// MessageText: +// +// Invalid message for single-selection list box. +// +pub const ERROR_INVALID_LB_MESSAGE: i32 = 1432; + +// +// MessageId: ERROR_SETCOUNT_ON_BAD_LB +// +// MessageText: +// +// LB_SETCOUNT sent to non-lazy list box. +// +pub const ERROR_SETCOUNT_ON_BAD_LB: i32 = 1433; + +// +// MessageId: ERROR_LB_WITHOUT_TABSTOPS +// +// MessageText: +// +// This list box does not support tab stops. +// +pub const ERROR_LB_WITHOUT_TABSTOPS: i32 = 1434; + +// +// MessageId: ERROR_DESTROY_OBJECT_OF_OTHER_THREAD +// +// MessageText: +// +// Cannot destroy object created by another thread. +// +pub const ERROR_DESTROY_OBJECT_OF_OTHER_THREAD: i32 = 1435; + +// +// MessageId: ERROR_CHILD_WINDOW_MENU +// +// MessageText: +// +// Child windows cannot have menus. +// +pub const ERROR_CHILD_WINDOW_MENU: i32 = 1436; + +// +// MessageId: ERROR_NO_SYSTEM_MENU +// +// MessageText: +// +// The window does not have a system menu. +// +pub const ERROR_NO_SYSTEM_MENU: i32 = 1437; + +// +// MessageId: ERROR_INVALID_MSGBOX_STYLE +// +// MessageText: +// +// Invalid message box style. +// +pub const ERROR_INVALID_MSGBOX_STYLE: i32 = 1438; + +// +// MessageId: ERROR_INVALID_SPI_VALUE +// +// MessageText: +// +// Invalid system-wide (SPI_*) parameter. +// +pub const ERROR_INVALID_SPI_VALUE: i32 = 1439; + +// +// MessageId: ERROR_SCREEN_ALREADY_LOCKED +// +// MessageText: +// +// Screen already locked. +// +pub const ERROR_SCREEN_ALREADY_LOCKED: i32 = 1440; + +// +// MessageId: ERROR_HWNDS_HAVE_DIFF_PARENT +// +// MessageText: +// +// All handles to windows in a multiple-window position structure must have the same parent. +// +pub const ERROR_HWNDS_HAVE_DIFF_PARENT: i32 = 1441; + +// +// MessageId: ERROR_NOT_CHILD_WINDOW +// +// MessageText: +// +// The window is not a child window. +// +pub const ERROR_NOT_CHILD_WINDOW: i32 = 1442; + +// +// MessageId: ERROR_INVALID_GW_COMMAND +// +// MessageText: +// +// Invalid GW_* command. +// +pub const ERROR_INVALID_GW_COMMAND: i32 = 1443; + +// +// MessageId: ERROR_INVALID_THREAD_ID +// +// MessageText: +// +// Invalid thread identifier. +// +pub const ERROR_INVALID_THREAD_ID: i32 = 1444; + +// +// MessageId: ERROR_NON_MDICHILD_WINDOW +// +// MessageText: +// +// Cannot process a message from a window that is not a multiple document interface (MDI) window. +// +pub const ERROR_NON_MDICHILD_WINDOW: i32 = 1445; + +// +// MessageId: ERROR_POPUP_ALREADY_ACTIVE +// +// MessageText: +// +// Popup menu already active. +// +pub const ERROR_POPUP_ALREADY_ACTIVE: i32 = 1446; + +// +// MessageId: ERROR_NO_SCROLLBARS +// +// MessageText: +// +// The window does not have scroll bars. +// +pub const ERROR_NO_SCROLLBARS: i32 = 1447; + +// +// MessageId: ERROR_INVALID_SCROLLBAR_RANGE +// +// MessageText: +// +// Scroll bar range cannot be greater than MAXLONG. +// +pub const ERROR_INVALID_SCROLLBAR_RANGE: i32 = 1448; + +// +// MessageId: ERROR_INVALID_SHOWWIN_COMMAND +// +// MessageText: +// +// Cannot show or remove the window in the way specified. +// +pub const ERROR_INVALID_SHOWWIN_COMMAND: i32 = 1449; + +// +// MessageId: ERROR_NO_SYSTEM_RESOURCES +// +// MessageText: +// +// Insufficient system resources exist to complete the requested service. +// +pub const ERROR_NO_SYSTEM_RESOURCES: i32 = 1450; + +// +// MessageId: ERROR_NONPAGED_SYSTEM_RESOURCES +// +// MessageText: +// +// Insufficient system resources exist to complete the requested service. +// +pub const ERROR_NONPAGED_SYSTEM_RESOURCES: i32 = 1451; + +// +// MessageId: ERROR_PAGED_SYSTEM_RESOURCES +// +// MessageText: +// +// Insufficient system resources exist to complete the requested service. +// +pub const ERROR_PAGED_SYSTEM_RESOURCES: i32 = 1452; + +// +// MessageId: ERROR_WORKING_SET_QUOTA +// +// MessageText: +// +// Insufficient quota to complete the requested service. +// +pub const ERROR_WORKING_SET_QUOTA: i32 = 1453; + +// +// MessageId: ERROR_PAGEFILE_QUOTA +// +// MessageText: +// +// Insufficient quota to complete the requested service. +// +pub const ERROR_PAGEFILE_QUOTA: i32 = 1454; + +// +// MessageId: ERROR_COMMITMENT_LIMIT +// +// MessageText: +// +// The paging file is too small for this operation to complete. +// +pub const ERROR_COMMITMENT_LIMIT: i32 = 1455; + +// +// MessageId: ERROR_MENU_ITEM_NOT_FOUND +// +// MessageText: +// +// A menu item was not found. +// +pub const ERROR_MENU_ITEM_NOT_FOUND: i32 = 1456; + +// +// MessageId: ERROR_INVALID_KEYBOARD_HANDLE +// +// MessageText: +// +// Invalid keyboard layout handle. +// +pub const ERROR_INVALID_KEYBOARD_HANDLE: i32 = 1457; + +// +// MessageId: ERROR_HOOK_TYPE_NOT_ALLOWED +// +// MessageText: +// +// Hook type not allowed. +// +pub const ERROR_HOOK_TYPE_NOT_ALLOWED: i32 = 1458; + +// +// MessageId: ERROR_REQUIRES_INTERACTIVE_WINDOWSTATION +// +// MessageText: +// +// This operation requires an interactive window station. +// +pub const ERROR_REQUIRES_INTERACTIVE_WINDOWSTATION: i32 = 1459; + +// +// MessageId: ERROR_TIMEOUT +// +// MessageText: +// +// This operation returned because the timeout period expired. +// +pub const ERROR_TIMEOUT: i32 = 1460; + +// +// MessageId: ERROR_INVALID_MONITOR_HANDLE +// +// MessageText: +// +// Invalid monitor handle. +// +pub const ERROR_INVALID_MONITOR_HANDLE: i32 = 1461; + +// +// MessageId: ERROR_INCORRECT_SIZE +// +// MessageText: +// +// Incorrect size argument. +// +pub const ERROR_INCORRECT_SIZE: i32 = 1462; + +// End of WinUser error codes + +/////////////////////////// +// // +// Eventlog Status Codes // +// // +/////////////////////////// + +// +// MessageId: ERROR_EVENTLOG_FILE_CORRUPT +// +// MessageText: +// +// The event log file is corrupted. +// +pub const ERROR_EVENTLOG_FILE_CORRUPT: i32 = 1500; + +// +// MessageId: ERROR_EVENTLOG_CANT_START +// +// MessageText: +// +// No event log file could be opened, so the event logging service did not start. +// +pub const ERROR_EVENTLOG_CANT_START: i32 = 1501; + +// +// MessageId: ERROR_LOG_FILE_FULL +// +// MessageText: +// +// The event log file is full. +// +pub const ERROR_LOG_FILE_FULL: i32 = 1502; + +// +// MessageId: ERROR_EVENTLOG_FILE_CHANGED +// +// MessageText: +// +// The event log file has changed between read operations. +// +pub const ERROR_EVENTLOG_FILE_CHANGED: i32 = 1503; + +// End of eventlog error codes + +/////////////////////////// +// // +// MSI Error Codes // +// // +/////////////////////////// + +// +// MessageId: ERROR_INSTALL_SERVICE_FAILURE +// +// MessageText: +// +// The Windows Installer Service could not be accessed. This can occur if you are running Windows in safe mode, or if the Windows Installer is not correctly installed. Contact your support personnel for assistance. +// +pub const ERROR_INSTALL_SERVICE_FAILURE: i32 = 1601; + +// +// MessageId: ERROR_INSTALL_USEREXIT +// +// MessageText: +// +// User cancelled installation. +// +pub const ERROR_INSTALL_USEREXIT: i32 = 1602; + +// +// MessageId: ERROR_INSTALL_FAILURE +// +// MessageText: +// +// Fatal error during installation. +// +pub const ERROR_INSTALL_FAILURE: i32 = 1603; + +// +// MessageId: ERROR_INSTALL_SUSPEND +// +// MessageText: +// +// Installation suspended, incomplete. +// +pub const ERROR_INSTALL_SUSPEND: i32 = 1604; + +// +// MessageId: ERROR_UNKNOWN_PRODUCT +// +// MessageText: +// +// This action is only valid for products that are currently installed. +// +pub const ERROR_UNKNOWN_PRODUCT: i32 = 1605; + +// +// MessageId: ERROR_UNKNOWN_FEATURE +// +// MessageText: +// +// Feature ID not registered. +// +pub const ERROR_UNKNOWN_FEATURE: i32 = 1606; + +// +// MessageId: ERROR_UNKNOWN_COMPONENT +// +// MessageText: +// +// Component ID not registered. +// +pub const ERROR_UNKNOWN_COMPONENT: i32 = 1607; + +// +// MessageId: ERROR_UNKNOWN_PROPERTY +// +// MessageText: +// +// Unknown property. +// +pub const ERROR_UNKNOWN_PROPERTY: i32 = 1608; + +// +// MessageId: ERROR_INVALID_HANDLE_STATE +// +// MessageText: +// +// Handle is in an invalid state. +// +pub const ERROR_INVALID_HANDLE_STATE: i32 = 1609; + +// +// MessageId: ERROR_BAD_CONFIGURATION +// +// MessageText: +// +// The configuration data for this product is corrupt. Contact your support personnel. +// +pub const ERROR_BAD_CONFIGURATION: i32 = 1610; + +// +// MessageId: ERROR_INDEX_ABSENT +// +// MessageText: +// +// Component qualifier not present. +// +pub const ERROR_INDEX_ABSENT: i32 = 1611; + +// +// MessageId: ERROR_INSTALL_SOURCE_ABSENT +// +// MessageText: +// +// The installation source for this product is not available. Verify that the source exists and that you can access it. +// +pub const ERROR_INSTALL_SOURCE_ABSENT: i32 = 1612; + +// +// MessageId: ERROR_INSTALL_PACKAGE_VERSION +// +// MessageText: +// +// This installation package cannot be installed by the Windows Installer service. You must install a Windows service pack that contains a newer version of the Windows Installer service. +// +pub const ERROR_INSTALL_PACKAGE_VERSION: i32 = 1613; + +// +// MessageId: ERROR_PRODUCT_UNINSTALLED +// +// MessageText: +// +// Product is uninstalled. +// +pub const ERROR_PRODUCT_UNINSTALLED: i32 = 1614; + +// +// MessageId: ERROR_BAD_QUERY_SYNTAX +// +// MessageText: +// +// SQL query syntax invalid or unsupported. +// +pub const ERROR_BAD_QUERY_SYNTAX: i32 = 1615; + +// +// MessageId: ERROR_INVALID_FIELD +// +// MessageText: +// +// Record field does not exist. +// +pub const ERROR_INVALID_FIELD: i32 = 1616; + +// +// MessageId: ERROR_DEVICE_REMOVED +// +// MessageText: +// +// The device has been removed. +// +pub const ERROR_DEVICE_REMOVED: i32 = 1617; + +// +// MessageId: ERROR_INSTALL_ALREADY_RUNNING +// +// MessageText: +// +// Another installation is already in progress. Complete that installation before proceeding with this install. +// +pub const ERROR_INSTALL_ALREADY_RUNNING: i32 = 1618; + +// +// MessageId: ERROR_INSTALL_PACKAGE_OPEN_FAILED +// +// MessageText: +// +// This installation package could not be opened. Verify that the package exists and that you can access it, or contact the application vendor to verify that this is a valid Windows Installer package. +// +pub const ERROR_INSTALL_PACKAGE_OPEN_FAILED: i32 = 1619; + +// +// MessageId: ERROR_INSTALL_PACKAGE_INVALID +// +// MessageText: +// +// This installation package could not be opened. Contact the application vendor to verify that this is a valid Windows Installer package. +// +pub const ERROR_INSTALL_PACKAGE_INVALID: i32 = 1620; + +// +// MessageId: ERROR_INSTALL_UI_FAILURE +// +// MessageText: +// +// There was an error starting the Windows Installer service user interface. Contact your support personnel. +// +pub const ERROR_INSTALL_UI_FAILURE: i32 = 1621; + +// +// MessageId: ERROR_INSTALL_LOG_FAILURE +// +// MessageText: +// +// Error opening installation log file. Verify that the specified log file location exists and that you can write to it. +// +pub const ERROR_INSTALL_LOG_FAILURE: i32 = 1622; + +// +// MessageId: ERROR_INSTALL_LANGUAGE_UNSUPPORTED +// +// MessageText: +// +// The language of this installation package is not supported by your system. +// +pub const ERROR_INSTALL_LANGUAGE_UNSUPPORTED: i32 = 1623; + +// +// MessageId: ERROR_INSTALL_TRANSFORM_FAILURE +// +// MessageText: +// +// Error applying transforms. Verify that the specified transform paths are valid. +// +pub const ERROR_INSTALL_TRANSFORM_FAILURE: i32 = 1624; + +// +// MessageId: ERROR_INSTALL_PACKAGE_REJECTED +// +// MessageText: +// +// This installation is forbidden by system policy. Contact your system administrator. +// +pub const ERROR_INSTALL_PACKAGE_REJECTED: i32 = 1625; + +// +// MessageId: ERROR_FUNCTION_NOT_CALLED +// +// MessageText: +// +// Function could not be executed. +// +pub const ERROR_FUNCTION_NOT_CALLED: i32 = 1626; + +// +// MessageId: ERROR_FUNCTION_FAILED +// +// MessageText: +// +// Function failed during execution. +// +pub const ERROR_FUNCTION_FAILED: i32 = 1627; + +// +// MessageId: ERROR_INVALID_TABLE +// +// MessageText: +// +// Invalid or unknown table specified. +// +pub const ERROR_INVALID_TABLE: i32 = 1628; + +// +// MessageId: ERROR_DATATYPE_MISMATCH +// +// MessageText: +// +// Data supplied is of wrong type. +// +pub const ERROR_DATATYPE_MISMATCH: i32 = 1629; + +// +// MessageId: ERROR_UNSUPPORTED_TYPE +// +// MessageText: +// +// Data of this type is not supported. +// +pub const ERROR_UNSUPPORTED_TYPE: i32 = 1630; + +// +// MessageId: ERROR_CREATE_FAILED +// +// MessageText: +// +// The Windows Installer service failed to start. Contact your support personnel. +// +pub const ERROR_CREATE_FAILED: i32 = 1631; + +// +// MessageId: ERROR_INSTALL_TEMP_UNWRITABLE +// +// MessageText: +// +// The Temp folder is on a drive that is full or is inaccessible. Free up space on the drive or verify that you have write permission on the Temp folder. +// +pub const ERROR_INSTALL_TEMP_UNWRITABLE: i32 = 1632; + +// +// MessageId: ERROR_INSTALL_PLATFORM_UNSUPPORTED +// +// MessageText: +// +// This installation package is not supported by this processor type. Contact your product vendor. +// +pub const ERROR_INSTALL_PLATFORM_UNSUPPORTED: i32 = 1633; + +// +// MessageId: ERROR_INSTALL_NOTUSED +// +// MessageText: +// +// Component not used on this computer. +// +pub const ERROR_INSTALL_NOTUSED: i32 = 1634; + +// +// MessageId: ERROR_PATCH_PACKAGE_OPEN_FAILED +// +// MessageText: +// +// This patch package could not be opened. Verify that the patch package exists and that you can access it, or contact the application vendor to verify that this is a valid Windows Installer patch package. +// +pub const ERROR_PATCH_PACKAGE_OPEN_FAILED: i32 = 1635; + +// +// MessageId: ERROR_PATCH_PACKAGE_INVALID +// +// MessageText: +// +// This patch package could not be opened. Contact the application vendor to verify that this is a valid Windows Installer patch package. +// +pub const ERROR_PATCH_PACKAGE_INVALID: i32 = 1636; + +// +// MessageId: ERROR_PATCH_PACKAGE_UNSUPPORTED +// +// MessageText: +// +// This patch package cannot be processed by the Windows Installer service. You must install a Windows service pack that contains a newer version of the Windows Installer service. +// +pub const ERROR_PATCH_PACKAGE_UNSUPPORTED: i32 = 1637; + +// +// MessageId: ERROR_PRODUCT_VERSION +// +// MessageText: +// +// Another version of this product is already installed. Installation of this version cannot continue. To configure or remove the existing version of this product, use Add/Remove Programs on the Control Panel. +// +pub const ERROR_PRODUCT_VERSION: i32 = 1638; + +// +// MessageId: ERROR_INVALID_COMMAND_LINE +// +// MessageText: +// +// Invalid command line argument. Consult the Windows Installer SDK for detailed command line help. +// +pub const ERROR_INVALID_COMMAND_LINE: i32 = 1639; + +// +// MessageId: ERROR_INSTALL_REMOTE_DISALLOWED +// +// MessageText: +// +// Only administrators have permission to add, remove, or configure server software during a Terminal services remote session. If you want to install or configure software on the server, contact your network administrator. +// +pub const ERROR_INSTALL_REMOTE_DISALLOWED: i32 = 1640; + +// +// MessageId: ERROR_SUCCESS_REBOOT_INITIATED +// +// MessageText: +// +// The requested operation completed successfully. The system will be restarted so the changes can take effect. +// +pub const ERROR_SUCCESS_REBOOT_INITIATED: i32 = 1641; + +// +// MessageId: ERROR_PATCH_TARGET_NOT_FOUND +// +// MessageText: +// +// The upgrade patch cannot be installed by the Windows Installer service because the program to be upgraded may be missing, or the upgrade patch may update a different version of the program. Verify that the program to be upgraded exists on your computer an +// d that you have the correct upgrade patch. +// +pub const ERROR_PATCH_TARGET_NOT_FOUND: i32 = 1642; + +// +// MessageId: ERROR_PATCH_PACKAGE_REJECTED +// +// MessageText: +// +// The patch package is not permitted by software restriction policy. +// +pub const ERROR_PATCH_PACKAGE_REJECTED: i32 = 1643; + +// +// MessageId: ERROR_INSTALL_TRANSFORM_REJECTED +// +// MessageText: +// +// One or more customizations are not permitted by software restriction policy. +// +pub const ERROR_INSTALL_TRANSFORM_REJECTED: i32 = 1644; + +// +// MessageId: ERROR_INSTALL_REMOTE_PROHIBITED +// +// MessageText: +// +// The Windows Installer does not permit installation from a Remote Desktop Connection. +// +pub const ERROR_INSTALL_REMOTE_PROHIBITED: i32 = 1645; + +// End of MSI error codes + +/////////////////////////// +// // +// RPC Status Codes // +// // +/////////////////////////// + +// +// MessageId: RPC_S_INVALID_STRING_BINDING +// +// MessageText: +// +// The string binding is invalid. +// +pub const RPC_S_INVALID_STRING_BINDING: i32 = 1700; + +// +// MessageId: RPC_S_WRONG_KIND_OF_BINDING +// +// MessageText: +// +// The binding handle is not the correct type. +// +pub const RPC_S_WRONG_KIND_OF_BINDING: i32 = 1701; + +// +// MessageId: RPC_S_INVALID_BINDING +// +// MessageText: +// +// The binding handle is invalid. +// +pub const RPC_S_INVALID_BINDING: i32 = 1702; + +// +// MessageId: RPC_S_PROTSEQ_NOT_SUPPORTED +// +// MessageText: +// +// The RPC protocol sequence is not supported. +// +pub const RPC_S_PROTSEQ_NOT_SUPPORTED: i32 = 1703; + +// +// MessageId: RPC_S_INVALID_RPC_PROTSEQ +// +// MessageText: +// +// The RPC protocol sequence is invalid. +// +pub const RPC_S_INVALID_RPC_PROTSEQ: i32 = 1704; + +// +// MessageId: RPC_S_INVALID_STRING_UUID +// +// MessageText: +// +// The string universal unique identifier (UUID) is invalid. +// +pub const RPC_S_INVALID_STRING_UUID: i32 = 1705; + +// +// MessageId: RPC_S_INVALID_ENDPOINT_FORMAT +// +// MessageText: +// +// The endpoint format is invalid. +// +pub const RPC_S_INVALID_ENDPOINT_FORMAT: i32 = 1706; + +// +// MessageId: RPC_S_INVALID_NET_ADDR +// +// MessageText: +// +// The network address is invalid. +// +pub const RPC_S_INVALID_NET_ADDR: i32 = 1707; + +// +// MessageId: RPC_S_NO_ENDPOINT_FOUND +// +// MessageText: +// +// No endpoint was found. +// +pub const RPC_S_NO_ENDPOINT_FOUND: i32 = 1708; + +// +// MessageId: RPC_S_INVALID_TIMEOUT +// +// MessageText: +// +// The timeout value is invalid. +// +pub const RPC_S_INVALID_TIMEOUT: i32 = 1709; + +// +// MessageId: RPC_S_OBJECT_NOT_FOUND +// +// MessageText: +// +// The object universal unique identifier (UUID) was not found. +// +pub const RPC_S_OBJECT_NOT_FOUND: i32 = 1710; + +// +// MessageId: RPC_S_ALREADY_REGISTERED +// +// MessageText: +// +// The object universal unique identifier (UUID) has already been registered. +// +pub const RPC_S_ALREADY_REGISTERED: i32 = 1711; + +// +// MessageId: RPC_S_TYPE_ALREADY_REGISTERED +// +// MessageText: +// +// The type universal unique identifier (UUID) has already been registered. +// +pub const RPC_S_TYPE_ALREADY_REGISTERED: i32 = 1712; + +// +// MessageId: RPC_S_ALREADY_LISTENING +// +// MessageText: +// +// The RPC server is already listening. +// +pub const RPC_S_ALREADY_LISTENING: i32 = 1713; + +// +// MessageId: RPC_S_NO_PROTSEQS_REGISTERED +// +// MessageText: +// +// No protocol sequences have been registered. +// +pub const RPC_S_NO_PROTSEQS_REGISTERED: i32 = 1714; + +// +// MessageId: RPC_S_NOT_LISTENING +// +// MessageText: +// +// The RPC server is not listening. +// +pub const RPC_S_NOT_LISTENING: i32 = 1715; + +// +// MessageId: RPC_S_UNKNOWN_MGR_TYPE +// +// MessageText: +// +// The manager type is unknown. +// +pub const RPC_S_UNKNOWN_MGR_TYPE: i32 = 1716; + +// +// MessageId: RPC_S_UNKNOWN_IF +// +// MessageText: +// +// The interface is unknown. +// +pub const RPC_S_UNKNOWN_IF: i32 = 1717; + +// +// MessageId: RPC_S_NO_BINDINGS +// +// MessageText: +// +// There are no bindings. +// +pub const RPC_S_NO_BINDINGS: i32 = 1718; + +// +// MessageId: RPC_S_NO_PROTSEQS +// +// MessageText: +// +// There are no protocol sequences. +// +pub const RPC_S_NO_PROTSEQS: i32 = 1719; + +// +// MessageId: RPC_S_CANT_CREATE_ENDPOINT +// +// MessageText: +// +// The endpoint cannot be created. +// +pub const RPC_S_CANT_CREATE_ENDPOINT: i32 = 1720; + +// +// MessageId: RPC_S_OUT_OF_RESOURCES +// +// MessageText: +// +// Not enough resources are available to complete this operation. +// +pub const RPC_S_OUT_OF_RESOURCES: i32 = 1721; + +// +// MessageId: RPC_S_SERVER_UNAVAILABLE +// +// MessageText: +// +// The RPC server is unavailable. +// +pub const RPC_S_SERVER_UNAVAILABLE: i32 = 1722; + +// +// MessageId: RPC_S_SERVER_TOO_BUSY +// +// MessageText: +// +// The RPC server is too busy to complete this operation. +// +pub const RPC_S_SERVER_TOO_BUSY: i32 = 1723; + +// +// MessageId: RPC_S_INVALID_NETWORK_OPTIONS +// +// MessageText: +// +// The network options are invalid. +// +pub const RPC_S_INVALID_NETWORK_OPTIONS: i32 = 1724; + +// +// MessageId: RPC_S_NO_CALL_ACTIVE +// +// MessageText: +// +// There are no remote procedure calls active on this thread. +// +pub const RPC_S_NO_CALL_ACTIVE: i32 = 1725; + +// +// MessageId: RPC_S_CALL_FAILED +// +// MessageText: +// +// The remote procedure call failed. +// +pub const RPC_S_CALL_FAILED: i32 = 1726; + +// +// MessageId: RPC_S_CALL_FAILED_DNE +// +// MessageText: +// +// The remote procedure call failed and did not execute. +// +pub const RPC_S_CALL_FAILED_DNE: i32 = 1727; + +// +// MessageId: RPC_S_PROTOCOL_ERROR +// +// MessageText: +// +// A remote procedure call (RPC) protocol error occurred. +// +pub const RPC_S_PROTOCOL_ERROR: i32 = 1728; + +// +// MessageId: RPC_S_UNSUPPORTED_TRANS_SYN +// +// MessageText: +// +// The transfer syntax is not supported by the RPC server. +// +pub const RPC_S_UNSUPPORTED_TRANS_SYN: i32 = 1730; + +// +// MessageId: RPC_S_UNSUPPORTED_TYPE +// +// MessageText: +// +// The universal unique identifier (UUID) type is not supported. +// +pub const RPC_S_UNSUPPORTED_TYPE: i32 = 1732; + +// +// MessageId: RPC_S_INVALID_TAG +// +// MessageText: +// +// The tag is invalid. +// +pub const RPC_S_INVALID_TAG: i32 = 1733; + +// +// MessageId: RPC_S_INVALID_BOUND +// +// MessageText: +// +// The array bounds are invalid. +// +pub const RPC_S_INVALID_BOUND: i32 = 1734; + +// +// MessageId: RPC_S_NO_ENTRY_NAME +// +// MessageText: +// +// The binding does not contain an entry name. +// +pub const RPC_S_NO_ENTRY_NAME: i32 = 1735; + +// +// MessageId: RPC_S_INVALID_NAME_SYNTAX +// +// MessageText: +// +// The name syntax is invalid. +// +pub const RPC_S_INVALID_NAME_SYNTAX: i32 = 1736; + +// +// MessageId: RPC_S_UNSUPPORTED_NAME_SYNTAX +// +// MessageText: +// +// The name syntax is not supported. +// +pub const RPC_S_UNSUPPORTED_NAME_SYNTAX: i32 = 1737; + +// +// MessageId: RPC_S_UUID_NO_ADDRESS +// +// MessageText: +// +// No network address is available to use to export construct a universal unique identifier (UUID). +// +pub const RPC_S_UUID_NO_ADDRESS: i32 = 1739; + +// +// MessageId: RPC_S_DUPLICATE_ENDPOINT +// +// MessageText: +// +// The endpoint is a duplicate. +// +pub const RPC_S_DUPLICATE_ENDPOINT: i32 = 1740; + +// +// MessageId: RPC_S_UNKNOWN_AUTHN_TYPE +// +// MessageText: +// +// The authentication type is unknown. +// +pub const RPC_S_UNKNOWN_AUTHN_TYPE: i32 = 1741; + +// +// MessageId: RPC_S_MAX_CALLS_TOO_SMALL +// +// MessageText: +// +// The maximum number of calls is too small. +// +pub const RPC_S_MAX_CALLS_TOO_SMALL: i32 = 1742; + +// +// MessageId: RPC_S_STRING_TOO_LONG +// +// MessageText: +// +// The string is too long. +// +pub const RPC_S_STRING_TOO_LONG: i32 = 1743; + +// +// MessageId: RPC_S_PROTSEQ_NOT_FOUND +// +// MessageText: +// +// The RPC protocol sequence was not found. +// +pub const RPC_S_PROTSEQ_NOT_FOUND: i32 = 1744; + +// +// MessageId: RPC_S_PROCNUM_OUT_OF_RANGE +// +// MessageText: +// +// The procedure number is out of range. +// +pub const RPC_S_PROCNUM_OUT_OF_RANGE: i32 = 1745; + +// +// MessageId: RPC_S_BINDING_HAS_NO_AUTH +// +// MessageText: +// +// The binding does not contain any authentication information. +// +pub const RPC_S_BINDING_HAS_NO_AUTH: i32 = 1746; + +// +// MessageId: RPC_S_UNKNOWN_AUTHN_SERVICE +// +// MessageText: +// +// The authentication service is unknown. +// +pub const RPC_S_UNKNOWN_AUTHN_SERVICE: i32 = 1747; + +// +// MessageId: RPC_S_UNKNOWN_AUTHN_LEVEL +// +// MessageText: +// +// The authentication level is unknown. +// +pub const RPC_S_UNKNOWN_AUTHN_LEVEL: i32 = 1748; + +// +// MessageId: RPC_S_INVALID_AUTH_IDENTITY +// +// MessageText: +// +// The security context is invalid. +// +pub const RPC_S_INVALID_AUTH_IDENTITY: i32 = 1749; + +// +// MessageId: RPC_S_UNKNOWN_AUTHZ_SERVICE +// +// MessageText: +// +// The authorization service is unknown. +// +pub const RPC_S_UNKNOWN_AUTHZ_SERVICE: i32 = 1750; + +// +// MessageId: EPT_S_INVALID_ENTRY +// +// MessageText: +// +// The entry is invalid. +// +pub const EPT_S_INVALID_ENTRY: i32 = 1751; + +// +// MessageId: EPT_S_CANT_PERFORM_OP +// +// MessageText: +// +// The server endpoint cannot perform the operation. +// +pub const EPT_S_CANT_PERFORM_OP: i32 = 1752; + +// +// MessageId: EPT_S_NOT_REGISTERED +// +// MessageText: +// +// There are no more endpoints available from the endpoint mapper. +// +pub const EPT_S_NOT_REGISTERED: i32 = 1753; + +// +// MessageId: RPC_S_NOTHING_TO_EXPORT +// +// MessageText: +// +// No interfaces have been exported. +// +pub const RPC_S_NOTHING_TO_EXPORT: i32 = 1754; + +// +// MessageId: RPC_S_INCOMPLETE_NAME +// +// MessageText: +// +// The entry name is incomplete. +// +pub const RPC_S_INCOMPLETE_NAME: i32 = 1755; + +// +// MessageId: RPC_S_INVALID_VERS_OPTION +// +// MessageText: +// +// The version option is invalid. +// +pub const RPC_S_INVALID_VERS_OPTION: i32 = 1756; + +// +// MessageId: RPC_S_NO_MORE_MEMBERS +// +// MessageText: +// +// There are no more members. +// +pub const RPC_S_NO_MORE_MEMBERS: i32 = 1757; + +// +// MessageId: RPC_S_NOT_ALL_OBJS_UNEXPORTED +// +// MessageText: +// +// There is nothing to unexport. +// +pub const RPC_S_NOT_ALL_OBJS_UNEXPORTED: i32 = 1758; + +// +// MessageId: RPC_S_INTERFACE_NOT_FOUND +// +// MessageText: +// +// The interface was not found. +// +pub const RPC_S_INTERFACE_NOT_FOUND: i32 = 1759; + +// +// MessageId: RPC_S_ENTRY_ALREADY_EXISTS +// +// MessageText: +// +// The entry already exists. +// +pub const RPC_S_ENTRY_ALREADY_EXISTS: i32 = 1760; + +// +// MessageId: RPC_S_ENTRY_NOT_FOUND +// +// MessageText: +// +// The entry is not found. +// +pub const RPC_S_ENTRY_NOT_FOUND: i32 = 1761; + +// +// MessageId: RPC_S_NAME_SERVICE_UNAVAILABLE +// +// MessageText: +// +// The name service is unavailable. +// +pub const RPC_S_NAME_SERVICE_UNAVAILABLE: i32 = 1762; + +// +// MessageId: RPC_S_INVALID_NAF_ID +// +// MessageText: +// +// The network address family is invalid. +// +pub const RPC_S_INVALID_NAF_ID: i32 = 1763; + +// +// MessageId: RPC_S_CANNOT_SUPPORT +// +// MessageText: +// +// The requested operation is not supported. +// +pub const RPC_S_CANNOT_SUPPORT: i32 = 1764; + +// +// MessageId: RPC_S_NO_CONTEXT_AVAILABLE +// +// MessageText: +// +// No security context is available to allow impersonation. +// +pub const RPC_S_NO_CONTEXT_AVAILABLE: i32 = 1765; + +// +// MessageId: RPC_S_INTERNAL_ERROR +// +// MessageText: +// +// An internal error occurred in a remote procedure call (RPC). +// +pub const RPC_S_INTERNAL_ERROR: i32 = 1766; + +// +// MessageId: RPC_S_ZERO_DIVIDE +// +// MessageText: +// +// The RPC server attempted an integer division by zero. +// +pub const RPC_S_ZERO_DIVIDE: i32 = 1767; + +// +// MessageId: RPC_S_ADDRESS_ERROR +// +// MessageText: +// +// An addressing error occurred in the RPC server. +// +pub const RPC_S_ADDRESS_ERROR: i32 = 1768; + +// +// MessageId: RPC_S_FP_DIV_ZERO +// +// MessageText: +// +// A floating-point operation at the RPC server caused a division by zero. +// +pub const RPC_S_FP_DIV_ZERO: i32 = 1769; + +// +// MessageId: RPC_S_FP_UNDERFLOW +// +// MessageText: +// +// A floating-point underflow occurred at the RPC server. +// +pub const RPC_S_FP_UNDERFLOW: i32 = 1770; + +// +// MessageId: RPC_S_FP_OVERFLOW +// +// MessageText: +// +// A floating-point overflow occurred at the RPC server. +// +pub const RPC_S_FP_OVERFLOW: i32 = 1771; + +// +// MessageId: RPC_X_NO_MORE_ENTRIES +// +// MessageText: +// +// The list of RPC servers available for the binding of auto handles has been exhausted. +// +pub const RPC_X_NO_MORE_ENTRIES: i32 = 1772; + +// +// MessageId: RPC_X_SS_CHAR_TRANS_OPEN_FAIL +// +// MessageText: +// +// Unable to open the character translation table file. +// +pub const RPC_X_SS_CHAR_TRANS_OPEN_FAIL: i32 = 1773; + +// +// MessageId: RPC_X_SS_CHAR_TRANS_SHORT_FILE +// +// MessageText: +// +// The file containing the character translation table has fewer than 512 bytes. +// +pub const RPC_X_SS_CHAR_TRANS_SHORT_FILE: i32 = 1774; + +// +// MessageId: RPC_X_SS_IN_NULL_CONTEXT +// +// MessageText: +// +// A null context handle was passed from the client to the host during a remote procedure call. +// +pub const RPC_X_SS_IN_NULL_CONTEXT: i32 = 1775; + +// +// MessageId: RPC_X_SS_CONTEXT_DAMAGED +// +// MessageText: +// +// The context handle changed during a remote procedure call. +// +pub const RPC_X_SS_CONTEXT_DAMAGED: i32 = 1777; + +// +// MessageId: RPC_X_SS_HANDLES_MISMATCH +// +// MessageText: +// +// The binding handles passed to a remote procedure call do not match. +// +pub const RPC_X_SS_HANDLES_MISMATCH: i32 = 1778; + +// +// MessageId: RPC_X_SS_CANNOT_GET_CALL_HANDLE +// +// MessageText: +// +// The stub is unable to get the remote procedure call handle. +// +pub const RPC_X_SS_CANNOT_GET_CALL_HANDLE: i32 = 1779; + +// +// MessageId: RPC_X_NULL_REF_POINTER +// +// MessageText: +// +// A null reference pointer was passed to the stub. +// +pub const RPC_X_NULL_REF_POINTER: i32 = 1780; + +// +// MessageId: RPC_X_ENUM_VALUE_OUT_OF_RANGE +// +// MessageText: +// +// The enumeration value is out of range. +// +pub const RPC_X_ENUM_VALUE_OUT_OF_RANGE: i32 = 1781; + +// +// MessageId: RPC_X_BYTE_COUNT_TOO_SMALL +// +// MessageText: +// +// The byte count is too small. +// +pub const RPC_X_BYTE_COUNT_TOO_SMALL: i32 = 1782; + +// +// MessageId: RPC_X_BAD_STUB_DATA +// +// MessageText: +// +// The stub received bad data. +// +pub const RPC_X_BAD_STUB_DATA: i32 = 1783; + +// +// MessageId: ERROR_INVALID_USER_BUFFER +// +// MessageText: +// +// The supplied user buffer is not valid for the requested operation. +// +pub const ERROR_INVALID_USER_BUFFER: i32 = 1784; + +// +// MessageId: ERROR_UNRECOGNIZED_MEDIA +// +// MessageText: +// +// The disk media is not recognized. It may not be formatted. +// +pub const ERROR_UNRECOGNIZED_MEDIA: i32 = 1785; + +// +// MessageId: ERROR_NO_TRUST_LSA_SECRET +// +// MessageText: +// +// The workstation does not have a trust secret. +// +pub const ERROR_NO_TRUST_LSA_SECRET: i32 = 1786; + +// +// MessageId: ERROR_NO_TRUST_SAM_ACCOUNT +// +// MessageText: +// +// The security database on the server does not have a computer account for this workstation trust relationship. +// +pub const ERROR_NO_TRUST_SAM_ACCOUNT: i32 = 1787; + +// +// MessageId: ERROR_TRUSTED_DOMAIN_FAILURE +// +// MessageText: +// +// The trust relationship between the primary domain and the trusted domain failed. +// +pub const ERROR_TRUSTED_DOMAIN_FAILURE: i32 = 1788; + +// +// MessageId: ERROR_TRUSTED_RELATIONSHIP_FAILURE +// +// MessageText: +// +// The trust relationship between this workstation and the primary domain failed. +// +pub const ERROR_TRUSTED_RELATIONSHIP_FAILURE: i32 = 1789; + +// +// MessageId: ERROR_TRUST_FAILURE +// +// MessageText: +// +// The network logon failed. +// +pub const ERROR_TRUST_FAILURE: i32 = 1790; + +// +// MessageId: RPC_S_CALL_IN_PROGRESS +// +// MessageText: +// +// A remote procedure call is already in progress for this thread. +// +pub const RPC_S_CALL_IN_PROGRESS: i32 = 1791; + +// +// MessageId: ERROR_NETLOGON_NOT_STARTED +// +// MessageText: +// +// An attempt was made to logon, but the network logon service was not started. +// +pub const ERROR_NETLOGON_NOT_STARTED: i32 = 1792; + +// +// MessageId: ERROR_ACCOUNT_EXPIRED +// +// MessageText: +// +// The user's account has expired. +// +pub const ERROR_ACCOUNT_EXPIRED: i32 = 1793; + +// +// MessageId: ERROR_REDIRECTOR_HAS_OPEN_HANDLES +// +// MessageText: +// +// The redirector is in use and cannot be unloaded. +// +pub const ERROR_REDIRECTOR_HAS_OPEN_HANDLES: i32 = 1794; + +// +// MessageId: ERROR_PRINTER_DRIVER_ALREADY_INSTALLED +// +// MessageText: +// +// The specified printer driver is already installed. +// +pub const ERROR_PRINTER_DRIVER_ALREADY_INSTALLED: i32 = 1795; + +// +// MessageId: ERROR_UNKNOWN_PORT +// +// MessageText: +// +// The specified port is unknown. +// +pub const ERROR_UNKNOWN_PORT: i32 = 1796; + +// +// MessageId: ERROR_UNKNOWN_PRINTER_DRIVER +// +// MessageText: +// +// The printer driver is unknown. +// +pub const ERROR_UNKNOWN_PRINTER_DRIVER: i32 = 1797; + +// +// MessageId: ERROR_UNKNOWN_PRINTPROCESSOR +// +// MessageText: +// +// The print processor is unknown. +// +pub const ERROR_UNKNOWN_PRINTPROCESSOR: i32 = 1798; + +// +// MessageId: ERROR_INVALID_SEPARATOR_FILE +// +// MessageText: +// +// The specified separator file is invalid. +// +pub const ERROR_INVALID_SEPARATOR_FILE: i32 = 1799; + +// +// MessageId: ERROR_INVALID_PRIORITY +// +// MessageText: +// +// The specified priority is invalid. +// +pub const ERROR_INVALID_PRIORITY: i32 = 1800; + +// +// MessageId: ERROR_INVALID_PRINTER_NAME +// +// MessageText: +// +// The printer name is invalid. +// +pub const ERROR_INVALID_PRINTER_NAME: i32 = 1801; + +// +// MessageId: ERROR_PRINTER_ALREADY_EXISTS +// +// MessageText: +// +// The printer already exists. +// +pub const ERROR_PRINTER_ALREADY_EXISTS: i32 = 1802; + +// +// MessageId: ERROR_INVALID_PRINTER_COMMAND +// +// MessageText: +// +// The printer command is invalid. +// +pub const ERROR_INVALID_PRINTER_COMMAND: i32 = 1803; + +// +// MessageId: ERROR_INVALID_DATATYPE +// +// MessageText: +// +// The specified datatype is invalid. +// +pub const ERROR_INVALID_DATATYPE: i32 = 1804; + +// +// MessageId: ERROR_INVALID_ENVIRONMENT +// +// MessageText: +// +// The environment specified is invalid. +// +pub const ERROR_INVALID_ENVIRONMENT: i32 = 1805; + +// +// MessageId: RPC_S_NO_MORE_BINDINGS +// +// MessageText: +// +// There are no more bindings. +// +pub const RPC_S_NO_MORE_BINDINGS: i32 = 1806; + +// +// MessageId: ERROR_NOLOGON_INTERDOMAIN_TRUST_ACCOUNT +// +// MessageText: +// +// The account used is an interdomain trust account. Use your global user account or local user account to access this server. +// +pub const ERROR_NOLOGON_INTERDOMAIN_TRUST_ACCOUNT: i32 = 1807; + +// +// MessageId: ERROR_NOLOGON_WORKSTATION_TRUST_ACCOUNT +// +// MessageText: +// +// The account used is a computer account. Use your global user account or local user account to access this server. +// +pub const ERROR_NOLOGON_WORKSTATION_TRUST_ACCOUNT: i32 = 1808; + +// +// MessageId: ERROR_NOLOGON_SERVER_TRUST_ACCOUNT +// +// MessageText: +// +// The account used is a server trust account. Use your global user account or local user account to access this server. +// +pub const ERROR_NOLOGON_SERVER_TRUST_ACCOUNT: i32 = 1809; + +// +// MessageId: ERROR_DOMAIN_TRUST_INCONSISTENT +// +// MessageText: +// +// The name or security ID (SID) of the domain specified is inconsistent with the trust information for that domain. +// +pub const ERROR_DOMAIN_TRUST_INCONSISTENT: i32 = 1810; + +// +// MessageId: ERROR_SERVER_HAS_OPEN_HANDLES +// +// MessageText: +// +// The server is in use and cannot be unloaded. +// +pub const ERROR_SERVER_HAS_OPEN_HANDLES: i32 = 1811; + +// +// MessageId: ERROR_RESOURCE_DATA_NOT_FOUND +// +// MessageText: +// +// The specified image file did not contain a resource section. +// +pub const ERROR_RESOURCE_DATA_NOT_FOUND: i32 = 1812; + +// +// MessageId: ERROR_RESOURCE_TYPE_NOT_FOUND +// +// MessageText: +// +// The specified resource type cannot be found in the image file. +// +pub const ERROR_RESOURCE_TYPE_NOT_FOUND: i32 = 1813; + +// +// MessageId: ERROR_RESOURCE_NAME_NOT_FOUND +// +// MessageText: +// +// The specified resource name cannot be found in the image file. +// +pub const ERROR_RESOURCE_NAME_NOT_FOUND: i32 = 1814; + +// +// MessageId: ERROR_RESOURCE_LANG_NOT_FOUND +// +// MessageText: +// +// The specified resource language ID cannot be found in the image file. +// +pub const ERROR_RESOURCE_LANG_NOT_FOUND: i32 = 1815; + +// +// MessageId: ERROR_NOT_ENOUGH_QUOTA +// +// MessageText: +// +// Not enough quota is available to process this command. +// +pub const ERROR_NOT_ENOUGH_QUOTA: i32 = 1816; + +// +// MessageId: RPC_S_NO_INTERFACES +// +// MessageText: +// +// No interfaces have been registered. +// +pub const RPC_S_NO_INTERFACES: i32 = 1817; + +// +// MessageId: RPC_S_CALL_CANCELLED +// +// MessageText: +// +// The remote procedure call was cancelled. +// +pub const RPC_S_CALL_CANCELLED: i32 = 1818; + +// +// MessageId: RPC_S_BINDING_INCOMPLETE +// +// MessageText: +// +// The binding handle does not contain all required information. +// +pub const RPC_S_BINDING_INCOMPLETE: i32 = 1819; + +// +// MessageId: RPC_S_COMM_FAILURE +// +// MessageText: +// +// A communications failure occurred during a remote procedure call. +// +pub const RPC_S_COMM_FAILURE: i32 = 1820; + +// +// MessageId: RPC_S_UNSUPPORTED_AUTHN_LEVEL +// +// MessageText: +// +// The requested authentication level is not supported. +// +pub const RPC_S_UNSUPPORTED_AUTHN_LEVEL: i32 = 1821; + +// +// MessageId: RPC_S_NO_PRINC_NAME +// +// MessageText: +// +// No principal name registered. +// +pub const RPC_S_NO_PRINC_NAME: i32 = 1822; + +// +// MessageId: RPC_S_NOT_RPC_ERROR +// +// MessageText: +// +// The error specified is not a valid Windows RPC error code. +// +pub const RPC_S_NOT_RPC_ERROR: i32 = 1823; + +// +// MessageId: RPC_S_UUID_LOCAL_ONLY +// +// MessageText: +// +// A UUID that is valid only on this computer has been allocated. +// +pub const RPC_S_UUID_LOCAL_ONLY: i32 = 1824; + +// +// MessageId: RPC_S_SEC_PKG_ERROR +// +// MessageText: +// +// A security package specific error occurred. +// +pub const RPC_S_SEC_PKG_ERROR: i32 = 1825; + +// +// MessageId: RPC_S_NOT_CANCELLED +// +// MessageText: +// +// Thread is not canceled. +// +pub const RPC_S_NOT_CANCELLED: i32 = 1826; + +// +// MessageId: RPC_X_INVALID_ES_ACTION +// +// MessageText: +// +// Invalid operation on the encoding/decoding handle. +// +pub const RPC_X_INVALID_ES_ACTION: i32 = 1827; + +// +// MessageId: RPC_X_WRONG_ES_VERSION +// +// MessageText: +// +// Incompatible version of the serializing package. +// +pub const RPC_X_WRONG_ES_VERSION: i32 = 1828; + +// +// MessageId: RPC_X_WRONG_STUB_VERSION +// +// MessageText: +// +// Incompatible version of the RPC stub. +// +pub const RPC_X_WRONG_STUB_VERSION: i32 = 1829; + +// +// MessageId: RPC_X_INVALID_PIPE_OBJECT +// +// MessageText: +// +// The RPC pipe object is invalid or corrupted. +// +pub const RPC_X_INVALID_PIPE_OBJECT: i32 = 1830; + +// +// MessageId: RPC_X_WRONG_PIPE_ORDER +// +// MessageText: +// +// An invalid operation was attempted on an RPC pipe object. +// +pub const RPC_X_WRONG_PIPE_ORDER: i32 = 1831; + +// +// MessageId: RPC_X_WRONG_PIPE_VERSION +// +// MessageText: +// +// Unsupported RPC pipe version. +// +pub const RPC_X_WRONG_PIPE_VERSION: i32 = 1832; + +// +// MessageId: RPC_S_GROUP_MEMBER_NOT_FOUND +// +// MessageText: +// +// The group member was not found. +// +pub const RPC_S_GROUP_MEMBER_NOT_FOUND: i32 = 1898; + +// +// MessageId: EPT_S_CANT_CREATE +// +// MessageText: +// +// The endpoint mapper database entry could not be created. +// +pub const EPT_S_CANT_CREATE: i32 = 1899; + +// +// MessageId: RPC_S_INVALID_OBJECT +// +// MessageText: +// +// The object universal unique identifier (UUID) is the nil UUID. +// +pub const RPC_S_INVALID_OBJECT: i32 = 1900; + +// +// MessageId: ERROR_INVALID_TIME +// +// MessageText: +// +// The specified time is invalid. +// +pub const ERROR_INVALID_TIME: i32 = 1901; + +// +// MessageId: ERROR_INVALID_FORM_NAME +// +// MessageText: +// +// The specified form name is invalid. +// +pub const ERROR_INVALID_FORM_NAME: i32 = 1902; + +// +// MessageId: ERROR_INVALID_FORM_SIZE +// +// MessageText: +// +// The specified form size is invalid. +// +pub const ERROR_INVALID_FORM_SIZE: i32 = 1903; + +// +// MessageId: ERROR_ALREADY_WAITING +// +// MessageText: +// +// The specified printer handle is already being waited on +// +pub const ERROR_ALREADY_WAITING: i32 = 1904; + +// +// MessageId: ERROR_PRINTER_DELETED +// +// MessageText: +// +// The specified printer has been deleted. +// +pub const ERROR_PRINTER_DELETED: i32 = 1905; + +// +// MessageId: ERROR_INVALID_PRINTER_STATE +// +// MessageText: +// +// The state of the printer is invalid. +// +pub const ERROR_INVALID_PRINTER_STATE: i32 = 1906; + +// +// MessageId: ERROR_PASSWORD_MUST_CHANGE +// +// MessageText: +// +// The user's password must be changed before logging on the first time. +// +pub const ERROR_PASSWORD_MUST_CHANGE: i32 = 1907; + +// +// MessageId: ERROR_DOMAIN_CONTROLLER_NOT_FOUND +// +// MessageText: +// +// Could not find the domain controller for this domain. +// +pub const ERROR_DOMAIN_CONTROLLER_NOT_FOUND: i32 = 1908; + +// +// MessageId: ERROR_ACCOUNT_LOCKED_OUT +// +// MessageText: +// +// The referenced account is currently locked out and may not be logged on to. +// +pub const ERROR_ACCOUNT_LOCKED_OUT: i32 = 1909; + +// +// MessageId: OR_INVALID_OXID +// +// MessageText: +// +// The object exporter specified was not found. +// +pub const OR_INVALID_OXID: i32 = 1910; + +// +// MessageId: OR_INVALID_OID +// +// MessageText: +// +// The object specified was not found. +// +pub const OR_INVALID_OID: i32 = 1911; + +// +// MessageId: OR_INVALID_SET +// +// MessageText: +// +// The object resolver set specified was not found. +// +pub const OR_INVALID_SET: i32 = 1912; + +// +// MessageId: RPC_S_SEND_INCOMPLETE +// +// MessageText: +// +// Some data remains to be sent in the request buffer. +// +pub const RPC_S_SEND_INCOMPLETE: i32 = 1913; + +// +// MessageId: RPC_S_INVALID_ASYNC_HANDLE +// +// MessageText: +// +// Invalid asynchronous remote procedure call handle. +// +pub const RPC_S_INVALID_ASYNC_HANDLE: i32 = 1914; + +// +// MessageId: RPC_S_INVALID_ASYNC_CALL +// +// MessageText: +// +// Invalid asynchronous RPC call handle for this operation. +// +pub const RPC_S_INVALID_ASYNC_CALL: i32 = 1915; + +// +// MessageId: RPC_X_PIPE_CLOSED +// +// MessageText: +// +// The RPC pipe object has already been closed. +// +pub const RPC_X_PIPE_CLOSED: i32 = 1916; + +// +// MessageId: RPC_X_PIPE_DISCIPLINE_ERROR +// +// MessageText: +// +// The RPC call completed before all pipes were processed. +// +pub const RPC_X_PIPE_DISCIPLINE_ERROR: i32 = 1917; + +// +// MessageId: RPC_X_PIPE_EMPTY +// +// MessageText: +// +// No more data is available from the RPC pipe. +// +pub const RPC_X_PIPE_EMPTY: i32 = 1918; + +// +// MessageId: ERROR_NO_SITENAME +// +// MessageText: +// +// No site name is available for this machine. +// +pub const ERROR_NO_SITENAME: i32 = 1919; + +// +// MessageId: ERROR_CANT_ACCESS_FILE +// +// MessageText: +// +// The file can not be accessed by the system. +// +pub const ERROR_CANT_ACCESS_FILE: i32 = 1920; + +// +// MessageId: ERROR_CANT_RESOLVE_FILENAME +// +// MessageText: +// +// The name of the file cannot be resolved by the system. +// +pub const ERROR_CANT_RESOLVE_FILENAME: i32 = 1921; + +// +// MessageId: RPC_S_ENTRY_TYPE_MISMATCH +// +// MessageText: +// +// The entry is not of the expected type. +// +pub const RPC_S_ENTRY_TYPE_MISMATCH: i32 = 1922; + +// +// MessageId: RPC_S_NOT_ALL_OBJS_EXPORTED +// +// MessageText: +// +// Not all object UUIDs could be exported to the specified entry. +// +pub const RPC_S_NOT_ALL_OBJS_EXPORTED: i32 = 1923; + +// +// MessageId: RPC_S_INTERFACE_NOT_EXPORTED +// +// MessageText: +// +// Interface could not be exported to the specified entry. +// +pub const RPC_S_INTERFACE_NOT_EXPORTED: i32 = 1924; + +// +// MessageId: RPC_S_PROFILE_NOT_ADDED +// +// MessageText: +// +// The specified profile entry could not be added. +// +pub const RPC_S_PROFILE_NOT_ADDED: i32 = 1925; + +// +// MessageId: RPC_S_PRF_ELT_NOT_ADDED +// +// MessageText: +// +// The specified profile element could not be added. +// +pub const RPC_S_PRF_ELT_NOT_ADDED: i32 = 1926; + +// +// MessageId: RPC_S_PRF_ELT_NOT_REMOVED +// +// MessageText: +// +// The specified profile element could not be removed. +// +pub const RPC_S_PRF_ELT_NOT_REMOVED: i32 = 1927; + +// +// MessageId: RPC_S_GRP_ELT_NOT_ADDED +// +// MessageText: +// +// The group element could not be added. +// +pub const RPC_S_GRP_ELT_NOT_ADDED: i32 = 1928; + +// +// MessageId: RPC_S_GRP_ELT_NOT_REMOVED +// +// MessageText: +// +// The group element could not be removed. +// +pub const RPC_S_GRP_ELT_NOT_REMOVED: i32 = 1929; + +// +// MessageId: ERROR_KM_DRIVER_BLOCKED +// +// MessageText: +// +// The printer driver is not compatible with a policy enabled on your computer that blocks NT 4.0 drivers. +// +pub const ERROR_KM_DRIVER_BLOCKED: i32 = 1930; + +// +// MessageId: ERROR_CONTEXT_EXPIRED +// +// MessageText: +// +// The context has expired and can no longer be used. +// +pub const ERROR_CONTEXT_EXPIRED: i32 = 1931; + +// +// MessageId: ERROR_PER_USER_TRUST_QUOTA_EXCEEDED +// +// MessageText: +// +// The current user's delegated trust creation quota has been exceeded. +// +pub const ERROR_PER_USER_TRUST_QUOTA_EXCEEDED: i32 = 1932; + +// +// MessageId: ERROR_ALL_USER_TRUST_QUOTA_EXCEEDED +// +// MessageText: +// +// The total delegated trust creation quota has been exceeded. +// +pub const ERROR_ALL_USER_TRUST_QUOTA_EXCEEDED: i32 = 1933; + +// +// MessageId: ERROR_USER_DELETE_TRUST_QUOTA_EXCEEDED +// +// MessageText: +// +// The current user's delegated trust deletion quota has been exceeded. +// +pub const ERROR_USER_DELETE_TRUST_QUOTA_EXCEEDED: i32 = 1934; + +// +// MessageId: ERROR_AUTHENTICATION_FIREWALL_FAILED +// +// MessageText: +// +// Logon Failure: The machine you are logging onto is protected by an authentication firewall. The specified account is not allowed to authenticate to the machine. +// +pub const ERROR_AUTHENTICATION_FIREWALL_FAILED: i32 = 1935; + +// +// MessageId: ERROR_REMOTE_PRINT_CONNECTIONS_BLOCKED +// +// MessageText: +// +// Remote connections to the Print Spooler are blocked by a policy set on your machine. +// +pub const ERROR_REMOTE_PRINT_CONNECTIONS_BLOCKED: i32 = 1936; + +/////////////////////////// +// // +// OpenGL Error Code // +// // +/////////////////////////// + +// +// MessageId: ERROR_INVALID_PIXEL_FORMAT +// +// MessageText: +// +// The pixel format is invalid. +// +pub const ERROR_INVALID_PIXEL_FORMAT: i32 = 2000; + +// +// MessageId: ERROR_BAD_DRIVER +// +// MessageText: +// +// The specified driver is invalid. +// +pub const ERROR_BAD_DRIVER: i32 = 2001; + +// +// MessageId: ERROR_INVALID_WINDOW_STYLE +// +// MessageText: +// +// The window style or class attribute is invalid for this operation. +// +pub const ERROR_INVALID_WINDOW_STYLE: i32 = 2002; + +// +// MessageId: ERROR_METAFILE_NOT_SUPPORTED +// +// MessageText: +// +// The requested metafile operation is not supported. +// +pub const ERROR_METAFILE_NOT_SUPPORTED: i32 = 2003; + +// +// MessageId: ERROR_TRANSFORM_NOT_SUPPORTED +// +// MessageText: +// +// The requested transformation operation is not supported. +// +pub const ERROR_TRANSFORM_NOT_SUPPORTED: i32 = 2004; + +// +// MessageId: ERROR_CLIPPING_NOT_SUPPORTED +// +// MessageText: +// +// The requested clipping operation is not supported. +// +pub const ERROR_CLIPPING_NOT_SUPPORTED: i32 = 2005; + +// End of OpenGL error codes + +/////////////////////////////////////////// +// // +// Image Color Management Error Code // +// // +/////////////////////////////////////////// + +// +// MessageId: ERROR_INVALID_CMM +// +// MessageText: +// +// The specified color management module is invalid. +// +pub const ERROR_INVALID_CMM: i32 = 2010; + +// +// MessageId: ERROR_INVALID_PROFILE +// +// MessageText: +// +// The specified color profile is invalid. +// +pub const ERROR_INVALID_PROFILE: i32 = 2011; + +// +// MessageId: ERROR_TAG_NOT_FOUND +// +// MessageText: +// +// The specified tag was not found. +// +pub const ERROR_TAG_NOT_FOUND: i32 = 2012; + +// +// MessageId: ERROR_TAG_NOT_PRESENT +// +// MessageText: +// +// A required tag is not present. +// +pub const ERROR_TAG_NOT_PRESENT: i32 = 2013; + +// +// MessageId: ERROR_DUPLICATE_TAG +// +// MessageText: +// +// The specified tag is already present. +// +pub const ERROR_DUPLICATE_TAG: i32 = 2014; + +// +// MessageId: ERROR_PROFILE_NOT_ASSOCIATED_WITH_DEVICE +// +// MessageText: +// +// The specified color profile is not associated with any device. +// +pub const ERROR_PROFILE_NOT_ASSOCIATED_WITH_DEVICE: i32 = 2015; + +// +// MessageId: ERROR_PROFILE_NOT_FOUND +// +// MessageText: +// +// The specified color profile was not found. +// +pub const ERROR_PROFILE_NOT_FOUND: i32 = 2016; + +// +// MessageId: ERROR_INVALID_COLORSPACE +// +// MessageText: +// +// The specified color space is invalid. +// +pub const ERROR_INVALID_COLORSPACE: i32 = 2017; + +// +// MessageId: ERROR_ICM_NOT_ENABLED +// +// MessageText: +// +// Image Color Management is not enabled. +// +pub const ERROR_ICM_NOT_ENABLED: i32 = 2018; + +// +// MessageId: ERROR_DELETING_ICM_XFORM +// +// MessageText: +// +// There was an error while deleting the color transform. +// +pub const ERROR_DELETING_ICM_XFORM: i32 = 2019; + +// +// MessageId: ERROR_INVALID_TRANSFORM +// +// MessageText: +// +// The specified color transform is invalid. +// +pub const ERROR_INVALID_TRANSFORM: i32 = 2020; + +// +// MessageId: ERROR_COLORSPACE_MISMATCH +// +// MessageText: +// +// The specified transform does not match the bitmap's color space. +// +pub const ERROR_COLORSPACE_MISMATCH: i32 = 2021; + +// +// MessageId: ERROR_INVALID_COLORINDEX +// +// MessageText: +// +// The specified named color index is not present in the profile. +// +pub const ERROR_INVALID_COLORINDEX: i32 = 2022; + +/////////////////////////// +// // +// Winnet32 Status Codes // +// // +// The range 2100 through 2999 is reserved for network status codes. +// See lmerr.h for a complete listing +/////////////////////////// + +// +// MessageId: ERROR_CONNECTED_OTHER_PASSWORD +// +// MessageText: +// +// The network connection was made successfully, but the user had to be prompted for a password other than the one originally specified. +// +pub const ERROR_CONNECTED_OTHER_PASSWORD: i32 = 2108; + +// +// MessageId: ERROR_CONNECTED_OTHER_PASSWORD_DEFAULT +// +// MessageText: +// +// The network connection was made successfully using default credentials. +// +pub const ERROR_CONNECTED_OTHER_PASSWORD_DEFAULT: i32 = 2109; + +// +// MessageId: ERROR_BAD_USERNAME +// +// MessageText: +// +// The specified username is invalid. +// +pub const ERROR_BAD_USERNAME: i32 = 2202; + +// +// MessageId: ERROR_NOT_CONNECTED +// +// MessageText: +// +// This network connection does not exist. +// +pub const ERROR_NOT_CONNECTED: i32 = 2250; + +// +// MessageId: ERROR_OPEN_FILES +// +// MessageText: +// +// This network connection has files open or requests pending. +// +pub const ERROR_OPEN_FILES: i32 = 2401; + +// +// MessageId: ERROR_ACTIVE_CONNECTIONS +// +// MessageText: +// +// Active connections still exist. +// +pub const ERROR_ACTIVE_CONNECTIONS: i32 = 2402; + +// +// MessageId: ERROR_DEVICE_IN_USE +// +// MessageText: +// +// The device is in use by an active process and cannot be disconnected. +// +pub const ERROR_DEVICE_IN_USE: i32 = 2404; + +//////////////////////////////////// +// // +// Win32 Spooler Error Codes // +// // +//////////////////////////////////// +// +// MessageId: ERROR_UNKNOWN_PRINT_MONITOR +// +// MessageText: +// +// The specified print monitor is unknown. +// +pub const ERROR_UNKNOWN_PRINT_MONITOR: i32 = 3000; + +// +// MessageId: ERROR_PRINTER_DRIVER_IN_USE +// +// MessageText: +// +// The specified printer driver is currently in use. +// +pub const ERROR_PRINTER_DRIVER_IN_USE: i32 = 3001; + +// +// MessageId: ERROR_SPOOL_FILE_NOT_FOUND +// +// MessageText: +// +// The spool file was not found. +// +pub const ERROR_SPOOL_FILE_NOT_FOUND: i32 = 3002; + +// +// MessageId: ERROR_SPL_NO_STARTDOC +// +// MessageText: +// +// A StartDocPrinter call was not issued. +// +pub const ERROR_SPL_NO_STARTDOC: i32 = 3003; + +// +// MessageId: ERROR_SPL_NO_ADDJOB +// +// MessageText: +// +// An AddJob call was not issued. +// +pub const ERROR_SPL_NO_ADDJOB: i32 = 3004; + +// +// MessageId: ERROR_PRINT_PROCESSOR_ALREADY_INSTALLED +// +// MessageText: +// +// The specified print processor has already been installed. +// +pub const ERROR_PRINT_PROCESSOR_ALREADY_INSTALLED: i32 = 3005; + +// +// MessageId: ERROR_PRINT_MONITOR_ALREADY_INSTALLED +// +// MessageText: +// +// The specified print monitor has already been installed. +// +pub const ERROR_PRINT_MONITOR_ALREADY_INSTALLED: i32 = 3006; + +// +// MessageId: ERROR_INVALID_PRINT_MONITOR +// +// MessageText: +// +// The specified print monitor does not have the required functions. +// +pub const ERROR_INVALID_PRINT_MONITOR: i32 = 3007; + +// +// MessageId: ERROR_PRINT_MONITOR_IN_USE +// +// MessageText: +// +// The specified print monitor is currently in use. +// +pub const ERROR_PRINT_MONITOR_IN_USE: i32 = 3008; + +// +// MessageId: ERROR_PRINTER_HAS_JOBS_QUEUED +// +// MessageText: +// +// The requested operation is not allowed when there are jobs queued to the printer. +// +pub const ERROR_PRINTER_HAS_JOBS_QUEUED: i32 = 3009; + +// +// MessageId: ERROR_SUCCESS_REBOOT_REQUIRED +// +// MessageText: +// +// The requested operation is successful. Changes will not be effective until the system is rebooted. +// +pub const ERROR_SUCCESS_REBOOT_REQUIRED: i32 = 3010; + +// +// MessageId: ERROR_SUCCESS_RESTART_REQUIRED +// +// MessageText: +// +// The requested operation is successful. Changes will not be effective until the service is restarted. +// +pub const ERROR_SUCCESS_RESTART_REQUIRED: i32 = 3011; + +// +// MessageId: ERROR_PRINTER_NOT_FOUND +// +// MessageText: +// +// No printers were found. +// +pub const ERROR_PRINTER_NOT_FOUND: i32 = 3012; + +// +// MessageId: ERROR_PRINTER_DRIVER_WARNED +// +// MessageText: +// +// The printer driver is known to be unreliable. +// +pub const ERROR_PRINTER_DRIVER_WARNED: i32 = 3013; + +// +// MessageId: ERROR_PRINTER_DRIVER_BLOCKED +// +// MessageText: +// +// The printer driver is known to harm the system. +// +pub const ERROR_PRINTER_DRIVER_BLOCKED: i32 = 3014; + +//////////////////////////////////// +// // +// Wins Error Codes // +// // +//////////////////////////////////// +// +// MessageId: ERROR_WINS_INTERNAL +// +// MessageText: +// +// WINS encountered an error while processing the command. +// +pub const ERROR_WINS_INTERNAL: i32 = 4000; + +// +// MessageId: ERROR_CAN_NOT_DEL_LOCAL_WINS +// +// MessageText: +// +// The local WINS can not be deleted. +// +pub const ERROR_CAN_NOT_DEL_LOCAL_WINS: i32 = 4001; + +// +// MessageId: ERROR_STATIC_INIT +// +// MessageText: +// +// The importation from the file failed. +// +pub const ERROR_STATIC_INIT: i32 = 4002; + +// +// MessageId: ERROR_INC_BACKUP +// +// MessageText: +// +// The backup failed. Was a full backup done before? +// +pub const ERROR_INC_BACKUP: i32 = 4003; + +// +// MessageId: ERROR_FULL_BACKUP +// +// MessageText: +// +// The backup failed. Check the directory to which you are backing the database. +// +pub const ERROR_FULL_BACKUP: i32 = 4004; + +// +// MessageId: ERROR_REC_NON_EXISTENT +// +// MessageText: +// +// The name does not exist in the WINS database. +// +pub const ERROR_REC_NON_EXISTENT: i32 = 4005; + +// +// MessageId: ERROR_RPL_NOT_ALLOWED +// +// MessageText: +// +// Replication with a nonconfigured partner is not allowed. +// +pub const ERROR_RPL_NOT_ALLOWED: i32 = 4006; + +//////////////////////////////////// +// // +// DHCP Error Codes // +// // +//////////////////////////////////// +// +// MessageId: ERROR_DHCP_ADDRESS_CONFLICT +// +// MessageText: +// +// The DHCP client has obtained an IP address that is already in use on the network. The local interface will be disabled until the DHCP client can obtain a new address. +// +pub const ERROR_DHCP_ADDRESS_CONFLICT: i32 = 4100; + +//////////////////////////////////// +// // +// WMI Error Codes // +// // +//////////////////////////////////// +// +// MessageId: ERROR_WMI_GUID_NOT_FOUND +// +// MessageText: +// +// The GUID passed was not recognized as valid by a WMI data provider. +// +pub const ERROR_WMI_GUID_NOT_FOUND: i32 = 4200; + +// +// MessageId: ERROR_WMI_INSTANCE_NOT_FOUND +// +// MessageText: +// +// The instance name passed was not recognized as valid by a WMI data provider. +// +pub const ERROR_WMI_INSTANCE_NOT_FOUND: i32 = 4201; + +// +// MessageId: ERROR_WMI_ITEMID_NOT_FOUND +// +// MessageText: +// +// The data item ID passed was not recognized as valid by a WMI data provider. +// +pub const ERROR_WMI_ITEMID_NOT_FOUND: i32 = 4202; + +// +// MessageId: ERROR_WMI_TRY_AGAIN +// +// MessageText: +// +// The WMI request could not be completed and should be retried. +// +pub const ERROR_WMI_TRY_AGAIN: i32 = 4203; + +// +// MessageId: ERROR_WMI_DP_NOT_FOUND +// +// MessageText: +// +// The WMI data provider could not be located. +// +pub const ERROR_WMI_DP_NOT_FOUND: i32 = 4204; + +// +// MessageId: ERROR_WMI_UNRESOLVED_INSTANCE_REF +// +// MessageText: +// +// The WMI data provider references an instance set that has not been registered. +// +pub const ERROR_WMI_UNRESOLVED_INSTANCE_REF: i32 = 4205; + +// +// MessageId: ERROR_WMI_ALREADY_ENABLED +// +// MessageText: +// +// The WMI data block or event notification has already been enabled. +// +pub const ERROR_WMI_ALREADY_ENABLED: i32 = 4206; + +// +// MessageId: ERROR_WMI_GUID_DISCONNECTED +// +// MessageText: +// +// The WMI data block is no longer available. +// +pub const ERROR_WMI_GUID_DISCONNECTED: i32 = 4207; + +// +// MessageId: ERROR_WMI_SERVER_UNAVAILABLE +// +// MessageText: +// +// The WMI data service is not available. +// +pub const ERROR_WMI_SERVER_UNAVAILABLE: i32 = 4208; + +// +// MessageId: ERROR_WMI_DP_FAILED +// +// MessageText: +// +// The WMI data provider failed to carry out the request. +// +pub const ERROR_WMI_DP_FAILED: i32 = 4209; + +// +// MessageId: ERROR_WMI_INVALID_MOF +// +// MessageText: +// +// The WMI MOF information is not valid. +// +pub const ERROR_WMI_INVALID_MOF: i32 = 4210; + +// +// MessageId: ERROR_WMI_INVALID_REGINFO +// +// MessageText: +// +// The WMI registration information is not valid. +// +pub const ERROR_WMI_INVALID_REGINFO: i32 = 4211; + +// +// MessageId: ERROR_WMI_ALREADY_DISABLED +// +// MessageText: +// +// The WMI data block or event notification has already been disabled. +// +pub const ERROR_WMI_ALREADY_DISABLED: i32 = 4212; + +// +// MessageId: ERROR_WMI_READ_ONLY +// +// MessageText: +// +// The WMI data item or data block is read only. +// +pub const ERROR_WMI_READ_ONLY: i32 = 4213; + +// +// MessageId: ERROR_WMI_SET_FAILURE +// +// MessageText: +// +// The WMI data item or data block could not be changed. +// +pub const ERROR_WMI_SET_FAILURE: i32 = 4214; + +////////////////////////////////////////// +// // +// NT Media Services (RSM) Error Codes // +// // +////////////////////////////////////////// +// +// MessageId: ERROR_INVALID_MEDIA +// +// MessageText: +// +// The media identifier does not represent a valid medium. +// +pub const ERROR_INVALID_MEDIA: i32 = 4300; + +// +// MessageId: ERROR_INVALID_LIBRARY +// +// MessageText: +// +// The library identifier does not represent a valid library. +// +pub const ERROR_INVALID_LIBRARY: i32 = 4301; + +// +// MessageId: ERROR_INVALID_MEDIA_POOL +// +// MessageText: +// +// The media pool identifier does not represent a valid media pool. +// +pub const ERROR_INVALID_MEDIA_POOL: i32 = 4302; + +// +// MessageId: ERROR_DRIVE_MEDIA_MISMATCH +// +// MessageText: +// +// The drive and medium are not compatible or exist in different libraries. +// +pub const ERROR_DRIVE_MEDIA_MISMATCH: i32 = 4303; + +// +// MessageId: ERROR_MEDIA_OFFLINE +// +// MessageText: +// +// The medium currently exists in an offline library and must be online to perform this operation. +// +pub const ERROR_MEDIA_OFFLINE: i32 = 4304; + +// +// MessageId: ERROR_LIBRARY_OFFLINE +// +// MessageText: +// +// The operation cannot be performed on an offline library. +// +pub const ERROR_LIBRARY_OFFLINE: i32 = 4305; + +// +// MessageId: ERROR_EMPTY +// +// MessageText: +// +// The library, drive, or media pool is empty. +// +pub const ERROR_EMPTY: i32 = 4306; + +// +// MessageId: ERROR_NOT_EMPTY +// +// MessageText: +// +// The library, drive, or media pool must be empty to perform this operation. +// +pub const ERROR_NOT_EMPTY: i32 = 4307; + +// +// MessageId: ERROR_MEDIA_UNAVAILABLE +// +// MessageText: +// +// No media is currently available in this media pool or library. +// +pub const ERROR_MEDIA_UNAVAILABLE: i32 = 4308; + +// +// MessageId: ERROR_RESOURCE_DISABLED +// +// MessageText: +// +// A resource required for this operation is disabled. +// +pub const ERROR_RESOURCE_DISABLED: i32 = 4309; + +// +// MessageId: ERROR_INVALID_CLEANER +// +// MessageText: +// +// The media identifier does not represent a valid cleaner. +// +pub const ERROR_INVALID_CLEANER: i32 = 4310; + +// +// MessageId: ERROR_UNABLE_TO_CLEAN +// +// MessageText: +// +// The drive cannot be cleaned or does not support cleaning. +// +pub const ERROR_UNABLE_TO_CLEAN: i32 = 4311; + +// +// MessageId: ERROR_OBJECT_NOT_FOUND +// +// MessageText: +// +// The object identifier does not represent a valid object. +// +pub const ERROR_OBJECT_NOT_FOUND: i32 = 4312; + +// +// MessageId: ERROR_DATABASE_FAILURE +// +// MessageText: +// +// Unable to read from or write to the database. +// +pub const ERROR_DATABASE_FAILURE: i32 = 4313; + +// +// MessageId: ERROR_DATABASE_FULL +// +// MessageText: +// +// The database is full. +// +pub const ERROR_DATABASE_FULL: i32 = 4314; + +// +// MessageId: ERROR_MEDIA_INCOMPATIBLE +// +// MessageText: +// +// The medium is not compatible with the device or media pool. +// +pub const ERROR_MEDIA_INCOMPATIBLE: i32 = 4315; + +// +// MessageId: ERROR_RESOURCE_NOT_PRESENT +// +// MessageText: +// +// The resource required for this operation does not exist. +// +pub const ERROR_RESOURCE_NOT_PRESENT: i32 = 4316; + +// +// MessageId: ERROR_INVALID_OPERATION +// +// MessageText: +// +// The operation identifier is not valid. +// +pub const ERROR_INVALID_OPERATION: i32 = 4317; + +// +// MessageId: ERROR_MEDIA_NOT_AVAILABLE +// +// MessageText: +// +// The media is not mounted or ready for use. +// +pub const ERROR_MEDIA_NOT_AVAILABLE: i32 = 4318; + +// +// MessageId: ERROR_DEVICE_NOT_AVAILABLE +// +// MessageText: +// +// The device is not ready for use. +// +pub const ERROR_DEVICE_NOT_AVAILABLE: i32 = 4319; + +// +// MessageId: ERROR_REQUEST_REFUSED +// +// MessageText: +// +// The operator or administrator has refused the request. +// +pub const ERROR_REQUEST_REFUSED: i32 = 4320; + +// +// MessageId: ERROR_INVALID_DRIVE_OBJECT +// +// MessageText: +// +// The drive identifier does not represent a valid drive. +// +pub const ERROR_INVALID_DRIVE_OBJECT: i32 = 4321; + +// +// MessageId: ERROR_LIBRARY_FULL +// +// MessageText: +// +// Library is full. No slot is available for use. +// +pub const ERROR_LIBRARY_FULL: i32 = 4322; + +// +// MessageId: ERROR_MEDIUM_NOT_ACCESSIBLE +// +// MessageText: +// +// The transport cannot access the medium. +// +pub const ERROR_MEDIUM_NOT_ACCESSIBLE: i32 = 4323; + +// +// MessageId: ERROR_UNABLE_TO_LOAD_MEDIUM +// +// MessageText: +// +// Unable to load the medium into the drive. +// +pub const ERROR_UNABLE_TO_LOAD_MEDIUM: i32 = 4324; + +// +// MessageId: ERROR_UNABLE_TO_INVENTORY_DRIVE +// +// MessageText: +// +// Unable to retrieve the drive status. +// +pub const ERROR_UNABLE_TO_INVENTORY_DRIVE: i32 = 4325; + +// +// MessageId: ERROR_UNABLE_TO_INVENTORY_SLOT +// +// MessageText: +// +// Unable to retrieve the slot status. +// +pub const ERROR_UNABLE_TO_INVENTORY_SLOT: i32 = 4326; + +// +// MessageId: ERROR_UNABLE_TO_INVENTORY_TRANSPORT +// +// MessageText: +// +// Unable to retrieve status about the transport. +// +pub const ERROR_UNABLE_TO_INVENTORY_TRANSPORT: i32 = 4327; + +// +// MessageId: ERROR_TRANSPORT_FULL +// +// MessageText: +// +// Cannot use the transport because it is already in use. +// +pub const ERROR_TRANSPORT_FULL: i32 = 4328; + +// +// MessageId: ERROR_CONTROLLING_IEPORT +// +// MessageText: +// +// Unable to open or close the inject/eject port. +// +pub const ERROR_CONTROLLING_IEPORT: i32 = 4329; + +// +// MessageId: ERROR_UNABLE_TO_EJECT_MOUNTED_MEDIA +// +// MessageText: +// +// Unable to eject the medium because it is in a drive. +// +pub const ERROR_UNABLE_TO_EJECT_MOUNTED_MEDIA: i32 = 4330; + +// +// MessageId: ERROR_CLEANER_SLOT_SET +// +// MessageText: +// +// A cleaner slot is already reserved. +// +pub const ERROR_CLEANER_SLOT_SET: i32 = 4331; + +// +// MessageId: ERROR_CLEANER_SLOT_NOT_SET +// +// MessageText: +// +// A cleaner slot is not reserved. +// +pub const ERROR_CLEANER_SLOT_NOT_SET: i32 = 4332; + +// +// MessageId: ERROR_CLEANER_CARTRIDGE_SPENT +// +// MessageText: +// +// The cleaner cartridge has performed the maximum number of drive cleanings. +// +pub const ERROR_CLEANER_CARTRIDGE_SPENT: i32 = 4333; + +// +// MessageId: ERROR_UNEXPECTED_OMID +// +// MessageText: +// +// Unexpected on-medium identifier. +// +pub const ERROR_UNEXPECTED_OMID: i32 = 4334; + +// +// MessageId: ERROR_CANT_DELETE_LAST_ITEM +// +// MessageText: +// +// The last remaining item in this group or resource cannot be deleted. +// +pub const ERROR_CANT_DELETE_LAST_ITEM: i32 = 4335; + +// +// MessageId: ERROR_MESSAGE_EXCEEDS_MAX_SIZE +// +// MessageText: +// +// The message provided exceeds the maximum size allowed for this parameter. +// +pub const ERROR_MESSAGE_EXCEEDS_MAX_SIZE: i32 = 4336; + +// +// MessageId: ERROR_VOLUME_CONTAINS_SYS_FILES +// +// MessageText: +// +// The volume contains system or paging files. +// +pub const ERROR_VOLUME_CONTAINS_SYS_FILES: i32 = 4337; + +// +// MessageId: ERROR_INDIGENOUS_TYPE +// +// MessageText: +// +// The media type cannot be removed from this library since at least one drive in the library reports it can support this media type. +// +pub const ERROR_INDIGENOUS_TYPE: i32 = 4338; + +// +// MessageId: ERROR_NO_SUPPORTING_DRIVES +// +// MessageText: +// +// This offline media cannot be mounted on this system since no enabled drives are present which can be used. +// +pub const ERROR_NO_SUPPORTING_DRIVES: i32 = 4339; + +// +// MessageId: ERROR_CLEANER_CARTRIDGE_INSTALLED +// +// MessageText: +// +// A cleaner cartridge is present in the tape library. +// +pub const ERROR_CLEANER_CARTRIDGE_INSTALLED: i32 = 4340; + +// +// MessageId: ERROR_IEPORT_FULL +// +// MessageText: +// +// Cannot use the ieport because it is not empty. +// +pub const ERROR_IEPORT_FULL: i32 = 4341; + +//////////////////////////////////////////// +// // +// NT Remote Storage Service Error Codes // +// // +//////////////////////////////////////////// +// +// MessageId: ERROR_FILE_OFFLINE +// +// MessageText: +// +// The remote storage service was not able to recall the file. +// +pub const ERROR_FILE_OFFLINE: i32 = 4350; + +// +// MessageId: ERROR_REMOTE_STORAGE_NOT_ACTIVE +// +// MessageText: +// +// The remote storage service is not operational at this time. +// +pub const ERROR_REMOTE_STORAGE_NOT_ACTIVE: i32 = 4351; + +// +// MessageId: ERROR_REMOTE_STORAGE_MEDIA_ERROR +// +// MessageText: +// +// The remote storage service encountered a media error. +// +pub const ERROR_REMOTE_STORAGE_MEDIA_ERROR: i32 = 4352; + +//////////////////////////////////////////// +// // +// NT Reparse Points Error Codes // +// // +//////////////////////////////////////////// +// +// MessageId: ERROR_NOT_A_REPARSE_POINT +// +// MessageText: +// +// The file or directory is not a reparse point. +// +pub const ERROR_NOT_A_REPARSE_POINT: i32 = 4390; + +// +// MessageId: ERROR_REPARSE_ATTRIBUTE_CONFLICT +// +// MessageText: +// +// The reparse point attribute cannot be set because it conflicts with an existing attribute. +// +pub const ERROR_REPARSE_ATTRIBUTE_CONFLICT: i32 = 4391; + +// +// MessageId: ERROR_INVALID_REPARSE_DATA +// +// MessageText: +// +// The data present in the reparse point buffer is invalid. +// +pub const ERROR_INVALID_REPARSE_DATA: i32 = 4392; + +// +// MessageId: ERROR_REPARSE_TAG_INVALID +// +// MessageText: +// +// The tag present in the reparse point buffer is invalid. +// +pub const ERROR_REPARSE_TAG_INVALID: i32 = 4393; + +// +// MessageId: ERROR_REPARSE_TAG_MISMATCH +// +// MessageText: +// +// There is a mismatch between the tag specified in the request and the tag present in the reparse point. +// +// +pub const ERROR_REPARSE_TAG_MISMATCH: i32 = 4394; + +//////////////////////////////////////////// +// // +// NT Single Instance Store Error Codes // +// // +//////////////////////////////////////////// +// +// MessageId: ERROR_VOLUME_NOT_SIS_ENABLED +// +// MessageText: +// +// Single Instance Storage is not available on this volume. +// +pub const ERROR_VOLUME_NOT_SIS_ENABLED: i32 = 4500; + +//////////////////////////////////// +// // +// Cluster Error Codes // +// // +//////////////////////////////////// +// +// MessageId: ERROR_DEPENDENT_RESOURCE_EXISTS +// +// MessageText: +// +// The cluster resource cannot be moved to another group because other resources are dependent on it. +// +pub const ERROR_DEPENDENT_RESOURCE_EXISTS: i32 = 5001; + +// +// MessageId: ERROR_DEPENDENCY_NOT_FOUND +// +// MessageText: +// +// The cluster resource dependency cannot be found. +// +pub const ERROR_DEPENDENCY_NOT_FOUND: i32 = 5002; + +// +// MessageId: ERROR_DEPENDENCY_ALREADY_EXISTS +// +// MessageText: +// +// The cluster resource cannot be made dependent on the specified resource because it is already dependent. +// +pub const ERROR_DEPENDENCY_ALREADY_EXISTS: i32 = 5003; + +// +// MessageId: ERROR_RESOURCE_NOT_ONLINE +// +// MessageText: +// +// The cluster resource is not online. +// +pub const ERROR_RESOURCE_NOT_ONLINE: i32 = 5004; + +// +// MessageId: ERROR_HOST_NODE_NOT_AVAILABLE +// +// MessageText: +// +// A cluster node is not available for this operation. +// +pub const ERROR_HOST_NODE_NOT_AVAILABLE: i32 = 5005; + +// +// MessageId: ERROR_RESOURCE_NOT_AVAILABLE +// +// MessageText: +// +// The cluster resource is not available. +// +pub const ERROR_RESOURCE_NOT_AVAILABLE: i32 = 5006; + +// +// MessageId: ERROR_RESOURCE_NOT_FOUND +// +// MessageText: +// +// The cluster resource could not be found. +// +pub const ERROR_RESOURCE_NOT_FOUND: i32 = 5007; + +// +// MessageId: ERROR_SHUTDOWN_CLUSTER +// +// MessageText: +// +// The cluster is being shut down. +// +pub const ERROR_SHUTDOWN_CLUSTER: i32 = 5008; + +// +// MessageId: ERROR_CANT_EVICT_ACTIVE_NODE +// +// MessageText: +// +// A cluster node cannot be evicted from the cluster unless the node is down or it is the last node. +// +pub const ERROR_CANT_EVICT_ACTIVE_NODE: i32 = 5009; + +// +// MessageId: ERROR_OBJECT_ALREADY_EXISTS +// +// MessageText: +// +// The object already exists. +// +pub const ERROR_OBJECT_ALREADY_EXISTS: i32 = 5010; + +// +// MessageId: ERROR_OBJECT_IN_LIST +// +// MessageText: +// +// The object is already in the list. +// +pub const ERROR_OBJECT_IN_LIST: i32 = 5011; + +// +// MessageId: ERROR_GROUP_NOT_AVAILABLE +// +// MessageText: +// +// The cluster group is not available for any new requests. +// +pub const ERROR_GROUP_NOT_AVAILABLE: i32 = 5012; + +// +// MessageId: ERROR_GROUP_NOT_FOUND +// +// MessageText: +// +// The cluster group could not be found. +// +pub const ERROR_GROUP_NOT_FOUND: i32 = 5013; + +// +// MessageId: ERROR_GROUP_NOT_ONLINE +// +// MessageText: +// +// The operation could not be completed because the cluster group is not online. +// +pub const ERROR_GROUP_NOT_ONLINE: i32 = 5014; + +// +// MessageId: ERROR_HOST_NODE_NOT_RESOURCE_OWNER +// +// MessageText: +// +// The cluster node is not the owner of the resource. +// +pub const ERROR_HOST_NODE_NOT_RESOURCE_OWNER: i32 = 5015; + +// +// MessageId: ERROR_HOST_NODE_NOT_GROUP_OWNER +// +// MessageText: +// +// The cluster node is not the owner of the group. +// +pub const ERROR_HOST_NODE_NOT_GROUP_OWNER: i32 = 5016; + +// +// MessageId: ERROR_RESMON_CREATE_FAILED +// +// MessageText: +// +// The cluster resource could not be created in the specified resource monitor. +// +pub const ERROR_RESMON_CREATE_FAILED: i32 = 5017; + +// +// MessageId: ERROR_RESMON_ONLINE_FAILED +// +// MessageText: +// +// The cluster resource could not be brought online by the resource monitor. +// +pub const ERROR_RESMON_ONLINE_FAILED: i32 = 5018; + +// +// MessageId: ERROR_RESOURCE_ONLINE +// +// MessageText: +// +// The operation could not be completed because the cluster resource is online. +// +pub const ERROR_RESOURCE_ONLINE: i32 = 5019; + +// +// MessageId: ERROR_QUORUM_RESOURCE +// +// MessageText: +// +// The cluster resource could not be deleted or brought offline because it is the quorum resource. +// +pub const ERROR_QUORUM_RESOURCE: i32 = 5020; + +// +// MessageId: ERROR_NOT_QUORUM_CAPABLE +// +// MessageText: +// +// The cluster could not make the specified resource a quorum resource because it is not capable of being a quorum resource. +// +pub const ERROR_NOT_QUORUM_CAPABLE: i32 = 5021; + +// +// MessageId: ERROR_CLUSTER_SHUTTING_DOWN +// +// MessageText: +// +// The cluster software is shutting down. +// +pub const ERROR_CLUSTER_SHUTTING_DOWN: i32 = 5022; + +// +// MessageId: ERROR_INVALID_STATE +// +// MessageText: +// +// The group or resource is not in the correct state to perform the requested operation. +// +pub const ERROR_INVALID_STATE: i32 = 5023; + +// +// MessageId: ERROR_RESOURCE_PROPERTIES_STORED +// +// MessageText: +// +// The properties were stored but not all changes will take effect until the next time the resource is brought online. +// +pub const ERROR_RESOURCE_PROPERTIES_STORED: i32 = 5024; + +// +// MessageId: ERROR_NOT_QUORUM_CLASS +// +// MessageText: +// +// The cluster could not make the specified resource a quorum resource because it does not belong to a shared storage class. +// +pub const ERROR_NOT_QUORUM_CLASS: i32 = 5025; + +// +// MessageId: ERROR_CORE_RESOURCE +// +// MessageText: +// +// The cluster resource could not be deleted since it is a core resource. +// +pub const ERROR_CORE_RESOURCE: i32 = 5026; + +// +// MessageId: ERROR_QUORUM_RESOURCE_ONLINE_FAILED +// +// MessageText: +// +// The quorum resource failed to come online. +// +pub const ERROR_QUORUM_RESOURCE_ONLINE_FAILED: i32 = 5027; + +// +// MessageId: ERROR_QUORUMLOG_OPEN_FAILED +// +// MessageText: +// +// The quorum log could not be created or mounted successfully. +// +pub const ERROR_QUORUMLOG_OPEN_FAILED: i32 = 5028; + +// +// MessageId: ERROR_CLUSTERLOG_CORRUPT +// +// MessageText: +// +// The cluster log is corrupt. +// +pub const ERROR_CLUSTERLOG_CORRUPT: i32 = 5029; + +// +// MessageId: ERROR_CLUSTERLOG_RECORD_EXCEEDS_MAXSIZE +// +// MessageText: +// +// The record could not be written to the cluster log since it exceeds the maximum size. +// +pub const ERROR_CLUSTERLOG_RECORD_EXCEEDS_MAXSIZE: i32 = 5030; + +// +// MessageId: ERROR_CLUSTERLOG_EXCEEDS_MAXSIZE +// +// MessageText: +// +// The cluster log exceeds its maximum size. +// +pub const ERROR_CLUSTERLOG_EXCEEDS_MAXSIZE: i32 = 5031; + +// +// MessageId: ERROR_CLUSTERLOG_CHKPOINT_NOT_FOUND +// +// MessageText: +// +// No checkpoint record was found in the cluster log. +// +pub const ERROR_CLUSTERLOG_CHKPOINT_NOT_FOUND: i32 = 5032; + +// +// MessageId: ERROR_CLUSTERLOG_NOT_ENOUGH_SPACE +// +// MessageText: +// +// The minimum required disk space needed for logging is not available. +// +pub const ERROR_CLUSTERLOG_NOT_ENOUGH_SPACE: i32 = 5033; + +// +// MessageId: ERROR_QUORUM_OWNER_ALIVE +// +// MessageText: +// +// The cluster node failed to take control of the quorum resource because the resource is owned by another active node. +// +pub const ERROR_QUORUM_OWNER_ALIVE: i32 = 5034; + +// +// MessageId: ERROR_NETWORK_NOT_AVAILABLE +// +// MessageText: +// +// A cluster network is not available for this operation. +// +pub const ERROR_NETWORK_NOT_AVAILABLE: i32 = 5035; + +// +// MessageId: ERROR_NODE_NOT_AVAILABLE +// +// MessageText: +// +// A cluster node is not available for this operation. +// +pub const ERROR_NODE_NOT_AVAILABLE: i32 = 5036; + +// +// MessageId: ERROR_ALL_NODES_NOT_AVAILABLE +// +// MessageText: +// +// All cluster nodes must be running to perform this operation. +// +pub const ERROR_ALL_NODES_NOT_AVAILABLE: i32 = 5037; + +// +// MessageId: ERROR_RESOURCE_FAILED +// +// MessageText: +// +// A cluster resource failed. +// +pub const ERROR_RESOURCE_FAILED: i32 = 5038; + +// +// MessageId: ERROR_CLUSTER_INVALID_NODE +// +// MessageText: +// +// The cluster node is not valid. +// +pub const ERROR_CLUSTER_INVALID_NODE: i32 = 5039; + +// +// MessageId: ERROR_CLUSTER_NODE_EXISTS +// +// MessageText: +// +// The cluster node already exists. +// +pub const ERROR_CLUSTER_NODE_EXISTS: i32 = 5040; + +// +// MessageId: ERROR_CLUSTER_JOIN_IN_PROGRESS +// +// MessageText: +// +// A node is in the process of joining the cluster. +// +pub const ERROR_CLUSTER_JOIN_IN_PROGRESS: i32 = 5041; + +// +// MessageId: ERROR_CLUSTER_NODE_NOT_FOUND +// +// MessageText: +// +// The cluster node was not found. +// +pub const ERROR_CLUSTER_NODE_NOT_FOUND: i32 = 5042; + +// +// MessageId: ERROR_CLUSTER_LOCAL_NODE_NOT_FOUND +// +// MessageText: +// +// The cluster local node information was not found. +// +pub const ERROR_CLUSTER_LOCAL_NODE_NOT_FOUND: i32 = 5043; + +// +// MessageId: ERROR_CLUSTER_NETWORK_EXISTS +// +// MessageText: +// +// The cluster network already exists. +// +pub const ERROR_CLUSTER_NETWORK_EXISTS: i32 = 5044; + +// +// MessageId: ERROR_CLUSTER_NETWORK_NOT_FOUND +// +// MessageText: +// +// The cluster network was not found. +// +pub const ERROR_CLUSTER_NETWORK_NOT_FOUND: i32 = 5045; + +// +// MessageId: ERROR_CLUSTER_NETINTERFACE_EXISTS +// +// MessageText: +// +// The cluster network interface already exists. +// +pub const ERROR_CLUSTER_NETINTERFACE_EXISTS: i32 = 5046; + +// +// MessageId: ERROR_CLUSTER_NETINTERFACE_NOT_FOUND +// +// MessageText: +// +// The cluster network interface was not found. +// +pub const ERROR_CLUSTER_NETINTERFACE_NOT_FOUND: i32 = 5047; + +// +// MessageId: ERROR_CLUSTER_INVALID_REQUEST +// +// MessageText: +// +// The cluster request is not valid for this object. +// +pub const ERROR_CLUSTER_INVALID_REQUEST: i32 = 5048; + +// +// MessageId: ERROR_CLUSTER_INVALID_NETWORK_PROVIDER +// +// MessageText: +// +// The cluster network provider is not valid. +// +pub const ERROR_CLUSTER_INVALID_NETWORK_PROVIDER: i32 = 5049; + +// +// MessageId: ERROR_CLUSTER_NODE_DOWN +// +// MessageText: +// +// The cluster node is down. +// +pub const ERROR_CLUSTER_NODE_DOWN: i32 = 5050; + +// +// MessageId: ERROR_CLUSTER_NODE_UNREACHABLE +// +// MessageText: +// +// The cluster node is not reachable. +// +pub const ERROR_CLUSTER_NODE_UNREACHABLE: i32 = 5051; + +// +// MessageId: ERROR_CLUSTER_NODE_NOT_MEMBER +// +// MessageText: +// +// The cluster node is not a member of the cluster. +// +pub const ERROR_CLUSTER_NODE_NOT_MEMBER: i32 = 5052; + +// +// MessageId: ERROR_CLUSTER_JOIN_NOT_IN_PROGRESS +// +// MessageText: +// +// A cluster join operation is not in progress. +// +pub const ERROR_CLUSTER_JOIN_NOT_IN_PROGRESS: i32 = 5053; + +// +// MessageId: ERROR_CLUSTER_INVALID_NETWORK +// +// MessageText: +// +// The cluster network is not valid. +// +pub const ERROR_CLUSTER_INVALID_NETWORK: i32 = 5054; + +// +// MessageId: ERROR_CLUSTER_NODE_UP +// +// MessageText: +// +// The cluster node is up. +// +pub const ERROR_CLUSTER_NODE_UP: i32 = 5056; + +// +// MessageId: ERROR_CLUSTER_IPADDR_IN_USE +// +// MessageText: +// +// The cluster IP address is already in use. +// +pub const ERROR_CLUSTER_IPADDR_IN_USE: i32 = 5057; + +// +// MessageId: ERROR_CLUSTER_NODE_NOT_PAUSED +// +// MessageText: +// +// The cluster node is not paused. +// +pub const ERROR_CLUSTER_NODE_NOT_PAUSED: i32 = 5058; + +// +// MessageId: ERROR_CLUSTER_NO_SECURITY_CONTEXT +// +// MessageText: +// +// No cluster security context is available. +// +pub const ERROR_CLUSTER_NO_SECURITY_CONTEXT: i32 = 5059; + +// +// MessageId: ERROR_CLUSTER_NETWORK_NOT_INTERNAL +// +// MessageText: +// +// The cluster network is not configured for internal cluster communication. +// +pub const ERROR_CLUSTER_NETWORK_NOT_INTERNAL: i32 = 5060; + +// +// MessageId: ERROR_CLUSTER_NODE_ALREADY_UP +// +// MessageText: +// +// The cluster node is already up. +// +pub const ERROR_CLUSTER_NODE_ALREADY_UP: i32 = 5061; + +// +// MessageId: ERROR_CLUSTER_NODE_ALREADY_DOWN +// +// MessageText: +// +// The cluster node is already down. +// +pub const ERROR_CLUSTER_NODE_ALREADY_DOWN: i32 = 5062; + +// +// MessageId: ERROR_CLUSTER_NETWORK_ALREADY_ONLINE +// +// MessageText: +// +// The cluster network is already online. +// +pub const ERROR_CLUSTER_NETWORK_ALREADY_ONLINE: i32 = 5063; + +// +// MessageId: ERROR_CLUSTER_NETWORK_ALREADY_OFFLINE +// +// MessageText: +// +// The cluster network is already offline. +// +pub const ERROR_CLUSTER_NETWORK_ALREADY_OFFLINE: i32 = 5064; + +// +// MessageId: ERROR_CLUSTER_NODE_ALREADY_MEMBER +// +// MessageText: +// +// The cluster node is already a member of the cluster. +// +pub const ERROR_CLUSTER_NODE_ALREADY_MEMBER: i32 = 5065; + +// +// MessageId: ERROR_CLUSTER_LAST_INTERNAL_NETWORK +// +// MessageText: +// +// The cluster network is the only one configured for internal cluster communication between two or more active cluster nodes. The internal communication capability cannot be removed from the network. +// +pub const ERROR_CLUSTER_LAST_INTERNAL_NETWORK: i32 = 5066; + +// +// MessageId: ERROR_CLUSTER_NETWORK_HAS_DEPENDENTS +// +// MessageText: +// +// One or more cluster resources depend on the network to provide service to clients. The client access capability cannot be removed from the network. +// +pub const ERROR_CLUSTER_NETWORK_HAS_DEPENDENTS: i32 = 5067; + +// +// MessageId: ERROR_INVALID_OPERATION_ON_QUORUM +// +// MessageText: +// +// This operation cannot be performed on the cluster resource as it the quorum resource. You may not bring the quorum resource offline or modify its possible owners list. +// +pub const ERROR_INVALID_OPERATION_ON_QUORUM: i32 = 5068; + +// +// MessageId: ERROR_DEPENDENCY_NOT_ALLOWED +// +// MessageText: +// +// The cluster quorum resource is not allowed to have any dependencies. +// +pub const ERROR_DEPENDENCY_NOT_ALLOWED: i32 = 5069; + +// +// MessageId: ERROR_CLUSTER_NODE_PAUSED +// +// MessageText: +// +// The cluster node is paused. +// +pub const ERROR_CLUSTER_NODE_PAUSED: i32 = 5070; + +// +// MessageId: ERROR_NODE_CANT_HOST_RESOURCE +// +// MessageText: +// +// The cluster resource cannot be brought online. The owner node cannot run this resource. +// +pub const ERROR_NODE_CANT_HOST_RESOURCE: i32 = 5071; + +// +// MessageId: ERROR_CLUSTER_NODE_NOT_READY +// +// MessageText: +// +// The cluster node is not ready to perform the requested operation. +// +pub const ERROR_CLUSTER_NODE_NOT_READY: i32 = 5072; + +// +// MessageId: ERROR_CLUSTER_NODE_SHUTTING_DOWN +// +// MessageText: +// +// The cluster node is shutting down. +// +pub const ERROR_CLUSTER_NODE_SHUTTING_DOWN: i32 = 5073; + +// +// MessageId: ERROR_CLUSTER_JOIN_ABORTED +// +// MessageText: +// +// The cluster join operation was aborted. +// +pub const ERROR_CLUSTER_JOIN_ABORTED: i32 = 5074; + +// +// MessageId: ERROR_CLUSTER_INCOMPATIBLE_VERSIONS +// +// MessageText: +// +// The cluster join operation failed due to incompatible software versions between the joining node and its sponsor. +// +pub const ERROR_CLUSTER_INCOMPATIBLE_VERSIONS: i32 = 5075; + +// +// MessageId: ERROR_CLUSTER_MAXNUM_OF_RESOURCES_EXCEEDED +// +// MessageText: +// +// This resource cannot be created because the cluster has reached the limit on the number of resources it can monitor. +// +pub const ERROR_CLUSTER_MAXNUM_OF_RESOURCES_EXCEEDED: i32 = 5076; + +// +// MessageId: ERROR_CLUSTER_SYSTEM_CONFIG_CHANGED +// +// MessageText: +// +// The system configuration changed during the cluster join or form operation. The join or form operation was aborted. +// +pub const ERROR_CLUSTER_SYSTEM_CONFIG_CHANGED: i32 = 5077; + +// +// MessageId: ERROR_CLUSTER_RESOURCE_TYPE_NOT_FOUND +// +// MessageText: +// +// The specified resource type was not found. +// +pub const ERROR_CLUSTER_RESOURCE_TYPE_NOT_FOUND: i32 = 5078; + +// +// MessageId: ERROR_CLUSTER_RESTYPE_NOT_SUPPORTED +// +// MessageText: +// +// The specified node does not support a resource of this type. This may be due to version inconsistencies or due to the absence of the resource DLL on this node. +// +pub const ERROR_CLUSTER_RESTYPE_NOT_SUPPORTED: i32 = 5079; + +// +// MessageId: ERROR_CLUSTER_RESNAME_NOT_FOUND +// +// MessageText: +// +// The specified resource name is not supported by this resource DLL. This may be due to a bad (or changed) name supplied to the resource DLL. +// +pub const ERROR_CLUSTER_RESNAME_NOT_FOUND: i32 = 5080; + +// +// MessageId: ERROR_CLUSTER_NO_RPC_PACKAGES_REGISTERED +// +// MessageText: +// +// No authentication package could be registered with the RPC server. +// +pub const ERROR_CLUSTER_NO_RPC_PACKAGES_REGISTERED: i32 = 5081; + +// +// MessageId: ERROR_CLUSTER_OWNER_NOT_IN_PREFLIST +// +// MessageText: +// +// You cannot bring the group online because the owner of the group is not in the preferred list for the group. To change the owner node for the group, move the group. +// +pub const ERROR_CLUSTER_OWNER_NOT_IN_PREFLIST: i32 = 5082; + +// +// MessageId: ERROR_CLUSTER_DATABASE_SEQMISMATCH +// +// MessageText: +// +// The join operation failed because the cluster database sequence number has changed or is incompatible with the locker node. This may happen during a join operation if the cluster database was changing during the join. +// +pub const ERROR_CLUSTER_DATABASE_SEQMISMATCH: i32 = 5083; + +// +// MessageId: ERROR_RESMON_INVALID_STATE +// +// MessageText: +// +// The resource monitor will not allow the fail operation to be performed while the resource is in its current state. This may happen if the resource is in a pending state. +// +pub const ERROR_RESMON_INVALID_STATE: i32 = 5084; + +// +// MessageId: ERROR_CLUSTER_GUM_NOT_LOCKER +// +// MessageText: +// +// A non locker code got a request to reserve the lock for making global updates. +// +pub const ERROR_CLUSTER_GUM_NOT_LOCKER: i32 = 5085; + +// +// MessageId: ERROR_QUORUM_DISK_NOT_FOUND +// +// MessageText: +// +// The quorum disk could not be located by the cluster service. +// +pub const ERROR_QUORUM_DISK_NOT_FOUND: i32 = 5086; + +// +// MessageId: ERROR_DATABASE_BACKUP_CORRUPT +// +// MessageText: +// +// The backed up cluster database is possibly corrupt. +// +pub const ERROR_DATABASE_BACKUP_CORRUPT: i32 = 5087; + +// +// MessageId: ERROR_CLUSTER_NODE_ALREADY_HAS_DFS_ROOT +// +// MessageText: +// +// A DFS root already exists in this cluster node. +// +pub const ERROR_CLUSTER_NODE_ALREADY_HAS_DFS_ROOT: i32 = 5088; + +// +// MessageId: ERROR_RESOURCE_PROPERTY_UNCHANGEABLE +// +// MessageText: +// +// An attempt to modify a resource property failed because it conflicts with another existing property. +// +pub const ERROR_RESOURCE_PROPERTY_UNCHANGEABLE: i32 = 5089; + +/* + Codes from 4300 through 5889 overlap with codes in ds\published\inc\apperr2.w. + Do not add any more error codes in that range. +*/ +// +// MessageId: ERROR_CLUSTER_MEMBERSHIP_INVALID_STATE +// +// MessageText: +// +// An operation was attempted that is incompatible with the current membership state of the node. +// +pub const ERROR_CLUSTER_MEMBERSHIP_INVALID_STATE: i32 = 5890; + +// +// MessageId: ERROR_CLUSTER_QUORUMLOG_NOT_FOUND +// +// MessageText: +// +// The quorum resource does not contain the quorum log. +// +pub const ERROR_CLUSTER_QUORUMLOG_NOT_FOUND: i32 = 5891; + +// +// MessageId: ERROR_CLUSTER_MEMBERSHIP_HALT +// +// MessageText: +// +// The membership engine requested shutdown of the cluster service on this node. +// +pub const ERROR_CLUSTER_MEMBERSHIP_HALT: i32 = 5892; + +// +// MessageId: ERROR_CLUSTER_INSTANCE_ID_MISMATCH +// +// MessageText: +// +// The join operation failed because the cluster instance ID of the joining node does not match the cluster instance ID of the sponsor node. +// +pub const ERROR_CLUSTER_INSTANCE_ID_MISMATCH: i32 = 5893; + +// +// MessageId: ERROR_CLUSTER_NETWORK_NOT_FOUND_FOR_IP +// +// MessageText: +// +// A matching network for the specified IP address could not be found. Please also specify a subnet mask and a cluster network. +// +pub const ERROR_CLUSTER_NETWORK_NOT_FOUND_FOR_IP: i32 = 5894; + +// +// MessageId: ERROR_CLUSTER_PROPERTY_DATA_TYPE_MISMATCH +// +// MessageText: +// +// The actual data type of the property did not match the expected data type of the property. +// +pub const ERROR_CLUSTER_PROPERTY_DATA_TYPE_MISMATCH: i32 = 5895; + +// +// MessageId: ERROR_CLUSTER_EVICT_WITHOUT_CLEANUP +// +// MessageText: +// +// The cluster node was evicted from the cluster successfully, but the node was not cleaned up. Extended status information explaining why the node was not cleaned up is available. +// +pub const ERROR_CLUSTER_EVICT_WITHOUT_CLEANUP: i32 = 5896; + +// +// MessageId: ERROR_CLUSTER_PARAMETER_MISMATCH +// +// MessageText: +// +// Two or more parameter values specified for a resource's properties are in conflict. +// +pub const ERROR_CLUSTER_PARAMETER_MISMATCH: i32 = 5897; + +// +// MessageId: ERROR_NODE_CANNOT_BE_CLUSTERED +// +// MessageText: +// +// This computer cannot be made a member of a cluster. +// +pub const ERROR_NODE_CANNOT_BE_CLUSTERED: i32 = 5898; + +// +// MessageId: ERROR_CLUSTER_WRONG_OS_VERSION +// +// MessageText: +// +// This computer cannot be made a member of a cluster because it does not have the correct version of Windows installed. +// +pub const ERROR_CLUSTER_WRONG_OS_VERSION: i32 = 5899; + +// +// MessageId: ERROR_CLUSTER_CANT_CREATE_DUP_CLUSTER_NAME +// +// MessageText: +// +// A cluster cannot be created with the specified cluster name because that cluster name is already in use. Specify a different name for the cluster. +// +pub const ERROR_CLUSTER_CANT_CREATE_DUP_CLUSTER_NAME: i32 = 5900; + +// +// MessageId: ERROR_CLUSCFG_ALREADY_COMMITTED +// +// MessageText: +// +// The cluster configuration action has already been committed. +// +pub const ERROR_CLUSCFG_ALREADY_COMMITTED: i32 = 5901; + +// +// MessageId: ERROR_CLUSCFG_ROLLBACK_FAILED +// +// MessageText: +// +// The cluster configuration action could not be rolled back. +// +pub const ERROR_CLUSCFG_ROLLBACK_FAILED: i32 = 5902; + +// +// MessageId: ERROR_CLUSCFG_SYSTEM_DISK_DRIVE_LETTER_CONFLICT +// +// MessageText: +// +// The drive letter assigned to a system disk on one node conflicted with the drive letter assigned to a disk on another node. +// +pub const ERROR_CLUSCFG_SYSTEM_DISK_DRIVE_LETTER_CONFLICT: i32 = 5903; + +// +// MessageId: ERROR_CLUSTER_OLD_VERSION +// +// MessageText: +// +// One or more nodes in the cluster are running a version of Windows that does not support this operation. +// +pub const ERROR_CLUSTER_OLD_VERSION: i32 = 5904; + +// +// MessageId: ERROR_CLUSTER_MISMATCHED_COMPUTER_ACCT_NAME +// +// MessageText: +// +// The name of the corresponding computer account doesn't match the Network Name for this resource. +// +pub const ERROR_CLUSTER_MISMATCHED_COMPUTER_ACCT_NAME: i32 = 5905; + +//////////////////////////////////// +// // +// EFS Error Codes // +// // +//////////////////////////////////// +// +// MessageId: ERROR_ENCRYPTION_FAILED +// +// MessageText: +// +// The specified file could not be encrypted. +// +pub const ERROR_ENCRYPTION_FAILED: i32 = 6000; + +// +// MessageId: ERROR_DECRYPTION_FAILED +// +// MessageText: +// +// The specified file could not be decrypted. +// +pub const ERROR_DECRYPTION_FAILED: i32 = 6001; + +// +// MessageId: ERROR_FILE_ENCRYPTED +// +// MessageText: +// +// The specified file is encrypted and the user does not have the ability to decrypt it. +// +pub const ERROR_FILE_ENCRYPTED: i32 = 6002; + +// +// MessageId: ERROR_NO_RECOVERY_POLICY +// +// MessageText: +// +// There is no valid encryption recovery policy configured for this system. +// +pub const ERROR_NO_RECOVERY_POLICY: i32 = 6003; + +// +// MessageId: ERROR_NO_EFS +// +// MessageText: +// +// The required encryption driver is not loaded for this system. +// +pub const ERROR_NO_EFS: i32 = 6004; + +// +// MessageId: ERROR_WRONG_EFS +// +// MessageText: +// +// The file was encrypted with a different encryption driver than is currently loaded. +// +pub const ERROR_WRONG_EFS: i32 = 6005; + +// +// MessageId: ERROR_NO_USER_KEYS +// +// MessageText: +// +// There are no EFS keys defined for the user. +// +pub const ERROR_NO_USER_KEYS: i32 = 6006; + +// +// MessageId: ERROR_FILE_NOT_ENCRYPTED +// +// MessageText: +// +// The specified file is not encrypted. +// +pub const ERROR_FILE_NOT_ENCRYPTED: i32 = 6007; + +// +// MessageId: ERROR_NOT_EXPORT_FORMAT +// +// MessageText: +// +// The specified file is not in the defined EFS export format. +// +pub const ERROR_NOT_EXPORT_FORMAT: i32 = 6008; + +// +// MessageId: ERROR_FILE_READ_ONLY +// +// MessageText: +// +// The specified file is read only. +// +pub const ERROR_FILE_READ_ONLY: i32 = 6009; + +// +// MessageId: ERROR_DIR_EFS_DISALLOWED +// +// MessageText: +// +// The directory has been disabled for encryption. +// +pub const ERROR_DIR_EFS_DISALLOWED: i32 = 6010; + +// +// MessageId: ERROR_EFS_SERVER_NOT_TRUSTED +// +// MessageText: +// +// The server is not trusted for remote encryption operation. +// +pub const ERROR_EFS_SERVER_NOT_TRUSTED: i32 = 6011; + +// +// MessageId: ERROR_BAD_RECOVERY_POLICY +// +// MessageText: +// +// Recovery policy configured for this system contains invalid recovery certificate. +// +pub const ERROR_BAD_RECOVERY_POLICY: i32 = 6012; + +// +// MessageId: ERROR_EFS_ALG_BLOB_TOO_BIG +// +// MessageText: +// +// The encryption algorithm used on the source file needs a bigger key buffer than the one on the destination file. +// +pub const ERROR_EFS_ALG_BLOB_TOO_BIG: i32 = 6013; + +// +// MessageId: ERROR_VOLUME_NOT_SUPPORT_EFS +// +// MessageText: +// +// The disk partition does not support file encryption. +// +pub const ERROR_VOLUME_NOT_SUPPORT_EFS: i32 = 6014; + +// +// MessageId: ERROR_EFS_DISABLED +// +// MessageText: +// +// This machine is disabled for file encryption. +// +pub const ERROR_EFS_DISABLED: i32 = 6015; + +// +// MessageId: ERROR_EFS_VERSION_NOT_SUPPORT +// +// MessageText: +// +// A newer system is required to decrypt this encrypted file. +// +pub const ERROR_EFS_VERSION_NOT_SUPPORT: i32 = 6016; + +// This message number is for historical purposes and cannot be changed or re-used. +// +// MessageId: ERROR_NO_BROWSER_SERVERS_FOUND +// +// MessageText: +// +// The list of servers for this workgroup is not currently available +// +pub const ERROR_NO_BROWSER_SERVERS_FOUND: i32 = 6118; + +////////////////////////////////////////////////////////////////// +// // +// Task Scheduler Error Codes that NET START must understand // +// // +////////////////////////////////////////////////////////////////// +// +// MessageId: SCHED_E_SERVICE_NOT_LOCALSYSTEM +// +// MessageText: +// +// The Task Scheduler service must be configured to run in the System account to function properly. Individual tasks may be configured to run in other accounts. +// +pub const SCHED_E_SERVICE_NOT_LOCALSYSTEM: i32 = 6200; + +//////////////////////////////////// +// // +// Terminal Server Error Codes // +// // +//////////////////////////////////// +// +// MessageId: ERROR_CTX_WINSTATION_NAME_INVALID +// +// MessageText: +// +// The specified session name is invalid. +// +pub const ERROR_CTX_WINSTATION_NAME_INVALID: i32 = 7001; + +// +// MessageId: ERROR_CTX_INVALID_PD +// +// MessageText: +// +// The specified protocol driver is invalid. +// +pub const ERROR_CTX_INVALID_PD: i32 = 7002; + +// +// MessageId: ERROR_CTX_PD_NOT_FOUND +// +// MessageText: +// +// The specified protocol driver was not found in the system path. +// +pub const ERROR_CTX_PD_NOT_FOUND: i32 = 7003; + +// +// MessageId: ERROR_CTX_WD_NOT_FOUND +// +// MessageText: +// +// The specified terminal connection driver was not found in the system path. +// +pub const ERROR_CTX_WD_NOT_FOUND: i32 = 7004; + +// +// MessageId: ERROR_CTX_CANNOT_MAKE_EVENTLOG_ENTRY +// +// MessageText: +// +// A registry key for event logging could not be created for this session. +// +pub const ERROR_CTX_CANNOT_MAKE_EVENTLOG_ENTRY: i32 = 7005; + +// +// MessageId: ERROR_CTX_SERVICE_NAME_COLLISION +// +// MessageText: +// +// A service with the same name already exists on the system. +// +pub const ERROR_CTX_SERVICE_NAME_COLLISION: i32 = 7006; + +// +// MessageId: ERROR_CTX_CLOSE_PENDING +// +// MessageText: +// +// A close operation is pending on the session. +// +pub const ERROR_CTX_CLOSE_PENDING: i32 = 7007; + +// +// MessageId: ERROR_CTX_NO_OUTBUF +// +// MessageText: +// +// There are no free output buffers available. +// +pub const ERROR_CTX_NO_OUTBUF: i32 = 7008; + +// +// MessageId: ERROR_CTX_MODEM_INF_NOT_FOUND +// +// MessageText: +// +// The MODEM.INF file was not found. +// +pub const ERROR_CTX_MODEM_INF_NOT_FOUND: i32 = 7009; + +// +// MessageId: ERROR_CTX_INVALID_MODEMNAME +// +// MessageText: +// +// The modem name was not found in MODEM.INF. +// +pub const ERROR_CTX_INVALID_MODEMNAME: i32 = 7010; + +// +// MessageId: ERROR_CTX_MODEM_RESPONSE_ERROR +// +// MessageText: +// +// The modem did not accept the command sent to it. Verify that the configured modem name matches the attached modem. +// +pub const ERROR_CTX_MODEM_RESPONSE_ERROR: i32 = 7011; + +// +// MessageId: ERROR_CTX_MODEM_RESPONSE_TIMEOUT +// +// MessageText: +// +// The modem did not respond to the command sent to it. Verify that the modem is properly cabled and powered on. +// +pub const ERROR_CTX_MODEM_RESPONSE_TIMEOUT: i32 = 7012; + +// +// MessageId: ERROR_CTX_MODEM_RESPONSE_NO_CARRIER +// +// MessageText: +// +// Carrier detect has failed or carrier has been dropped due to disconnect. +// +pub const ERROR_CTX_MODEM_RESPONSE_NO_CARRIER: i32 = 7013; + +// +// MessageId: ERROR_CTX_MODEM_RESPONSE_NO_DIALTONE +// +// MessageText: +// +// Dial tone not detected within the required time. Verify that the phone cable is properly attached and functional. +// +pub const ERROR_CTX_MODEM_RESPONSE_NO_DIALTONE: i32 = 7014; + +// +// MessageId: ERROR_CTX_MODEM_RESPONSE_BUSY +// +// MessageText: +// +// Busy signal detected at remote site on callback. +// +pub const ERROR_CTX_MODEM_RESPONSE_BUSY: i32 = 7015; + +// +// MessageId: ERROR_CTX_MODEM_RESPONSE_VOICE +// +// MessageText: +// +// Voice detected at remote site on callback. +// +pub const ERROR_CTX_MODEM_RESPONSE_VOICE: i32 = 7016; + +// +// MessageId: ERROR_CTX_TD_ERROR +// +// MessageText: +// +// Transport driver error +// +pub const ERROR_CTX_TD_ERROR: i32 = 7017; + +// +// MessageId: ERROR_CTX_WINSTATION_NOT_FOUND +// +// MessageText: +// +// The specified session cannot be found. +// +pub const ERROR_CTX_WINSTATION_NOT_FOUND: i32 = 7022; + +// +// MessageId: ERROR_CTX_WINSTATION_ALREADY_EXISTS +// +// MessageText: +// +// The specified session name is already in use. +// +pub const ERROR_CTX_WINSTATION_ALREADY_EXISTS: i32 = 7023; + +// +// MessageId: ERROR_CTX_WINSTATION_BUSY +// +// MessageText: +// +// The requested operation cannot be completed because the terminal connection is currently busy processing a connect, disconnect, reset, or delete operation. +// +pub const ERROR_CTX_WINSTATION_BUSY: i32 = 7024; + +// +// MessageId: ERROR_CTX_BAD_VIDEO_MODE +// +// MessageText: +// +// An attempt has been made to connect to a session whose video mode is not supported by the current client. +// +pub const ERROR_CTX_BAD_VIDEO_MODE: i32 = 7025; + +// +// MessageId: ERROR_CTX_GRAPHICS_INVALID +// +// MessageText: +// +// The application attempted to enable DOS graphics mode. +// DOS graphics mode is not supported. +// +pub const ERROR_CTX_GRAPHICS_INVALID: i32 = 7035; + +// +// MessageId: ERROR_CTX_LOGON_DISABLED +// +// MessageText: +// +// Your interactive logon privilege has been disabled. +// Please contact your administrator. +// +pub const ERROR_CTX_LOGON_DISABLED: i32 = 7037; + +// +// MessageId: ERROR_CTX_NOT_CONSOLE +// +// MessageText: +// +// The requested operation can be performed only on the system console. +// This is most often the result of a driver or system DLL requiring direct console access. +// +pub const ERROR_CTX_NOT_CONSOLE: i32 = 7038; + +// +// MessageId: ERROR_CTX_CLIENT_QUERY_TIMEOUT +// +// MessageText: +// +// The client failed to respond to the server connect message. +// +pub const ERROR_CTX_CLIENT_QUERY_TIMEOUT: i32 = 7040; + +// +// MessageId: ERROR_CTX_CONSOLE_DISCONNECT +// +// MessageText: +// +// Disconnecting the console session is not supported. +// +pub const ERROR_CTX_CONSOLE_DISCONNECT: i32 = 7041; + +// +// MessageId: ERROR_CTX_CONSOLE_CONNECT +// +// MessageText: +// +// Reconnecting a disconnected session to the console is not supported. +// +pub const ERROR_CTX_CONSOLE_CONNECT: i32 = 7042; + +// +// MessageId: ERROR_CTX_SHADOW_DENIED +// +// MessageText: +// +// The request to control another session remotely was denied. +// +pub const ERROR_CTX_SHADOW_DENIED: i32 = 7044; + +// +// MessageId: ERROR_CTX_WINSTATION_ACCESS_DENIED +// +// MessageText: +// +// The requested session access is denied. +// +pub const ERROR_CTX_WINSTATION_ACCESS_DENIED: i32 = 7045; + +// +// MessageId: ERROR_CTX_INVALID_WD +// +// MessageText: +// +// The specified terminal connection driver is invalid. +// +pub const ERROR_CTX_INVALID_WD: i32 = 7049; + +// +// MessageId: ERROR_CTX_SHADOW_INVALID +// +// MessageText: +// +// The requested session cannot be controlled remotely. +// This may be because the session is disconnected or does not currently have a user logged on. +// +pub const ERROR_CTX_SHADOW_INVALID: i32 = 7050; + +// +// MessageId: ERROR_CTX_SHADOW_DISABLED +// +// MessageText: +// +// The requested session is not configured to allow remote control. +// +pub const ERROR_CTX_SHADOW_DISABLED: i32 = 7051; + +// +// MessageId: ERROR_CTX_CLIENT_LICENSE_IN_USE +// +// MessageText: +// +// Your request to connect to this Terminal Server has been rejected. Your Terminal Server client license number is currently being used by another user. +// Please call your system administrator to obtain a unique license number. +// +pub const ERROR_CTX_CLIENT_LICENSE_IN_USE: i32 = 7052; + +// +// MessageId: ERROR_CTX_CLIENT_LICENSE_NOT_SET +// +// MessageText: +// +// Your request to connect to this Terminal Server has been rejected. Your Terminal Server client license number has not been entered for this copy of the Terminal Server client. +// Please contact your system administrator. +// +pub const ERROR_CTX_CLIENT_LICENSE_NOT_SET: i32 = 7053; + +// +// MessageId: ERROR_CTX_LICENSE_NOT_AVAILABLE +// +// MessageText: +// +// The system has reached its licensed logon limit. +// Please try again later. +// +pub const ERROR_CTX_LICENSE_NOT_AVAILABLE: i32 = 7054; + +// +// MessageId: ERROR_CTX_LICENSE_CLIENT_INVALID +// +// MessageText: +// +// The client you are using is not licensed to use this system. Your logon request is denied. +// +pub const ERROR_CTX_LICENSE_CLIENT_INVALID: i32 = 7055; + +// +// MessageId: ERROR_CTX_LICENSE_EXPIRED +// +// MessageText: +// +// The system license has expired. Your logon request is denied. +// +pub const ERROR_CTX_LICENSE_EXPIRED: i32 = 7056; + +// +// MessageId: ERROR_CTX_SHADOW_NOT_RUNNING +// +// MessageText: +// +// Remote control could not be terminated because the specified session is not currently being remotely controlled. +// +pub const ERROR_CTX_SHADOW_NOT_RUNNING: i32 = 7057; + +// +// MessageId: ERROR_CTX_SHADOW_ENDED_BY_MODE_CHANGE +// +// MessageText: +// +// The remote control of the console was terminated because the display mode was changed. Changing the display mode in a remote control session is not supported. +// +pub const ERROR_CTX_SHADOW_ENDED_BY_MODE_CHANGE: i32 = 7058; + +// +// MessageId: ERROR_ACTIVATION_COUNT_EXCEEDED +// +// MessageText: +// +// Activation has already been reset the maximum number of times for this installation. Your activation timer will not be cleared. +// +pub const ERROR_ACTIVATION_COUNT_EXCEEDED: i32 = 7059; + +/////////////////////////////////////////////////// +// / +// Traffic Control Error Codes / +// / +// 7500 to 7999 / +// / +// defined in: tcerror.h / +/////////////////////////////////////////////////// +/////////////////////////////////////////////////// +// / +// Active Directory Error Codes / +// / +// 8000 to 8999 / +/////////////////////////////////////////////////// +// ***************** +// FACILITY_FILE_REPLICATION_SERVICE +// ***************** +// +// MessageId: FRS_ERR_INVALID_API_SEQUENCE +// +// MessageText: +// +// The file replication service API was called incorrectly. +// +pub const FRS_ERR_INVALID_API_SEQUENCE: i32 = 8001; + +// +// MessageId: FRS_ERR_STARTING_SERVICE +// +// MessageText: +// +// The file replication service cannot be started. +// +pub const FRS_ERR_STARTING_SERVICE: i32 = 8002; + +// +// MessageId: FRS_ERR_STOPPING_SERVICE +// +// MessageText: +// +// The file replication service cannot be stopped. +// +pub const FRS_ERR_STOPPING_SERVICE: i32 = 8003; + +// +// MessageId: FRS_ERR_INTERNAL_API +// +// MessageText: +// +// The file replication service API terminated the request. +// The event log may have more information. +// +pub const FRS_ERR_INTERNAL_API: i32 = 8004; + +// +// MessageId: FRS_ERR_INTERNAL +// +// MessageText: +// +// The file replication service terminated the request. +// The event log may have more information. +// +pub const FRS_ERR_INTERNAL: i32 = 8005; + +// +// MessageId: FRS_ERR_SERVICE_COMM +// +// MessageText: +// +// The file replication service cannot be contacted. +// The event log may have more information. +// +pub const FRS_ERR_SERVICE_COMM: i32 = 8006; + +// +// MessageId: FRS_ERR_INSUFFICIENT_PRIV +// +// MessageText: +// +// The file replication service cannot satisfy the request because the user has insufficient privileges. +// The event log may have more information. +// +pub const FRS_ERR_INSUFFICIENT_PRIV: i32 = 8007; + +// +// MessageId: FRS_ERR_AUTHENTICATION +// +// MessageText: +// +// The file replication service cannot satisfy the request because authenticated RPC is not available. +// The event log may have more information. +// +pub const FRS_ERR_AUTHENTICATION: i32 = 8008; + +// +// MessageId: FRS_ERR_PARENT_INSUFFICIENT_PRIV +// +// MessageText: +// +// The file replication service cannot satisfy the request because the user has insufficient privileges on the domain controller. +// The event log may have more information. +// +pub const FRS_ERR_PARENT_INSUFFICIENT_PRIV: i32 = 8009; + +// +// MessageId: FRS_ERR_PARENT_AUTHENTICATION +// +// MessageText: +// +// The file replication service cannot satisfy the request because authenticated RPC is not available on the domain controller. +// The event log may have more information. +// +pub const FRS_ERR_PARENT_AUTHENTICATION: i32 = 8010; + +// +// MessageId: FRS_ERR_CHILD_TO_PARENT_COMM +// +// MessageText: +// +// The file replication service cannot communicate with the file replication service on the domain controller. +// The event log may have more information. +// +pub const FRS_ERR_CHILD_TO_PARENT_COMM: i32 = 8011; + +// +// MessageId: FRS_ERR_PARENT_TO_CHILD_COMM +// +// MessageText: +// +// The file replication service on the domain controller cannot communicate with the file replication service on this computer. +// The event log may have more information. +// +pub const FRS_ERR_PARENT_TO_CHILD_COMM: i32 = 8012; + +// +// MessageId: FRS_ERR_SYSVOL_POPULATE +// +// MessageText: +// +// The file replication service cannot populate the system volume because of an internal error. +// The event log may have more information. +// +pub const FRS_ERR_SYSVOL_POPULATE: i32 = 8013; + +// +// MessageId: FRS_ERR_SYSVOL_POPULATE_TIMEOUT +// +// MessageText: +// +// The file replication service cannot populate the system volume because of an internal timeout. +// The event log may have more information. +// +pub const FRS_ERR_SYSVOL_POPULATE_TIMEOUT: i32 = 8014; + +// +// MessageId: FRS_ERR_SYSVOL_IS_BUSY +// +// MessageText: +// +// The file replication service cannot process the request. The system volume is busy with a previous request. +// +pub const FRS_ERR_SYSVOL_IS_BUSY: i32 = 8015; + +// +// MessageId: FRS_ERR_SYSVOL_DEMOTE +// +// MessageText: +// +// The file replication service cannot stop replicating the system volume because of an internal error. +// The event log may have more information. +// +pub const FRS_ERR_SYSVOL_DEMOTE: i32 = 8016; + +// +// MessageId: FRS_ERR_INVALID_SERVICE_PARAMETER +// +// MessageText: +// +// The file replication service detected an invalid parameter. +// +pub const FRS_ERR_INVALID_SERVICE_PARAMETER: i32 = 8017; + +// ***************** +// FACILITY DIRECTORY SERVICE +// ***************** +// +// MessageId: ERROR_DS_NOT_INSTALLED +// +// MessageText: +// +// An error occurred while installing the directory service. For more information, see the event log. +// +pub const ERROR_DS_NOT_INSTALLED: i32 = 8200; + +// +// MessageId: ERROR_DS_MEMBERSHIP_EVALUATED_LOCALLY +// +// MessageText: +// +// The directory service evaluated group memberships locally. +// +pub const ERROR_DS_MEMBERSHIP_EVALUATED_LOCALLY: i32 = 8201; + +// +// MessageId: ERROR_DS_NO_ATTRIBUTE_OR_VALUE +// +// MessageText: +// +// The specified directory service attribute or value does not exist. +// +pub const ERROR_DS_NO_ATTRIBUTE_OR_VALUE: i32 = 8202; + +// +// MessageId: ERROR_DS_INVALID_ATTRIBUTE_SYNTAX +// +// MessageText: +// +// The attribute syntax specified to the directory service is invalid. +// +pub const ERROR_DS_INVALID_ATTRIBUTE_SYNTAX: i32 = 8203; + +// +// MessageId: ERROR_DS_ATTRIBUTE_TYPE_UNDEFINED +// +// MessageText: +// +// The attribute type specified to the directory service is not defined. +// +pub const ERROR_DS_ATTRIBUTE_TYPE_UNDEFINED: i32 = 8204; + +// +// MessageId: ERROR_DS_ATTRIBUTE_OR_VALUE_EXISTS +// +// MessageText: +// +// The specified directory service attribute or value already exists. +// +pub const ERROR_DS_ATTRIBUTE_OR_VALUE_EXISTS: i32 = 8205; + +// +// MessageId: ERROR_DS_BUSY +// +// MessageText: +// +// The directory service is busy. +// +pub const ERROR_DS_BUSY: i32 = 8206; + +// +// MessageId: ERROR_DS_UNAVAILABLE +// +// MessageText: +// +// The directory service is unavailable. +// +pub const ERROR_DS_UNAVAILABLE: i32 = 8207; + +// +// MessageId: ERROR_DS_NO_RIDS_ALLOCATED +// +// MessageText: +// +// The directory service was unable to allocate a relative identifier. +// +pub const ERROR_DS_NO_RIDS_ALLOCATED: i32 = 8208; + +// +// MessageId: ERROR_DS_NO_MORE_RIDS +// +// MessageText: +// +// The directory service has exhausted the pool of relative identifiers. +// +pub const ERROR_DS_NO_MORE_RIDS: i32 = 8209; + +// +// MessageId: ERROR_DS_INCORRECT_ROLE_OWNER +// +// MessageText: +// +// The requested operation could not be performed because the directory service is not the master for that type of operation. +// +pub const ERROR_DS_INCORRECT_ROLE_OWNER: i32 = 8210; + +// +// MessageId: ERROR_DS_RIDMGR_INIT_ERROR +// +// MessageText: +// +// The directory service was unable to initialize the subsystem that allocates relative identifiers. +// +pub const ERROR_DS_RIDMGR_INIT_ERROR: i32 = 8211; + +// +// MessageId: ERROR_DS_OBJ_CLASS_VIOLATION +// +// MessageText: +// +// The requested operation did not satisfy one or more export constraints associated with the class of the object. +// +pub const ERROR_DS_OBJ_CLASS_VIOLATION: i32 = 8212; + +// +// MessageId: ERROR_DS_CANT_ON_NON_LEAF +// +// MessageText: +// +// The directory service can perform the requested operation only on a leaf object. +// +pub const ERROR_DS_CANT_ON_NON_LEAF: i32 = 8213; + +// +// MessageId: ERROR_DS_CANT_ON_RDN +// +// MessageText: +// +// The directory service cannot perform the requested operation on the RDN attribute of an object. +// +pub const ERROR_DS_CANT_ON_RDN: i32 = 8214; + +// +// MessageId: ERROR_DS_CANT_MOD_OBJ_CLASS +// +// MessageText: +// +// The directory service detected an attempt to modify the object class of an object. +// +pub const ERROR_DS_CANT_MOD_OBJ_CLASS: i32 = 8215; + +// +// MessageId: ERROR_DS_CROSS_DOM_MOVE_ERROR +// +// MessageText: +// +// The requested cross-domain move operation could not be performed. +// +pub const ERROR_DS_CROSS_DOM_MOVE_ERROR: i32 = 8216; + +// +// MessageId: ERROR_DS_GC_NOT_AVAILABLE +// +// MessageText: +// +// Unable to contact the global catalog server. +// +pub const ERROR_DS_GC_NOT_AVAILABLE: i32 = 8217; + +// +// MessageId: ERROR_SHARED_POLICY +// +// MessageText: +// +// The policy object is shared and can only be modified at the root. +// +pub const ERROR_SHARED_POLICY: i32 = 8218; + +// +// MessageId: ERROR_POLICY_OBJECT_NOT_FOUND +// +// MessageText: +// +// The policy object does not exist. +// +pub const ERROR_POLICY_OBJECT_NOT_FOUND: i32 = 8219; + +// +// MessageId: ERROR_POLICY_ONLY_IN_DS +// +// MessageText: +// +// The requested policy information is only in the directory service. +// +pub const ERROR_POLICY_ONLY_IN_DS: i32 = 8220; + +// +// MessageId: ERROR_PROMOTION_ACTIVE +// +// MessageText: +// +// A domain controller promotion is currently active. +// +pub const ERROR_PROMOTION_ACTIVE: i32 = 8221; + +// +// MessageId: ERROR_NO_PROMOTION_ACTIVE +// +// MessageText: +// +// A domain controller promotion is not currently active +// +pub const ERROR_NO_PROMOTION_ACTIVE: i32 = 8222; + +// 8223 unused +// +// MessageId: ERROR_DS_OPERATIONS_ERROR +// +// MessageText: +// +// An operations error occurred. +// +pub const ERROR_DS_OPERATIONS_ERROR: i32 = 8224; + +// +// MessageId: ERROR_DS_PROTOCOL_ERROR +// +// MessageText: +// +// A protocol error occurred. +// +pub const ERROR_DS_PROTOCOL_ERROR: i32 = 8225; + +// +// MessageId: ERROR_DS_TIMELIMIT_EXCEEDED +// +// MessageText: +// +// The time limit for this request was exceeded. +// +pub const ERROR_DS_TIMELIMIT_EXCEEDED: i32 = 8226; + +// +// MessageId: ERROR_DS_SIZELIMIT_EXCEEDED +// +// MessageText: +// +// The size limit for this request was exceeded. +// +pub const ERROR_DS_SIZELIMIT_EXCEEDED: i32 = 8227; + +// +// MessageId: ERROR_DS_ADMIN_LIMIT_EXCEEDED +// +// MessageText: +// +// The administrative limit for this request was exceeded. +// +pub const ERROR_DS_ADMIN_LIMIT_EXCEEDED: i32 = 8228; + +// +// MessageId: ERROR_DS_COMPARE_FALSE +// +// MessageText: +// +// The compare response was false. +// +pub const ERROR_DS_COMPARE_FALSE: i32 = 8229; + +// +// MessageId: ERROR_DS_COMPARE_TRUE +// +// MessageText: +// +// The compare response was true. +// +pub const ERROR_DS_COMPARE_TRUE: i32 = 8230; + +// +// MessageId: ERROR_DS_AUTH_METHOD_NOT_SUPPORTED +// +// MessageText: +// +// The requested authentication method is not supported by the server. +// +pub const ERROR_DS_AUTH_METHOD_NOT_SUPPORTED: i32 = 8231; + +// +// MessageId: ERROR_DS_STRONG_AUTH_REQUIRED +// +// MessageText: +// +// A more secure authentication method is required for this server. +// +pub const ERROR_DS_STRONG_AUTH_REQUIRED: i32 = 8232; + +// +// MessageId: ERROR_DS_INAPPROPRIATE_AUTH +// +// MessageText: +// +// Inappropriate authentication. +// +pub const ERROR_DS_INAPPROPRIATE_AUTH: i32 = 8233; + +// +// MessageId: ERROR_DS_AUTH_UNKNOWN +// +// MessageText: +// +// The authentication mechanism is unknown. +// +pub const ERROR_DS_AUTH_UNKNOWN: i32 = 8234; + +// +// MessageId: ERROR_DS_REFERRAL +// +// MessageText: +// +// A referral was returned from the server. +// +pub const ERROR_DS_REFERRAL: i32 = 8235; + +// +// MessageId: ERROR_DS_UNAVAILABLE_CRIT_EXTENSION +// +// MessageText: +// +// The server does not support the requested critical extension. +// +pub const ERROR_DS_UNAVAILABLE_CRIT_EXTENSION: i32 = 8236; + +// +// MessageId: ERROR_DS_CONFIDENTIALITY_REQUIRED +// +// MessageText: +// +// This request requires a secure connection. +// +pub const ERROR_DS_CONFIDENTIALITY_REQUIRED: i32 = 8237; + +// +// MessageId: ERROR_DS_INAPPROPRIATE_MATCHING +// +// MessageText: +// +// Inappropriate matching. +// +pub const ERROR_DS_INAPPROPRIATE_MATCHING: i32 = 8238; + +// +// MessageId: ERROR_DS_NO_SUCH_OBJECT +// +// MessageText: +// +// There is no such object on the server. +// +pub const ERROR_DS_NO_SUCH_OBJECT: i32 = 8240; + +// +// MessageId: ERROR_DS_ALIAS_PROBLEM +// +// MessageText: +// +// There is an alias problem. +// +pub const ERROR_DS_ALIAS_PROBLEM: i32 = 8241; + +// +// MessageId: ERROR_DS_INVALID_DN_SYNTAX +// +// MessageText: +// +// An invalid dn syntax has been specified. +// +pub const ERROR_DS_INVALID_DN_SYNTAX: i32 = 8242; + +// +// MessageId: ERROR_DS_IS_LEAF +// +// MessageText: +// +// The object is a leaf object. +// +pub const ERROR_DS_IS_LEAF: i32 = 8243; + +// +// MessageId: ERROR_DS_ALIAS_DEREF_PROBLEM +// +// MessageText: +// +// There is an alias dereferencing problem. +// +pub const ERROR_DS_ALIAS_DEREF_PROBLEM: i32 = 8244; + +// +// MessageId: ERROR_DS_UNWILLING_TO_PERFORM +// +// MessageText: +// +// The server is unwilling to process the request. +// +pub const ERROR_DS_UNWILLING_TO_PERFORM: i32 = 8245; + +// +// MessageId: ERROR_DS_LOOP_DETECT +// +// MessageText: +// +// A loop has been detected. +// +pub const ERROR_DS_LOOP_DETECT: i32 = 8246; + +// +// MessageId: ERROR_DS_NAMING_VIOLATION +// +// MessageText: +// +// There is a naming violation. +// +pub const ERROR_DS_NAMING_VIOLATION: i32 = 8247; + +// +// MessageId: ERROR_DS_OBJECT_RESULTS_TOO_LARGE +// +// MessageText: +// +// The result set is too large. +// +pub const ERROR_DS_OBJECT_RESULTS_TOO_LARGE: i32 = 8248; + +// +// MessageId: ERROR_DS_AFFECTS_MULTIPLE_DSAS +// +// MessageText: +// +// The operation affects multiple DSAs +// +pub const ERROR_DS_AFFECTS_MULTIPLE_DSAS: i32 = 8249; + +// +// MessageId: ERROR_DS_SERVER_DOWN +// +// MessageText: +// +// The server is not operational. +// +pub const ERROR_DS_SERVER_DOWN: i32 = 8250; + +// +// MessageId: ERROR_DS_LOCAL_ERROR +// +// MessageText: +// +// A local error has occurred. +// +pub const ERROR_DS_LOCAL_ERROR: i32 = 8251; + +// +// MessageId: ERROR_DS_ENCODING_ERROR +// +// MessageText: +// +// An encoding error has occurred. +// +pub const ERROR_DS_ENCODING_ERROR: i32 = 8252; + +// +// MessageId: ERROR_DS_DECODING_ERROR +// +// MessageText: +// +// A decoding error has occurred. +// +pub const ERROR_DS_DECODING_ERROR: i32 = 8253; + +// +// MessageId: ERROR_DS_FILTER_UNKNOWN +// +// MessageText: +// +// The search filter cannot be recognized. +// +pub const ERROR_DS_FILTER_UNKNOWN: i32 = 8254; + +// +// MessageId: ERROR_DS_PARAM_ERROR +// +// MessageText: +// +// One or more parameters are illegal. +// +pub const ERROR_DS_PARAM_ERROR: i32 = 8255; + +// +// MessageId: ERROR_DS_NOT_SUPPORTED +// +// MessageText: +// +// The specified method is not supported. +// +pub const ERROR_DS_NOT_SUPPORTED: i32 = 8256; + +// +// MessageId: ERROR_DS_NO_RESULTS_RETURNED +// +// MessageText: +// +// No results were returned. +// +pub const ERROR_DS_NO_RESULTS_RETURNED: i32 = 8257; + +// +// MessageId: ERROR_DS_CONTROL_NOT_FOUND +// +// MessageText: +// +// The specified control is not supported by the server. +// +pub const ERROR_DS_CONTROL_NOT_FOUND: i32 = 8258; + +// +// MessageId: ERROR_DS_CLIENT_LOOP +// +// MessageText: +// +// A referral loop was detected by the client. +// +pub const ERROR_DS_CLIENT_LOOP: i32 = 8259; + +// +// MessageId: ERROR_DS_REFERRAL_LIMIT_EXCEEDED +// +// MessageText: +// +// The preset referral limit was exceeded. +// +pub const ERROR_DS_REFERRAL_LIMIT_EXCEEDED: i32 = 8260; + +// +// MessageId: ERROR_DS_SORT_CONTROL_MISSING +// +// MessageText: +// +// The search requires a SORT control. +// +pub const ERROR_DS_SORT_CONTROL_MISSING: i32 = 8261; + +// +// MessageId: ERROR_DS_OFFSET_RANGE_ERROR +// +// MessageText: +// +// The search results exceed the offset range specified. +// +pub const ERROR_DS_OFFSET_RANGE_ERROR: i32 = 8262; + +// +// MessageId: ERROR_DS_ROOT_MUST_BE_NC +// +// MessageText: +// +// The root object must be the head of a naming context. The root object cannot have an instantiated parent. +// +pub const ERROR_DS_ROOT_MUST_BE_NC: i32 = 8301; + +// +// MessageId: ERROR_DS_ADD_REPLICA_INHIBITED +// +// MessageText: +// +// The add replica operation cannot be performed. The naming context must be writeable in order to create the replica. +// +pub const ERROR_DS_ADD_REPLICA_INHIBITED: i32 = 8302; + +// +// MessageId: ERROR_DS_ATT_NOT_DEF_IN_SCHEMA +// +// MessageText: +// +// A reference to an attribute that is not defined in the schema occurred. +// +pub const ERROR_DS_ATT_NOT_DEF_IN_SCHEMA: i32 = 8303; + +// +// MessageId: ERROR_DS_MAX_OBJ_SIZE_EXCEEDED +// +// MessageText: +// +// The maximum size of an object has been exceeded. +// +pub const ERROR_DS_MAX_OBJ_SIZE_EXCEEDED: i32 = 8304; + +// +// MessageId: ERROR_DS_OBJ_STRING_NAME_EXISTS +// +// MessageText: +// +// An attempt was made to add an object to the directory with a name that is already in use. +// +pub const ERROR_DS_OBJ_STRING_NAME_EXISTS: i32 = 8305; + +// +// MessageId: ERROR_DS_NO_RDN_DEFINED_IN_SCHEMA +// +// MessageText: +// +// An attempt was made to add an object of a class that does not have an RDN defined in the schema. +// +pub const ERROR_DS_NO_RDN_DEFINED_IN_SCHEMA: i32 = 8306; + +// +// MessageId: ERROR_DS_RDN_DOESNT_MATCH_SCHEMA +// +// MessageText: +// +// An attempt was made to add an object using an RDN that is not the RDN defined in the schema. +// +pub const ERROR_DS_RDN_DOESNT_MATCH_SCHEMA: i32 = 8307; + +// +// MessageId: ERROR_DS_NO_REQUESTED_ATTS_FOUND +// +// MessageText: +// +// None of the requested attributes were found on the objects. +// +pub const ERROR_DS_NO_REQUESTED_ATTS_FOUND: i32 = 8308; + +// +// MessageId: ERROR_DS_USER_BUFFER_TO_SMALL +// +// MessageText: +// +// The user buffer is too small. +// +pub const ERROR_DS_USER_BUFFER_TO_SMALL: i32 = 8309; + +// +// MessageId: ERROR_DS_ATT_IS_NOT_ON_OBJ +// +// MessageText: +// +// The attribute specified in the operation is not present on the object. +// +pub const ERROR_DS_ATT_IS_NOT_ON_OBJ: i32 = 8310; + +// +// MessageId: ERROR_DS_ILLEGAL_MOD_OPERATION +// +// MessageText: +// +// Illegal modify operation. Some aspect of the modification is not permitted. +// +pub const ERROR_DS_ILLEGAL_MOD_OPERATION: i32 = 8311; + +// +// MessageId: ERROR_DS_OBJ_TOO_LARGE +// +// MessageText: +// +// The specified object is too large. +// +pub const ERROR_DS_OBJ_TOO_LARGE: i32 = 8312; + +// +// MessageId: ERROR_DS_BAD_INSTANCE_TYPE +// +// MessageText: +// +// The specified instance type is not valid. +// +pub const ERROR_DS_BAD_INSTANCE_TYPE: i32 = 8313; + +// +// MessageId: ERROR_DS_MASTERDSA_REQUIRED +// +// MessageText: +// +// The operation must be performed at a master DSA. +// +pub const ERROR_DS_MASTERDSA_REQUIRED: i32 = 8314; + +// +// MessageId: ERROR_DS_OBJECT_CLASS_REQUIRED +// +// MessageText: +// +// The object class attribute must be specified. +// +pub const ERROR_DS_OBJECT_CLASS_REQUIRED: i32 = 8315; + +// +// MessageId: ERROR_DS_MISSING_REQUIRED_ATT +// +// MessageText: +// +// A required attribute is missing. +// +pub const ERROR_DS_MISSING_REQUIRED_ATT: i32 = 8316; + +// +// MessageId: ERROR_DS_ATT_NOT_DEF_FOR_CLASS +// +// MessageText: +// +// An attempt was made to modify an object to include an attribute that is not legal for its class. +// +pub const ERROR_DS_ATT_NOT_DEF_FOR_CLASS: i32 = 8317; + +// +// MessageId: ERROR_DS_ATT_ALREADY_EXISTS +// +// MessageText: +// +// The specified attribute is already present on the object. +// +pub const ERROR_DS_ATT_ALREADY_EXISTS: i32 = 8318; + +// 8319 unused +// +// MessageId: ERROR_DS_CANT_ADD_ATT_VALUES +// +// MessageText: +// +// The specified attribute is not present, or has no values. +// +pub const ERROR_DS_CANT_ADD_ATT_VALUES: i32 = 8320; + +// +// MessageId: ERROR_DS_ATT_VAL_ALREADY_EXISTS +// +// MessageText: +// +// The specified value already exists. +// +pub const ERROR_DS_ATT_VAL_ALREADY_EXISTS: i32 = 8323; + +// +// MessageId: ERROR_DS_CANT_REM_MISSING_ATT +// +// MessageText: +// +// The attribute cannot be removed because it is not present on the object. +// +pub const ERROR_DS_CANT_REM_MISSING_ATT: i32 = 8324; + +// +// MessageId: ERROR_DS_CANT_REM_MISSING_ATT_VAL +// +// MessageText: +// +// The attribute value cannot be removed because it is not present on the object. +// +pub const ERROR_DS_CANT_REM_MISSING_ATT_VAL: i32 = 8325; + +// +// MessageId: ERROR_DS_ROOT_CANT_BE_SUBREF +// +// MessageText: +// +// The specified root object cannot be a subref. +// +pub const ERROR_DS_ROOT_CANT_BE_SUBREF: i32 = 8326; + +// +// MessageId: ERROR_DS_NO_CHAINING +// +// MessageText: +// +// Chaining is not permitted. +// +pub const ERROR_DS_NO_CHAINING: i32 = 8327; + +// +// MessageId: ERROR_DS_NO_CHAINED_EVAL +// +// MessageText: +// +// Chained evaluation is not permitted. +// +pub const ERROR_DS_NO_CHAINED_EVAL: i32 = 8328; + +// +// MessageId: ERROR_DS_NO_PARENT_OBJECT +// +// MessageText: +// +// The operation could not be performed because the object's parent is either uninstantiated or deleted. +// +pub const ERROR_DS_NO_PARENT_OBJECT: i32 = 8329; + +// +// MessageId: ERROR_DS_PARENT_IS_AN_ALIAS +// +// MessageText: +// +// Having a parent that is an alias is not permitted. Aliases are leaf objects. +// +pub const ERROR_DS_PARENT_IS_AN_ALIAS: i32 = 8330; + +// +// MessageId: ERROR_DS_CANT_MIX_MASTER_AND_REPS +// +// MessageText: +// +// The object and parent must be of the same type, either both masters or both replicas. +// +pub const ERROR_DS_CANT_MIX_MASTER_AND_REPS: i32 = 8331; + +// +// MessageId: ERROR_DS_CHILDREN_EXIST +// +// MessageText: +// +// The operation cannot be performed because child objects exist. This operation can only be performed on a leaf object. +// +pub const ERROR_DS_CHILDREN_EXIST: i32 = 8332; + +// +// MessageId: ERROR_DS_OBJ_NOT_FOUND +// +// MessageText: +// +// Directory object not found. +// +pub const ERROR_DS_OBJ_NOT_FOUND: i32 = 8333; + +// +// MessageId: ERROR_DS_ALIASED_OBJ_MISSING +// +// MessageText: +// +// The aliased object is missing. +// +pub const ERROR_DS_ALIASED_OBJ_MISSING: i32 = 8334; + +// +// MessageId: ERROR_DS_BAD_NAME_SYNTAX +// +// MessageText: +// +// The object name has bad syntax. +// +pub const ERROR_DS_BAD_NAME_SYNTAX: i32 = 8335; + +// +// MessageId: ERROR_DS_ALIAS_POINTS_TO_ALIAS +// +// MessageText: +// +// It is not permitted for an alias to refer to another alias. +// +pub const ERROR_DS_ALIAS_POINTS_TO_ALIAS: i32 = 8336; + +// +// MessageId: ERROR_DS_CANT_DEREF_ALIAS +// +// MessageText: +// +// The alias cannot be dereferenced. +// +pub const ERROR_DS_CANT_DEREF_ALIAS: i32 = 8337; + +// +// MessageId: ERROR_DS_OUT_OF_SCOPE +// +// MessageText: +// +// The operation is out of scope. +// +pub const ERROR_DS_OUT_OF_SCOPE: i32 = 8338; + +// +// MessageId: ERROR_DS_OBJECT_BEING_REMOVED +// +// MessageText: +// +// The operation cannot continue because the object is in the process of being removed. +// +pub const ERROR_DS_OBJECT_BEING_REMOVED: i32 = 8339; + +// +// MessageId: ERROR_DS_CANT_DELETE_DSA_OBJ +// +// MessageText: +// +// The DSA object cannot be deleted. +// +pub const ERROR_DS_CANT_DELETE_DSA_OBJ: i32 = 8340; + +// +// MessageId: ERROR_DS_GENERIC_ERROR +// +// MessageText: +// +// A directory service error has occurred. +// +pub const ERROR_DS_GENERIC_ERROR: i32 = 8341; + +// +// MessageId: ERROR_DS_DSA_MUST_BE_INT_MASTER +// +// MessageText: +// +// The operation can only be performed on an internal master DSA object. +// +pub const ERROR_DS_DSA_MUST_BE_INT_MASTER: i32 = 8342; + +// +// MessageId: ERROR_DS_CLASS_NOT_DSA +// +// MessageText: +// +// The object must be of class DSA. +// +pub const ERROR_DS_CLASS_NOT_DSA: i32 = 8343; + +// +// MessageId: ERROR_DS_INSUFF_ACCESS_RIGHTS +// +// MessageText: +// +// Insufficient access rights to perform the operation. +// +pub const ERROR_DS_INSUFF_ACCESS_RIGHTS: i32 = 8344; + +// +// MessageId: ERROR_DS_ILLEGAL_SUPERIOR +// +// MessageText: +// +// The object cannot be added because the parent is not on the list of possible superiors. +// +pub const ERROR_DS_ILLEGAL_SUPERIOR: i32 = 8345; + +// +// MessageId: ERROR_DS_ATTRIBUTE_OWNED_BY_SAM +// +// MessageText: +// +// Access to the attribute is not permitted because the attribute is owned by the Security Accounts Manager (SAM). +// +pub const ERROR_DS_ATTRIBUTE_OWNED_BY_SAM: i32 = 8346; + +// +// MessageId: ERROR_DS_NAME_TOO_MANY_PARTS +// +// MessageText: +// +// The name has too many parts. +// +pub const ERROR_DS_NAME_TOO_MANY_PARTS: i32 = 8347; + +// +// MessageId: ERROR_DS_NAME_TOO_LONG +// +// MessageText: +// +// The name is too long. +// +pub const ERROR_DS_NAME_TOO_LONG: i32 = 8348; + +// +// MessageId: ERROR_DS_NAME_VALUE_TOO_LONG +// +// MessageText: +// +// The name value is too long. +// +pub const ERROR_DS_NAME_VALUE_TOO_LONG: i32 = 8349; + +// +// MessageId: ERROR_DS_NAME_UNPARSEABLE +// +// MessageText: +// +// The directory service encountered an error parsing a name. +// +pub const ERROR_DS_NAME_UNPARSEABLE: i32 = 8350; + +// +// MessageId: ERROR_DS_NAME_TYPE_UNKNOWN +// +// MessageText: +// +// The directory service cannot get the attribute type for a name. +// +pub const ERROR_DS_NAME_TYPE_UNKNOWN: i32 = 8351; + +// +// MessageId: ERROR_DS_NOT_AN_OBJECT +// +// MessageText: +// +// The name does not identify an object; the name identifies a phantom. +// +pub const ERROR_DS_NOT_AN_OBJECT: i32 = 8352; + +// +// MessageId: ERROR_DS_SEC_DESC_TOO_SHORT +// +// MessageText: +// +// The security descriptor is too short. +// +pub const ERROR_DS_SEC_DESC_TOO_SHORT: i32 = 8353; + +// +// MessageId: ERROR_DS_SEC_DESC_INVALID +// +// MessageText: +// +// The security descriptor is invalid. +// +pub const ERROR_DS_SEC_DESC_INVALID: i32 = 8354; + +// +// MessageId: ERROR_DS_NO_DELETED_NAME +// +// MessageText: +// +// Failed to create name for deleted object. +// +pub const ERROR_DS_NO_DELETED_NAME: i32 = 8355; + +// +// MessageId: ERROR_DS_SUBREF_MUST_HAVE_PARENT +// +// MessageText: +// +// The parent of a new subref must exist. +// +pub const ERROR_DS_SUBREF_MUST_HAVE_PARENT: i32 = 8356; + +// +// MessageId: ERROR_DS_NCNAME_MUST_BE_NC +// +// MessageText: +// +// The object must be a naming context. +// +pub const ERROR_DS_NCNAME_MUST_BE_NC: i32 = 8357; + +// +// MessageId: ERROR_DS_CANT_ADD_SYSTEM_ONLY +// +// MessageText: +// +// It is not permitted to add an attribute which is owned by the system. +// +pub const ERROR_DS_CANT_ADD_SYSTEM_ONLY: i32 = 8358; + +// +// MessageId: ERROR_DS_CLASS_MUST_BE_CONCRETE +// +// MessageText: +// +// The class of the object must be structural; you cannot instantiate an abstract class. +// +pub const ERROR_DS_CLASS_MUST_BE_CONCRETE: i32 = 8359; + +// +// MessageId: ERROR_DS_INVALID_DMD +// +// MessageText: +// +// The schema object could not be found. +// +pub const ERROR_DS_INVALID_DMD: i32 = 8360; + +// +// MessageId: ERROR_DS_OBJ_GUID_EXISTS +// +// MessageText: +// +// A local object with this GUID (dead or alive) already exists. +// +pub const ERROR_DS_OBJ_GUID_EXISTS: i32 = 8361; + +// +// MessageId: ERROR_DS_NOT_ON_BACKLINK +// +// MessageText: +// +// The operation cannot be performed on a back link. +// +pub const ERROR_DS_NOT_ON_BACKLINK: i32 = 8362; + +// +// MessageId: ERROR_DS_NO_CROSSREF_FOR_NC +// +// MessageText: +// +// The cross reference for the specified naming context could not be found. +// +pub const ERROR_DS_NO_CROSSREF_FOR_NC: i32 = 8363; + +// +// MessageId: ERROR_DS_SHUTTING_DOWN +// +// MessageText: +// +// The operation could not be performed because the directory service is shutting down. +// +pub const ERROR_DS_SHUTTING_DOWN: i32 = 8364; + +// +// MessageId: ERROR_DS_UNKNOWN_OPERATION +// +// MessageText: +// +// The directory service request is invalid. +// +pub const ERROR_DS_UNKNOWN_OPERATION: i32 = 8365; + +// +// MessageId: ERROR_DS_INVALID_ROLE_OWNER +// +// MessageText: +// +// The role owner attribute could not be read. +// +pub const ERROR_DS_INVALID_ROLE_OWNER: i32 = 8366; + +// +// MessageId: ERROR_DS_COULDNT_CONTACT_FSMO +// +// MessageText: +// +// The requested FSMO operation failed. The current FSMO holder could not be contacted. +// +pub const ERROR_DS_COULDNT_CONTACT_FSMO: i32 = 8367; + +// +// MessageId: ERROR_DS_CROSS_NC_DN_RENAME +// +// MessageText: +// +// Modification of a DN across a naming context is not permitted. +// +pub const ERROR_DS_CROSS_NC_DN_RENAME: i32 = 8368; + +// +// MessageId: ERROR_DS_CANT_MOD_SYSTEM_ONLY +// +// MessageText: +// +// The attribute cannot be modified because it is owned by the system. +// +pub const ERROR_DS_CANT_MOD_SYSTEM_ONLY: i32 = 8369; + +// +// MessageId: ERROR_DS_REPLICATOR_ONLY +// +// MessageText: +// +// Only the replicator can perform this function. +// +pub const ERROR_DS_REPLICATOR_ONLY: i32 = 8370; + +// +// MessageId: ERROR_DS_OBJ_CLASS_NOT_DEFINED +// +// MessageText: +// +// The specified class is not defined. +// +pub const ERROR_DS_OBJ_CLASS_NOT_DEFINED: i32 = 8371; + +// +// MessageId: ERROR_DS_OBJ_CLASS_NOT_SUBCLASS +// +// MessageText: +// +// The specified class is not a subclass. +// +pub const ERROR_DS_OBJ_CLASS_NOT_SUBCLASS: i32 = 8372; + +// +// MessageId: ERROR_DS_NAME_REFERENCE_INVALID +// +// MessageText: +// +// The name reference is invalid. +// +pub const ERROR_DS_NAME_REFERENCE_INVALID: i32 = 8373; + +// +// MessageId: ERROR_DS_CROSS_REF_EXISTS +// +// MessageText: +// +// A cross reference already exists. +// +pub const ERROR_DS_CROSS_REF_EXISTS: i32 = 8374; + +// +// MessageId: ERROR_DS_CANT_DEL_MASTER_CROSSREF +// +// MessageText: +// +// It is not permitted to delete a master cross reference. +// +pub const ERROR_DS_CANT_DEL_MASTER_CROSSREF: i32 = 8375; + +// +// MessageId: ERROR_DS_SUBTREE_NOTIFY_NOT_NC_HEAD +// +// MessageText: +// +// Subtree notifications are only supported on NC heads. +// +pub const ERROR_DS_SUBTREE_NOTIFY_NOT_NC_HEAD: i32 = 8376; + +// +// MessageId: ERROR_DS_NOTIFY_FILTER_TOO_COMPLEX +// +// MessageText: +// +// Notification filter is too complex. +// +pub const ERROR_DS_NOTIFY_FILTER_TOO_COMPLEX: i32 = 8377; + +// +// MessageId: ERROR_DS_DUP_RDN +// +// MessageText: +// +// Schema update failed: duplicate RDN. +// +pub const ERROR_DS_DUP_RDN: i32 = 8378; + +// +// MessageId: ERROR_DS_DUP_OID +// +// MessageText: +// +// Schema update failed: duplicate OID. +// +pub const ERROR_DS_DUP_OID: i32 = 8379; + +// +// MessageId: ERROR_DS_DUP_MAPI_ID +// +// MessageText: +// +// Schema update failed: duplicate MAPI identifier. +// +pub const ERROR_DS_DUP_MAPI_ID: i32 = 8380; + +// +// MessageId: ERROR_DS_DUP_SCHEMA_ID_GUID +// +// MessageText: +// +// Schema update failed: duplicate schema-id GUID. +// +pub const ERROR_DS_DUP_SCHEMA_ID_GUID: i32 = 8381; + +// +// MessageId: ERROR_DS_DUP_LDAP_DISPLAY_NAME +// +// MessageText: +// +// Schema update failed: duplicate LDAP display name. +// +pub const ERROR_DS_DUP_LDAP_DISPLAY_NAME: i32 = 8382; + +// +// MessageId: ERROR_DS_SEMANTIC_ATT_TEST +// +// MessageText: +// +// Schema update failed: range-lower less than range upper. +// +pub const ERROR_DS_SEMANTIC_ATT_TEST: i32 = 8383; + +// +// MessageId: ERROR_DS_SYNTAX_MISMATCH +// +// MessageText: +// +// Schema update failed: syntax mismatch. +// +pub const ERROR_DS_SYNTAX_MISMATCH: i32 = 8384; + +// +// MessageId: ERROR_DS_EXISTS_IN_MUST_HAVE +// +// MessageText: +// +// Schema deletion failed: attribute is used in must-contain. +// +pub const ERROR_DS_EXISTS_IN_MUST_HAVE: i32 = 8385; + +// +// MessageId: ERROR_DS_EXISTS_IN_MAY_HAVE +// +// MessageText: +// +// Schema deletion failed: attribute is used in may-contain. +// +pub const ERROR_DS_EXISTS_IN_MAY_HAVE: i32 = 8386; + +// +// MessageId: ERROR_DS_NONEXISTENT_MAY_HAVE +// +// MessageText: +// +// Schema update failed: attribute in may-contain does not exist. +// +pub const ERROR_DS_NONEXISTENT_MAY_HAVE: i32 = 8387; + +// +// MessageId: ERROR_DS_NONEXISTENT_MUST_HAVE +// +// MessageText: +// +// Schema update failed: attribute in must-contain does not exist. +// +pub const ERROR_DS_NONEXISTENT_MUST_HAVE: i32 = 8388; + +// +// MessageId: ERROR_DS_AUX_CLS_TEST_FAIL +// +// MessageText: +// +// Schema update failed: class in aux-class list does not exist or is not an auxiliary class. +// +pub const ERROR_DS_AUX_CLS_TEST_FAIL: i32 = 8389; + +// +// MessageId: ERROR_DS_NONEXISTENT_POSS_SUP +// +// MessageText: +// +// Schema update failed: class in poss-superiors does not exist. +// +pub const ERROR_DS_NONEXISTENT_POSS_SUP: i32 = 8390; + +// +// MessageId: ERROR_DS_SUB_CLS_TEST_FAIL +// +// MessageText: +// +// Schema update failed: class in subclassof list does not exist or does not satisfy hierarchy rules. +// +pub const ERROR_DS_SUB_CLS_TEST_FAIL: i32 = 8391; + +// +// MessageId: ERROR_DS_BAD_RDN_ATT_ID_SYNTAX +// +// MessageText: +// +// Schema update failed: Rdn-Att-Id has wrong syntax. +// +pub const ERROR_DS_BAD_RDN_ATT_ID_SYNTAX: i32 = 8392; + +// +// MessageId: ERROR_DS_EXISTS_IN_AUX_CLS +// +// MessageText: +// +// Schema deletion failed: class is used as auxiliary class. +// +pub const ERROR_DS_EXISTS_IN_AUX_CLS: i32 = 8393; + +// +// MessageId: ERROR_DS_EXISTS_IN_SUB_CLS +// +// MessageText: +// +// Schema deletion failed: class is used as sub class. +// +pub const ERROR_DS_EXISTS_IN_SUB_CLS: i32 = 8394; + +// +// MessageId: ERROR_DS_EXISTS_IN_POSS_SUP +// +// MessageText: +// +// Schema deletion failed: class is used as poss-superior. +// +pub const ERROR_DS_EXISTS_IN_POSS_SUP: i32 = 8395; + +// +// MessageId: ERROR_DS_RECALCSCHEMA_FAILED +// +// MessageText: +// +// Schema update failed in recalculating validation cache. +// +pub const ERROR_DS_RECALCSCHEMA_FAILED: i32 = 8396; + +// +// MessageId: ERROR_DS_TREE_DELETE_NOT_FINISHED +// +// MessageText: +// +// The tree deletion is not finished. The request must be made again to continue deleting the tree. +// +pub const ERROR_DS_TREE_DELETE_NOT_FINISHED: i32 = 8397; + +// +// MessageId: ERROR_DS_CANT_DELETE +// +// MessageText: +// +// The requested delete operation could not be performed. +// +pub const ERROR_DS_CANT_DELETE: i32 = 8398; + +// +// MessageId: ERROR_DS_ATT_SCHEMA_REQ_ID +// +// MessageText: +// +// Cannot read the governs class identifier for the schema record. +// +pub const ERROR_DS_ATT_SCHEMA_REQ_ID: i32 = 8399; + +// +// MessageId: ERROR_DS_BAD_ATT_SCHEMA_SYNTAX +// +// MessageText: +// +// The attribute schema has bad syntax. +// +pub const ERROR_DS_BAD_ATT_SCHEMA_SYNTAX: i32 = 8400; + +// +// MessageId: ERROR_DS_CANT_CACHE_ATT +// +// MessageText: +// +// The attribute could not be cached. +// +pub const ERROR_DS_CANT_CACHE_ATT: i32 = 8401; + +// +// MessageId: ERROR_DS_CANT_CACHE_CLASS +// +// MessageText: +// +// The class could not be cached. +// +pub const ERROR_DS_CANT_CACHE_CLASS: i32 = 8402; + +// +// MessageId: ERROR_DS_CANT_REMOVE_ATT_CACHE +// +// MessageText: +// +// The attribute could not be removed from the cache. +// +pub const ERROR_DS_CANT_REMOVE_ATT_CACHE: i32 = 8403; + +// +// MessageId: ERROR_DS_CANT_REMOVE_CLASS_CACHE +// +// MessageText: +// +// The class could not be removed from the cache. +// +pub const ERROR_DS_CANT_REMOVE_CLASS_CACHE: i32 = 8404; + +// +// MessageId: ERROR_DS_CANT_RETRIEVE_DN +// +// MessageText: +// +// The distinguished name attribute could not be read. +// +pub const ERROR_DS_CANT_RETRIEVE_DN: i32 = 8405; + +// +// MessageId: ERROR_DS_MISSING_SUPREF +// +// MessageText: +// +// No superior reference has been configured for the directory service. The directory service is therefore unable to issue referrals to objects outside this forest. +// +pub const ERROR_DS_MISSING_SUPREF: i32 = 8406; + +// +// MessageId: ERROR_DS_CANT_RETRIEVE_INSTANCE +// +// MessageText: +// +// The instance type attribute could not be retrieved. +// +pub const ERROR_DS_CANT_RETRIEVE_INSTANCE: i32 = 8407; + +// +// MessageId: ERROR_DS_CODE_INCONSISTENCY +// +// MessageText: +// +// An internal error has occurred. +// +pub const ERROR_DS_CODE_INCONSISTENCY: i32 = 8408; + +// +// MessageId: ERROR_DS_DATABASE_ERROR +// +// MessageText: +// +// A database error has occurred. +// +pub const ERROR_DS_DATABASE_ERROR: i32 = 8409; + +// +// MessageId: ERROR_DS_GOVERNSID_MISSING +// +// MessageText: +// +// The attribute GOVERNSID is missing. +// +pub const ERROR_DS_GOVERNSID_MISSING: i32 = 8410; + +// +// MessageId: ERROR_DS_MISSING_EXPECTED_ATT +// +// MessageText: +// +// An expected attribute is missing. +// +pub const ERROR_DS_MISSING_EXPECTED_ATT: i32 = 8411; + +// +// MessageId: ERROR_DS_NCNAME_MISSING_CR_REF +// +// MessageText: +// +// The specified naming context is missing a cross reference. +// +pub const ERROR_DS_NCNAME_MISSING_CR_REF: i32 = 8412; + +// +// MessageId: ERROR_DS_SECURITY_CHECKING_ERROR +// +// MessageText: +// +// A security checking error has occurred. +// +pub const ERROR_DS_SECURITY_CHECKING_ERROR: i32 = 8413; + +// +// MessageId: ERROR_DS_SCHEMA_NOT_LOADED +// +// MessageText: +// +// The schema is not loaded. +// +pub const ERROR_DS_SCHEMA_NOT_LOADED: i32 = 8414; + +// +// MessageId: ERROR_DS_SCHEMA_ALLOC_FAILED +// +// MessageText: +// +// Schema allocation failed. Please check if the machine is running low on memory. +// +pub const ERROR_DS_SCHEMA_ALLOC_FAILED: i32 = 8415; + +// +// MessageId: ERROR_DS_ATT_SCHEMA_REQ_SYNTAX +// +// MessageText: +// +// Failed to obtain the required syntax for the attribute schema. +// +pub const ERROR_DS_ATT_SCHEMA_REQ_SYNTAX: i32 = 8416; + +// +// MessageId: ERROR_DS_GCVERIFY_ERROR +// +// MessageText: +// +// The global catalog verification failed. The global catalog is not available or does not support the operation. Some part of the directory is currently not available. +// +pub const ERROR_DS_GCVERIFY_ERROR: i32 = 8417; + +// +// MessageId: ERROR_DS_DRA_SCHEMA_MISMATCH +// +// MessageText: +// +// The replication operation failed because of a schema mismatch between the servers involved. +// +pub const ERROR_DS_DRA_SCHEMA_MISMATCH: i32 = 8418; + +// +// MessageId: ERROR_DS_CANT_FIND_DSA_OBJ +// +// MessageText: +// +// The DSA object could not be found. +// +pub const ERROR_DS_CANT_FIND_DSA_OBJ: i32 = 8419; + +// +// MessageId: ERROR_DS_CANT_FIND_EXPECTED_NC +// +// MessageText: +// +// The naming context could not be found. +// +pub const ERROR_DS_CANT_FIND_EXPECTED_NC: i32 = 8420; + +// +// MessageId: ERROR_DS_CANT_FIND_NC_IN_CACHE +// +// MessageText: +// +// The naming context could not be found in the cache. +// +pub const ERROR_DS_CANT_FIND_NC_IN_CACHE: i32 = 8421; + +// +// MessageId: ERROR_DS_CANT_RETRIEVE_CHILD +// +// MessageText: +// +// The child object could not be retrieved. +// +pub const ERROR_DS_CANT_RETRIEVE_CHILD: i32 = 8422; + +// +// MessageId: ERROR_DS_SECURITY_ILLEGAL_MODIFY +// +// MessageText: +// +// The modification was not permitted for security reasons. +// +pub const ERROR_DS_SECURITY_ILLEGAL_MODIFY: i32 = 8423; + +// +// MessageId: ERROR_DS_CANT_REPLACE_HIDDEN_REC +// +// MessageText: +// +// The operation cannot replace the hidden record. +// +pub const ERROR_DS_CANT_REPLACE_HIDDEN_REC: i32 = 8424; + +// +// MessageId: ERROR_DS_BAD_HIERARCHY_FILE +// +// MessageText: +// +// The hierarchy file is invalid. +// +pub const ERROR_DS_BAD_HIERARCHY_FILE: i32 = 8425; + +// +// MessageId: ERROR_DS_BUILD_HIERARCHY_TABLE_FAILED +// +// MessageText: +// +// The attempt to build the hierarchy table failed. +// +pub const ERROR_DS_BUILD_HIERARCHY_TABLE_FAILED: i32 = 8426; + +// +// MessageId: ERROR_DS_CONFIG_PARAM_MISSING +// +// MessageText: +// +// The directory configuration parameter is missing from the registry. +// +pub const ERROR_DS_CONFIG_PARAM_MISSING: i32 = 8427; + +// +// MessageId: ERROR_DS_COUNTING_AB_INDICES_FAILED +// +// MessageText: +// +// The attempt to count the address book indices failed. +// +pub const ERROR_DS_COUNTING_AB_INDICES_FAILED: i32 = 8428; + +// +// MessageId: ERROR_DS_HIERARCHY_TABLE_MALLOC_FAILED +// +// MessageText: +// +// The allocation of the hierarchy table failed. +// +pub const ERROR_DS_HIERARCHY_TABLE_MALLOC_FAILED: i32 = 8429; + +// +// MessageId: ERROR_DS_INTERNAL_FAILURE +// +// MessageText: +// +// The directory service encountered an internal failure. +// +pub const ERROR_DS_INTERNAL_FAILURE: i32 = 8430; + +// +// MessageId: ERROR_DS_UNKNOWN_ERROR +// +// MessageText: +// +// The directory service encountered an unknown failure. +// +pub const ERROR_DS_UNKNOWN_ERROR: i32 = 8431; + +// +// MessageId: ERROR_DS_ROOT_REQUIRES_CLASS_TOP +// +// MessageText: +// +// A root object requires a class of 'top'. +// +pub const ERROR_DS_ROOT_REQUIRES_CLASS_TOP: i32 = 8432; + +// +// MessageId: ERROR_DS_REFUSING_FSMO_ROLES +// +// MessageText: +// +// This directory server is shutting down, and cannot take ownership of new floating single-master operation roles. +// +pub const ERROR_DS_REFUSING_FSMO_ROLES: i32 = 8433; + +// +// MessageId: ERROR_DS_MISSING_FSMO_SETTINGS +// +// MessageText: +// +// The directory service is missing mandatory configuration information, and is unable to determine the ownership of floating single-master operation roles. +// +pub const ERROR_DS_MISSING_FSMO_SETTINGS: i32 = 8434; + +// +// MessageId: ERROR_DS_UNABLE_TO_SURRENDER_ROLES +// +// MessageText: +// +// The directory service was unable to transfer ownership of one or more floating single-master operation roles to other servers. +// +pub const ERROR_DS_UNABLE_TO_SURRENDER_ROLES: i32 = 8435; + +// +// MessageId: ERROR_DS_DRA_GENERIC +// +// MessageText: +// +// The replication operation failed. +// +pub const ERROR_DS_DRA_GENERIC: i32 = 8436; + +// +// MessageId: ERROR_DS_DRA_INVALID_PARAMETER +// +// MessageText: +// +// An invalid parameter was specified for this replication operation. +// +pub const ERROR_DS_DRA_INVALID_PARAMETER: i32 = 8437; + +// +// MessageId: ERROR_DS_DRA_BUSY +// +// MessageText: +// +// The directory service is too busy to complete the replication operation at this time. +// +pub const ERROR_DS_DRA_BUSY: i32 = 8438; + +// +// MessageId: ERROR_DS_DRA_BAD_DN +// +// MessageText: +// +// The distinguished name specified for this replication operation is invalid. +// +pub const ERROR_DS_DRA_BAD_DN: i32 = 8439; + +// +// MessageId: ERROR_DS_DRA_BAD_NC +// +// MessageText: +// +// The naming context specified for this replication operation is invalid. +// +pub const ERROR_DS_DRA_BAD_NC: i32 = 8440; + +// +// MessageId: ERROR_DS_DRA_DN_EXISTS +// +// MessageText: +// +// The distinguished name specified for this replication operation already exists. +// +pub const ERROR_DS_DRA_DN_EXISTS: i32 = 8441; + +// +// MessageId: ERROR_DS_DRA_INTERNAL_ERROR +// +// MessageText: +// +// The replication system encountered an internal error. +// +pub const ERROR_DS_DRA_INTERNAL_ERROR: i32 = 8442; + +// +// MessageId: ERROR_DS_DRA_INCONSISTENT_DIT +// +// MessageText: +// +// The replication operation encountered a database inconsistency. +// +pub const ERROR_DS_DRA_INCONSISTENT_DIT: i32 = 8443; + +// +// MessageId: ERROR_DS_DRA_CONNECTION_FAILED +// +// MessageText: +// +// The server specified for this replication operation could not be contacted. +// +pub const ERROR_DS_DRA_CONNECTION_FAILED: i32 = 8444; + +// +// MessageId: ERROR_DS_DRA_BAD_INSTANCE_TYPE +// +// MessageText: +// +// The replication operation encountered an object with an invalid instance type. +// +pub const ERROR_DS_DRA_BAD_INSTANCE_TYPE: i32 = 8445; + +// +// MessageId: ERROR_DS_DRA_OUT_OF_MEM +// +// MessageText: +// +// The replication operation failed to allocate memory. +// +pub const ERROR_DS_DRA_OUT_OF_MEM: i32 = 8446; + +// +// MessageId: ERROR_DS_DRA_MAIL_PROBLEM +// +// MessageText: +// +// The replication operation encountered an error with the mail system. +// +pub const ERROR_DS_DRA_MAIL_PROBLEM: i32 = 8447; + +// +// MessageId: ERROR_DS_DRA_REF_ALREADY_EXISTS +// +// MessageText: +// +// The replication reference information for the target server already exists. +// +pub const ERROR_DS_DRA_REF_ALREADY_EXISTS: i32 = 8448; + +// +// MessageId: ERROR_DS_DRA_REF_NOT_FOUND +// +// MessageText: +// +// The replication reference information for the target server does not exist. +// +pub const ERROR_DS_DRA_REF_NOT_FOUND: i32 = 8449; + +// +// MessageId: ERROR_DS_DRA_OBJ_IS_REP_SOURCE +// +// MessageText: +// +// The naming context cannot be removed because it is replicated to another server. +// +pub const ERROR_DS_DRA_OBJ_IS_REP_SOURCE: i32 = 8450; + +// +// MessageId: ERROR_DS_DRA_DB_ERROR +// +// MessageText: +// +// The replication operation encountered a database error. +// +pub const ERROR_DS_DRA_DB_ERROR: i32 = 8451; + +// +// MessageId: ERROR_DS_DRA_NO_REPLICA +// +// MessageText: +// +// The naming context is in the process of being removed or is not replicated from the specified server. +// +pub const ERROR_DS_DRA_NO_REPLICA: i32 = 8452; + +// +// MessageId: ERROR_DS_DRA_ACCESS_DENIED +// +// MessageText: +// +// Replication access was denied. +// +pub const ERROR_DS_DRA_ACCESS_DENIED: i32 = 8453; + +// +// MessageId: ERROR_DS_DRA_NOT_SUPPORTED +// +// MessageText: +// +// The requested operation is not supported by this version of the directory service. +// +pub const ERROR_DS_DRA_NOT_SUPPORTED: i32 = 8454; + +// +// MessageId: ERROR_DS_DRA_RPC_CANCELLED +// +// MessageText: +// +// The replication remote procedure call was cancelled. +// +pub const ERROR_DS_DRA_RPC_CANCELLED: i32 = 8455; + +// +// MessageId: ERROR_DS_DRA_SOURCE_DISABLED +// +// MessageText: +// +// The source server is currently rejecting replication requests. +// +pub const ERROR_DS_DRA_SOURCE_DISABLED: i32 = 8456; + +// +// MessageId: ERROR_DS_DRA_SINK_DISABLED +// +// MessageText: +// +// The destination server is currently rejecting replication requests. +// +pub const ERROR_DS_DRA_SINK_DISABLED: i32 = 8457; + +// +// MessageId: ERROR_DS_DRA_NAME_COLLISION +// +// MessageText: +// +// The replication operation failed due to a collision of object names. +// +pub const ERROR_DS_DRA_NAME_COLLISION: i32 = 8458; + +// +// MessageId: ERROR_DS_DRA_SOURCE_REINSTALLED +// +// MessageText: +// +// The replication source has been reinstalled. +// +pub const ERROR_DS_DRA_SOURCE_REINSTALLED: i32 = 8459; + +// +// MessageId: ERROR_DS_DRA_MISSING_PARENT +// +// MessageText: +// +// The replication operation failed because a required parent object is missing. +// +pub const ERROR_DS_DRA_MISSING_PARENT: i32 = 8460; + +// +// MessageId: ERROR_DS_DRA_PREEMPTED +// +// MessageText: +// +// The replication operation was preempted. +// +pub const ERROR_DS_DRA_PREEMPTED: i32 = 8461; + +// +// MessageId: ERROR_DS_DRA_ABANDON_SYNC +// +// MessageText: +// +// The replication synchronization attempt was abandoned because of a lack of updates. +// +pub const ERROR_DS_DRA_ABANDON_SYNC: i32 = 8462; + +// +// MessageId: ERROR_DS_DRA_SHUTDOWN +// +// MessageText: +// +// The replication operation was terminated because the system is shutting down. +// +pub const ERROR_DS_DRA_SHUTDOWN: i32 = 8463; + +// +// MessageId: ERROR_DS_DRA_INCOMPATIBLE_PARTIAL_SET +// +// MessageText: +// +// Synchronization attempt failed because the destination DC is currently waiting to synchronize new partial attributes from source. This condition is normal if a recent schema change modified the partial attribute set. The destination partial attribute set is not a subset of source partial attribute set. +// +pub const ERROR_DS_DRA_INCOMPATIBLE_PARTIAL_SET: i32 = 8464; + +// +// MessageId: ERROR_DS_DRA_SOURCE_IS_PARTIAL_REPLICA +// +// MessageText: +// +// The replication synchronization attempt failed because a master replica attempted to sync from a partial replica. +// +pub const ERROR_DS_DRA_SOURCE_IS_PARTIAL_REPLICA: i32 = 8465; + +// +// MessageId: ERROR_DS_DRA_EXTN_CONNECTION_FAILED +// +// MessageText: +// +// The server specified for this replication operation was contacted, but that server was unable to contact an additional server needed to complete the operation. +// +pub const ERROR_DS_DRA_EXTN_CONNECTION_FAILED: i32 = 8466; + +// +// MessageId: ERROR_DS_INSTALL_SCHEMA_MISMATCH +// +// MessageText: +// +// The version of the Active Directory schema of the source forest is not compatible with the version of Active Directory on this computer. +// +pub const ERROR_DS_INSTALL_SCHEMA_MISMATCH: i32 = 8467; + +// +// MessageId: ERROR_DS_DUP_LINK_ID +// +// MessageText: +// +// Schema update failed: An attribute with the same link identifier already exists. +// +pub const ERROR_DS_DUP_LINK_ID: i32 = 8468; + +// +// MessageId: ERROR_DS_NAME_ERROR_RESOLVING +// +// MessageText: +// +// Name translation: Generic processing error. +// +pub const ERROR_DS_NAME_ERROR_RESOLVING: i32 = 8469; + +// +// MessageId: ERROR_DS_NAME_ERROR_NOT_FOUND +// +// MessageText: +// +// Name translation: Could not find the name or insufficient right to see name. +// +pub const ERROR_DS_NAME_ERROR_NOT_FOUND: i32 = 8470; + +// +// MessageId: ERROR_DS_NAME_ERROR_NOT_UNIQUE +// +// MessageText: +// +// Name translation: Input name mapped to more than one output name. +// +pub const ERROR_DS_NAME_ERROR_NOT_UNIQUE: i32 = 8471; + +// +// MessageId: ERROR_DS_NAME_ERROR_NO_MAPPING +// +// MessageText: +// +// Name translation: Input name found, but not the associated output format. +// +pub const ERROR_DS_NAME_ERROR_NO_MAPPING: i32 = 8472; + +// +// MessageId: ERROR_DS_NAME_ERROR_DOMAIN_ONLY +// +// MessageText: +// +// Name translation: Unable to resolve completely, only the domain was found. +// +pub const ERROR_DS_NAME_ERROR_DOMAIN_ONLY: i32 = 8473; + +// +// MessageId: ERROR_DS_NAME_ERROR_NO_SYNTACTICAL_MAPPING +// +// MessageText: +// +// Name translation: Unable to perform purely syntactical mapping at the client without going out to the wire. +// +pub const ERROR_DS_NAME_ERROR_NO_SYNTACTICAL_MAPPING: i32 = 8474; + +// +// MessageId: ERROR_DS_WRONG_OM_OBJ_CLASS +// +// MessageText: +// +// The OM-Object-Class specified is incorrect for an attribute with the specified syntax. +// +pub const ERROR_DS_WRONG_OM_OBJ_CLASS: i32 = 8476; + +// +// MessageId: ERROR_DS_DRA_REPL_PENDING +// +// MessageText: +// +// The replication request has been posted; waiting for reply. +// +pub const ERROR_DS_DRA_REPL_PENDING: i32 = 8477; + +// +// MessageId: ERROR_DS_DS_REQUIRED +// +// MessageText: +// +// The requested operation requires a directory service, and none was available. +// +pub const ERROR_DS_DS_REQUIRED: i32 = 8478; + +// +// MessageId: ERROR_DS_INVALID_LDAP_DISPLAY_NAME +// +// MessageText: +// +// The LDAP display name of the class or attribute contains non-ASCII characters. +// +pub const ERROR_DS_INVALID_LDAP_DISPLAY_NAME: i32 = 8479; + +// +// MessageId: ERROR_DS_NON_BASE_SEARCH +// +// MessageText: +// +// The requested search operation is only supported for base searches. +// +pub const ERROR_DS_NON_BASE_SEARCH: i32 = 8480; + +// +// MessageId: ERROR_DS_CANT_RETRIEVE_ATTS +// +// MessageText: +// +// The search failed to retrieve attributes from the database. +// +pub const ERROR_DS_CANT_RETRIEVE_ATTS: i32 = 8481; + +// +// MessageId: ERROR_DS_BACKLINK_WITHOUT_LINK +// +// MessageText: +// +// The schema update operation tried to add a backward link attribute that has no corresponding forward link. +// +pub const ERROR_DS_BACKLINK_WITHOUT_LINK: i32 = 8482; + +// +// MessageId: ERROR_DS_EPOCH_MISMATCH +// +// MessageText: +// +// Source and destination of a cross-domain move do not agree on the object's epoch number. Either source or destination does not have the latest version of the object. +// +pub const ERROR_DS_EPOCH_MISMATCH: i32 = 8483; + +// +// MessageId: ERROR_DS_SRC_NAME_MISMATCH +// +// MessageText: +// +// Source and destination of a cross-domain move do not agree on the object's current name. Either source or destination does not have the latest version of the object. +// +pub const ERROR_DS_SRC_NAME_MISMATCH: i32 = 8484; + +// +// MessageId: ERROR_DS_SRC_AND_DST_NC_IDENTICAL +// +// MessageText: +// +// Source and destination for the cross-domain move operation are identical. Caller should use local move operation instead of cross-domain move operation. +// +pub const ERROR_DS_SRC_AND_DST_NC_IDENTICAL: i32 = 8485; + +// +// MessageId: ERROR_DS_DST_NC_MISMATCH +// +// MessageText: +// +// Source and destination for a cross-domain move are not in agreement on the naming contexts in the forest. Either source or destination does not have the latest version of the Partitions container. +// +pub const ERROR_DS_DST_NC_MISMATCH: i32 = 8486; + +// +// MessageId: ERROR_DS_NOT_AUTHORITIVE_FOR_DST_NC +// +// MessageText: +// +// Destination of a cross-domain move is not authoritative for the destination naming context. +// +pub const ERROR_DS_NOT_AUTHORITIVE_FOR_DST_NC: i32 = 8487; + +// +// MessageId: ERROR_DS_SRC_GUID_MISMATCH +// +// MessageText: +// +// Source and destination of a cross-domain move do not agree on the identity of the source object. Either source or destination does not have the latest version of the source object. +// +pub const ERROR_DS_SRC_GUID_MISMATCH: i32 = 8488; + +// +// MessageId: ERROR_DS_CANT_MOVE_DELETED_OBJECT +// +// MessageText: +// +// Object being moved across-domains is already known to be deleted by the destination server. The source server does not have the latest version of the source object. +// +pub const ERROR_DS_CANT_MOVE_DELETED_OBJECT: i32 = 8489; + +// +// MessageId: ERROR_DS_PDC_OPERATION_IN_PROGRESS +// +// MessageText: +// +// Another operation which requires exclusive access to the PDC FSMO is already in progress. +// +pub const ERROR_DS_PDC_OPERATION_IN_PROGRESS: i32 = 8490; + +// +// MessageId: ERROR_DS_CROSS_DOMAIN_CLEANUP_REQD +// +// MessageText: +// +// A cross-domain move operation failed such that two versions of the moved object exist - one each in the source and destination domains. The destination object needs to be removed to restore the system to a consistent state. +// +pub const ERROR_DS_CROSS_DOMAIN_CLEANUP_REQD: i32 = 8491; + +// +// MessageId: ERROR_DS_ILLEGAL_XDOM_MOVE_OPERATION +// +// MessageText: +// +// This object may not be moved across domain boundaries either because cross-domain moves for this class are disallowed, or the object has some special characteristics, e.g.: trust account or restricted RID, which prevent its move. +// +pub const ERROR_DS_ILLEGAL_XDOM_MOVE_OPERATION: i32 = 8492; + +// +// MessageId: ERROR_DS_CANT_WITH_ACCT_GROUP_MEMBERSHPS +// +// MessageText: +// +// Can't move objects with memberships across domain boundaries as once moved, this would violate the membership conditions of the account group. Remove the object from any account group memberships and retry. +// +pub const ERROR_DS_CANT_WITH_ACCT_GROUP_MEMBERSHPS: i32 = 8493; + +// +// MessageId: ERROR_DS_NC_MUST_HAVE_NC_PARENT +// +// MessageText: +// +// A naming context head must be the immediate child of another naming context head, not of an interior node. +// +pub const ERROR_DS_NC_MUST_HAVE_NC_PARENT: i32 = 8494; + +// +// MessageId: ERROR_DS_CR_IMPOSSIBLE_TO_VALIDATE +// +// MessageText: +// +// The directory cannot validate the proposed naming context name because it does not hold a replica of the naming context above the proposed naming context. Please ensure that the domain naming master role is held by a server that is configured as a global catalog server, and that the server is up to date with its replication partners. (Applies only to Windows 2000 Domain Naming masters) +// +pub const ERROR_DS_CR_IMPOSSIBLE_TO_VALIDATE: i32 = 8495; + +// +// MessageId: ERROR_DS_DST_DOMAIN_NOT_NATIVE +// +// MessageText: +// +// Destination domain must be in native mode. +// +pub const ERROR_DS_DST_DOMAIN_NOT_NATIVE: i32 = 8496; + +// +// MessageId: ERROR_DS_MISSING_INFRASTRUCTURE_CONTAINER +// +// MessageText: +// +// The operation can not be performed because the server does not have an infrastructure container in the domain of interest. +// +pub const ERROR_DS_MISSING_INFRASTRUCTURE_CONTAINER: i32 = 8497; + +// +// MessageId: ERROR_DS_CANT_MOVE_ACCOUNT_GROUP +// +// MessageText: +// +// Cross-domain move of non-empty account groups is not allowed. +// +pub const ERROR_DS_CANT_MOVE_ACCOUNT_GROUP: i32 = 8498; + +// +// MessageId: ERROR_DS_CANT_MOVE_RESOURCE_GROUP +// +// MessageText: +// +// Cross-domain move of non-empty resource groups is not allowed. +// +pub const ERROR_DS_CANT_MOVE_RESOURCE_GROUP: i32 = 8499; + +// +// MessageId: ERROR_DS_INVALID_SEARCH_FLAG +// +// MessageText: +// +// The search flags for the attribute are invalid. The ANR bit is valid only on attributes of Unicode or Teletex strings. +// +pub const ERROR_DS_INVALID_SEARCH_FLAG: i32 = 8500; + +// +// MessageId: ERROR_DS_NO_TREE_DELETE_ABOVE_NC +// +// MessageText: +// +// Tree deletions starting at an object which has an NC head as a descendant are not allowed. +// +pub const ERROR_DS_NO_TREE_DELETE_ABOVE_NC: i32 = 8501; + +// +// MessageId: ERROR_DS_COULDNT_LOCK_TREE_FOR_DELETE +// +// MessageText: +// +// The directory service failed to lock a tree in preparation for a tree deletion because the tree was in use. +// +pub const ERROR_DS_COULDNT_LOCK_TREE_FOR_DELETE: i32 = 8502; + +// +// MessageId: ERROR_DS_COULDNT_IDENTIFY_OBJECTS_FOR_TREE_DELETE +// +// MessageText: +// +// The directory service failed to identify the list of objects to delete while attempting a tree deletion. +// +pub const ERROR_DS_COULDNT_IDENTIFY_OBJECTS_FOR_TREE_DELETE: i32 = 8503; + +// +// MessageId: ERROR_DS_SAM_INIT_FAILURE +// +// MessageText: +// +// Security Accounts Manager initialization failed because of the following error: %1. +// Error Status: 0x%2. Click OK to shut down the system and reboot into Directory Services Restore Mode. Check the event log for detailed information. +// +pub const ERROR_DS_SAM_INIT_FAILURE: i32 = 8504; + +// +// MessageId: ERROR_DS_SENSITIVE_GROUP_VIOLATION +// +// MessageText: +// +// Only an administrator can modify the membership list of an administrative group. +// +pub const ERROR_DS_SENSITIVE_GROUP_VIOLATION: i32 = 8505; + +// +// MessageId: ERROR_DS_CANT_MOD_PRIMARYGROUPID +// +// MessageText: +// +// Cannot change the primary group ID of a domain controller account. +// +pub const ERROR_DS_CANT_MOD_PRIMARYGROUPID: i32 = 8506; + +// +// MessageId: ERROR_DS_ILLEGAL_BASE_SCHEMA_MOD +// +// MessageText: +// +// An attempt is made to modify the base schema. +// +pub const ERROR_DS_ILLEGAL_BASE_SCHEMA_MOD: i32 = 8507; + +// +// MessageId: ERROR_DS_NONSAFE_SCHEMA_CHANGE +// +// MessageText: +// +// Adding a new mandatory attribute to an existing class, deleting a mandatory attribute from an existing class, or adding an optional attribute to the special class Top that is not a backlink attribute (directly or through inheritance, for example, by adding or deleting an auxiliary class) is not allowed. +// +pub const ERROR_DS_NONSAFE_SCHEMA_CHANGE: i32 = 8508; + +// +// MessageId: ERROR_DS_SCHEMA_UPDATE_DISALLOWED +// +// MessageText: +// +// Schema update is not allowed on this DC because the DC is not the schema FSMO Role Owner. +// +pub const ERROR_DS_SCHEMA_UPDATE_DISALLOWED: i32 = 8509; + +// +// MessageId: ERROR_DS_CANT_CREATE_UNDER_SCHEMA +// +// MessageText: +// +// An object of this class cannot be created under the schema container. You can only create attribute-schema and class-schema objects under the schema container. +// +pub const ERROR_DS_CANT_CREATE_UNDER_SCHEMA: i32 = 8510; + +// +// MessageId: ERROR_DS_INSTALL_NO_SRC_SCH_VERSION +// +// MessageText: +// +// The replica/child install failed to get the objectVersion attribute on the schema container on the source DC. Either the attribute is missing on the schema container or the credentials supplied do not have permission to read it. +// +pub const ERROR_DS_INSTALL_NO_SRC_SCH_VERSION: i32 = 8511; + +// +// MessageId: ERROR_DS_INSTALL_NO_SCH_VERSION_IN_INIFILE +// +// MessageText: +// +// The replica/child install failed to read the objectVersion attribute in the SCHEMA section of the file schema.ini in the system32 directory. +// +pub const ERROR_DS_INSTALL_NO_SCH_VERSION_IN_INIFILE: i32 = 8512; + +// +// MessageId: ERROR_DS_INVALID_GROUP_TYPE +// +// MessageText: +// +// The specified group type is invalid. +// +pub const ERROR_DS_INVALID_GROUP_TYPE: i32 = 8513; + +// +// MessageId: ERROR_DS_NO_NEST_GLOBALGROUP_IN_MIXEDDOMAIN +// +// MessageText: +// +// You cannot nest global groups in a mixed domain if the group is security-enabled. +// +pub const ERROR_DS_NO_NEST_GLOBALGROUP_IN_MIXEDDOMAIN: i32 = 8514; + +// +// MessageId: ERROR_DS_NO_NEST_LOCALGROUP_IN_MIXEDDOMAIN +// +// MessageText: +// +// You cannot nest local groups in a mixed domain if the group is security-enabled. +// +pub const ERROR_DS_NO_NEST_LOCALGROUP_IN_MIXEDDOMAIN: i32 = 8515; + +// +// MessageId: ERROR_DS_GLOBAL_CANT_HAVE_LOCAL_MEMBER +// +// MessageText: +// +// A global group cannot have a local group as a member. +// +pub const ERROR_DS_GLOBAL_CANT_HAVE_LOCAL_MEMBER: i32 = 8516; + +// +// MessageId: ERROR_DS_GLOBAL_CANT_HAVE_UNIVERSAL_MEMBER +// +// MessageText: +// +// A global group cannot have a universal group as a member. +// +pub const ERROR_DS_GLOBAL_CANT_HAVE_UNIVERSAL_MEMBER: i32 = 8517; + +// +// MessageId: ERROR_DS_UNIVERSAL_CANT_HAVE_LOCAL_MEMBER +// +// MessageText: +// +// A universal group cannot have a local group as a member. +// +pub const ERROR_DS_UNIVERSAL_CANT_HAVE_LOCAL_MEMBER: i32 = 8518; + +// +// MessageId: ERROR_DS_GLOBAL_CANT_HAVE_CROSSDOMAIN_MEMBER +// +// MessageText: +// +// A global group cannot have a cross-domain member. +// +pub const ERROR_DS_GLOBAL_CANT_HAVE_CROSSDOMAIN_MEMBER: i32 = 8519; + +// +// MessageId: ERROR_DS_LOCAL_CANT_HAVE_CROSSDOMAIN_LOCAL_MEMBER +// +// MessageText: +// +// A local group cannot have another cross domain local group as a member. +// +pub const ERROR_DS_LOCAL_CANT_HAVE_CROSSDOMAIN_LOCAL_MEMBER: i32 = 8520; + +// +// MessageId: ERROR_DS_HAVE_PRIMARY_MEMBERS +// +// MessageText: +// +// A group with primary members cannot change to a security-disabled group. +// +pub const ERROR_DS_HAVE_PRIMARY_MEMBERS: i32 = 8521; + +// +// MessageId: ERROR_DS_STRING_SD_CONVERSION_FAILED +// +// MessageText: +// +// The schema cache load failed to convert the string default SD on a class-schema object. +// +pub const ERROR_DS_STRING_SD_CONVERSION_FAILED: i32 = 8522; + +// +// MessageId: ERROR_DS_NAMING_MASTER_GC +// +// MessageText: +// +// Only DSAs configured to be Global Catalog servers should be allowed to hold the Domain Naming Master FSMO role. (Applies only to Windows 2000 servers) +// +pub const ERROR_DS_NAMING_MASTER_GC: i32 = 8523; + +// +// MessageId: ERROR_DS_DNS_LOOKUP_FAILURE +// +// MessageText: +// +// The DSA operation is unable to proceed because of a DNS lookup failure. +// +pub const ERROR_DS_DNS_LOOKUP_FAILURE: i32 = 8524; + +// +// MessageId: ERROR_DS_COULDNT_UPDATE_SPNS +// +// MessageText: +// +// While processing a change to the DNS Host Name for an object, the Service Principal Name values could not be kept in sync. +// +pub const ERROR_DS_COULDNT_UPDATE_SPNS: i32 = 8525; + +// +// MessageId: ERROR_DS_CANT_RETRIEVE_SD +// +// MessageText: +// +// The Security Descriptor attribute could not be read. +// +pub const ERROR_DS_CANT_RETRIEVE_SD: i32 = 8526; + +// +// MessageId: ERROR_DS_KEY_NOT_UNIQUE +// +// MessageText: +// +// The object requested was not found, but an object with that key was found. +// +pub const ERROR_DS_KEY_NOT_UNIQUE: i32 = 8527; + +// +// MessageId: ERROR_DS_WRONG_LINKED_ATT_SYNTAX +// +// MessageText: +// +// The syntax of the linked attribute being added is incorrect. Forward links can only have syntax 2.5.5.1, 2.5.5.7, and 2.5.5.14, and backlinks can only have syntax 2.5.5.1 +// +pub const ERROR_DS_WRONG_LINKED_ATT_SYNTAX: i32 = 8528; + +// +// MessageId: ERROR_DS_SAM_NEED_BOOTKEY_PASSWORD +// +// MessageText: +// +// Security Account Manager needs to get the boot password. +// +pub const ERROR_DS_SAM_NEED_BOOTKEY_PASSWORD: i32 = 8529; + +// +// MessageId: ERROR_DS_SAM_NEED_BOOTKEY_FLOPPY +// +// MessageText: +// +// Security Account Manager needs to get the boot key from floppy disk. +// +pub const ERROR_DS_SAM_NEED_BOOTKEY_FLOPPY: i32 = 8530; + +// +// MessageId: ERROR_DS_CANT_START +// +// MessageText: +// +// Directory Service cannot start. +// +pub const ERROR_DS_CANT_START: i32 = 8531; + +// +// MessageId: ERROR_DS_INIT_FAILURE +// +// MessageText: +// +// Directory Services could not start. +// +pub const ERROR_DS_INIT_FAILURE: i32 = 8532; + +// +// MessageId: ERROR_DS_NO_PKT_PRIVACY_ON_CONNECTION +// +// MessageText: +// +// The connection between client and server requires packet privacy or better. +// +pub const ERROR_DS_NO_PKT_PRIVACY_ON_CONNECTION: i32 = 8533; + +// +// MessageId: ERROR_DS_SOURCE_DOMAIN_IN_FOREST +// +// MessageText: +// +// The source domain may not be in the same forest as destination. +// +pub const ERROR_DS_SOURCE_DOMAIN_IN_FOREST: i32 = 8534; + +// +// MessageId: ERROR_DS_DESTINATION_DOMAIN_NOT_IN_FOREST +// +// MessageText: +// +// The destination domain must be in the forest. +// +pub const ERROR_DS_DESTINATION_DOMAIN_NOT_IN_FOREST: i32 = 8535; + +// +// MessageId: ERROR_DS_DESTINATION_AUDITING_NOT_ENABLED +// +// MessageText: +// +// The operation requires that destination domain auditing be enabled. +// +pub const ERROR_DS_DESTINATION_AUDITING_NOT_ENABLED: i32 = 8536; + +// +// MessageId: ERROR_DS_CANT_FIND_DC_FOR_SRC_DOMAIN +// +// MessageText: +// +// The operation couldn't locate a DC for the source domain. +// +pub const ERROR_DS_CANT_FIND_DC_FOR_SRC_DOMAIN: i32 = 8537; + +// +// MessageId: ERROR_DS_SRC_OBJ_NOT_GROUP_OR_USER +// +// MessageText: +// +// The source object must be a group or user. +// +pub const ERROR_DS_SRC_OBJ_NOT_GROUP_OR_USER: i32 = 8538; + +// +// MessageId: ERROR_DS_SRC_SID_EXISTS_IN_FOREST +// +// MessageText: +// +// The source object's SID already exists in destination forest. +// +pub const ERROR_DS_SRC_SID_EXISTS_IN_FOREST: i32 = 8539; + +// +// MessageId: ERROR_DS_SRC_AND_DST_OBJECT_CLASS_MISMATCH +// +// MessageText: +// +// The source and destination object must be of the same type. +// +pub const ERROR_DS_SRC_AND_DST_OBJECT_CLASS_MISMATCH: i32 = 8540; + +// +// MessageId: ERROR_SAM_INIT_FAILURE +// +// MessageText: +// +// Security Accounts Manager initialization failed because of the following error: %1. +// Error Status: 0x%2. Click OK to shut down the system and reboot into Safe Mode. Check the event log for detailed information. +// +pub const ERROR_SAM_INIT_FAILURE: i32 = 8541; + +// +// MessageId: ERROR_DS_DRA_SCHEMA_INFO_SHIP +// +// MessageText: +// +// Schema information could not be included in the replication request. +// +pub const ERROR_DS_DRA_SCHEMA_INFO_SHIP: i32 = 8542; + +// +// MessageId: ERROR_DS_DRA_SCHEMA_CONFLICT +// +// MessageText: +// +// The replication operation could not be completed due to a schema incompatibility. +// +pub const ERROR_DS_DRA_SCHEMA_CONFLICT: i32 = 8543; + +// +// MessageId: ERROR_DS_DRA_EARLIER_SCHEMA_CONFLICT +// +// MessageText: +// +// The replication operation could not be completed due to a previous schema incompatibility. +// +pub const ERROR_DS_DRA_EARLIER_SCHEMA_CONFLICT: i32 = 8544; + +// +// MessageId: ERROR_DS_DRA_OBJ_NC_MISMATCH +// +// MessageText: +// +// The replication update could not be applied because either the source or the destination has not yet received information regarding a recent cross-domain move operation. +// +pub const ERROR_DS_DRA_OBJ_NC_MISMATCH: i32 = 8545; + +// +// MessageId: ERROR_DS_NC_STILL_HAS_DSAS +// +// MessageText: +// +// The requested domain could not be deleted because there exist domain controllers that still host this domain. +// +pub const ERROR_DS_NC_STILL_HAS_DSAS: i32 = 8546; + +// +// MessageId: ERROR_DS_GC_REQUIRED +// +// MessageText: +// +// The requested operation can be performed only on a global catalog server. +// +pub const ERROR_DS_GC_REQUIRED: i32 = 8547; + +// +// MessageId: ERROR_DS_LOCAL_MEMBER_OF_LOCAL_ONLY +// +// MessageText: +// +// A local group can only be a member of other local groups in the same domain. +// +pub const ERROR_DS_LOCAL_MEMBER_OF_LOCAL_ONLY: i32 = 8548; + +// +// MessageId: ERROR_DS_NO_FPO_IN_UNIVERSAL_GROUPS +// +// MessageText: +// +// Foreign security principals cannot be members of universal groups. +// +pub const ERROR_DS_NO_FPO_IN_UNIVERSAL_GROUPS: i32 = 8549; + +// +// MessageId: ERROR_DS_CANT_ADD_TO_GC +// +// MessageText: +// +// The attribute is not allowed to be replicated to the GC because of security reasons. +// +pub const ERROR_DS_CANT_ADD_TO_GC: i32 = 8550; + +// +// MessageId: ERROR_DS_NO_CHECKPOINT_WITH_PDC +// +// MessageText: +// +// The checkpoint with the PDC could not be taken because there too many modifications being processed currently. +// +pub const ERROR_DS_NO_CHECKPOINT_WITH_PDC: i32 = 8551; + +// +// MessageId: ERROR_DS_SOURCE_AUDITING_NOT_ENABLED +// +// MessageText: +// +// The operation requires that source domain auditing be enabled. +// +pub const ERROR_DS_SOURCE_AUDITING_NOT_ENABLED: i32 = 8552; + +// +// MessageId: ERROR_DS_CANT_CREATE_IN_NONDOMAIN_NC +// +// MessageText: +// +// Security principal objects can only be created inside domain naming contexts. +// +pub const ERROR_DS_CANT_CREATE_IN_NONDOMAIN_NC: i32 = 8553; + +// +// MessageId: ERROR_DS_INVALID_NAME_FOR_SPN +// +// MessageText: +// +// A Service Principal Name (SPN) could not be export constructed because the provided hostname is not in the necessary format. +// +pub const ERROR_DS_INVALID_NAME_FOR_SPN: i32 = 8554; + +// +// MessageId: ERROR_DS_FILTER_USES_CONTRUCTED_ATTRS +// +// MessageText: +// +// A Filter was passed that uses export constructed attributes. +// +pub const ERROR_DS_FILTER_USES_CONTRUCTED_ATTRS: i32 = 8555; + +// +// MessageId: ERROR_DS_UNICODEPWD_NOT_IN_QUOTES +// +// MessageText: +// +// The unicodePwd attribute value must be enclosed in double quotes. +// +pub const ERROR_DS_UNICODEPWD_NOT_IN_QUOTES: i32 = 8556; + +// +// MessageId: ERROR_DS_MACHINE_ACCOUNT_QUOTA_EXCEEDED +// +// MessageText: +// +// Your computer could not be joined to the domain. You have exceeded the maximum number of computer accounts you are allowed to create in this domain. Contact your system administrator to have this limit reset or increased. +// +pub const ERROR_DS_MACHINE_ACCOUNT_QUOTA_EXCEEDED: i32 = 8557; + +// +// MessageId: ERROR_DS_MUST_BE_RUN_ON_DST_DC +// +// MessageText: +// +// For security reasons, the operation must be run on the destination DC. +// +pub const ERROR_DS_MUST_BE_RUN_ON_DST_DC: i32 = 8558; + +// +// MessageId: ERROR_DS_SRC_DC_MUST_BE_SP4_OR_GREATER +// +// MessageText: +// +// For security reasons, the source DC must be NT4SP4 or greater. +// +pub const ERROR_DS_SRC_DC_MUST_BE_SP4_OR_GREATER: i32 = 8559; + +// +// MessageId: ERROR_DS_CANT_TREE_DELETE_CRITICAL_OBJ +// +// MessageText: +// +// Critical Directory Service System objects cannot be deleted during tree delete operations. The tree delete may have been partially performed. +// +pub const ERROR_DS_CANT_TREE_DELETE_CRITICAL_OBJ: i32 = 8560; + +// +// MessageId: ERROR_DS_INIT_FAILURE_CONSOLE +// +// MessageText: +// +// Directory Services could not start because of the following error: %1. +// Error Status: 0x%2. Please click OK to shutdown the system. You can use the recovery console to diagnose the system further. +// +pub const ERROR_DS_INIT_FAILURE_CONSOLE: i32 = 8561; + +// +// MessageId: ERROR_DS_SAM_INIT_FAILURE_CONSOLE +// +// MessageText: +// +// Security Accounts Manager initialization failed because of the following error: %1. +// Error Status: 0x%2. Please click OK to shutdown the system. You can use the recovery console to diagnose the system further. +// +pub const ERROR_DS_SAM_INIT_FAILURE_CONSOLE: i32 = 8562; + +// +// MessageId: ERROR_DS_FOREST_VERSION_TOO_HIGH +// +// MessageText: +// +// The version of the operating system installed is incompatible with the current forest functional level. You must upgrade to a new version of the operating system before this server can become a domain controller in this forest. +// +pub const ERROR_DS_FOREST_VERSION_TOO_HIGH: i32 = 8563; + +// +// MessageId: ERROR_DS_DOMAIN_VERSION_TOO_HIGH +// +// MessageText: +// +// The version of the operating system installed is incompatible with the current domain functional level. You must upgrade to a new version of the operating system before this server can become a domain controller in this domain. +// +pub const ERROR_DS_DOMAIN_VERSION_TOO_HIGH: i32 = 8564; + +// +// MessageId: ERROR_DS_FOREST_VERSION_TOO_LOW +// +// MessageText: +// +// The version of the operating system installed on this server no longer supports the current forest functional level. You must raise the forest functional level before this server can become a domain controller in this forest. +// +pub const ERROR_DS_FOREST_VERSION_TOO_LOW: i32 = 8565; + +// +// MessageId: ERROR_DS_DOMAIN_VERSION_TOO_LOW +// +// MessageText: +// +// The version of the operating system installed on this server no longer supports the current domain functional level. You must raise the domain functional level before this server can become a domain controller in this domain. +// +pub const ERROR_DS_DOMAIN_VERSION_TOO_LOW: i32 = 8566; + +// +// MessageId: ERROR_DS_INCOMPATIBLE_VERSION +// +// MessageText: +// +// The version of the operating system installed on this server is incompatible with the functional level of the domain or forest. +// +pub const ERROR_DS_INCOMPATIBLE_VERSION: i32 = 8567; + +// +// MessageId: ERROR_DS_LOW_DSA_VERSION +// +// MessageText: +// +// The functional level of the domain (or forest) cannot be raised to the requested value, because there exist one or more domain controllers in the domain (or forest) that are at a lower incompatible functional level. +// +pub const ERROR_DS_LOW_DSA_VERSION: i32 = 8568; + +// +// MessageId: ERROR_DS_NO_BEHAVIOR_VERSION_IN_MIXEDDOMAIN +// +// MessageText: +// +// The forest functional level cannot be raised to the requested value since one or more domains are still in mixed domain mode. All domains in the forest must be in native mode, for you to raise the forest functional level. +// +pub const ERROR_DS_NO_BEHAVIOR_VERSION_IN_MIXEDDOMAIN: i32 = 8569; + +// +// MessageId: ERROR_DS_NOT_SUPPORTED_SORT_ORDER +// +// MessageText: +// +// The sort order requested is not supported. +// +pub const ERROR_DS_NOT_SUPPORTED_SORT_ORDER: i32 = 8570; + +// +// MessageId: ERROR_DS_NAME_NOT_UNIQUE +// +// MessageText: +// +// The requested name already exists as a unique identifier. +// +pub const ERROR_DS_NAME_NOT_UNIQUE: i32 = 8571; + +// +// MessageId: ERROR_DS_MACHINE_ACCOUNT_CREATED_PRENT4 +// +// MessageText: +// +// The machine account was created pre-NT4. The account needs to be recreated. +// +pub const ERROR_DS_MACHINE_ACCOUNT_CREATED_PRENT4: i32 = 8572; + +// +// MessageId: ERROR_DS_OUT_OF_VERSION_STORE +// +// MessageText: +// +// The database is out of version store. +// +pub const ERROR_DS_OUT_OF_VERSION_STORE: i32 = 8573; + +// +// MessageId: ERROR_DS_INCOMPATIBLE_CONTROLS_USED +// +// MessageText: +// +// Unable to continue operation because multiple conflicting controls were used. +// +pub const ERROR_DS_INCOMPATIBLE_CONTROLS_USED: i32 = 8574; + +// +// MessageId: ERROR_DS_NO_REF_DOMAIN +// +// MessageText: +// +// Unable to find a valid security descriptor reference domain for this partition. +// +pub const ERROR_DS_NO_REF_DOMAIN: i32 = 8575; + +// +// MessageId: ERROR_DS_RESERVED_LINK_ID +// +// MessageText: +// +// Schema update failed: The link identifier is reserved. +// +pub const ERROR_DS_RESERVED_LINK_ID: i32 = 8576; + +// +// MessageId: ERROR_DS_LINK_ID_NOT_AVAILABLE +// +// MessageText: +// +// Schema update failed: There are no link identifiers available. +// +pub const ERROR_DS_LINK_ID_NOT_AVAILABLE: i32 = 8577; + +// +// MessageId: ERROR_DS_AG_CANT_HAVE_UNIVERSAL_MEMBER +// +// MessageText: +// +// An account group can not have a universal group as a member. +// +pub const ERROR_DS_AG_CANT_HAVE_UNIVERSAL_MEMBER: i32 = 8578; + +// +// MessageId: ERROR_DS_MODIFYDN_DISALLOWED_BY_INSTANCE_TYPE +// +// MessageText: +// +// Rename or move operations on naming context heads or read-only objects are not allowed. +// +pub const ERROR_DS_MODIFYDN_DISALLOWED_BY_INSTANCE_TYPE: i32 = 8579; + +// +// MessageId: ERROR_DS_NO_OBJECT_MOVE_IN_SCHEMA_NC +// +// MessageText: +// +// Move operations on objects in the schema naming context are not allowed. +// +pub const ERROR_DS_NO_OBJECT_MOVE_IN_SCHEMA_NC: i32 = 8580; + +// +// MessageId: ERROR_DS_MODIFYDN_DISALLOWED_BY_FLAG +// +// MessageText: +// +// A system flag has been set on the object and does not allow the object to be moved or renamed. +// +pub const ERROR_DS_MODIFYDN_DISALLOWED_BY_FLAG: i32 = 8581; + +// +// MessageId: ERROR_DS_MODIFYDN_WRONG_GRANDPARENT +// +// MessageText: +// +// This object is not allowed to change its grandparent container. Moves are not forbidden on this object, but are restricted to sibling containers. +// +pub const ERROR_DS_MODIFYDN_WRONG_GRANDPARENT: i32 = 8582; + +// +// MessageId: ERROR_DS_NAME_ERROR_TRUST_REFERRAL +// +// MessageText: +// +// Unable to resolve completely, a referral to another forest is generated. +// +pub const ERROR_DS_NAME_ERROR_TRUST_REFERRAL: i32 = 8583; + +// +// MessageId: ERROR_NOT_SUPPORTED_ON_STANDARD_SERVER +// +// MessageText: +// +// The requested action is not supported on standard server. +// +pub const ERROR_NOT_SUPPORTED_ON_STANDARD_SERVER: i32 = 8584; + +// +// MessageId: ERROR_DS_CANT_ACCESS_REMOTE_PART_OF_AD +// +// MessageText: +// +// Could not access a partition of the Active Directory located on a remote server. Make sure at least one server is running for the partition in question. +// +pub const ERROR_DS_CANT_ACCESS_REMOTE_PART_OF_AD: i32 = 8585; + +// +// MessageId: ERROR_DS_CR_IMPOSSIBLE_TO_VALIDATE_V2 +// +// MessageText: +// +// The directory cannot validate the proposed naming context (or partition) name because it does not hold a replica nor can it contact a replica of the naming context above the proposed naming context. Please ensure that the parent naming context is properly registered in DNS, and at least one replica of this naming context is reachable by the Domain Naming master. +// +pub const ERROR_DS_CR_IMPOSSIBLE_TO_VALIDATE_V2: i32 = 8586; + +// +// MessageId: ERROR_DS_THREAD_LIMIT_EXCEEDED +// +// MessageText: +// +// The thread limit for this request was exceeded. +// +pub const ERROR_DS_THREAD_LIMIT_EXCEEDED: i32 = 8587; + +// +// MessageId: ERROR_DS_NOT_CLOSEST +// +// MessageText: +// +// The Global catalog server is not in the closest site. +// +pub const ERROR_DS_NOT_CLOSEST: i32 = 8588; + +// +// MessageId: ERROR_DS_CANT_DERIVE_SPN_WITHOUT_SERVER_REF +// +// MessageText: +// +// The DS cannot derive a service principal name (SPN) with which to mutually authenticate the target server because the corresponding server object in the local DS database has no serverReference attribute. +// +pub const ERROR_DS_CANT_DERIVE_SPN_WITHOUT_SERVER_REF: i32 = 8589; + +// +// MessageId: ERROR_DS_SINGLE_USER_MODE_FAILED +// +// MessageText: +// +// The Directory Service failed to enter single user mode. +// +pub const ERROR_DS_SINGLE_USER_MODE_FAILED: i32 = 8590; + +// +// MessageId: ERROR_DS_NTDSCRIPT_SYNTAX_ERROR +// +// MessageText: +// +// The Directory Service cannot parse the script because of a syntax error. +// +pub const ERROR_DS_NTDSCRIPT_SYNTAX_ERROR: i32 = 8591; + +// +// MessageId: ERROR_DS_NTDSCRIPT_PROCESS_ERROR +// +// MessageText: +// +// The Directory Service cannot process the script because of an error. +// +pub const ERROR_DS_NTDSCRIPT_PROCESS_ERROR: i32 = 8592; + +// +// MessageId: ERROR_DS_DIFFERENT_REPL_EPOCHS +// +// MessageText: +// +// The directory service cannot perform the requested operation because the servers +// involved are of different replication epochs (which is usually related to a +// domain rename that is in progress). +// +pub const ERROR_DS_DIFFERENT_REPL_EPOCHS: i32 = 8593; + +// +// MessageId: ERROR_DS_DRS_EXTENSIONS_CHANGED +// +// MessageText: +// +// The directory service binding must be renegotiated due to a change in the server +// extensions information. +// +pub const ERROR_DS_DRS_EXTENSIONS_CHANGED: i32 = 8594; + +// +// MessageId: ERROR_DS_REPLICA_SET_CHANGE_NOT_ALLOWED_ON_DISABLED_CR +// +// MessageText: +// +// Operation not allowed on a disabled cross ref. +// +pub const ERROR_DS_REPLICA_SET_CHANGE_NOT_ALLOWED_ON_DISABLED_CR: i32 = 8595; + +// +// MessageId: ERROR_DS_NO_MSDS_INTID +// +// MessageText: +// +// Schema update failed: No values for msDS-IntId are available. +// +pub const ERROR_DS_NO_MSDS_INTID: i32 = 8596; + +// +// MessageId: ERROR_DS_DUP_MSDS_INTID +// +// MessageText: +// +// Schema update failed: Duplicate msDS-INtId. Retry the operation. +// +pub const ERROR_DS_DUP_MSDS_INTID: i32 = 8597; + +// +// MessageId: ERROR_DS_EXISTS_IN_RDNATTID +// +// MessageText: +// +// Schema deletion failed: attribute is used in rDNAttID. +// +pub const ERROR_DS_EXISTS_IN_RDNATTID: i32 = 8598; + +// +// MessageId: ERROR_DS_AUTHORIZATION_FAILED +// +// MessageText: +// +// The directory service failed to authorize the request. +// +pub const ERROR_DS_AUTHORIZATION_FAILED: i32 = 8599; + +// +// MessageId: ERROR_DS_INVALID_SCRIPT +// +// MessageText: +// +// The Directory Service cannot process the script because it is invalid. +// +pub const ERROR_DS_INVALID_SCRIPT: i32 = 8600; + +// +// MessageId: ERROR_DS_REMOTE_CROSSREF_OP_FAILED +// +// MessageText: +// +// The remote create cross reference operation failed on the Domain Naming Master FSMO. The operation's error is in the extended data. +// +pub const ERROR_DS_REMOTE_CROSSREF_OP_FAILED: i32 = 8601; + +// +// MessageId: ERROR_DS_CROSS_REF_BUSY +// +// MessageText: +// +// A cross reference is in use locally with the same name. +// +pub const ERROR_DS_CROSS_REF_BUSY: i32 = 8602; + +// +// MessageId: ERROR_DS_CANT_DERIVE_SPN_FOR_DELETED_DOMAIN +// +// MessageText: +// +// The DS cannot derive a service principal name (SPN) with which to mutually authenticate the target server because the server's domain has been deleted from the forest. +// +pub const ERROR_DS_CANT_DERIVE_SPN_FOR_DELETED_DOMAIN: i32 = 8603; + +// +// MessageId: ERROR_DS_CANT_DEMOTE_WITH_WRITEABLE_NC +// +// MessageText: +// +// Writeable NCs prevent this DC from demoting. +// +pub const ERROR_DS_CANT_DEMOTE_WITH_WRITEABLE_NC: i32 = 8604; + +// +// MessageId: ERROR_DS_DUPLICATE_ID_FOUND +// +// MessageText: +// +// The requested object has a non-unique identifier and cannot be retrieved. +// +pub const ERROR_DS_DUPLICATE_ID_FOUND: i32 = 8605; + +// +// MessageId: ERROR_DS_INSUFFICIENT_ATTR_TO_CREATE_OBJECT +// +// MessageText: +// +// Insufficient attributes were given to create an object. This object may not exist because it may have been deleted and already garbage collected. +// +pub const ERROR_DS_INSUFFICIENT_ATTR_TO_CREATE_OBJECT: i32 = 8606; + +// +// MessageId: ERROR_DS_GROUP_CONVERSION_ERROR +// +// MessageText: +// +// The group cannot be converted due to attribute restrictions on the requested group type. +// +pub const ERROR_DS_GROUP_CONVERSION_ERROR: i32 = 8607; + +// +// MessageId: ERROR_DS_CANT_MOVE_APP_BASIC_GROUP +// +// MessageText: +// +// Cross-domain move of non-empty basic application groups is not allowed. +// +pub const ERROR_DS_CANT_MOVE_APP_BASIC_GROUP: i32 = 8608; + +// +// MessageId: ERROR_DS_CANT_MOVE_APP_QUERY_GROUP +// +// MessageText: +// +// Cross-domain move of non-empty query based application groups is not allowed. +// +pub const ERROR_DS_CANT_MOVE_APP_QUERY_GROUP: i32 = 8609; + +// +// MessageId: ERROR_DS_ROLE_NOT_VERIFIED +// +// MessageText: +// +// The FSMO role ownership could not be verified because its directory partition has not replicated successfully with atleast one replication partner. +// +pub const ERROR_DS_ROLE_NOT_VERIFIED: i32 = 8610; + +// +// MessageId: ERROR_DS_WKO_CONTAINER_CANNOT_BE_SPECIAL +// +// MessageText: +// +// The target container for a redirection of a well known object container cannot already be a special container. +// +pub const ERROR_DS_WKO_CONTAINER_CANNOT_BE_SPECIAL: i32 = 8611; + +// +// MessageId: ERROR_DS_DOMAIN_RENAME_IN_PROGRESS +// +// MessageText: +// +// The Directory Service cannot perform the requested operation because a domain rename operation is in progress. +// +pub const ERROR_DS_DOMAIN_RENAME_IN_PROGRESS: i32 = 8612; + +// +// MessageId: ERROR_DS_EXISTING_AD_CHILD_NC +// +// MessageText: +// +// The Active Directory detected an Active Directory child partition below the +// requested new partition name. The Active Directory's partition hierarchy must +// be created in a top down method. +// +pub const ERROR_DS_EXISTING_AD_CHILD_NC: i32 = 8613; + +// +// MessageId: ERROR_DS_REPL_LIFETIME_EXCEEDED +// +// MessageText: +// +// The Active Directory cannot replicate with this server because the time since the last replication with this server has exceeded the tombstone lifetime. +// +pub const ERROR_DS_REPL_LIFETIME_EXCEEDED: i32 = 8614; + +// +// MessageId: ERROR_DS_DISALLOWED_IN_SYSTEM_CONTAINER +// +// MessageText: +// +// The requested operation is not allowed on an object under the system container. +// +pub const ERROR_DS_DISALLOWED_IN_SYSTEM_CONTAINER: i32 = 8615; + +// +// MessageId: ERROR_DS_LDAP_SEND_QUEUE_FULL +// +// MessageText: +// +// The LDAP servers network send queue has filled up because the client is not +// processing the results of it's requests fast enough. No more requests will +// be processed until the client catches up. If the client does not catch up +// then it will be disconnected. +// +pub const ERROR_DS_LDAP_SEND_QUEUE_FULL: i32 = 8616; + +// +// MessageId: ERROR_DS_DRA_OUT_SCHEDULE_WINDOW +// +// MessageText: +// +// The scheduled replication did not take place because the system was too busy to execute the request within the schedule window. The replication queue is overloaded. Consider reducing the number of partners or decreasing the scheduled replication frequency. +// +pub const ERROR_DS_DRA_OUT_SCHEDULE_WINDOW: i32 = 8617; + +/////////////////////////////////////////////////// +// / +// End of Active Directory Error Codes / +// / +// 8000 to 8999 / +/////////////////////////////////////////////////// + +/////////////////////////////////////////////////// +// // +// DNS Error Codes // +// // +// 9000 to 9999 // +/////////////////////////////////////////////////// + +// ============================= +// Facility DNS Error Messages +// ============================= + +// +// DNS response codes. +// + +pub const DNS_ERROR_RESPONSE_CODES_BASE: i32 = 9000; + +// DNS_ERROR_RCODE_FORMAT_ERROR 0x00002329 +// +// MessageId: DNS_ERROR_RCODE_FORMAT_ERROR +// +// MessageText: +// +// DNS server unable to interpret format. +// +pub const DNS_ERROR_RCODE_FORMAT_ERROR: i32 = 9001; + +// DNS_ERROR_RCODE_SERVER_FAILURE 0x0000232a +// +// MessageId: DNS_ERROR_RCODE_SERVER_FAILURE +// +// MessageText: +// +// DNS server failure. +// +pub const DNS_ERROR_RCODE_SERVER_FAILURE: i32 = 9002; + +// DNS_ERROR_RCODE_NAME_ERROR 0x0000232b +// +// MessageId: DNS_ERROR_RCODE_NAME_ERROR +// +// MessageText: +// +// DNS name does not exist. +// +pub const DNS_ERROR_RCODE_NAME_ERROR: i32 = 9003; + +// DNS_ERROR_RCODE_NOT_IMPLEMENTED 0x0000232c +// +// MessageId: DNS_ERROR_RCODE_NOT_IMPLEMENTED +// +// MessageText: +// +// DNS request not supported by name server. +// +pub const DNS_ERROR_RCODE_NOT_IMPLEMENTED: i32 = 9004; + +// DNS_ERROR_RCODE_REFUSED 0x0000232d +// +// MessageId: DNS_ERROR_RCODE_REFUSED +// +// MessageText: +// +// DNS operation refused. +// +pub const DNS_ERROR_RCODE_REFUSED: i32 = 9005; + +// DNS_ERROR_RCODE_YXDOMAIN 0x0000232e +// +// MessageId: DNS_ERROR_RCODE_YXDOMAIN +// +// MessageText: +// +// DNS name that ought not exist, does exist. +// +pub const DNS_ERROR_RCODE_YXDOMAIN: i32 = 9006; + +// DNS_ERROR_RCODE_YXRRSET 0x0000232f +// +// MessageId: DNS_ERROR_RCODE_YXRRSET +// +// MessageText: +// +// DNS RR set that ought not exist, does exist. +// +pub const DNS_ERROR_RCODE_YXRRSET: i32 = 9007; + +// DNS_ERROR_RCODE_NXRRSET 0x00002330 +// +// MessageId: DNS_ERROR_RCODE_NXRRSET +// +// MessageText: +// +// DNS RR set that ought to exist, does not exist. +// +pub const DNS_ERROR_RCODE_NXRRSET: i32 = 9008; + +// DNS_ERROR_RCODE_NOTAUTH 0x00002331 +// +// MessageId: DNS_ERROR_RCODE_NOTAUTH +// +// MessageText: +// +// DNS server not authoritative for zone. +// +pub const DNS_ERROR_RCODE_NOTAUTH: i32 = 9009; + +// DNS_ERROR_RCODE_NOTZONE 0x00002332 +// +// MessageId: DNS_ERROR_RCODE_NOTZONE +// +// MessageText: +// +// DNS name in update or prereq is not in zone. +// +pub const DNS_ERROR_RCODE_NOTZONE: i32 = 9010; + +// DNS_ERROR_RCODE_BADSIG 0x00002338 +// +// MessageId: DNS_ERROR_RCODE_BADSIG +// +// MessageText: +// +// DNS signature failed to verify. +// +pub const DNS_ERROR_RCODE_BADSIG: i32 = 9016; + +// DNS_ERROR_RCODE_BADKEY 0x00002339 +// +// MessageId: DNS_ERROR_RCODE_BADKEY +// +// MessageText: +// +// DNS bad key. +// +pub const DNS_ERROR_RCODE_BADKEY: i32 = 9017; + +// DNS_ERROR_RCODE_BADTIME 0x0000233a +// +// MessageId: DNS_ERROR_RCODE_BADTIME +// +// MessageText: +// +// DNS signature validity expired. +// +pub const DNS_ERROR_RCODE_BADTIME: i32 = 9018; + +// +// Packet format +// + +pub const DNS_ERROR_PACKET_FMT_BASE: i32 = 9500; + +// DNS_INFO_NO_RECORDS 0x0000251d +// +// MessageId: DNS_INFO_NO_RECORDS +// +// MessageText: +// +// No records found for given DNS query. +// +pub const DNS_INFO_NO_RECORDS: i32 = 9501; + +// DNS_ERROR_BAD_PACKET 0x0000251e +// +// MessageId: DNS_ERROR_BAD_PACKET +// +// MessageText: +// +// Bad DNS packet. +// +pub const DNS_ERROR_BAD_PACKET: i32 = 9502; + +// DNS_ERROR_NO_PACKET 0x0000251f +// +// MessageId: DNS_ERROR_NO_PACKET +// +// MessageText: +// +// No DNS packet. +// +pub const DNS_ERROR_NO_PACKET: i32 = 9503; + +// DNS_ERROR_RCODE 0x00002520 +// +// MessageId: DNS_ERROR_RCODE +// +// MessageText: +// +// DNS error, check rcode. +// +pub const DNS_ERROR_RCODE: i32 = 9504; + +// DNS_ERROR_UNSECURE_PACKET 0x00002521 +// +// MessageId: DNS_ERROR_UNSECURE_PACKET +// +// MessageText: +// +// Unsecured DNS packet. +// +pub const DNS_ERROR_UNSECURE_PACKET: i32 = 9505; + +// +// General API errors +// + +// DNS_ERROR_INVALID_TYPE 0x0000254f +// +// MessageId: DNS_ERROR_INVALID_TYPE +// +// MessageText: +// +// Invalid DNS type. +// +pub const DNS_ERROR_INVALID_TYPE: i32 = 9551; + +// DNS_ERROR_INVALID_IP_ADDRESS 0x00002550 +// +// MessageId: DNS_ERROR_INVALID_IP_ADDRESS +// +// MessageText: +// +// Invalid IP address. +// +pub const DNS_ERROR_INVALID_IP_ADDRESS: i32 = 9552; + +// DNS_ERROR_INVALID_PROPERTY 0x00002551 +// +// MessageId: DNS_ERROR_INVALID_PROPERTY +// +// MessageText: +// +// Invalid property. +// +pub const DNS_ERROR_INVALID_PROPERTY: i32 = 9553; + +// DNS_ERROR_TRY_AGAIN_LATER 0x00002552 +// +// MessageId: DNS_ERROR_TRY_AGAIN_LATER +// +// MessageText: +// +// Try DNS operation again later. +// +pub const DNS_ERROR_TRY_AGAIN_LATER: i32 = 9554; + +// DNS_ERROR_NOT_UNIQUE 0x00002553 +// +// MessageId: DNS_ERROR_NOT_UNIQUE +// +// MessageText: +// +// Record for given name and type is not unique. +// +pub const DNS_ERROR_NOT_UNIQUE: i32 = 9555; + +// DNS_ERROR_NON_RFC_NAME 0x00002554 +// +// MessageId: DNS_ERROR_NON_RFC_NAME +// +// MessageText: +// +// DNS name does not comply with RFC specifications. +// +pub const DNS_ERROR_NON_RFC_NAME: i32 = 9556; + +// DNS_STATUS_FQDN 0x00002555 +// +// MessageId: DNS_STATUS_FQDN +// +// MessageText: +// +// DNS name is a fully-qualified DNS name. +// +pub const DNS_STATUS_FQDN: i32 = 9557; + +// DNS_STATUS_DOTTED_NAME 0x00002556 +// +// MessageId: DNS_STATUS_DOTTED_NAME +// +// MessageText: +// +// DNS name is dotted (multi-label). +// +pub const DNS_STATUS_DOTTED_NAME: i32 = 9558; + +// DNS_STATUS_SINGLE_PART_NAME 0x00002557 +// +// MessageId: DNS_STATUS_SINGLE_PART_NAME +// +// MessageText: +// +// DNS name is a single-part name. +// +pub const DNS_STATUS_SINGLE_PART_NAME: i32 = 9559; + +// DNS_ERROR_INVALID_NAME_CHAR 0x00002558 +// +// MessageId: DNS_ERROR_INVALID_NAME_CHAR +// +// MessageText: +// +// DNS name contains an invalid character. +// +pub const DNS_ERROR_INVALID_NAME_CHAR: i32 = 9560; + +// DNS_ERROR_NUMERIC_NAME 0x00002559 +// +// MessageId: DNS_ERROR_NUMERIC_NAME +// +// MessageText: +// +// DNS name is entirely numeric. +// +pub const DNS_ERROR_NUMERIC_NAME: i32 = 9561; + +// DNS_ERROR_NOT_ALLOWED_ON_ROOT_SERVER 0x0000255A +// +// MessageId: DNS_ERROR_NOT_ALLOWED_ON_ROOT_SERVER +// +// MessageText: +// +// The operation requested is not permitted on a DNS root server. +// +pub const DNS_ERROR_NOT_ALLOWED_ON_ROOT_SERVER: i32 = 9562; + +// DNS_ERROR_NOT_ALLOWED_UNDER_DELEGATION 0x0000255B +// +// MessageId: DNS_ERROR_NOT_ALLOWED_UNDER_DELEGATION +// +// MessageText: +// +// The record could not be created because this part of the DNS namespace has +// been delegated to another server. +// +pub const DNS_ERROR_NOT_ALLOWED_UNDER_DELEGATION: i32 = 9563; + +// DNS_ERROR_CANNOT_FIND_ROOT_HINTS 0x0000255C +// +// MessageId: DNS_ERROR_CANNOT_FIND_ROOT_HINTS +// +// MessageText: +// +// The DNS server could not find a set of root hints. +// +pub const DNS_ERROR_CANNOT_FIND_ROOT_HINTS: i32 = 9564; + +// DNS_ERROR_INCONSISTENT_ROOT_HINTS 0x0000255D +// +// MessageId: DNS_ERROR_INCONSISTENT_ROOT_HINTS +// +// MessageText: +// +// The DNS server found root hints but they were not consistent across +// all adapters. +// +pub const DNS_ERROR_INCONSISTENT_ROOT_HINTS: i32 = 9565; + +// +// Zone errors +// + +pub const DNS_ERROR_ZONE_BASE: i32 = 9600; + +// DNS_ERROR_ZONE_DOES_NOT_EXIST 0x00002581 +// +// MessageId: DNS_ERROR_ZONE_DOES_NOT_EXIST +// +// MessageText: +// +// DNS zone does not exist. +// +pub const DNS_ERROR_ZONE_DOES_NOT_EXIST: i32 = 9601; + +// DNS_ERROR_NO_ZONE_INFO 0x00002582 +// +// MessageId: DNS_ERROR_NO_ZONE_INFO +// +// MessageText: +// +// DNS zone information not available. +// +pub const DNS_ERROR_NO_ZONE_INFO: i32 = 9602; + +// DNS_ERROR_INVALID_ZONE_OPERATION 0x00002583 +// +// MessageId: DNS_ERROR_INVALID_ZONE_OPERATION +// +// MessageText: +// +// Invalid operation for DNS zone. +// +pub const DNS_ERROR_INVALID_ZONE_OPERATION: i32 = 9603; + +// DNS_ERROR_ZONE_CONFIGURATION_ERROR 0x00002584 +// +// MessageId: DNS_ERROR_ZONE_CONFIGURATION_ERROR +// +// MessageText: +// +// Invalid DNS zone configuration. +// +pub const DNS_ERROR_ZONE_CONFIGURATION_ERROR: i32 = 9604; + +// DNS_ERROR_ZONE_HAS_NO_SOA_RECORD 0x00002585 +// +// MessageId: DNS_ERROR_ZONE_HAS_NO_SOA_RECORD +// +// MessageText: +// +// DNS zone has no start of authority (SOA) record. +// +pub const DNS_ERROR_ZONE_HAS_NO_SOA_RECORD: i32 = 9605; + +// DNS_ERROR_ZONE_HAS_NO_NS_RECORDS 0x00002586 +// +// MessageId: DNS_ERROR_ZONE_HAS_NO_NS_RECORDS +// +// MessageText: +// +// DNS zone has no Name Server (NS) record. +// +pub const DNS_ERROR_ZONE_HAS_NO_NS_RECORDS: i32 = 9606; + +// DNS_ERROR_ZONE_LOCKED 0x00002587 +// +// MessageId: DNS_ERROR_ZONE_LOCKED +// +// MessageText: +// +// DNS zone is locked. +// +pub const DNS_ERROR_ZONE_LOCKED: i32 = 9607; + +// DNS_ERROR_ZONE_CREATION_FAILED 0x00002588 +// +// MessageId: DNS_ERROR_ZONE_CREATION_FAILED +// +// MessageText: +// +// DNS zone creation failed. +// +pub const DNS_ERROR_ZONE_CREATION_FAILED: i32 = 9608; + +// DNS_ERROR_ZONE_ALREADY_EXISTS 0x00002589 +// +// MessageId: DNS_ERROR_ZONE_ALREADY_EXISTS +// +// MessageText: +// +// DNS zone already exists. +// +pub const DNS_ERROR_ZONE_ALREADY_EXISTS: i32 = 9609; + +// DNS_ERROR_AUTOZONE_ALREADY_EXISTS 0x0000258a +// +// MessageId: DNS_ERROR_AUTOZONE_ALREADY_EXISTS +// +// MessageText: +// +// DNS automatic zone already exists. +// +pub const DNS_ERROR_AUTOZONE_ALREADY_EXISTS: i32 = 9610; + +// DNS_ERROR_INVALID_ZONE_TYPE 0x0000258b +// +// MessageId: DNS_ERROR_INVALID_ZONE_TYPE +// +// MessageText: +// +// Invalid DNS zone type. +// +pub const DNS_ERROR_INVALID_ZONE_TYPE: i32 = 9611; + +// DNS_ERROR_SECONDARY_REQUIRES_MASTER_IP 0x0000258c +// +// MessageId: DNS_ERROR_SECONDARY_REQUIRES_MASTER_IP +// +// MessageText: +// +// Secondary DNS zone requires master IP address. +// +pub const DNS_ERROR_SECONDARY_REQUIRES_MASTER_IP: i32 = 9612; + +// DNS_ERROR_ZONE_NOT_SECONDARY 0x0000258d +// +// MessageId: DNS_ERROR_ZONE_NOT_SECONDARY +// +// MessageText: +// +// DNS zone not secondary. +// +pub const DNS_ERROR_ZONE_NOT_SECONDARY: i32 = 9613; + +// DNS_ERROR_NEED_SECONDARY_ADDRESSES 0x0000258e +// +// MessageId: DNS_ERROR_NEED_SECONDARY_ADDRESSES +// +// MessageText: +// +// Need secondary IP address. +// +pub const DNS_ERROR_NEED_SECONDARY_ADDRESSES: i32 = 9614; + +// DNS_ERROR_WINS_INIT_FAILED 0x0000258f +// +// MessageId: DNS_ERROR_WINS_INIT_FAILED +// +// MessageText: +// +// WINS initialization failed. +// +pub const DNS_ERROR_WINS_INIT_FAILED: i32 = 9615; + +// DNS_ERROR_NEED_WINS_SERVERS 0x00002590 +// +// MessageId: DNS_ERROR_NEED_WINS_SERVERS +// +// MessageText: +// +// Need WINS servers. +// +pub const DNS_ERROR_NEED_WINS_SERVERS: i32 = 9616; + +// DNS_ERROR_NBSTAT_INIT_FAILED 0x00002591 +// +// MessageId: DNS_ERROR_NBSTAT_INIT_FAILED +// +// MessageText: +// +// NBTSTAT initialization call failed. +// +pub const DNS_ERROR_NBSTAT_INIT_FAILED: i32 = 9617; + +// DNS_ERROR_SOA_DELETE_INVALID 0x00002592 +// +// MessageId: DNS_ERROR_SOA_DELETE_INVALID +// +// MessageText: +// +// Invalid delete of start of authority (SOA) +// +pub const DNS_ERROR_SOA_DELETE_INVALID: i32 = 9618; + +// DNS_ERROR_FORWARDER_ALREADY_EXISTS 0x00002593 +// +// MessageId: DNS_ERROR_FORWARDER_ALREADY_EXISTS +// +// MessageText: +// +// A conditional forwarding zone already exists for that name. +// +pub const DNS_ERROR_FORWARDER_ALREADY_EXISTS: i32 = 9619; + +// DNS_ERROR_ZONE_REQUIRES_MASTER_IP 0x00002594 +// +// MessageId: DNS_ERROR_ZONE_REQUIRES_MASTER_IP +// +// MessageText: +// +// This zone must be configured with one or more master DNS server IP addresses. +// +pub const DNS_ERROR_ZONE_REQUIRES_MASTER_IP: i32 = 9620; + +// DNS_ERROR_ZONE_IS_SHUTDOWN 0x00002595 +// +// MessageId: DNS_ERROR_ZONE_IS_SHUTDOWN +// +// MessageText: +// +// The operation cannot be performed because this zone is shutdown. +// +pub const DNS_ERROR_ZONE_IS_SHUTDOWN: i32 = 9621; + +// +// Datafile errors +// + +// DNS 0x000025b3 +// +// MessageId: DNS_ERROR_PRIMARY_REQUIRES_DATAFILE +// +// MessageText: +// +// Primary DNS zone requires datafile. +// +pub const DNS_ERROR_PRIMARY_REQUIRES_DATAFILE: i32 = 9651; + +// DNS 0x000025b4 +// +// MessageId: DNS_ERROR_INVALID_DATAFILE_NAME +// +// MessageText: +// +// Invalid datafile name for DNS zone. +// +pub const DNS_ERROR_INVALID_DATAFILE_NAME: i32 = 9652; + +// DNS 0x000025b5 +// +// MessageId: DNS_ERROR_DATAFILE_OPEN_FAILURE +// +// MessageText: +// +// Failed to open datafile for DNS zone. +// +pub const DNS_ERROR_DATAFILE_OPEN_FAILURE: i32 = 9653; + +// DNS 0x000025b6 +// +// MessageId: DNS_ERROR_FILE_WRITEBACK_FAILED +// +// MessageText: +// +// Failed to write datafile for DNS zone. +// +pub const DNS_ERROR_FILE_WRITEBACK_FAILED: i32 = 9654; + +// DNS 0x000025b7 +// +// MessageId: DNS_ERROR_DATAFILE_PARSING +// +// MessageText: +// +// Failure while reading datafile for DNS zone. +// +pub const DNS_ERROR_DATAFILE_PARSING: i32 = 9655; + +// +// Database errors +// + +pub const DNS_ERROR_DATABASE_BASE: i32 = 9700; + +// DNS_ERROR_RECORD_DOES_NOT_EXIST 0x000025e5 +// +// MessageId: DNS_ERROR_RECORD_DOES_NOT_EXIST +// +// MessageText: +// +// DNS record does not exist. +// +pub const DNS_ERROR_RECORD_DOES_NOT_EXIST: i32 = 9701; + +// DNS_ERROR_RECORD_FORMAT 0x000025e6 +// +// MessageId: DNS_ERROR_RECORD_FORMAT +// +// MessageText: +// +// DNS record format error. +// +pub const DNS_ERROR_RECORD_FORMAT: i32 = 9702; + +// DNS_ERROR_NODE_CREATION_FAILED 0x000025e7 +// +// MessageId: DNS_ERROR_NODE_CREATION_FAILED +// +// MessageText: +// +// Node creation failure in DNS. +// +pub const DNS_ERROR_NODE_CREATION_FAILED: i32 = 9703; + +// DNS_ERROR_UNKNOWN_RECORD_TYPE 0x000025e8 +// +// MessageId: DNS_ERROR_UNKNOWN_RECORD_TYPE +// +// MessageText: +// +// Unknown DNS record type. +// +pub const DNS_ERROR_UNKNOWN_RECORD_TYPE: i32 = 9704; + +// DNS_ERROR_RECORD_TIMED_OUT 0x000025e9 +// +// MessageId: DNS_ERROR_RECORD_TIMED_OUT +// +// MessageText: +// +// DNS record timed out. +// +pub const DNS_ERROR_RECORD_TIMED_OUT: i32 = 9705; + +// DNS_ERROR_NAME_NOT_IN_ZONE 0x000025ea +// +// MessageId: DNS_ERROR_NAME_NOT_IN_ZONE +// +// MessageText: +// +// Name not in DNS zone. +// +pub const DNS_ERROR_NAME_NOT_IN_ZONE: i32 = 9706; + +// DNS_ERROR_CNAME_LOOP 0x000025eb +// +// MessageId: DNS_ERROR_CNAME_LOOP +// +// MessageText: +// +// CNAME loop detected. +// +pub const DNS_ERROR_CNAME_LOOP: i32 = 9707; + +// DNS_ERROR_NODE_IS_CNAME 0x000025ec +// +// MessageId: DNS_ERROR_NODE_IS_CNAME +// +// MessageText: +// +// Node is a CNAME DNS record. +// +pub const DNS_ERROR_NODE_IS_CNAME: i32 = 9708; + +// DNS_ERROR_CNAME_COLLISION 0x000025ed +// +// MessageId: DNS_ERROR_CNAME_COLLISION +// +// MessageText: +// +// A CNAME record already exists for given name. +// +pub const DNS_ERROR_CNAME_COLLISION: i32 = 9709; + +// DNS_ERROR_RECORD_ONLY_AT_ZONE_ROOT 0x000025ee +// +// MessageId: DNS_ERROR_RECORD_ONLY_AT_ZONE_ROOT +// +// MessageText: +// +// Record only at DNS zone root. +// +pub const DNS_ERROR_RECORD_ONLY_AT_ZONE_ROOT: i32 = 9710; + +// DNS_ERROR_RECORD_ALREADY_EXISTS 0x000025ef +// +// MessageId: DNS_ERROR_RECORD_ALREADY_EXISTS +// +// MessageText: +// +// DNS record already exists. +// +pub const DNS_ERROR_RECORD_ALREADY_EXISTS: i32 = 9711; + +// DNS_ERROR_SECONDARY_DATA 0x000025f0 +// +// MessageId: DNS_ERROR_SECONDARY_DATA +// +// MessageText: +// +// Secondary DNS zone data error. +// +pub const DNS_ERROR_SECONDARY_DATA: i32 = 9712; + +// DNS_ERROR_NO_CREATE_CACHE_DATA 0x000025f1 +// +// MessageId: DNS_ERROR_NO_CREATE_CACHE_DATA +// +// MessageText: +// +// Could not create DNS cache data. +// +pub const DNS_ERROR_NO_CREATE_CACHE_DATA: i32 = 9713; + +// DNS_ERROR_NAME_DOES_NOT_EXIST 0x000025f2 +// +// MessageId: DNS_ERROR_NAME_DOES_NOT_EXIST +// +// MessageText: +// +// DNS name does not exist. +// +pub const DNS_ERROR_NAME_DOES_NOT_EXIST: i32 = 9714; + +// DNS_WARNING_PTR_CREATE_FAILED 0x000025f3 +// +// MessageId: DNS_WARNING_PTR_CREATE_FAILED +// +// MessageText: +// +// Could not create pointer (PTR) record. +// +pub const DNS_WARNING_PTR_CREATE_FAILED: i32 = 9715; + +// DNS_WARNING_DOMAIN_UNDELETED 0x000025f4 +// +// MessageId: DNS_WARNING_DOMAIN_UNDELETED +// +// MessageText: +// +// DNS domain was undeleted. +// +pub const DNS_WARNING_DOMAIN_UNDELETED: i32 = 9716; + +// DNS_ERROR_DS_UNAVAILABLE 0x000025f5 +// +// MessageId: DNS_ERROR_DS_UNAVAILABLE +// +// MessageText: +// +// The directory service is unavailable. +// +pub const DNS_ERROR_DS_UNAVAILABLE: i32 = 9717; + +// DNS_ERROR_DS_ZONE_ALREADY_EXISTS 0x000025f6 +// +// MessageId: DNS_ERROR_DS_ZONE_ALREADY_EXISTS +// +// MessageText: +// +// DNS zone already exists in the directory service. +// +pub const DNS_ERROR_DS_ZONE_ALREADY_EXISTS: i32 = 9718; + +// DNS_ERROR_NO_BOOTFILE_IF_DS_ZONE 0x000025f7 +// +// MessageId: DNS_ERROR_NO_BOOTFILE_IF_DS_ZONE +// +// MessageText: +// +// DNS server not creating or reading the boot file for the directory service integrated DNS zone. +// +pub const DNS_ERROR_NO_BOOTFILE_IF_DS_ZONE: i32 = 9719; + +// +// Operation errors +// + +pub const DNS_ERROR_OPERATION_BASE: i32 = 9750; + +// DNS_INFO_AXFR_COMPLETE 0x00002617 +// +// MessageId: DNS_INFO_AXFR_COMPLETE +// +// MessageText: +// +// DNS AXFR (zone transfer) complete. +// +pub const DNS_INFO_AXFR_COMPLETE: i32 = 9751; + +// DNS_ERROR_AXFR 0x00002618 +// +// MessageId: DNS_ERROR_AXFR +// +// MessageText: +// +// DNS zone transfer failed. +// +pub const DNS_ERROR_AXFR: i32 = 9752; + +// DNS_INFO_ADDED_LOCAL_WINS 0x00002619 +// +// MessageId: DNS_INFO_ADDED_LOCAL_WINS +// +// MessageText: +// +// Added local WINS server. +// +pub const DNS_INFO_ADDED_LOCAL_WINS: i32 = 9753; + +// +// Secure update +// + +pub const DNS_ERROR_SECURE_BASE: i32 = 9800; + +// DNS_STATUS_CONTINUE_NEEDED 0x00002649 +// +// MessageId: DNS_STATUS_CONTINUE_NEEDED +// +// MessageText: +// +// Secure update call needs to continue update request. +// +pub const DNS_STATUS_CONTINUE_NEEDED: i32 = 9801; + +// +// Setup errors +// + +pub const DNS_ERROR_SETUP_BASE: i32 = 9850; + +// DNS_ERROR_NO_TCPIP 0x0000267b +// +// MessageId: DNS_ERROR_NO_TCPIP +// +// MessageText: +// +// TCP/IP network protocol not installed. +// +pub const DNS_ERROR_NO_TCPIP: i32 = 9851; + +// DNS_ERROR_NO_DNS_SERVERS 0x0000267c +// +// MessageId: DNS_ERROR_NO_DNS_SERVERS +// +// MessageText: +// +// No DNS servers configured for local system. +// +pub const DNS_ERROR_NO_DNS_SERVERS: i32 = 9852; + +// +// Directory partition (DP) errors +// + +pub const DNS_ERROR_DP_BASE: i32 = 9900; + +// DNS_ERROR_DP_DOES_NOT_EXIST 0x000026ad +// +// MessageId: DNS_ERROR_DP_DOES_NOT_EXIST +// +// MessageText: +// +// The specified directory partition does not exist. +// +pub const DNS_ERROR_DP_DOES_NOT_EXIST: i32 = 9901; + +// DNS_ERROR_DP_ALREADY_EXISTS 0x000026ae +// +// MessageId: DNS_ERROR_DP_ALREADY_EXISTS +// +// MessageText: +// +// The specified directory partition already exists. +// +pub const DNS_ERROR_DP_ALREADY_EXISTS: i32 = 9902; + +// DNS_ERROR_DP_NOT_ENLISTED 0x000026af +// +// MessageId: DNS_ERROR_DP_NOT_ENLISTED +// +// MessageText: +// +// This DNS server is not enlisted in the specified directory partition. +// +pub const DNS_ERROR_DP_NOT_ENLISTED: i32 = 9903; + +// DNS_ERROR_DP_ALREADY_ENLISTED 0x000026b0 +// +// MessageId: DNS_ERROR_DP_ALREADY_ENLISTED +// +// MessageText: +// +// This DNS server is already enlisted in the specified directory partition. +// +pub const DNS_ERROR_DP_ALREADY_ENLISTED: i32 = 9904; + +// DNS_ERROR_DP_NOT_AVAILABLE 0x000026b1 +// +// MessageId: DNS_ERROR_DP_NOT_AVAILABLE +// +// MessageText: +// +// The directory partition is not available at this time. Please wait +// a few minutes and try again. +// +pub const DNS_ERROR_DP_NOT_AVAILABLE: i32 = 9905; + +// DNS_ERROR_DP_FSMO_ERROR 0x000026b2 +// +// MessageId: DNS_ERROR_DP_FSMO_ERROR +// +// MessageText: +// +// The application directory partition operation failed. The domain controller +// holding the domain naming master role is down or unable to service the +// request or is not running Windows Server 2003. +// +pub const DNS_ERROR_DP_FSMO_ERROR: i32 = 9906; + +/////////////////////////////////////////////////// +// // +// End of DNS Error Codes // +// // +// 9000 to 9999 // +/////////////////////////////////////////////////// + +/////////////////////////////////////////////////// +// // +// WinSock Error Codes // +// // +// 10000 to 11999 // +/////////////////////////////////////////////////// + +// +// WinSock error codes are also defined in WinSock.h +// and WinSock2.h, hence the IFDEF +// +pub const WSABASEERR: i32 = 10000; +// +// MessageId: WSAEINTR +// +// MessageText: +// +// A blocking operation was interrupted by a call to WSACancelBlockingCall. +// +pub const WSAEINTR: i32 = 10004; + +// +// MessageId: WSAEBADF +// +// MessageText: +// +// The file handle supplied is not valid. +// +pub const WSAEBADF: i32 = 10009; + +// +// MessageId: WSAEACCES +// +// MessageText: +// +// An attempt was made to access a socket in a way forbidden by its access permissions. +// +pub const WSAEACCES: i32 = 10013; + +// +// MessageId: WSAEFAULT +// +// MessageText: +// +// The system detected an invalid pointer address in attempting to use a pointer argument in a call. +// +pub const WSAEFAULT: i32 = 10014; + +// +// MessageId: WSAEINVAL +// +// MessageText: +// +// An invalid argument was supplied. +// +pub const WSAEINVAL: i32 = 10022; + +// +// MessageId: WSAEMFILE +// +// MessageText: +// +// Too many open sockets. +// +pub const WSAEMFILE: i32 = 10024; + +// +// MessageId: WSAEWOULDBLOCK +// +// MessageText: +// +// A non-blocking socket operation could not be completed immediately. +// +pub const WSAEWOULDBLOCK: i32 = 10035; + +// +// MessageId: WSAEINPROGRESS +// +// MessageText: +// +// A blocking operation is currently executing. +// +pub const WSAEINPROGRESS: i32 = 10036; + +// +// MessageId: WSAEALREADY +// +// MessageText: +// +// An operation was attempted on a non-blocking socket that already had an operation in progress. +// +pub const WSAEALREADY: i32 = 10037; + +// +// MessageId: WSAENOTSOCK +// +// MessageText: +// +// An operation was attempted on something that is not a socket. +// +pub const WSAENOTSOCK: i32 = 10038; + +// +// MessageId: WSAEDESTADDRREQ +// +// MessageText: +// +// A required address was omitted from an operation on a socket. +// +pub const WSAEDESTADDRREQ: i32 = 10039; + +// +// MessageId: WSAEMSGSIZE +// +// MessageText: +// +// A message sent on a datagram socket was larger than the internal message buffer or some other network limit, or the buffer used to receive a datagram into was smaller than the datagram itself. +// +pub const WSAEMSGSIZE: i32 = 10040; + +// +// MessageId: WSAEPROTOTYPE +// +// MessageText: +// +// A protocol was specified in the socket function call that does not support the semantics of the socket type requested. +// +pub const WSAEPROTOTYPE: i32 = 10041; + +// +// MessageId: WSAENOPROTOOPT +// +// MessageText: +// +// An unknown, invalid, or unsupported option or level was specified in a getsockopt or setsockopt call. +// +pub const WSAENOPROTOOPT: i32 = 10042; + +// +// MessageId: WSAEPROTONOSUPPORT +// +// MessageText: +// +// The requested protocol has not been configured into the system, or no implementation for it exists. +// +pub const WSAEPROTONOSUPPORT: i32 = 10043; + +// +// MessageId: WSAESOCKTNOSUPPORT +// +// MessageText: +// +// The support for the specified socket type does not exist in this address family. +// +pub const WSAESOCKTNOSUPPORT: i32 = 10044; + +// +// MessageId: WSAEOPNOTSUPP +// +// MessageText: +// +// The attempted operation is not supported for the type of object referenced. +// +pub const WSAEOPNOTSUPP: i32 = 10045; + +// +// MessageId: WSAEPFNOSUPPORT +// +// MessageText: +// +// The protocol family has not been configured into the system or no implementation for it exists. +// +pub const WSAEPFNOSUPPORT: i32 = 10046; + +// +// MessageId: WSAEAFNOSUPPORT +// +// MessageText: +// +// An address incompatible with the requested protocol was used. +// +pub const WSAEAFNOSUPPORT: i32 = 10047; + +// +// MessageId: WSAEADDRINUSE +// +// MessageText: +// +// Only one usage of each socket address (protocol/network address/port) is normally permitted. +// +pub const WSAEADDRINUSE: i32 = 10048; + +// +// MessageId: WSAEADDRNOTAVAIL +// +// MessageText: +// +// The requested address is not valid in its context. +// +pub const WSAEADDRNOTAVAIL: i32 = 10049; + +// +// MessageId: WSAENETDOWN +// +// MessageText: +// +// A socket operation encountered a dead network. +// +pub const WSAENETDOWN: i32 = 10050; + +// +// MessageId: WSAENETUNREACH +// +// MessageText: +// +// A socket operation was attempted to an unreachable network. +// +pub const WSAENETUNREACH: i32 = 10051; + +// +// MessageId: WSAENETRESET +// +// MessageText: +// +// The connection has been broken due to keep-alive activity detecting a failure while the operation was in progress. +// +pub const WSAENETRESET: i32 = 10052; + +// +// MessageId: WSAECONNABORTED +// +// MessageText: +// +// An established connection was aborted by the software in your host machine. +// +pub const WSAECONNABORTED: i32 = 10053; + +// +// MessageId: WSAECONNRESET +// +// MessageText: +// +// An existing connection was forcibly closed by the remote host. +// +pub const WSAECONNRESET: i32 = 10054; + +// +// MessageId: WSAENOBUFS +// +// MessageText: +// +// An operation on a socket could not be performed because the system lacked sufficient buffer space or because a queue was full. +// +pub const WSAENOBUFS: i32 = 10055; + +// +// MessageId: WSAEISCONN +// +// MessageText: +// +// A connect request was made on an already connected socket. +// +pub const WSAEISCONN: i32 = 10056; + +// +// MessageId: WSAENOTCONN +// +// MessageText: +// +// A request to send or receive data was disallowed because the socket is not connected and (when sending on a datagram socket using a sendto call) no address was supplied. +// +pub const WSAENOTCONN: i32 = 10057; + +// +// MessageId: WSAESHUTDOWN +// +// MessageText: +// +// A request to send or receive data was disallowed because the socket had already been shut down in that direction with a previous shutdown call. +// +pub const WSAESHUTDOWN: i32 = 10058; + +// +// MessageId: WSAETOOMANYREFS +// +// MessageText: +// +// Too many references to some kernel object. +// +pub const WSAETOOMANYREFS: i32 = 10059; + +// +// MessageId: WSAETIMEDOUT +// +// MessageText: +// +// A connection attempt failed because the connected party did not properly respond after a period of time, or established connection failed because connected host has failed to respond. +// +pub const WSAETIMEDOUT: i32 = 10060; + +// +// MessageId: WSAECONNREFUSED +// +// MessageText: +// +// No connection could be made because the target machine actively refused it. +// +pub const WSAECONNREFUSED: i32 = 10061; + +// +// MessageId: WSAELOOP +// +// MessageText: +// +// Cannot translate name. +// +pub const WSAELOOP: i32 = 10062; + +// +// MessageId: WSAENAMETOOLONG +// +// MessageText: +// +// Name component or name was too long. +// +pub const WSAENAMETOOLONG: i32 = 10063; + +// +// MessageId: WSAEHOSTDOWN +// +// MessageText: +// +// A socket operation failed because the destination host was down. +// +pub const WSAEHOSTDOWN: i32 = 10064; + +// +// MessageId: WSAEHOSTUNREACH +// +// MessageText: +// +// A socket operation was attempted to an unreachable host. +// +pub const WSAEHOSTUNREACH: i32 = 10065; + +// +// MessageId: WSAENOTEMPTY +// +// MessageText: +// +// Cannot remove a directory that is not empty. +// +pub const WSAENOTEMPTY: i32 = 10066; + +// +// MessageId: WSAEPROCLIM +// +// MessageText: +// +// A Windows Sockets implementation may have a limit on the number of applications that may use it simultaneously. +// +pub const WSAEPROCLIM: i32 = 10067; + +// +// MessageId: WSAEUSERS +// +// MessageText: +// +// Ran out of quota. +// +pub const WSAEUSERS: i32 = 10068; + +// +// MessageId: WSAEDQUOT +// +// MessageText: +// +// Ran out of disk quota. +// +pub const WSAEDQUOT: i32 = 10069; + +// +// MessageId: WSAESTALE +// +// MessageText: +// +// File handle reference is no longer available. +// +pub const WSAESTALE: i32 = 10070; + +// +// MessageId: WSAEREMOTE +// +// MessageText: +// +// Item is not available locally. +// +pub const WSAEREMOTE: i32 = 10071; + +// +// MessageId: WSASYSNOTREADY +// +// MessageText: +// +// WSAStartup cannot function at this time because the underlying system it uses to provide network services is currently unavailable. +// +pub const WSASYSNOTREADY: i32 = 10091; + +// +// MessageId: WSAVERNOTSUPPORTED +// +// MessageText: +// +// The Windows Sockets version requested is not supported. +// +pub const WSAVERNOTSUPPORTED: i32 = 10092; + +// +// MessageId: WSANOTINITIALISED +// +// MessageText: +// +// Either the application has not called WSAStartup, or WSAStartup failed. +// +pub const WSANOTINITIALISED: i32 = 10093; + +// +// MessageId: WSAEDISCON +// +// MessageText: +// +// Returned by WSARecv or WSARecvFrom to indicate the remote party has initiated a graceful shutdown sequence. +// +pub const WSAEDISCON: i32 = 10101; + +// +// MessageId: WSAENOMORE +// +// MessageText: +// +// No more results can be returned by WSALookupServiceNext. +// +pub const WSAENOMORE: i32 = 10102; + +// +// MessageId: WSAECANCELLED +// +// MessageText: +// +// A call to WSALookupServiceEnd was made while this call was still processing. The call has been canceled. +// +pub const WSAECANCELLED: i32 = 10103; + +// +// MessageId: WSAEINVALIDPROCTABLE +// +// MessageText: +// +// The procedure call table is invalid. +// +pub const WSAEINVALIDPROCTABLE: i32 = 10104; + +// +// MessageId: WSAEINVALIDPROVIDER +// +// MessageText: +// +// The requested service provider is invalid. +// +pub const WSAEINVALIDPROVIDER: i32 = 10105; + +// +// MessageId: WSAEPROVIDERFAILEDINIT +// +// MessageText: +// +// The requested service provider could not be loaded or initialized. +// +pub const WSAEPROVIDERFAILEDINIT: i32 = 10106; + +// +// MessageId: WSASYSCALLFAILURE +// +// MessageText: +// +// A system call that should never fail has failed. +// +pub const WSASYSCALLFAILURE: i32 = 10107; + +// +// MessageId: WSASERVICE_NOT_FOUND +// +// MessageText: +// +// No such service is known. The service cannot be found in the specified name space. +// +pub const WSASERVICE_NOT_FOUND: i32 = 10108; + +// +// MessageId: WSATYPE_NOT_FOUND +// +// MessageText: +// +// The specified class was not found. +// +pub const WSATYPE_NOT_FOUND: i32 = 10109; + +// +// MessageId: WSA_E_NO_MORE +// +// MessageText: +// +// No more results can be returned by WSALookupServiceNext. +// +pub const WSA_E_NO_MORE: i32 = 10110; + +// +// MessageId: WSA_E_CANCELLED +// +// MessageText: +// +// A call to WSALookupServiceEnd was made while this call was still processing. The call has been canceled. +// +pub const WSA_E_CANCELLED: i32 = 10111; + +// +// MessageId: WSAEREFUSED +// +// MessageText: +// +// A database query failed because it was actively refused. +// +pub const WSAEREFUSED: i32 = 10112; + +// +// MessageId: WSAHOST_NOT_FOUND +// +// MessageText: +// +// No such host is known. +// +pub const WSAHOST_NOT_FOUND: i32 = 11001; + +// +// MessageId: WSATRY_AGAIN +// +// MessageText: +// +// This is usually a temporary error during hostname resolution and means that the local server did not receive a response from an authoritative server. +// +pub const WSATRY_AGAIN: i32 = 11002; + +// +// MessageId: WSANO_RECOVERY +// +// MessageText: +// +// A non-recoverable error occurred during a database lookup. +// +pub const WSANO_RECOVERY: i32 = 11003; + +// +// MessageId: WSANO_DATA +// +// MessageText: +// +// The requested name is valid, but no data of the requested type was found. +// +pub const WSANO_DATA: i32 = 11004; + +// +// MessageId: WSA_QOS_RECEIVERS +// +// MessageText: +// +// At least one reserve has arrived. +// +pub const WSA_QOS_RECEIVERS: i32 = 11005; + +// +// MessageId: WSA_QOS_SENDERS +// +// MessageText: +// +// At least one path has arrived. +// +pub const WSA_QOS_SENDERS: i32 = 11006; + +// +// MessageId: WSA_QOS_NO_SENDERS +// +// MessageText: +// +// There are no senders. +// +pub const WSA_QOS_NO_SENDERS: i32 = 11007; + +// +// MessageId: WSA_QOS_NO_RECEIVERS +// +// MessageText: +// +// There are no receivers. +// +pub const WSA_QOS_NO_RECEIVERS: i32 = 11008; + +// +// MessageId: WSA_QOS_REQUEST_CONFIRMED +// +// MessageText: +// +// Reserve has been confirmed. +// +pub const WSA_QOS_REQUEST_CONFIRMED: i32 = 11009; + +// +// MessageId: WSA_QOS_ADMISSION_FAILURE +// +// MessageText: +// +// Error due to lack of resources. +// +pub const WSA_QOS_ADMISSION_FAILURE: i32 = 11010; + +// +// MessageId: WSA_QOS_POLICY_FAILURE +// +// MessageText: +// +// Rejected for administrative reasons - bad credentials. +// +pub const WSA_QOS_POLICY_FAILURE: i32 = 11011; + +// +// MessageId: WSA_QOS_BAD_STYLE +// +// MessageText: +// +// Unknown or conflicting style. +// +pub const WSA_QOS_BAD_STYLE: i32 = 11012; + +// +// MessageId: WSA_QOS_BAD_OBJECT +// +// MessageText: +// +// Problem with some part of the filterspec or providerspecific buffer in general. +// +pub const WSA_QOS_BAD_OBJECT: i32 = 11013; + +// +// MessageId: WSA_QOS_TRAFFIC_CTRL_ERROR +// +// MessageText: +// +// Problem with some part of the flowspec. +// +pub const WSA_QOS_TRAFFIC_CTRL_ERROR: i32 = 11014; + +// +// MessageId: WSA_QOS_GENERIC_ERROR +// +// MessageText: +// +// General QOS error. +// +pub const WSA_QOS_GENERIC_ERROR: i32 = 11015; + +// +// MessageId: WSA_QOS_ESERVICETYPE +// +// MessageText: +// +// An invalid or unrecognized service type was found in the flowspec. +// +pub const WSA_QOS_ESERVICETYPE: i32 = 11016; + +// +// MessageId: WSA_QOS_EFLOWSPEC +// +// MessageText: +// +// An invalid or inconsistent flowspec was found in the QOS structure. +// +pub const WSA_QOS_EFLOWSPEC: i32 = 11017; + +// +// MessageId: WSA_QOS_EPROVSPECBUF +// +// MessageText: +// +// Invalid QOS provider-specific buffer. +// +pub const WSA_QOS_EPROVSPECBUF: i32 = 11018; + +// +// MessageId: WSA_QOS_EFILTERSTYLE +// +// MessageText: +// +// An invalid QOS filter style was used. +// +pub const WSA_QOS_EFILTERSTYLE: i32 = 11019; + +// +// MessageId: WSA_QOS_EFILTERTYPE +// +// MessageText: +// +// An invalid QOS filter type was used. +// +pub const WSA_QOS_EFILTERTYPE: i32 = 11020; + +// +// MessageId: WSA_QOS_EFILTERCOUNT +// +// MessageText: +// +// An incorrect number of QOS FILTERSPECs were specified in the FLOWDESCRIPTOR. +// +pub const WSA_QOS_EFILTERCOUNT: i32 = 11021; + +// +// MessageId: WSA_QOS_EOBJLENGTH +// +// MessageText: +// +// An object with an invalid ObjectLength field was specified in the QOS provider-specific buffer. +// +pub const WSA_QOS_EOBJLENGTH: i32 = 11022; + +// +// MessageId: WSA_QOS_EFLOWCOUNT +// +// MessageText: +// +// An incorrect number of flow descriptors was specified in the QOS structure. +// +pub const WSA_QOS_EFLOWCOUNT: i32 = 11023; + +// +// MessageId: WSA_QOS_EUNKOWNPSOBJ +// +// MessageText: +// +// An unrecognized object was found in the QOS provider-specific buffer. +// +pub const WSA_QOS_EUNKOWNPSOBJ: i32 = 11024; + +// +// MessageId: WSA_QOS_EPOLICYOBJ +// +// MessageText: +// +// An invalid policy object was found in the QOS provider-specific buffer. +// +pub const WSA_QOS_EPOLICYOBJ: i32 = 11025; + +// +// MessageId: WSA_QOS_EFLOWDESC +// +// MessageText: +// +// An invalid QOS flow descriptor was found in the flow descriptor list. +// +pub const WSA_QOS_EFLOWDESC: i32 = 11026; + +// +// MessageId: WSA_QOS_EPSFLOWSPEC +// +// MessageText: +// +// An invalid or inconsistent flowspec was found in the QOS provider specific buffer. +// +pub const WSA_QOS_EPSFLOWSPEC: i32 = 11027; + +// +// MessageId: WSA_QOS_EPSFILTERSPEC +// +// MessageText: +// +// An invalid FILTERSPEC was found in the QOS provider-specific buffer. +// +pub const WSA_QOS_EPSFILTERSPEC: i32 = 11028; + +// +// MessageId: WSA_QOS_ESDMODEOBJ +// +// MessageText: +// +// An invalid shape discard mode object was found in the QOS provider specific buffer. +// +pub const WSA_QOS_ESDMODEOBJ: i32 = 11029; + +// +// MessageId: WSA_QOS_ESHAPERATEOBJ +// +// MessageText: +// +// An invalid shaping rate object was found in the QOS provider-specific buffer. +// +pub const WSA_QOS_ESHAPERATEOBJ: i32 = 11030; + +// +// MessageId: WSA_QOS_RESERVED_PETYPE +// +// MessageText: +// +// A reserved policy element was found in the QOS provider-specific buffer. +// +pub const WSA_QOS_RESERVED_PETYPE: i32 = 11031; + +/////////////////////////////////////////////////// +// // +// End of WinSock Error Codes // +// // +// 10000 to 11999 // +/////////////////////////////////////////////////// + +/////////////////////////////////////////////////// +// // +// Side By Side Error Codes // +// // +// 14000 to 14999 // +/////////////////////////////////////////////////// + +// +// MessageId: ERROR_SXS_SECTION_NOT_FOUND +// +// MessageText: +// +// The requested section was not present in the activation context. +// +pub const ERROR_SXS_SECTION_NOT_FOUND: i32 = 14000; + +// +// MessageId: ERROR_SXS_CANT_GEN_ACTCTX +// +// MessageText: +// +// This application has failed to start because the application configuration is incorrect. Reinstalling the application may fix this problem. +// +pub const ERROR_SXS_CANT_GEN_ACTCTX: i32 = 14001; + +// +// MessageId: ERROR_SXS_INVALID_ACTCTXDATA_FORMAT +// +// MessageText: +// +// The application binding data format is invalid. +// +pub const ERROR_SXS_INVALID_ACTCTXDATA_FORMAT: i32 = 14002; + +// +// MessageId: ERROR_SXS_ASSEMBLY_NOT_FOUND +// +// MessageText: +// +// The referenced assembly is not installed on your system. +// +pub const ERROR_SXS_ASSEMBLY_NOT_FOUND: i32 = 14003; + +// +// MessageId: ERROR_SXS_MANIFEST_FORMAT_ERROR +// +// MessageText: +// +// The manifest file does not begin with the required tag and format information. +// +pub const ERROR_SXS_MANIFEST_FORMAT_ERROR: i32 = 14004; + +// +// MessageId: ERROR_SXS_MANIFEST_PARSE_ERROR +// +// MessageText: +// +// The manifest file contains one or more syntax errors. +// +pub const ERROR_SXS_MANIFEST_PARSE_ERROR: i32 = 14005; + +// +// MessageId: ERROR_SXS_ACTIVATION_CONTEXT_DISABLED +// +// MessageText: +// +// The application attempted to activate a disabled activation context. +// +pub const ERROR_SXS_ACTIVATION_CONTEXT_DISABLED: i32 = 14006; + +// +// MessageId: ERROR_SXS_KEY_NOT_FOUND +// +// MessageText: +// +// The requested lookup key was not found in any active activation context. +// +pub const ERROR_SXS_KEY_NOT_FOUND: i32 = 14007; + +// +// MessageId: ERROR_SXS_VERSION_CONFLICT +// +// MessageText: +// +// A component version required by the application conflicts with another component version already active. +// +pub const ERROR_SXS_VERSION_CONFLICT: i32 = 14008; + +// +// MessageId: ERROR_SXS_WRONG_SECTION_TYPE +// +// MessageText: +// +// The type requested activation context section does not match the query API used. +// +pub const ERROR_SXS_WRONG_SECTION_TYPE: i32 = 14009; + +// +// MessageId: ERROR_SXS_THREAD_QUERIES_DISABLED +// +// MessageText: +// +// Lack of system resources has required isolated activation to be disabled for the current thread of execution. +// +pub const ERROR_SXS_THREAD_QUERIES_DISABLED: i32 = 14010; + +// +// MessageId: ERROR_SXS_PROCESS_DEFAULT_ALREADY_SET +// +// MessageText: +// +// An attempt to set the process default activation context failed because the process default activation context was already set. +// +pub const ERROR_SXS_PROCESS_DEFAULT_ALREADY_SET: i32 = 14011; + +// +// MessageId: ERROR_SXS_UNKNOWN_ENCODING_GROUP +// +// MessageText: +// +// The encoding group identifier specified is not recognized. +// +pub const ERROR_SXS_UNKNOWN_ENCODING_GROUP: i32 = 14012; + +// +// MessageId: ERROR_SXS_UNKNOWN_ENCODING +// +// MessageText: +// +// The encoding requested is not recognized. +// +pub const ERROR_SXS_UNKNOWN_ENCODING: i32 = 14013; + +// +// MessageId: ERROR_SXS_INVALID_XML_NAMESPACE_URI +// +// MessageText: +// +// The manifest contains a reference to an invalid URI. +// +pub const ERROR_SXS_INVALID_XML_NAMESPACE_URI: i32 = 14014; + +// +// MessageId: ERROR_SXS_ROOT_MANIFEST_DEPENDENCY_NOT_INSTALLED +// +// MessageText: +// +// The application manifest contains a reference to a dependent assembly which is not installed +// +pub const ERROR_SXS_ROOT_MANIFEST_DEPENDENCY_NOT_INSTALLED: i32 = 14015; + +// +// MessageId: ERROR_SXS_LEAF_MANIFEST_DEPENDENCY_NOT_INSTALLED +// +// MessageText: +// +// The manifest for an assembly used by the application has a reference to a dependent assembly which is not installed +// +pub const ERROR_SXS_LEAF_MANIFEST_DEPENDENCY_NOT_INSTALLED: i32 = 14016; + +// +// MessageId: ERROR_SXS_INVALID_ASSEMBLY_IDENTITY_ATTRIBUTE +// +// MessageText: +// +// The manifest contains an attribute for the assembly identity which is not valid. +// +pub const ERROR_SXS_INVALID_ASSEMBLY_IDENTITY_ATTRIBUTE: i32 = 14017; + +// +// MessageId: ERROR_SXS_MANIFEST_MISSING_REQUIRED_DEFAULT_NAMESPACE +// +// MessageText: +// +// The manifest is missing the required default namespace specification on the assembly element. +// +pub const ERROR_SXS_MANIFEST_MISSING_REQUIRED_DEFAULT_NAMESPACE: i32 = 14018; + +// +// MessageId: ERROR_SXS_MANIFEST_INVALID_REQUIRED_DEFAULT_NAMESPACE +// +// MessageText: +// +// The manifest has a default namespace specified on the assembly element but its value is not "urn:schemas-microsoft-com:asm.v1". +// +pub const ERROR_SXS_MANIFEST_INVALID_REQUIRED_DEFAULT_NAMESPACE: i32 = 14019; + +// +// MessageId: ERROR_SXS_PRIVATE_MANIFEST_CROSS_PATH_WITH_REPARSE_POINT +// +// MessageText: +// +// The private manifest probed has crossed reparse-point-associated path +// +pub const ERROR_SXS_PRIVATE_MANIFEST_CROSS_PATH_WITH_REPARSE_POINT: i32 = 14020; + +// +// MessageId: ERROR_SXS_DUPLICATE_DLL_NAME +// +// MessageText: +// +// Two or more components referenced directly or indirectly by the application manifest have files by the same name. +// +pub const ERROR_SXS_DUPLICATE_DLL_NAME: i32 = 14021; + +// +// MessageId: ERROR_SXS_DUPLICATE_WINDOWCLASS_NAME +// +// MessageText: +// +// Two or more components referenced directly or indirectly by the application manifest have window classes with the same name. +// +pub const ERROR_SXS_DUPLICATE_WINDOWCLASS_NAME: i32 = 14022; + +// +// MessageId: ERROR_SXS_DUPLICATE_CLSID +// +// MessageText: +// +// Two or more components referenced directly or indirectly by the application manifest have the same COM server CLSIDs. +// +pub const ERROR_SXS_DUPLICATE_CLSID: i32 = 14023; + +// +// MessageId: ERROR_SXS_DUPLICATE_IID +// +// MessageText: +// +// Two or more components referenced directly or indirectly by the application manifest have proxies for the same COM interface IIDs. +// +pub const ERROR_SXS_DUPLICATE_IID: i32 = 14024; + +// +// MessageId: ERROR_SXS_DUPLICATE_TLBID +// +// MessageText: +// +// Two or more components referenced directly or indirectly by the application manifest have the same COM type library TLBIDs. +// +pub const ERROR_SXS_DUPLICATE_TLBID: i32 = 14025; + +// +// MessageId: ERROR_SXS_DUPLICATE_PROGID +// +// MessageText: +// +// Two or more components referenced directly or indirectly by the application manifest have the same COM ProgIDs. +// +pub const ERROR_SXS_DUPLICATE_PROGID: i32 = 14026; + +// +// MessageId: ERROR_SXS_DUPLICATE_ASSEMBLY_NAME +// +// MessageText: +// +// Two or more components referenced directly or indirectly by the application manifest are different versions of the same component which is not permitted. +// +pub const ERROR_SXS_DUPLICATE_ASSEMBLY_NAME: i32 = 14027; + +// +// MessageId: ERROR_SXS_FILE_HASH_MISMATCH +// +// MessageText: +// +// A component's file does not match the verification information present in the +// component manifest. +// +pub const ERROR_SXS_FILE_HASH_MISMATCH: i32 = 14028; + +// +// MessageId: ERROR_SXS_POLICY_PARSE_ERROR +// +// MessageText: +// +// The policy manifest contains one or more syntax errors. +// +pub const ERROR_SXS_POLICY_PARSE_ERROR: i32 = 14029; + +// +// MessageId: ERROR_SXS_XML_E_MISSINGQUOTE +// +// MessageText: +// +// Manifest Parse Error : A string literal was expected, but no opening quote character was found. +// +pub const ERROR_SXS_XML_E_MISSINGQUOTE: i32 = 14030; + +// +// MessageId: ERROR_SXS_XML_E_COMMENTSYNTAX +// +// MessageText: +// +// Manifest Parse Error : Incorrect syntax was used in a comment. +// +pub const ERROR_SXS_XML_E_COMMENTSYNTAX: i32 = 14031; + +// +// MessageId: ERROR_SXS_XML_E_BADSTARTNAMECHAR +// +// MessageText: +// +// Manifest Parse Error : A name was started with an invalid character. +// +pub const ERROR_SXS_XML_E_BADSTARTNAMECHAR: i32 = 14032; + +// +// MessageId: ERROR_SXS_XML_E_BADNAMECHAR +// +// MessageText: +// +// Manifest Parse Error : A name contained an invalid character. +// +pub const ERROR_SXS_XML_E_BADNAMECHAR: i32 = 14033; + +// +// MessageId: ERROR_SXS_XML_E_BADCHARINSTRING +// +// MessageText: +// +// Manifest Parse Error : A string literal contained an invalid character. +// +pub const ERROR_SXS_XML_E_BADCHARINSTRING: i32 = 14034; + +// +// MessageId: ERROR_SXS_XML_E_XMLDECLSYNTAX +// +// MessageText: +// +// Manifest Parse Error : Invalid syntax for an xml declaration. +// +pub const ERROR_SXS_XML_E_XMLDECLSYNTAX: i32 = 14035; + +// +// MessageId: ERROR_SXS_XML_E_BADCHARDATA +// +// MessageText: +// +// Manifest Parse Error : An Invalid character was found in text content. +// +pub const ERROR_SXS_XML_E_BADCHARDATA: i32 = 14036; + +// +// MessageId: ERROR_SXS_XML_E_MISSINGWHITESPACE +// +// MessageText: +// +// Manifest Parse Error : Required white space was missing. +// +pub const ERROR_SXS_XML_E_MISSINGWHITESPACE: i32 = 14037; + +// +// MessageId: ERROR_SXS_XML_E_EXPECTINGTAGEND +// +// MessageText: +// +// Manifest Parse Error : The character '>' was expected. +// +pub const ERROR_SXS_XML_E_EXPECTINGTAGEND: i32 = 14038; + +// +// MessageId: ERROR_SXS_XML_E_MISSINGSEMICOLON +// +// MessageText: +// +// Manifest Parse Error : A semi colon character was expected. +// +pub const ERROR_SXS_XML_E_MISSINGSEMICOLON: i32 = 14039; + +// +// MessageId: ERROR_SXS_XML_E_UNBALANCEDPAREN +// +// MessageText: +// +// Manifest Parse Error : Unbalanced parentheses. +// +pub const ERROR_SXS_XML_E_UNBALANCEDPAREN: i32 = 14040; + +// +// MessageId: ERROR_SXS_XML_E_INTERNALERROR +// +// MessageText: +// +// Manifest Parse Error : Internal error. +// +pub const ERROR_SXS_XML_E_INTERNALERROR: i32 = 14041; + +// +// MessageId: ERROR_SXS_XML_E_UNEXPECTED_WHITESPACE +// +// MessageText: +// +// Manifest Parse Error : Whitespace is not allowed at this location. +// +pub const ERROR_SXS_XML_E_UNEXPECTED_WHITESPACE: i32 = 14042; + +// +// MessageId: ERROR_SXS_XML_E_INCOMPLETE_ENCODING +// +// MessageText: +// +// Manifest Parse Error : End of file reached in invalid state for current encoding. +// +pub const ERROR_SXS_XML_E_INCOMPLETE_ENCODING: i32 = 14043; + +// +// MessageId: ERROR_SXS_XML_E_MISSING_PAREN +// +// MessageText: +// +// Manifest Parse Error : Missing parenthesis. +// +pub const ERROR_SXS_XML_E_MISSING_PAREN: i32 = 14044; + +// +// MessageId: ERROR_SXS_XML_E_EXPECTINGCLOSEQUOTE +// +// MessageText: +// +// Manifest Parse Error : A single or double closing quote character (\' or \") is missing. +// +pub const ERROR_SXS_XML_E_EXPECTINGCLOSEQUOTE: i32 = 14045; + +// +// MessageId: ERROR_SXS_XML_E_MULTIPLE_COLONS +// +// MessageText: +// +// Manifest Parse Error : Multiple colons are not allowed in a name. +// +pub const ERROR_SXS_XML_E_MULTIPLE_COLONS: i32 = 14046; + +// +// MessageId: ERROR_SXS_XML_E_INVALID_DECIMAL +// +// MessageText: +// +// Manifest Parse Error : Invalid character for decimal digit. +// +pub const ERROR_SXS_XML_E_INVALID_DECIMAL: i32 = 14047; + +// +// MessageId: ERROR_SXS_XML_E_INVALID_HEXIDECIMAL +// +// MessageText: +// +// Manifest Parse Error : Invalid character for hexadecimal digit. +// +pub const ERROR_SXS_XML_E_INVALID_HEXIDECIMAL: i32 = 14048; + +// +// MessageId: ERROR_SXS_XML_E_INVALID_UNICODE +// +// MessageText: +// +// Manifest Parse Error : Invalid unicode character value for this platform. +// +pub const ERROR_SXS_XML_E_INVALID_UNICODE: i32 = 14049; + +// +// MessageId: ERROR_SXS_XML_E_WHITESPACEORQUESTIONMARK +// +// MessageText: +// +// Manifest Parse Error : Expecting whitespace or '?'. +// +pub const ERROR_SXS_XML_E_WHITESPACEORQUESTIONMARK: i32 = 14050; + +// +// MessageId: ERROR_SXS_XML_E_UNEXPECTEDENDTAG +// +// MessageText: +// +// Manifest Parse Error : End tag was not expected at this location. +// +pub const ERROR_SXS_XML_E_UNEXPECTEDENDTAG: i32 = 14051; + +// +// MessageId: ERROR_SXS_XML_E_UNCLOSEDTAG +// +// MessageText: +// +// Manifest Parse Error : The following tags were not closed: %1. +// +pub const ERROR_SXS_XML_E_UNCLOSEDTAG: i32 = 14052; + +// +// MessageId: ERROR_SXS_XML_E_DUPLICATEATTRIBUTE +// +// MessageText: +// +// Manifest Parse Error : Duplicate attribute. +// +pub const ERROR_SXS_XML_E_DUPLICATEATTRIBUTE: i32 = 14053; + +// +// MessageId: ERROR_SXS_XML_E_MULTIPLEROOTS +// +// MessageText: +// +// Manifest Parse Error : Only one top level element is allowed in an XML document. +// +pub const ERROR_SXS_XML_E_MULTIPLEROOTS: i32 = 14054; + +// +// MessageId: ERROR_SXS_XML_E_INVALIDATROOTLEVEL +// +// MessageText: +// +// Manifest Parse Error : Invalid at the top level of the document. +// +pub const ERROR_SXS_XML_E_INVALIDATROOTLEVEL: i32 = 14055; + +// +// MessageId: ERROR_SXS_XML_E_BADXMLDECL +// +// MessageText: +// +// Manifest Parse Error : Invalid xml declaration. +// +pub const ERROR_SXS_XML_E_BADXMLDECL: i32 = 14056; + +// +// MessageId: ERROR_SXS_XML_E_MISSINGROOT +// +// MessageText: +// +// Manifest Parse Error : XML document must have a top level element. +// +pub const ERROR_SXS_XML_E_MISSINGROOT: i32 = 14057; + +// +// MessageId: ERROR_SXS_XML_E_UNEXPECTEDEOF +// +// MessageText: +// +// Manifest Parse Error : Unexpected end of file. +// +pub const ERROR_SXS_XML_E_UNEXPECTEDEOF: i32 = 14058; + +// +// MessageId: ERROR_SXS_XML_E_BADPEREFINSUBSET +// +// MessageText: +// +// Manifest Parse Error : Parameter entities cannot be used inside markup declarations in an internal subset. +// +pub const ERROR_SXS_XML_E_BADPEREFINSUBSET: i32 = 14059; + +// +// MessageId: ERROR_SXS_XML_E_UNCLOSEDSTARTTAG +// +// MessageText: +// +// Manifest Parse Error : Element was not closed. +// +pub const ERROR_SXS_XML_E_UNCLOSEDSTARTTAG: i32 = 14060; + +// +// MessageId: ERROR_SXS_XML_E_UNCLOSEDENDTAG +// +// MessageText: +// +// Manifest Parse Error : End element was missing the character '>'. +// +pub const ERROR_SXS_XML_E_UNCLOSEDENDTAG: i32 = 14061; + +// +// MessageId: ERROR_SXS_XML_E_UNCLOSEDSTRING +// +// MessageText: +// +// Manifest Parse Error : A string literal was not closed. +// +pub const ERROR_SXS_XML_E_UNCLOSEDSTRING: i32 = 14062; + +// +// MessageId: ERROR_SXS_XML_E_UNCLOSEDCOMMENT +// +// MessageText: +// +// Manifest Parse Error : A comment was not closed. +// +pub const ERROR_SXS_XML_E_UNCLOSEDCOMMENT: i32 = 14063; + +// +// MessageId: ERROR_SXS_XML_E_UNCLOSEDDECL +// +// MessageText: +// +// Manifest Parse Error : A declaration was not closed. +// +pub const ERROR_SXS_XML_E_UNCLOSEDDECL: i32 = 14064; + +// +// MessageId: ERROR_SXS_XML_E_UNCLOSEDCDATA +// +// MessageText: +// +// Manifest Parse Error : A CDATA section was not closed. +// +pub const ERROR_SXS_XML_E_UNCLOSEDCDATA: i32 = 14065; + +// +// MessageId: ERROR_SXS_XML_E_RESERVEDNAMESPACE +// +// MessageText: +// +// Manifest Parse Error : The namespace prefix is not allowed to start with the reserved string "xml". +// +pub const ERROR_SXS_XML_E_RESERVEDNAMESPACE: i32 = 14066; + +// +// MessageId: ERROR_SXS_XML_E_INVALIDENCODING +// +// MessageText: +// +// Manifest Parse Error : System does not support the specified encoding. +// +pub const ERROR_SXS_XML_E_INVALIDENCODING: i32 = 14067; + +// +// MessageId: ERROR_SXS_XML_E_INVALIDSWITCH +// +// MessageText: +// +// Manifest Parse Error : Switch from current encoding to specified encoding not supported. +// +pub const ERROR_SXS_XML_E_INVALIDSWITCH: i32 = 14068; + +// +// MessageId: ERROR_SXS_XML_E_BADXMLCASE +// +// MessageText: +// +// Manifest Parse Error : The name 'xml' is reserved and must be lower case. +// +pub const ERROR_SXS_XML_E_BADXMLCASE: i32 = 14069; + +// +// MessageId: ERROR_SXS_XML_E_INVALID_STANDALONE +// +// MessageText: +// +// Manifest Parse Error : The standalone attribute must have the value 'yes' or 'no'. +// +pub const ERROR_SXS_XML_E_INVALID_STANDALONE: i32 = 14070; + +// +// MessageId: ERROR_SXS_XML_E_UNEXPECTED_STANDALONE +// +// MessageText: +// +// Manifest Parse Error : The standalone attribute cannot be used in external entities. +// +pub const ERROR_SXS_XML_E_UNEXPECTED_STANDALONE: i32 = 14071; + +// +// MessageId: ERROR_SXS_XML_E_INVALID_VERSION +// +// MessageText: +// +// Manifest Parse Error : Invalid version number. +// +pub const ERROR_SXS_XML_E_INVALID_VERSION: i32 = 14072; + +// +// MessageId: ERROR_SXS_XML_E_MISSINGEQUALS +// +// MessageText: +// +// Manifest Parse Error : Missing equals sign between attribute and attribute value. +// +pub const ERROR_SXS_XML_E_MISSINGEQUALS: i32 = 14073; + +// +// MessageId: ERROR_SXS_PROTECTION_RECOVERY_FAILED +// +// MessageText: +// +// Assembly Protection Error : Unable to recover the specified assembly. +// +pub const ERROR_SXS_PROTECTION_RECOVERY_FAILED: i32 = 14074; + +// +// MessageId: ERROR_SXS_PROTECTION_PUBLIC_KEY_TOO_SHORT +// +// MessageText: +// +// Assembly Protection Error : The public key for an assembly was too short to be allowed. +// +pub const ERROR_SXS_PROTECTION_PUBLIC_KEY_TOO_SHORT: i32 = 14075; + +// +// MessageId: ERROR_SXS_PROTECTION_CATALOG_NOT_VALID +// +// MessageText: +// +// Assembly Protection Error : The catalog for an assembly is not valid, or does not match the assembly's manifest. +// +pub const ERROR_SXS_PROTECTION_CATALOG_NOT_VALID: i32 = 14076; + +// +// MessageId: ERROR_SXS_UNTRANSLATABLE_HRESULT +// +// MessageText: +// +// An HRESULT could not be translated to a corresponding Win32 error code. +// +pub const ERROR_SXS_UNTRANSLATABLE_HRESULT: i32 = 14077; + +// +// MessageId: ERROR_SXS_PROTECTION_CATALOG_FILE_MISSING +// +// MessageText: +// +// Assembly Protection Error : The catalog for an assembly is missing. +// +pub const ERROR_SXS_PROTECTION_CATALOG_FILE_MISSING: i32 = 14078; + +// +// MessageId: ERROR_SXS_MISSING_ASSEMBLY_IDENTITY_ATTRIBUTE +// +// MessageText: +// +// The supplied assembly identity is missing one or more attributes which must be present in this context. +// +pub const ERROR_SXS_MISSING_ASSEMBLY_IDENTITY_ATTRIBUTE: i32 = 14079; + +// +// MessageId: ERROR_SXS_INVALID_ASSEMBLY_IDENTITY_ATTRIBUTE_NAME +// +// MessageText: +// +// The supplied assembly identity has one or more attribute names that contain characters not permitted in XML names. +// +pub const ERROR_SXS_INVALID_ASSEMBLY_IDENTITY_ATTRIBUTE_NAME: i32 = 14080; + +/////////////////////////////////////////////////// +// // +// End of Side By Side Error Codes // +// // +// 14000 to 14999 // +/////////////////////////////////////////////////// + +/////////////////////////////////////////////////// +// // +// Start of IPSec Error codes // +// // +// 13000 to 13999 // +/////////////////////////////////////////////////// + +// +// MessageId: ERROR_IPSEC_QM_POLICY_EXISTS +// +// MessageText: +// +// The specified quick mode policy already exists. +// +pub const ERROR_IPSEC_QM_POLICY_EXISTS: i32 = 13000; + +// +// MessageId: ERROR_IPSEC_QM_POLICY_NOT_FOUND +// +// MessageText: +// +// The specified quick mode policy was not found. +// +pub const ERROR_IPSEC_QM_POLICY_NOT_FOUND: i32 = 13001; + +// +// MessageId: ERROR_IPSEC_QM_POLICY_IN_USE +// +// MessageText: +// +// The specified quick mode policy is being used. +// +pub const ERROR_IPSEC_QM_POLICY_IN_USE: i32 = 13002; + +// +// MessageId: ERROR_IPSEC_MM_POLICY_EXISTS +// +// MessageText: +// +// The specified main mode policy already exists. +// +pub const ERROR_IPSEC_MM_POLICY_EXISTS: i32 = 13003; + +// +// MessageId: ERROR_IPSEC_MM_POLICY_NOT_FOUND +// +// MessageText: +// +// The specified main mode policy was not found +// +pub const ERROR_IPSEC_MM_POLICY_NOT_FOUND: i32 = 13004; + +// +// MessageId: ERROR_IPSEC_MM_POLICY_IN_USE +// +// MessageText: +// +// The specified main mode policy is being used. +// +pub const ERROR_IPSEC_MM_POLICY_IN_USE: i32 = 13005; + +// +// MessageId: ERROR_IPSEC_MM_FILTER_EXISTS +// +// MessageText: +// +// The specified main mode filter already exists. +// +pub const ERROR_IPSEC_MM_FILTER_EXISTS: i32 = 13006; + +// +// MessageId: ERROR_IPSEC_MM_FILTER_NOT_FOUND +// +// MessageText: +// +// The specified main mode filter was not found. +// +pub const ERROR_IPSEC_MM_FILTER_NOT_FOUND: i32 = 13007; + +// +// MessageId: ERROR_IPSEC_TRANSPORT_FILTER_EXISTS +// +// MessageText: +// +// The specified transport mode filter already exists. +// +pub const ERROR_IPSEC_TRANSPORT_FILTER_EXISTS: i32 = 13008; + +// +// MessageId: ERROR_IPSEC_TRANSPORT_FILTER_NOT_FOUND +// +// MessageText: +// +// The specified transport mode filter does not exist. +// +pub const ERROR_IPSEC_TRANSPORT_FILTER_NOT_FOUND: i32 = 13009; + +// +// MessageId: ERROR_IPSEC_MM_AUTH_EXISTS +// +// MessageText: +// +// The specified main mode authentication list exists. +// +pub const ERROR_IPSEC_MM_AUTH_EXISTS: i32 = 13010; + +// +// MessageId: ERROR_IPSEC_MM_AUTH_NOT_FOUND +// +// MessageText: +// +// The specified main mode authentication list was not found. +// +pub const ERROR_IPSEC_MM_AUTH_NOT_FOUND: i32 = 13011; + +// +// MessageId: ERROR_IPSEC_MM_AUTH_IN_USE +// +// MessageText: +// +// The specified quick mode policy is being used. +// +pub const ERROR_IPSEC_MM_AUTH_IN_USE: i32 = 13012; + +// +// MessageId: ERROR_IPSEC_DEFAULT_MM_POLICY_NOT_FOUND +// +// MessageText: +// +// The specified main mode policy was not found. +// +pub const ERROR_IPSEC_DEFAULT_MM_POLICY_NOT_FOUND: i32 = 13013; + +// +// MessageId: ERROR_IPSEC_DEFAULT_MM_AUTH_NOT_FOUND +// +// MessageText: +// +// The specified quick mode policy was not found +// +pub const ERROR_IPSEC_DEFAULT_MM_AUTH_NOT_FOUND: i32 = 13014; + +// +// MessageId: ERROR_IPSEC_DEFAULT_QM_POLICY_NOT_FOUND +// +// MessageText: +// +// The manifest file contains one or more syntax errors. +// +pub const ERROR_IPSEC_DEFAULT_QM_POLICY_NOT_FOUND: i32 = 13015; + +// +// MessageId: ERROR_IPSEC_TUNNEL_FILTER_EXISTS +// +// MessageText: +// +// The application attempted to activate a disabled activation context. +// +pub const ERROR_IPSEC_TUNNEL_FILTER_EXISTS: i32 = 13016; + +// +// MessageId: ERROR_IPSEC_TUNNEL_FILTER_NOT_FOUND +// +// MessageText: +// +// The requested lookup key was not found in any active activation context. +// +pub const ERROR_IPSEC_TUNNEL_FILTER_NOT_FOUND: i32 = 13017; + +// +// MessageId: ERROR_IPSEC_MM_FILTER_PENDING_DELETION +// +// MessageText: +// +// The Main Mode filter is pending deletion. +// +pub const ERROR_IPSEC_MM_FILTER_PENDING_DELETION: i32 = 13018; + +// +// MessageId: ERROR_IPSEC_TRANSPORT_FILTER_PENDING_DELETION +// +// MessageText: +// +// The transport filter is pending deletion. +// +pub const ERROR_IPSEC_TRANSPORT_FILTER_PENDING_DELETION: i32 = 13019; + +// +// MessageId: ERROR_IPSEC_TUNNEL_FILTER_PENDING_DELETION +// +// MessageText: +// +// The tunnel filter is pending deletion. +// +pub const ERROR_IPSEC_TUNNEL_FILTER_PENDING_DELETION: i32 = 13020; + +// +// MessageId: ERROR_IPSEC_MM_POLICY_PENDING_DELETION +// +// MessageText: +// +// The Main Mode policy is pending deletion. +// +pub const ERROR_IPSEC_MM_POLICY_PENDING_DELETION: i32 = 13021; + +// +// MessageId: ERROR_IPSEC_MM_AUTH_PENDING_DELETION +// +// MessageText: +// +// The Main Mode authentication bundle is pending deletion. +// +pub const ERROR_IPSEC_MM_AUTH_PENDING_DELETION: i32 = 13022; + +// +// MessageId: ERROR_IPSEC_QM_POLICY_PENDING_DELETION +// +// MessageText: +// +// The Quick Mode policy is pending deletion. +// +pub const ERROR_IPSEC_QM_POLICY_PENDING_DELETION: i32 = 13023; + +// +// MessageId: WARNING_IPSEC_MM_POLICY_PRUNED +// +// MessageText: +// +// The Main Mode policy was successfully added, but some of the requested offers are not supported. +// +pub const WARNING_IPSEC_MM_POLICY_PRUNED: i32 = 13024; + +// +// MessageId: WARNING_IPSEC_QM_POLICY_PRUNED +// +// MessageText: +// +// The Quick Mode policy was successfully added, but some of the requested offers are not supported. +// +pub const WARNING_IPSEC_QM_POLICY_PRUNED: i32 = 13025; + +// +// MessageId: ERROR_IPSEC_IKE_NEG_STATUS_BEGIN +// +// MessageText: +// +// ERROR_IPSEC_IKE_NEG_STATUS_BEGIN +// +pub const ERROR_IPSEC_IKE_NEG_STATUS_BEGIN: i32 = 13800; + +// +// MessageId: ERROR_IPSEC_IKE_AUTH_FAIL +// +// MessageText: +// +// IKE authentication credentials are unacceptable +// +pub const ERROR_IPSEC_IKE_AUTH_FAIL: i32 = 13801; + +// +// MessageId: ERROR_IPSEC_IKE_ATTRIB_FAIL +// +// MessageText: +// +// IKE security attributes are unacceptable +// +pub const ERROR_IPSEC_IKE_ATTRIB_FAIL: i32 = 13802; + +// +// MessageId: ERROR_IPSEC_IKE_NEGOTIATION_PENDING +// +// MessageText: +// +// IKE Negotiation in progress +// +pub const ERROR_IPSEC_IKE_NEGOTIATION_PENDING: i32 = 13803; + +// +// MessageId: ERROR_IPSEC_IKE_GENERAL_PROCESSING_ERROR +// +// MessageText: +// +// General processing error +// +pub const ERROR_IPSEC_IKE_GENERAL_PROCESSING_ERROR: i32 = 13804; + +// +// MessageId: ERROR_IPSEC_IKE_TIMED_OUT +// +// MessageText: +// +// Negotiation timed out +// +pub const ERROR_IPSEC_IKE_TIMED_OUT: i32 = 13805; + +// +// MessageId: ERROR_IPSEC_IKE_NO_CERT +// +// MessageText: +// +// IKE failed to find valid machine certificate +// +pub const ERROR_IPSEC_IKE_NO_CERT: i32 = 13806; + +// +// MessageId: ERROR_IPSEC_IKE_SA_DELETED +// +// MessageText: +// +// IKE SA deleted by peer before establishment completed +// +pub const ERROR_IPSEC_IKE_SA_DELETED: i32 = 13807; + +// +// MessageId: ERROR_IPSEC_IKE_SA_REAPED +// +// MessageText: +// +// IKE SA deleted before establishment completed +// +pub const ERROR_IPSEC_IKE_SA_REAPED: i32 = 13808; + +// +// MessageId: ERROR_IPSEC_IKE_MM_ACQUIRE_DROP +// +// MessageText: +// +// Negotiation request sat in Queue too long +// +pub const ERROR_IPSEC_IKE_MM_ACQUIRE_DROP: i32 = 13809; + +// +// MessageId: ERROR_IPSEC_IKE_QM_ACQUIRE_DROP +// +// MessageText: +// +// Negotiation request sat in Queue too long +// +pub const ERROR_IPSEC_IKE_QM_ACQUIRE_DROP: i32 = 13810; + +// +// MessageId: ERROR_IPSEC_IKE_QUEUE_DROP_MM +// +// MessageText: +// +// Negotiation request sat in Queue too long +// +pub const ERROR_IPSEC_IKE_QUEUE_DROP_MM: i32 = 13811; + +// +// MessageId: ERROR_IPSEC_IKE_QUEUE_DROP_NO_MM +// +// MessageText: +// +// Negotiation request sat in Queue too long +// +pub const ERROR_IPSEC_IKE_QUEUE_DROP_NO_MM: i32 = 13812; + +// +// MessageId: ERROR_IPSEC_IKE_DROP_NO_RESPONSE +// +// MessageText: +// +// No response from peer +// +pub const ERROR_IPSEC_IKE_DROP_NO_RESPONSE: i32 = 13813; + +// +// MessageId: ERROR_IPSEC_IKE_MM_DELAY_DROP +// +// MessageText: +// +// Negotiation took too long +// +pub const ERROR_IPSEC_IKE_MM_DELAY_DROP: i32 = 13814; + +// +// MessageId: ERROR_IPSEC_IKE_QM_DELAY_DROP +// +// MessageText: +// +// Negotiation took too long +// +pub const ERROR_IPSEC_IKE_QM_DELAY_DROP: i32 = 13815; + +// +// MessageId: ERROR_IPSEC_IKE_ERROR +// +// MessageText: +// +// Unknown error occurred +// +pub const ERROR_IPSEC_IKE_ERROR: i32 = 13816; + +// +// MessageId: ERROR_IPSEC_IKE_CRL_FAILED +// +// MessageText: +// +// Certificate Revocation Check failed +// +pub const ERROR_IPSEC_IKE_CRL_FAILED: i32 = 13817; + +// +// MessageId: ERROR_IPSEC_IKE_INVALID_KEY_USAGE +// +// MessageText: +// +// Invalid certificate key usage +// +pub const ERROR_IPSEC_IKE_INVALID_KEY_USAGE: i32 = 13818; + +// +// MessageId: ERROR_IPSEC_IKE_INVALID_CERT_TYPE +// +// MessageText: +// +// Invalid certificate type +// +pub const ERROR_IPSEC_IKE_INVALID_CERT_TYPE: i32 = 13819; + +// +// MessageId: ERROR_IPSEC_IKE_NO_PRIVATE_KEY +// +// MessageText: +// +// No private key associated with machine certificate +// +pub const ERROR_IPSEC_IKE_NO_PRIVATE_KEY: i32 = 13820; + +// +// MessageId: ERROR_IPSEC_IKE_DH_FAIL +// +// MessageText: +// +// Failure in Diffie-Hellman computation +// +pub const ERROR_IPSEC_IKE_DH_FAIL: i32 = 13822; + +// +// MessageId: ERROR_IPSEC_IKE_INVALID_HEADER +// +// MessageText: +// +// Invalid header +// +pub const ERROR_IPSEC_IKE_INVALID_HEADER: i32 = 13824; + +// +// MessageId: ERROR_IPSEC_IKE_NO_POLICY +// +// MessageText: +// +// No policy configured +// +pub const ERROR_IPSEC_IKE_NO_POLICY: i32 = 13825; + +// +// MessageId: ERROR_IPSEC_IKE_INVALID_SIGNATURE +// +// MessageText: +// +// Failed to verify signature +// +pub const ERROR_IPSEC_IKE_INVALID_SIGNATURE: i32 = 13826; + +// +// MessageId: ERROR_IPSEC_IKE_KERBEROS_ERROR +// +// MessageText: +// +// Failed to authenticate using kerberos +// +pub const ERROR_IPSEC_IKE_KERBEROS_ERROR: i32 = 13827; + +// +// MessageId: ERROR_IPSEC_IKE_NO_PUBLIC_KEY +// +// MessageText: +// +// Peer's certificate did not have a public key +// +pub const ERROR_IPSEC_IKE_NO_PUBLIC_KEY: i32 = 13828; + +// These must stay as a unit. +// +// MessageId: ERROR_IPSEC_IKE_PROCESS_ERR +// +// MessageText: +// +// Error processing error payload +// +pub const ERROR_IPSEC_IKE_PROCESS_ERR: i32 = 13829; + +// +// MessageId: ERROR_IPSEC_IKE_PROCESS_ERR_SA +// +// MessageText: +// +// Error processing SA payload +// +pub const ERROR_IPSEC_IKE_PROCESS_ERR_SA: i32 = 13830; + +// +// MessageId: ERROR_IPSEC_IKE_PROCESS_ERR_PROP +// +// MessageText: +// +// Error processing Proposal payload +// +pub const ERROR_IPSEC_IKE_PROCESS_ERR_PROP: i32 = 13831; + +// +// MessageId: ERROR_IPSEC_IKE_PROCESS_ERR_TRANS +// +// MessageText: +// +// Error processing Transform payload +// +pub const ERROR_IPSEC_IKE_PROCESS_ERR_TRANS: i32 = 13832; + +// +// MessageId: ERROR_IPSEC_IKE_PROCESS_ERR_KE +// +// MessageText: +// +// Error processing KE payload +// +pub const ERROR_IPSEC_IKE_PROCESS_ERR_KE: i32 = 13833; + +// +// MessageId: ERROR_IPSEC_IKE_PROCESS_ERR_ID +// +// MessageText: +// +// Error processing ID payload +// +pub const ERROR_IPSEC_IKE_PROCESS_ERR_ID: i32 = 13834; + +// +// MessageId: ERROR_IPSEC_IKE_PROCESS_ERR_CERT +// +// MessageText: +// +// Error processing Cert payload +// +pub const ERROR_IPSEC_IKE_PROCESS_ERR_CERT: i32 = 13835; + +// +// MessageId: ERROR_IPSEC_IKE_PROCESS_ERR_CERT_REQ +// +// MessageText: +// +// Error processing Certificate Request payload +// +pub const ERROR_IPSEC_IKE_PROCESS_ERR_CERT_REQ: i32 = 13836; + +// +// MessageId: ERROR_IPSEC_IKE_PROCESS_ERR_HASH +// +// MessageText: +// +// Error processing Hash payload +// +pub const ERROR_IPSEC_IKE_PROCESS_ERR_HASH: i32 = 13837; + +// +// MessageId: ERROR_IPSEC_IKE_PROCESS_ERR_SIG +// +// MessageText: +// +// Error processing Signature payload +// +pub const ERROR_IPSEC_IKE_PROCESS_ERR_SIG: i32 = 13838; + +// +// MessageId: ERROR_IPSEC_IKE_PROCESS_ERR_NONCE +// +// MessageText: +// +// Error processing Nonce payload +// +pub const ERROR_IPSEC_IKE_PROCESS_ERR_NONCE: i32 = 13839; + +// +// MessageId: ERROR_IPSEC_IKE_PROCESS_ERR_NOTIFY +// +// MessageText: +// +// Error processing Notify payload +// +pub const ERROR_IPSEC_IKE_PROCESS_ERR_NOTIFY: i32 = 13840; + +// +// MessageId: ERROR_IPSEC_IKE_PROCESS_ERR_DELETE +// +// MessageText: +// +// Error processing Delete Payload +// +pub const ERROR_IPSEC_IKE_PROCESS_ERR_DELETE: i32 = 13841; + +// +// MessageId: ERROR_IPSEC_IKE_PROCESS_ERR_VENDOR +// +// MessageText: +// +// Error processing VendorId payload +// +pub const ERROR_IPSEC_IKE_PROCESS_ERR_VENDOR: i32 = 13842; + +// +// MessageId: ERROR_IPSEC_IKE_INVALID_PAYLOAD +// +// MessageText: +// +// Invalid payload received +// +pub const ERROR_IPSEC_IKE_INVALID_PAYLOAD: i32 = 13843; + +// +// MessageId: ERROR_IPSEC_IKE_LOAD_SOFT_SA +// +// MessageText: +// +// Soft SA loaded +// +pub const ERROR_IPSEC_IKE_LOAD_SOFT_SA: i32 = 13844; + +// +// MessageId: ERROR_IPSEC_IKE_SOFT_SA_TORN_DOWN +// +// MessageText: +// +// Soft SA torn down +// +pub const ERROR_IPSEC_IKE_SOFT_SA_TORN_DOWN: i32 = 13845; + +// +// MessageId: ERROR_IPSEC_IKE_INVALID_COOKIE +// +// MessageText: +// +// Invalid cookie received. +// +pub const ERROR_IPSEC_IKE_INVALID_COOKIE: i32 = 13846; + +// +// MessageId: ERROR_IPSEC_IKE_NO_PEER_CERT +// +// MessageText: +// +// Peer failed to send valid machine certificate +// +pub const ERROR_IPSEC_IKE_NO_PEER_CERT: i32 = 13847; + +// +// MessageId: ERROR_IPSEC_IKE_PEER_CRL_FAILED +// +// MessageText: +// +// Certification Revocation check of peer's certificate failed +// +pub const ERROR_IPSEC_IKE_PEER_CRL_FAILED: i32 = 13848; + +// +// MessageId: ERROR_IPSEC_IKE_POLICY_CHANGE +// +// MessageText: +// +// New policy invalidated SAs formed with old policy +// +pub const ERROR_IPSEC_IKE_POLICY_CHANGE: i32 = 13849; + +// +// MessageId: ERROR_IPSEC_IKE_NO_MM_POLICY +// +// MessageText: +// +// There is no available Main Mode IKE policy. +// +pub const ERROR_IPSEC_IKE_NO_MM_POLICY: i32 = 13850; + +// +// MessageId: ERROR_IPSEC_IKE_NOTCBPRIV +// +// MessageText: +// +// Failed to enabled TCB privilege. +// +pub const ERROR_IPSEC_IKE_NOTCBPRIV: i32 = 13851; + +// +// MessageId: ERROR_IPSEC_IKE_SECLOADFAIL +// +// MessageText: +// +// Failed to load SECURITY.DLL. +// +pub const ERROR_IPSEC_IKE_SECLOADFAIL: i32 = 13852; + +// +// MessageId: ERROR_IPSEC_IKE_FAILSSPINIT +// +// MessageText: +// +// Failed to obtain security function table dispatch address from SSPI. +// +pub const ERROR_IPSEC_IKE_FAILSSPINIT: i32 = 13853; + +// +// MessageId: ERROR_IPSEC_IKE_FAILQUERYSSP +// +// MessageText: +// +// Failed to query Kerberos package to obtain max token size. +// +pub const ERROR_IPSEC_IKE_FAILQUERYSSP: i32 = 13854; + +// +// MessageId: ERROR_IPSEC_IKE_SRVACQFAIL +// +// MessageText: +// +// Failed to obtain Kerberos server credentials for ISAKMP/ERROR_IPSEC_IKE service. Kerberos authentication will not function. The most likely reason for this is lack of domain membership. This is normal if your computer is a member of a workgroup. +// +pub const ERROR_IPSEC_IKE_SRVACQFAIL: i32 = 13855; + +// +// MessageId: ERROR_IPSEC_IKE_SRVQUERYCRED +// +// MessageText: +// +// Failed to determine SSPI principal name for ISAKMP/ERROR_IPSEC_IKE service (QueryCredentialsAttributes). +// +pub const ERROR_IPSEC_IKE_SRVQUERYCRED: i32 = 13856; + +// +// MessageId: ERROR_IPSEC_IKE_GETSPIFAIL +// +// MessageText: +// +// Failed to obtain new SPI for the inbound SA from Ipsec driver. The most common cause for this is that the driver does not have the correct filter. Check your policy to verify the filters. +// +pub const ERROR_IPSEC_IKE_GETSPIFAIL: i32 = 13857; + +// +// MessageId: ERROR_IPSEC_IKE_INVALID_FILTER +// +// MessageText: +// +// Given filter is invalid +// +pub const ERROR_IPSEC_IKE_INVALID_FILTER: i32 = 13858; + +// +// MessageId: ERROR_IPSEC_IKE_OUT_OF_MEMORY +// +// MessageText: +// +// Memory allocation failed. +// +pub const ERROR_IPSEC_IKE_OUT_OF_MEMORY: i32 = 13859; + +// +// MessageId: ERROR_IPSEC_IKE_ADD_UPDATE_KEY_FAILED +// +// MessageText: +// +// Failed to add Security Association to IPSec Driver. The most common cause for this is if the IKE negotiation took too long to complete. If the problem persists, reduce the load on the faulting machine. +// +pub const ERROR_IPSEC_IKE_ADD_UPDATE_KEY_FAILED: i32 = 13860; + +// +// MessageId: ERROR_IPSEC_IKE_INVALID_POLICY +// +// MessageText: +// +// Invalid policy +// +pub const ERROR_IPSEC_IKE_INVALID_POLICY: i32 = 13861; + +// +// MessageId: ERROR_IPSEC_IKE_UNKNOWN_DOI +// +// MessageText: +// +// Invalid DOI +// +pub const ERROR_IPSEC_IKE_UNKNOWN_DOI: i32 = 13862; + +// +// MessageId: ERROR_IPSEC_IKE_INVALID_SITUATION +// +// MessageText: +// +// Invalid situation +// +pub const ERROR_IPSEC_IKE_INVALID_SITUATION: i32 = 13863; + +// +// MessageId: ERROR_IPSEC_IKE_DH_FAILURE +// +// MessageText: +// +// Diffie-Hellman failure +// +pub const ERROR_IPSEC_IKE_DH_FAILURE: i32 = 13864; + +// +// MessageId: ERROR_IPSEC_IKE_INVALID_GROUP +// +// MessageText: +// +// Invalid Diffie-Hellman group +// +pub const ERROR_IPSEC_IKE_INVALID_GROUP: i32 = 13865; + +// +// MessageId: ERROR_IPSEC_IKE_ENCRYPT +// +// MessageText: +// +// Error encrypting payload +// +pub const ERROR_IPSEC_IKE_ENCRYPT: i32 = 13866; + +// +// MessageId: ERROR_IPSEC_IKE_DECRYPT +// +// MessageText: +// +// Error decrypting payload +// +pub const ERROR_IPSEC_IKE_DECRYPT: i32 = 13867; + +// +// MessageId: ERROR_IPSEC_IKE_POLICY_MATCH +// +// MessageText: +// +// Policy match error +// +pub const ERROR_IPSEC_IKE_POLICY_MATCH: i32 = 13868; + +// +// MessageId: ERROR_IPSEC_IKE_UNSUPPORTED_ID +// +// MessageText: +// +// Unsupported ID +// +pub const ERROR_IPSEC_IKE_UNSUPPORTED_ID: i32 = 13869; + +// +// MessageId: ERROR_IPSEC_IKE_INVALID_HASH +// +// MessageText: +// +// Hash verification failed +// +pub const ERROR_IPSEC_IKE_INVALID_HASH: i32 = 13870; + +// +// MessageId: ERROR_IPSEC_IKE_INVALID_HASH_ALG +// +// MessageText: +// +// Invalid hash algorithm +// +pub const ERROR_IPSEC_IKE_INVALID_HASH_ALG: i32 = 13871; + +// +// MessageId: ERROR_IPSEC_IKE_INVALID_HASH_SIZE +// +// MessageText: +// +// Invalid hash size +// +pub const ERROR_IPSEC_IKE_INVALID_HASH_SIZE: i32 = 13872; + +// +// MessageId: ERROR_IPSEC_IKE_INVALID_ENCRYPT_ALG +// +// MessageText: +// +// Invalid encryption algorithm +// +pub const ERROR_IPSEC_IKE_INVALID_ENCRYPT_ALG: i32 = 13873; + +// +// MessageId: ERROR_IPSEC_IKE_INVALID_AUTH_ALG +// +// MessageText: +// +// Invalid authentication algorithm +// +pub const ERROR_IPSEC_IKE_INVALID_AUTH_ALG: i32 = 13874; + +// +// MessageId: ERROR_IPSEC_IKE_INVALID_SIG +// +// MessageText: +// +// Invalid certificate signature +// +pub const ERROR_IPSEC_IKE_INVALID_SIG: i32 = 13875; + +// +// MessageId: ERROR_IPSEC_IKE_LOAD_FAILED +// +// MessageText: +// +// Load failed +// +pub const ERROR_IPSEC_IKE_LOAD_FAILED: i32 = 13876; + +// +// MessageId: ERROR_IPSEC_IKE_RPC_DELETE +// +// MessageText: +// +// Deleted via RPC call +// +pub const ERROR_IPSEC_IKE_RPC_DELETE: i32 = 13877; + +// +// MessageId: ERROR_IPSEC_IKE_BENIGN_REINIT +// +// MessageText: +// +// Temporary state created to perform reinit. This is not a real failure. +// +pub const ERROR_IPSEC_IKE_BENIGN_REINIT: i32 = 13878; + +// +// MessageId: ERROR_IPSEC_IKE_INVALID_RESPONDER_LIFETIME_NOTIFY +// +// MessageText: +// +// The lifetime value received in the Responder Lifetime Notify is below the Windows 2000 configured minimum value. Please fix the policy on the peer machine. +// +pub const ERROR_IPSEC_IKE_INVALID_RESPONDER_LIFETIME_NOTIFY: i32 = 13879; + +// +// MessageId: ERROR_IPSEC_IKE_INVALID_CERT_KEYLEN +// +// MessageText: +// +// Key length in certificate is too small for configured security requirements. +// +pub const ERROR_IPSEC_IKE_INVALID_CERT_KEYLEN: i32 = 13881; + +// +// MessageId: ERROR_IPSEC_IKE_MM_LIMIT +// +// MessageText: +// +// Max number of established MM SAs to peer exceeded. +// +pub const ERROR_IPSEC_IKE_MM_LIMIT: i32 = 13882; + +// +// MessageId: ERROR_IPSEC_IKE_NEGOTIATION_DISABLED +// +// MessageText: +// +// IKE received a policy that disables negotiation. +// +pub const ERROR_IPSEC_IKE_NEGOTIATION_DISABLED: i32 = 13883; + +// +// MessageId: ERROR_IPSEC_IKE_NEG_STATUS_END +// +// MessageText: +// +// ERROR_IPSEC_IKE_NEG_STATUS_END +// +pub const ERROR_IPSEC_IKE_NEG_STATUS_END: i32 = 13884; diff --git a/ext/tls/Cargo.toml b/ext/tls/Cargo.toml index 5b047c4c5e7d58..e73a6e242f0a56 100644 --- a/ext/tls/Cargo.toml +++ b/ext/tls/Cargo.toml @@ -2,7 +2,7 @@ [package] name = "deno_tls" -version = "0.76.0" +version = "0.78.0" authors.workspace = true edition.workspace = true license.workspace = true diff --git a/ext/url/00_url.js b/ext/url/00_url.js index 1191565ee2ebf0..da5849d43e00d0 100644 --- a/ext/url/00_url.js +++ b/ext/url/00_url.js @@ -5,806 +5,803 @@ /// /// -"use strict"; - -((window) => { - const core = window.Deno.core; - const ops = core.ops; - const webidl = window.__bootstrap.webidl; - const { - ArrayIsArray, - ArrayPrototypeMap, - ArrayPrototypePush, - ArrayPrototypeSome, - ArrayPrototypeSort, - ArrayPrototypeSplice, - ObjectKeys, - Uint32Array, - SafeArrayIterator, - StringPrototypeSlice, - Symbol, - SymbolFor, - SymbolIterator, - TypeError, - } = window.__bootstrap.primordials; - - const _list = Symbol("list"); - const _urlObject = Symbol("url object"); - - // WARNING: must match rust code's UrlSetter::* - const SET_HASH = 0; - const SET_HOST = 1; - const SET_HOSTNAME = 2; - const SET_PASSWORD = 3; - const SET_PATHNAME = 4; - const SET_PORT = 5; - const SET_PROTOCOL = 6; - const SET_SEARCH = 7; - const SET_USERNAME = 8; - - // Helper functions - function opUrlReparse(href, setter, value) { - const status = ops.op_url_reparse( +const core = globalThis.Deno.core; +const ops = core.ops; +import * as webidl from "internal:deno_webidl/00_webidl.js"; +const primordials = globalThis.__bootstrap.primordials; +const { + ArrayIsArray, + ArrayPrototypeMap, + ArrayPrototypePush, + ArrayPrototypeSome, + ArrayPrototypeSort, + ArrayPrototypeSplice, + ObjectKeys, + Uint32Array, + SafeArrayIterator, + StringPrototypeSlice, + Symbol, + SymbolFor, + SymbolIterator, + TypeError, +} = primordials; + +const _list = Symbol("list"); +const _urlObject = Symbol("url object"); + +// WARNING: must match rust code's UrlSetter::* +const SET_HASH = 0; +const SET_HOST = 1; +const SET_HOSTNAME = 2; +const SET_PASSWORD = 3; +const SET_PATHNAME = 4; +const SET_PORT = 5; +const SET_PROTOCOL = 6; +const SET_SEARCH = 7; +const SET_USERNAME = 8; + +// Helper functions +function opUrlReparse(href, setter, value) { + const status = ops.op_url_reparse( + href, + setter, + value, + componentsBuf.buffer, + ); + return getSerialization(status, href); +} + +function opUrlParse(href, maybeBase) { + let status; + if (maybeBase === undefined) { + status = ops.op_url_parse(href, componentsBuf.buffer); + } else { + status = ops.op_url_parse_with_base( href, - setter, - value, + maybeBase, componentsBuf.buffer, ); - return getSerialization(status, href); } + return getSerialization(status, href, maybeBase); +} + +function getSerialization(status, href, maybeBase) { + if (status === 0) { + return href; + } else if (status === 1) { + return ops.op_url_get_serialization(); + } else { + throw new TypeError( + `Invalid URL: '${href}'` + + (maybeBase ? ` with base '${maybeBase}'` : ""), + ); + } +} - function opUrlParse(href, maybeBase) { - let status; - if (maybeBase === undefined) { - status = ops.op_url_parse(href, componentsBuf.buffer); +class URLSearchParams { + [_list]; + [_urlObject] = null; + + /** + * @param {string | [string][] | Record} init + */ + constructor(init = "") { + const prefix = "Failed to construct 'URL'"; + init = webidl.converters + ["sequence> or record or USVString"]( + init, + { prefix, context: "Argument 1" }, + ); + this[webidl.brand] = webidl.brand; + if (!init) { + // if there is no query string, return early + this[_list] = []; + return; + } + + if (typeof init === "string") { + // Overload: USVString + // If init is a string and starts with U+003F (?), + // remove the first code point from init. + if (init[0] == "?") { + init = StringPrototypeSlice(init, 1); + } + this[_list] = ops.op_url_parse_search_params(init); + } else if (ArrayIsArray(init)) { + // Overload: sequence> + this[_list] = ArrayPrototypeMap(init, (pair, i) => { + if (pair.length !== 2) { + throw new TypeError( + `${prefix}: Item ${ + i + 0 + } in the parameter list does have length 2 exactly.`, + ); + } + return [pair[0], pair[1]]; + }); } else { - status = core.ops.op_url_parse_with_base( - href, - maybeBase, - componentsBuf.buffer, + // Overload: record + this[_list] = ArrayPrototypeMap( + ObjectKeys(init), + (key) => [key, init[key]], ); } - return getSerialization(status, href, maybeBase); } - function getSerialization(status, href, maybeBase) { - if (status === 0) { - return href; - } else if (status === 1) { - return core.ops.op_url_get_serialization(); - } else { - throw new TypeError( - `Invalid URL: '${href}'` + - (maybeBase ? ` with base '${maybeBase}'` : ""), - ); + #updateUrlSearch() { + const url = this[_urlObject]; + if (url === null) { + return; } + url[_updateUrlSearch](this.toString()); } - class URLSearchParams { - [_list]; - [_urlObject] = null; - - /** - * @param {string | [string][] | Record} init - */ - constructor(init = "") { - const prefix = "Failed to construct 'URL'"; - init = webidl.converters - ["sequence> or record or USVString"]( - init, - { prefix, context: "Argument 1" }, - ); - this[webidl.brand] = webidl.brand; - if (!init) { - // if there is no query string, return early - this[_list] = []; - return; - } + /** + * @param {string} name + * @param {string} value + */ + append(name, value) { + webidl.assertBranded(this, URLSearchParamsPrototype); + const prefix = "Failed to execute 'append' on 'URLSearchParams'"; + webidl.requiredArguments(arguments.length, 2, { prefix }); + name = webidl.converters.USVString(name, { + prefix, + context: "Argument 1", + }); + value = webidl.converters.USVString(value, { + prefix, + context: "Argument 2", + }); + ArrayPrototypePush(this[_list], [name, value]); + this.#updateUrlSearch(); + } - if (typeof init === "string") { - // Overload: USVString - // If init is a string and starts with U+003F (?), - // remove the first code point from init. - if (init[0] == "?") { - init = StringPrototypeSlice(init, 1); - } - this[_list] = ops.op_url_parse_search_params(init); - } else if (ArrayIsArray(init)) { - // Overload: sequence> - this[_list] = ArrayPrototypeMap(init, (pair, i) => { - if (pair.length !== 2) { - throw new TypeError( - `${prefix}: Item ${ - i + 0 - } in the parameter list does have length 2 exactly.`, - ); - } - return [pair[0], pair[1]]; - }); + /** + * @param {string} name + */ + delete(name) { + webidl.assertBranded(this, URLSearchParamsPrototype); + const prefix = "Failed to execute 'append' on 'URLSearchParams'"; + webidl.requiredArguments(arguments.length, 1, { prefix }); + name = webidl.converters.USVString(name, { + prefix, + context: "Argument 1", + }); + const list = this[_list]; + let i = 0; + while (i < list.length) { + if (list[i][0] === name) { + ArrayPrototypeSplice(list, i, 1); } else { - // Overload: record - this[_list] = ArrayPrototypeMap( - ObjectKeys(init), - (key) => [key, init[key]], - ); + i++; } } + this.#updateUrlSearch(); + } - #updateUrlSearch() { - const url = this[_urlObject]; - if (url === null) { - return; + /** + * @param {string} name + * @returns {string[]} + */ + getAll(name) { + webidl.assertBranded(this, URLSearchParamsPrototype); + const prefix = "Failed to execute 'getAll' on 'URLSearchParams'"; + webidl.requiredArguments(arguments.length, 1, { prefix }); + name = webidl.converters.USVString(name, { + prefix, + context: "Argument 1", + }); + const values = []; + const entries = this[_list]; + for (let i = 0; i < entries.length; ++i) { + const entry = entries[i]; + if (entry[0] === name) { + ArrayPrototypePush(values, entry[1]); } - url[_updateUrlSearch](this.toString()); } + return values; + } - /** - * @param {string} name - * @param {string} value - */ - append(name, value) { - webidl.assertBranded(this, URLSearchParamsPrototype); - const prefix = "Failed to execute 'append' on 'URLSearchParams'"; - webidl.requiredArguments(arguments.length, 2, { prefix }); - name = webidl.converters.USVString(name, { - prefix, - context: "Argument 1", - }); - value = webidl.converters.USVString(value, { - prefix, - context: "Argument 2", - }); - ArrayPrototypePush(this[_list], [name, value]); - this.#updateUrlSearch(); + /** + * @param {string} name + * @return {string | null} + */ + get(name) { + webidl.assertBranded(this, URLSearchParamsPrototype); + const prefix = "Failed to execute 'get' on 'URLSearchParams'"; + webidl.requiredArguments(arguments.length, 1, { prefix }); + name = webidl.converters.USVString(name, { + prefix, + context: "Argument 1", + }); + const entries = this[_list]; + for (let i = 0; i < entries.length; ++i) { + const entry = entries[i]; + if (entry[0] === name) { + return entry[1]; + } } + return null; + } - /** - * @param {string} name - */ - delete(name) { - webidl.assertBranded(this, URLSearchParamsPrototype); - const prefix = "Failed to execute 'append' on 'URLSearchParams'"; - webidl.requiredArguments(arguments.length, 1, { prefix }); - name = webidl.converters.USVString(name, { - prefix, - context: "Argument 1", - }); - const list = this[_list]; - let i = 0; - while (i < list.length) { - if (list[i][0] === name) { - ArrayPrototypeSplice(list, i, 1); - } else { + /** + * @param {string} name + * @return {boolean} + */ + has(name) { + webidl.assertBranded(this, URLSearchParamsPrototype); + const prefix = "Failed to execute 'has' on 'URLSearchParams'"; + webidl.requiredArguments(arguments.length, 1, { prefix }); + name = webidl.converters.USVString(name, { + prefix, + context: "Argument 1", + }); + return ArrayPrototypeSome(this[_list], (entry) => entry[0] === name); + } + + /** + * @param {string} name + * @param {string} value + */ + set(name, value) { + webidl.assertBranded(this, URLSearchParamsPrototype); + const prefix = "Failed to execute 'set' on 'URLSearchParams'"; + webidl.requiredArguments(arguments.length, 2, { prefix }); + name = webidl.converters.USVString(name, { + prefix, + context: "Argument 1", + }); + value = webidl.converters.USVString(value, { + prefix, + context: "Argument 2", + }); + + const list = this[_list]; + + // If there are any name-value pairs whose name is name, in list, + // set the value of the first such name-value pair to value + // and remove the others. + let found = false; + let i = 0; + while (i < list.length) { + if (list[i][0] === name) { + if (!found) { + list[i][1] = value; + found = true; i++; + } else { + ArrayPrototypeSplice(list, i, 1); } + } else { + i++; } - this.#updateUrlSearch(); } - /** - * @param {string} name - * @returns {string[]} - */ - getAll(name) { - webidl.assertBranded(this, URLSearchParamsPrototype); - const prefix = "Failed to execute 'getAll' on 'URLSearchParams'"; - webidl.requiredArguments(arguments.length, 1, { prefix }); - name = webidl.converters.USVString(name, { - prefix, - context: "Argument 1", - }); - const values = []; - const entries = this[_list]; - for (let i = 0; i < entries.length; ++i) { - const entry = entries[i]; - if (entry[0] === name) { - ArrayPrototypePush(values, entry[1]); - } - } - return values; + // Otherwise, append a new name-value pair whose name is name + // and value is value, to list. + if (!found) { + ArrayPrototypePush(list, [name, value]); } - /** - * @param {string} name - * @return {string | null} - */ - get(name) { - webidl.assertBranded(this, URLSearchParamsPrototype); - const prefix = "Failed to execute 'get' on 'URLSearchParams'"; - webidl.requiredArguments(arguments.length, 1, { prefix }); - name = webidl.converters.USVString(name, { - prefix, - context: "Argument 1", - }); - const entries = this[_list]; - for (let i = 0; i < entries.length; ++i) { - const entry = entries[i]; - if (entry[0] === name) { - return entry[1]; - } - } - return null; - } + this.#updateUrlSearch(); + } - /** - * @param {string} name - * @return {boolean} - */ - has(name) { - webidl.assertBranded(this, URLSearchParamsPrototype); - const prefix = "Failed to execute 'has' on 'URLSearchParams'"; - webidl.requiredArguments(arguments.length, 1, { prefix }); - name = webidl.converters.USVString(name, { - prefix, - context: "Argument 1", - }); - return ArrayPrototypeSome(this[_list], (entry) => entry[0] === name); - } + sort() { + webidl.assertBranded(this, URLSearchParamsPrototype); + ArrayPrototypeSort( + this[_list], + (a, b) => (a[0] === b[0] ? 0 : a[0] > b[0] ? 1 : -1), + ); + this.#updateUrlSearch(); + } - /** - * @param {string} name - * @param {string} value - */ - set(name, value) { - webidl.assertBranded(this, URLSearchParamsPrototype); - const prefix = "Failed to execute 'set' on 'URLSearchParams'"; - webidl.requiredArguments(arguments.length, 2, { prefix }); - name = webidl.converters.USVString(name, { - prefix, - context: "Argument 1", - }); - value = webidl.converters.USVString(value, { + /** + * @return {string} + */ + toString() { + webidl.assertBranded(this, URLSearchParamsPrototype); + return ops.op_url_stringify_search_params(this[_list]); + } +} + +webidl.mixinPairIterable("URLSearchParams", URLSearchParams, _list, 0, 1); + +webidl.configurePrototype(URLSearchParams); +const URLSearchParamsPrototype = URLSearchParams.prototype; + +webidl.converters["URLSearchParams"] = webidl.createInterfaceConverter( + "URLSearchParams", + URLSearchParamsPrototype, +); + +const _updateUrlSearch = Symbol("updateUrlSearch"); + +function trim(s) { + if (s.length === 1) return ""; + return s; +} + +// Represents a "no port" value. A port in URL cannot be greater than 2^16 − 1 +const NO_PORT = 65536; + +const componentsBuf = new Uint32Array(8); +class URL { + #queryObject = null; + #serialization; + #schemeEnd; + #usernameEnd; + #hostStart; + #hostEnd; + #port; + #pathStart; + #queryStart; + #fragmentStart; + + [_updateUrlSearch](value) { + this.#serialization = opUrlReparse( + this.#serialization, + SET_SEARCH, + value, + ); + this.#updateComponents(); + } + + /** + * @param {string} url + * @param {string} base + */ + constructor(url, base = undefined) { + const prefix = "Failed to construct 'URL'"; + url = webidl.converters.DOMString(url, { prefix, context: "Argument 1" }); + if (base !== undefined) { + base = webidl.converters.DOMString(base, { prefix, context: "Argument 2", }); + } + this[webidl.brand] = webidl.brand; + this.#serialization = opUrlParse(url, base); + this.#updateComponents(); + } - const list = this[_list]; - - // If there are any name-value pairs whose name is name, in list, - // set the value of the first such name-value pair to value - // and remove the others. - let found = false; - let i = 0; - while (i < list.length) { - if (list[i][0] === name) { - if (!found) { - list[i][1] = value; - found = true; - i++; - } else { - ArrayPrototypeSplice(list, i, 1); - } - } else { - i++; - } - } - - // Otherwise, append a new name-value pair whose name is name - // and value is value, to list. - if (!found) { - ArrayPrototypePush(list, [name, value]); - } + #updateComponents() { + ({ + 0: this.#schemeEnd, + 1: this.#usernameEnd, + 2: this.#hostStart, + 3: this.#hostEnd, + 4: this.#port, + 5: this.#pathStart, + 6: this.#queryStart, + 7: this.#fragmentStart, + } = componentsBuf); + } - this.#updateUrlSearch(); - } + [SymbolFor("Deno.privateCustomInspect")](inspect, inspectOptions) { + const object = { + href: this.href, + origin: this.origin, + protocol: this.protocol, + username: this.username, + password: this.password, + host: this.host, + hostname: this.hostname, + port: this.port, + pathname: this.pathname, + hash: this.hash, + search: this.search, + }; + return `${this.constructor.name} ${inspect(object, inspectOptions)}`; + } - sort() { - webidl.assertBranded(this, URLSearchParamsPrototype); - ArrayPrototypeSort( - this[_list], - (a, b) => (a[0] === b[0] ? 0 : a[0] > b[0] ? 1 : -1), + #updateSearchParams() { + if (this.#queryObject !== null) { + const params = this.#queryObject[_list]; + const newParams = ops.op_url_parse_search_params( + StringPrototypeSlice(this.search, 1), + ); + ArrayPrototypeSplice( + params, + 0, + params.length, + ...new SafeArrayIterator(newParams), ); - this.#updateUrlSearch(); - } - - /** - * @return {string} - */ - toString() { - webidl.assertBranded(this, URLSearchParamsPrototype); - return ops.op_url_stringify_search_params(this[_list]); } } - webidl.mixinPairIterable("URLSearchParams", URLSearchParams, _list, 0, 1); - - webidl.configurePrototype(URLSearchParams); - const URLSearchParamsPrototype = URLSearchParams.prototype; - - webidl.converters["URLSearchParams"] = webidl.createInterfaceConverter( - "URLSearchParams", - URLSearchParamsPrototype, - ); - - const _updateUrlSearch = Symbol("updateUrlSearch"); - - function trim(s) { - if (s.length === 1) return ""; - return s; + #hasAuthority() { + // https://github.com/servo/rust-url/blob/1d307ae51a28fecc630ecec03380788bfb03a643/url/src/lib.rs#L824 + return this.#serialization.slice(this.#schemeEnd).startsWith("://"); } - // Represents a "no port" value. A port in URL cannot be greater than 2^16 − 1 - const NO_PORT = 65536; - - const componentsBuf = new Uint32Array(8); - class URL { - #queryObject = null; - #serialization; - #schemeEnd; - #usernameEnd; - #hostStart; - #hostEnd; - #port; - #pathStart; - #queryStart; - #fragmentStart; + /** @return {string} */ + get hash() { + webidl.assertBranded(this, URLPrototype); + // https://github.com/servo/rust-url/blob/1d307ae51a28fecc630ecec03380788bfb03a643/url/src/quirks.rs#L263 + return this.#fragmentStart + ? trim(this.#serialization.slice(this.#fragmentStart)) + : ""; + } - [_updateUrlSearch](value) { + /** @param {string} value */ + set hash(value) { + webidl.assertBranded(this, URLPrototype); + const prefix = "Failed to set 'hash' on 'URL'"; + webidl.requiredArguments(arguments.length, 1, { prefix }); + value = webidl.converters.DOMString(value, { + prefix, + context: "Argument 1", + }); + try { this.#serialization = opUrlReparse( this.#serialization, - SET_SEARCH, + SET_HASH, value, ); this.#updateComponents(); + } catch { + /* pass */ } + } - /** - * @param {string} url - * @param {string} base - */ - constructor(url, base = undefined) { - const prefix = "Failed to construct 'URL'"; - url = webidl.converters.DOMString(url, { prefix, context: "Argument 1" }); - if (base !== undefined) { - base = webidl.converters.DOMString(base, { - prefix, - context: "Argument 2", - }); - } - this[webidl.brand] = webidl.brand; - this.#serialization = opUrlParse(url, base); + /** @return {string} */ + get host() { + webidl.assertBranded(this, URLPrototype); + // https://github.com/servo/rust-url/blob/1d307ae51a28fecc630ecec03380788bfb03a643/url/src/quirks.rs#L101 + return this.#serialization.slice(this.#hostStart, this.#pathStart); + } + + /** @param {string} value */ + set host(value) { + webidl.assertBranded(this, URLPrototype); + const prefix = "Failed to set 'host' on 'URL'"; + webidl.requiredArguments(arguments.length, 1, { prefix }); + value = webidl.converters.DOMString(value, { + prefix, + context: "Argument 1", + }); + try { + this.#serialization = opUrlReparse( + this.#serialization, + SET_HOST, + value, + ); this.#updateComponents(); + } catch { + /* pass */ } + } - #updateComponents() { - ({ - 0: this.#schemeEnd, - 1: this.#usernameEnd, - 2: this.#hostStart, - 3: this.#hostEnd, - 4: this.#port, - 5: this.#pathStart, - 6: this.#queryStart, - 7: this.#fragmentStart, - } = componentsBuf); - } + /** @return {string} */ + get hostname() { + webidl.assertBranded(this, URLPrototype); + // https://github.com/servo/rust-url/blob/1d307ae51a28fecc630ecec03380788bfb03a643/url/src/lib.rs#L988 + return this.#serialization.slice(this.#hostStart, this.#hostEnd); + } - [SymbolFor("Deno.privateCustomInspect")](inspect, inspectOptions) { - const object = { - href: this.href, - origin: this.origin, - protocol: this.protocol, - username: this.username, - password: this.password, - host: this.host, - hostname: this.hostname, - port: this.port, - pathname: this.pathname, - hash: this.hash, - search: this.search, - }; - return `${this.constructor.name} ${inspect(object, inspectOptions)}`; + /** @param {string} value */ + set hostname(value) { + webidl.assertBranded(this, URLPrototype); + const prefix = "Failed to set 'hostname' on 'URL'"; + webidl.requiredArguments(arguments.length, 1, { prefix }); + value = webidl.converters.DOMString(value, { + prefix, + context: "Argument 1", + }); + try { + this.#serialization = opUrlReparse( + this.#serialization, + SET_HOSTNAME, + value, + ); + this.#updateComponents(); + } catch { + /* pass */ } + } - #updateSearchParams() { - if (this.#queryObject !== null) { - const params = this.#queryObject[_list]; - const newParams = ops.op_url_parse_search_params( - StringPrototypeSlice(this.search, 1), - ); - ArrayPrototypeSplice( - params, - 0, - params.length, - ...new SafeArrayIterator(newParams), - ); - } - } + /** @return {string} */ + get href() { + webidl.assertBranded(this, URLPrototype); + return this.#serialization; + } - #hasAuthority() { - // https://github.com/servo/rust-url/blob/1d307ae51a28fecc630ecec03380788bfb03a643/url/src/lib.rs#L824 - return this.#serialization.slice(this.#schemeEnd).startsWith("://"); - } + /** @param {string} value */ + set href(value) { + webidl.assertBranded(this, URLPrototype); + const prefix = "Failed to set 'href' on 'URL'"; + webidl.requiredArguments(arguments.length, 1, { prefix }); + value = webidl.converters.DOMString(value, { + prefix, + context: "Argument 1", + }); + this.#serialization = opUrlParse(value); + this.#updateComponents(); + this.#updateSearchParams(); + } - /** @return {string} */ - get hash() { - webidl.assertBranded(this, URLPrototype); - // https://github.com/servo/rust-url/blob/1d307ae51a28fecc630ecec03380788bfb03a643/url/src/quirks.rs#L263 - return this.#fragmentStart - ? trim(this.#serialization.slice(this.#fragmentStart)) - : ""; + /** @return {string} */ + get origin() { + webidl.assertBranded(this, URLPrototype); + // https://github.com/servo/rust-url/blob/1d307ae51a28fecc630ecec03380788bfb03a643/url/src/origin.rs#L14 + const scheme = this.#serialization.slice(0, this.#schemeEnd); + if ( + scheme === "http" || scheme === "https" || scheme === "ftp" || + scheme === "ws" || scheme === "wss" + ) { + return `${scheme}://${this.host}`; } - /** @param {string} value */ - set hash(value) { - webidl.assertBranded(this, URLPrototype); - const prefix = "Failed to set 'hash' on 'URL'"; - webidl.requiredArguments(arguments.length, 1, { prefix }); - value = webidl.converters.DOMString(value, { - prefix, - context: "Argument 1", - }); + if (scheme === "blob") { + // TODO(@littledivy): Fast path. try { - this.#serialization = opUrlReparse( - this.#serialization, - SET_HASH, - value, - ); - this.#updateComponents(); + return new URL(this.pathname).origin; } catch { - /* pass */ + return "null"; } } - /** @return {string} */ - get host() { - webidl.assertBranded(this, URLPrototype); - // https://github.com/servo/rust-url/blob/1d307ae51a28fecc630ecec03380788bfb03a643/url/src/quirks.rs#L101 - return this.#serialization.slice(this.#hostStart, this.#pathStart); - } - - /** @param {string} value */ - set host(value) { - webidl.assertBranded(this, URLPrototype); - const prefix = "Failed to set 'host' on 'URL'"; - webidl.requiredArguments(arguments.length, 1, { prefix }); - value = webidl.converters.DOMString(value, { - prefix, - context: "Argument 1", - }); - try { - this.#serialization = opUrlReparse( - this.#serialization, - SET_HOST, - value, - ); - this.#updateComponents(); - } catch { - /* pass */ - } - } - - /** @return {string} */ - get hostname() { - webidl.assertBranded(this, URLPrototype); - // https://github.com/servo/rust-url/blob/1d307ae51a28fecc630ecec03380788bfb03a643/url/src/lib.rs#L988 - return this.#serialization.slice(this.#hostStart, this.#hostEnd); - } - - /** @param {string} value */ - set hostname(value) { - webidl.assertBranded(this, URLPrototype); - const prefix = "Failed to set 'hostname' on 'URL'"; - webidl.requiredArguments(arguments.length, 1, { prefix }); - value = webidl.converters.DOMString(value, { - prefix, - context: "Argument 1", - }); - try { - this.#serialization = opUrlReparse( - this.#serialization, - SET_HOSTNAME, - value, - ); - this.#updateComponents(); - } catch { - /* pass */ - } - } + return "null"; + } - /** @return {string} */ - get href() { - webidl.assertBranded(this, URLPrototype); - return this.#serialization; + /** @return {string} */ + get password() { + webidl.assertBranded(this, URLPrototype); + // https://github.com/servo/rust-url/blob/1d307ae51a28fecc630ecec03380788bfb03a643/url/src/lib.rs#L914 + if ( + this.#hasAuthority() && + this.#usernameEnd !== this.#serialization.length && + this.#serialization[this.#usernameEnd] === ":" + ) { + return this.#serialization.slice( + this.#usernameEnd + 1, + this.#hostStart - 1, + ); } + return ""; + } - /** @param {string} value */ - set href(value) { - webidl.assertBranded(this, URLPrototype); - const prefix = "Failed to set 'href' on 'URL'"; - webidl.requiredArguments(arguments.length, 1, { prefix }); - value = webidl.converters.DOMString(value, { - prefix, - context: "Argument 1", - }); - this.#serialization = opUrlParse(value); + /** @param {string} value */ + set password(value) { + webidl.assertBranded(this, URLPrototype); + const prefix = "Failed to set 'password' on 'URL'"; + webidl.requiredArguments(arguments.length, 1, { prefix }); + value = webidl.converters.DOMString(value, { + prefix, + context: "Argument 1", + }); + try { + this.#serialization = opUrlReparse( + this.#serialization, + SET_PASSWORD, + value, + ); this.#updateComponents(); - this.#updateSearchParams(); - } - - /** @return {string} */ - get origin() { - webidl.assertBranded(this, URLPrototype); - // https://github.com/servo/rust-url/blob/1d307ae51a28fecc630ecec03380788bfb03a643/url/src/origin.rs#L14 - const scheme = this.#serialization.slice(0, this.#schemeEnd); - if ( - scheme === "http" || scheme === "https" || scheme === "ftp" || - scheme === "ws" || scheme === "wss" - ) { - return `${scheme}://${this.host}`; - } - - if (scheme === "blob") { - // TODO(@littledivy): Fast path. - try { - return new URL(this.pathname).origin; - } catch { - return "null"; - } - } - - return "null"; - } - - /** @return {string} */ - get password() { - webidl.assertBranded(this, URLPrototype); - // https://github.com/servo/rust-url/blob/1d307ae51a28fecc630ecec03380788bfb03a643/url/src/lib.rs#L914 - if ( - this.#hasAuthority() && - this.#usernameEnd !== this.#serialization.length && - this.#serialization[this.#usernameEnd] === ":" - ) { - return this.#serialization.slice( - this.#usernameEnd + 1, - this.#hostStart - 1, - ); - } - return ""; - } - - /** @param {string} value */ - set password(value) { - webidl.assertBranded(this, URLPrototype); - const prefix = "Failed to set 'password' on 'URL'"; - webidl.requiredArguments(arguments.length, 1, { prefix }); - value = webidl.converters.DOMString(value, { - prefix, - context: "Argument 1", - }); - try { - this.#serialization = opUrlReparse( - this.#serialization, - SET_PASSWORD, - value, - ); - this.#updateComponents(); - } catch { - /* pass */ - } - } - - /** @return {string} */ - get pathname() { - webidl.assertBranded(this, URLPrototype); - // https://github.com/servo/rust-url/blob/1d307ae51a28fecc630ecec03380788bfb03a643/url/src/lib.rs#L1203 - if (!this.#queryStart && !this.#fragmentStart) { - return this.#serialization.slice(this.#pathStart); - } - - const nextComponentStart = this.#queryStart || this.#fragmentStart; - return this.#serialization.slice(this.#pathStart, nextComponentStart); + } catch { + /* pass */ } + } - /** @param {string} value */ - set pathname(value) { - webidl.assertBranded(this, URLPrototype); - const prefix = "Failed to set 'pathname' on 'URL'"; - webidl.requiredArguments(arguments.length, 1, { prefix }); - value = webidl.converters.DOMString(value, { - prefix, - context: "Argument 1", - }); - try { - this.#serialization = opUrlReparse( - this.#serialization, - SET_PATHNAME, - value, - ); - this.#updateComponents(); - } catch { - /* pass */ - } + /** @return {string} */ + get pathname() { + webidl.assertBranded(this, URLPrototype); + // https://github.com/servo/rust-url/blob/1d307ae51a28fecc630ecec03380788bfb03a643/url/src/lib.rs#L1203 + if (!this.#queryStart && !this.#fragmentStart) { + return this.#serialization.slice(this.#pathStart); } - /** @return {string} */ - get port() { - webidl.assertBranded(this, URLPrototype); - // https://github.com/servo/rust-url/blob/1d307ae51a28fecc630ecec03380788bfb03a643/url/src/quirks.rs#L196 - if (this.#port === NO_PORT) { - return this.#serialization.slice(this.#hostEnd, this.#pathStart); - } else { - return this.#serialization.slice( - this.#hostEnd + 1, /* : */ - this.#pathStart, - ); - } - } + const nextComponentStart = this.#queryStart || this.#fragmentStart; + return this.#serialization.slice(this.#pathStart, nextComponentStart); + } - /** @param {string} value */ - set port(value) { - webidl.assertBranded(this, URLPrototype); - const prefix = "Failed to set 'port' on 'URL'"; - webidl.requiredArguments(arguments.length, 1, { prefix }); - value = webidl.converters.DOMString(value, { - prefix, - context: "Argument 1", - }); - try { - this.#serialization = opUrlReparse( - this.#serialization, - SET_PORT, - value, - ); - this.#updateComponents(); - } catch { - /* pass */ - } + /** @param {string} value */ + set pathname(value) { + webidl.assertBranded(this, URLPrototype); + const prefix = "Failed to set 'pathname' on 'URL'"; + webidl.requiredArguments(arguments.length, 1, { prefix }); + value = webidl.converters.DOMString(value, { + prefix, + context: "Argument 1", + }); + try { + this.#serialization = opUrlReparse( + this.#serialization, + SET_PATHNAME, + value, + ); + this.#updateComponents(); + } catch { + /* pass */ } + } - /** @return {string} */ - get protocol() { - webidl.assertBranded(this, URLPrototype); - // https://github.com/servo/rust-url/blob/1d307ae51a28fecc630ecec03380788bfb03a643/url/src/quirks.rs#L56 - return this.#serialization.slice(0, this.#schemeEnd + 1 /* : */); + /** @return {string} */ + get port() { + webidl.assertBranded(this, URLPrototype); + // https://github.com/servo/rust-url/blob/1d307ae51a28fecc630ecec03380788bfb03a643/url/src/quirks.rs#L196 + if (this.#port === NO_PORT) { + return this.#serialization.slice(this.#hostEnd, this.#pathStart); + } else { + return this.#serialization.slice( + this.#hostEnd + 1, /* : */ + this.#pathStart, + ); } + } - /** @param {string} value */ - set protocol(value) { - webidl.assertBranded(this, URLPrototype); - const prefix = "Failed to set 'protocol' on 'URL'"; - webidl.requiredArguments(arguments.length, 1, { prefix }); - value = webidl.converters.DOMString(value, { - prefix, - context: "Argument 1", - }); - try { - this.#serialization = opUrlReparse( - this.#serialization, - SET_PROTOCOL, - value, - ); - this.#updateComponents(); - } catch { - /* pass */ - } + /** @param {string} value */ + set port(value) { + webidl.assertBranded(this, URLPrototype); + const prefix = "Failed to set 'port' on 'URL'"; + webidl.requiredArguments(arguments.length, 1, { prefix }); + value = webidl.converters.DOMString(value, { + prefix, + context: "Argument 1", + }); + try { + this.#serialization = opUrlReparse( + this.#serialization, + SET_PORT, + value, + ); + this.#updateComponents(); + } catch { + /* pass */ } + } - /** @return {string} */ - get search() { - webidl.assertBranded(this, URLPrototype); - // https://github.com/servo/rust-url/blob/1d307ae51a28fecc630ecec03380788bfb03a643/url/src/quirks.rs#L249 - const afterPath = this.#queryStart || this.#fragmentStart || - this.#serialization.length; - const afterQuery = this.#fragmentStart || this.#serialization.length; - return trim(this.#serialization.slice(afterPath, afterQuery)); - } + /** @return {string} */ + get protocol() { + webidl.assertBranded(this, URLPrototype); + // https://github.com/servo/rust-url/blob/1d307ae51a28fecc630ecec03380788bfb03a643/url/src/quirks.rs#L56 + return this.#serialization.slice(0, this.#schemeEnd + 1 /* : */); + } - /** @param {string} value */ - set search(value) { - webidl.assertBranded(this, URLPrototype); - const prefix = "Failed to set 'search' on 'URL'"; - webidl.requiredArguments(arguments.length, 1, { prefix }); - value = webidl.converters.DOMString(value, { - prefix, - context: "Argument 1", - }); - try { - this.#serialization = opUrlReparse( - this.#serialization, - SET_SEARCH, - value, - ); - this.#updateComponents(); - this.#updateSearchParams(); - } catch { - /* pass */ - } + /** @param {string} value */ + set protocol(value) { + webidl.assertBranded(this, URLPrototype); + const prefix = "Failed to set 'protocol' on 'URL'"; + webidl.requiredArguments(arguments.length, 1, { prefix }); + value = webidl.converters.DOMString(value, { + prefix, + context: "Argument 1", + }); + try { + this.#serialization = opUrlReparse( + this.#serialization, + SET_PROTOCOL, + value, + ); + this.#updateComponents(); + } catch { + /* pass */ } + } - /** @return {string} */ - get username() { - webidl.assertBranded(this, URLPrototype); - // https://github.com/servo/rust-url/blob/1d307ae51a28fecc630ecec03380788bfb03a643/url/src/lib.rs#L881 - const schemeSeperatorLen = 3; /* :// */ - if ( - this.#hasAuthority() && - this.#usernameEnd > this.#schemeEnd + schemeSeperatorLen - ) { - return this.#serialization.slice( - this.#schemeEnd + schemeSeperatorLen, - this.#usernameEnd, - ); - } else { - return ""; - } - } + /** @return {string} */ + get search() { + webidl.assertBranded(this, URLPrototype); + // https://github.com/servo/rust-url/blob/1d307ae51a28fecc630ecec03380788bfb03a643/url/src/quirks.rs#L249 + const afterPath = this.#queryStart || this.#fragmentStart || + this.#serialization.length; + const afterQuery = this.#fragmentStart || this.#serialization.length; + return trim(this.#serialization.slice(afterPath, afterQuery)); + } - /** @param {string} value */ - set username(value) { - webidl.assertBranded(this, URLPrototype); - const prefix = "Failed to set 'username' on 'URL'"; - webidl.requiredArguments(arguments.length, 1, { prefix }); - value = webidl.converters.DOMString(value, { - prefix, - context: "Argument 1", - }); - try { - this.#serialization = opUrlReparse( - this.#serialization, - SET_USERNAME, - value, - ); - this.#updateComponents(); - } catch { - /* pass */ - } + /** @param {string} value */ + set search(value) { + webidl.assertBranded(this, URLPrototype); + const prefix = "Failed to set 'search' on 'URL'"; + webidl.requiredArguments(arguments.length, 1, { prefix }); + value = webidl.converters.DOMString(value, { + prefix, + context: "Argument 1", + }); + try { + this.#serialization = opUrlReparse( + this.#serialization, + SET_SEARCH, + value, + ); + this.#updateComponents(); + this.#updateSearchParams(); + } catch { + /* pass */ } + } - /** @return {string} */ - get searchParams() { - if (this.#queryObject == null) { - this.#queryObject = new URLSearchParams(this.search); - this.#queryObject[_urlObject] = this; - } - return this.#queryObject; + /** @return {string} */ + get username() { + webidl.assertBranded(this, URLPrototype); + // https://github.com/servo/rust-url/blob/1d307ae51a28fecc630ecec03380788bfb03a643/url/src/lib.rs#L881 + const schemeSeperatorLen = 3; /* :// */ + if ( + this.#hasAuthority() && + this.#usernameEnd > this.#schemeEnd + schemeSeperatorLen + ) { + return this.#serialization.slice( + this.#schemeEnd + schemeSeperatorLen, + this.#usernameEnd, + ); + } else { + return ""; } + } - /** @return {string} */ - toString() { - webidl.assertBranded(this, URLPrototype); - return this.#serialization; + /** @param {string} value */ + set username(value) { + webidl.assertBranded(this, URLPrototype); + const prefix = "Failed to set 'username' on 'URL'"; + webidl.requiredArguments(arguments.length, 1, { prefix }); + value = webidl.converters.DOMString(value, { + prefix, + context: "Argument 1", + }); + try { + this.#serialization = opUrlReparse( + this.#serialization, + SET_USERNAME, + value, + ); + this.#updateComponents(); + } catch { + /* pass */ } + } - /** @return {string} */ - toJSON() { - webidl.assertBranded(this, URLPrototype); - return this.#serialization; + /** @return {string} */ + get searchParams() { + if (this.#queryObject == null) { + this.#queryObject = new URLSearchParams(this.search); + this.#queryObject[_urlObject] = this; } + return this.#queryObject; } - webidl.configurePrototype(URL); - const URLPrototype = URL.prototype; + /** @return {string} */ + toString() { + webidl.assertBranded(this, URLPrototype); + return this.#serialization; + } - /** - * This function implements application/x-www-form-urlencoded parsing. - * https://url.spec.whatwg.org/#concept-urlencoded-parser - * @param {Uint8Array} bytes - * @returns {[string, string][]} - */ - function parseUrlEncoded(bytes) { - return ops.op_url_parse_search_params(null, bytes); - } - - webidl - .converters[ - "sequence> or record or USVString" - ] = (V, opts) => { - // Union for (sequence> or record or USVString) - if (webidl.type(V) === "Object" && V !== null) { - if (V[SymbolIterator] !== undefined) { - return webidl.converters["sequence>"](V, opts); - } - return webidl.converters["record"](V, opts); + /** @return {string} */ + toJSON() { + webidl.assertBranded(this, URLPrototype); + return this.#serialization; + } +} + +webidl.configurePrototype(URL); +const URLPrototype = URL.prototype; + +/** + * This function implements application/x-www-form-urlencoded parsing. + * https://url.spec.whatwg.org/#concept-urlencoded-parser + * @param {Uint8Array} bytes + * @returns {[string, string][]} + */ +function parseUrlEncoded(bytes) { + return ops.op_url_parse_search_params(null, bytes); +} + +webidl + .converters[ + "sequence> or record or USVString" + ] = (V, opts) => { + // Union for (sequence> or record or USVString) + if (webidl.type(V) === "Object" && V !== null) { + if (V[SymbolIterator] !== undefined) { + return webidl.converters["sequence>"](V, opts); } - return webidl.converters.USVString(V, opts); - }; - - window.__bootstrap.url = { - URL, - URLPrototype, - URLSearchParams, - URLSearchParamsPrototype, - parseUrlEncoded, + return webidl.converters["record"](V, opts); + } + return webidl.converters.USVString(V, opts); }; -})(this); + +export { + parseUrlEncoded, + URL, + URLPrototype, + URLSearchParams, + URLSearchParamsPrototype, +}; diff --git a/ext/url/01_urlpattern.js b/ext/url/01_urlpattern.js index 14f0525514e8f3..c311b5abca36e5 100644 --- a/ext/url/01_urlpattern.js +++ b/ext/url/01_urlpattern.js @@ -7,268 +7,263 @@ /// /// -"use strict"; - -((window) => { - const core = window.Deno.core; - const ops = core.ops; - const webidl = window.__bootstrap.webidl; - const { - ArrayPrototypeMap, - ObjectKeys, - ObjectFromEntries, - RegExp, - RegExpPrototypeExec, - RegExpPrototypeTest, - Symbol, - SymbolFor, - TypeError, - } = window.__bootstrap.primordials; - - const _components = Symbol("components"); +const core = globalThis.Deno.core; +const ops = core.ops; +import * as webidl from "internal:deno_webidl/00_webidl.js"; +const primordials = globalThis.__bootstrap.primordials; +const { + ArrayPrototypeMap, + ObjectKeys, + ObjectFromEntries, + RegExp, + RegExpPrototypeExec, + RegExpPrototypeTest, + Symbol, + SymbolFor, + TypeError, +} = primordials; + +const _components = Symbol("components"); + +/** + * @typedef Components + * @property {Component} protocol + * @property {Component} username + * @property {Component} password + * @property {Component} hostname + * @property {Component} port + * @property {Component} pathname + * @property {Component} search + * @property {Component} hash + */ + +/** + * @typedef Component + * @property {string} patternString + * @property {RegExp} regexp + * @property {string[]} groupNameList + */ + +class URLPattern { + /** @type {Components} */ + [_components]; /** - * @typedef Components - * @property {Component} protocol - * @property {Component} username - * @property {Component} password - * @property {Component} hostname - * @property {Component} port - * @property {Component} pathname - * @property {Component} search - * @property {Component} hash + * @param {URLPatternInput} input + * @param {string} [baseURL] */ - - /** - * @typedef Component - * @property {string} patternString - * @property {RegExp} regexp - * @property {string[]} groupNameList - */ - - class URLPattern { - /** @type {Components} */ - [_components]; - - /** - * @param {URLPatternInput} input - * @param {string} [baseURL] - */ - constructor(input, baseURL = undefined) { - this[webidl.brand] = webidl.brand; - const prefix = "Failed to construct 'URLPattern'"; - webidl.requiredArguments(arguments.length, 1, { prefix }); - input = webidl.converters.URLPatternInput(input, { + constructor(input, baseURL = undefined) { + this[webidl.brand] = webidl.brand; + const prefix = "Failed to construct 'URLPattern'"; + webidl.requiredArguments(arguments.length, 1, { prefix }); + input = webidl.converters.URLPatternInput(input, { + prefix, + context: "Argument 1", + }); + if (baseURL !== undefined) { + baseURL = webidl.converters.USVString(baseURL, { prefix, - context: "Argument 1", + context: "Argument 2", }); - if (baseURL !== undefined) { - baseURL = webidl.converters.USVString(baseURL, { - prefix, - context: "Argument 2", - }); - } + } - const components = ops.op_urlpattern_parse(input, baseURL); - - const keys = ObjectKeys(components); - for (let i = 0; i < keys.length; ++i) { - const key = keys[i]; - try { - components[key].regexp = new RegExp( - components[key].regexpString, - "u", - ); - } catch (e) { - throw new TypeError(`${prefix}: ${key} is invalid; ${e.message}`); - } - } + const components = ops.op_urlpattern_parse(input, baseURL); - this[_components] = components; + const keys = ObjectKeys(components); + for (let i = 0; i < keys.length; ++i) { + const key = keys[i]; + try { + components[key].regexp = new RegExp( + components[key].regexpString, + "u", + ); + } catch (e) { + throw new TypeError(`${prefix}: ${key} is invalid; ${e.message}`); + } } - get protocol() { - webidl.assertBranded(this, URLPatternPrototype); - return this[_components].protocol.patternString; - } + this[_components] = components; + } - get username() { - webidl.assertBranded(this, URLPatternPrototype); - return this[_components].username.patternString; - } + get protocol() { + webidl.assertBranded(this, URLPatternPrototype); + return this[_components].protocol.patternString; + } - get password() { - webidl.assertBranded(this, URLPatternPrototype); - return this[_components].password.patternString; - } + get username() { + webidl.assertBranded(this, URLPatternPrototype); + return this[_components].username.patternString; + } - get hostname() { - webidl.assertBranded(this, URLPatternPrototype); - return this[_components].hostname.patternString; - } + get password() { + webidl.assertBranded(this, URLPatternPrototype); + return this[_components].password.patternString; + } - get port() { - webidl.assertBranded(this, URLPatternPrototype); - return this[_components].port.patternString; - } + get hostname() { + webidl.assertBranded(this, URLPatternPrototype); + return this[_components].hostname.patternString; + } - get pathname() { - webidl.assertBranded(this, URLPatternPrototype); - return this[_components].pathname.patternString; - } + get port() { + webidl.assertBranded(this, URLPatternPrototype); + return this[_components].port.patternString; + } - get search() { - webidl.assertBranded(this, URLPatternPrototype); - return this[_components].search.patternString; - } + get pathname() { + webidl.assertBranded(this, URLPatternPrototype); + return this[_components].pathname.patternString; + } - get hash() { - webidl.assertBranded(this, URLPatternPrototype); - return this[_components].hash.patternString; - } + get search() { + webidl.assertBranded(this, URLPatternPrototype); + return this[_components].search.patternString; + } + + get hash() { + webidl.assertBranded(this, URLPatternPrototype); + return this[_components].hash.patternString; + } - /** - * @param {URLPatternInput} input - * @param {string} [baseURL] - * @returns {boolean} - */ - test(input, baseURL = undefined) { - webidl.assertBranded(this, URLPatternPrototype); - const prefix = "Failed to execute 'test' on 'URLPattern'"; - webidl.requiredArguments(arguments.length, 1, { prefix }); - input = webidl.converters.URLPatternInput(input, { + /** + * @param {URLPatternInput} input + * @param {string} [baseURL] + * @returns {boolean} + */ + test(input, baseURL = undefined) { + webidl.assertBranded(this, URLPatternPrototype); + const prefix = "Failed to execute 'test' on 'URLPattern'"; + webidl.requiredArguments(arguments.length, 1, { prefix }); + input = webidl.converters.URLPatternInput(input, { + prefix, + context: "Argument 1", + }); + if (baseURL !== undefined) { + baseURL = webidl.converters.USVString(baseURL, { prefix, - context: "Argument 1", + context: "Argument 2", }); - if (baseURL !== undefined) { - baseURL = webidl.converters.USVString(baseURL, { - prefix, - context: "Argument 2", - }); - } + } - const res = ops.op_urlpattern_process_match_input( - input, - baseURL, - ); - if (res === null) { - return false; - } + const res = ops.op_urlpattern_process_match_input( + input, + baseURL, + ); + if (res === null) { + return false; + } - const values = res[0]; + const values = res[0]; - const keys = ObjectKeys(values); - for (let i = 0; i < keys.length; ++i) { - const key = keys[i]; - if (!RegExpPrototypeTest(this[_components][key].regexp, values[key])) { - return false; - } + const keys = ObjectKeys(values); + for (let i = 0; i < keys.length; ++i) { + const key = keys[i]; + if (!RegExpPrototypeTest(this[_components][key].regexp, values[key])) { + return false; } - - return true; } - /** - * @param {URLPatternInput} input - * @param {string} [baseURL] - * @returns {URLPatternResult | null} - */ - exec(input, baseURL = undefined) { - webidl.assertBranded(this, URLPatternPrototype); - const prefix = "Failed to execute 'exec' on 'URLPattern'"; - webidl.requiredArguments(arguments.length, 1, { prefix }); - input = webidl.converters.URLPatternInput(input, { + return true; + } + + /** + * @param {URLPatternInput} input + * @param {string} [baseURL] + * @returns {URLPatternResult | null} + */ + exec(input, baseURL = undefined) { + webidl.assertBranded(this, URLPatternPrototype); + const prefix = "Failed to execute 'exec' on 'URLPattern'"; + webidl.requiredArguments(arguments.length, 1, { prefix }); + input = webidl.converters.URLPatternInput(input, { + prefix, + context: "Argument 1", + }); + if (baseURL !== undefined) { + baseURL = webidl.converters.USVString(baseURL, { prefix, - context: "Argument 1", + context: "Argument 2", }); - if (baseURL !== undefined) { - baseURL = webidl.converters.USVString(baseURL, { - prefix, - context: "Argument 2", - }); - } + } - const res = ops.op_urlpattern_process_match_input( - input, - baseURL, - ); - if (res === null) { - return null; - } + const res = ops.op_urlpattern_process_match_input( + input, + baseURL, + ); + if (res === null) { + return null; + } - const { 0: values, 1: inputs } = res; - if (inputs[1] === null) { - inputs.pop(); - } + const { 0: values, 1: inputs } = res; + if (inputs[1] === null) { + inputs.pop(); + } - /** @type {URLPatternResult} */ - const result = { inputs }; - - const keys = ObjectKeys(values); - for (let i = 0; i < keys.length; ++i) { - const key = keys[i]; - /** @type {Component} */ - const component = this[_components][key]; - const input = values[key]; - const match = RegExpPrototypeExec(component.regexp, input); - if (match === null) { - return null; - } - const groupEntries = ArrayPrototypeMap( - component.groupNameList, - (name, i) => [name, match[i + 1] ?? ""], - ); - const groups = ObjectFromEntries(groupEntries); - result[key] = { - input, - groups, - }; + /** @type {URLPatternResult} */ + const result = { inputs }; + + const keys = ObjectKeys(values); + for (let i = 0; i < keys.length; ++i) { + const key = keys[i]; + /** @type {Component} */ + const component = this[_components][key]; + const input = values[key]; + const match = RegExpPrototypeExec(component.regexp, input); + if (match === null) { + return null; } - - return result; + const groupEntries = ArrayPrototypeMap( + component.groupNameList, + (name, i) => [name, match[i + 1] ?? ""], + ); + const groups = ObjectFromEntries(groupEntries); + result[key] = { + input, + groups, + }; } - [SymbolFor("Deno.customInspect")](inspect) { - return `URLPattern ${ - inspect({ - protocol: this.protocol, - username: this.username, - password: this.password, - hostname: this.hostname, - port: this.port, - pathname: this.pathname, - search: this.search, - hash: this.hash, - }) - }`; - } + return result; } - webidl.configurePrototype(URLPattern); - const URLPatternPrototype = URLPattern.prototype; - - webidl.converters.URLPatternInit = webidl - .createDictionaryConverter("URLPatternInit", [ - { key: "protocol", converter: webidl.converters.USVString }, - { key: "username", converter: webidl.converters.USVString }, - { key: "password", converter: webidl.converters.USVString }, - { key: "hostname", converter: webidl.converters.USVString }, - { key: "port", converter: webidl.converters.USVString }, - { key: "pathname", converter: webidl.converters.USVString }, - { key: "search", converter: webidl.converters.USVString }, - { key: "hash", converter: webidl.converters.USVString }, - { key: "baseURL", converter: webidl.converters.USVString }, - ]); - - webidl.converters["URLPatternInput"] = (V, opts) => { - // Union for (URLPatternInit or USVString) - if (typeof V == "object") { - return webidl.converters.URLPatternInit(V, opts); - } - return webidl.converters.USVString(V, opts); - }; + [SymbolFor("Deno.customInspect")](inspect) { + return `URLPattern ${ + inspect({ + protocol: this.protocol, + username: this.username, + password: this.password, + hostname: this.hostname, + port: this.port, + pathname: this.pathname, + search: this.search, + hash: this.hash, + }) + }`; + } +} + +webidl.configurePrototype(URLPattern); +const URLPatternPrototype = URLPattern.prototype; + +webidl.converters.URLPatternInit = webidl + .createDictionaryConverter("URLPatternInit", [ + { key: "protocol", converter: webidl.converters.USVString }, + { key: "username", converter: webidl.converters.USVString }, + { key: "password", converter: webidl.converters.USVString }, + { key: "hostname", converter: webidl.converters.USVString }, + { key: "port", converter: webidl.converters.USVString }, + { key: "pathname", converter: webidl.converters.USVString }, + { key: "search", converter: webidl.converters.USVString }, + { key: "hash", converter: webidl.converters.USVString }, + { key: "baseURL", converter: webidl.converters.USVString }, + ]); + +webidl.converters["URLPatternInput"] = (V, opts) => { + // Union for (URLPatternInit or USVString) + if (typeof V == "object") { + return webidl.converters.URLPatternInit(V, opts); + } + return webidl.converters.USVString(V, opts); +}; - window.__bootstrap.urlPattern = { - URLPattern, - }; -})(globalThis); +export { URLPattern }; diff --git a/ext/url/Cargo.toml b/ext/url/Cargo.toml index d833ab533dcb57..3ec46f9704764e 100644 --- a/ext/url/Cargo.toml +++ b/ext/url/Cargo.toml @@ -2,7 +2,7 @@ [package] name = "deno_url" -version = "0.89.0" +version = "0.91.0" authors.workspace = true edition.workspace = true license.workspace = true diff --git a/ext/url/benches/url_ops.rs b/ext/url/benches/url_ops.rs index 5a5997fc828390..828b022978b5a3 100644 --- a/ext/url/benches/url_ops.rs +++ b/ext/url/benches/url_ops.rs @@ -6,16 +6,22 @@ use deno_bench_util::bencher::benchmark_group; use deno_bench_util::bencher::Bencher; use deno_core::Extension; +use deno_core::ExtensionFileSource; +use deno_core::ExtensionFileSourceCode; fn setup() -> Vec { vec![ deno_webidl::init(), deno_url::init(), Extension::builder("bench_setup") - .js(vec![( - "setup", - "const { URL } = globalThis.__bootstrap.url;", - )]) + .esm(vec![ExtensionFileSource { + specifier: "internal:setup".to_string(), + code: ExtensionFileSourceCode::IncludedInBinary( + r#"import { URL } from "internal:deno_url/00_url.js"; + globalThis.URL = URL; + "#, + ), + }]) .build(), ] } diff --git a/ext/url/internal.d.ts b/ext/url/internal.d.ts index 7065c432f4617c..5859996699dcc1 100644 --- a/ext/url/internal.d.ts +++ b/ext/url/internal.d.ts @@ -1,20 +1,14 @@ // Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. -// deno-lint-ignore-file no-var - /// /// -declare namespace globalThis { - declare namespace __bootstrap { - declare var url: { - URL: typeof URL; - URLSearchParams: typeof URLSearchParams; - parseUrlEncoded(bytes: Uint8Array): [string, string][]; - }; +declare module "internal:deno_url/00_url.js" { + const URL: typeof URL; + const URLSearchParams: typeof URLSearchParams; + function parseUrlEncoded(bytes: Uint8Array): [string, string][]; +} - declare var urlPattern: { - URLPattern: typeof URLPattern; - }; - } +declare module "internal:deno_url/01_urlpattern.js" { + const URLPattern: typeof URLPattern; } diff --git a/ext/url/lib.rs b/ext/url/lib.rs index 064590f290dc6d..087ccbfe72d4eb 100644 --- a/ext/url/lib.rs +++ b/ext/url/lib.rs @@ -20,11 +20,7 @@ use crate::urlpattern::op_urlpattern_process_match_input; pub fn init() -> Extension { Extension::builder(env!("CARGO_PKG_NAME")) .dependencies(vec!["deno_webidl"]) - .js(include_js_files!( - prefix "internal:ext/url", - "00_url.js", - "01_urlpattern.js", - )) + .esm(include_js_files!("00_url.js", "01_urlpattern.js",)) .ops(vec![ op_url_reparse::decl(), op_url_parse::decl(), diff --git a/ext/web/00_infra.js b/ext/web/00_infra.js index 3f3f98165fd9fa..0a8491563c4111 100644 --- a/ext/web/00_infra.js +++ b/ext/web/00_infra.js @@ -6,334 +6,387 @@ /// /// -"use strict"; +const core = globalThis.Deno.core; +const ops = core.ops; +const primordials = globalThis.__bootstrap.primordials; +const { + ArrayPrototypeJoin, + ArrayPrototypeMap, + Error, + JSONStringify, + NumberPrototypeToString, + RegExp, + SafeArrayIterator, + String, + StringPrototypeCharAt, + StringPrototypeCharCodeAt, + StringPrototypeMatch, + StringPrototypePadStart, + StringPrototypeReplace, + StringPrototypeSlice, + StringPrototypeSubstring, + StringPrototypeToLowerCase, + StringPrototypeToUpperCase, + TypeError, +} = primordials; -((window) => { - const core = Deno.core; - const ops = core.ops; - const { - ArrayPrototypeJoin, - ArrayPrototypeMap, - Error, - JSONStringify, - NumberPrototypeToString, - RegExp, - SafeArrayIterator, - String, - StringPrototypeCharAt, - StringPrototypeCharCodeAt, - StringPrototypeMatch, - StringPrototypePadStart, - StringPrototypeReplace, - StringPrototypeSlice, - StringPrototypeSubstring, - StringPrototypeToLowerCase, - StringPrototypeToUpperCase, - TypeError, - } = window.__bootstrap.primordials; +const ASCII_DIGIT = ["\u0030-\u0039"]; +const ASCII_UPPER_ALPHA = ["\u0041-\u005A"]; +const ASCII_LOWER_ALPHA = ["\u0061-\u007A"]; +const ASCII_ALPHA = [ + ...new SafeArrayIterator(ASCII_UPPER_ALPHA), + ...new SafeArrayIterator(ASCII_LOWER_ALPHA), +]; +const ASCII_ALPHANUMERIC = [ + ...new SafeArrayIterator(ASCII_DIGIT), + ...new SafeArrayIterator(ASCII_ALPHA), +]; - const ASCII_DIGIT = ["\u0030-\u0039"]; - const ASCII_UPPER_ALPHA = ["\u0041-\u005A"]; - const ASCII_LOWER_ALPHA = ["\u0061-\u007A"]; - const ASCII_ALPHA = [ - ...new SafeArrayIterator(ASCII_UPPER_ALPHA), - ...new SafeArrayIterator(ASCII_LOWER_ALPHA), - ]; - const ASCII_ALPHANUMERIC = [ - ...new SafeArrayIterator(ASCII_DIGIT), - ...new SafeArrayIterator(ASCII_ALPHA), - ]; +const HTTP_TAB_OR_SPACE = ["\u0009", "\u0020"]; +const HTTP_WHITESPACE = [ + "\u000A", + "\u000D", + ...new SafeArrayIterator(HTTP_TAB_OR_SPACE), +]; - const HTTP_TAB_OR_SPACE = ["\u0009", "\u0020"]; - const HTTP_WHITESPACE = [ - "\u000A", - "\u000D", - ...new SafeArrayIterator(HTTP_TAB_OR_SPACE), - ]; +const HTTP_TOKEN_CODE_POINT = [ + "\u0021", + "\u0023", + "\u0024", + "\u0025", + "\u0026", + "\u0027", + "\u002A", + "\u002B", + "\u002D", + "\u002E", + "\u005E", + "\u005F", + "\u0060", + "\u007C", + "\u007E", + ...new SafeArrayIterator(ASCII_ALPHANUMERIC), +]; +const HTTP_TOKEN_CODE_POINT_RE = new RegExp( + `^[${regexMatcher(HTTP_TOKEN_CODE_POINT)}]+$`, +); +const HTTP_QUOTED_STRING_TOKEN_POINT = [ + "\u0009", + "\u0020-\u007E", + "\u0080-\u00FF", +]; +const HTTP_QUOTED_STRING_TOKEN_POINT_RE = new RegExp( + `^[${regexMatcher(HTTP_QUOTED_STRING_TOKEN_POINT)}]+$`, +); +const HTTP_TAB_OR_SPACE_MATCHER = regexMatcher(HTTP_TAB_OR_SPACE); +const HTTP_TAB_OR_SPACE_PREFIX_RE = new RegExp( + `^[${HTTP_TAB_OR_SPACE_MATCHER}]+`, + "g", +); +const HTTP_TAB_OR_SPACE_SUFFIX_RE = new RegExp( + `[${HTTP_TAB_OR_SPACE_MATCHER}]+$`, + "g", +); +const HTTP_WHITESPACE_MATCHER = regexMatcher(HTTP_WHITESPACE); +const HTTP_BETWEEN_WHITESPACE = new RegExp( + `^[${HTTP_WHITESPACE_MATCHER}]*(.*?)[${HTTP_WHITESPACE_MATCHER}]*$`, +); +const HTTP_WHITESPACE_PREFIX_RE = new RegExp( + `^[${HTTP_WHITESPACE_MATCHER}]+`, + "g", +); +const HTTP_WHITESPACE_SUFFIX_RE = new RegExp( + `[${HTTP_WHITESPACE_MATCHER}]+$`, + "g", +); - const HTTP_TOKEN_CODE_POINT = [ - "\u0021", - "\u0023", - "\u0024", - "\u0025", - "\u0026", - "\u0027", - "\u002A", - "\u002B", - "\u002D", - "\u002E", - "\u005E", - "\u005F", - "\u0060", - "\u007C", - "\u007E", - ...new SafeArrayIterator(ASCII_ALPHANUMERIC), - ]; - const HTTP_TOKEN_CODE_POINT_RE = new RegExp( - `^[${regexMatcher(HTTP_TOKEN_CODE_POINT)}]+$`, - ); - const HTTP_QUOTED_STRING_TOKEN_POINT = [ - "\u0009", - "\u0020-\u007E", - "\u0080-\u00FF", - ]; - const HTTP_QUOTED_STRING_TOKEN_POINT_RE = new RegExp( - `^[${regexMatcher(HTTP_QUOTED_STRING_TOKEN_POINT)}]+$`, - ); - const HTTP_TAB_OR_SPACE_MATCHER = regexMatcher(HTTP_TAB_OR_SPACE); - const HTTP_TAB_OR_SPACE_PREFIX_RE = new RegExp( - `^[${HTTP_TAB_OR_SPACE_MATCHER}]+`, - "g", - ); - const HTTP_TAB_OR_SPACE_SUFFIX_RE = new RegExp( - `[${HTTP_TAB_OR_SPACE_MATCHER}]+$`, - "g", - ); - const HTTP_WHITESPACE_MATCHER = regexMatcher(HTTP_WHITESPACE); - const HTTP_BETWEEN_WHITESPACE = new RegExp( - `^[${HTTP_WHITESPACE_MATCHER}]*(.*?)[${HTTP_WHITESPACE_MATCHER}]*$`, - ); - const HTTP_WHITESPACE_PREFIX_RE = new RegExp( - `^[${HTTP_WHITESPACE_MATCHER}]+`, - "g", - ); - const HTTP_WHITESPACE_SUFFIX_RE = new RegExp( - `[${HTTP_WHITESPACE_MATCHER}]+$`, - "g", +/** + * Turn a string of chars into a regex safe matcher. + * @param {string[]} chars + * @returns {string} + */ +function regexMatcher(chars) { + const matchers = ArrayPrototypeMap(chars, (char) => { + if (char.length === 1) { + const a = StringPrototypePadStart( + NumberPrototypeToString(StringPrototypeCharCodeAt(char, 0), 16), + 4, + "0", + ); + return `\\u${a}`; + } else if (char.length === 3 && char[1] === "-") { + const a = StringPrototypePadStart( + NumberPrototypeToString(StringPrototypeCharCodeAt(char, 0), 16), + 4, + "0", + ); + const b = StringPrototypePadStart( + NumberPrototypeToString(StringPrototypeCharCodeAt(char, 2), 16), + 4, + "0", + ); + return `\\u${a}-\\u${b}`; + } else { + throw TypeError("unreachable"); + } + }); + return ArrayPrototypeJoin(matchers, ""); +} + +/** + * https://infra.spec.whatwg.org/#collect-a-sequence-of-code-points + * @param {string} input + * @param {number} position + * @param {(char: string) => boolean} condition + * @returns {{result: string, position: number}} + */ +function collectSequenceOfCodepoints(input, position, condition) { + const start = position; + for ( + let c = StringPrototypeCharAt(input, position); + position < input.length && condition(c); + c = StringPrototypeCharAt(input, ++position) ); + return { result: StringPrototypeSlice(input, start, position), position }; +} - /** - * Turn a string of chars into a regex safe matcher. - * @param {string[]} chars - * @returns {string} - */ - function regexMatcher(chars) { - const matchers = ArrayPrototypeMap(chars, (char) => { - if (char.length === 1) { - const a = StringPrototypePadStart( - NumberPrototypeToString(StringPrototypeCharCodeAt(char, 0), 16), - 4, - "0", - ); - return `\\u${a}`; - } else if (char.length === 3 && char[1] === "-") { - const a = StringPrototypePadStart( - NumberPrototypeToString(StringPrototypeCharCodeAt(char, 0), 16), - 4, - "0", - ); - const b = StringPrototypePadStart( - NumberPrototypeToString(StringPrototypeCharCodeAt(char, 2), 16), - 4, - "0", - ); - return `\\u${a}-\\u${b}`; - } else { - throw TypeError("unreachable"); - } - }); - return ArrayPrototypeJoin(matchers, ""); - } +/** + * @param {string} s + * @returns {string} + */ +function byteUpperCase(s) { + return StringPrototypeReplace( + String(s), + /[a-z]/g, + function byteUpperCaseReplace(c) { + return StringPrototypeToUpperCase(c); + }, + ); +} - /** - * https://infra.spec.whatwg.org/#collect-a-sequence-of-code-points - * @param {string} input - * @param {number} position - * @param {(char: string) => boolean} condition - * @returns {{result: string, position: number}} - */ - function collectSequenceOfCodepoints(input, position, condition) { - const start = position; - for ( - let c = StringPrototypeCharAt(input, position); - position < input.length && condition(c); - c = StringPrototypeCharAt(input, ++position) - ); - return { result: StringPrototypeSlice(input, start, position), position }; - } +/** + * @param {string} s + * @returns {string} + */ +function byteLowerCase(s) { + // NOTE: correct since all callers convert to ByteString first + // TODO(@AaronO): maybe prefer a ByteString_Lower webidl converter + return StringPrototypeToLowerCase(s); +} - /** - * @param {string} s - * @returns {string} - */ - function byteUpperCase(s) { - return StringPrototypeReplace( - String(s), - /[a-z]/g, - function byteUpperCaseReplace(c) { - return StringPrototypeToUpperCase(c); - }, +/** + * https://fetch.spec.whatwg.org/#collect-an-http-quoted-string + * @param {string} input + * @param {number} position + * @param {boolean} extractValue + * @returns {{result: string, position: number}} + */ +function collectHttpQuotedString(input, position, extractValue) { + // 1. + const positionStart = position; + // 2. + let value = ""; + // 3. + if (input[position] !== "\u0022") throw new TypeError('must be "'); + // 4. + position++; + // 5. + while (true) { + // 5.1. + const res = collectSequenceOfCodepoints( + input, + position, + (c) => c !== "\u0022" && c !== "\u005C", ); - } - - /** - * @param {string} s - * @returns {string} - */ - function byteLowerCase(s) { - // NOTE: correct since all callers convert to ByteString first - // TODO(@AaronO): maybe prefer a ByteString_Lower webidl converter - return StringPrototypeToLowerCase(s); - } - - /** - * https://fetch.spec.whatwg.org/#collect-an-http-quoted-string - * @param {string} input - * @param {number} position - * @param {boolean} extractValue - * @returns {{result: string, position: number}} - */ - function collectHttpQuotedString(input, position, extractValue) { - // 1. - const positionStart = position; - // 2. - let value = ""; - // 3. - if (input[position] !== "\u0022") throw new TypeError('must be "'); - // 4. + value += res.result; + position = res.position; + // 5.2. + if (position >= input.length) break; + // 5.3. + const quoteOrBackslash = input[position]; + // 5.4. position++; - // 5. - while (true) { - // 5.1. - const res = collectSequenceOfCodepoints( - input, - position, - (c) => c !== "\u0022" && c !== "\u005C", - ); - value += res.result; - position = res.position; - // 5.2. - if (position >= input.length) break; - // 5.3. - const quoteOrBackslash = input[position]; - // 5.4. - position++; - // 5.5. - if (quoteOrBackslash === "\u005C") { - // 5.5.1. - if (position >= input.length) { - value += "\u005C"; - break; - } - // 5.5.2. - value += input[position]; - // 5.5.3. - position++; - } else { // 5.6. - // 5.6.1 - if (quoteOrBackslash !== "\u0022") throw new TypeError('must be "'); - // 5.6.2 + // 5.5. + if (quoteOrBackslash === "\u005C") { + // 5.5.1. + if (position >= input.length) { + value += "\u005C"; break; } + // 5.5.2. + value += input[position]; + // 5.5.3. + position++; + } else { // 5.6. + // 5.6.1 + if (quoteOrBackslash !== "\u0022") throw new TypeError('must be "'); + // 5.6.2 + break; } - // 6. - if (extractValue) return { result: value, position }; - // 7. - return { - result: StringPrototypeSubstring(input, positionStart, position + 1), - position, - }; } + // 6. + if (extractValue) return { result: value, position }; + // 7. + return { + result: StringPrototypeSubstring(input, positionStart, position + 1), + position, + }; +} + +/** + * @param {Uint8Array} data + * @returns {string} + */ +function forgivingBase64Encode(data) { + return ops.op_base64_encode(data); +} + +/** + * @param {string} data + * @returns {Uint8Array} + */ +function forgivingBase64Decode(data) { + return ops.op_base64_decode(data); +} - /** - * @param {Uint8Array} data - * @returns {string} - */ - function forgivingBase64Encode(data) { - return ops.op_base64_encode(data); +// Taken from std/encoding/base64url.ts +/* + * Some variants allow or require omitting the padding '=' signs: + * https://en.wikipedia.org/wiki/Base64#The_URL_applications + * @param base64url + */ +/** + * @param {string} base64url + * @returns {string} + */ +function addPaddingToBase64url(base64url) { + if (base64url.length % 4 === 2) return base64url + "=="; + if (base64url.length % 4 === 3) return base64url + "="; + if (base64url.length % 4 === 1) { + throw new TypeError("Illegal base64url string!"); } + return base64url; +} - /** - * @param {string} data - * @returns {Uint8Array} - */ - function forgivingBase64Decode(data) { - return ops.op_base64_decode(data); +/** + * @param {string} base64url + * @returns {string} + */ +function convertBase64urlToBase64(base64url) { + if (!/^[-_A-Z0-9]*?={0,2}$/i.test(base64url)) { + // Contains characters not part of base64url spec. + throw new TypeError("Failed to decode base64url: invalid character"); } + return addPaddingToBase64url(base64url).replace(/\-/g, "+").replace( + /_/g, + "/", + ); +} - /** - * @param {string} char - * @returns {boolean} - */ - function isHttpWhitespace(char) { - switch (char) { - case "\u0009": - case "\u000A": - case "\u000D": - case "\u0020": - return true; - default: - return false; - } +/** + * Encodes a given ArrayBuffer or string into a base64url representation + * @param {ArrayBuffer | string} data + * @returns {string} + */ +function forgivingBase64UrlEncode(data) { + return forgivingBase64Encode( + typeof data === "string" ? new TextEncoder().encode(data) : data, + ).replace(/=/g, "").replace(/\+/g, "-").replace(/\//g, "_"); +} + +/** + * Converts given base64url encoded data back to original + * @param {string} b64url + * @returns {Uint8Array} + */ +function forgivingBase64UrlDecode(b64url) { + return forgivingBase64Decode(convertBase64urlToBase64(b64url)); +} + +/** + * @param {string} char + * @returns {boolean} + */ +function isHttpWhitespace(char) { + switch (char) { + case "\u0009": + case "\u000A": + case "\u000D": + case "\u0020": + return true; + default: + return false; } +} - /** - * @param {string} s - * @returns {string} - */ - function httpTrim(s) { - if (!isHttpWhitespace(s[0]) && !isHttpWhitespace(s[s.length - 1])) { - return s; - } - return StringPrototypeMatch(s, HTTP_BETWEEN_WHITESPACE)?.[1] ?? ""; +/** + * @param {string} s + * @returns {string} + */ +function httpTrim(s) { + if (!isHttpWhitespace(s[0]) && !isHttpWhitespace(s[s.length - 1])) { + return s; } + return StringPrototypeMatch(s, HTTP_BETWEEN_WHITESPACE)?.[1] ?? ""; +} - class AssertionError extends Error { - constructor(msg) { - super(msg); - this.name = "AssertionError"; - } +class AssertionError extends Error { + constructor(msg) { + super(msg); + this.name = "AssertionError"; } +} - /** - * @param {unknown} cond - * @param {string=} msg - * @returns {asserts cond} - */ - function assert(cond, msg = "Assertion failed.") { - if (!cond) { - throw new AssertionError(msg); - } +/** + * @param {unknown} cond + * @param {string=} msg + * @returns {asserts cond} + */ +function assert(cond, msg = "Assertion failed.") { + if (!cond) { + throw new AssertionError(msg); } +} - /** - * @param {unknown} value - * @returns {string} - */ - function serializeJSValueToJSONString(value) { - const result = JSONStringify(value); - if (result === undefined) { - throw new TypeError("Value is not JSON serializable."); - } - return result; +/** + * @param {unknown} value + * @returns {string} + */ +function serializeJSValueToJSONString(value) { + const result = JSONStringify(value); + if (result === undefined) { + throw new TypeError("Value is not JSON serializable."); } + return result; +} - window.__bootstrap.infra = { - collectSequenceOfCodepoints, - ASCII_DIGIT, - ASCII_UPPER_ALPHA, - ASCII_LOWER_ALPHA, - ASCII_ALPHA, - ASCII_ALPHANUMERIC, - HTTP_TAB_OR_SPACE, - HTTP_WHITESPACE, - HTTP_TOKEN_CODE_POINT, - HTTP_TOKEN_CODE_POINT_RE, - HTTP_QUOTED_STRING_TOKEN_POINT, - HTTP_QUOTED_STRING_TOKEN_POINT_RE, - HTTP_TAB_OR_SPACE_PREFIX_RE, - HTTP_TAB_OR_SPACE_SUFFIX_RE, - HTTP_WHITESPACE_PREFIX_RE, - HTTP_WHITESPACE_SUFFIX_RE, - httpTrim, - regexMatcher, - byteUpperCase, - byteLowerCase, - collectHttpQuotedString, - forgivingBase64Encode, - forgivingBase64Decode, - AssertionError, - assert, - serializeJSValueToJSONString, - }; -})(globalThis); +export { + ASCII_ALPHA, + ASCII_ALPHANUMERIC, + ASCII_DIGIT, + ASCII_LOWER_ALPHA, + ASCII_UPPER_ALPHA, + assert, + AssertionError, + byteLowerCase, + byteUpperCase, + collectHttpQuotedString, + collectSequenceOfCodepoints, + forgivingBase64Decode, + forgivingBase64Encode, + forgivingBase64UrlDecode, + forgivingBase64UrlEncode, + HTTP_QUOTED_STRING_TOKEN_POINT, + HTTP_QUOTED_STRING_TOKEN_POINT_RE, + HTTP_TAB_OR_SPACE, + HTTP_TAB_OR_SPACE_PREFIX_RE, + HTTP_TAB_OR_SPACE_SUFFIX_RE, + HTTP_TOKEN_CODE_POINT, + HTTP_TOKEN_CODE_POINT_RE, + HTTP_WHITESPACE, + HTTP_WHITESPACE_PREFIX_RE, + HTTP_WHITESPACE_SUFFIX_RE, + httpTrim, + regexMatcher, + serializeJSValueToJSONString, +}; diff --git a/ext/web/01_dom_exception.js b/ext/web/01_dom_exception.js index a4556c03c7a55b..116fe0490e69be 100644 --- a/ext/web/01_dom_exception.js +++ b/ext/web/01_dom_exception.js @@ -7,197 +7,194 @@ /// /// -"use strict"; - -((window) => { - const { - ArrayPrototypeSlice, - Error, - ErrorPrototype, - ObjectDefineProperty, - ObjectCreate, - ObjectEntries, - ObjectPrototypeIsPrototypeOf, - ObjectSetPrototypeOf, - Symbol, - SymbolFor, - } = window.__bootstrap.primordials; - const webidl = window.__bootstrap.webidl; - const consoleInternal = window.__bootstrap.console; - - const _name = Symbol("name"); - const _message = Symbol("message"); - const _code = Symbol("code"); - - // Defined in WebIDL 4.3. - // https://webidl.spec.whatwg.org/#idl-DOMException - const INDEX_SIZE_ERR = 1; - const DOMSTRING_SIZE_ERR = 2; - const HIERARCHY_REQUEST_ERR = 3; - const WRONG_DOCUMENT_ERR = 4; - const INVALID_CHARACTER_ERR = 5; - const NO_DATA_ALLOWED_ERR = 6; - const NO_MODIFICATION_ALLOWED_ERR = 7; - const NOT_FOUND_ERR = 8; - const NOT_SUPPORTED_ERR = 9; - const INUSE_ATTRIBUTE_ERR = 10; - const INVALID_STATE_ERR = 11; - const SYNTAX_ERR = 12; - const INVALID_MODIFICATION_ERR = 13; - const NAMESPACE_ERR = 14; - const INVALID_ACCESS_ERR = 15; - const VALIDATION_ERR = 16; - const TYPE_MISMATCH_ERR = 17; - const SECURITY_ERR = 18; - const NETWORK_ERR = 19; - const ABORT_ERR = 20; - const URL_MISMATCH_ERR = 21; - const QUOTA_EXCEEDED_ERR = 22; - const TIMEOUT_ERR = 23; - const INVALID_NODE_TYPE_ERR = 24; - const DATA_CLONE_ERR = 25; - - // Defined in WebIDL 2.8.1. - // https://webidl.spec.whatwg.org/#dfn-error-names-table - /** @type {Record} */ - // the prototype should be null, to prevent user code from looking - // up Object.prototype properties, such as "toString" - const nameToCodeMapping = ObjectCreate(null, { - IndexSizeError: { value: INDEX_SIZE_ERR }, - HierarchyRequestError: { value: HIERARCHY_REQUEST_ERR }, - WrongDocumentError: { value: WRONG_DOCUMENT_ERR }, - InvalidCharacterError: { value: INVALID_CHARACTER_ERR }, - NoModificationAllowedError: { value: NO_MODIFICATION_ALLOWED_ERR }, - NotFoundError: { value: NOT_FOUND_ERR }, - NotSupportedError: { value: NOT_SUPPORTED_ERR }, - InUseAttributeError: { value: INUSE_ATTRIBUTE_ERR }, - InvalidStateError: { value: INVALID_STATE_ERR }, - SyntaxError: { value: SYNTAX_ERR }, - InvalidModificationError: { value: INVALID_MODIFICATION_ERR }, - NamespaceError: { value: NAMESPACE_ERR }, - InvalidAccessError: { value: INVALID_ACCESS_ERR }, - TypeMismatchError: { value: TYPE_MISMATCH_ERR }, - SecurityError: { value: SECURITY_ERR }, - NetworkError: { value: NETWORK_ERR }, - AbortError: { value: ABORT_ERR }, - URLMismatchError: { value: URL_MISMATCH_ERR }, - QuotaExceededError: { value: QUOTA_EXCEEDED_ERR }, - TimeoutError: { value: TIMEOUT_ERR }, - InvalidNodeTypeError: { value: INVALID_NODE_TYPE_ERR }, - DataCloneError: { value: DATA_CLONE_ERR }, - }); - - // Defined in WebIDL 4.3. - // https://webidl.spec.whatwg.org/#idl-DOMException - class DOMException { - [_message]; - [_name]; - [_code]; - - // https://webidl.spec.whatwg.org/#dom-domexception-domexception - constructor(message = "", name = "Error") { - message = webidl.converters.DOMString(message, { - prefix: "Failed to construct 'DOMException'", - context: "Argument 1", - }); - name = webidl.converters.DOMString(name, { - prefix: "Failed to construct 'DOMException'", - context: "Argument 2", - }); - const code = nameToCodeMapping[name] ?? 0; - - this[_message] = message; - this[_name] = name; - this[_code] = code; - this[webidl.brand] = webidl.brand; - - const error = new Error(message); - error.name = "DOMException"; - ObjectDefineProperty(this, "stack", { - value: error.stack, - writable: true, - configurable: true, - }); - - // `DOMException` isn't a native error, so `Error.prepareStackTrace()` is - // not called when accessing `.stack`, meaning our structured stack trace - // hack doesn't apply. This patches it in. - ObjectDefineProperty(this, "__callSiteEvals", { - value: ArrayPrototypeSlice(error.__callSiteEvals, 1), - configurable: true, - }); - } - - get message() { - webidl.assertBranded(this, DOMExceptionPrototype); - return this[_message]; - } - - get name() { - webidl.assertBranded(this, DOMExceptionPrototype); - return this[_name]; - } +const primordials = globalThis.__bootstrap.primordials; +const { + ArrayPrototypeSlice, + Error, + ErrorPrototype, + ObjectDefineProperty, + ObjectCreate, + ObjectEntries, + ObjectPrototypeIsPrototypeOf, + ObjectSetPrototypeOf, + Symbol, + SymbolFor, +} = primordials; +import * as webidl from "internal:deno_webidl/00_webidl.js"; +import { createFilteredInspectProxy } from "internal:deno_console/02_console.js"; + +const _name = Symbol("name"); +const _message = Symbol("message"); +const _code = Symbol("code"); + +// Defined in WebIDL 4.3. +// https://webidl.spec.whatwg.org/#idl-DOMException +const INDEX_SIZE_ERR = 1; +const DOMSTRING_SIZE_ERR = 2; +const HIERARCHY_REQUEST_ERR = 3; +const WRONG_DOCUMENT_ERR = 4; +const INVALID_CHARACTER_ERR = 5; +const NO_DATA_ALLOWED_ERR = 6; +const NO_MODIFICATION_ALLOWED_ERR = 7; +const NOT_FOUND_ERR = 8; +const NOT_SUPPORTED_ERR = 9; +const INUSE_ATTRIBUTE_ERR = 10; +const INVALID_STATE_ERR = 11; +const SYNTAX_ERR = 12; +const INVALID_MODIFICATION_ERR = 13; +const NAMESPACE_ERR = 14; +const INVALID_ACCESS_ERR = 15; +const VALIDATION_ERR = 16; +const TYPE_MISMATCH_ERR = 17; +const SECURITY_ERR = 18; +const NETWORK_ERR = 19; +const ABORT_ERR = 20; +const URL_MISMATCH_ERR = 21; +const QUOTA_EXCEEDED_ERR = 22; +const TIMEOUT_ERR = 23; +const INVALID_NODE_TYPE_ERR = 24; +const DATA_CLONE_ERR = 25; + +// Defined in WebIDL 2.8.1. +// https://webidl.spec.whatwg.org/#dfn-error-names-table +/** @type {Record} */ +// the prototype should be null, to prevent user code from looking +// up Object.prototype properties, such as "toString" +const nameToCodeMapping = ObjectCreate(null, { + IndexSizeError: { value: INDEX_SIZE_ERR }, + HierarchyRequestError: { value: HIERARCHY_REQUEST_ERR }, + WrongDocumentError: { value: WRONG_DOCUMENT_ERR }, + InvalidCharacterError: { value: INVALID_CHARACTER_ERR }, + NoModificationAllowedError: { value: NO_MODIFICATION_ALLOWED_ERR }, + NotFoundError: { value: NOT_FOUND_ERR }, + NotSupportedError: { value: NOT_SUPPORTED_ERR }, + InUseAttributeError: { value: INUSE_ATTRIBUTE_ERR }, + InvalidStateError: { value: INVALID_STATE_ERR }, + SyntaxError: { value: SYNTAX_ERR }, + InvalidModificationError: { value: INVALID_MODIFICATION_ERR }, + NamespaceError: { value: NAMESPACE_ERR }, + InvalidAccessError: { value: INVALID_ACCESS_ERR }, + TypeMismatchError: { value: TYPE_MISMATCH_ERR }, + SecurityError: { value: SECURITY_ERR }, + NetworkError: { value: NETWORK_ERR }, + AbortError: { value: ABORT_ERR }, + URLMismatchError: { value: URL_MISMATCH_ERR }, + QuotaExceededError: { value: QUOTA_EXCEEDED_ERR }, + TimeoutError: { value: TIMEOUT_ERR }, + InvalidNodeTypeError: { value: INVALID_NODE_TYPE_ERR }, + DataCloneError: { value: DATA_CLONE_ERR }, +}); + +// Defined in WebIDL 4.3. +// https://webidl.spec.whatwg.org/#idl-DOMException +class DOMException { + [_message]; + [_name]; + [_code]; + + // https://webidl.spec.whatwg.org/#dom-domexception-domexception + constructor(message = "", name = "Error") { + message = webidl.converters.DOMString(message, { + prefix: "Failed to construct 'DOMException'", + context: "Argument 1", + }); + name = webidl.converters.DOMString(name, { + prefix: "Failed to construct 'DOMException'", + context: "Argument 2", + }); + const code = nameToCodeMapping[name] ?? 0; + + this[_message] = message; + this[_name] = name; + this[_code] = code; + this[webidl.brand] = webidl.brand; + + const error = new Error(message); + error.name = "DOMException"; + ObjectDefineProperty(this, "stack", { + value: error.stack, + writable: true, + configurable: true, + }); + + // `DOMException` isn't a native error, so `Error.prepareStackTrace()` is + // not called when accessing `.stack`, meaning our structured stack trace + // hack doesn't apply. This patches it in. + ObjectDefineProperty(this, "__callSiteEvals", { + value: ArrayPrototypeSlice(error.__callSiteEvals, 1), + configurable: true, + }); + } - get code() { - webidl.assertBranded(this, DOMExceptionPrototype); - return this[_code]; - } + get message() { + webidl.assertBranded(this, DOMExceptionPrototype); + return this[_message]; + } - [SymbolFor("Deno.customInspect")](inspect) { - if (ObjectPrototypeIsPrototypeOf(DOMExceptionPrototype, this)) { - return `DOMException: ${this[_message]}`; - } else { - return inspect(consoleInternal.createFilteredInspectProxy({ - object: this, - evaluate: false, - keys: [ - "message", - "name", - "code", - ], - })); - } - } + get name() { + webidl.assertBranded(this, DOMExceptionPrototype); + return this[_name]; } - ObjectSetPrototypeOf(DOMException.prototype, ErrorPrototype); - - webidl.configurePrototype(DOMException); - const DOMExceptionPrototype = DOMException.prototype; - - const entries = ObjectEntries({ - INDEX_SIZE_ERR, - DOMSTRING_SIZE_ERR, - HIERARCHY_REQUEST_ERR, - WRONG_DOCUMENT_ERR, - INVALID_CHARACTER_ERR, - NO_DATA_ALLOWED_ERR, - NO_MODIFICATION_ALLOWED_ERR, - NOT_FOUND_ERR, - NOT_SUPPORTED_ERR, - INUSE_ATTRIBUTE_ERR, - INVALID_STATE_ERR, - SYNTAX_ERR, - INVALID_MODIFICATION_ERR, - NAMESPACE_ERR, - INVALID_ACCESS_ERR, - VALIDATION_ERR, - TYPE_MISMATCH_ERR, - SECURITY_ERR, - NETWORK_ERR, - ABORT_ERR, - URL_MISMATCH_ERR, - QUOTA_EXCEEDED_ERR, - TIMEOUT_ERR, - INVALID_NODE_TYPE_ERR, - DATA_CLONE_ERR, - }); - for (let i = 0; i < entries.length; ++i) { - const { 0: key, 1: value } = entries[i]; - const desc = { value, enumerable: true }; - ObjectDefineProperty(DOMException, key, desc); - ObjectDefineProperty(DOMException.prototype, key, desc); + get code() { + webidl.assertBranded(this, DOMExceptionPrototype); + return this[_code]; } - window.__bootstrap.domException = { DOMException }; -})(this); + [SymbolFor("Deno.customInspect")](inspect) { + if (ObjectPrototypeIsPrototypeOf(DOMExceptionPrototype, this)) { + return `DOMException: ${this[_message]}`; + } else { + return inspect(createFilteredInspectProxy({ + object: this, + evaluate: false, + keys: [ + "message", + "name", + "code", + ], + })); + } + } +} + +ObjectSetPrototypeOf(DOMException.prototype, ErrorPrototype); + +webidl.configurePrototype(DOMException); +const DOMExceptionPrototype = DOMException.prototype; + +const entries = ObjectEntries({ + INDEX_SIZE_ERR, + DOMSTRING_SIZE_ERR, + HIERARCHY_REQUEST_ERR, + WRONG_DOCUMENT_ERR, + INVALID_CHARACTER_ERR, + NO_DATA_ALLOWED_ERR, + NO_MODIFICATION_ALLOWED_ERR, + NOT_FOUND_ERR, + NOT_SUPPORTED_ERR, + INUSE_ATTRIBUTE_ERR, + INVALID_STATE_ERR, + SYNTAX_ERR, + INVALID_MODIFICATION_ERR, + NAMESPACE_ERR, + INVALID_ACCESS_ERR, + VALIDATION_ERR, + TYPE_MISMATCH_ERR, + SECURITY_ERR, + NETWORK_ERR, + ABORT_ERR, + URL_MISMATCH_ERR, + QUOTA_EXCEEDED_ERR, + TIMEOUT_ERR, + INVALID_NODE_TYPE_ERR, + DATA_CLONE_ERR, +}); +for (let i = 0; i < entries.length; ++i) { + const { 0: key, 1: value } = entries[i]; + const desc = { value, enumerable: true }; + ObjectDefineProperty(DOMException, key, desc); + ObjectDefineProperty(DOMException.prototype, key, desc); +} + +export default DOMException; diff --git a/ext/web/01_mimesniff.js b/ext/web/01_mimesniff.js index 2d67d5f9548df3..40f5c03ebbbd0c 100644 --- a/ext/web/01_mimesniff.js +++ b/ext/web/01_mimesniff.js @@ -6,255 +6,247 @@ /// /// -"use strict"; - -((window) => { - const { - ArrayPrototypeIncludes, - Map, - MapPrototypeGet, - MapPrototypeHas, - MapPrototypeSet, - RegExpPrototypeTest, - SafeMapIterator, - StringPrototypeReplaceAll, - StringPrototypeToLowerCase, - } = window.__bootstrap.primordials; - const { - collectSequenceOfCodepoints, - HTTP_WHITESPACE, - HTTP_WHITESPACE_PREFIX_RE, - HTTP_WHITESPACE_SUFFIX_RE, - HTTP_QUOTED_STRING_TOKEN_POINT_RE, - HTTP_TOKEN_CODE_POINT_RE, - collectHttpQuotedString, - } = window.__bootstrap.infra; +const primordials = globalThis.__bootstrap.primordials; +const { + ArrayPrototypeIncludes, + Map, + MapPrototypeGet, + MapPrototypeHas, + MapPrototypeSet, + RegExpPrototypeTest, + SafeMapIterator, + StringPrototypeReplaceAll, + StringPrototypeToLowerCase, +} = primordials; +import { + collectHttpQuotedString, + collectSequenceOfCodepoints, + HTTP_QUOTED_STRING_TOKEN_POINT_RE, + HTTP_TOKEN_CODE_POINT_RE, + HTTP_WHITESPACE, + HTTP_WHITESPACE_PREFIX_RE, + HTTP_WHITESPACE_SUFFIX_RE, +} from "internal:deno_web/00_infra.js"; + +/** + * @typedef MimeType + * @property {string} type + * @property {string} subtype + * @property {Map} parameters + */ + +/** + * @param {string} input + * @returns {MimeType | null} + */ +function parseMimeType(input) { + // 1. + input = StringPrototypeReplaceAll(input, HTTP_WHITESPACE_PREFIX_RE, ""); + input = StringPrototypeReplaceAll(input, HTTP_WHITESPACE_SUFFIX_RE, ""); + + // 2. + let position = 0; + const endOfInput = input.length; + + // 3. + const res1 = collectSequenceOfCodepoints( + input, + position, + (c) => c != "\u002F", + ); + const type = res1.result; + position = res1.position; + + // 4. + if (type === "" || !RegExpPrototypeTest(HTTP_TOKEN_CODE_POINT_RE, type)) { + return null; + } - /** - * @typedef MimeType - * @property {string} type - * @property {string} subtype - * @property {Map} parameters - */ + // 5. + if (position >= endOfInput) return null; + + // 6. + position++; + + // 7. + const res2 = collectSequenceOfCodepoints( + input, + position, + (c) => c != "\u003B", + ); + let subtype = res2.result; + position = res2.position; + + // 8. + subtype = StringPrototypeReplaceAll(subtype, HTTP_WHITESPACE_SUFFIX_RE, ""); + + // 9. + if ( + subtype === "" || !RegExpPrototypeTest(HTTP_TOKEN_CODE_POINT_RE, subtype) + ) { + return null; + } - /** - * @param {string} input - * @returns {MimeType | null} - */ - function parseMimeType(input) { - // 1. - input = StringPrototypeReplaceAll(input, HTTP_WHITESPACE_PREFIX_RE, ""); - input = StringPrototypeReplaceAll(input, HTTP_WHITESPACE_SUFFIX_RE, ""); + // 10. + const mimeType = { + type: StringPrototypeToLowerCase(type), + subtype: StringPrototypeToLowerCase(subtype), + /** @type {Map} */ + parameters: new Map(), + }; - // 2. - let position = 0; - const endOfInput = input.length; + // 11. + while (position < endOfInput) { + // 11.1. + position++; - // 3. + // 11.2. const res1 = collectSequenceOfCodepoints( input, position, - (c) => c != "\u002F", + (c) => ArrayPrototypeIncludes(HTTP_WHITESPACE, c), ); - const type = res1.result; position = res1.position; - // 4. - if (type === "" || !RegExpPrototypeTest(HTTP_TOKEN_CODE_POINT_RE, type)) { - return null; - } - - // 5. - if (position >= endOfInput) return null; - - // 6. - position++; - - // 7. + // 11.3. const res2 = collectSequenceOfCodepoints( input, position, - (c) => c != "\u003B", + (c) => c !== "\u003B" && c !== "\u003D", ); - let subtype = res2.result; + let parameterName = res2.result; position = res2.position; - // 8. - subtype = StringPrototypeReplaceAll(subtype, HTTP_WHITESPACE_SUFFIX_RE, ""); + // 11.4. + parameterName = StringPrototypeToLowerCase(parameterName); - // 9. - if ( - subtype === "" || !RegExpPrototypeTest(HTTP_TOKEN_CODE_POINT_RE, subtype) - ) { - return null; + // 11.5. + if (position < endOfInput) { + if (input[position] == "\u003B") continue; + position++; } - // 10. - const mimeType = { - type: StringPrototypeToLowerCase(type), - subtype: StringPrototypeToLowerCase(subtype), - /** @type {Map} */ - parameters: new Map(), - }; + // 11.6. + if (position >= endOfInput) break; - // 11. - while (position < endOfInput) { - // 11.1. - position++; + // 11.7. + let parameterValue = null; - // 11.2. - const res1 = collectSequenceOfCodepoints( - input, - position, - (c) => ArrayPrototypeIncludes(HTTP_WHITESPACE, c), - ); - position = res1.position; + // 11.8. + if (input[position] === "\u0022") { + // 11.8.1. + const res = collectHttpQuotedString(input, position, true); + parameterValue = res.result; + position = res.position; - // 11.3. - const res2 = collectSequenceOfCodepoints( + // 11.8.2. + position++; + } else { // 11.9. + // 11.9.1. + const res = collectSequenceOfCodepoints( input, position, - (c) => c !== "\u003B" && c !== "\u003D", + (c) => c !== "\u003B", + ); + parameterValue = res.result; + position = res.position; + + // 11.9.2. + parameterValue = StringPrototypeReplaceAll( + parameterValue, + HTTP_WHITESPACE_SUFFIX_RE, + "", ); - let parameterName = res2.result; - position = res2.position; - - // 11.4. - parameterName = StringPrototypeToLowerCase(parameterName); - - // 11.5. - if (position < endOfInput) { - if (input[position] == "\u003B") continue; - position++; - } - - // 11.6. - if (position >= endOfInput) break; - - // 11.7. - let parameterValue = null; - - // 11.8. - if (input[position] === "\u0022") { - // 11.8.1. - const res = collectHttpQuotedString(input, position, true); - parameterValue = res.result; - position = res.position; - - // 11.8.2. - position++; - } else { // 11.9. - // 11.9.1. - const res = collectSequenceOfCodepoints( - input, - position, - (c) => c !== "\u003B", - ); - parameterValue = res.result; - position = res.position; - - // 11.9.2. - parameterValue = StringPrototypeReplaceAll( - parameterValue, - HTTP_WHITESPACE_SUFFIX_RE, - "", - ); - - // 11.9.3. - if (parameterValue === "") continue; - } - // 11.10. - if ( - parameterName !== "" && - RegExpPrototypeTest(HTTP_TOKEN_CODE_POINT_RE, parameterName) && - RegExpPrototypeTest( - HTTP_QUOTED_STRING_TOKEN_POINT_RE, - parameterValue, - ) && - !MapPrototypeHas(mimeType.parameters, parameterName) - ) { - MapPrototypeSet(mimeType.parameters, parameterName, parameterValue); - } + // 11.9.3. + if (parameterValue === "") continue; } - // 12. - return mimeType; - } - - /** - * @param {MimeType} mimeType - * @returns {string} - */ - function essence(mimeType) { - return `${mimeType.type}/${mimeType.subtype}`; + // 11.10. + if ( + parameterName !== "" && + RegExpPrototypeTest(HTTP_TOKEN_CODE_POINT_RE, parameterName) && + RegExpPrototypeTest( + HTTP_QUOTED_STRING_TOKEN_POINT_RE, + parameterValue, + ) && + !MapPrototypeHas(mimeType.parameters, parameterName) + ) { + MapPrototypeSet(mimeType.parameters, parameterName, parameterValue); + } } - /** - * @param {MimeType} mimeType - * @returns {string} - */ - function serializeMimeType(mimeType) { - let serialization = essence(mimeType); - for (const param of new SafeMapIterator(mimeType.parameters)) { - serialization += `;${param[0]}=`; - let value = param[1]; - if (!RegExpPrototypeTest(HTTP_TOKEN_CODE_POINT_RE, value)) { - value = StringPrototypeReplaceAll(value, "\\", "\\\\"); - value = StringPrototypeReplaceAll(value, '"', '\\"'); - value = `"${value}"`; - } - serialization += value; + // 12. + return mimeType; +} + +/** + * @param {MimeType} mimeType + * @returns {string} + */ +function essence(mimeType) { + return `${mimeType.type}/${mimeType.subtype}`; +} + +/** + * @param {MimeType} mimeType + * @returns {string} + */ +function serializeMimeType(mimeType) { + let serialization = essence(mimeType); + for (const param of new SafeMapIterator(mimeType.parameters)) { + serialization += `;${param[0]}=`; + let value = param[1]; + if (!RegExpPrototypeTest(HTTP_TOKEN_CODE_POINT_RE, value)) { + value = StringPrototypeReplaceAll(value, "\\", "\\\\"); + value = StringPrototypeReplaceAll(value, '"', '\\"'); + value = `"${value}"`; } - return serialization; + serialization += value; } - - /** - * Part of the Fetch spec's "extract a MIME type" algorithm - * (https://fetch.spec.whatwg.org/#concept-header-extract-mime-type). - * @param {string[] | null} headerValues The result of getting, decoding and - * splitting the "Content-Type" header. - * @returns {MimeType | null} - */ - function extractMimeType(headerValues) { - if (headerValues === null) return null; - - let charset = null; - let essence_ = null; - let mimeType = null; - for (let i = 0; i < headerValues.length; ++i) { - const value = headerValues[i]; - const temporaryMimeType = parseMimeType(value); + return serialization; +} + +/** + * Part of the Fetch spec's "extract a MIME type" algorithm + * (https://fetch.spec.whatwg.org/#concept-header-extract-mime-type). + * @param {string[] | null} headerValues The result of getting, decoding and + * splitting the "Content-Type" header. + * @returns {MimeType | null} + */ +function extractMimeType(headerValues) { + if (headerValues === null) return null; + + let charset = null; + let essence_ = null; + let mimeType = null; + for (let i = 0; i < headerValues.length; ++i) { + const value = headerValues[i]; + const temporaryMimeType = parseMimeType(value); + if ( + temporaryMimeType === null || + essence(temporaryMimeType) == "*/*" + ) { + continue; + } + mimeType = temporaryMimeType; + if (essence(mimeType) !== essence_) { + charset = null; + const newCharset = MapPrototypeGet(mimeType.parameters, "charset"); + if (newCharset !== undefined) { + charset = newCharset; + } + essence_ = essence(mimeType); + } else { if ( - temporaryMimeType === null || - essence(temporaryMimeType) == "*/*" + !MapPrototypeHas(mimeType.parameters, "charset") && + charset !== null ) { - continue; - } - mimeType = temporaryMimeType; - if (essence(mimeType) !== essence_) { - charset = null; - const newCharset = MapPrototypeGet(mimeType.parameters, "charset"); - if (newCharset !== undefined) { - charset = newCharset; - } - essence_ = essence(mimeType); - } else { - if ( - !MapPrototypeHas(mimeType.parameters, "charset") && - charset !== null - ) { - MapPrototypeSet(mimeType.parameters, "charset", charset); - } + MapPrototypeSet(mimeType.parameters, "charset", charset); } } - return mimeType; } + return mimeType; +} - window.__bootstrap.mimesniff = { - parseMimeType, - essence, - serializeMimeType, - extractMimeType, - }; -})(this); +export { essence, extractMimeType, parseMimeType, serializeMimeType }; diff --git a/ext/web/02_event.js b/ext/web/02_event.js index c99eb8f6ed748f..37cdab9a2c4b5b 100644 --- a/ext/web/02_event.js +++ b/ext/web/02_event.js @@ -4,1520 +4,1525 @@ // Many parts of the DOM are not implemented in Deno, but the logic for those // parts still exists. This means you will observe a lot of strange structures // and impossible logic branches based on what Deno currently supports. -"use strict"; - -((window) => { - const core = window.Deno.core; - const ops = core.ops; - const webidl = window.__bootstrap.webidl; - const { DOMException } = window.__bootstrap.domException; - const consoleInternal = window.__bootstrap.console; - const { - ArrayPrototypeFilter, - ArrayPrototypeIncludes, - ArrayPrototypeIndexOf, - ArrayPrototypeMap, - ArrayPrototypePush, - ArrayPrototypeSlice, - ArrayPrototypeSplice, - ArrayPrototypeUnshift, - Boolean, - DateNow, - Error, - FunctionPrototypeCall, - Map, - MapPrototypeGet, - MapPrototypeSet, - ObjectCreate, - ObjectDefineProperty, - ObjectGetOwnPropertyDescriptor, - ObjectPrototypeIsPrototypeOf, - ReflectDefineProperty, - ReflectHas, - SafeArrayIterator, - StringPrototypeStartsWith, - Symbol, - SymbolFor, - SymbolToStringTag, - TypeError, - } = window.__bootstrap.primordials; - - // accessors for non runtime visible data - - function getDispatched(event) { - return Boolean(event[_dispatched]); - } - - function getPath(event) { - return event[_path] ?? []; - } - - function getStopImmediatePropagation(event) { - return Boolean(event[_stopImmediatePropagationFlag]); - } - - function setCurrentTarget( - event, - value, - ) { - event[_attributes].currentTarget = value; - } - - function setIsTrusted(event, value) { - event[_isTrusted] = value; - } - function setDispatched(event, value) { - event[_dispatched] = value; - } - - function setEventPhase(event, value) { - event[_attributes].eventPhase = value; +const core = globalThis.Deno.core; +const ops = core.ops; +import * as webidl from "internal:deno_webidl/00_webidl.js"; +import DOMException from "internal:deno_web/01_dom_exception.js"; +import { createFilteredInspectProxy } from "internal:deno_console/02_console.js"; +const primordials = globalThis.__bootstrap.primordials; +const { + ArrayPrototypeFilter, + ArrayPrototypeIncludes, + ArrayPrototypeIndexOf, + ArrayPrototypeMap, + ArrayPrototypePush, + ArrayPrototypeSlice, + ArrayPrototypeSplice, + ArrayPrototypeUnshift, + Boolean, + DateNow, + Error, + FunctionPrototypeCall, + Map, + MapPrototypeGet, + MapPrototypeSet, + ObjectCreate, + ObjectDefineProperty, + ObjectGetOwnPropertyDescriptor, + ObjectPrototypeIsPrototypeOf, + ReflectDefineProperty, + ReflectHas, + SafeArrayIterator, + StringPrototypeStartsWith, + Symbol, + SymbolFor, + SymbolToStringTag, + TypeError, +} = primordials; + +// This should be set via setGlobalThis this is required so that if even +// user deletes globalThis it is still usable +let globalThis_; + +function saveGlobalThisReference(val) { + globalThis_ = val; +} + +// accessors for non runtime visible data + +function getDispatched(event) { + return Boolean(event[_dispatched]); +} + +function getPath(event) { + return event[_path] ?? []; +} + +function getStopImmediatePropagation(event) { + return Boolean(event[_stopImmediatePropagationFlag]); +} + +function setCurrentTarget( + event, + value, +) { + event[_attributes].currentTarget = value; +} + +function setIsTrusted(event, value) { + event[_isTrusted] = value; +} + +function setDispatched(event, value) { + event[_dispatched] = value; +} + +function setEventPhase(event, value) { + event[_attributes].eventPhase = value; +} + +function setInPassiveListener(event, value) { + event[_inPassiveListener] = value; +} + +function setPath(event, value) { + event[_path] = value; +} + +function setRelatedTarget( + event, + value, +) { + event[_attributes].relatedTarget = value; +} + +function setTarget(event, value) { + event[_attributes].target = value; +} + +function setStopImmediatePropagation( + event, + value, +) { + event[_stopImmediatePropagationFlag] = value; +} + +// Type guards that widen the event type + +function hasRelatedTarget( + event, +) { + return ReflectHas(event, "relatedTarget"); +} + +const isTrusted = ObjectGetOwnPropertyDescriptor({ + get isTrusted() { + return this[_isTrusted]; + }, +}, "isTrusted").get; + +const eventInitConverter = webidl.createDictionaryConverter("EventInit", [{ + key: "bubbles", + defaultValue: false, + converter: webidl.converters.boolean, +}, { + key: "cancelable", + defaultValue: false, + converter: webidl.converters.boolean, +}, { + key: "composed", + defaultValue: false, + converter: webidl.converters.boolean, +}]); + +const _attributes = Symbol("[[attributes]]"); +const _canceledFlag = Symbol("[[canceledFlag]]"); +const _stopPropagationFlag = Symbol("[[stopPropagationFlag]]"); +const _stopImmediatePropagationFlag = Symbol( + "[[stopImmediatePropagationFlag]]", +); +const _inPassiveListener = Symbol("[[inPassiveListener]]"); +const _dispatched = Symbol("[[dispatched]]"); +const _isTrusted = Symbol("[[isTrusted]]"); +const _path = Symbol("[[path]]"); +// internal. +const _skipInternalInit = Symbol("[[skipSlowInit]]"); + +class Event { + constructor(type, eventInitDict = {}) { + // TODO(lucacasonato): remove when this interface is spec aligned + this[SymbolToStringTag] = "Event"; + this[_canceledFlag] = false; + this[_stopPropagationFlag] = false; + this[_stopImmediatePropagationFlag] = false; + this[_inPassiveListener] = false; + this[_dispatched] = false; + this[_isTrusted] = false; + this[_path] = []; + + if (!eventInitDict[_skipInternalInit]) { + webidl.requiredArguments(arguments.length, 1, { + prefix: "Failed to construct 'Event'", + }); + type = webidl.converters.DOMString(type, { + prefix: "Failed to construct 'Event'", + context: "Argument 1", + }); + const eventInit = eventInitConverter(eventInitDict, { + prefix: "Failed to construct 'Event'", + context: "Argument 2", + }); + this[_attributes] = { + type, + ...eventInit, + currentTarget: null, + eventPhase: Event.NONE, + target: null, + timeStamp: DateNow(), + }; + // [LegacyUnforgeable] + ReflectDefineProperty(this, "isTrusted", { + enumerable: true, + get: isTrusted, + }); + } else { + this[_attributes] = { + type, + data: eventInitDict.data ?? null, + bubbles: eventInitDict.bubbles ?? false, + cancelable: eventInitDict.cancelable ?? false, + composed: eventInitDict.composed ?? false, + currentTarget: null, + eventPhase: Event.NONE, + target: null, + timeStamp: DateNow(), + }; + // TODO(@littledivy): Not spec compliant but performance is hurt badly + // for users of `_skipInternalInit`. + this.isTrusted = false; + } } - function setInPassiveListener(event, value) { - event[_inPassiveListener] = value; + [SymbolFor("Deno.privateCustomInspect")](inspect) { + return inspect(createFilteredInspectProxy({ + object: this, + evaluate: ObjectPrototypeIsPrototypeOf(Event.prototype, this), + keys: EVENT_PROPS, + })); } - function setPath(event, value) { - event[_path] = value; + get type() { + return this[_attributes].type; } - function setRelatedTarget( - event, - value, - ) { - event[_attributes].relatedTarget = value; + get target() { + return this[_attributes].target; } - function setTarget(event, value) { - event[_attributes].target = value; + get srcElement() { + return null; } - function setStopImmediatePropagation( - event, - value, - ) { - event[_stopImmediatePropagationFlag] = value; + set srcElement(_) { + // this member is deprecated } - // Type guards that widen the event type - - function hasRelatedTarget( - event, - ) { - return ReflectHas(event, "relatedTarget"); + get currentTarget() { + return this[_attributes].currentTarget; } - const isTrusted = ObjectGetOwnPropertyDescriptor({ - get isTrusted() { - return this[_isTrusted]; - }, - }, "isTrusted").get; - - const eventInitConverter = webidl.createDictionaryConverter("EventInit", [{ - key: "bubbles", - defaultValue: false, - converter: webidl.converters.boolean, - }, { - key: "cancelable", - defaultValue: false, - converter: webidl.converters.boolean, - }, { - key: "composed", - defaultValue: false, - converter: webidl.converters.boolean, - }]); - - const _attributes = Symbol("[[attributes]]"); - const _canceledFlag = Symbol("[[canceledFlag]]"); - const _stopPropagationFlag = Symbol("[[stopPropagationFlag]]"); - const _stopImmediatePropagationFlag = Symbol( - "[[stopImmediatePropagationFlag]]", - ); - const _inPassiveListener = Symbol("[[inPassiveListener]]"); - const _dispatched = Symbol("[[dispatched]]"); - const _isTrusted = Symbol("[[isTrusted]]"); - const _path = Symbol("[[path]]"); - // internal. - const _skipInternalInit = Symbol("[[skipSlowInit]]"); - - class Event { - constructor(type, eventInitDict = {}) { - // TODO(lucacasonato): remove when this interface is spec aligned - this[SymbolToStringTag] = "Event"; - this[_canceledFlag] = false; - this[_stopPropagationFlag] = false; - this[_stopImmediatePropagationFlag] = false; - this[_inPassiveListener] = false; - this[_dispatched] = false; - this[_isTrusted] = false; - this[_path] = []; - - if (!eventInitDict[_skipInternalInit]) { - webidl.requiredArguments(arguments.length, 1, { - prefix: "Failed to construct 'Event'", - }); - type = webidl.converters.DOMString(type, { - prefix: "Failed to construct 'Event'", - context: "Argument 1", - }); - const eventInit = eventInitConverter(eventInitDict, { - prefix: "Failed to construct 'Event'", - context: "Argument 2", - }); - this[_attributes] = { - type, - ...eventInit, - currentTarget: null, - eventPhase: Event.NONE, - target: null, - timeStamp: DateNow(), - }; - // [LegacyUnforgeable] - ReflectDefineProperty(this, "isTrusted", { - enumerable: true, - get: isTrusted, - }); - } else { - this[_attributes] = { - type, - data: eventInitDict.data ?? null, - bubbles: eventInitDict.bubbles ?? false, - cancelable: eventInitDict.cancelable ?? false, - composed: eventInitDict.composed ?? false, - currentTarget: null, - eventPhase: Event.NONE, - target: null, - timeStamp: DateNow(), - }; - // TODO(@littledivy): Not spec compliant but performance is hurt badly - // for users of `_skipInternalInit`. - this.isTrusted = false; - } + composedPath() { + const path = this[_path]; + if (path.length === 0) { + return []; } - [SymbolFor("Deno.privateCustomInspect")](inspect) { - return inspect(consoleInternal.createFilteredInspectProxy({ - object: this, - evaluate: ObjectPrototypeIsPrototypeOf(Event.prototype, this), - keys: EVENT_PROPS, - })); + if (!this.currentTarget) { + throw new Error("assertion error"); } + const composedPath = [ + { + item: this.currentTarget, + itemInShadowTree: false, + relatedTarget: null, + rootOfClosedTree: false, + slotInClosedTree: false, + target: null, + touchTargetList: [], + }, + ]; - get type() { - return this[_attributes].type; - } + let currentTargetIndex = 0; + let currentTargetHiddenSubtreeLevel = 0; - get target() { - return this[_attributes].target; - } + for (let index = path.length - 1; index >= 0; index--) { + const { item, rootOfClosedTree, slotInClosedTree } = path[index]; - get srcElement() { - return null; - } + if (rootOfClosedTree) { + currentTargetHiddenSubtreeLevel++; + } - set srcElement(_) { - // this member is deprecated - } + if (item === this.currentTarget) { + currentTargetIndex = index; + break; + } - get currentTarget() { - return this[_attributes].currentTarget; + if (slotInClosedTree) { + currentTargetHiddenSubtreeLevel--; + } } - composedPath() { - const path = this[_path]; - if (path.length === 0) { - return []; - } + let currentHiddenLevel = currentTargetHiddenSubtreeLevel; + let maxHiddenLevel = currentTargetHiddenSubtreeLevel; + + for (let i = currentTargetIndex - 1; i >= 0; i--) { + const { item, rootOfClosedTree, slotInClosedTree } = path[i]; - if (!this.currentTarget) { - throw new Error("assertion error"); + if (rootOfClosedTree) { + currentHiddenLevel++; } - const composedPath = [ - { - item: this.currentTarget, + + if (currentHiddenLevel <= maxHiddenLevel) { + ArrayPrototypeUnshift(composedPath, { + item, itemInShadowTree: false, relatedTarget: null, rootOfClosedTree: false, slotInClosedTree: false, target: null, touchTargetList: [], - }, - ]; - - let currentTargetIndex = 0; - let currentTargetHiddenSubtreeLevel = 0; - - for (let index = path.length - 1; index >= 0; index--) { - const { item, rootOfClosedTree, slotInClosedTree } = path[index]; - - if (rootOfClosedTree) { - currentTargetHiddenSubtreeLevel++; - } - - if (item === this.currentTarget) { - currentTargetIndex = index; - break; - } - - if (slotInClosedTree) { - currentTargetHiddenSubtreeLevel--; - } + }); } - let currentHiddenLevel = currentTargetHiddenSubtreeLevel; - let maxHiddenLevel = currentTargetHiddenSubtreeLevel; - - for (let i = currentTargetIndex - 1; i >= 0; i--) { - const { item, rootOfClosedTree, slotInClosedTree } = path[i]; - - if (rootOfClosedTree) { - currentHiddenLevel++; - } - - if (currentHiddenLevel <= maxHiddenLevel) { - ArrayPrototypeUnshift(composedPath, { - item, - itemInShadowTree: false, - relatedTarget: null, - rootOfClosedTree: false, - slotInClosedTree: false, - target: null, - touchTargetList: [], - }); - } - - if (slotInClosedTree) { - currentHiddenLevel--; + if (slotInClosedTree) { + currentHiddenLevel--; - if (currentHiddenLevel < maxHiddenLevel) { - maxHiddenLevel = currentHiddenLevel; - } + if (currentHiddenLevel < maxHiddenLevel) { + maxHiddenLevel = currentHiddenLevel; } } + } - currentHiddenLevel = currentTargetHiddenSubtreeLevel; - maxHiddenLevel = currentTargetHiddenSubtreeLevel; + currentHiddenLevel = currentTargetHiddenSubtreeLevel; + maxHiddenLevel = currentTargetHiddenSubtreeLevel; - for (let index = currentTargetIndex + 1; index < path.length; index++) { - const { item, rootOfClosedTree, slotInClosedTree } = path[index]; + for (let index = currentTargetIndex + 1; index < path.length; index++) { + const { item, rootOfClosedTree, slotInClosedTree } = path[index]; - if (slotInClosedTree) { - currentHiddenLevel++; - } + if (slotInClosedTree) { + currentHiddenLevel++; + } - if (currentHiddenLevel <= maxHiddenLevel) { - ArrayPrototypePush(composedPath, { - item, - itemInShadowTree: false, - relatedTarget: null, - rootOfClosedTree: false, - slotInClosedTree: false, - target: null, - touchTargetList: [], - }); - } + if (currentHiddenLevel <= maxHiddenLevel) { + ArrayPrototypePush(composedPath, { + item, + itemInShadowTree: false, + relatedTarget: null, + rootOfClosedTree: false, + slotInClosedTree: false, + target: null, + touchTargetList: [], + }); + } - if (rootOfClosedTree) { - currentHiddenLevel--; + if (rootOfClosedTree) { + currentHiddenLevel--; - if (currentHiddenLevel < maxHiddenLevel) { - maxHiddenLevel = currentHiddenLevel; - } + if (currentHiddenLevel < maxHiddenLevel) { + maxHiddenLevel = currentHiddenLevel; } } - return ArrayPrototypeMap(composedPath, (p) => p.item); - } - - get NONE() { - return Event.NONE; - } - - get CAPTURING_PHASE() { - return Event.CAPTURING_PHASE; - } - - get AT_TARGET() { - return Event.AT_TARGET; - } - - get BUBBLING_PHASE() { - return Event.BUBBLING_PHASE; - } - - static get NONE() { - return 0; } + return ArrayPrototypeMap(composedPath, (p) => p.item); + } - static get CAPTURING_PHASE() { - return 1; - } + get NONE() { + return Event.NONE; + } - static get AT_TARGET() { - return 2; - } + get CAPTURING_PHASE() { + return Event.CAPTURING_PHASE; + } - static get BUBBLING_PHASE() { - return 3; - } + get AT_TARGET() { + return Event.AT_TARGET; + } - get eventPhase() { - return this[_attributes].eventPhase; - } + get BUBBLING_PHASE() { + return Event.BUBBLING_PHASE; + } - stopPropagation() { - this[_stopPropagationFlag] = true; - } + static get NONE() { + return 0; + } - get cancelBubble() { - return this[_stopPropagationFlag]; - } + static get CAPTURING_PHASE() { + return 1; + } - set cancelBubble(value) { - this[_stopPropagationFlag] = webidl.converters.boolean(value); - } + static get AT_TARGET() { + return 2; + } - stopImmediatePropagation() { - this[_stopPropagationFlag] = true; - this[_stopImmediatePropagationFlag] = true; - } + static get BUBBLING_PHASE() { + return 3; + } - get bubbles() { - return this[_attributes].bubbles; - } + get eventPhase() { + return this[_attributes].eventPhase; + } - get cancelable() { - return this[_attributes].cancelable; - } + stopPropagation() { + this[_stopPropagationFlag] = true; + } - get returnValue() { - return !this[_canceledFlag]; - } + get cancelBubble() { + return this[_stopPropagationFlag]; + } - set returnValue(value) { - if (!webidl.converters.boolean(value)) { - this[_canceledFlag] = true; - } - } + set cancelBubble(value) { + this[_stopPropagationFlag] = webidl.converters.boolean(value); + } - preventDefault() { - if (this[_attributes].cancelable && !this[_inPassiveListener]) { - this[_canceledFlag] = true; - } - } + stopImmediatePropagation() { + this[_stopPropagationFlag] = true; + this[_stopImmediatePropagationFlag] = true; + } - get defaultPrevented() { - return this[_canceledFlag]; - } + get bubbles() { + return this[_attributes].bubbles; + } - get composed() { - return this[_attributes].composed; - } + get cancelable() { + return this[_attributes].cancelable; + } - get initialized() { - return true; - } + get returnValue() { + return !this[_canceledFlag]; + } - get timeStamp() { - return this[_attributes].timeStamp; + set returnValue(value) { + if (!webidl.converters.boolean(value)) { + this[_canceledFlag] = true; } } - function defineEnumerableProps( - Ctor, - props, - ) { - for (let i = 0; i < props.length; ++i) { - const prop = props[i]; - ReflectDefineProperty(Ctor.prototype, prop, { enumerable: true }); + preventDefault() { + if (this[_attributes].cancelable && !this[_inPassiveListener]) { + this[_canceledFlag] = true; } } - const EVENT_PROPS = [ - "bubbles", - "cancelable", - "composed", - "currentTarget", - "defaultPrevented", - "eventPhase", - "srcElement", - "target", - "returnValue", - "timeStamp", - "type", - ]; - - defineEnumerableProps(Event, EVENT_PROPS); - - // This is currently the only node type we are using, so instead of implementing - // the whole of the Node interface at the moment, this just gives us the one - // value to power the standards based logic - const DOCUMENT_FRAGMENT_NODE = 11; - - // DOM Logic Helper functions and type guards - - /** Get the parent node, for event targets that have a parent. - * - * Ref: https://dom.spec.whatwg.org/#get-the-parent */ - function getParent(eventTarget) { - return isNode(eventTarget) ? eventTarget.parentNode : null; + get defaultPrevented() { + return this[_canceledFlag]; } - function getRoot(eventTarget) { - return isNode(eventTarget) - ? eventTarget.getRootNode({ composed: true }) - : null; + get composed() { + return this[_attributes].composed; } - function isNode( - eventTarget, - ) { - return Boolean(eventTarget && ReflectHas(eventTarget, "nodeType")); + get initialized() { + return true; } - // https://dom.spec.whatwg.org/#concept-shadow-including-inclusive-ancestor - function isShadowInclusiveAncestor( - ancestor, - node, - ) { - while (isNode(node)) { - if (node === ancestor) { - return true; - } - - if (isShadowRoot(node)) { - node = node && getHost(node); - } else { - node = getParent(node); - } - } - - return false; + get timeStamp() { + return this[_attributes].timeStamp; } - - function isShadowRoot(nodeImpl) { - return Boolean( - nodeImpl && - isNode(nodeImpl) && - nodeImpl.nodeType === DOCUMENT_FRAGMENT_NODE && - getHost(nodeImpl) != null, - ); +} + +function defineEnumerableProps( + Ctor, + props, +) { + for (let i = 0; i < props.length; ++i) { + const prop = props[i]; + ReflectDefineProperty(Ctor.prototype, prop, { enumerable: true }); } +} + +const EVENT_PROPS = [ + "bubbles", + "cancelable", + "composed", + "currentTarget", + "defaultPrevented", + "eventPhase", + "srcElement", + "target", + "returnValue", + "timeStamp", + "type", +]; + +defineEnumerableProps(Event, EVENT_PROPS); + +// This is currently the only node type we are using, so instead of implementing +// the whole of the Node interface at the moment, this just gives us the one +// value to power the standards based logic +const DOCUMENT_FRAGMENT_NODE = 11; + +// DOM Logic Helper functions and type guards + +/** Get the parent node, for event targets that have a parent. + * + * Ref: https://dom.spec.whatwg.org/#get-the-parent */ +function getParent(eventTarget) { + return isNode(eventTarget) ? eventTarget.parentNode : null; +} + +function getRoot(eventTarget) { + return isNode(eventTarget) + ? eventTarget.getRootNode({ composed: true }) + : null; +} + +function isNode( + eventTarget, +) { + return Boolean(eventTarget && ReflectHas(eventTarget, "nodeType")); +} + +// https://dom.spec.whatwg.org/#concept-shadow-including-inclusive-ancestor +function isShadowInclusiveAncestor( + ancestor, + node, +) { + while (isNode(node)) { + if (node === ancestor) { + return true; + } - function isSlotable( - nodeImpl, - ) { - return Boolean(isNode(nodeImpl) && ReflectHas(nodeImpl, "assignedSlot")); + if (isShadowRoot(node)) { + node = node && getHost(node); + } else { + node = getParent(node); + } } - // DOM Logic functions + return false; +} - /** Append a path item to an event's path. - * - * Ref: https://dom.spec.whatwg.org/#concept-event-path-append - */ - function appendToEventPath( - eventImpl, - target, - targetOverride, +function isShadowRoot(nodeImpl) { + return Boolean( + nodeImpl && + isNode(nodeImpl) && + nodeImpl.nodeType === DOCUMENT_FRAGMENT_NODE && + getHost(nodeImpl) != null, + ); +} + +function isSlotable( + nodeImpl, +) { + return Boolean(isNode(nodeImpl) && ReflectHas(nodeImpl, "assignedSlot")); +} + +// DOM Logic functions + +/** Append a path item to an event's path. + * + * Ref: https://dom.spec.whatwg.org/#concept-event-path-append + */ +function appendToEventPath( + eventImpl, + target, + targetOverride, + relatedTarget, + touchTargets, + slotInClosedTree, +) { + const itemInShadowTree = isNode(target) && isShadowRoot(getRoot(target)); + const rootOfClosedTree = isShadowRoot(target) && + getMode(target) === "closed"; + + ArrayPrototypePush(getPath(eventImpl), { + item: target, + itemInShadowTree, + target: targetOverride, relatedTarget, - touchTargets, + touchTargetList: touchTargets, + rootOfClosedTree, slotInClosedTree, - ) { - const itemInShadowTree = isNode(target) && isShadowRoot(getRoot(target)); - const rootOfClosedTree = isShadowRoot(target) && - getMode(target) === "closed"; - - ArrayPrototypePush(getPath(eventImpl), { - item: target, - itemInShadowTree, - target: targetOverride, + }); +} + +function dispatch( + targetImpl, + eventImpl, + targetOverride, +) { + let clearTargets = false; + let activationTarget = null; + + setDispatched(eventImpl, true); + + targetOverride = targetOverride ?? targetImpl; + const eventRelatedTarget = hasRelatedTarget(eventImpl) + ? eventImpl.relatedTarget + : null; + let relatedTarget = retarget(eventRelatedTarget, targetImpl); + + if (targetImpl !== relatedTarget || targetImpl === eventRelatedTarget) { + const touchTargets = []; + + appendToEventPath( + eventImpl, + targetImpl, + targetOverride, relatedTarget, - touchTargetList: touchTargets, - rootOfClosedTree, - slotInClosedTree, - }); - } + touchTargets, + false, + ); - function dispatch( - targetImpl, - eventImpl, - targetOverride, - ) { - let clearTargets = false; - let activationTarget = null; + const isActivationEvent = eventImpl.type === "click"; - setDispatched(eventImpl, true); + if (isActivationEvent && getHasActivationBehavior(targetImpl)) { + activationTarget = targetImpl; + } - targetOverride = targetOverride ?? targetImpl; - const eventRelatedTarget = hasRelatedTarget(eventImpl) - ? eventImpl.relatedTarget + let slotInClosedTree = false; + let slotable = isSlotable(targetImpl) && getAssignedSlot(targetImpl) + ? targetImpl : null; - let relatedTarget = retarget(eventRelatedTarget, targetImpl); - - if (targetImpl !== relatedTarget || targetImpl === eventRelatedTarget) { - const touchTargets = []; - - appendToEventPath( - eventImpl, - targetImpl, - targetOverride, - relatedTarget, - touchTargets, - false, - ); - - const isActivationEvent = eventImpl.type === "click"; + let parent = getParent(targetImpl); - if (isActivationEvent && getHasActivationBehavior(targetImpl)) { - activationTarget = targetImpl; - } - - let slotInClosedTree = false; - let slotable = isSlotable(targetImpl) && getAssignedSlot(targetImpl) - ? targetImpl - : null; - let parent = getParent(targetImpl); - - // Populate event path - // https://dom.spec.whatwg.org/#event-path - while (parent !== null) { - if (slotable !== null) { - slotable = null; - - const parentRoot = getRoot(parent); - if ( - isShadowRoot(parentRoot) && - parentRoot && - getMode(parentRoot) === "closed" - ) { - slotInClosedTree = true; - } - } - - relatedTarget = retarget(eventRelatedTarget, parent); + // Populate event path + // https://dom.spec.whatwg.org/#event-path + while (parent !== null) { + if (slotable !== null) { + slotable = null; + const parentRoot = getRoot(parent); if ( - isNode(parent) && - isShadowInclusiveAncestor(getRoot(targetImpl), parent) + isShadowRoot(parentRoot) && + parentRoot && + getMode(parentRoot) === "closed" ) { - appendToEventPath( - eventImpl, - parent, - null, - relatedTarget, - touchTargets, - slotInClosedTree, - ); - } else if (parent === relatedTarget) { - parent = null; - } else { - targetImpl = parent; - - if ( - isActivationEvent && - activationTarget === null && - getHasActivationBehavior(targetImpl) - ) { - activationTarget = targetImpl; - } - - appendToEventPath( - eventImpl, - parent, - targetImpl, - relatedTarget, - touchTargets, - slotInClosedTree, - ); - } - - if (parent !== null) { - parent = getParent(parent); - } - - slotInClosedTree = false; - } - - let clearTargetsTupleIndex = -1; - const path = getPath(eventImpl); - for ( - let i = path.length - 1; - i >= 0 && clearTargetsTupleIndex === -1; - i-- - ) { - if (path[i].target !== null) { - clearTargetsTupleIndex = i; - } - } - const clearTargetsTuple = path[clearTargetsTupleIndex]; - - clearTargets = (isNode(clearTargetsTuple.target) && - isShadowRoot(getRoot(clearTargetsTuple.target))) || - (isNode(clearTargetsTuple.relatedTarget) && - isShadowRoot(getRoot(clearTargetsTuple.relatedTarget))); - - setEventPhase(eventImpl, Event.CAPTURING_PHASE); - - for (let i = path.length - 1; i >= 0; --i) { - const tuple = path[i]; - - if (tuple.target === null) { - invokeEventListeners(tuple, eventImpl); + slotInClosedTree = true; } } - for (let i = 0; i < path.length; i++) { - const tuple = path[i]; + relatedTarget = retarget(eventRelatedTarget, parent); - if (tuple.target !== null) { - setEventPhase(eventImpl, Event.AT_TARGET); - } else { - setEventPhase(eventImpl, Event.BUBBLING_PHASE); - } + if ( + isNode(parent) && + isShadowInclusiveAncestor(getRoot(targetImpl), parent) + ) { + appendToEventPath( + eventImpl, + parent, + null, + relatedTarget, + touchTargets, + slotInClosedTree, + ); + } else if (parent === relatedTarget) { + parent = null; + } else { + targetImpl = parent; if ( - (eventImpl.eventPhase === Event.BUBBLING_PHASE && - eventImpl.bubbles) || - eventImpl.eventPhase === Event.AT_TARGET + isActivationEvent && + activationTarget === null && + getHasActivationBehavior(targetImpl) ) { - invokeEventListeners(tuple, eventImpl); + activationTarget = targetImpl; } + + appendToEventPath( + eventImpl, + parent, + targetImpl, + relatedTarget, + touchTargets, + slotInClosedTree, + ); } - } - setEventPhase(eventImpl, Event.NONE); - setCurrentTarget(eventImpl, null); - setPath(eventImpl, []); - setDispatched(eventImpl, false); - eventImpl.cancelBubble = false; - setStopImmediatePropagation(eventImpl, false); + if (parent !== null) { + parent = getParent(parent); + } - if (clearTargets) { - setTarget(eventImpl, null); - setRelatedTarget(eventImpl, null); + slotInClosedTree = false; } - // TODO(bartlomieju): invoke activation targets if HTML nodes will be implemented - // if (activationTarget !== null) { - // if (!eventImpl.defaultPrevented) { - // activationTarget._activationBehavior(); - // } - // } + let clearTargetsTupleIndex = -1; + const path = getPath(eventImpl); + for ( + let i = path.length - 1; + i >= 0 && clearTargetsTupleIndex === -1; + i-- + ) { + if (path[i].target !== null) { + clearTargetsTupleIndex = i; + } + } + const clearTargetsTuple = path[clearTargetsTupleIndex]; - return !eventImpl.defaultPrevented; - } + clearTargets = (isNode(clearTargetsTuple.target) && + isShadowRoot(getRoot(clearTargetsTuple.target))) || + (isNode(clearTargetsTuple.relatedTarget) && + isShadowRoot(getRoot(clearTargetsTuple.relatedTarget))); - /** Inner invoking of the event listeners where the resolved listeners are - * called. - * - * Ref: https://dom.spec.whatwg.org/#concept-event-listener-inner-invoke */ - function innerInvokeEventListeners( - eventImpl, - targetListeners, - ) { - let found = false; + setEventPhase(eventImpl, Event.CAPTURING_PHASE); - const { type } = eventImpl; + for (let i = path.length - 1; i >= 0; --i) { + const tuple = path[i]; - if (!targetListeners || !targetListeners[type]) { - return found; + if (tuple.target === null) { + invokeEventListeners(tuple, eventImpl); + } } - // Copy event listeners before iterating since the list can be modified during the iteration. - const handlers = ArrayPrototypeSlice(targetListeners[type]); + for (let i = 0; i < path.length; i++) { + const tuple = path[i]; - for (let i = 0; i < handlers.length; i++) { - const listener = handlers[i]; - - let capture, once, passive; - if (typeof listener.options === "boolean") { - capture = listener.options; - once = false; - passive = false; + if (tuple.target !== null) { + setEventPhase(eventImpl, Event.AT_TARGET); } else { - capture = listener.options.capture; - once = listener.options.once; - passive = listener.options.passive; - } - - // Check if the event listener has been removed since the listeners has been cloned. - if (!ArrayPrototypeIncludes(targetListeners[type], listener)) { - continue; + setEventPhase(eventImpl, Event.BUBBLING_PHASE); } - found = true; - if ( - (eventImpl.eventPhase === Event.CAPTURING_PHASE && !capture) || - (eventImpl.eventPhase === Event.BUBBLING_PHASE && capture) + (eventImpl.eventPhase === Event.BUBBLING_PHASE && + eventImpl.bubbles) || + eventImpl.eventPhase === Event.AT_TARGET ) { - continue; - } - - if (once) { - ArrayPrototypeSplice( - targetListeners[type], - ArrayPrototypeIndexOf(targetListeners[type], listener), - 1, - ); - } - - if (passive) { - setInPassiveListener(eventImpl, true); - } - - if (typeof listener.callback === "object") { - if (typeof listener.callback.handleEvent === "function") { - listener.callback.handleEvent(eventImpl); - } - } else { - FunctionPrototypeCall( - listener.callback, - eventImpl.currentTarget, - eventImpl, - ); + invokeEventListeners(tuple, eventImpl); } + } + } - setInPassiveListener(eventImpl, false); + setEventPhase(eventImpl, Event.NONE); + setCurrentTarget(eventImpl, null); + setPath(eventImpl, []); + setDispatched(eventImpl, false); + eventImpl.cancelBubble = false; + setStopImmediatePropagation(eventImpl, false); - if (getStopImmediatePropagation(eventImpl)) { - return found; - } - } + if (clearTargets) { + setTarget(eventImpl, null); + setRelatedTarget(eventImpl, null); + } + // TODO(bartlomieju): invoke activation targets if HTML nodes will be implemented + // if (activationTarget !== null) { + // if (!eventImpl.defaultPrevented) { + // activationTarget._activationBehavior(); + // } + // } + + return !eventImpl.defaultPrevented; +} + +/** Inner invoking of the event listeners where the resolved listeners are + * called. + * + * Ref: https://dom.spec.whatwg.org/#concept-event-listener-inner-invoke */ +function innerInvokeEventListeners( + eventImpl, + targetListeners, +) { + let found = false; + + const { type } = eventImpl; + + if (!targetListeners || !targetListeners[type]) { return found; } - /** Invokes the listeners on a given event path with the supplied event. - * - * Ref: https://dom.spec.whatwg.org/#concept-event-listener-invoke */ - function invokeEventListeners(tuple, eventImpl) { - const path = getPath(eventImpl); - const tupleIndex = ArrayPrototypeIndexOf(path, tuple); - for (let i = tupleIndex; i >= 0; i--) { - const t = path[i]; - if (t.target) { - setTarget(eventImpl, t.target); - break; - } - } + // Copy event listeners before iterating since the list can be modified during the iteration. + const handlers = ArrayPrototypeSlice(targetListeners[type]); - setRelatedTarget(eventImpl, tuple.relatedTarget); + for (let i = 0; i < handlers.length; i++) { + const listener = handlers[i]; - if (eventImpl.cancelBubble) { - return; + let capture, once, passive; + if (typeof listener.options === "boolean") { + capture = listener.options; + once = false; + passive = false; + } else { + capture = listener.options.capture; + once = listener.options.once; + passive = listener.options.passive; } - setCurrentTarget(eventImpl, tuple.item); - - try { - innerInvokeEventListeners(eventImpl, getListeners(tuple.item)); - } catch (error) { - reportException(error); + // Check if the event listener has been removed since the listeners has been cloned. + if (!ArrayPrototypeIncludes(targetListeners[type], listener)) { + continue; } - } - function normalizeEventHandlerOptions( - options, - ) { - if (typeof options === "boolean" || typeof options === "undefined") { - return { - capture: Boolean(options), - }; - } else { - return options; - } - } + found = true; - /** Retarget the target following the spec logic. - * - * Ref: https://dom.spec.whatwg.org/#retarget */ - function retarget(a, b) { - while (true) { - if (!isNode(a)) { - return a; - } + if ( + (eventImpl.eventPhase === Event.CAPTURING_PHASE && !capture) || + (eventImpl.eventPhase === Event.BUBBLING_PHASE && capture) + ) { + continue; + } - const aRoot = a.getRootNode(); + if (once) { + ArrayPrototypeSplice( + targetListeners[type], + ArrayPrototypeIndexOf(targetListeners[type], listener), + 1, + ); + } - if (aRoot) { - if ( - !isShadowRoot(aRoot) || - (isNode(b) && isShadowInclusiveAncestor(aRoot, b)) - ) { - return a; - } + if (passive) { + setInPassiveListener(eventImpl, true); + } - a = getHost(aRoot); + if (typeof listener.callback === "object") { + if (typeof listener.callback.handleEvent === "function") { + listener.callback.handleEvent(eventImpl); } + } else { + FunctionPrototypeCall( + listener.callback, + eventImpl.currentTarget, + eventImpl, + ); } - } - // Accessors for non-public data + setInPassiveListener(eventImpl, false); - const eventTargetData = Symbol(); - - function setEventTargetData(target) { - target[eventTargetData] = getDefaultTargetData(); - } - - function getAssignedSlot(target) { - return Boolean(target?.[eventTargetData]?.assignedSlot); + if (getStopImmediatePropagation(eventImpl)) { + return found; + } } - function getHasActivationBehavior(target) { - return Boolean(target?.[eventTargetData]?.hasActivationBehavior); + return found; +} + +/** Invokes the listeners on a given event path with the supplied event. + * + * Ref: https://dom.spec.whatwg.org/#concept-event-listener-invoke */ +function invokeEventListeners(tuple, eventImpl) { + const path = getPath(eventImpl); + const tupleIndex = ArrayPrototypeIndexOf(path, tuple); + for (let i = tupleIndex; i >= 0; i--) { + const t = path[i]; + if (t.target) { + setTarget(eventImpl, t.target); + break; + } } - function getHost(target) { - return target?.[eventTargetData]?.host ?? null; - } + setRelatedTarget(eventImpl, tuple.relatedTarget); - function getListeners(target) { - return target?.[eventTargetData]?.listeners ?? {}; + if (eventImpl.cancelBubble) { + return; } - function getMode(target) { - return target?.[eventTargetData]?.mode ?? null; - } + setCurrentTarget(eventImpl, tuple.item); - function listenerCount(target, type) { - return getListeners(target)?.[type]?.length ?? 0; + try { + innerInvokeEventListeners(eventImpl, getListeners(tuple.item)); + } catch (error) { + reportException(error); } +} - function getDefaultTargetData() { +function normalizeEventHandlerOptions( + options, +) { + if (typeof options === "boolean" || typeof options === "undefined") { return { - assignedSlot: false, - hasActivationBehavior: false, - host: null, - listeners: ObjectCreate(null), - mode: "", + capture: Boolean(options), }; + } else { + return options; } +} - // This is lazy loaded because there is a circular dependency with AbortSignal. - let addEventListenerOptionsConverter; - - function lazyAddEventListenerOptionsConverter() { - addEventListenerOptionsConverter ??= webidl.createDictionaryConverter( - "AddEventListenerOptions", - [ - { - key: "capture", - defaultValue: false, - converter: webidl.converters.boolean, - }, - { - key: "passive", - defaultValue: false, - converter: webidl.converters.boolean, - }, - { - key: "once", - defaultValue: false, - converter: webidl.converters.boolean, - }, - { - key: "signal", - converter: webidl.converters.AbortSignal, - }, - ], - ); - } - - webidl.converters.AddEventListenerOptions = (V, opts) => { - if (webidl.type(V) !== "Object" || V === null) { - V = { capture: Boolean(V) }; - } - - lazyAddEventListenerOptionsConverter(); - return addEventListenerOptionsConverter(V, opts); - }; - - class EventTarget { - constructor() { - this[eventTargetData] = getDefaultTargetData(); - this[webidl.brand] = webidl.brand; +/** Retarget the target following the spec logic. + * + * Ref: https://dom.spec.whatwg.org/#retarget */ +function retarget(a, b) { + while (true) { + if (!isNode(a)) { + return a; } - addEventListener( - type, - callback, - options, - ) { - const self = this ?? globalThis; - webidl.assertBranded(self, EventTargetPrototype); - const prefix = "Failed to execute 'addEventListener' on 'EventTarget'"; + const aRoot = a.getRootNode(); - webidl.requiredArguments(arguments.length, 2, { - prefix, - }); + if (aRoot) { + if ( + !isShadowRoot(aRoot) || + (isNode(b) && isShadowInclusiveAncestor(aRoot, b)) + ) { + return a; + } - options = webidl.converters.AddEventListenerOptions(options, { - prefix, - context: "Argument 3", - }); + a = getHost(aRoot); + } + } +} - if (callback === null) { - return; - } +// Accessors for non-public data - const { listeners } = self[eventTargetData]; +const eventTargetData = Symbol(); - if (!(ReflectHas(listeners, type))) { - listeners[type] = []; - } +function setEventTargetData(target) { + target[eventTargetData] = getDefaultTargetData(); +} - const listenerList = listeners[type]; - for (let i = 0; i < listenerList.length; ++i) { - const listener = listenerList[i]; - if ( - ((typeof listener.options === "boolean" && - listener.options === options.capture) || - (typeof listener.options === "object" && - listener.options.capture === options.capture)) && - listener.callback === callback - ) { - return; - } - } - if (options?.signal) { - const signal = options?.signal; - if (signal.aborted) { - // If signal is not null and its aborted flag is set, then return. - return; - } else { - // If listener’s signal is not null, then add the following abort - // abort steps to it: Remove an event listener. - signal.addEventListener("abort", () => { - self.removeEventListener(type, callback, options); - }); - } - } +function getAssignedSlot(target) { + return Boolean(target?.[eventTargetData]?.assignedSlot); +} - ArrayPrototypePush(listeners[type], { callback, options }); - } +function getHasActivationBehavior(target) { + return Boolean(target?.[eventTargetData]?.hasActivationBehavior); +} - removeEventListener( - type, - callback, - options, - ) { - const self = this ?? globalThis; - webidl.assertBranded(self, EventTargetPrototype); - webidl.requiredArguments(arguments.length, 2, { - prefix: "Failed to execute 'removeEventListener' on 'EventTarget'", - }); +function getHost(target) { + return target?.[eventTargetData]?.host ?? null; +} - const { listeners } = self[eventTargetData]; - if (callback !== null && ReflectHas(listeners, type)) { - listeners[type] = ArrayPrototypeFilter( - listeners[type], - (listener) => listener.callback !== callback, - ); - } else if (callback === null || !listeners[type]) { - return; - } +function getListeners(target) { + return target?.[eventTargetData]?.listeners ?? {}; +} - options = normalizeEventHandlerOptions(options); +function getMode(target) { + return target?.[eventTargetData]?.mode ?? null; +} - for (let i = 0; i < listeners[type].length; ++i) { - const listener = listeners[type][i]; - if ( - ((typeof listener.options === "boolean" && - listener.options === options.capture) || - (typeof listener.options === "object" && - listener.options.capture === options.capture)) && - listener.callback === callback - ) { - ArrayPrototypeSplice(listeners[type], i, 1); - break; - } - } - } +function listenerCount(target, type) { + return getListeners(target)?.[type]?.length ?? 0; +} - dispatchEvent(event) { - // If `this` is not present, then fallback to global scope. We don't use - // `globalThis` directly here, because it could be deleted by user. - // Instead use saved reference to global scope when the script was - // executed. - const self = this ?? window; - webidl.assertBranded(self, EventTargetPrototype); - webidl.requiredArguments(arguments.length, 1, { - prefix: "Failed to execute 'dispatchEvent' on 'EventTarget'", - }); +function getDefaultTargetData() { + return { + assignedSlot: false, + hasActivationBehavior: false, + host: null, + listeners: ObjectCreate(null), + mode: "", + }; +} - const { listeners } = self[eventTargetData]; - if (!ReflectHas(listeners, event.type)) { - setTarget(event, this); - return true; - } +// This is lazy loaded because there is a circular dependency with AbortSignal. +let addEventListenerOptionsConverter; - if (getDispatched(event)) { - throw new DOMException("Invalid event state.", "InvalidStateError"); - } +function lazyAddEventListenerOptionsConverter() { + addEventListenerOptionsConverter ??= webidl.createDictionaryConverter( + "AddEventListenerOptions", + [ + { + key: "capture", + defaultValue: false, + converter: webidl.converters.boolean, + }, + { + key: "passive", + defaultValue: false, + converter: webidl.converters.boolean, + }, + { + key: "once", + defaultValue: false, + converter: webidl.converters.boolean, + }, + { + key: "signal", + converter: webidl.converters.AbortSignal, + }, + ], + ); +} - if (event.eventPhase !== Event.NONE) { - throw new DOMException("Invalid event state.", "InvalidStateError"); - } +webidl.converters.AddEventListenerOptions = (V, opts) => { + if (webidl.type(V) !== "Object" || V === null) { + V = { capture: Boolean(V) }; + } - return dispatch(self, event); - } + lazyAddEventListenerOptionsConverter(); + return addEventListenerOptionsConverter(V, opts); +}; - getParent(_event) { - return null; - } +class EventTarget { + constructor() { + this[eventTargetData] = getDefaultTargetData(); + this[webidl.brand] = webidl.brand; } - webidl.configurePrototype(EventTarget); - const EventTargetPrototype = EventTarget.prototype; + addEventListener( + type, + callback, + options, + ) { + const self = this ?? globalThis_; + webidl.assertBranded(self, EventTargetPrototype); + const prefix = "Failed to execute 'addEventListener' on 'EventTarget'"; - defineEnumerableProps(EventTarget, [ - "addEventListener", - "removeEventListener", - "dispatchEvent", - ]); + webidl.requiredArguments(arguments.length, 2, { + prefix, + }); - class ErrorEvent extends Event { - #message = ""; - #filename = ""; - #lineno = ""; - #colno = ""; - #error = ""; + options = webidl.converters.AddEventListenerOptions(options, { + prefix, + context: "Argument 3", + }); - get message() { - return this.#message; - } - get filename() { - return this.#filename; + if (callback === null) { + return; } - get lineno() { - return this.#lineno; + + const { listeners } = self[eventTargetData]; + + if (!(ReflectHas(listeners, type))) { + listeners[type] = []; } - get colno() { - return this.#colno; + + const listenerList = listeners[type]; + for (let i = 0; i < listenerList.length; ++i) { + const listener = listenerList[i]; + if ( + ((typeof listener.options === "boolean" && + listener.options === options.capture) || + (typeof listener.options === "object" && + listener.options.capture === options.capture)) && + listener.callback === callback + ) { + return; + } } - get error() { - return this.#error; + if (options?.signal) { + const signal = options?.signal; + if (signal.aborted) { + // If signal is not null and its aborted flag is set, then return. + return; + } else { + // If listener’s signal is not null, then add the following abort + // abort steps to it: Remove an event listener. + signal.addEventListener("abort", () => { + self.removeEventListener(type, callback, options); + }); + } } - constructor( - type, - { - bubbles, - cancelable, - composed, - message = "", - filename = "", - lineno = 0, - colno = 0, - error, - } = {}, - ) { - super(type, { - bubbles: bubbles, - cancelable: cancelable, - composed: composed, - }); + ArrayPrototypePush(listeners[type], { callback, options }); + } - this.#message = message; - this.#filename = filename; - this.#lineno = lineno; - this.#colno = colno; - this.#error = error; - } + removeEventListener( + type, + callback, + options, + ) { + const self = this ?? globalThis_; + webidl.assertBranded(self, EventTargetPrototype); + webidl.requiredArguments(arguments.length, 2, { + prefix: "Failed to execute 'removeEventListener' on 'EventTarget'", + }); - [SymbolFor("Deno.privateCustomInspect")](inspect) { - return inspect(consoleInternal.createFilteredInspectProxy({ - object: this, - evaluate: ObjectPrototypeIsPrototypeOf(ErrorEvent.prototype, this), - keys: [ - ...new SafeArrayIterator(EVENT_PROPS), - "message", - "filename", - "lineno", - "colno", - "error", - ], - })); + const { listeners } = self[eventTargetData]; + if (callback !== null && ReflectHas(listeners, type)) { + listeners[type] = ArrayPrototypeFilter( + listeners[type], + (listener) => listener.callback !== callback, + ); + } else if (callback === null || !listeners[type]) { + return; } - // TODO(lucacasonato): remove when this interface is spec aligned - [SymbolToStringTag] = "ErrorEvent"; - } + options = normalizeEventHandlerOptions(options); - defineEnumerableProps(ErrorEvent, [ - "message", - "filename", - "lineno", - "colno", - "error", - ]); + for (let i = 0; i < listeners[type].length; ++i) { + const listener = listeners[type][i]; + if ( + ((typeof listener.options === "boolean" && + listener.options === options.capture) || + (typeof listener.options === "object" && + listener.options.capture === options.capture)) && + listener.callback === callback + ) { + ArrayPrototypeSplice(listeners[type], i, 1); + break; + } + } + } - class CloseEvent extends Event { - #wasClean = ""; - #code = ""; - #reason = ""; + dispatchEvent(event) { + // If `this` is not present, then fallback to global scope. We don't use + // `globalThis` directly here, because it could be deleted by user. + // Instead use saved reference to global scope when the script was + // executed. + const self = this ?? globalThis_; + webidl.assertBranded(self, EventTargetPrototype); + webidl.requiredArguments(arguments.length, 1, { + prefix: "Failed to execute 'dispatchEvent' on 'EventTarget'", + }); - get wasClean() { - return this.#wasClean; + const { listeners } = self[eventTargetData]; + if (!ReflectHas(listeners, event.type)) { + setTarget(event, this); + return true; } - get code() { - return this.#code; + + if (getDispatched(event)) { + throw new DOMException("Invalid event state.", "InvalidStateError"); } - get reason() { - return this.#reason; + + if (event.eventPhase !== Event.NONE) { + throw new DOMException("Invalid event state.", "InvalidStateError"); } - constructor(type, { + return dispatch(self, event); + } + + getParent(_event) { + return null; + } +} + +webidl.configurePrototype(EventTarget); +const EventTargetPrototype = EventTarget.prototype; + +defineEnumerableProps(EventTarget, [ + "addEventListener", + "removeEventListener", + "dispatchEvent", +]); + +class ErrorEvent extends Event { + #message = ""; + #filename = ""; + #lineno = ""; + #colno = ""; + #error = ""; + + get message() { + return this.#message; + } + get filename() { + return this.#filename; + } + get lineno() { + return this.#lineno; + } + get colno() { + return this.#colno; + } + get error() { + return this.#error; + } + + constructor( + type, + { bubbles, cancelable, composed, - wasClean = false, - code = 0, - reason = "", - } = {}) { - super(type, { - bubbles: bubbles, - cancelable: cancelable, - composed: composed, - }); + message = "", + filename = "", + lineno = 0, + colno = 0, + error, + } = {}, + ) { + super(type, { + bubbles: bubbles, + cancelable: cancelable, + composed: composed, + }); - this.#wasClean = wasClean; - this.#code = code; - this.#reason = reason; - } + this.#message = message; + this.#filename = filename; + this.#lineno = lineno; + this.#colno = colno; + this.#error = error; + } - [SymbolFor("Deno.privateCustomInspect")](inspect) { - return inspect(consoleInternal.createFilteredInspectProxy({ - object: this, - evaluate: ObjectPrototypeIsPrototypeOf(CloseEvent.prototype, this), - keys: [ - ...new SafeArrayIterator(EVENT_PROPS), - "wasClean", - "code", - "reason", - ], - })); - } + [SymbolFor("Deno.privateCustomInspect")](inspect) { + return inspect(createFilteredInspectProxy({ + object: this, + evaluate: ObjectPrototypeIsPrototypeOf(ErrorEvent.prototype, this), + keys: [ + ...new SafeArrayIterator(EVENT_PROPS), + "message", + "filename", + "lineno", + "colno", + "error", + ], + })); } - class MessageEvent extends Event { - get source() { - return null; - } + // TODO(lucacasonato): remove when this interface is spec aligned + [SymbolToStringTag] = "ErrorEvent"; +} + +defineEnumerableProps(ErrorEvent, [ + "message", + "filename", + "lineno", + "colno", + "error", +]); + +class CloseEvent extends Event { + #wasClean = ""; + #code = ""; + #reason = ""; + + get wasClean() { + return this.#wasClean; + } + get code() { + return this.#code; + } + get reason() { + return this.#reason; + } - constructor(type, eventInitDict) { - super(type, { - bubbles: eventInitDict?.bubbles ?? false, - cancelable: eventInitDict?.cancelable ?? false, - composed: eventInitDict?.composed ?? false, - [_skipInternalInit]: eventInitDict?.[_skipInternalInit], - }); + constructor(type, { + bubbles, + cancelable, + composed, + wasClean = false, + code = 0, + reason = "", + } = {}) { + super(type, { + bubbles: bubbles, + cancelable: cancelable, + composed: composed, + }); - this.data = eventInitDict?.data ?? null; - this.ports = eventInitDict?.ports ?? []; - this.origin = eventInitDict?.origin ?? ""; - this.lastEventId = eventInitDict?.lastEventId ?? ""; - } + this.#wasClean = wasClean; + this.#code = code; + this.#reason = reason; + } - [SymbolFor("Deno.privateCustomInspect")](inspect) { - return inspect(consoleInternal.createFilteredInspectProxy({ - object: this, - evaluate: ObjectPrototypeIsPrototypeOf(MessageEvent.prototype, this), - keys: [ - ...new SafeArrayIterator(EVENT_PROPS), - "data", - "origin", - "lastEventId", - ], - })); - } + [SymbolFor("Deno.privateCustomInspect")](inspect) { + return inspect(createFilteredInspectProxy({ + object: this, + evaluate: ObjectPrototypeIsPrototypeOf(CloseEvent.prototype, this), + keys: [ + ...new SafeArrayIterator(EVENT_PROPS), + "wasClean", + "code", + "reason", + ], + })); + } +} - // TODO(lucacasonato): remove when this interface is spec aligned - [SymbolToStringTag] = "CloseEvent"; +class MessageEvent extends Event { + get source() { + return null; } - class CustomEvent extends Event { - #detail = null; + constructor(type, eventInitDict) { + super(type, { + bubbles: eventInitDict?.bubbles ?? false, + cancelable: eventInitDict?.cancelable ?? false, + composed: eventInitDict?.composed ?? false, + [_skipInternalInit]: eventInitDict?.[_skipInternalInit], + }); - constructor(type, eventInitDict = {}) { - super(type, eventInitDict); - webidl.requiredArguments(arguments.length, 1, { - prefix: "Failed to construct 'CustomEvent'", - }); - const { detail } = eventInitDict; - this.#detail = detail; - } + this.data = eventInitDict?.data ?? null; + this.ports = eventInitDict?.ports ?? []; + this.origin = eventInitDict?.origin ?? ""; + this.lastEventId = eventInitDict?.lastEventId ?? ""; + } - get detail() { - return this.#detail; - } + [SymbolFor("Deno.privateCustomInspect")](inspect) { + return inspect(createFilteredInspectProxy({ + object: this, + evaluate: ObjectPrototypeIsPrototypeOf(MessageEvent.prototype, this), + keys: [ + ...new SafeArrayIterator(EVENT_PROPS), + "data", + "origin", + "lastEventId", + ], + })); + } - [SymbolFor("Deno.privateCustomInspect")](inspect) { - return inspect(consoleInternal.createFilteredInspectProxy({ - object: this, - evaluate: ObjectPrototypeIsPrototypeOf(CustomEvent.prototype, this), - keys: [ - ...new SafeArrayIterator(EVENT_PROPS), - "detail", - ], - })); - } + // TODO(lucacasonato): remove when this interface is spec aligned + [SymbolToStringTag] = "CloseEvent"; +} - // TODO(lucacasonato): remove when this interface is spec aligned - [SymbolToStringTag] = "CustomEvent"; +class CustomEvent extends Event { + #detail = null; + + constructor(type, eventInitDict = {}) { + super(type, eventInitDict); + webidl.requiredArguments(arguments.length, 1, { + prefix: "Failed to construct 'CustomEvent'", + }); + const { detail } = eventInitDict; + this.#detail = detail; } - ReflectDefineProperty(CustomEvent.prototype, "detail", { - enumerable: true, - }); + get detail() { + return this.#detail; + } - // ProgressEvent could also be used in other DOM progress event emits. - // Current use is for FileReader. - class ProgressEvent extends Event { - constructor(type, eventInitDict = {}) { - super(type, eventInitDict); + [SymbolFor("Deno.privateCustomInspect")](inspect) { + return inspect(createFilteredInspectProxy({ + object: this, + evaluate: ObjectPrototypeIsPrototypeOf(CustomEvent.prototype, this), + keys: [ + ...new SafeArrayIterator(EVENT_PROPS), + "detail", + ], + })); + } - this.lengthComputable = eventInitDict?.lengthComputable ?? false; - this.loaded = eventInitDict?.loaded ?? 0; - this.total = eventInitDict?.total ?? 0; - } + // TODO(lucacasonato): remove when this interface is spec aligned + [SymbolToStringTag] = "CustomEvent"; +} - [SymbolFor("Deno.privateCustomInspect")](inspect) { - return inspect(consoleInternal.createFilteredInspectProxy({ - object: this, - evaluate: ObjectPrototypeIsPrototypeOf(ProgressEvent.prototype, this), - keys: [ - ...new SafeArrayIterator(EVENT_PROPS), - "lengthComputable", - "loaded", - "total", - ], - })); - } +ReflectDefineProperty(CustomEvent.prototype, "detail", { + enumerable: true, +}); - // TODO(lucacasonato): remove when this interface is spec aligned - [SymbolToStringTag] = "ProgressEvent"; +// ProgressEvent could also be used in other DOM progress event emits. +// Current use is for FileReader. +class ProgressEvent extends Event { + constructor(type, eventInitDict = {}) { + super(type, eventInitDict); + + this.lengthComputable = eventInitDict?.lengthComputable ?? false; + this.loaded = eventInitDict?.loaded ?? 0; + this.total = eventInitDict?.total ?? 0; } - class PromiseRejectionEvent extends Event { - #promise = null; - #reason = null; + [SymbolFor("Deno.privateCustomInspect")](inspect) { + return inspect(createFilteredInspectProxy({ + object: this, + evaluate: ObjectPrototypeIsPrototypeOf(ProgressEvent.prototype, this), + keys: [ + ...new SafeArrayIterator(EVENT_PROPS), + "lengthComputable", + "loaded", + "total", + ], + })); + } - get promise() { - return this.#promise; - } - get reason() { - return this.#reason; - } + // TODO(lucacasonato): remove when this interface is spec aligned + [SymbolToStringTag] = "ProgressEvent"; +} - constructor( - type, - { - bubbles, - cancelable, - composed, - promise, - reason, - } = {}, - ) { - super(type, { - bubbles: bubbles, - cancelable: cancelable, - composed: composed, - }); +class PromiseRejectionEvent extends Event { + #promise = null; + #reason = null; - this.#promise = promise; - this.#reason = reason; - } + get promise() { + return this.#promise; + } + get reason() { + return this.#reason; + } - [SymbolFor("Deno.privateCustomInspect")](inspect) { - return inspect(consoleInternal.createFilteredInspectProxy({ - object: this, - evaluate: ObjectPrototypeIsPrototypeOf( - PromiseRejectionEvent.prototype, - this, - ), - keys: [ - ...new SafeArrayIterator(EVENT_PROPS), - "promise", - "reason", - ], - })); - } + constructor( + type, + { + bubbles, + cancelable, + composed, + promise, + reason, + } = {}, + ) { + super(type, { + bubbles: bubbles, + cancelable: cancelable, + composed: composed, + }); - // TODO(lucacasonato): remove when this interface is spec aligned - [SymbolToStringTag] = "PromiseRejectionEvent"; + this.#promise = promise; + this.#reason = reason; } - defineEnumerableProps(PromiseRejectionEvent, [ - "promise", - "reason", - ]); + [SymbolFor("Deno.privateCustomInspect")](inspect) { + return inspect(createFilteredInspectProxy({ + object: this, + evaluate: ObjectPrototypeIsPrototypeOf( + PromiseRejectionEvent.prototype, + this, + ), + keys: [ + ...new SafeArrayIterator(EVENT_PROPS), + "promise", + "reason", + ], + })); + } - const _eventHandlers = Symbol("eventHandlers"); + // TODO(lucacasonato): remove when this interface is spec aligned + [SymbolToStringTag] = "PromiseRejectionEvent"; +} - function makeWrappedHandler(handler, isSpecialErrorEventHandler) { - function wrappedHandler(evt) { - if (typeof wrappedHandler.handler !== "function") { - return; - } +defineEnumerableProps(PromiseRejectionEvent, [ + "promise", + "reason", +]); - if ( - isSpecialErrorEventHandler && - ObjectPrototypeIsPrototypeOf(ErrorEvent.prototype, evt) && - evt.type === "error" - ) { - const ret = FunctionPrototypeCall( - wrappedHandler.handler, - this, - evt.message, - evt.filename, - evt.lineno, - evt.colno, - evt.error, - ); - if (ret === true) { - evt.preventDefault(); - } - return; - } +const _eventHandlers = Symbol("eventHandlers"); - return FunctionPrototypeCall(wrappedHandler.handler, this, evt); +function makeWrappedHandler(handler, isSpecialErrorEventHandler) { + function wrappedHandler(evt) { + if (typeof wrappedHandler.handler !== "function") { + return; } - wrappedHandler.handler = handler; - return wrappedHandler; - } - - // `init` is an optional function that will be called the first time that the - // event handler property is set. It will be called with the object on which - // the property is set as its argument. - // `isSpecialErrorEventHandler` can be set to true to opt into the special - // behavior of event handlers for the "error" event in a global scope. - function defineEventHandler( - emitter, - name, - init = undefined, - isSpecialErrorEventHandler = false, - ) { - // HTML specification section 8.1.7.1 - ObjectDefineProperty(emitter, `on${name}`, { - get() { - if (!this[_eventHandlers]) { - return null; - } - return MapPrototypeGet(this[_eventHandlers], name)?.handler ?? null; - }, - set(value) { - // All three Web IDL event handler types are nullable callback functions - // with the [LegacyTreatNonObjectAsNull] extended attribute, meaning - // anything other than an object is treated as null. - if (typeof value !== "object" && typeof value !== "function") { - value = null; - } + if ( + isSpecialErrorEventHandler && + ObjectPrototypeIsPrototypeOf(ErrorEvent.prototype, evt) && + evt.type === "error" + ) { + const ret = FunctionPrototypeCall( + wrappedHandler.handler, + this, + evt.message, + evt.filename, + evt.lineno, + evt.colno, + evt.error, + ); + if (ret === true) { + evt.preventDefault(); + } + return; + } - if (!this[_eventHandlers]) { - this[_eventHandlers] = new Map(); - } - let handlerWrapper = MapPrototypeGet(this[_eventHandlers], name); - if (handlerWrapper) { - handlerWrapper.handler = value; - } else if (value !== null) { - handlerWrapper = makeWrappedHandler( - value, - isSpecialErrorEventHandler, - ); - this.addEventListener(name, handlerWrapper); - init?.(this); - } - MapPrototypeSet(this[_eventHandlers], name, handlerWrapper); - }, - configurable: true, - enumerable: true, - }); + return FunctionPrototypeCall(wrappedHandler.handler, this, evt); } + wrappedHandler.handler = handler; + return wrappedHandler; +} + +// `init` is an optional function that will be called the first time that the +// event handler property is set. It will be called with the object on which +// the property is set as its argument. +// `isSpecialErrorEventHandler` can be set to true to opt into the special +// behavior of event handlers for the "error" event in a global scope. +function defineEventHandler( + emitter, + name, + init = undefined, + isSpecialErrorEventHandler = false, +) { + // HTML specification section 8.1.7.1 + ObjectDefineProperty(emitter, `on${name}`, { + get() { + if (!this[_eventHandlers]) { + return null; + } - let reportExceptionStackedCalls = 0; - - // https://html.spec.whatwg.org/#report-the-exception - function reportException(error) { - reportExceptionStackedCalls++; - const jsError = core.destructureError(error); - const message = jsError.exceptionMessage; - let filename = ""; - let lineno = 0; - let colno = 0; - if (jsError.frames.length > 0) { - filename = jsError.frames[0].fileName; - lineno = jsError.frames[0].lineNumber; - colno = jsError.frames[0].columnNumber; - } else { - const jsError = core.destructureError(new Error()); - const frames = jsError.frames; - for (let i = 0; i < frames.length; ++i) { - const frame = frames[i]; - if ( - typeof frame.fileName == "string" && - !StringPrototypeStartsWith(frame.fileName, "internal:") - ) { - filename = frame.fileName; - lineno = frame.lineNumber; - colno = frame.columnNumber; - break; - } + return MapPrototypeGet(this[_eventHandlers], name)?.handler ?? null; + }, + set(value) { + // All three Web IDL event handler types are nullable callback functions + // with the [LegacyTreatNonObjectAsNull] extended attribute, meaning + // anything other than an object is treated as null. + if (typeof value !== "object" && typeof value !== "function") { + value = null; } - } - const event = new ErrorEvent("error", { - cancelable: true, - message, - filename, - lineno, - colno, - error, - }); - // Avoid recursing `reportException()` via error handlers more than once. - if (reportExceptionStackedCalls > 1 || window.dispatchEvent(event)) { - ops.op_dispatch_exception(error); - } - reportExceptionStackedCalls--; - } - function checkThis(thisArg) { - if (thisArg !== null && thisArg !== undefined && thisArg !== globalThis) { - throw new TypeError("Illegal invocation"); + if (!this[_eventHandlers]) { + this[_eventHandlers] = new Map(); + } + let handlerWrapper = MapPrototypeGet(this[_eventHandlers], name); + if (handlerWrapper) { + handlerWrapper.handler = value; + } else if (value !== null) { + handlerWrapper = makeWrappedHandler( + value, + isSpecialErrorEventHandler, + ); + this.addEventListener(name, handlerWrapper); + init?.(this); + } + MapPrototypeSet(this[_eventHandlers], name, handlerWrapper); + }, + configurable: true, + enumerable: true, + }); +} + +let reportExceptionStackedCalls = 0; + +// https://html.spec.whatwg.org/#report-the-exception +function reportException(error) { + reportExceptionStackedCalls++; + const jsError = core.destructureError(error); + const message = jsError.exceptionMessage; + let filename = ""; + let lineno = 0; + let colno = 0; + if (jsError.frames.length > 0) { + filename = jsError.frames[0].fileName; + lineno = jsError.frames[0].lineNumber; + colno = jsError.frames[0].columnNumber; + } else { + const jsError = core.destructureError(new Error()); + const frames = jsError.frames; + for (let i = 0; i < frames.length; ++i) { + const frame = frames[i]; + if ( + typeof frame.fileName == "string" && + !StringPrototypeStartsWith(frame.fileName, "internal:") + ) { + filename = frame.fileName; + lineno = frame.lineNumber; + colno = frame.columnNumber; + break; + } } } - - // https://html.spec.whatwg.org/#dom-reporterror - function reportError(error) { - checkThis(this); - const prefix = "Failed to call 'reportError'"; - webidl.requiredArguments(arguments.length, 1, { prefix }); - reportException(error); + const event = new ErrorEvent("error", { + cancelable: true, + message, + filename, + lineno, + colno, + error, + }); + // Avoid recursing `reportException()` via error handlers more than once. + if (reportExceptionStackedCalls > 1 || globalThis_.dispatchEvent(event)) { + ops.op_dispatch_exception(error); } + reportExceptionStackedCalls--; +} - window.__bootstrap.eventTarget = { - EventTarget, - setEventTargetData, - listenerCount, - }; - window.__bootstrap.event = { - reportException, - setIsTrusted, - setTarget, - defineEventHandler, - _skipInternalInit, - Event, - ErrorEvent, - CloseEvent, - MessageEvent, - CustomEvent, - ProgressEvent, - PromiseRejectionEvent, - reportError, - }; -})(this); +function checkThis(thisArg) { + if (thisArg !== null && thisArg !== undefined && thisArg !== globalThis_) { + throw new TypeError("Illegal invocation"); + } +} + +// https://html.spec.whatwg.org/#dom-reporterror +function reportError(error) { + checkThis(this); + const prefix = "Failed to call 'reportError'"; + webidl.requiredArguments(arguments.length, 1, { prefix }); + reportException(error); +} + +export { + _skipInternalInit, + CloseEvent, + CustomEvent, + defineEventHandler, + ErrorEvent, + Event, + EventTarget, + listenerCount, + MessageEvent, + ProgressEvent, + PromiseRejectionEvent, + reportError, + reportException, + saveGlobalThisReference, + setEventTargetData, + setIsTrusted, + setTarget, +}; diff --git a/ext/web/02_structured_clone.js b/ext/web/02_structured_clone.js index 793cb1c75eb6ee..082b0a80f4a0c9 100644 --- a/ext/web/02_structured_clone.js +++ b/ext/web/02_structured_clone.js @@ -6,138 +6,135 @@ /// /// -"use strict"; +const core = globalThis.Deno.core; +import DOMException from "internal:deno_web/01_dom_exception.js"; +const primordials = globalThis.__bootstrap.primordials; +const { + ArrayBuffer, + ArrayBufferPrototype, + ArrayBufferPrototypeGetByteLength, + ArrayBufferPrototypeSlice, + ArrayBufferIsView, + DataView, + DataViewPrototypeGetBuffer, + DataViewPrototypeGetByteLength, + DataViewPrototypeGetByteOffset, + ObjectPrototypeIsPrototypeOf, + TypedArrayPrototypeGetBuffer, + TypedArrayPrototypeGetByteOffset, + TypedArrayPrototypeGetLength, + TypedArrayPrototypeGetSymbolToStringTag, + TypeErrorPrototype, + WeakMap, + WeakMapPrototypeSet, + Int8Array, + Int16Array, + Int32Array, + BigInt64Array, + Uint8Array, + Uint8ClampedArray, + Uint16Array, + Uint32Array, + BigUint64Array, + Float32Array, + Float64Array, +} = primordials; -((window) => { - const core = window.Deno.core; - const { DOMException } = window.__bootstrap.domException; - const { - ArrayBuffer, - ArrayBufferPrototype, - ArrayBufferPrototypeGetByteLength, - ArrayBufferPrototypeSlice, - ArrayBufferIsView, - DataView, - DataViewPrototypeGetBuffer, - DataViewPrototypeGetByteLength, - DataViewPrototypeGetByteOffset, - ObjectPrototypeIsPrototypeOf, - TypedArrayPrototypeGetBuffer, - TypedArrayPrototypeGetByteOffset, - TypedArrayPrototypeGetLength, - TypedArrayPrototypeGetSymbolToStringTag, - TypeErrorPrototype, - WeakMap, - WeakMapPrototypeSet, - Int8Array, - Int16Array, - Int32Array, - BigInt64Array, - Uint8Array, - Uint8ClampedArray, - Uint16Array, - Uint32Array, - BigUint64Array, - Float32Array, - Float64Array, - } = window.__bootstrap.primordials; +const objectCloneMemo = new WeakMap(); - const objectCloneMemo = new WeakMap(); - - function cloneArrayBuffer( +function cloneArrayBuffer( + srcBuffer, + srcByteOffset, + srcLength, + _cloneConstructor, +) { + // this function fudges the return type but SharedArrayBuffer is disabled for a while anyway + return ArrayBufferPrototypeSlice( srcBuffer, srcByteOffset, - srcLength, - _cloneConstructor, - ) { - // this function fudges the return type but SharedArrayBuffer is disabled for a while anyway - return ArrayBufferPrototypeSlice( - srcBuffer, - srcByteOffset, - srcByteOffset + srcLength, + srcByteOffset + srcLength, + ); +} + +// TODO(petamoriken): Resizable ArrayBuffer support in the future +/** Clone a value in a similar way to structured cloning. It is similar to a + * StructureDeserialize(StructuredSerialize(...)). */ +function structuredClone(value) { + // Performance optimization for buffers, otherwise + // `serialize/deserialize` will allocate new buffer. + if (ObjectPrototypeIsPrototypeOf(ArrayBufferPrototype, value)) { + const cloned = cloneArrayBuffer( + value, + 0, + ArrayBufferPrototypeGetByteLength(value), + ArrayBuffer, ); + WeakMapPrototypeSet(objectCloneMemo, value, cloned); + return cloned; } - // TODO(petamoriken): Resizable ArrayBuffer support in the future - /** Clone a value in a similar way to structured cloning. It is similar to a - * StructureDeserialize(StructuredSerialize(...)). */ - function structuredClone(value) { - // Performance optimization for buffers, otherwise - // `serialize/deserialize` will allocate new buffer. - if (ObjectPrototypeIsPrototypeOf(ArrayBufferPrototype, value)) { - const cloned = cloneArrayBuffer( - value, - 0, - ArrayBufferPrototypeGetByteLength(value), - ArrayBuffer, + if (ArrayBufferIsView(value)) { + const tag = TypedArrayPrototypeGetSymbolToStringTag(value); + // DataView + if (tag === undefined) { + return new DataView( + structuredClone(DataViewPrototypeGetBuffer(value)), + DataViewPrototypeGetByteOffset(value), + DataViewPrototypeGetByteLength(value), ); - WeakMapPrototypeSet(objectCloneMemo, value, cloned); - return cloned; } - - if (ArrayBufferIsView(value)) { - const tag = TypedArrayPrototypeGetSymbolToStringTag(value); - // DataView - if (tag === undefined) { - return new DataView( - structuredClone(DataViewPrototypeGetBuffer(value)), - DataViewPrototypeGetByteOffset(value), - DataViewPrototypeGetByteLength(value), - ); - } - // TypedArray - let Constructor; - switch (tag) { - case "Int8Array": - Constructor = Int8Array; - break; - case "Int16Array": - Constructor = Int16Array; - break; - case "Int32Array": - Constructor = Int32Array; - break; - case "BigInt64Array": - Constructor = BigInt64Array; - break; - case "Uint8Array": - Constructor = Uint8Array; - break; - case "Uint8ClampedArray": - Constructor = Uint8ClampedArray; - break; - case "Uint16Array": - Constructor = Uint16Array; - break; - case "Uint32Array": - Constructor = Uint32Array; - break; - case "BigUint64Array": - Constructor = BigUint64Array; - break; - case "Float32Array": - Constructor = Float32Array; - break; - case "Float64Array": - Constructor = Float64Array; - break; - } - return new Constructor( - structuredClone(TypedArrayPrototypeGetBuffer(value)), - TypedArrayPrototypeGetByteOffset(value), - TypedArrayPrototypeGetLength(value), - ); + // TypedArray + let Constructor; + switch (tag) { + case "Int8Array": + Constructor = Int8Array; + break; + case "Int16Array": + Constructor = Int16Array; + break; + case "Int32Array": + Constructor = Int32Array; + break; + case "BigInt64Array": + Constructor = BigInt64Array; + break; + case "Uint8Array": + Constructor = Uint8Array; + break; + case "Uint8ClampedArray": + Constructor = Uint8ClampedArray; + break; + case "Uint16Array": + Constructor = Uint16Array; + break; + case "Uint32Array": + Constructor = Uint32Array; + break; + case "BigUint64Array": + Constructor = BigUint64Array; + break; + case "Float32Array": + Constructor = Float32Array; + break; + case "Float64Array": + Constructor = Float64Array; + break; } + return new Constructor( + structuredClone(TypedArrayPrototypeGetBuffer(value)), + TypedArrayPrototypeGetByteOffset(value), + TypedArrayPrototypeGetLength(value), + ); + } - try { - return core.deserialize(core.serialize(value)); - } catch (e) { - if (ObjectPrototypeIsPrototypeOf(TypeErrorPrototype, e)) { - throw new DOMException(e.message, "DataCloneError"); - } - throw e; + try { + return core.deserialize(core.serialize(value)); + } catch (e) { + if (ObjectPrototypeIsPrototypeOf(TypeErrorPrototype, e)) { + throw new DOMException(e.message, "DataCloneError"); } + throw e; } +} - window.__bootstrap.structuredClone = structuredClone; -})(globalThis); +export { structuredClone }; diff --git a/ext/web/02_timers.js b/ext/web/02_timers.js index a582cf428a32a9..24ec760bd04207 100644 --- a/ext/web/02_timers.js +++ b/ext/web/02_timers.js @@ -1,375 +1,372 @@ // Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. -"use strict"; - -((window) => { - const core = window.Deno.core; - const ops = core.ops; - const { - ArrayPrototypePush, - ArrayPrototypeShift, - FunctionPrototypeCall, - Map, - MapPrototypeDelete, - MapPrototypeGet, - MapPrototypeHas, - MapPrototypeSet, - Uint8Array, - Uint32Array, - // deno-lint-ignore camelcase - NumberPOSITIVE_INFINITY, - PromisePrototypeThen, - SafeArrayIterator, - SymbolFor, - TypeError, - indirectEval, - } = window.__bootstrap.primordials; - const { webidl } = window.__bootstrap; - const { reportException } = window.__bootstrap.event; - const { assert } = window.__bootstrap.infra; - - const hrU8 = new Uint8Array(8); - const hr = new Uint32Array(hrU8.buffer); - function opNow() { - ops.op_now(hrU8); - return (hr[0] * 1000 + hr[1] / 1e6); - } - // --------------------------------------------------------------------------- - - /** - * The task queue corresponding to the timer task source. - * - * @type { {action: () => void, nestingLevel: number}[] } - */ - const timerTasks = []; - - /** - * The current task's timer nesting level, or zero if we're not currently - * running a timer task (since the minimum nesting level is 1). - * - * @type {number} - */ - let timerNestingLevel = 0; - - function handleTimerMacrotask() { - if (timerTasks.length === 0) { - return true; - } - - const task = ArrayPrototypeShift(timerTasks); - - timerNestingLevel = task.nestingLevel; - - try { - task.action(); - } finally { - timerNestingLevel = 0; - } - return timerTasks.length === 0; +const core = globalThis.Deno.core; +const ops = core.ops; +const primordials = globalThis.__bootstrap.primordials; +const { + ArrayPrototypePush, + ArrayPrototypeShift, + FunctionPrototypeCall, + Map, + MapPrototypeDelete, + MapPrototypeGet, + MapPrototypeHas, + MapPrototypeSet, + Uint8Array, + Uint32Array, + // deno-lint-ignore camelcase + NumberPOSITIVE_INFINITY, + PromisePrototypeThen, + SafeArrayIterator, + SymbolFor, + TypeError, + indirectEval, +} = primordials; +import * as webidl from "internal:deno_webidl/00_webidl.js"; +import { reportException } from "internal:deno_web/02_event.js"; +import { assert } from "internal:deno_web/00_infra.js"; + +const hrU8 = new Uint8Array(8); +const hr = new Uint32Array(hrU8.buffer); +function opNow() { + ops.op_now(hrU8); + return (hr[0] * 1000 + hr[1] / 1e6); +} + +// --------------------------------------------------------------------------- + +/** + * The task queue corresponding to the timer task source. + * + * @type { {action: () => void, nestingLevel: number}[] } + */ +const timerTasks = []; + +/** + * The current task's timer nesting level, or zero if we're not currently + * running a timer task (since the minimum nesting level is 1). + * + * @type {number} + */ +let timerNestingLevel = 0; + +function handleTimerMacrotask() { + if (timerTasks.length === 0) { + return true; } - // --------------------------------------------------------------------------- - - /** - * The keys in this map correspond to the key ID's in the spec's map of active - * timers. The values are the timeout's cancel rid. - * - * @type {Map} - */ - const activeTimers = new Map(); - - let nextId = 1; - - /** - * @param {Function | string} callback - * @param {number} timeout - * @param {Array} args - * @param {boolean} repeat - * @param {number | undefined} prevId - * @returns {number} The timer ID - */ - function initializeTimer( - callback, - timeout, - args, - repeat, - prevId, - ) { - // 2. If previousId was given, let id be previousId; otherwise, let - // previousId be an implementation-defined integer than is greater than zero - // and does not already exist in global's map of active timers. - let id; - let timerInfo; - if (prevId !== undefined) { - // `prevId` is only passed for follow-up calls on intervals - assert(repeat); - id = prevId; - timerInfo = MapPrototypeGet(activeTimers, id); - } else { - // TODO(@andreubotella): Deal with overflow. - // https://github.com/whatwg/html/issues/7358 - id = nextId++; - const cancelRid = ops.op_timer_handle(); - timerInfo = { cancelRid, isRef: true, promiseId: -1 }; - - // Step 4 in "run steps after a timeout". - MapPrototypeSet(activeTimers, id, timerInfo); - } - - // 3. If the surrounding agent's event loop's currently running task is a - // task that was created by this algorithm, then let nesting level be the - // task's timer nesting level. Otherwise, let nesting level be zero. - // 4. If timeout is less than 0, then set timeout to 0. - // 5. If nesting level is greater than 5, and timeout is less than 4, then - // set timeout to 4. - // - // The nesting level of 5 and minimum of 4 ms are spec-mandated magic - // constants. - if (timeout < 0) timeout = 0; - if (timerNestingLevel > 5 && timeout < 4) timeout = 4; - - // 9. Let task be a task that runs the following steps: - const task = { - action: () => { - // 1. If id does not exist in global's map of active timers, then abort - // these steps. - // - // This is relevant if the timer has been canceled after the sleep op - // resolves but before this task runs. - if (!MapPrototypeHas(activeTimers, id)) { - return; - } + const task = ArrayPrototypeShift(timerTasks); - // 2. - // 3. - if (typeof callback === "function") { - try { - FunctionPrototypeCall( - callback, - globalThis, - ...new SafeArrayIterator(args), - ); - } catch (error) { - reportException(error); - } - } else { - indirectEval(callback); - } + timerNestingLevel = task.nestingLevel; - if (repeat) { - if (MapPrototypeHas(activeTimers, id)) { - // 4. If id does not exist in global's map of active timers, then - // abort these steps. - // NOTE: If might have been removed via the author code in handler - // calling clearTimeout() or clearInterval(). - // 5. If repeat is true, then perform the timer initialization steps - // again, given global, handler, timeout, arguments, true, and id. - initializeTimer(callback, timeout, args, true, id); - } - } else { - // 6. Otherwise, remove global's map of active timers[id]. - core.tryClose(timerInfo.cancelRid); - MapPrototypeDelete(activeTimers, id); - } - }, - - // 10. Increment nesting level by one. - // 11. Set task's timer nesting level to nesting level. - nestingLevel: timerNestingLevel + 1, - }; - - // 12. Let completionStep be an algorithm step which queues a global task on - // the timer task source given global to run task. - // 13. Run steps after a timeout given global, "setTimeout/setInterval", - // timeout, completionStep, and id. - runAfterTimeout( - () => ArrayPrototypePush(timerTasks, task), - timeout, - timerInfo, - ); - - return id; + try { + task.action(); + } finally { + timerNestingLevel = 0; + } + return timerTasks.length === 0; +} + +// --------------------------------------------------------------------------- + +/** + * The keys in this map correspond to the key ID's in the spec's map of active + * timers. The values are the timeout's cancel rid. + * + * @type {Map} + */ +const activeTimers = new Map(); + +let nextId = 1; + +/** + * @param {Function | string} callback + * @param {number} timeout + * @param {Array} args + * @param {boolean} repeat + * @param {number | undefined} prevId + * @returns {number} The timer ID + */ +function initializeTimer( + callback, + timeout, + args, + repeat, + prevId, +) { + // 2. If previousId was given, let id be previousId; otherwise, let + // previousId be an implementation-defined integer than is greater than zero + // and does not already exist in global's map of active timers. + let id; + let timerInfo; + if (prevId !== undefined) { + // `prevId` is only passed for follow-up calls on intervals + assert(repeat); + id = prevId; + timerInfo = MapPrototypeGet(activeTimers, id); + } else { + // TODO(@andreubotella): Deal with overflow. + // https://github.com/whatwg/html/issues/7358 + id = nextId++; + const cancelRid = ops.op_timer_handle(); + timerInfo = { cancelRid, isRef: true, promiseId: -1 }; + + // Step 4 in "run steps after a timeout". + MapPrototypeSet(activeTimers, id, timerInfo); } - // --------------------------------------------------------------------------- - - /** - * @typedef ScheduledTimer - * @property {number} millis - * @property {() => void} cb - * @property {boolean} resolved - * @property {ScheduledTimer | null} prev - * @property {ScheduledTimer | null} next - */ - - /** - * A doubly linked list of timers. - * @type { { head: ScheduledTimer | null, tail: ScheduledTimer | null } } - */ - const scheduledTimers = { head: null, tail: null }; - - /** - * @param {() => void} cb Will be run after the timeout, if it hasn't been - * cancelled. - * @param {number} millis - * @param {{ cancelRid: number, isRef: boolean, promiseId: number }} timerInfo - */ - function runAfterTimeout(cb, millis, timerInfo) { - const cancelRid = timerInfo.cancelRid; - const sleepPromise = core.opAsync("op_sleep", millis, cancelRid); - timerInfo.promiseId = - sleepPromise[SymbolFor("Deno.core.internalPromiseId")]; - if (!timerInfo.isRef) { - core.unrefOp(timerInfo.promiseId); - } - - /** @type {ScheduledTimer} */ - const timerObject = { - millis, - cb, - resolved: false, - prev: scheduledTimers.tail, - next: null, - }; - - // Add timerObject to the end of the list. - if (scheduledTimers.tail === null) { - assert(scheduledTimers.head === null); - scheduledTimers.head = scheduledTimers.tail = timerObject; - } else { - scheduledTimers.tail.next = timerObject; - scheduledTimers.tail = timerObject; - } - - // 1. - PromisePrototypeThen( - sleepPromise, - (cancelled) => { - if (!cancelled) { - // The timer was cancelled. - removeFromScheduledTimers(timerObject); - return; + // 3. If the surrounding agent's event loop's currently running task is a + // task that was created by this algorithm, then let nesting level be the + // task's timer nesting level. Otherwise, let nesting level be zero. + // 4. If timeout is less than 0, then set timeout to 0. + // 5. If nesting level is greater than 5, and timeout is less than 4, then + // set timeout to 4. + // + // The nesting level of 5 and minimum of 4 ms are spec-mandated magic + // constants. + if (timeout < 0) timeout = 0; + if (timerNestingLevel > 5 && timeout < 4) timeout = 4; + + // 9. Let task be a task that runs the following steps: + const task = { + action: () => { + // 1. If id does not exist in global's map of active timers, then abort + // these steps. + // + // This is relevant if the timer has been canceled after the sleep op + // resolves but before this task runs. + if (!MapPrototypeHas(activeTimers, id)) { + return; + } + + // 2. + // 3. + if (typeof callback === "function") { + try { + FunctionPrototypeCall( + callback, + globalThis, + ...new SafeArrayIterator(args), + ); + } catch (error) { + reportException(error); } - // 2. Wait until any invocations of this algorithm that had the same - // global and orderingIdentifier, that started before this one, and - // whose milliseconds is equal to or less than this one's, have - // completed. - // 4. Perform completionSteps. - - // IMPORTANT: Since the sleep ops aren't guaranteed to resolve in the - // right order, whenever one resolves, we run through the scheduled - // timers list (which is in the order in which they were scheduled), and - // we call the callback for every timer which both: - // a) has resolved, and - // b) its timeout is lower than the lowest unresolved timeout found so - // far in the list. - - timerObject.resolved = true; - - let lowestUnresolvedTimeout = NumberPOSITIVE_INFINITY; - - let currentEntry = scheduledTimers.head; - while (currentEntry !== null) { - if (currentEntry.millis < lowestUnresolvedTimeout) { - if (currentEntry.resolved) { - currentEntry.cb(); - removeFromScheduledTimers(currentEntry); - } else { - lowestUnresolvedTimeout = currentEntry.millis; - } - } - - currentEntry = currentEntry.next; + } else { + indirectEval(callback); + } + + if (repeat) { + if (MapPrototypeHas(activeTimers, id)) { + // 4. If id does not exist in global's map of active timers, then + // abort these steps. + // NOTE: If might have been removed via the author code in handler + // calling clearTimeout() or clearInterval(). + // 5. If repeat is true, then perform the timer initialization steps + // again, given global, handler, timeout, arguments, true, and id. + initializeTimer(callback, timeout, args, true, id); } - }, - ); - } + } else { + // 6. Otherwise, remove global's map of active timers[id]. + core.tryClose(timerInfo.cancelRid); + MapPrototypeDelete(activeTimers, id); + } + }, + + // 10. Increment nesting level by one. + // 11. Set task's timer nesting level to nesting level. + nestingLevel: timerNestingLevel + 1, + }; - /** @param {ScheduledTimer} timerObj */ - function removeFromScheduledTimers(timerObj) { - if (timerObj.prev !== null) { - timerObj.prev.next = timerObj.next; - } else { - assert(scheduledTimers.head === timerObj); - scheduledTimers.head = timerObj.next; - } - if (timerObj.next !== null) { - timerObj.next.prev = timerObj.prev; - } else { - assert(scheduledTimers.tail === timerObj); - scheduledTimers.tail = timerObj.prev; - } + // 12. Let completionStep be an algorithm step which queues a global task on + // the timer task source given global to run task. + // 13. Run steps after a timeout given global, "setTimeout/setInterval", + // timeout, completionStep, and id. + runAfterTimeout( + () => ArrayPrototypePush(timerTasks, task), + timeout, + timerInfo, + ); + + return id; +} + +// --------------------------------------------------------------------------- + +/** + * @typedef ScheduledTimer + * @property {number} millis + * @property {() => void} cb + * @property {boolean} resolved + * @property {ScheduledTimer | null} prev + * @property {ScheduledTimer | null} next + */ + +/** + * A doubly linked list of timers. + * @type { { head: ScheduledTimer | null, tail: ScheduledTimer | null } } + */ +const scheduledTimers = { head: null, tail: null }; + +/** + * @param {() => void} cb Will be run after the timeout, if it hasn't been + * cancelled. + * @param {number} millis + * @param {{ cancelRid: number, isRef: boolean, promiseId: number }} timerInfo + */ +function runAfterTimeout(cb, millis, timerInfo) { + const cancelRid = timerInfo.cancelRid; + const sleepPromise = core.opAsync("op_sleep", millis, cancelRid); + timerInfo.promiseId = sleepPromise[SymbolFor("Deno.core.internalPromiseId")]; + if (!timerInfo.isRef) { + core.unrefOp(timerInfo.promiseId); } - // --------------------------------------------------------------------------- + /** @type {ScheduledTimer} */ + const timerObject = { + millis, + cb, + resolved: false, + prev: scheduledTimers.tail, + next: null, + }; - function checkThis(thisArg) { - if (thisArg !== null && thisArg !== undefined && thisArg !== globalThis) { - throw new TypeError("Illegal invocation"); - } + // Add timerObject to the end of the list. + if (scheduledTimers.tail === null) { + assert(scheduledTimers.head === null); + scheduledTimers.head = scheduledTimers.tail = timerObject; + } else { + scheduledTimers.tail.next = timerObject; + scheduledTimers.tail = timerObject; } - function setTimeout(callback, timeout = 0, ...args) { - checkThis(this); - if (typeof callback !== "function") { - callback = webidl.converters.DOMString(callback); - } - timeout = webidl.converters.long(timeout); + // 1. + PromisePrototypeThen( + sleepPromise, + (cancelled) => { + if (!cancelled) { + // The timer was cancelled. + removeFromScheduledTimers(timerObject); + return; + } + // 2. Wait until any invocations of this algorithm that had the same + // global and orderingIdentifier, that started before this one, and + // whose milliseconds is equal to or less than this one's, have + // completed. + // 4. Perform completionSteps. + + // IMPORTANT: Since the sleep ops aren't guaranteed to resolve in the + // right order, whenever one resolves, we run through the scheduled + // timers list (which is in the order in which they were scheduled), and + // we call the callback for every timer which both: + // a) has resolved, and + // b) its timeout is lower than the lowest unresolved timeout found so + // far in the list. + + timerObject.resolved = true; + + let lowestUnresolvedTimeout = NumberPOSITIVE_INFINITY; + + let currentEntry = scheduledTimers.head; + while (currentEntry !== null) { + if (currentEntry.millis < lowestUnresolvedTimeout) { + if (currentEntry.resolved) { + currentEntry.cb(); + removeFromScheduledTimers(currentEntry); + } else { + lowestUnresolvedTimeout = currentEntry.millis; + } + } - return initializeTimer(callback, timeout, args, false); + currentEntry = currentEntry.next; + } + }, + ); +} + +/** @param {ScheduledTimer} timerObj */ +function removeFromScheduledTimers(timerObj) { + if (timerObj.prev !== null) { + timerObj.prev.next = timerObj.next; + } else { + assert(scheduledTimers.head === timerObj); + scheduledTimers.head = timerObj.next; + } + if (timerObj.next !== null) { + timerObj.next.prev = timerObj.prev; + } else { + assert(scheduledTimers.tail === timerObj); + scheduledTimers.tail = timerObj.prev; } +} - function setInterval(callback, timeout = 0, ...args) { - checkThis(this); - if (typeof callback !== "function") { - callback = webidl.converters.DOMString(callback); - } - timeout = webidl.converters.long(timeout); +// --------------------------------------------------------------------------- - return initializeTimer(callback, timeout, args, true); +function checkThis(thisArg) { + if (thisArg !== null && thisArg !== undefined && thisArg !== globalThis) { + throw new TypeError("Illegal invocation"); } +} - function clearTimeout(id = 0) { - checkThis(this); - id = webidl.converters.long(id); - const timerInfo = MapPrototypeGet(activeTimers, id); - if (timerInfo !== undefined) { - core.tryClose(timerInfo.cancelRid); - MapPrototypeDelete(activeTimers, id); - } +function setTimeout(callback, timeout = 0, ...args) { + checkThis(this); + if (typeof callback !== "function") { + callback = webidl.converters.DOMString(callback); } + timeout = webidl.converters.long(timeout); - function clearInterval(id = 0) { - checkThis(this); - clearTimeout(id); - } + return initializeTimer(callback, timeout, args, false); +} - function refTimer(id) { - const timerInfo = MapPrototypeGet(activeTimers, id); - if (timerInfo === undefined || timerInfo.isRef) { - return; - } - timerInfo.isRef = true; - core.refOp(timerInfo.promiseId); +function setInterval(callback, timeout = 0, ...args) { + checkThis(this); + if (typeof callback !== "function") { + callback = webidl.converters.DOMString(callback); } - - function unrefTimer(id) { - const timerInfo = MapPrototypeGet(activeTimers, id); - if (timerInfo === undefined || !timerInfo.isRef) { - return; - } - timerInfo.isRef = false; - core.unrefOp(timerInfo.promiseId); + timeout = webidl.converters.long(timeout); + + return initializeTimer(callback, timeout, args, true); +} + +function clearTimeout(id = 0) { + checkThis(this); + id = webidl.converters.long(id); + const timerInfo = MapPrototypeGet(activeTimers, id); + if (timerInfo !== undefined) { + core.tryClose(timerInfo.cancelRid); + MapPrototypeDelete(activeTimers, id); } +} - window.__bootstrap.timers = { - setTimeout, - setInterval, - clearTimeout, - clearInterval, - handleTimerMacrotask, - opNow, - refTimer, - unrefTimer, - }; -})(this); +function clearInterval(id = 0) { + checkThis(this); + clearTimeout(id); +} + +function refTimer(id) { + const timerInfo = MapPrototypeGet(activeTimers, id); + if (timerInfo === undefined || timerInfo.isRef) { + return; + } + timerInfo.isRef = true; + core.refOp(timerInfo.promiseId); +} + +function unrefTimer(id) { + const timerInfo = MapPrototypeGet(activeTimers, id); + if (timerInfo === undefined || !timerInfo.isRef) { + return; + } + timerInfo.isRef = false; + core.unrefOp(timerInfo.promiseId); +} + +export { + clearInterval, + clearTimeout, + handleTimerMacrotask, + opNow, + refTimer, + setInterval, + setTimeout, + unrefTimer, +}; diff --git a/ext/web/03_abort_signal.js b/ext/web/03_abort_signal.js index cce1bac7e7ca4a..1842217bb43238 100644 --- a/ext/web/03_abort_signal.js +++ b/ext/web/03_abort_signal.js @@ -1,200 +1,205 @@ // Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. -"use strict"; // @ts-check /// -((window) => { - const webidl = window.__bootstrap.webidl; - const { Event, setIsTrusted, defineEventHandler } = window.__bootstrap.event; - const { EventTarget, listenerCount } = window.__bootstrap.eventTarget; - const { - SafeArrayIterator, - SafeSetIterator, - Set, - SetPrototypeAdd, - SetPrototypeDelete, - Symbol, - TypeError, - } = window.__bootstrap.primordials; - const { setTimeout, refTimer, unrefTimer } = window.__bootstrap.timers; - - const add = Symbol("[[add]]"); - const signalAbort = Symbol("[[signalAbort]]"); - const remove = Symbol("[[remove]]"); - const abortReason = Symbol("[[abortReason]]"); - const abortAlgos = Symbol("[[abortAlgos]]"); - const signal = Symbol("[[signal]]"); - const timerId = Symbol("[[timerId]]"); - - const illegalConstructorKey = Symbol("illegalConstructorKey"); - - class AbortSignal extends EventTarget { - static abort(reason = undefined) { - if (reason !== undefined) { - reason = webidl.converters.any(reason); - } - const signal = new AbortSignal(illegalConstructorKey); - signal[signalAbort](reason); - return signal; - } +import * as webidl from "internal:deno_webidl/00_webidl.js"; +import { + defineEventHandler, + Event, + EventTarget, + listenerCount, + setIsTrusted, +} from "internal:deno_web/02_event.js"; +const primordials = globalThis.__bootstrap.primordials; +const { + SafeArrayIterator, + SafeSetIterator, + Set, + SetPrototypeAdd, + SetPrototypeDelete, + Symbol, + TypeError, +} = primordials; +import { + refTimer, + setTimeout, + unrefTimer, +} from "internal:deno_web/02_timers.js"; + +const add = Symbol("[[add]]"); +const signalAbort = Symbol("[[signalAbort]]"); +const remove = Symbol("[[remove]]"); +const abortReason = Symbol("[[abortReason]]"); +const abortAlgos = Symbol("[[abortAlgos]]"); +const signal = Symbol("[[signal]]"); +const timerId = Symbol("[[timerId]]"); + +const illegalConstructorKey = Symbol("illegalConstructorKey"); + +class AbortSignal extends EventTarget { + static abort(reason = undefined) { + if (reason !== undefined) { + reason = webidl.converters.any(reason); + } + const signal = new AbortSignal(illegalConstructorKey); + signal[signalAbort](reason); + return signal; + } - static timeout(millis) { - const prefix = "Failed to call 'AbortSignal.timeout'"; - webidl.requiredArguments(arguments.length, 1, { prefix }); - millis = webidl.converters["unsigned long long"](millis, { - enforceRange: true, - }); - - const signal = new AbortSignal(illegalConstructorKey); - signal[timerId] = setTimeout( - () => { - signal[timerId] = null; - signal[signalAbort]( - new DOMException("Signal timed out.", "TimeoutError"), - ); - }, - millis, - ); - unrefTimer(signal[timerId]); - return signal; - } + static timeout(millis) { + const prefix = "Failed to call 'AbortSignal.timeout'"; + webidl.requiredArguments(arguments.length, 1, { prefix }); + millis = webidl.converters["unsigned long long"](millis, { + enforceRange: true, + }); + + const signal = new AbortSignal(illegalConstructorKey); + signal[timerId] = setTimeout( + () => { + signal[timerId] = null; + signal[signalAbort]( + new DOMException("Signal timed out.", "TimeoutError"), + ); + }, + millis, + ); + unrefTimer(signal[timerId]); + return signal; + } - [add](algorithm) { - if (this.aborted) { - return; - } - if (this[abortAlgos] === null) { - this[abortAlgos] = new Set(); - } - SetPrototypeAdd(this[abortAlgos], algorithm); + [add](algorithm) { + if (this.aborted) { + return; } - - [signalAbort]( - reason = new DOMException("The signal has been aborted", "AbortError"), - ) { - if (this.aborted) { - return; - } - this[abortReason] = reason; - if (this[abortAlgos] !== null) { - for (const algorithm of new SafeSetIterator(this[abortAlgos])) { - algorithm(); - } - this[abortAlgos] = null; - } - const event = new Event("abort"); - setIsTrusted(event, true); - this.dispatchEvent(event); + if (this[abortAlgos] === null) { + this[abortAlgos] = new Set(); } + SetPrototypeAdd(this[abortAlgos], algorithm); + } - [remove](algorithm) { - this[abortAlgos] && SetPrototypeDelete(this[abortAlgos], algorithm); + [signalAbort]( + reason = new DOMException("The signal has been aborted", "AbortError"), + ) { + if (this.aborted) { + return; } - - constructor(key = null) { - if (key != illegalConstructorKey) { - throw new TypeError("Illegal constructor."); + this[abortReason] = reason; + if (this[abortAlgos] !== null) { + for (const algorithm of new SafeSetIterator(this[abortAlgos])) { + algorithm(); } - super(); - this[abortReason] = undefined; this[abortAlgos] = null; - this[timerId] = null; - this[webidl.brand] = webidl.brand; } + const event = new Event("abort"); + setIsTrusted(event, true); + this.dispatchEvent(event); + } - get aborted() { - webidl.assertBranded(this, AbortSignalPrototype); - return this[abortReason] !== undefined; - } + [remove](algorithm) { + this[abortAlgos] && SetPrototypeDelete(this[abortAlgos], algorithm); + } - get reason() { - webidl.assertBranded(this, AbortSignalPrototype); - return this[abortReason]; + constructor(key = null) { + if (key != illegalConstructorKey) { + throw new TypeError("Illegal constructor."); } + super(); + this[abortReason] = undefined; + this[abortAlgos] = null; + this[timerId] = null; + this[webidl.brand] = webidl.brand; + } - throwIfAborted() { - webidl.assertBranded(this, AbortSignalPrototype); - if (this[abortReason] !== undefined) { - throw this[abortReason]; - } + get aborted() { + webidl.assertBranded(this, AbortSignalPrototype); + return this[abortReason] !== undefined; + } + + get reason() { + webidl.assertBranded(this, AbortSignalPrototype); + return this[abortReason]; + } + + throwIfAborted() { + webidl.assertBranded(this, AbortSignalPrototype); + if (this[abortReason] !== undefined) { + throw this[abortReason]; } + } - // `addEventListener` and `removeEventListener` have to be overriden in - // order to have the timer block the event loop while there are listeners. - // `[add]` and `[remove]` don't ref and unref the timer because they can - // only be used by Deno internals, which use it to essentially cancel async - // ops which would block the event loop. - addEventListener(...args) { - super.addEventListener(...new SafeArrayIterator(args)); - if (this[timerId] !== null && listenerCount(this, "abort") > 0) { - refTimer(this[timerId]); - } + // `addEventListener` and `removeEventListener` have to be overriden in + // order to have the timer block the event loop while there are listeners. + // `[add]` and `[remove]` don't ref and unref the timer because they can + // only be used by Deno internals, which use it to essentially cancel async + // ops which would block the event loop. + addEventListener(...args) { + super.addEventListener(...new SafeArrayIterator(args)); + if (this[timerId] !== null && listenerCount(this, "abort") > 0) { + refTimer(this[timerId]); } + } - removeEventListener(...args) { - super.removeEventListener(...new SafeArrayIterator(args)); - if (this[timerId] !== null && listenerCount(this, "abort") === 0) { - unrefTimer(this[timerId]); - } + removeEventListener(...args) { + super.removeEventListener(...new SafeArrayIterator(args)); + if (this[timerId] !== null && listenerCount(this, "abort") === 0) { + unrefTimer(this[timerId]); } } - defineEventHandler(AbortSignal.prototype, "abort"); +} +defineEventHandler(AbortSignal.prototype, "abort"); - webidl.configurePrototype(AbortSignal); - const AbortSignalPrototype = AbortSignal.prototype; +webidl.configurePrototype(AbortSignal); +const AbortSignalPrototype = AbortSignal.prototype; - class AbortController { - [signal] = new AbortSignal(illegalConstructorKey); +class AbortController { + [signal] = new AbortSignal(illegalConstructorKey); - constructor() { - this[webidl.brand] = webidl.brand; - } + constructor() { + this[webidl.brand] = webidl.brand; + } - get signal() { - webidl.assertBranded(this, AbortControllerPrototype); - return this[signal]; - } + get signal() { + webidl.assertBranded(this, AbortControllerPrototype); + return this[signal]; + } - abort(reason) { - webidl.assertBranded(this, AbortControllerPrototype); - this[signal][signalAbort](reason); - } + abort(reason) { + webidl.assertBranded(this, AbortControllerPrototype); + this[signal][signalAbort](reason); } +} - webidl.configurePrototype(AbortController); - const AbortControllerPrototype = AbortController.prototype; +webidl.configurePrototype(AbortController); +const AbortControllerPrototype = AbortController.prototype; - webidl.converters["AbortSignal"] = webidl.createInterfaceConverter( - "AbortSignal", - AbortSignal.prototype, - ); +webidl.converters["AbortSignal"] = webidl.createInterfaceConverter( + "AbortSignal", + AbortSignal.prototype, +); - function newSignal() { - return new AbortSignal(illegalConstructorKey); - } +function newSignal() { + return new AbortSignal(illegalConstructorKey); +} - function follow(followingSignal, parentSignal) { - if (followingSignal.aborted) { - return; - } - if (parentSignal.aborted) { - followingSignal[signalAbort](parentSignal.reason); - } else { - parentSignal[add](() => - followingSignal[signalAbort](parentSignal.reason) - ); - } +function follow(followingSignal, parentSignal) { + if (followingSignal.aborted) { + return; } - - window.__bootstrap.abortSignal = { - AbortSignal, - AbortController, - AbortSignalPrototype, - add, - signalAbort, - remove, - follow, - newSignal, - }; -})(this); + if (parentSignal.aborted) { + followingSignal[signalAbort](parentSignal.reason); + } else { + parentSignal[add](() => followingSignal[signalAbort](parentSignal.reason)); + } +} + +export { + AbortController, + AbortSignal, + AbortSignalPrototype, + add, + follow, + newSignal, + remove, + signalAbort, +}; diff --git a/ext/web/04_global_interfaces.js b/ext/web/04_global_interfaces.js index 840f93ba9c2120..edac0d5b77dbdd 100644 --- a/ext/web/04_global_interfaces.js +++ b/ext/web/04_global_interfaces.js @@ -1,79 +1,83 @@ // Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. -"use strict"; // @ts-check /// -((window) => { - const { EventTarget } = window.__bootstrap.eventTarget; - const { - Symbol, - SymbolToStringTag, - TypeError, - } = window.__bootstrap.primordials; +import { EventTarget } from "internal:deno_web/02_event.js"; +const primordials = globalThis.__bootstrap.primordials; +const { + Symbol, + SymbolToStringTag, + TypeError, +} = primordials; - const illegalConstructorKey = Symbol("illegalConstructorKey"); +const illegalConstructorKey = Symbol("illegalConstructorKey"); - class Window extends EventTarget { - constructor(key = null) { - if (key !== illegalConstructorKey) { - throw new TypeError("Illegal constructor."); - } - super(); +class Window extends EventTarget { + constructor(key = null) { + if (key !== illegalConstructorKey) { + throw new TypeError("Illegal constructor."); } + super(); + } - get [SymbolToStringTag]() { - return "Window"; - } + get [SymbolToStringTag]() { + return "Window"; } +} - class WorkerGlobalScope extends EventTarget { - constructor(key = null) { - if (key != illegalConstructorKey) { - throw new TypeError("Illegal constructor."); - } - super(); +class WorkerGlobalScope extends EventTarget { + constructor(key = null) { + if (key != illegalConstructorKey) { + throw new TypeError("Illegal constructor."); } + super(); + } - get [SymbolToStringTag]() { - return "WorkerGlobalScope"; - } + get [SymbolToStringTag]() { + return "WorkerGlobalScope"; } +} - class DedicatedWorkerGlobalScope extends WorkerGlobalScope { - constructor(key = null) { - if (key != illegalConstructorKey) { - throw new TypeError("Illegal constructor."); - } - super(); +class DedicatedWorkerGlobalScope extends WorkerGlobalScope { + constructor(key = null) { + if (key != illegalConstructorKey) { + throw new TypeError("Illegal constructor."); } + super(); + } - get [SymbolToStringTag]() { - return "DedicatedWorkerGlobalScope"; - } + get [SymbolToStringTag]() { + return "DedicatedWorkerGlobalScope"; } +} + +const dedicatedWorkerGlobalScopeConstructorDescriptor = { + configurable: true, + enumerable: false, + value: DedicatedWorkerGlobalScope, + writable: true, +}; + +const windowConstructorDescriptor = { + configurable: true, + enumerable: false, + value: Window, + writable: true, +}; + +const workerGlobalScopeConstructorDescriptor = { + configurable: true, + enumerable: false, + value: WorkerGlobalScope, + writable: true, +}; - window.__bootstrap.globalInterfaces = { - DedicatedWorkerGlobalScope, - Window, - WorkerGlobalScope, - dedicatedWorkerGlobalScopeConstructorDescriptor: { - configurable: true, - enumerable: false, - value: DedicatedWorkerGlobalScope, - writable: true, - }, - windowConstructorDescriptor: { - configurable: true, - enumerable: false, - value: Window, - writable: true, - }, - workerGlobalScopeConstructorDescriptor: { - configurable: true, - enumerable: false, - value: WorkerGlobalScope, - writable: true, - }, - }; -})(this); +export { + DedicatedWorkerGlobalScope, + dedicatedWorkerGlobalScopeConstructorDescriptor, + Window, + windowConstructorDescriptor, + WorkerGlobalScope, + workerGlobalScopeConstructorDescriptor, +}; diff --git a/ext/web/05_base64.js b/ext/web/05_base64.js index dac366ca005b54..df64eab0d7a044 100644 --- a/ext/web/05_base64.js +++ b/ext/web/05_base64.js @@ -6,68 +6,62 @@ /// /// -"use strict"; +const core = globalThis.Deno.core; +const ops = core.ops; +import * as webidl from "internal:deno_webidl/00_webidl.js"; +import DOMException from "internal:deno_web/01_dom_exception.js"; +const primordials = globalThis.__bootstrap.primordials; +const { + ObjectPrototypeIsPrototypeOf, + TypeErrorPrototype, +} = primordials; -((window) => { - const core = Deno.core; - const ops = core.ops; - const webidl = window.__bootstrap.webidl; - const { DOMException } = window.__bootstrap.domException; - const { - ObjectPrototypeIsPrototypeOf, - TypeErrorPrototype, - } = window.__bootstrap.primordials; - - /** - * @param {string} data - * @returns {string} - */ - function atob(data) { - const prefix = "Failed to execute 'atob'"; - webidl.requiredArguments(arguments.length, 1, { prefix }); - data = webidl.converters.DOMString(data, { - prefix, - context: "Argument 1", - }); - try { - return ops.op_base64_atob(data); - } catch (e) { - if (ObjectPrototypeIsPrototypeOf(TypeErrorPrototype, e)) { - throw new DOMException( - "Failed to decode base64: invalid character", - "InvalidCharacterError", - ); - } - throw e; +/** + * @param {string} data + * @returns {string} + */ +function atob(data) { + const prefix = "Failed to execute 'atob'"; + webidl.requiredArguments(arguments.length, 1, { prefix }); + data = webidl.converters.DOMString(data, { + prefix, + context: "Argument 1", + }); + try { + return ops.op_base64_atob(data); + } catch (e) { + if (ObjectPrototypeIsPrototypeOf(TypeErrorPrototype, e)) { + throw new DOMException( + "Failed to decode base64: invalid character", + "InvalidCharacterError", + ); } + throw e; } +} - /** - * @param {string} data - * @returns {string} - */ - function btoa(data) { - const prefix = "Failed to execute 'btoa'"; - webidl.requiredArguments(arguments.length, 1, { prefix }); - data = webidl.converters.DOMString(data, { - prefix, - context: "Argument 1", - }); - try { - return ops.op_base64_btoa(data); - } catch (e) { - if (ObjectPrototypeIsPrototypeOf(TypeErrorPrototype, e)) { - throw new DOMException( - "The string to be encoded contains characters outside of the Latin1 range.", - "InvalidCharacterError", - ); - } - throw e; +/** + * @param {string} data + * @returns {string} + */ +function btoa(data) { + const prefix = "Failed to execute 'btoa'"; + webidl.requiredArguments(arguments.length, 1, { prefix }); + data = webidl.converters.DOMString(data, { + prefix, + context: "Argument 1", + }); + try { + return ops.op_base64_btoa(data); + } catch (e) { + if (ObjectPrototypeIsPrototypeOf(TypeErrorPrototype, e)) { + throw new DOMException( + "The string to be encoded contains characters outside of the Latin1 range.", + "InvalidCharacterError", + ); } + throw e; } +} - window.__bootstrap.base64 = { - atob, - btoa, - }; -})(globalThis); +export { atob, btoa }; diff --git a/ext/web/06_streams.js b/ext/web/06_streams.js index 1bad4f314a27db..5dcf64af24eb5a 100644 --- a/ext/web/06_streams.js +++ b/ext/web/06_streams.js @@ -5,6251 +5,6246 @@ /// /// /// -"use strict"; - -((window) => { - const core = window.Deno.core; - const webidl = window.__bootstrap.webidl; - const { add, remove, signalAbort, newSignal, AbortSignalPrototype } = - window.__bootstrap.abortSignal; - const { - ArrayBuffer, - ArrayBufferPrototype, - ArrayBufferIsView, - ArrayPrototypeMap, - ArrayPrototypePush, - ArrayPrototypeShift, - AsyncGeneratorPrototype, - BigInt64ArrayPrototype, - BigUint64ArrayPrototype, - DataView, - FinalizationRegistry, - Int8ArrayPrototype, - Int16ArrayPrototype, - Int32ArrayPrototype, - NumberIsInteger, - NumberIsNaN, - MathMin, - ObjectCreate, - ObjectDefineProperties, - ObjectDefineProperty, - ObjectGetPrototypeOf, - ObjectPrototype, - ObjectPrototypeIsPrototypeOf, - ObjectSetPrototypeOf, - Promise, - PromisePrototypeCatch, - PromisePrototypeThen, - PromiseReject, - PromiseResolve, - queueMicrotask, - RangeError, - ReflectHas, - SafePromiseAll, - // TODO(lucacasonato): add SharedArrayBuffer to primordials - // SharedArrayBufferPrototype - Symbol, - SymbolAsyncIterator, - SymbolFor, - TypeError, - TypedArrayPrototypeSet, - Uint8Array, - Uint8ArrayPrototype, - Uint16ArrayPrototype, - Uint32ArrayPrototype, - Uint8ClampedArrayPrototype, - WeakMap, - WeakMapPrototypeGet, - WeakMapPrototypeHas, - WeakMapPrototypeSet, - } = globalThis.__bootstrap.primordials; - const consoleInternal = window.__bootstrap.console; - const ops = core.ops; - const { AssertionError, assert } = window.__bootstrap.infra; - - /** @template T */ - class Deferred { - /** @type {Promise} */ - #promise; - /** @type {(reject?: any) => void} */ - #reject; - /** @type {(value: T | PromiseLike) => void} */ - #resolve; - /** @type {"pending" | "fulfilled"} */ - #state = "pending"; - - constructor() { - this.#promise = new Promise((resolve, reject) => { - this.#resolve = resolve; - this.#reject = reject; - }); - } - - /** @returns {Promise} */ - get promise() { - return this.#promise; - } - - /** @returns {"pending" | "fulfilled"} */ - get state() { - return this.#state; - } - - /** @param {any=} reason */ - reject(reason) { - // already settled promises are a no-op - if (this.#state !== "pending") { - return; - } - this.#state = "fulfilled"; - this.#reject(reason); - } - - /** @param {T | PromiseLike} value */ - resolve(value) { - // already settled promises are a no-op - if (this.#state !== "pending") { - return; - } - this.#state = "fulfilled"; - this.#resolve(value); - } - } - - /** - * @template T - * @param {T | PromiseLike} value - * @returns {Promise} - */ - function resolvePromiseWith(value) { - return new Promise((resolve) => resolve(value)); - } - - /** @param {any} e */ - function rethrowAssertionErrorRejection(e) { - if (e && ObjectPrototypeIsPrototypeOf(AssertionError.prototype, e)) { - queueMicrotask(() => { - console.error(`Internal Error: ${e.stack}`); - }); - } - } - - /** @param {Promise} promise */ - function setPromiseIsHandledToTrue(promise) { - PromisePrototypeThen(promise, undefined, rethrowAssertionErrorRejection); - } - - /** - * @template T - * @template TResult1 - * @template TResult2 - * @param {Promise} promise - * @param {(value: T) => TResult1 | PromiseLike} fulfillmentHandler - * @param {(reason: any) => TResult2 | PromiseLike=} rejectionHandler - * @returns {Promise} - */ - function transformPromiseWith(promise, fulfillmentHandler, rejectionHandler) { - return PromisePrototypeThen(promise, fulfillmentHandler, rejectionHandler); - } - - /** - * @template T - * @template TResult - * @param {Promise} promise - * @param {(value: T) => TResult | PromiseLike} onFulfilled - * @returns {void} - */ - function uponFulfillment(promise, onFulfilled) { - uponPromise(promise, onFulfilled); - } - /** - * @template T - * @template TResult - * @param {Promise} promise - * @param {(value: T) => TResult | PromiseLike} onRejected - * @returns {void} - */ - function uponRejection(promise, onRejected) { - uponPromise(promise, undefined, onRejected); +const core = globalThis.Deno.core; +const ops = core.ops; +import * as webidl from "internal:deno_webidl/00_webidl.js"; +import { + AbortSignalPrototype, + add, + newSignal, + remove, + signalAbort, +} from "internal:deno_web/03_abort_signal.js"; +const primordials = globalThis.__bootstrap.primordials; +const { + ArrayBuffer, + ArrayBufferPrototype, + ArrayBufferIsView, + ArrayPrototypeMap, + ArrayPrototypePush, + ArrayPrototypeShift, + AsyncGeneratorPrototype, + BigInt64ArrayPrototype, + BigUint64ArrayPrototype, + DataView, + FinalizationRegistry, + Int8ArrayPrototype, + Int16ArrayPrototype, + Int32ArrayPrototype, + NumberIsInteger, + NumberIsNaN, + MathMin, + ObjectCreate, + ObjectDefineProperties, + ObjectDefineProperty, + ObjectGetPrototypeOf, + ObjectPrototype, + ObjectPrototypeIsPrototypeOf, + ObjectSetPrototypeOf, + Promise, + PromisePrototypeCatch, + PromisePrototypeThen, + PromiseReject, + PromiseResolve, + queueMicrotask, + RangeError, + ReflectHas, + SafePromiseAll, + // TODO(lucacasonato): add SharedArrayBuffer to primordials + // SharedArrayBufferPrototype + Symbol, + SymbolAsyncIterator, + SymbolFor, + TypeError, + TypedArrayPrototypeSet, + Uint8Array, + Uint8ArrayPrototype, + Uint16ArrayPrototype, + Uint32ArrayPrototype, + Uint8ClampedArrayPrototype, + WeakMap, + WeakMapPrototypeGet, + WeakMapPrototypeHas, + WeakMapPrototypeSet, +} = primordials; +import { createFilteredInspectProxy } from "internal:deno_console/02_console.js"; +import { assert, AssertionError } from "internal:deno_web/00_infra.js"; + +/** @template T */ +class Deferred { + /** @type {Promise} */ + #promise; + /** @type {(reject?: any) => void} */ + #reject; + /** @type {(value: T | PromiseLike) => void} */ + #resolve; + /** @type {"pending" | "fulfilled"} */ + #state = "pending"; + + constructor() { + this.#promise = new Promise((resolve, reject) => { + this.#resolve = resolve; + this.#reject = reject; + }); } - /** - * @template T - * @template TResult1 - * @template TResult2 - * @param {Promise} promise - * @param {(value: T) => TResult1 | PromiseLike} onFulfilled - * @param {(reason: any) => TResult2 | PromiseLike=} onRejected - * @returns {void} - */ - function uponPromise(promise, onFulfilled, onRejected) { - PromisePrototypeThen( - PromisePrototypeThen(promise, onFulfilled, onRejected), - undefined, - rethrowAssertionErrorRejection, - ); + /** @returns {Promise} */ + get promise() { + return this.#promise; } - /** - * @param {ArrayBufferLike} O - * @returns {boolean} - */ - function isDetachedBuffer(O) { - return O.byteLength === 0 && ops.op_arraybuffer_was_detached(O); + /** @returns {"pending" | "fulfilled"} */ + get state() { + return this.#state; } - /** - * @param {ArrayBufferLike} O - * @returns {boolean} - */ - function canTransferArrayBuffer(O) { - assert(typeof O === "object"); - assert( - ObjectPrototypeIsPrototypeOf(ArrayBufferPrototype, O) || - // deno-lint-ignore prefer-primordials - ObjectPrototypeIsPrototypeOf(SharedArrayBuffer.prototype, O), - ); - if (isDetachedBuffer(O)) { - return false; + /** @param {any=} reason */ + reject(reason) { + // already settled promises are a no-op + if (this.#state !== "pending") { + return; } - // TODO(@crowlKats): 4. If SameValue(O.[[ArrayBufferDetachKey]], undefined) is false, return false. - return true; - } - - /** - * @param {ArrayBufferLike} O - * @returns {ArrayBufferLike} - */ - function transferArrayBuffer(O) { - return ops.op_transfer_arraybuffer(O); - } - - /** - * @param {ArrayBufferView} O - * @returns {Uint8Array} - */ - function cloneAsUint8Array(O) { - assert(typeof O === "object"); - assert(ArrayBufferIsView(O)); - assert(!isDetachedBuffer(O.buffer)); - const buffer = O.buffer.slice(O.byteOffset, O.byteOffset + O.byteLength); - return new Uint8Array(buffer); - } - - const _abortAlgorithm = Symbol("[[abortAlgorithm]]"); - const _abortSteps = Symbol("[[AbortSteps]]"); - const _autoAllocateChunkSize = Symbol("[[autoAllocateChunkSize]]"); - const _backpressure = Symbol("[[backpressure]]"); - const _backpressureChangePromise = Symbol("[[backpressureChangePromise]]"); - const _byobRequest = Symbol("[[byobRequest]]"); - const _cancelAlgorithm = Symbol("[[cancelAlgorithm]]"); - const _cancelSteps = Symbol("[[CancelSteps]]"); - const _close = Symbol("close sentinel"); - const _closeAlgorithm = Symbol("[[closeAlgorithm]]"); - const _closedPromise = Symbol("[[closedPromise]]"); - const _closeRequest = Symbol("[[closeRequest]]"); - const _closeRequested = Symbol("[[closeRequested]]"); - const _controller = Symbol("[[controller]]"); - const _detached = Symbol("[[Detached]]"); - const _disturbed = Symbol("[[disturbed]]"); - const _errorSteps = Symbol("[[ErrorSteps]]"); - const _flushAlgorithm = Symbol("[[flushAlgorithm]]"); - const _globalObject = Symbol("[[globalObject]]"); - const _highWaterMark = Symbol("[[highWaterMark]]"); - const _inFlightCloseRequest = Symbol("[[inFlightCloseRequest]]"); - const _inFlightWriteRequest = Symbol("[[inFlightWriteRequest]]"); - const _pendingAbortRequest = Symbol("[pendingAbortRequest]"); - const _pendingPullIntos = Symbol("[[pendingPullIntos]]"); - const _preventCancel = Symbol("[[preventCancel]]"); - const _pullAgain = Symbol("[[pullAgain]]"); - const _pullAlgorithm = Symbol("[[pullAlgorithm]]"); - const _pulling = Symbol("[[pulling]]"); - const _pullSteps = Symbol("[[PullSteps]]"); - const _releaseSteps = Symbol("[[ReleaseSteps]]"); - const _queue = Symbol("[[queue]]"); - const _queueTotalSize = Symbol("[[queueTotalSize]]"); - const _readable = Symbol("[[readable]]"); - const _reader = Symbol("[[reader]]"); - const _readRequests = Symbol("[[readRequests]]"); - const _readIntoRequests = Symbol("[[readIntoRequests]]"); - const _readyPromise = Symbol("[[readyPromise]]"); - const _signal = Symbol("[[signal]]"); - const _started = Symbol("[[started]]"); - const _state = Symbol("[[state]]"); - const _storedError = Symbol("[[storedError]]"); - const _strategyHWM = Symbol("[[strategyHWM]]"); - const _strategySizeAlgorithm = Symbol("[[strategySizeAlgorithm]]"); - const _stream = Symbol("[[stream]]"); - const _transformAlgorithm = Symbol("[[transformAlgorithm]]"); - const _view = Symbol("[[view]]"); - const _writable = Symbol("[[writable]]"); - const _writeAlgorithm = Symbol("[[writeAlgorithm]]"); - const _writer = Symbol("[[writer]]"); - const _writeRequests = Symbol("[[writeRequests]]"); - - /** - * @template R - * @param {ReadableStream} stream - * @returns {ReadableStreamDefaultReader} - */ - function acquireReadableStreamDefaultReader(stream) { - return new ReadableStreamDefaultReader(stream); + this.#state = "fulfilled"; + this.#reject(reason); } - /** - * @template R - * @param {ReadableStream} stream - * @returns {ReadableStreamBYOBReader} - */ - function acquireReadableStreamBYOBReader(stream) { - const reader = webidl.createBranded(ReadableStreamBYOBReader); - setUpReadableStreamBYOBReader(reader, stream); - return reader; + /** @param {T | PromiseLike} value */ + resolve(value) { + // already settled promises are a no-op + if (this.#state !== "pending") { + return; + } + this.#state = "fulfilled"; + this.#resolve(value); + } +} + +/** + * @template T + * @param {T | PromiseLike} value + * @returns {Promise} + */ +function resolvePromiseWith(value) { + return new Promise((resolve) => resolve(value)); +} + +/** @param {any} e */ +function rethrowAssertionErrorRejection(e) { + if (e && ObjectPrototypeIsPrototypeOf(AssertionError.prototype, e)) { + queueMicrotask(() => { + console.error(`Internal Error: ${e.stack}`); + }); } - - /** - * @template W - * @param {WritableStream} stream - * @returns {WritableStreamDefaultWriter} - */ - function acquireWritableStreamDefaultWriter(stream) { - return new WritableStreamDefaultWriter(stream); +} + +/** @param {Promise} promise */ +function setPromiseIsHandledToTrue(promise) { + PromisePrototypeThen(promise, undefined, rethrowAssertionErrorRejection); +} + +/** + * @template T + * @template TResult1 + * @template TResult2 + * @param {Promise} promise + * @param {(value: T) => TResult1 | PromiseLike} fulfillmentHandler + * @param {(reason: any) => TResult2 | PromiseLike=} rejectionHandler + * @returns {Promise} + */ +function transformPromiseWith(promise, fulfillmentHandler, rejectionHandler) { + return PromisePrototypeThen(promise, fulfillmentHandler, rejectionHandler); +} + +/** + * @template T + * @template TResult + * @param {Promise} promise + * @param {(value: T) => TResult | PromiseLike} onFulfilled + * @returns {void} + */ +function uponFulfillment(promise, onFulfilled) { + uponPromise(promise, onFulfilled); +} + +/** + * @template T + * @template TResult + * @param {Promise} promise + * @param {(value: T) => TResult | PromiseLike} onRejected + * @returns {void} + */ +function uponRejection(promise, onRejected) { + uponPromise(promise, undefined, onRejected); +} + +/** + * @template T + * @template TResult1 + * @template TResult2 + * @param {Promise} promise + * @param {(value: T) => TResult1 | PromiseLike} onFulfilled + * @param {(reason: any) => TResult2 | PromiseLike=} onRejected + * @returns {void} + */ +function uponPromise(promise, onFulfilled, onRejected) { + PromisePrototypeThen( + PromisePrototypeThen(promise, onFulfilled, onRejected), + undefined, + rethrowAssertionErrorRejection, + ); +} + +/** + * @param {ArrayBufferLike} O + * @returns {boolean} + */ +function isDetachedBuffer(O) { + return O.byteLength === 0 && ops.op_arraybuffer_was_detached(O); +} + +/** + * @param {ArrayBufferLike} O + * @returns {boolean} + */ +function canTransferArrayBuffer(O) { + assert(typeof O === "object"); + assert( + ObjectPrototypeIsPrototypeOf(ArrayBufferPrototype, O) || + // deno-lint-ignore prefer-primordials + ObjectPrototypeIsPrototypeOf(SharedArrayBuffer.prototype, O), + ); + if (isDetachedBuffer(O)) { + return false; } - - /** - * @template R - * @param {() => void} startAlgorithm - * @param {() => Promise} pullAlgorithm - * @param {(reason: any) => Promise} cancelAlgorithm - * @param {number=} highWaterMark - * @param {((chunk: R) => number)=} sizeAlgorithm - * @returns {ReadableStream} - */ - function createReadableStream( + // TODO(@crowlKats): 4. If SameValue(O.[[ArrayBufferDetachKey]], undefined) is false, return false. + return true; +} + +/** + * @param {ArrayBufferLike} O + * @returns {ArrayBufferLike} + */ +function transferArrayBuffer(O) { + return ops.op_transfer_arraybuffer(O); +} + +/** + * @param {ArrayBufferView} O + * @returns {Uint8Array} + */ +function cloneAsUint8Array(O) { + assert(typeof O === "object"); + assert(ArrayBufferIsView(O)); + assert(!isDetachedBuffer(O.buffer)); + const buffer = O.buffer.slice(O.byteOffset, O.byteOffset + O.byteLength); + return new Uint8Array(buffer); +} + +const _abortAlgorithm = Symbol("[[abortAlgorithm]]"); +const _abortSteps = Symbol("[[AbortSteps]]"); +const _autoAllocateChunkSize = Symbol("[[autoAllocateChunkSize]]"); +const _backpressure = Symbol("[[backpressure]]"); +const _backpressureChangePromise = Symbol("[[backpressureChangePromise]]"); +const _byobRequest = Symbol("[[byobRequest]]"); +const _cancelAlgorithm = Symbol("[[cancelAlgorithm]]"); +const _cancelSteps = Symbol("[[CancelSteps]]"); +const _close = Symbol("close sentinel"); +const _closeAlgorithm = Symbol("[[closeAlgorithm]]"); +const _closedPromise = Symbol("[[closedPromise]]"); +const _closeRequest = Symbol("[[closeRequest]]"); +const _closeRequested = Symbol("[[closeRequested]]"); +const _controller = Symbol("[[controller]]"); +const _detached = Symbol("[[Detached]]"); +const _disturbed = Symbol("[[disturbed]]"); +const _errorSteps = Symbol("[[ErrorSteps]]"); +const _flushAlgorithm = Symbol("[[flushAlgorithm]]"); +const _globalObject = Symbol("[[globalObject]]"); +const _highWaterMark = Symbol("[[highWaterMark]]"); +const _inFlightCloseRequest = Symbol("[[inFlightCloseRequest]]"); +const _inFlightWriteRequest = Symbol("[[inFlightWriteRequest]]"); +const _pendingAbortRequest = Symbol("[pendingAbortRequest]"); +const _pendingPullIntos = Symbol("[[pendingPullIntos]]"); +const _preventCancel = Symbol("[[preventCancel]]"); +const _pullAgain = Symbol("[[pullAgain]]"); +const _pullAlgorithm = Symbol("[[pullAlgorithm]]"); +const _pulling = Symbol("[[pulling]]"); +const _pullSteps = Symbol("[[PullSteps]]"); +const _releaseSteps = Symbol("[[ReleaseSteps]]"); +const _queue = Symbol("[[queue]]"); +const _queueTotalSize = Symbol("[[queueTotalSize]]"); +const _readable = Symbol("[[readable]]"); +const _reader = Symbol("[[reader]]"); +const _readRequests = Symbol("[[readRequests]]"); +const _readIntoRequests = Symbol("[[readIntoRequests]]"); +const _readyPromise = Symbol("[[readyPromise]]"); +const _signal = Symbol("[[signal]]"); +const _started = Symbol("[[started]]"); +const _state = Symbol("[[state]]"); +const _storedError = Symbol("[[storedError]]"); +const _strategyHWM = Symbol("[[strategyHWM]]"); +const _strategySizeAlgorithm = Symbol("[[strategySizeAlgorithm]]"); +const _stream = Symbol("[[stream]]"); +const _transformAlgorithm = Symbol("[[transformAlgorithm]]"); +const _view = Symbol("[[view]]"); +const _writable = Symbol("[[writable]]"); +const _writeAlgorithm = Symbol("[[writeAlgorithm]]"); +const _writer = Symbol("[[writer]]"); +const _writeRequests = Symbol("[[writeRequests]]"); + +/** + * @template R + * @param {ReadableStream} stream + * @returns {ReadableStreamDefaultReader} + */ +function acquireReadableStreamDefaultReader(stream) { + return new ReadableStreamDefaultReader(stream); +} + +/** + * @template R + * @param {ReadableStream} stream + * @returns {ReadableStreamBYOBReader} + */ +function acquireReadableStreamBYOBReader(stream) { + const reader = webidl.createBranded(ReadableStreamBYOBReader); + setUpReadableStreamBYOBReader(reader, stream); + return reader; +} + +/** + * @template W + * @param {WritableStream} stream + * @returns {WritableStreamDefaultWriter} + */ +function acquireWritableStreamDefaultWriter(stream) { + return new WritableStreamDefaultWriter(stream); +} + +/** + * @template R + * @param {() => void} startAlgorithm + * @param {() => Promise} pullAlgorithm + * @param {(reason: any) => Promise} cancelAlgorithm + * @param {number=} highWaterMark + * @param {((chunk: R) => number)=} sizeAlgorithm + * @returns {ReadableStream} + */ +function createReadableStream( + startAlgorithm, + pullAlgorithm, + cancelAlgorithm, + highWaterMark = 1, + sizeAlgorithm = () => 1, +) { + assert(isNonNegativeNumber(highWaterMark)); + /** @type {ReadableStream} */ + const stream = webidl.createBranded(ReadableStream); + initializeReadableStream(stream); + const controller = webidl.createBranded(ReadableStreamDefaultController); + setUpReadableStreamDefaultController( + stream, + controller, startAlgorithm, pullAlgorithm, cancelAlgorithm, - highWaterMark = 1, - sizeAlgorithm = () => 1, - ) { - assert(isNonNegativeNumber(highWaterMark)); - /** @type {ReadableStream} */ - const stream = webidl.createBranded(ReadableStream); - initializeReadableStream(stream); - const controller = webidl.createBranded(ReadableStreamDefaultController); - setUpReadableStreamDefaultController( - stream, - controller, - startAlgorithm, - pullAlgorithm, - cancelAlgorithm, - highWaterMark, - sizeAlgorithm, - ); - return stream; - } - - /** - * @template W - * @param {(controller: WritableStreamDefaultController) => Promise} startAlgorithm - * @param {(chunk: W) => Promise} writeAlgorithm - * @param {() => Promise} closeAlgorithm - * @param {(reason: any) => Promise} abortAlgorithm - * @param {number} highWaterMark - * @param {(chunk: W) => number} sizeAlgorithm - * @returns {WritableStream} - */ - function createWritableStream( + highWaterMark, + sizeAlgorithm, + ); + return stream; +} + +/** + * @template W + * @param {(controller: WritableStreamDefaultController) => Promise} startAlgorithm + * @param {(chunk: W) => Promise} writeAlgorithm + * @param {() => Promise} closeAlgorithm + * @param {(reason: any) => Promise} abortAlgorithm + * @param {number} highWaterMark + * @param {(chunk: W) => number} sizeAlgorithm + * @returns {WritableStream} + */ +function createWritableStream( + startAlgorithm, + writeAlgorithm, + closeAlgorithm, + abortAlgorithm, + highWaterMark, + sizeAlgorithm, +) { + assert(isNonNegativeNumber(highWaterMark)); + const stream = webidl.createBranded(WritableStream); + initializeWritableStream(stream); + const controller = webidl.createBranded(WritableStreamDefaultController); + setUpWritableStreamDefaultController( + stream, + controller, startAlgorithm, writeAlgorithm, closeAlgorithm, abortAlgorithm, highWaterMark, sizeAlgorithm, - ) { - assert(isNonNegativeNumber(highWaterMark)); - const stream = webidl.createBranded(WritableStream); - initializeWritableStream(stream); - const controller = webidl.createBranded(WritableStreamDefaultController); - setUpWritableStreamDefaultController( - stream, - controller, - startAlgorithm, - writeAlgorithm, - closeAlgorithm, - abortAlgorithm, - highWaterMark, - sizeAlgorithm, - ); - return stream; + ); + return stream; +} + +/** + * @template T + * @param {{ [_queue]: Array>, [_queueTotalSize]: number }} container + * @returns {T} + */ +function dequeueValue(container) { + assert( + ReflectHas(container, _queue) && + ReflectHas(container, _queueTotalSize), + ); + assert(container[_queue].length); + const valueWithSize = ArrayPrototypeShift(container[_queue]); + container[_queueTotalSize] -= valueWithSize.size; + if (container[_queueTotalSize] < 0) { + container[_queueTotalSize] = 0; } - - /** - * @template T - * @param {{ [_queue]: Array>, [_queueTotalSize]: number }} container - * @returns {T} - */ - function dequeueValue(container) { - assert( - ReflectHas(container, _queue) && - ReflectHas(container, _queueTotalSize), + return valueWithSize.value; +} + +/** + * @template T + * @param {{ [_queue]: Array>, [_queueTotalSize]: number }} container + * @param {T} value + * @param {number} size + * @returns {void} + */ +function enqueueValueWithSize(container, value, size) { + assert( + ReflectHas(container, _queue) && + ReflectHas(container, _queueTotalSize), + ); + if (isNonNegativeNumber(size) === false) { + throw RangeError("chunk size isn't a positive number"); + } + if (size === Infinity) { + throw RangeError("chunk size is invalid"); + } + ArrayPrototypePush(container[_queue], { value, size }); + container[_queueTotalSize] += size; +} + +/** + * @param {QueuingStrategy} strategy + * @param {number} defaultHWM + */ +function extractHighWaterMark(strategy, defaultHWM) { + if (strategy.highWaterMark === undefined) { + return defaultHWM; + } + const highWaterMark = strategy.highWaterMark; + if (NumberIsNaN(highWaterMark) || highWaterMark < 0) { + throw RangeError( + `Expected highWaterMark to be a positive number or Infinity, got "${highWaterMark}".`, ); - assert(container[_queue].length); - const valueWithSize = ArrayPrototypeShift(container[_queue]); - container[_queueTotalSize] -= valueWithSize.size; - if (container[_queueTotalSize] < 0) { - container[_queueTotalSize] = 0; - } - return valueWithSize.value; } - - /** - * @template T - * @param {{ [_queue]: Array>, [_queueTotalSize]: number }} container - * @param {T} value - * @param {number} size - * @returns {void} - */ - function enqueueValueWithSize(container, value, size) { - assert( - ReflectHas(container, _queue) && - ReflectHas(container, _queueTotalSize), + return highWaterMark; +} + +/** + * @template T + * @param {QueuingStrategy} strategy + * @return {(chunk: T) => number} + */ +function extractSizeAlgorithm(strategy) { + if (strategy.size === undefined) { + return () => 1; + } + return (chunk) => + webidl.invokeCallbackFunction( + strategy.size, + [chunk], + undefined, + webidl.converters["unrestricted double"], + { prefix: "Failed to call `sizeAlgorithm`" }, ); - if (isNonNegativeNumber(size) === false) { - throw RangeError("chunk size isn't a positive number"); - } - if (size === Infinity) { - throw RangeError("chunk size is invalid"); - } - ArrayPrototypePush(container[_queue], { value, size }); - container[_queueTotalSize] += size; - } +} + +/** + * @param {() => void} startAlgorithm + * @param {() => Promise} pullAlgorithm + * @param {(reason: any) => Promise} cancelAlgorithm + * @returns {ReadableStream} + */ +function createReadableByteStream( + startAlgorithm, + pullAlgorithm, + cancelAlgorithm, +) { + const stream = webidl.createBranded(ReadableStream); + initializeReadableStream(stream); + const controller = webidl.createBranded(ReadableByteStreamController); + setUpReadableByteStreamController( + stream, + controller, + startAlgorithm, + pullAlgorithm, + cancelAlgorithm, + 0, + undefined, + ); + return stream; +} + +/** + * @param {ReadableStream} stream + * @returns {void} + */ +function initializeReadableStream(stream) { + stream[_state] = "readable"; + stream[_reader] = stream[_storedError] = undefined; + stream[_disturbed] = false; +} + +/** + * @template I + * @template O + * @param {TransformStream} stream + * @param {Deferred} startPromise + * @param {number} writableHighWaterMark + * @param {(chunk: I) => number} writableSizeAlgorithm + * @param {number} readableHighWaterMark + * @param {(chunk: O) => number} readableSizeAlgorithm + */ +function initializeTransformStream( + stream, + startPromise, + writableHighWaterMark, + writableSizeAlgorithm, + readableHighWaterMark, + readableSizeAlgorithm, +) { + function startAlgorithm() { + return startPromise.promise; + } + + function writeAlgorithm(chunk) { + return transformStreamDefaultSinkWriteAlgorithm(stream, chunk); + } + + function abortAlgorithm(reason) { + return transformStreamDefaultSinkAbortAlgorithm(stream, reason); + } + + function closeAlgorithm() { + return transformStreamDefaultSinkCloseAlgorithm(stream); + } + + stream[_writable] = createWritableStream( + startAlgorithm, + writeAlgorithm, + closeAlgorithm, + abortAlgorithm, + writableHighWaterMark, + writableSizeAlgorithm, + ); - /** - * @param {QueuingStrategy} strategy - * @param {number} defaultHWM - */ - function extractHighWaterMark(strategy, defaultHWM) { - if (strategy.highWaterMark === undefined) { - return defaultHWM; - } - const highWaterMark = strategy.highWaterMark; - if (NumberIsNaN(highWaterMark) || highWaterMark < 0) { - throw RangeError( - `Expected highWaterMark to be a positive number or Infinity, got "${highWaterMark}".`, - ); - } - return highWaterMark; + function pullAlgorithm() { + return transformStreamDefaultSourcePullAlgorithm(stream); } - /** - * @template T - * @param {QueuingStrategy} strategy - * @return {(chunk: T) => number} - */ - function extractSizeAlgorithm(strategy) { - if (strategy.size === undefined) { - return () => 1; - } - return (chunk) => - webidl.invokeCallbackFunction( - strategy.size, - [chunk], - undefined, - webidl.converters["unrestricted double"], - { prefix: "Failed to call `sizeAlgorithm`" }, - ); + function cancelAlgorithm(reason) { + transformStreamErrorWritableAndUnblockWrite(stream, reason); + return resolvePromiseWith(undefined); } - /** - * @param {() => void} startAlgorithm - * @param {() => Promise} pullAlgorithm - * @param {(reason: any) => Promise} cancelAlgorithm - * @returns {ReadableStream} - */ - function createReadableByteStream( + stream[_readable] = createReadableStream( startAlgorithm, pullAlgorithm, cancelAlgorithm, - ) { - const stream = webidl.createBranded(ReadableStream); - initializeReadableStream(stream); - const controller = webidl.createBranded(ReadableByteStreamController); - setUpReadableByteStreamController( - stream, - controller, - startAlgorithm, - pullAlgorithm, - cancelAlgorithm, - 0, - undefined, - ); - return stream; - } - - /** - * @param {ReadableStream} stream - * @returns {void} - */ - function initializeReadableStream(stream) { - stream[_state] = "readable"; - stream[_reader] = stream[_storedError] = undefined; - stream[_disturbed] = false; - } - - /** - * @template I - * @template O - * @param {TransformStream} stream - * @param {Deferred} startPromise - * @param {number} writableHighWaterMark - * @param {(chunk: I) => number} writableSizeAlgorithm - * @param {number} readableHighWaterMark - * @param {(chunk: O) => number} readableSizeAlgorithm - */ - function initializeTransformStream( - stream, - startPromise, - writableHighWaterMark, - writableSizeAlgorithm, readableHighWaterMark, readableSizeAlgorithm, - ) { - function startAlgorithm() { - return startPromise.promise; - } - - function writeAlgorithm(chunk) { - return transformStreamDefaultSinkWriteAlgorithm(stream, chunk); - } - - function abortAlgorithm(reason) { - return transformStreamDefaultSinkAbortAlgorithm(stream, reason); - } + ); - function closeAlgorithm() { - return transformStreamDefaultSinkCloseAlgorithm(stream); - } + stream[_backpressure] = stream[_backpressureChangePromise] = undefined; + transformStreamSetBackpressure(stream, true); + stream[_controller] = undefined; +} + +/** @param {WritableStream} stream */ +function initializeWritableStream(stream) { + stream[_state] = "writable"; + stream[_storedError] = + stream[_writer] = + stream[_controller] = + stream[_inFlightWriteRequest] = + stream[_closeRequest] = + stream[_inFlightCloseRequest] = + stream[_pendingAbortRequest] = + undefined; + stream[_writeRequests] = []; + stream[_backpressure] = false; +} + +/** + * @param {unknown} v + * @returns {v is number} + */ +function isNonNegativeNumber(v) { + if (typeof v !== "number") { + return false; + } + if (NumberIsNaN(v)) { + return false; + } + if (v < 0) { + return false; + } + return true; +} + +/** + * @param {unknown} value + * @returns {value is ReadableStream} + */ +function isReadableStream(value) { + return !(typeof value !== "object" || value === null || + !ReflectHas(value, _controller)); +} + +/** + * @param {ReadableStream} stream + * @returns {boolean} + */ +function isReadableStreamLocked(stream) { + if (stream[_reader] === undefined) { + return false; + } + return true; +} + +/** + * @param {unknown} value + * @returns {value is ReadableStreamDefaultReader} + */ +function isReadableStreamDefaultReader(value) { + return !(typeof value !== "object" || value === null || + !ReflectHas(value, _readRequests)); +} + +/** + * @param {unknown} value + * @returns {value is ReadableStreamBYOBReader} + */ +function isReadableStreamBYOBReader(value) { + return !(typeof value !== "object" || value === null || + !ReflectHas(value, _readIntoRequests)); +} + +/** + * @param {ReadableStream} stream + * @returns {boolean} + */ +function isReadableStreamDisturbed(stream) { + assert(isReadableStream(stream)); + return stream[_disturbed]; +} + +const DEFAULT_CHUNK_SIZE = 64 * 1024; // 64 KiB + +// A finalization registry to clean up underlying resources that are GC'ed. +const RESOURCE_REGISTRY = new FinalizationRegistry((rid) => { + core.tryClose(rid); +}); + +const _readAll = Symbol("[[readAll]]"); +const _original = Symbol("[[original]]"); +/** + * Create a new ReadableStream object that is backed by a Resource that + * implements `Resource::read_return`. This object contains enough metadata to + * allow callers to bypass the JavaScript ReadableStream implementation and + * read directly from the underlying resource if they so choose (FastStream). + * + * @param {number} rid The resource ID to read from. + * @param {boolean=} autoClose If the resource should be auto-closed when the stream closes. Defaults to true. + * @returns {ReadableStream} + */ +function readableStreamForRid(rid, autoClose = true) { + const stream = webidl.createBranded(ReadableStream); + stream[_resourceBacking] = { rid, autoClose }; + + const tryClose = () => { + if (!autoClose) return; + RESOURCE_REGISTRY.unregister(stream); + core.tryClose(rid); + }; - stream[_writable] = createWritableStream( - startAlgorithm, - writeAlgorithm, - closeAlgorithm, - abortAlgorithm, - writableHighWaterMark, - writableSizeAlgorithm, - ); + if (autoClose) { + RESOURCE_REGISTRY.register(stream, rid, stream); + } - function pullAlgorithm() { - return transformStreamDefaultSourcePullAlgorithm(stream); - } + const underlyingSource = { + type: "bytes", + async pull(controller) { + const v = controller.byobRequest.view; + try { + if (controller[_readAll] === true) { + // fast path for tee'd streams consuming body + const chunk = await core.readAll(rid); + if (chunk.byteLength > 0) { + controller.enqueue(chunk); + } + controller.close(); + tryClose(); + return; + } - function cancelAlgorithm(reason) { - transformStreamErrorWritableAndUnblockWrite(stream, reason); - return resolvePromiseWith(undefined); - } + const bytesRead = await core.read(rid, v); + if (bytesRead === 0) { + tryClose(); + controller.close(); + controller.byobRequest.respond(0); + } else { + controller.byobRequest.respond(bytesRead); + } + } catch (e) { + controller.error(e); + tryClose(); + } + }, + cancel() { + tryClose(); + }, + autoAllocateChunkSize: DEFAULT_CHUNK_SIZE, + }; + initializeReadableStream(stream); + setUpReadableByteStreamControllerFromUnderlyingSource( + stream, + underlyingSource, + underlyingSource, + 0, + ); + return stream; +} + +const promiseIdSymbol = SymbolFor("Deno.core.internalPromiseId"); +const _isUnref = Symbol("isUnref"); +/** + * Create a new ReadableStream object that is backed by a Resource that + * implements `Resource::read_return`. This readable stream supports being + * refed and unrefed by calling `readableStreamForRidUnrefableRef` and + * `readableStreamForRidUnrefableUnref` on it. Unrefable streams are not + * FastStream compatible. + * + * @param {number} rid The resource ID to read from. + * @returns {ReadableStream} + */ +function readableStreamForRidUnrefable(rid) { + const stream = webidl.createBranded(ReadableStream); + stream[promiseIdSymbol] = undefined; + stream[_isUnref] = false; + stream[_resourceBackingUnrefable] = { rid, autoClose: true }; + const underlyingSource = { + type: "bytes", + async pull(controller) { + const v = controller.byobRequest.view; + try { + const promise = core.read(rid, v); + const promiseId = stream[promiseIdSymbol] = promise[promiseIdSymbol]; + if (stream[_isUnref]) core.unrefOp(promiseId); + const bytesRead = await promise; + stream[promiseIdSymbol] = undefined; + if (bytesRead === 0) { + core.tryClose(rid); + controller.close(); + controller.byobRequest.respond(0); + } else { + controller.byobRequest.respond(bytesRead); + } + } catch (e) { + controller.error(e); + core.tryClose(rid); + } + }, + cancel() { + core.tryClose(rid); + }, + autoAllocateChunkSize: DEFAULT_CHUNK_SIZE, + }; + initializeReadableStream(stream); + setUpReadableByteStreamControllerFromUnderlyingSource( + stream, + underlyingSource, + underlyingSource, + 0, + ); + return stream; +} - stream[_readable] = createReadableStream( - startAlgorithm, - pullAlgorithm, - cancelAlgorithm, - readableHighWaterMark, - readableSizeAlgorithm, - ); +function readableStreamIsUnrefable(stream) { + return ReflectHas(stream, _isUnref); +} - stream[_backpressure] = stream[_backpressureChangePromise] = undefined; - transformStreamSetBackpressure(stream, true); - stream[_controller] = undefined; +function readableStreamForRidUnrefableRef(stream) { + if (!readableStreamIsUnrefable(stream)) { + throw new TypeError("Not an unrefable stream"); } - - /** @param {WritableStream} stream */ - function initializeWritableStream(stream) { - stream[_state] = "writable"; - stream[_storedError] = - stream[_writer] = - stream[_controller] = - stream[_inFlightWriteRequest] = - stream[_closeRequest] = - stream[_inFlightCloseRequest] = - stream[_pendingAbortRequest] = - undefined; - stream[_writeRequests] = []; - stream[_backpressure] = false; + stream[_isUnref] = false; + if (stream[promiseIdSymbol] !== undefined) { + core.refOp(stream[promiseIdSymbol]); } +} - /** - * @param {unknown} v - * @returns {v is number} - */ - function isNonNegativeNumber(v) { - if (typeof v !== "number") { - return false; - } - if (NumberIsNaN(v)) { - return false; - } - if (v < 0) { - return false; - } - return true; +function readableStreamForRidUnrefableUnref(stream) { + if (!readableStreamIsUnrefable(stream)) { + throw new TypeError("Not an unrefable stream"); } - - /** - * @param {unknown} value - * @returns {value is ReadableStream} - */ - function isReadableStream(value) { - return !(typeof value !== "object" || value === null || - !ReflectHas(value, _controller)); + stream[_isUnref] = true; + if (stream[promiseIdSymbol] !== undefined) { + core.unrefOp(stream[promiseIdSymbol]); } +} - /** - * @param {ReadableStream} stream - * @returns {boolean} - */ - function isReadableStreamLocked(stream) { - if (stream[_reader] === undefined) { - return false; +function getReadableStreamResourceBacking(stream) { + return stream[_resourceBacking]; +} + +function getReadableStreamResourceBackingUnrefable(stream) { + return stream[_resourceBackingUnrefable]; +} + +async function readableStreamCollectIntoUint8Array(stream) { + const resourceBacking = getReadableStreamResourceBacking(stream) || + getReadableStreamResourceBackingUnrefable(stream); + const reader = acquireReadableStreamDefaultReader(stream); + + if (resourceBacking) { + // fast path, read whole body in a single op call + try { + readableStreamDisturb(stream); + const promise = core.opAsync("op_read_all", resourceBacking.rid); + if (readableStreamIsUnrefable(stream)) { + const promiseId = stream[promiseIdSymbol] = promise[promiseIdSymbol]; + if (stream[_isUnref]) core.unrefOp(promiseId); + } + const buf = await promise; + readableStreamThrowIfErrored(stream); + readableStreamClose(stream); + return buf; + } catch (err) { + readableStreamThrowIfErrored(stream); + readableStreamError(stream, err); + throw err; + } finally { + if (resourceBacking.autoClose) { + core.tryClose(resourceBacking.rid); + } } - return true; } - /** - * @param {unknown} value - * @returns {value is ReadableStreamDefaultReader} - */ - function isReadableStreamDefaultReader(value) { - return !(typeof value !== "object" || value === null || - !ReflectHas(value, _readRequests)); - } + // slow path + /** @type {Uint8Array[]} */ + const chunks = []; + let totalLength = 0; - /** - * @param {unknown} value - * @returns {value is ReadableStreamBYOBReader} - */ - function isReadableStreamBYOBReader(value) { - return !(typeof value !== "object" || value === null || - !ReflectHas(value, _readIntoRequests)); + // tee'd stream + if (stream[_original]) { + // One of the branches is consuming the stream + // signal controller.pull that we can consume it in a single op + stream[_original][_controller][_readAll] = true; } - /** - * @param {ReadableStream} stream - * @returns {boolean} - */ - function isReadableStreamDisturbed(stream) { - assert(isReadableStream(stream)); - return stream[_disturbed]; - } + while (true) { + const { value: chunk, done } = await reader.read(); - const DEFAULT_CHUNK_SIZE = 64 * 1024; // 64 KiB + if (done) break; - // A finalization registry to clean up underlying resources that are GC'ed. - const RESOURCE_REGISTRY = new FinalizationRegistry((rid) => { - core.tryClose(rid); - }); - - const _readAll = Symbol("[[readAll]]"); - const _original = Symbol("[[original]]"); - /** - * Create a new ReadableStream object that is backed by a Resource that - * implements `Resource::read_return`. This object contains enough metadata to - * allow callers to bypass the JavaScript ReadableStream implementation and - * read directly from the underlying resource if they so choose (FastStream). - * - * @param {number} rid The resource ID to read from. - * @param {boolean=} autoClose If the resource should be auto-closed when the stream closes. Defaults to true. - * @returns {ReadableStream} - */ - function readableStreamForRid(rid, autoClose = true) { - const stream = webidl.createBranded(ReadableStream); - stream[_resourceBacking] = { rid, autoClose }; - - const tryClose = () => { - if (!autoClose) return; - RESOURCE_REGISTRY.unregister(stream); - core.tryClose(rid); - }; - - if (autoClose) { - RESOURCE_REGISTRY.register(stream, rid, stream); + if (!ObjectPrototypeIsPrototypeOf(Uint8ArrayPrototype, chunk)) { + throw new TypeError( + "Can't convert value to Uint8Array while consuming the stream", + ); } - const underlyingSource = { - type: "bytes", - async pull(controller) { - const v = controller.byobRequest.view; - try { - if (controller[_readAll] === true) { - // fast path for tee'd streams consuming body - const chunk = await core.readAll(rid); - if (chunk.byteLength > 0) { - controller.enqueue(chunk); - } - controller.close(); - tryClose(); - return; - } + ArrayPrototypePush(chunks, chunk); + totalLength += chunk.byteLength; + } + + const finalBuffer = new Uint8Array(totalLength); + let offset = 0; + for (let i = 0; i < chunks.length; ++i) { + const chunk = chunks[i]; + TypedArrayPrototypeSet(finalBuffer, chunk, offset); + offset += chunk.byteLength; + } + return finalBuffer; +} + +/** + * Create a new Writable object that is backed by a Resource that implements + * `Resource::write` / `Resource::write_all`. This object contains enough + * metadata to allow callers to bypass the JavaScript WritableStream + * implementation and write directly to the underlying resource if they so + * choose (FastStream). + * + * @param {number} rid The resource ID to write to. + * @param {boolean=} autoClose If the resource should be auto-closed when the stream closes. Defaults to true. + * @returns {ReadableStream} + */ +function writableStreamForRid(rid, autoClose = true) { + const stream = webidl.createBranded(WritableStream); + stream[_resourceBacking] = { rid, autoClose }; + + const tryClose = () => { + if (!autoClose) return; + RESOURCE_REGISTRY.unregister(stream); + core.tryClose(rid); + }; - const bytesRead = await core.read(rid, v); - if (bytesRead === 0) { - tryClose(); - controller.close(); - controller.byobRequest.respond(0); - } else { - controller.byobRequest.respond(bytesRead); - } - } catch (e) { - controller.error(e); - tryClose(); - } - }, - cancel() { - tryClose(); - }, - autoAllocateChunkSize: DEFAULT_CHUNK_SIZE, - }; - initializeReadableStream(stream); - setUpReadableByteStreamControllerFromUnderlyingSource( - stream, - underlyingSource, - underlyingSource, - 0, - ); - return stream; + if (autoClose) { + RESOURCE_REGISTRY.register(stream, rid, stream); } - const promiseIdSymbol = SymbolFor("Deno.core.internalPromiseId"); - const _isUnref = Symbol("isUnref"); - /** - * Create a new ReadableStream object that is backed by a Resource that - * implements `Resource::read_return`. This readable stream supports being - * refed and unrefed by calling `readableStreamForRidUnrefableRef` and - * `readableStreamForRidUnrefableUnref` on it. Unrefable streams are not - * FastStream compatible. - * - * @param {number} rid The resource ID to read from. - * @returns {ReadableStream} - */ - function readableStreamForRidUnrefable(rid) { - const stream = webidl.createBranded(ReadableStream); - stream[promiseIdSymbol] = undefined; - stream[_isUnref] = false; - stream[_resourceBackingUnrefable] = { rid, autoClose: true }; - const underlyingSource = { - type: "bytes", - async pull(controller) { - const v = controller.byobRequest.view; - try { - const promise = core.read(rid, v); - const promiseId = stream[promiseIdSymbol] = promise[promiseIdSymbol]; - if (stream[_isUnref]) core.unrefOp(promiseId); - const bytesRead = await promise; - stream[promiseIdSymbol] = undefined; - if (bytesRead === 0) { - core.tryClose(rid); - controller.close(); - controller.byobRequest.respond(0); - } else { - controller.byobRequest.respond(bytesRead); - } - } catch (e) { - controller.error(e); - core.tryClose(rid); + const underlyingSink = { + async write(chunk, controller) { + try { + await core.writeAll(rid, chunk); + } catch (e) { + controller.error(e); + tryClose(); + } + }, + close() { + tryClose(); + }, + abort() { + tryClose(); + }, + }; + initializeWritableStream(stream); + setUpWritableStreamDefaultControllerFromUnderlyingSink( + stream, + underlyingSink, + underlyingSink, + 1, + () => 1, + ); + return stream; +} + +function getWritableStreamResourceBacking(stream) { + return stream[_resourceBacking]; +} + +/* + * @param {ReadableStream} stream + */ +function readableStreamThrowIfErrored(stream) { + if (stream[_state] === "errored") { + throw stream[_storedError]; + } +} + +/** + * @param {unknown} value + * @returns {value is WritableStream} + */ +function isWritableStream(value) { + return !(typeof value !== "object" || value === null || + !ReflectHas(value, _controller)); +} + +/** + * @param {WritableStream} stream + * @returns {boolean} + */ +function isWritableStreamLocked(stream) { + if (stream[_writer] === undefined) { + return false; + } + return true; +} +/** + * @template T + * @param {{ [_queue]: Array>, [_queueTotalSize]: number }} container + * @returns {T | _close} + */ +function peekQueueValue(container) { + assert( + ReflectHas(container, _queue) && + ReflectHas(container, _queueTotalSize), + ); + assert(container[_queue].length); + const valueWithSize = container[_queue][0]; + return valueWithSize.value; +} + +/** + * @param {ReadableByteStreamController} controller + * @returns {void} + */ +function readableByteStreamControllerCallPullIfNeeded(controller) { + const shouldPull = readableByteStreamControllerShouldCallPull(controller); + if (!shouldPull) { + return; + } + if (controller[_pulling]) { + controller[_pullAgain] = true; + return; + } + assert(controller[_pullAgain] === false); + controller[_pulling] = true; + /** @type {Promise} */ + const pullPromise = controller[_pullAlgorithm](controller); + setPromiseIsHandledToTrue( + PromisePrototypeThen( + pullPromise, + () => { + controller[_pulling] = false; + if (controller[_pullAgain]) { + controller[_pullAgain] = false; + readableByteStreamControllerCallPullIfNeeded(controller); } }, - cancel() { - core.tryClose(rid); + (e) => { + readableByteStreamControllerError(controller, e); }, - autoAllocateChunkSize: DEFAULT_CHUNK_SIZE, - }; - initializeReadableStream(stream); - setUpReadableByteStreamControllerFromUnderlyingSource( - stream, - underlyingSource, - underlyingSource, - 0, - ); - return stream; + ), + ); +} + +/** + * @param {ReadableByteStreamController} controller + * @returns {void} + */ +function readableByteStreamControllerClearAlgorithms(controller) { + controller[_pullAlgorithm] = undefined; + controller[_cancelAlgorithm] = undefined; +} + +/** + * @param {ReadableByteStreamController} controller + * @param {any} e + */ +function readableByteStreamControllerError(controller, e) { + /** @type {ReadableStream} */ + const stream = controller[_stream]; + if (stream[_state] !== "readable") { + return; + } + readableByteStreamControllerClearPendingPullIntos(controller); + resetQueue(controller); + readableByteStreamControllerClearAlgorithms(controller); + readableStreamError(stream, e); +} + +/** + * @param {ReadableByteStreamController} controller + * @returns {void} + */ +function readableByteStreamControllerClearPendingPullIntos(controller) { + readableByteStreamControllerInvalidateBYOBRequest(controller); + controller[_pendingPullIntos] = []; +} + +/** + * @param {ReadableByteStreamController} controller + * @returns {void} + */ +function readableByteStreamControllerClose(controller) { + /** @type {ReadableStream} */ + const stream = controller[_stream]; + if (controller[_closeRequested] || stream[_state] !== "readable") { + return; + } + if (controller[_queueTotalSize] > 0) { + controller[_closeRequested] = true; + return; } - - function readableStreamIsUnrefable(stream) { - return ReflectHas(stream, _isUnref); + if (controller[_pendingPullIntos].length !== 0) { + const firstPendingPullInto = controller[_pendingPullIntos][0]; + if (firstPendingPullInto.bytesFilled > 0) { + const e = new TypeError( + "Insufficient bytes to fill elements in the given buffer", + ); + readableByteStreamControllerError(controller, e); + throw e; + } + } + readableByteStreamControllerClearAlgorithms(controller); + readableStreamClose(stream); +} + +/** + * @param {ReadableByteStreamController} controller + * @param {ArrayBufferView} chunk + */ +function readableByteStreamControllerEnqueue(controller, chunk) { + /** @type {ReadableStream} */ + const stream = controller[_stream]; + if ( + controller[_closeRequested] || + controller[_stream][_state] !== "readable" + ) { + return; } - function readableStreamForRidUnrefableRef(stream) { - if (!readableStreamIsUnrefable(stream)) { - throw new TypeError("Not an unrefable stream"); + const { buffer, byteOffset, byteLength } = chunk; + if (isDetachedBuffer(buffer)) { + throw new TypeError( + "chunk's buffer is detached and so cannot be enqueued", + ); + } + const transferredBuffer = transferArrayBuffer(buffer); + if (controller[_pendingPullIntos].length !== 0) { + const firstPendingPullInto = controller[_pendingPullIntos][0]; + if (isDetachedBuffer(firstPendingPullInto.buffer)) { + throw new TypeError( + "The BYOB request's buffer has been detached and so cannot be filled with an enqueued chunk", + ); } - stream[_isUnref] = false; - if (stream[promiseIdSymbol] !== undefined) { - core.refOp(stream[promiseIdSymbol]); + readableByteStreamControllerInvalidateBYOBRequest(controller); + firstPendingPullInto.buffer = transferArrayBuffer( + firstPendingPullInto.buffer, + ); + if (firstPendingPullInto.readerType === "none") { + readableByteStreamControllerEnqueueDetachedPullIntoToQueue( + controller, + firstPendingPullInto, + ); } } - - function readableStreamForRidUnrefableUnref(stream) { - if (!readableStreamIsUnrefable(stream)) { - throw new TypeError("Not an unrefable stream"); - } - stream[_isUnref] = true; - if (stream[promiseIdSymbol] !== undefined) { - core.unrefOp(stream[promiseIdSymbol]); + if (readableStreamHasDefaultReader(stream)) { + readableByteStreamControllerProcessReadRequestsUsingQueue(controller); + if (readableStreamGetNumReadRequests(stream) === 0) { + assert(controller[_pendingPullIntos].length === 0); + readableByteStreamControllerEnqueueChunkToQueue( + controller, + transferredBuffer, + byteOffset, + byteLength, + ); + } else { + assert(controller[_queue].length === 0); + if (controller[_pendingPullIntos].length !== 0) { + assert(controller[_pendingPullIntos][0].readerType === "default"); + readableByteStreamControllerShiftPendingPullInto(controller); + } + const transferredView = new Uint8Array( + transferredBuffer, + byteOffset, + byteLength, + ); + readableStreamFulfillReadRequest(stream, transferredView, false); } + } else if (readableStreamHasBYOBReader(stream)) { + readableByteStreamControllerEnqueueChunkToQueue( + controller, + transferredBuffer, + byteOffset, + byteLength, + ); + readableByteStreamControllerProcessPullIntoDescriptorsUsingQueue( + controller, + ); + } else { + assert(isReadableStreamLocked(stream) === false); + readableByteStreamControllerEnqueueChunkToQueue( + controller, + transferredBuffer, + byteOffset, + byteLength, + ); } - - function getReadableStreamResourceBacking(stream) { - return stream[_resourceBacking]; + readableByteStreamControllerCallPullIfNeeded(controller); +} + +/** + * @param {ReadableByteStreamController} controller + * @param {ArrayBufferLike} buffer + * @param {number} byteOffset + * @param {number} byteLength + * @returns {void} + */ +function readableByteStreamControllerEnqueueChunkToQueue( + controller, + buffer, + byteOffset, + byteLength, +) { + ArrayPrototypePush(controller[_queue], { buffer, byteOffset, byteLength }); + controller[_queueTotalSize] += byteLength; +} + +/** + * @param {ReadableByteStreamController} controller + * @param {ArrayBufferLike} buffer + * @param {number} byteOffset + * @param {number} byteLength + * @returns {void} + */ +function readableByteStreamControllerEnqueueClonedChunkToQueue( + controller, + buffer, + byteOffset, + byteLength, +) { + let cloneResult; + try { + cloneResult = buffer.slice(byteOffset, byteOffset + byteLength); + } catch (e) { + readableByteStreamControllerError(controller, e); + } + readableByteStreamControllerEnqueueChunkToQueue( + controller, + cloneResult, + 0, + byteLength, + ); +} + +/** + * @param {ReadableByteStreamController} controller + * @param {PullIntoDescriptor} pullIntoDescriptor + * @returns {void} + */ +function readableByteStreamControllerEnqueueDetachedPullIntoToQueue( + controller, + pullIntoDescriptor, +) { + assert(pullIntoDescriptor.readerType === "none"); + if (pullIntoDescriptor.bytesFilled > 0) { + readableByteStreamControllerEnqueueClonedChunkToQueue( + controller, + pullIntoDescriptor.buffer, + pullIntoDescriptor.byteOffset, + pullIntoDescriptor.bytesFilled, + ); } - - function getReadableStreamResourceBackingUnrefable(stream) { - return stream[_resourceBackingUnrefable]; + readableByteStreamControllerShiftPendingPullInto(controller); +} + +/** + * @param {ReadableByteStreamController} controller + * @returns {ReadableStreamBYOBRequest | null} + */ +function readableByteStreamControllerGetBYOBRequest(controller) { + if ( + controller[_byobRequest] === null && + controller[_pendingPullIntos].length !== 0 + ) { + const firstDescriptor = controller[_pendingPullIntos][0]; + const view = new Uint8Array( + firstDescriptor.buffer, + firstDescriptor.byteOffset + firstDescriptor.bytesFilled, + firstDescriptor.byteLength - firstDescriptor.bytesFilled, + ); + const byobRequest = webidl.createBranded(ReadableStreamBYOBRequest); + byobRequest[_controller] = controller; + byobRequest[_view] = view; + controller[_byobRequest] = byobRequest; + } + return controller[_byobRequest]; +} + +/** + * @param {ReadableByteStreamController} controller + * @returns {number | null} + */ +function readableByteStreamControllerGetDesiredSize(controller) { + const state = controller[_stream][_state]; + if (state === "errored") { + return null; + } + if (state === "closed") { + return 0; + } + return controller[_strategyHWM] - controller[_queueTotalSize]; +} + +/** + * @param {{ [_queue]: any[], [_queueTotalSize]: number }} container + * @returns {void} + */ +function resetQueue(container) { + container[_queue] = []; + container[_queueTotalSize] = 0; +} + +/** + * @param {ReadableByteStreamController} controller + * @returns {void} + */ +function readableByteStreamControllerHandleQueueDrain(controller) { + assert(controller[_stream][_state] === "readable"); + if ( + controller[_queueTotalSize] === 0 && controller[_closeRequested] + ) { + readableByteStreamControllerClearAlgorithms(controller); + readableStreamClose(controller[_stream]); + } else { + readableByteStreamControllerCallPullIfNeeded(controller); } - - async function readableStreamCollectIntoUint8Array(stream) { - const resourceBacking = getReadableStreamResourceBacking(stream) || - getReadableStreamResourceBackingUnrefable(stream); - const reader = acquireReadableStreamDefaultReader(stream); - - if (resourceBacking) { - // fast path, read whole body in a single op call - try { - readableStreamDisturb(stream); - const promise = core.opAsync("op_read_all", resourceBacking.rid); - if (readableStreamIsUnrefable(stream)) { - const promiseId = stream[promiseIdSymbol] = promise[promiseIdSymbol]; - if (stream[_isUnref]) core.unrefOp(promiseId); - } - const buf = await promise; - readableStreamThrowIfErrored(stream); - readableStreamClose(stream); - return buf; - } catch (err) { - readableStreamThrowIfErrored(stream); - readableStreamError(stream, err); - throw err; - } finally { - if (resourceBacking.autoClose) { - core.tryClose(resourceBacking.rid); - } - } - } - - // slow path - /** @type {Uint8Array[]} */ - const chunks = []; - let totalLength = 0; - - // tee'd stream - if (stream[_original]) { - // One of the branches is consuming the stream - // signal controller.pull that we can consume it in a single op - stream[_original][_controller][_readAll] = true; +} + +/** + * @param {ReadableByteStreamController} controller + * @returns {boolean} + */ +function readableByteStreamControllerShouldCallPull(controller) { + /** @type {ReadableStream} */ + const stream = controller[_stream]; + if ( + stream[_state] !== "readable" || + controller[_closeRequested] || + !controller[_started] + ) { + return false; + } + if ( + readableStreamHasDefaultReader(stream) && + readableStreamGetNumReadRequests(stream) > 0 + ) { + return true; + } + if ( + readableStreamHasBYOBReader(stream) && + readableStreamGetNumReadIntoRequests(stream) > 0 + ) { + return true; + } + const desiredSize = readableByteStreamControllerGetDesiredSize(controller); + assert(desiredSize !== null); + return desiredSize > 0; +} + +/** + * @template R + * @param {ReadableStream} stream + * @param {ReadRequest} readRequest + * @returns {void} + */ +function readableStreamAddReadRequest(stream, readRequest) { + assert(isReadableStreamDefaultReader(stream[_reader])); + assert(stream[_state] === "readable"); + ArrayPrototypePush(stream[_reader][_readRequests], readRequest); +} + +/** + * @param {ReadableStream} stream + * @param {ReadIntoRequest} readRequest + * @returns {void} + */ +function readableStreamAddReadIntoRequest(stream, readRequest) { + assert(isReadableStreamBYOBReader(stream[_reader])); + assert(stream[_state] === "readable" || stream[_state] === "closed"); + ArrayPrototypePush(stream[_reader][_readIntoRequests], readRequest); +} + +/** + * @template R + * @param {ReadableStream} stream + * @param {any=} reason + * @returns {Promise} + */ +function readableStreamCancel(stream, reason) { + stream[_disturbed] = true; + if (stream[_state] === "closed") { + return resolvePromiseWith(undefined); + } + if (stream[_state] === "errored") { + return PromiseReject(stream[_storedError]); + } + readableStreamClose(stream); + const reader = stream[_reader]; + if (reader !== undefined && isReadableStreamBYOBReader(reader)) { + const readIntoRequests = reader[_readIntoRequests]; + reader[_readIntoRequests] = []; + for (let i = 0; i < readIntoRequests.length; ++i) { + const readIntoRequest = readIntoRequests[i]; + readIntoRequest.closeSteps(undefined); + } + } + /** @type {Promise} */ + const sourceCancelPromise = stream[_controller][_cancelSteps](reason); + return PromisePrototypeThen(sourceCancelPromise, () => undefined); +} + +/** + * @template R + * @param {ReadableStream} stream + * @returns {void} + */ +function readableStreamClose(stream) { + assert(stream[_state] === "readable"); + stream[_state] = "closed"; + /** @type {ReadableStreamDefaultReader | undefined} */ + const reader = stream[_reader]; + if (!reader) { + return; + } + if (isReadableStreamDefaultReader(reader)) { + /** @type {Array>} */ + const readRequests = reader[_readRequests]; + reader[_readRequests] = []; + for (let i = 0; i < readRequests.length; ++i) { + const readRequest = readRequests[i]; + readRequest.closeSteps(); } + } + // This promise can be double resolved. + // See: https://github.com/whatwg/streams/issues/1100 + reader[_closedPromise].resolve(undefined); +} - while (true) { - const { value: chunk, done } = await reader.read(); - - if (done) break; - - if (!ObjectPrototypeIsPrototypeOf(Uint8ArrayPrototype, chunk)) { - throw new TypeError( - "Can't convert value to Uint8Array while consuming the stream", - ); - } - - ArrayPrototypePush(chunks, chunk); - totalLength += chunk.byteLength; - } +/** + * @template R + * @param {ReadableStream} stream + * @returns {void} + */ +function readableStreamDisturb(stream) { + stream[_disturbed] = true; +} - const finalBuffer = new Uint8Array(totalLength); - let offset = 0; - for (let i = 0; i < chunks.length; ++i) { - const chunk = chunks[i]; - TypedArrayPrototypeSet(finalBuffer, chunk, offset); - offset += chunk.byteLength; +/** @param {ReadableStreamDefaultController} controller */ +function readableStreamDefaultControllerCallPullIfNeeded(controller) { + const shouldPull = readableStreamDefaultcontrollerShouldCallPull( + controller, + ); + if (shouldPull === false) { + return; + } + if (controller[_pulling] === true) { + controller[_pullAgain] = true; + return; + } + assert(controller[_pullAgain] === false); + controller[_pulling] = true; + const pullPromise = controller[_pullAlgorithm](controller); + uponFulfillment(pullPromise, () => { + controller[_pulling] = false; + if (controller[_pullAgain] === true) { + controller[_pullAgain] = false; + readableStreamDefaultControllerCallPullIfNeeded(controller); } - return finalBuffer; + }); + uponRejection(pullPromise, (e) => { + readableStreamDefaultControllerError(controller, e); + }); +} + +/** + * @param {ReadableStreamDefaultController} controller + * @returns {boolean} + */ +function readableStreamDefaultControllerCanCloseOrEnqueue(controller) { + const state = controller[_stream][_state]; + if (controller[_closeRequested] === false && state === "readable") { + return true; + } else { + return false; } +} - /** - * Create a new Writable object that is backed by a Resource that implements - * `Resource::write` / `Resource::write_all`. This object contains enough - * metadata to allow callers to bypass the JavaScript WritableStream - * implementation and write directly to the underlying resource if they so - * choose (FastStream). - * - * @param {number} rid The resource ID to write to. - * @param {boolean=} autoClose If the resource should be auto-closed when the stream closes. Defaults to true. - * @returns {ReadableStream} - */ - function writableStreamForRid(rid, autoClose = true) { - const stream = webidl.createBranded(WritableStream); - stream[_resourceBacking] = { rid, autoClose }; - - const tryClose = () => { - if (!autoClose) return; - RESOURCE_REGISTRY.unregister(stream); - core.tryClose(rid); - }; +/** @param {ReadableStreamDefaultController} controller */ +function readableStreamDefaultControllerClearAlgorithms(controller) { + controller[_pullAlgorithm] = undefined; + controller[_cancelAlgorithm] = undefined; + controller[_strategySizeAlgorithm] = undefined; +} - if (autoClose) { - RESOURCE_REGISTRY.register(stream, rid, stream); +/** @param {ReadableStreamDefaultController} controller */ +function readableStreamDefaultControllerClose(controller) { + if ( + readableStreamDefaultControllerCanCloseOrEnqueue(controller) === false + ) { + return; + } + const stream = controller[_stream]; + controller[_closeRequested] = true; + if (controller[_queue].length === 0) { + readableStreamDefaultControllerClearAlgorithms(controller); + readableStreamClose(stream); + } +} + +/** + * @template R + * @param {ReadableStreamDefaultController} controller + * @param {R} chunk + * @returns {void} + */ +function readableStreamDefaultControllerEnqueue(controller, chunk) { + if ( + readableStreamDefaultControllerCanCloseOrEnqueue(controller) === false + ) { + return; + } + const stream = controller[_stream]; + if ( + isReadableStreamLocked(stream) === true && + readableStreamGetNumReadRequests(stream) > 0 + ) { + readableStreamFulfillReadRequest(stream, chunk, false); + } else { + let chunkSize; + try { + chunkSize = controller[_strategySizeAlgorithm](chunk); + } catch (e) { + readableStreamDefaultControllerError(controller, e); + throw e; } - const underlyingSink = { - async write(chunk, controller) { - try { - await core.writeAll(rid, chunk); - } catch (e) { - controller.error(e); - tryClose(); - } - }, - close() { - tryClose(); - }, - abort() { - tryClose(); - }, - }; - initializeWritableStream(stream); - setUpWritableStreamDefaultControllerFromUnderlyingSink( - stream, - underlyingSink, - underlyingSink, - 1, - () => 1, - ); - return stream; + try { + enqueueValueWithSize(controller, chunk, chunkSize); + } catch (e) { + readableStreamDefaultControllerError(controller, e); + throw e; + } + } + readableStreamDefaultControllerCallPullIfNeeded(controller); +} + +/** + * @param {ReadableStreamDefaultController} controller + * @param {any} e + */ +function readableStreamDefaultControllerError(controller, e) { + const stream = controller[_stream]; + if (stream[_state] !== "readable") { + return; + } + resetQueue(controller); + readableStreamDefaultControllerClearAlgorithms(controller); + readableStreamError(stream, e); +} + +/** + * @param {ReadableStreamDefaultController} controller + * @returns {number | null} + */ +function readableStreamDefaultControllerGetDesiredSize(controller) { + const state = controller[_stream][_state]; + if (state === "errored") { + return null; + } + if (state === "closed") { + return 0; + } + return controller[_strategyHWM] - controller[_queueTotalSize]; +} + +/** @param {ReadableStreamDefaultController} controller */ +function readableStreamDefaultcontrollerHasBackpressure(controller) { + if (readableStreamDefaultcontrollerShouldCallPull(controller) === true) { + return false; + } else { + return true; } +} - function getWritableStreamResourceBacking(stream) { - return stream[_resourceBacking]; +/** + * @param {ReadableStreamDefaultController} controller + * @returns {boolean} + */ +function readableStreamDefaultcontrollerShouldCallPull(controller) { + const stream = controller[_stream]; + if ( + readableStreamDefaultControllerCanCloseOrEnqueue(controller) === false + ) { + return false; } - - /* - * @param {ReadableStream} stream - */ - function readableStreamThrowIfErrored(stream) { - if (stream[_state] === "errored") { - throw stream[_storedError]; - } + if (controller[_started] === false) { + return false; } - - /** - * @param {unknown} value - * @returns {value is WritableStream} - */ - function isWritableStream(value) { - return !(typeof value !== "object" || value === null || - !ReflectHas(value, _controller)); + if ( + isReadableStreamLocked(stream) && + readableStreamGetNumReadRequests(stream) > 0 + ) { + return true; } - - /** - * @param {WritableStream} stream - * @returns {boolean} - */ - function isWritableStreamLocked(stream) { - if (stream[_writer] === undefined) { - return false; - } + const desiredSize = readableStreamDefaultControllerGetDesiredSize( + controller, + ); + assert(desiredSize !== null); + if (desiredSize > 0) { return true; } - /** - * @template T - * @param {{ [_queue]: Array>, [_queueTotalSize]: number }} container - * @returns {T | _close} - */ - function peekQueueValue(container) { - assert( - ReflectHas(container, _queue) && - ReflectHas(container, _queueTotalSize), + return false; +} + +/** + * @param {ReadableStreamBYOBReader} reader + * @param {ArrayBufferView} view + * @param {ReadIntoRequest} readIntoRequest + * @returns {void} + */ +function readableStreamBYOBReaderRead(reader, view, readIntoRequest) { + const stream = reader[_stream]; + assert(stream); + stream[_disturbed] = true; + if (stream[_state] === "errored") { + readIntoRequest.errorSteps(stream[_storedError]); + } else { + readableByteStreamControllerPullInto( + stream[_controller], + view, + readIntoRequest, ); - assert(container[_queue].length); - const valueWithSize = container[_queue][0]; - return valueWithSize.value; } - - /** - * @param {ReadableByteStreamController} controller - * @returns {void} - */ - function readableByteStreamControllerCallPullIfNeeded(controller) { - const shouldPull = readableByteStreamControllerShouldCallPull(controller); - if (!shouldPull) { +} + +/** + * @param {ReadableStreamBYOBReader} reader + */ +function readableStreamBYOBReaderRelease(reader) { + readableStreamReaderGenericRelease(reader); + const e = new TypeError("The reader was released."); + readableStreamBYOBReaderErrorReadIntoRequests(reader, e); +} + +/** + * @param {ReadableStreamBYOBReader} reader + * @param {any} e + */ +function readableStreamDefaultReaderErrorReadRequests(reader, e) { + const readRequests = reader[_readRequests]; + reader[_readRequests] = []; + for (let i = 0; i < readRequests.length; ++i) { + const readRequest = readRequests[i]; + readRequest.errorSteps(e); + } +} + +/** + * @param {ReadableByteStreamController} controller + */ +function readableByteStreamControllerProcessPullIntoDescriptorsUsingQueue( + controller, +) { + assert(!controller[_closeRequested]); + while (controller[_pendingPullIntos].length !== 0) { + if (controller[_queueTotalSize] === 0) { return; } - if (controller[_pulling]) { - controller[_pullAgain] = true; + const pullIntoDescriptor = controller[_pendingPullIntos][0]; + if ( + readableByteStreamControllerFillPullIntoDescriptorFromQueue( + controller, + pullIntoDescriptor, + ) + ) { + readableByteStreamControllerShiftPendingPullInto(controller); + readableByteStreamControllerCommitPullIntoDescriptor( + controller[_stream], + pullIntoDescriptor, + ); + } + } +} +/** + * @param {ReadableByteStreamController} controller + */ +function readableByteStreamControllerProcessReadRequestsUsingQueue( + controller, +) { + const reader = controller[_stream][_reader]; + assert(isReadableStreamDefaultReader(reader)); + while (reader[_readRequests].length !== 0) { + if (controller[_queueTotalSize] === 0) { return; } - assert(controller[_pullAgain] === false); - controller[_pulling] = true; - /** @type {Promise} */ - const pullPromise = controller[_pullAlgorithm](controller); - setPromiseIsHandledToTrue( - PromisePrototypeThen( - pullPromise, - () => { - controller[_pulling] = false; - if (controller[_pullAgain]) { - controller[_pullAgain] = false; - readableByteStreamControllerCallPullIfNeeded(controller); - } - }, - (e) => { - readableByteStreamControllerError(controller, e); - }, - ), + const readRequest = ArrayPrototypeShift(reader[_readRequests]); + readableByteStreamControllerFillReadRequestFromQueue( + controller, + readRequest, ); } - - /** - * @param {ReadableByteStreamController} controller - * @returns {void} - */ - function readableByteStreamControllerClearAlgorithms(controller) { - controller[_pullAlgorithm] = undefined; - controller[_cancelAlgorithm] = undefined; +} + +/** + * @param {ReadableByteStreamController} controller + * @param {ArrayBufferView} view + * @param {ReadIntoRequest} readIntoRequest + * @returns {void} + */ +function readableByteStreamControllerPullInto( + controller, + view, + readIntoRequest, +) { + const stream = controller[_stream]; + let elementSize = 1; + let ctor = DataView; + + if ( + ObjectPrototypeIsPrototypeOf(Int8ArrayPrototype, view) || + ObjectPrototypeIsPrototypeOf(Uint8ArrayPrototype, view) || + ObjectPrototypeIsPrototypeOf(Uint8ClampedArrayPrototype, view) || + ObjectPrototypeIsPrototypeOf(Int16ArrayPrototype, view) || + ObjectPrototypeIsPrototypeOf(Uint16ArrayPrototype, view) || + ObjectPrototypeIsPrototypeOf(Int32ArrayPrototype, view) || + ObjectPrototypeIsPrototypeOf(Uint32ArrayPrototype, view) || + ObjectPrototypeIsPrototypeOf(BigInt64ArrayPrototype, view) || + ObjectPrototypeIsPrototypeOf(BigUint64ArrayPrototype, view) + ) { + elementSize = view.constructor.BYTES_PER_ELEMENT; + ctor = view.constructor; } + const byteOffset = view.byteOffset; + const byteLength = view.byteLength; - /** - * @param {ReadableByteStreamController} controller - * @param {any} e - */ - function readableByteStreamControllerError(controller, e) { - /** @type {ReadableStream} */ - const stream = controller[_stream]; - if (stream[_state] !== "readable") { - return; - } - readableByteStreamControllerClearPendingPullIntos(controller); - resetQueue(controller); - readableByteStreamControllerClearAlgorithms(controller); - readableStreamError(stream, e); - } + /** @type {ArrayBufferLike} */ + let buffer; - /** - * @param {ReadableByteStreamController} controller - * @returns {void} - */ - function readableByteStreamControllerClearPendingPullIntos(controller) { - readableByteStreamControllerInvalidateBYOBRequest(controller); - controller[_pendingPullIntos] = []; + try { + buffer = transferArrayBuffer(view.buffer); + } catch (e) { + readIntoRequest.errorSteps(e); + return; } - /** - * @param {ReadableByteStreamController} controller - * @returns {void} - */ - function readableByteStreamControllerClose(controller) { - /** @type {ReadableStream} */ - const stream = controller[_stream]; - if (controller[_closeRequested] || stream[_state] !== "readable") { - return; - } - if (controller[_queueTotalSize] > 0) { - controller[_closeRequested] = true; - return; - } - if (controller[_pendingPullIntos].length !== 0) { - const firstPendingPullInto = controller[_pendingPullIntos][0]; - if (firstPendingPullInto.bytesFilled > 0) { - const e = new TypeError( - "Insufficient bytes to fill elements in the given buffer", - ); - readableByteStreamControllerError(controller, e); - throw e; - } - } - readableByteStreamControllerClearAlgorithms(controller); - readableStreamClose(stream); - } + /** @type {PullIntoDescriptor} */ + const pullIntoDescriptor = { + buffer, + bufferByteLength: buffer.byteLength, + byteOffset, + byteLength, + bytesFilled: 0, + elementSize, + viewConstructor: ctor, + readerType: "byob", + }; - /** - * @param {ReadableByteStreamController} controller - * @param {ArrayBufferView} chunk - */ - function readableByteStreamControllerEnqueue(controller, chunk) { - /** @type {ReadableStream} */ - const stream = controller[_stream]; + if (controller[_pendingPullIntos].length !== 0) { + ArrayPrototypePush(controller[_pendingPullIntos], pullIntoDescriptor); + readableStreamAddReadIntoRequest(stream, readIntoRequest); + return; + } + if (stream[_state] === "closed") { + const emptyView = new ctor( + pullIntoDescriptor.buffer, + pullIntoDescriptor.byteOffset, + 0, + ); + readIntoRequest.closeSteps(emptyView); + return; + } + if (controller[_queueTotalSize] > 0) { if ( - controller[_closeRequested] || - controller[_stream][_state] !== "readable" + readableByteStreamControllerFillPullIntoDescriptorFromQueue( + controller, + pullIntoDescriptor, + ) ) { + const filledView = readableByteStreamControllerConvertPullIntoDescriptor( + pullIntoDescriptor, + ); + readableByteStreamControllerHandleQueueDrain(controller); + readIntoRequest.chunkSteps(filledView); return; } - - const { buffer, byteOffset, byteLength } = chunk; - if (isDetachedBuffer(buffer)) { + if (controller[_closeRequested]) { + const e = new TypeError( + "Insufficient bytes to fill elements in the given buffer", + ); + readableByteStreamControllerError(controller, e); + readIntoRequest.errorSteps(e); + return; + } + } + controller[_pendingPullIntos].push(pullIntoDescriptor); + readableStreamAddReadIntoRequest(stream, readIntoRequest); + readableByteStreamControllerCallPullIfNeeded(controller); +} + +/** + * @param {ReadableByteStreamController} controller + * @param {number} bytesWritten + * @returns {void} + */ +function readableByteStreamControllerRespond(controller, bytesWritten) { + assert(controller[_pendingPullIntos].length !== 0); + const firstDescriptor = controller[_pendingPullIntos][0]; + const state = controller[_stream][_state]; + if (state === "closed") { + if (bytesWritten !== 0) { throw new TypeError( - "chunk's buffer is detached and so cannot be enqueued", + "bytesWritten must be 0 when calling respond() on a closed stream", ); } - const transferredBuffer = transferArrayBuffer(buffer); - if (controller[_pendingPullIntos].length !== 0) { - const firstPendingPullInto = controller[_pendingPullIntos][0]; - if (isDetachedBuffer(firstPendingPullInto.buffer)) { - throw new TypeError( - "The BYOB request's buffer has been detached and so cannot be filled with an enqueued chunk", - ); - } - readableByteStreamControllerInvalidateBYOBRequest(controller); - firstPendingPullInto.buffer = transferArrayBuffer( - firstPendingPullInto.buffer, + } else { + assert(state === "readable"); + if (bytesWritten === 0) { + throw new TypeError( + "bytesWritten must be greater than 0 when calling respond() on a readable stream", ); - if (firstPendingPullInto.readerType === "none") { - readableByteStreamControllerEnqueueDetachedPullIntoToQueue( - controller, - firstPendingPullInto, - ); - } } - if (readableStreamHasDefaultReader(stream)) { - readableByteStreamControllerProcessReadRequestsUsingQueue(controller); - if (readableStreamGetNumReadRequests(stream) === 0) { - assert(controller[_pendingPullIntos].length === 0); - readableByteStreamControllerEnqueueChunkToQueue( - controller, - transferredBuffer, - byteOffset, - byteLength, - ); - } else { - assert(controller[_queue].length === 0); - if (controller[_pendingPullIntos].length !== 0) { - assert(controller[_pendingPullIntos][0].readerType === "default"); - readableByteStreamControllerShiftPendingPullInto(controller); - } - const transferredView = new Uint8Array( - transferredBuffer, - byteOffset, - byteLength, - ); - readableStreamFulfillReadRequest(stream, transferredView, false); - } - } else if (readableStreamHasBYOBReader(stream)) { - readableByteStreamControllerEnqueueChunkToQueue( - controller, - transferredBuffer, - byteOffset, - byteLength, + if ( + (firstDescriptor.bytesFilled + bytesWritten) > + firstDescriptor.byteLength + ) { + throw new RangeError("bytesWritten out of range"); + } + } + firstDescriptor.buffer = transferArrayBuffer(firstDescriptor.buffer); + readableByteStreamControllerRespondInternal(controller, bytesWritten); +} + +/** + * @param {ReadableByteStreamController} controller + * @param {number} bytesWritten + * @param {PullIntoDescriptor} pullIntoDescriptor + * @returns {void} + */ +function readableByteStreamControllerRespondInReadableState( + controller, + bytesWritten, + pullIntoDescriptor, +) { + assert( + (pullIntoDescriptor.bytesFilled + bytesWritten) <= + pullIntoDescriptor.byteLength, + ); + readableByteStreamControllerFillHeadPullIntoDescriptor( + controller, + bytesWritten, + pullIntoDescriptor, + ); + if (pullIntoDescriptor.readerType === "none") { + readableByteStreamControllerEnqueueDetachedPullIntoToQueue( + controller, + pullIntoDescriptor, + ); + readableByteStreamControllerProcessPullIntoDescriptorsUsingQueue( + controller, + ); + return; + } + if (pullIntoDescriptor.bytesFilled < pullIntoDescriptor.elementSize) { + return; + } + readableByteStreamControllerShiftPendingPullInto(controller); + const remainderSize = pullIntoDescriptor.bytesFilled % + pullIntoDescriptor.elementSize; + if (remainderSize > 0) { + const end = pullIntoDescriptor.byteOffset + + pullIntoDescriptor.bytesFilled; + readableByteStreamControllerEnqueueClonedChunkToQueue( + controller, + pullIntoDescriptor.buffer, + end - remainderSize, + remainderSize, + ); + } + pullIntoDescriptor.bytesFilled -= remainderSize; + readableByteStreamControllerCommitPullIntoDescriptor( + controller[_stream], + pullIntoDescriptor, + ); + readableByteStreamControllerProcessPullIntoDescriptorsUsingQueue( + controller, + ); +} + +/** + * @param {ReadableByteStreamController} controller + * @param {number} bytesWritten + * @returns {void} + */ +function readableByteStreamControllerRespondInternal( + controller, + bytesWritten, +) { + const firstDescriptor = controller[_pendingPullIntos][0]; + assert(canTransferArrayBuffer(firstDescriptor.buffer)); + readableByteStreamControllerInvalidateBYOBRequest(controller); + const state = controller[_stream][_state]; + if (state === "closed") { + assert(bytesWritten === 0); + readableByteStreamControllerRespondInClosedState( + controller, + firstDescriptor, + ); + } else { + assert(state === "readable"); + assert(bytesWritten > 0); + readableByteStreamControllerRespondInReadableState( + controller, + bytesWritten, + firstDescriptor, + ); + } + readableByteStreamControllerCallPullIfNeeded(controller); +} + +/** + * @param {ReadableByteStreamController} controller + */ +function readableByteStreamControllerInvalidateBYOBRequest(controller) { + if (controller[_byobRequest] === null) { + return; + } + controller[_byobRequest][_controller] = undefined; + controller[_byobRequest][_view] = null; + controller[_byobRequest] = null; +} + +/** + * @param {ReadableByteStreamController} controller + * @param {PullIntoDescriptor} firstDescriptor + */ +function readableByteStreamControllerRespondInClosedState( + controller, + firstDescriptor, +) { + assert(firstDescriptor.bytesFilled === 0); + if (firstDescriptor.readerType === "none") { + readableByteStreamControllerShiftPendingPullInto(controller); + } + const stream = controller[_stream]; + if (readableStreamHasBYOBReader(stream)) { + while (readableStreamGetNumReadIntoRequests(stream) > 0) { + const pullIntoDescriptor = + readableByteStreamControllerShiftPendingPullInto(controller); + readableByteStreamControllerCommitPullIntoDescriptor( + stream, + pullIntoDescriptor, ); - readableByteStreamControllerProcessPullIntoDescriptorsUsingQueue( - controller, + } + } +} + +/** + * @template R + * @param {ReadableStream} stream + * @param {PullIntoDescriptor} pullIntoDescriptor + */ +function readableByteStreamControllerCommitPullIntoDescriptor( + stream, + pullIntoDescriptor, +) { + assert(stream[_state] !== "errored"); + assert(pullIntoDescriptor.readerType !== "none"); + let done = false; + if (stream[_state] === "closed") { + assert(pullIntoDescriptor.bytesFilled === 0); + done = true; + } + const filledView = readableByteStreamControllerConvertPullIntoDescriptor( + pullIntoDescriptor, + ); + if (pullIntoDescriptor.readerType === "default") { + readableStreamFulfillReadRequest(stream, filledView, done); + } else { + assert(pullIntoDescriptor.readerType === "byob"); + readableStreamFulfillReadIntoRequest(stream, filledView, done); + } +} + +/** + * @param {ReadableByteStreamController} controller + * @param {ArrayBufferView} view + */ +function readableByteStreamControllerRespondWithNewView(controller, view) { + assert(controller[_pendingPullIntos].length !== 0); + assert(!isDetachedBuffer(view.buffer)); + const firstDescriptor = controller[_pendingPullIntos][0]; + const state = controller[_stream][_state]; + if (state === "closed") { + if (view.byteLength !== 0) { + throw new TypeError( + "The view's length must be 0 when calling respondWithNewView() on a closed stream", ); - } else { - assert(isReadableStreamLocked(stream) === false); - readableByteStreamControllerEnqueueChunkToQueue( - controller, - transferredBuffer, - byteOffset, - byteLength, + } + } else { + assert(state === "readable"); + if (view.byteLength === 0) { + throw new TypeError( + "The view's length must be greater than 0 when calling respondWithNewView() on a readable stream", ); } - readableByteStreamControllerCallPullIfNeeded(controller); } - - /** - * @param {ReadableByteStreamController} controller - * @param {ArrayBufferLike} buffer - * @param {number} byteOffset - * @param {number} byteLength - * @returns {void} - */ - function readableByteStreamControllerEnqueueChunkToQueue( - controller, - buffer, - byteOffset, - byteLength, + if ( + (firstDescriptor.byteOffset + firstDescriptor.bytesFilled) !== + view.byteOffset ) { - ArrayPrototypePush(controller[_queue], { buffer, byteOffset, byteLength }); - controller[_queueTotalSize] += byteLength; + throw new RangeError( + "The region specified by view does not match byobRequest", + ); } - - /** - * @param {ReadableByteStreamController} controller - * @param {ArrayBufferLike} buffer - * @param {number} byteOffset - * @param {number} byteLength - * @returns {void} - */ - function readableByteStreamControllerEnqueueClonedChunkToQueue( - controller, - buffer, - byteOffset, - byteLength, + if (firstDescriptor.bufferByteLength !== view.buffer.byteLength) { + throw new RangeError( + "The buffer of view has different capacity than byobRequest", + ); + } + if ( + (firstDescriptor.bytesFilled + view.byteLength) > + firstDescriptor.byteLength ) { - let cloneResult; - try { - cloneResult = buffer.slice(byteOffset, byteOffset + byteLength); - } catch (e) { - readableByteStreamControllerError(controller, e); + throw new RangeError( + "The region specified by view is larger than byobRequest", + ); + } + const viewByteLength = view.byteLength; + firstDescriptor.buffer = transferArrayBuffer(view.buffer); + readableByteStreamControllerRespondInternal(controller, viewByteLength); +} + +/** + * @param {ReadableByteStreamController} controller + * @returns {PullIntoDescriptor} + */ +function readableByteStreamControllerShiftPendingPullInto(controller) { + assert(controller[_byobRequest] === null); + return ArrayPrototypeShift(controller[_pendingPullIntos]); +} + +/** + * @param {ReadableByteStreamController} controller + * @param {PullIntoDescriptor} pullIntoDescriptor + * @returns {boolean} + */ +function readableByteStreamControllerFillPullIntoDescriptorFromQueue( + controller, + pullIntoDescriptor, +) { + const elementSize = pullIntoDescriptor.elementSize; + const currentAlignedBytes = pullIntoDescriptor.bytesFilled - + (pullIntoDescriptor.bytesFilled % elementSize); + const maxBytesToCopy = MathMin( + controller[_queueTotalSize], + pullIntoDescriptor.byteLength - pullIntoDescriptor.bytesFilled, + ); + const maxBytesFilled = pullIntoDescriptor.bytesFilled + maxBytesToCopy; + const maxAlignedBytes = maxBytesFilled - (maxBytesFilled % elementSize); + let totalBytesToCopyRemaining = maxBytesToCopy; + let ready = false; + if (maxAlignedBytes > currentAlignedBytes) { + totalBytesToCopyRemaining = maxAlignedBytes - + pullIntoDescriptor.bytesFilled; + ready = true; + } + const queue = controller[_queue]; + while (totalBytesToCopyRemaining > 0) { + const headOfQueue = queue[0]; + const bytesToCopy = MathMin( + totalBytesToCopyRemaining, + headOfQueue.byteLength, + ); + const destStart = pullIntoDescriptor.byteOffset + + pullIntoDescriptor.bytesFilled; + + const destBuffer = new Uint8Array( + pullIntoDescriptor.buffer, + destStart, + bytesToCopy, + ); + const srcBuffer = new Uint8Array( + headOfQueue.buffer, + headOfQueue.byteOffset, + bytesToCopy, + ); + destBuffer.set(srcBuffer); + + if (headOfQueue.byteLength === bytesToCopy) { + ArrayPrototypeShift(queue); + } else { + headOfQueue.byteOffset += bytesToCopy; + headOfQueue.byteLength -= bytesToCopy; } - readableByteStreamControllerEnqueueChunkToQueue( + controller[_queueTotalSize] -= bytesToCopy; + readableByteStreamControllerFillHeadPullIntoDescriptor( controller, - cloneResult, - 0, - byteLength, + bytesToCopy, + pullIntoDescriptor, ); + totalBytesToCopyRemaining -= bytesToCopy; + } + if (!ready) { + assert(controller[_queueTotalSize] === 0); + assert(pullIntoDescriptor.bytesFilled > 0); + assert(pullIntoDescriptor.bytesFilled < pullIntoDescriptor.elementSize); + } + return ready; +} + +/** + * @param {ReadableByteStreamController} controller + * @param {ReadRequest} readRequest + * @returns {void} + */ +function readableByteStreamControllerFillReadRequestFromQueue( + controller, + readRequest, +) { + assert(controller[_queueTotalSize] > 0); + const entry = ArrayPrototypeShift(controller[_queue]); + controller[_queueTotalSize] -= entry.byteLength; + readableByteStreamControllerHandleQueueDrain(controller); + const view = new Uint8Array( + entry.buffer, + entry.byteOffset, + entry.byteLength, + ); + readRequest.chunkSteps(view); +} + +/** + * @param {ReadableByteStreamController} controller + * @param {number} size + * @param {PullIntoDescriptor} pullIntoDescriptor + * @returns {void} + */ +function readableByteStreamControllerFillHeadPullIntoDescriptor( + controller, + size, + pullIntoDescriptor, +) { + assert( + controller[_pendingPullIntos].length === 0 || + controller[_pendingPullIntos][0] === pullIntoDescriptor, + ); + assert(controller[_byobRequest] === null); + pullIntoDescriptor.bytesFilled += size; +} + +/** + * @param {PullIntoDescriptor} pullIntoDescriptor + * @returns {ArrayBufferView} + */ +function readableByteStreamControllerConvertPullIntoDescriptor( + pullIntoDescriptor, +) { + const bytesFilled = pullIntoDescriptor.bytesFilled; + const elementSize = pullIntoDescriptor.elementSize; + assert(bytesFilled <= pullIntoDescriptor.byteLength); + assert((bytesFilled % elementSize) === 0); + const buffer = transferArrayBuffer(pullIntoDescriptor.buffer); + return new pullIntoDescriptor.viewConstructor( + buffer, + pullIntoDescriptor.byteOffset, + bytesFilled / elementSize, + ); +} + +/** + * @template R + * @param {ReadableStreamDefaultReader} reader + * @param {ReadRequest} readRequest + * @returns {void} + */ +function readableStreamDefaultReaderRead(reader, readRequest) { + const stream = reader[_stream]; + assert(stream); + stream[_disturbed] = true; + if (stream[_state] === "closed") { + readRequest.closeSteps(); + } else if (stream[_state] === "errored") { + readRequest.errorSteps(stream[_storedError]); + } else { + assert(stream[_state] === "readable"); + stream[_controller][_pullSteps](readRequest); + } +} + +/** + * @template R + * @param {ReadableStreamDefaultReader} reader + */ +function readableStreamDefaultReaderRelease(reader) { + readableStreamReaderGenericRelease(reader); + const e = new TypeError("The reader was released."); + readableStreamDefaultReaderErrorReadRequests(reader, e); +} + +/** + * @template R + * @param {ReadableStream} stream + * @param {any} e + */ +function readableStreamError(stream, e) { + assert(stream[_state] === "readable"); + stream[_state] = "errored"; + stream[_storedError] = e; + /** @type {ReadableStreamDefaultReader | undefined} */ + const reader = stream[_reader]; + if (reader === undefined) { + return; + } + /** @type {Deferred} */ + const closedPromise = reader[_closedPromise]; + closedPromise.reject(e); + setPromiseIsHandledToTrue(closedPromise.promise); + if (isReadableStreamDefaultReader(reader)) { + readableStreamDefaultReaderErrorReadRequests(reader, e); + } else { + assert(isReadableStreamBYOBReader(reader)); + readableStreamBYOBReaderErrorReadIntoRequests(reader, e); + } +} + +/** + * @template R + * @param {ReadableStream} stream + * @param {R} chunk + * @param {boolean} done + */ +function readableStreamFulfillReadIntoRequest(stream, chunk, done) { + assert(readableStreamHasBYOBReader(stream)); + /** @type {ReadableStreamDefaultReader} */ + const reader = stream[_reader]; + assert(reader[_readIntoRequests].length !== 0); + /** @type {ReadIntoRequest} */ + const readIntoRequest = ArrayPrototypeShift(reader[_readIntoRequests]); + if (done) { + readIntoRequest.closeSteps(chunk); + } else { + readIntoRequest.chunkSteps(chunk); + } +} + +/** + * @template R + * @param {ReadableStream} stream + * @param {R} chunk + * @param {boolean} done + */ +function readableStreamFulfillReadRequest(stream, chunk, done) { + assert(readableStreamHasDefaultReader(stream) === true); + /** @type {ReadableStreamDefaultReader} */ + const reader = stream[_reader]; + assert(reader[_readRequests].length); + /** @type {ReadRequest} */ + const readRequest = ArrayPrototypeShift(reader[_readRequests]); + if (done) { + readRequest.closeSteps(); + } else { + readRequest.chunkSteps(chunk); + } +} + +/** + * @param {ReadableStream} stream + * @return {number} + */ +function readableStreamGetNumReadIntoRequests(stream) { + assert(readableStreamHasBYOBReader(stream) === true); + return stream[_reader][_readIntoRequests].length; +} + +/** + * @param {ReadableStream} stream + * @return {number} + */ +function readableStreamGetNumReadRequests(stream) { + assert(readableStreamHasDefaultReader(stream) === true); + return stream[_reader][_readRequests].length; +} + +/** + * @param {ReadableStream} stream + * @returns {boolean} + */ +function readableStreamHasBYOBReader(stream) { + const reader = stream[_reader]; + if (reader === undefined) { + return false; } + if (isReadableStreamBYOBReader(reader)) { + return true; + } + return false; +} - /** - * @param {ReadableByteStreamController} controller - * @param {PullIntoDescriptor} pullIntoDescriptor - * @returns {void} - */ - function readableByteStreamControllerEnqueueDetachedPullIntoToQueue( - controller, - pullIntoDescriptor, - ) { - assert(pullIntoDescriptor.readerType === "none"); - if (pullIntoDescriptor.bytesFilled > 0) { - readableByteStreamControllerEnqueueClonedChunkToQueue( - controller, - pullIntoDescriptor.buffer, - pullIntoDescriptor.byteOffset, - pullIntoDescriptor.bytesFilled, +/** + * @param {ReadableStream} stream + * @returns {boolean} + */ +function readableStreamHasDefaultReader(stream) { + const reader = stream[_reader]; + if (reader === undefined) { + return false; + } + if (isReadableStreamDefaultReader(reader)) { + return true; + } + return false; +} + +/** + * @template T + * @param {ReadableStream} source + * @param {WritableStream} dest + * @param {boolean} preventClose + * @param {boolean} preventAbort + * @param {boolean} preventCancel + * @param {AbortSignal=} signal + * @returns {Promise} + */ +function readableStreamPipeTo( + source, + dest, + preventClose, + preventAbort, + preventCancel, + signal, +) { + assert(isReadableStream(source)); + assert(isWritableStream(dest)); + assert( + typeof preventClose === "boolean" && typeof preventAbort === "boolean" && + typeof preventCancel === "boolean", + ); + assert( + signal === undefined || + ObjectPrototypeIsPrototypeOf(AbortSignalPrototype, signal), + ); + assert(!isReadableStreamLocked(source)); + assert(!isWritableStreamLocked(dest)); + // We use acquireReadableStreamDefaultReader even in case of ReadableByteStreamController + // as the spec allows us, and the only reason to use BYOBReader is to do some smart things + // with it, but the spec does not specify what things, so to simplify we stick to DefaultReader. + const reader = acquireReadableStreamDefaultReader(source); + const writer = acquireWritableStreamDefaultWriter(dest); + source[_disturbed] = true; + let shuttingDown = false; + let currentWrite = resolvePromiseWith(undefined); + /** @type {Deferred} */ + const promise = new Deferred(); + /** @type {() => void} */ + let abortAlgorithm; + if (signal) { + abortAlgorithm = () => { + const error = signal.reason; + /** @type {Array<() => Promise>} */ + const actions = []; + if (preventAbort === false) { + ArrayPrototypePush(actions, () => { + if (dest[_state] === "writable") { + return writableStreamAbort(dest, error); + } else { + return resolvePromiseWith(undefined); + } + }); + } + if (preventCancel === false) { + ArrayPrototypePush(actions, () => { + if (source[_state] === "readable") { + return readableStreamCancel(source, error); + } else { + return resolvePromiseWith(undefined); + } + }); + } + shutdownWithAction( + () => SafePromiseAll(ArrayPrototypeMap(actions, (action) => action())), + true, + error, ); + }; + + if (signal.aborted) { + abortAlgorithm(); + return promise.promise; } - readableByteStreamControllerShiftPendingPullInto(controller); + signal[add](abortAlgorithm); } - /** - * @param {ReadableByteStreamController} controller - * @returns {ReadableStreamBYOBRequest | null} - */ - function readableByteStreamControllerGetBYOBRequest(controller) { - if ( - controller[_byobRequest] === null && - controller[_pendingPullIntos].length !== 0 - ) { - const firstDescriptor = controller[_pendingPullIntos][0]; - const view = new Uint8Array( - firstDescriptor.buffer, - firstDescriptor.byteOffset + firstDescriptor.bytesFilled, - firstDescriptor.byteLength - firstDescriptor.bytesFilled, - ); - const byobRequest = webidl.createBranded(ReadableStreamBYOBRequest); - byobRequest[_controller] = controller; - byobRequest[_view] = view; - controller[_byobRequest] = byobRequest; + function pipeLoop() { + return new Promise((resolveLoop, rejectLoop) => { + /** @param {boolean} done */ + function next(done) { + if (done) { + resolveLoop(); + } else { + uponPromise(pipeStep(), next, rejectLoop); + } + } + next(false); + }); + } + + /** @returns {Promise} */ + function pipeStep() { + if (shuttingDown === true) { + return resolvePromiseWith(true); } - return controller[_byobRequest]; + + return transformPromiseWith(writer[_readyPromise].promise, () => { + return new Promise((resolveRead, rejectRead) => { + readableStreamDefaultReaderRead( + reader, + { + chunkSteps(chunk) { + currentWrite = transformPromiseWith( + writableStreamDefaultWriterWrite(writer, chunk), + undefined, + () => {}, + ); + resolveRead(false); + }, + closeSteps() { + resolveRead(true); + }, + errorSteps: rejectRead, + }, + ); + }); + }); } - /** - * @param {ReadableByteStreamController} controller - * @returns {number | null} - */ - function readableByteStreamControllerGetDesiredSize(controller) { - const state = controller[_stream][_state]; - if (state === "errored") { - return null; + isOrBecomesErrored( + source, + reader[_closedPromise].promise, + (storedError) => { + if (preventAbort === false) { + shutdownWithAction( + () => writableStreamAbort(dest, storedError), + true, + storedError, + ); + } else { + shutdown(true, storedError); + } + }, + ); + + isOrBecomesErrored(dest, writer[_closedPromise].promise, (storedError) => { + if (preventCancel === false) { + shutdownWithAction( + () => readableStreamCancel(source, storedError), + true, + storedError, + ); + } else { + shutdown(true, storedError); + } + }); + + isOrBecomesClosed(source, reader[_closedPromise].promise, () => { + if (preventClose === false) { + shutdownWithAction(() => + writableStreamDefaultWriterCloseWithErrorPropagation(writer) + ); + } else { + shutdown(); } - if (state === "closed") { - return 0; + }); + + if ( + writableStreamCloseQueuedOrInFlight(dest) === true || + dest[_state] === "closed" + ) { + const destClosed = new TypeError( + "The destination writable stream closed before all the data could be piped to it.", + ); + if (preventCancel === false) { + shutdownWithAction( + () => readableStreamCancel(source, destClosed), + true, + destClosed, + ); + } else { + shutdown(true, destClosed); } - return controller[_strategyHWM] - controller[_queueTotalSize]; + } + + setPromiseIsHandledToTrue(pipeLoop()); + + return promise.promise; + + /** @returns {Promise} */ + function waitForWritesToFinish() { + const oldCurrentWrite = currentWrite; + return transformPromiseWith( + currentWrite, + () => + oldCurrentWrite !== currentWrite ? waitForWritesToFinish() : undefined, + ); } /** - * @param {{ [_queue]: any[], [_queueTotalSize]: number }} container - * @returns {void} + * @param {ReadableStream | WritableStream} stream + * @param {Promise} promise + * @param {(e: any) => void} action */ - function resetQueue(container) { - container[_queue] = []; - container[_queueTotalSize] = 0; + function isOrBecomesErrored(stream, promise, action) { + if (stream[_state] === "errored") { + action(stream[_storedError]); + } else { + uponRejection(promise, action); + } } /** - * @param {ReadableByteStreamController} controller - * @returns {void} + * @param {ReadableStream} stream + * @param {Promise} promise + * @param {() => void} action */ - function readableByteStreamControllerHandleQueueDrain(controller) { - assert(controller[_stream][_state] === "readable"); - if ( - controller[_queueTotalSize] === 0 && controller[_closeRequested] - ) { - readableByteStreamControllerClearAlgorithms(controller); - readableStreamClose(controller[_stream]); + function isOrBecomesClosed(stream, promise, action) { + if (stream[_state] === "closed") { + action(); } else { - readableByteStreamControllerCallPullIfNeeded(controller); + uponFulfillment(promise, action); } } /** - * @param {ReadableByteStreamController} controller - * @returns {boolean} + * @param {() => Promise} action + * @param {boolean=} originalIsError + * @param {any=} originalError */ - function readableByteStreamControllerShouldCallPull(controller) { - /** @type {ReadableStream} */ - const stream = controller[_stream]; - if ( - stream[_state] !== "readable" || - controller[_closeRequested] || - !controller[_started] - ) { - return false; + function shutdownWithAction(action, originalIsError, originalError) { + function doTheRest() { + uponPromise( + action(), + () => finalize(originalIsError, originalError), + (newError) => finalize(true, newError), + ); } - if ( - readableStreamHasDefaultReader(stream) && - readableStreamGetNumReadRequests(stream) > 0 - ) { - return true; + + if (shuttingDown === true) { + return; } + shuttingDown = true; + if ( - readableStreamHasBYOBReader(stream) && - readableStreamGetNumReadIntoRequests(stream) > 0 + dest[_state] === "writable" && + writableStreamCloseQueuedOrInFlight(dest) === false ) { - return true; + uponFulfillment(waitForWritesToFinish(), doTheRest); + } else { + doTheRest(); } - const desiredSize = readableByteStreamControllerGetDesiredSize(controller); - assert(desiredSize !== null); - return desiredSize > 0; } /** - * @template R - * @param {ReadableStream} stream - * @param {ReadRequest} readRequest - * @returns {void} + * @param {boolean=} isError + * @param {any=} error */ - function readableStreamAddReadRequest(stream, readRequest) { - assert(isReadableStreamDefaultReader(stream[_reader])); - assert(stream[_state] === "readable"); - ArrayPrototypePush(stream[_reader][_readRequests], readRequest); + function shutdown(isError, error) { + if (shuttingDown) { + return; + } + shuttingDown = true; + if ( + dest[_state] === "writable" && + writableStreamCloseQueuedOrInFlight(dest) === false + ) { + uponFulfillment( + waitForWritesToFinish(), + () => finalize(isError, error), + ); + } else { + finalize(isError, error); + } } /** - * @param {ReadableStream} stream - * @param {ReadIntoRequest} readRequest - * @returns {void} + * @param {boolean=} isError + * @param {any=} error */ - function readableStreamAddReadIntoRequest(stream, readRequest) { - assert(isReadableStreamBYOBReader(stream[_reader])); - assert(stream[_state] === "readable" || stream[_state] === "closed"); - ArrayPrototypePush(stream[_reader][_readIntoRequests], readRequest); - } + function finalize(isError, error) { + writableStreamDefaultWriterRelease(writer); + readableStreamDefaultReaderRelease(reader); - /** - * @template R - * @param {ReadableStream} stream - * @param {any=} reason - * @returns {Promise} - */ - function readableStreamCancel(stream, reason) { - stream[_disturbed] = true; - if (stream[_state] === "closed") { - return resolvePromiseWith(undefined); + if (signal !== undefined) { + signal[remove](abortAlgorithm); } - if (stream[_state] === "errored") { - return PromiseReject(stream[_storedError]); - } - readableStreamClose(stream); - const reader = stream[_reader]; - if (reader !== undefined && isReadableStreamBYOBReader(reader)) { - const readIntoRequests = reader[_readIntoRequests]; - reader[_readIntoRequests] = []; - for (let i = 0; i < readIntoRequests.length; ++i) { - const readIntoRequest = readIntoRequests[i]; - readIntoRequest.closeSteps(undefined); - } + if (isError) { + promise.reject(error); + } else { + promise.resolve(undefined); + } + } +} + +/** + * @param {ReadableStreamGenericReader | ReadableStreamBYOBReader} reader + * @param {any} reason + * @returns {Promise} + */ +function readableStreamReaderGenericCancel(reader, reason) { + const stream = reader[_stream]; + assert(stream !== undefined); + return readableStreamCancel(stream, reason); +} + +/** + * @template R + * @param {ReadableStreamDefaultReader | ReadableStreamBYOBReader} reader + * @param {ReadableStream} stream + */ +function readableStreamReaderGenericInitialize(reader, stream) { + reader[_stream] = stream; + stream[_reader] = reader; + if (stream[_state] === "readable") { + reader[_closedPromise] = new Deferred(); + } else if (stream[_state] === "closed") { + reader[_closedPromise] = new Deferred(); + reader[_closedPromise].resolve(undefined); + } else { + assert(stream[_state] === "errored"); + reader[_closedPromise] = new Deferred(); + reader[_closedPromise].reject(stream[_storedError]); + setPromiseIsHandledToTrue(reader[_closedPromise].promise); + } +} + +/** + * @template R + * @param {ReadableStreamGenericReader | ReadableStreamBYOBReader} reader + */ +function readableStreamReaderGenericRelease(reader) { + const stream = reader[_stream]; + assert(stream !== undefined); + assert(stream[_reader] === reader); + if (stream[_state] === "readable") { + reader[_closedPromise].reject( + new TypeError( + "Reader was released and can no longer be used to monitor the stream's closedness.", + ), + ); + } else { + reader[_closedPromise] = new Deferred(); + reader[_closedPromise].reject( + new TypeError( + "Reader was released and can no longer be used to monitor the stream's closedness.", + ), + ); + } + setPromiseIsHandledToTrue(reader[_closedPromise].promise); + stream[_controller][_releaseSteps](); + stream[_reader] = undefined; + reader[_stream] = undefined; +} + +/** + * @param {ReadableStreamBYOBReader} reader + * @param {any} e + */ +function readableStreamBYOBReaderErrorReadIntoRequests(reader, e) { + const readIntoRequests = reader[_readIntoRequests]; + reader[_readIntoRequests] = []; + for (let i = 0; i < readIntoRequests.length; ++i) { + const readIntoRequest = readIntoRequests[i]; + readIntoRequest.errorSteps(e); + } +} + +/** + * @template R + * @param {ReadableStream} stream + * @param {boolean} cloneForBranch2 + * @returns {[ReadableStream, ReadableStream]} + */ +function readableStreamTee(stream, cloneForBranch2) { + assert(isReadableStream(stream)); + assert(typeof cloneForBranch2 === "boolean"); + if ( + ObjectPrototypeIsPrototypeOf( + ReadableByteStreamControllerPrototype, + stream[_controller], + ) + ) { + return readableByteStreamTee(stream); + } else { + return readableStreamDefaultTee(stream, cloneForBranch2); + } +} + +/** + * @template R + * @param {ReadableStream} stream + * @param {boolean} cloneForBranch2 + * @returns {[ReadableStream, ReadableStream]} + */ +function readableStreamDefaultTee(stream, cloneForBranch2) { + assert(isReadableStream(stream)); + assert(typeof cloneForBranch2 === "boolean"); + const reader = acquireReadableStreamDefaultReader(stream); + let reading = false; + let readAgain = false; + let canceled1 = false; + let canceled2 = false; + /** @type {any} */ + let reason1; + /** @type {any} */ + let reason2; + /** @type {ReadableStream} */ + // deno-lint-ignore prefer-const + let branch1; + /** @type {ReadableStream} */ + // deno-lint-ignore prefer-const + let branch2; + + /** @type {Deferred} */ + const cancelPromise = new Deferred(); + + function pullAlgorithm() { + if (reading === true) { + readAgain = true; + return resolvePromiseWith(undefined); } - /** @type {Promise} */ - const sourceCancelPromise = stream[_controller][_cancelSteps](reason); - return PromisePrototypeThen(sourceCancelPromise, () => undefined); + reading = true; + /** @type {ReadRequest} */ + const readRequest = { + chunkSteps(value) { + queueMicrotask(() => { + readAgain = false; + const value1 = value; + const value2 = value; + + // TODO(lucacasonato): respect clonedForBranch2. + + if (canceled1 === false) { + readableStreamDefaultControllerEnqueue( + /** @type {ReadableStreamDefaultController} */ branch1[ + _controller + ], + value1, + ); + } + if (canceled2 === false) { + readableStreamDefaultControllerEnqueue( + /** @type {ReadableStreamDefaultController} */ branch2[ + _controller + ], + value2, + ); + } + + reading = false; + if (readAgain === true) { + pullAlgorithm(); + } + }); + }, + closeSteps() { + reading = false; + if (canceled1 === false) { + readableStreamDefaultControllerClose( + /** @type {ReadableStreamDefaultController} */ branch1[ + _controller + ], + ); + } + if (canceled2 === false) { + readableStreamDefaultControllerClose( + /** @type {ReadableStreamDefaultController} */ branch2[ + _controller + ], + ); + } + if (canceled1 === false || canceled2 === false) { + cancelPromise.resolve(undefined); + } + }, + errorSteps() { + reading = false; + }, + }; + readableStreamDefaultReaderRead(reader, readRequest); + return resolvePromiseWith(undefined); } /** - * @template R - * @param {ReadableStream} stream - * @returns {void} + * @param {any} reason + * @returns {Promise} */ - function readableStreamClose(stream) { - assert(stream[_state] === "readable"); - stream[_state] = "closed"; - /** @type {ReadableStreamDefaultReader | undefined} */ - const reader = stream[_reader]; - if (!reader) { - return; - } - if (isReadableStreamDefaultReader(reader)) { - /** @type {Array>} */ - const readRequests = reader[_readRequests]; - reader[_readRequests] = []; - for (let i = 0; i < readRequests.length; ++i) { - const readRequest = readRequests[i]; - readRequest.closeSteps(); - } + function cancel1Algorithm(reason) { + canceled1 = true; + reason1 = reason; + if (canceled2 === true) { + const compositeReason = [reason1, reason2]; + const cancelResult = readableStreamCancel(stream, compositeReason); + cancelPromise.resolve(cancelResult); } - // This promise can be double resolved. - // See: https://github.com/whatwg/streams/issues/1100 - reader[_closedPromise].resolve(undefined); + return cancelPromise.promise; } /** - * @template R - * @param {ReadableStream} stream - * @returns {void} + * @param {any} reason + * @returns {Promise} */ - function readableStreamDisturb(stream) { - stream[_disturbed] = true; + function cancel2Algorithm(reason) { + canceled2 = true; + reason2 = reason; + if (canceled1 === true) { + const compositeReason = [reason1, reason2]; + const cancelResult = readableStreamCancel(stream, compositeReason); + cancelPromise.resolve(cancelResult); + } + return cancelPromise.promise; } - /** @param {ReadableStreamDefaultController} controller */ - function readableStreamDefaultControllerCallPullIfNeeded(controller) { - const shouldPull = readableStreamDefaultcontrollerShouldCallPull( - controller, + function startAlgorithm() {} + + branch1 = createReadableStream( + startAlgorithm, + pullAlgorithm, + cancel1Algorithm, + ); + branch2 = createReadableStream( + startAlgorithm, + pullAlgorithm, + cancel2Algorithm, + ); + + uponRejection(reader[_closedPromise].promise, (r) => { + readableStreamDefaultControllerError( + /** @type {ReadableStreamDefaultController} */ branch1[ + _controller + ], + r, ); - if (shouldPull === false) { - return; - } - if (controller[_pulling] === true) { - controller[_pullAgain] = true; - return; + readableStreamDefaultControllerError( + /** @type {ReadableStreamDefaultController} */ branch2[ + _controller + ], + r, + ); + if (canceled1 === false || canceled2 === false) { + cancelPromise.resolve(undefined); } - assert(controller[_pullAgain] === false); - controller[_pulling] = true; - const pullPromise = controller[_pullAlgorithm](controller); - uponFulfillment(pullPromise, () => { - controller[_pulling] = false; - if (controller[_pullAgain] === true) { - controller[_pullAgain] = false; - readableStreamDefaultControllerCallPullIfNeeded(controller); + }); + + return [branch1, branch2]; +} + +/** + * @template R + * @param {ReadableStream} stream + * @returns {[ReadableStream, ReadableStream]} + */ +function readableByteStreamTee(stream) { + assert(isReadableStream(stream)); + assert( + ObjectPrototypeIsPrototypeOf( + ReadableByteStreamControllerPrototype, + stream[_controller], + ), + ); + let reader = acquireReadableStreamDefaultReader(stream); + let reading = false; + let readAgainForBranch1 = false; + let readAgainForBranch2 = false; + let canceled1 = false; + let canceled2 = false; + let reason1 = undefined; + let reason2 = undefined; + let branch1 = undefined; + let branch2 = undefined; + /** @type {Deferred} */ + const cancelPromise = new Deferred(); + + /** + * @param {ReadableStreamBYOBReader} thisReader + */ + function forwardReaderError(thisReader) { + PromisePrototypeCatch(thisReader[_closedPromise].promise, (e) => { + if (thisReader !== reader) { + return; + } + readableByteStreamControllerError(branch1[_controller], e); + readableByteStreamControllerError(branch2[_controller], e); + if (!canceled1 || !canceled2) { + cancelPromise.resolve(undefined); } - }); - uponRejection(pullPromise, (e) => { - readableStreamDefaultControllerError(controller, e); }); } - /** - * @param {ReadableStreamDefaultController} controller - * @returns {boolean} - */ - function readableStreamDefaultControllerCanCloseOrEnqueue(controller) { - const state = controller[_stream][_state]; - if (controller[_closeRequested] === false && state === "readable") { - return true; - } else { - return false; - } + function pullWithDefaultReader() { + if (isReadableStreamBYOBReader(reader)) { + assert(reader[_readIntoRequests].length === 0); + readableStreamBYOBReaderRelease(reader); + reader = acquireReadableStreamDefaultReader(stream); + forwardReaderError(reader); + } + + /** @type {ReadRequest} */ + const readRequest = { + chunkSteps(chunk) { + queueMicrotask(() => { + readAgainForBranch1 = false; + readAgainForBranch2 = false; + const chunk1 = chunk; + let chunk2 = chunk; + if (!canceled1 && !canceled2) { + try { + chunk2 = cloneAsUint8Array(chunk); + } catch (e) { + readableByteStreamControllerError(branch1[_controller], e); + readableByteStreamControllerError(branch2[_controller], e); + cancelPromise.resolve(readableStreamCancel(stream, e)); + return; + } + } + if (!canceled1) { + readableByteStreamControllerEnqueue(branch1[_controller], chunk1); + } + if (!canceled2) { + readableByteStreamControllerEnqueue(branch2[_controller], chunk2); + } + reading = false; + if (readAgainForBranch1) { + pull1Algorithm(); + } else if (readAgainForBranch2) { + pull2Algorithm(); + } + }); + }, + closeSteps() { + reading = false; + if (!canceled1) { + readableByteStreamControllerClose(branch1[_controller]); + } + if (!canceled2) { + readableByteStreamControllerClose(branch2[_controller]); + } + if (branch1[_controller][_pendingPullIntos].length !== 0) { + readableByteStreamControllerRespond(branch1[_controller], 0); + } + if (branch2[_controller][_pendingPullIntos].length !== 0) { + readableByteStreamControllerRespond(branch2[_controller], 0); + } + if (!canceled1 || !canceled2) { + cancelPromise.resolve(undefined); + } + }, + errorSteps() { + reading = false; + }, + }; + readableStreamDefaultReaderRead(reader, readRequest); } - /** @param {ReadableStreamDefaultController} controller */ - function readableStreamDefaultControllerClearAlgorithms(controller) { - controller[_pullAlgorithm] = undefined; - controller[_cancelAlgorithm] = undefined; - controller[_strategySizeAlgorithm] = undefined; + function pullWithBYOBReader(view, forBranch2) { + if (isReadableStreamDefaultReader(reader)) { + assert(reader[_readRequests].length === 0); + readableStreamDefaultReaderRelease(reader); + reader = acquireReadableStreamBYOBReader(stream); + forwardReaderError(reader); + } + const byobBranch = forBranch2 ? branch2 : branch1; + const otherBranch = forBranch2 ? branch1 : branch2; + + /** @type {ReadIntoRequest} */ + const readIntoRequest = { + chunkSteps(chunk) { + queueMicrotask(() => { + readAgainForBranch1 = false; + readAgainForBranch2 = false; + const byobCanceled = forBranch2 ? canceled2 : canceled1; + const otherCanceled = forBranch2 ? canceled1 : canceled2; + if (!otherCanceled) { + let clonedChunk; + try { + clonedChunk = cloneAsUint8Array(chunk); + } catch (e) { + readableByteStreamControllerError(byobBranch[_controller], e); + readableByteStreamControllerError(otherBranch[_controller], e); + cancelPromise.resolve(readableStreamCancel(stream, e)); + return; + } + if (!byobCanceled) { + readableByteStreamControllerRespondWithNewView( + byobBranch[_controller], + chunk, + ); + } + readableByteStreamControllerEnqueue( + otherBranch[_controller], + clonedChunk, + ); + } else if (!byobCanceled) { + readableByteStreamControllerRespondWithNewView( + byobBranch[_controller], + chunk, + ); + } + reading = false; + if (readAgainForBranch1) { + pull1Algorithm(); + } else if (readAgainForBranch2) { + pull2Algorithm(); + } + }); + }, + closeSteps(chunk) { + reading = false; + const byobCanceled = forBranch2 ? canceled2 : canceled1; + const otherCanceled = forBranch2 ? canceled1 : canceled2; + if (!byobCanceled) { + readableByteStreamControllerClose(byobBranch[_controller]); + } + if (!otherCanceled) { + readableByteStreamControllerClose(otherBranch[_controller]); + } + if (chunk !== undefined) { + assert(chunk.byteLength === 0); + if (!byobCanceled) { + readableByteStreamControllerRespondWithNewView( + byobBranch[_controller], + chunk, + ); + } + if ( + !otherCanceled && + otherBranch[_controller][_pendingPullIntos].length !== 0 + ) { + readableByteStreamControllerRespond(otherBranch[_controller], 0); + } + } + if (!byobCanceled || !otherCanceled) { + cancelPromise.resolve(undefined); + } + }, + errorSteps() { + reading = false; + }, + }; + readableStreamBYOBReaderRead(reader, view, readIntoRequest); } - /** @param {ReadableStreamDefaultController} controller */ - function readableStreamDefaultControllerClose(controller) { - if ( - readableStreamDefaultControllerCanCloseOrEnqueue(controller) === false - ) { - return; + function pull1Algorithm() { + if (reading) { + readAgainForBranch1 = true; + return PromiseResolve(undefined); } - const stream = controller[_stream]; - controller[_closeRequested] = true; - if (controller[_queue].length === 0) { - readableStreamDefaultControllerClearAlgorithms(controller); - readableStreamClose(stream); + reading = true; + const byobRequest = readableByteStreamControllerGetBYOBRequest( + branch1[_controller], + ); + if (byobRequest === null) { + pullWithDefaultReader(); + } else { + pullWithBYOBReader(byobRequest[_view], false); } + return PromiseResolve(undefined); } - /** - * @template R - * @param {ReadableStreamDefaultController} controller - * @param {R} chunk - * @returns {void} - */ - function readableStreamDefaultControllerEnqueue(controller, chunk) { - if ( - readableStreamDefaultControllerCanCloseOrEnqueue(controller) === false - ) { - return; + function pull2Algorithm() { + if (reading) { + readAgainForBranch2 = true; + return PromiseResolve(undefined); } - const stream = controller[_stream]; - if ( - isReadableStreamLocked(stream) === true && - readableStreamGetNumReadRequests(stream) > 0 - ) { - readableStreamFulfillReadRequest(stream, chunk, false); + reading = true; + const byobRequest = readableByteStreamControllerGetBYOBRequest( + branch2[_controller], + ); + if (byobRequest === null) { + pullWithDefaultReader(); } else { - let chunkSize; - try { - chunkSize = controller[_strategySizeAlgorithm](chunk); - } catch (e) { - readableStreamDefaultControllerError(controller, e); - throw e; - } - - try { - enqueueValueWithSize(controller, chunk, chunkSize); - } catch (e) { - readableStreamDefaultControllerError(controller, e); - throw e; - } + pullWithBYOBReader(byobRequest[_view], true); } - readableStreamDefaultControllerCallPullIfNeeded(controller); + return PromiseResolve(undefined); } - /** - * @param {ReadableStreamDefaultController} controller - * @param {any} e - */ - function readableStreamDefaultControllerError(controller, e) { - const stream = controller[_stream]; - if (stream[_state] !== "readable") { - return; + function cancel1Algorithm(reason) { + canceled1 = true; + reason1 = reason; + if (canceled2) { + const compositeReason = [reason1, reason2]; + const cancelResult = readableStreamCancel(stream, compositeReason); + cancelPromise.resolve(cancelResult); } - resetQueue(controller); - readableStreamDefaultControllerClearAlgorithms(controller); - readableStreamError(stream, e); + return cancelPromise.promise; } - /** - * @param {ReadableStreamDefaultController} controller - * @returns {number | null} - */ - function readableStreamDefaultControllerGetDesiredSize(controller) { - const state = controller[_stream][_state]; - if (state === "errored") { - return null; + function cancel2Algorithm(reason) { + canceled2 = true; + reason2 = reason; + if (canceled1) { + const compositeReason = [reason1, reason2]; + const cancelResult = readableStreamCancel(stream, compositeReason); + cancelPromise.resolve(cancelResult); } - if (state === "closed") { - return 0; - } - return controller[_strategyHWM] - controller[_queueTotalSize]; + return cancelPromise.promise; } - /** @param {ReadableStreamDefaultController} controller */ - function readableStreamDefaultcontrollerHasBackpressure(controller) { - if (readableStreamDefaultcontrollerShouldCallPull(controller) === true) { - return false; - } else { - return true; - } + function startAlgorithm() { + return undefined; } - /** - * @param {ReadableStreamDefaultController} controller - * @returns {boolean} - */ - function readableStreamDefaultcontrollerShouldCallPull(controller) { - const stream = controller[_stream]; - if ( - readableStreamDefaultControllerCanCloseOrEnqueue(controller) === false - ) { - return false; - } - if (controller[_started] === false) { - return false; - } - if ( - isReadableStreamLocked(stream) && - readableStreamGetNumReadRequests(stream) > 0 - ) { - return true; - } - const desiredSize = readableStreamDefaultControllerGetDesiredSize( - controller, - ); - assert(desiredSize !== null); - if (desiredSize > 0) { - return true; - } - return false; - } + branch1 = createReadableByteStream( + startAlgorithm, + pull1Algorithm, + cancel1Algorithm, + ); + branch2 = createReadableByteStream( + startAlgorithm, + pull2Algorithm, + cancel2Algorithm, + ); - /** - * @param {ReadableStreamBYOBReader} reader - * @param {ArrayBufferView} view - * @param {ReadIntoRequest} readIntoRequest - * @returns {void} - */ - function readableStreamBYOBReaderRead(reader, view, readIntoRequest) { - const stream = reader[_stream]; - assert(stream); - stream[_disturbed] = true; - if (stream[_state] === "errored") { - readIntoRequest.errorSteps(stream[_storedError]); - } else { - readableByteStreamControllerPullInto( - stream[_controller], - view, - readIntoRequest, + branch1[_original] = stream; + branch2[_original] = stream; + + forwardReaderError(reader); + return [branch1, branch2]; +} + +/** + * @param {ReadableStream} stream + * @param {ReadableByteStreamController} controller + * @param {() => void} startAlgorithm + * @param {() => Promise} pullAlgorithm + * @param {(reason: any) => Promise} cancelAlgorithm + * @param {number} highWaterMark + * @param {number | undefined} autoAllocateChunkSize + */ +function setUpReadableByteStreamController( + stream, + controller, + startAlgorithm, + pullAlgorithm, + cancelAlgorithm, + highWaterMark, + autoAllocateChunkSize, +) { + assert(stream[_controller] === undefined); + if (autoAllocateChunkSize !== undefined) { + assert(NumberIsInteger(autoAllocateChunkSize)); + assert(autoAllocateChunkSize >= 0); + } + controller[_stream] = stream; + controller[_pullAgain] = controller[_pulling] = false; + controller[_byobRequest] = null; + resetQueue(controller); + controller[_closeRequested] = controller[_started] = false; + controller[_strategyHWM] = highWaterMark; + controller[_pullAlgorithm] = pullAlgorithm; + controller[_cancelAlgorithm] = cancelAlgorithm; + controller[_autoAllocateChunkSize] = autoAllocateChunkSize; + controller[_pendingPullIntos] = []; + stream[_controller] = controller; + const startResult = startAlgorithm(); + const startPromise = resolvePromiseWith(startResult); + setPromiseIsHandledToTrue( + PromisePrototypeThen( + startPromise, + () => { + controller[_started] = true; + assert(controller[_pulling] === false); + assert(controller[_pullAgain] === false); + readableByteStreamControllerCallPullIfNeeded(controller); + }, + (r) => { + readableByteStreamControllerError(controller, r); + }, + ), + ); +} + +/** + * @param {ReadableStream} stream + * @param {UnderlyingSource} underlyingSource + * @param {UnderlyingSource} underlyingSourceDict + * @param {number} highWaterMark + */ +function setUpReadableByteStreamControllerFromUnderlyingSource( + stream, + underlyingSource, + underlyingSourceDict, + highWaterMark, +) { + const controller = webidl.createBranded(ReadableByteStreamController); + /** @type {() => void} */ + let startAlgorithm = () => undefined; + /** @type {() => Promise} */ + let pullAlgorithm = () => resolvePromiseWith(undefined); + /** @type {(reason: any) => Promise} */ + let cancelAlgorithm = (_reason) => resolvePromiseWith(undefined); + if (underlyingSourceDict.start !== undefined) { + startAlgorithm = () => + webidl.invokeCallbackFunction( + underlyingSourceDict.start, + [controller], + underlyingSource, + webidl.converters.any, + { + prefix: + "Failed to call 'startAlgorithm' on 'ReadableByteStreamController'", + }, ); - } } - - /** - * @param {ReadableStreamBYOBReader} reader - */ - function readableStreamBYOBReaderRelease(reader) { - readableStreamReaderGenericRelease(reader); - const e = new TypeError("The reader was released."); - readableStreamBYOBReaderErrorReadIntoRequests(reader, e); + if (underlyingSourceDict.pull !== undefined) { + pullAlgorithm = () => + webidl.invokeCallbackFunction( + underlyingSourceDict.pull, + [controller], + underlyingSource, + webidl.converters["Promise"], + { + prefix: + "Failed to call 'pullAlgorithm' on 'ReadableByteStreamController'", + returnsPromise: true, + }, + ); } - - /** - * @param {ReadableStreamBYOBReader} reader - * @param {any} e - */ - function readableStreamDefaultReaderErrorReadRequests(reader, e) { - const readRequests = reader[_readRequests]; - reader[_readRequests] = []; - for (let i = 0; i < readRequests.length; ++i) { - const readRequest = readRequests[i]; - readRequest.errorSteps(e); - } + if (underlyingSourceDict.cancel !== undefined) { + cancelAlgorithm = (reason) => + webidl.invokeCallbackFunction( + underlyingSourceDict.cancel, + [reason], + underlyingSource, + webidl.converters["Promise"], + { + prefix: + "Failed to call 'cancelAlgorithm' on 'ReadableByteStreamController'", + returnsPromise: true, + }, + ); } - - /** - * @param {ReadableByteStreamController} controller - */ - function readableByteStreamControllerProcessPullIntoDescriptorsUsingQueue( - controller, - ) { - assert(!controller[_closeRequested]); - while (controller[_pendingPullIntos].length !== 0) { - if (controller[_queueTotalSize] === 0) { - return; - } - const pullIntoDescriptor = controller[_pendingPullIntos][0]; - if ( - readableByteStreamControllerFillPullIntoDescriptorFromQueue( - controller, - pullIntoDescriptor, - ) - ) { - readableByteStreamControllerShiftPendingPullInto(controller); - readableByteStreamControllerCommitPullIntoDescriptor( - controller[_stream], - pullIntoDescriptor, - ); - } - } + const autoAllocateChunkSize = underlyingSourceDict["autoAllocateChunkSize"]; + if (autoAllocateChunkSize === 0) { + throw new TypeError("autoAllocateChunkSize must be greater than 0"); } - /** - * @param {ReadableByteStreamController} controller - */ - function readableByteStreamControllerProcessReadRequestsUsingQueue( + setUpReadableByteStreamController( + stream, controller, - ) { - const reader = controller[_stream][_reader]; - assert(isReadableStreamDefaultReader(reader)); - while (reader[_readRequests].length !== 0) { - if (controller[_queueTotalSize] === 0) { - return; - } - const readRequest = ArrayPrototypeShift(reader[_readRequests]); - readableByteStreamControllerFillReadRequestFromQueue( - controller, - readRequest, - ); - } - } - - /** - * @param {ReadableByteStreamController} controller - * @param {ArrayBufferView} view - * @param {ReadIntoRequest} readIntoRequest - * @returns {void} - */ - function readableByteStreamControllerPullInto( - controller, - view, - readIntoRequest, - ) { - const stream = controller[_stream]; - let elementSize = 1; - let ctor = DataView; - - if ( - ObjectPrototypeIsPrototypeOf(Int8ArrayPrototype, view) || - ObjectPrototypeIsPrototypeOf(Uint8ArrayPrototype, view) || - ObjectPrototypeIsPrototypeOf(Uint8ClampedArrayPrototype, view) || - ObjectPrototypeIsPrototypeOf(Int16ArrayPrototype, view) || - ObjectPrototypeIsPrototypeOf(Uint16ArrayPrototype, view) || - ObjectPrototypeIsPrototypeOf(Int32ArrayPrototype, view) || - ObjectPrototypeIsPrototypeOf(Uint32ArrayPrototype, view) || - ObjectPrototypeIsPrototypeOf(BigInt64ArrayPrototype, view) || - ObjectPrototypeIsPrototypeOf(BigUint64ArrayPrototype, view) - ) { - elementSize = view.constructor.BYTES_PER_ELEMENT; - ctor = view.constructor; - } - const byteOffset = view.byteOffset; - const byteLength = view.byteLength; - - /** @type {ArrayBufferLike} */ - let buffer; - - try { - buffer = transferArrayBuffer(view.buffer); - } catch (e) { - readIntoRequest.errorSteps(e); - return; - } - - /** @type {PullIntoDescriptor} */ - const pullIntoDescriptor = { - buffer, - bufferByteLength: buffer.byteLength, - byteOffset, - byteLength, - bytesFilled: 0, - elementSize, - viewConstructor: ctor, - readerType: "byob", - }; - - if (controller[_pendingPullIntos].length !== 0) { - ArrayPrototypePush(controller[_pendingPullIntos], pullIntoDescriptor); - readableStreamAddReadIntoRequest(stream, readIntoRequest); - return; - } - if (stream[_state] === "closed") { - const emptyView = new ctor( - pullIntoDescriptor.buffer, - pullIntoDescriptor.byteOffset, - 0, + startAlgorithm, + pullAlgorithm, + cancelAlgorithm, + highWaterMark, + autoAllocateChunkSize, + ); +} + +/** + * @template R + * @param {ReadableStream} stream + * @param {ReadableStreamDefaultController} controller + * @param {(controller: ReadableStreamDefaultController) => void | Promise} startAlgorithm + * @param {(controller: ReadableStreamDefaultController) => Promise} pullAlgorithm + * @param {(reason: any) => Promise} cancelAlgorithm + * @param {number} highWaterMark + * @param {(chunk: R) => number} sizeAlgorithm + */ +function setUpReadableStreamDefaultController( + stream, + controller, + startAlgorithm, + pullAlgorithm, + cancelAlgorithm, + highWaterMark, + sizeAlgorithm, +) { + assert(stream[_controller] === undefined); + controller[_stream] = stream; + resetQueue(controller); + controller[_started] = + controller[_closeRequested] = + controller[_pullAgain] = + controller[_pulling] = + false; + controller[_strategySizeAlgorithm] = sizeAlgorithm; + controller[_strategyHWM] = highWaterMark; + controller[_pullAlgorithm] = pullAlgorithm; + controller[_cancelAlgorithm] = cancelAlgorithm; + stream[_controller] = controller; + const startResult = startAlgorithm(controller); + const startPromise = resolvePromiseWith(startResult); + uponPromise(startPromise, () => { + controller[_started] = true; + assert(controller[_pulling] === false); + assert(controller[_pullAgain] === false); + readableStreamDefaultControllerCallPullIfNeeded(controller); + }, (r) => { + readableStreamDefaultControllerError(controller, r); + }); +} + +/** + * @template R + * @param {ReadableStream} stream + * @param {UnderlyingSource} underlyingSource + * @param {UnderlyingSource} underlyingSourceDict + * @param {number} highWaterMark + * @param {(chunk: R) => number} sizeAlgorithm + */ +function setUpReadableStreamDefaultControllerFromUnderlyingSource( + stream, + underlyingSource, + underlyingSourceDict, + highWaterMark, + sizeAlgorithm, +) { + const controller = webidl.createBranded(ReadableStreamDefaultController); + /** @type {() => Promise} */ + let startAlgorithm = () => undefined; + /** @type {() => Promise} */ + let pullAlgorithm = () => resolvePromiseWith(undefined); + /** @type {(reason?: any) => Promise} */ + let cancelAlgorithm = () => resolvePromiseWith(undefined); + if (underlyingSourceDict.start !== undefined) { + startAlgorithm = () => + webidl.invokeCallbackFunction( + underlyingSourceDict.start, + [controller], + underlyingSource, + webidl.converters.any, + { + prefix: + "Failed to call 'startAlgorithm' on 'ReadableStreamDefaultController'", + }, ); - readIntoRequest.closeSteps(emptyView); - return; - } - if (controller[_queueTotalSize] > 0) { - if ( - readableByteStreamControllerFillPullIntoDescriptorFromQueue( - controller, - pullIntoDescriptor, - ) - ) { - const filledView = - readableByteStreamControllerConvertPullIntoDescriptor( - pullIntoDescriptor, - ); - readableByteStreamControllerHandleQueueDrain(controller); - readIntoRequest.chunkSteps(filledView); - return; - } - if (controller[_closeRequested]) { - const e = new TypeError( - "Insufficient bytes to fill elements in the given buffer", - ); - readableByteStreamControllerError(controller, e); - readIntoRequest.errorSteps(e); - return; - } - } - controller[_pendingPullIntos].push(pullIntoDescriptor); - readableStreamAddReadIntoRequest(stream, readIntoRequest); - readableByteStreamControllerCallPullIfNeeded(controller); - } - - /** - * @param {ReadableByteStreamController} controller - * @param {number} bytesWritten - * @returns {void} - */ - function readableByteStreamControllerRespond(controller, bytesWritten) { - assert(controller[_pendingPullIntos].length !== 0); - const firstDescriptor = controller[_pendingPullIntos][0]; - const state = controller[_stream][_state]; - if (state === "closed") { - if (bytesWritten !== 0) { - throw new TypeError( - "bytesWritten must be 0 when calling respond() on a closed stream", - ); - } - } else { - assert(state === "readable"); - if (bytesWritten === 0) { - throw new TypeError( - "bytesWritten must be greater than 0 when calling respond() on a readable stream", - ); - } - if ( - (firstDescriptor.bytesFilled + bytesWritten) > - firstDescriptor.byteLength - ) { - throw new RangeError("bytesWritten out of range"); - } - } - firstDescriptor.buffer = transferArrayBuffer(firstDescriptor.buffer); - readableByteStreamControllerRespondInternal(controller, bytesWritten); } - - /** - * @param {ReadableByteStreamController} controller - * @param {number} bytesWritten - * @param {PullIntoDescriptor} pullIntoDescriptor - * @returns {void} - */ - function readableByteStreamControllerRespondInReadableState( - controller, - bytesWritten, - pullIntoDescriptor, - ) { - assert( - (pullIntoDescriptor.bytesFilled + bytesWritten) <= - pullIntoDescriptor.byteLength, - ); - readableByteStreamControllerFillHeadPullIntoDescriptor( - controller, - bytesWritten, - pullIntoDescriptor, - ); - if (pullIntoDescriptor.readerType === "none") { - readableByteStreamControllerEnqueueDetachedPullIntoToQueue( - controller, - pullIntoDescriptor, - ); - readableByteStreamControllerProcessPullIntoDescriptorsUsingQueue( - controller, - ); - return; - } - if (pullIntoDescriptor.bytesFilled < pullIntoDescriptor.elementSize) { - return; - } - readableByteStreamControllerShiftPendingPullInto(controller); - const remainderSize = pullIntoDescriptor.bytesFilled % - pullIntoDescriptor.elementSize; - if (remainderSize > 0) { - const end = pullIntoDescriptor.byteOffset + - pullIntoDescriptor.bytesFilled; - readableByteStreamControllerEnqueueClonedChunkToQueue( - controller, - pullIntoDescriptor.buffer, - end - remainderSize, - remainderSize, + if (underlyingSourceDict.pull !== undefined) { + pullAlgorithm = () => + webidl.invokeCallbackFunction( + underlyingSourceDict.pull, + [controller], + underlyingSource, + webidl.converters["Promise"], + { + prefix: + "Failed to call 'pullAlgorithm' on 'ReadableStreamDefaultController'", + returnsPromise: true, + }, ); - } - pullIntoDescriptor.bytesFilled -= remainderSize; - readableByteStreamControllerCommitPullIntoDescriptor( - controller[_stream], - pullIntoDescriptor, - ); - readableByteStreamControllerProcessPullIntoDescriptorsUsingQueue( - controller, - ); } - - /** - * @param {ReadableByteStreamController} controller - * @param {number} bytesWritten - * @returns {void} - */ - function readableByteStreamControllerRespondInternal( - controller, - bytesWritten, - ) { - const firstDescriptor = controller[_pendingPullIntos][0]; - assert(canTransferArrayBuffer(firstDescriptor.buffer)); - readableByteStreamControllerInvalidateBYOBRequest(controller); - const state = controller[_stream][_state]; - if (state === "closed") { - assert(bytesWritten === 0); - readableByteStreamControllerRespondInClosedState( - controller, - firstDescriptor, - ); - } else { - assert(state === "readable"); - assert(bytesWritten > 0); - readableByteStreamControllerRespondInReadableState( - controller, - bytesWritten, - firstDescriptor, + if (underlyingSourceDict.cancel !== undefined) { + cancelAlgorithm = (reason) => + webidl.invokeCallbackFunction( + underlyingSourceDict.cancel, + [reason], + underlyingSource, + webidl.converters["Promise"], + { + prefix: + "Failed to call 'cancelAlgorithm' on 'ReadableStreamDefaultController'", + returnsPromise: true, + }, ); - } - readableByteStreamControllerCallPullIfNeeded(controller); - } - - /** - * @param {ReadableByteStreamController} controller - */ - function readableByteStreamControllerInvalidateBYOBRequest(controller) { - if (controller[_byobRequest] === null) { - return; - } - controller[_byobRequest][_controller] = undefined; - controller[_byobRequest][_view] = null; - controller[_byobRequest] = null; - } - - /** - * @param {ReadableByteStreamController} controller - * @param {PullIntoDescriptor} firstDescriptor - */ - function readableByteStreamControllerRespondInClosedState( - controller, - firstDescriptor, - ) { - assert(firstDescriptor.bytesFilled === 0); - if (firstDescriptor.readerType === "none") { - readableByteStreamControllerShiftPendingPullInto(controller); - } - const stream = controller[_stream]; - if (readableStreamHasBYOBReader(stream)) { - while (readableStreamGetNumReadIntoRequests(stream) > 0) { - const pullIntoDescriptor = - readableByteStreamControllerShiftPendingPullInto(controller); - readableByteStreamControllerCommitPullIntoDescriptor( - stream, - pullIntoDescriptor, - ); - } - } } - - /** - * @template R - * @param {ReadableStream} stream - * @param {PullIntoDescriptor} pullIntoDescriptor - */ - function readableByteStreamControllerCommitPullIntoDescriptor( + setUpReadableStreamDefaultController( stream, - pullIntoDescriptor, + controller, + startAlgorithm, + pullAlgorithm, + cancelAlgorithm, + highWaterMark, + sizeAlgorithm, + ); +} + +/** + * @template R + * @param {ReadableStreamBYOBReader} reader + * @param {ReadableStream} stream + */ +function setUpReadableStreamBYOBReader(reader, stream) { + if (isReadableStreamLocked(stream)) { + throw new TypeError("ReadableStream is locked."); + } + if ( + !(ObjectPrototypeIsPrototypeOf( + ReadableByteStreamControllerPrototype, + stream[_controller], + )) ) { - assert(stream[_state] !== "errored"); - assert(pullIntoDescriptor.readerType !== "none"); - let done = false; - if (stream[_state] === "closed") { - assert(pullIntoDescriptor.bytesFilled === 0); - done = true; - } - const filledView = readableByteStreamControllerConvertPullIntoDescriptor( - pullIntoDescriptor, - ); - if (pullIntoDescriptor.readerType === "default") { - readableStreamFulfillReadRequest(stream, filledView, done); - } else { - assert(pullIntoDescriptor.readerType === "byob"); - readableStreamFulfillReadIntoRequest(stream, filledView, done); - } - } - - /** - * @param {ReadableByteStreamController} controller - * @param {ArrayBufferView} view - */ - function readableByteStreamControllerRespondWithNewView(controller, view) { - assert(controller[_pendingPullIntos].length !== 0); - assert(!isDetachedBuffer(view.buffer)); - const firstDescriptor = controller[_pendingPullIntos][0]; - const state = controller[_stream][_state]; - if (state === "closed") { - if (view.byteLength !== 0) { - throw new TypeError( - "The view's length must be 0 when calling respondWithNewView() on a closed stream", - ); - } - } else { - assert(state === "readable"); - if (view.byteLength === 0) { - throw new TypeError( - "The view's length must be greater than 0 when calling respondWithNewView() on a readable stream", - ); - } - } - if ( - (firstDescriptor.byteOffset + firstDescriptor.bytesFilled) !== - view.byteOffset - ) { - throw new RangeError( - "The region specified by view does not match byobRequest", - ); - } - if (firstDescriptor.bufferByteLength !== view.buffer.byteLength) { - throw new RangeError( - "The buffer of view has different capacity than byobRequest", - ); + throw new TypeError("Cannot use a BYOB reader with a non-byte stream"); + } + readableStreamReaderGenericInitialize(reader, stream); + reader[_readIntoRequests] = []; +} + +/** + * @template R + * @param {ReadableStreamDefaultReader} reader + * @param {ReadableStream} stream + */ +function setUpReadableStreamDefaultReader(reader, stream) { + if (isReadableStreamLocked(stream)) { + throw new TypeError("ReadableStream is locked."); + } + readableStreamReaderGenericInitialize(reader, stream); + reader[_readRequests] = []; +} + +/** + * @template O + * @param {TransformStream} stream + * @param {TransformStreamDefaultController} controller + * @param {(chunk: O, controller: TransformStreamDefaultController) => Promise} transformAlgorithm + * @param {(controller: TransformStreamDefaultController) => Promise} flushAlgorithm + */ +function setUpTransformStreamDefaultController( + stream, + controller, + transformAlgorithm, + flushAlgorithm, +) { + assert(ObjectPrototypeIsPrototypeOf(TransformStreamPrototype, stream)); + assert(stream[_controller] === undefined); + controller[_stream] = stream; + stream[_controller] = controller; + controller[_transformAlgorithm] = transformAlgorithm; + controller[_flushAlgorithm] = flushAlgorithm; +} + +/** + * @template I + * @template O + * @param {TransformStream} stream + * @param {Transformer} transformer + * @param {Transformer} transformerDict + */ +function setUpTransformStreamDefaultControllerFromTransformer( + stream, + transformer, + transformerDict, +) { + /** @type {TransformStreamDefaultController} */ + const controller = webidl.createBranded(TransformStreamDefaultController); + /** @type {(chunk: O, controller: TransformStreamDefaultController) => Promise} */ + let transformAlgorithm = (chunk) => { + try { + transformStreamDefaultControllerEnqueue(controller, chunk); + } catch (e) { + return PromiseReject(e); } - if ( - (firstDescriptor.bytesFilled + view.byteLength) > - firstDescriptor.byteLength - ) { - throw new RangeError( - "The region specified by view is larger than byobRequest", + return resolvePromiseWith(undefined); + }; + /** @type {(controller: TransformStreamDefaultController) => Promise} */ + let flushAlgorithm = () => resolvePromiseWith(undefined); + if (transformerDict.transform !== undefined) { + transformAlgorithm = (chunk, controller) => + webidl.invokeCallbackFunction( + transformerDict.transform, + [chunk, controller], + transformer, + webidl.converters["Promise"], + { + prefix: + "Failed to call 'transformAlgorithm' on 'TransformStreamDefaultController'", + returnsPromise: true, + }, ); - } - const viewByteLength = view.byteLength; - firstDescriptor.buffer = transferArrayBuffer(view.buffer); - readableByteStreamControllerRespondInternal(controller, viewByteLength); - } - - /** - * @param {ReadableByteStreamController} controller - * @returns {PullIntoDescriptor} - */ - function readableByteStreamControllerShiftPendingPullInto(controller) { - assert(controller[_byobRequest] === null); - return ArrayPrototypeShift(controller[_pendingPullIntos]); } - - /** - * @param {ReadableByteStreamController} controller - * @param {PullIntoDescriptor} pullIntoDescriptor - * @returns {boolean} - */ - function readableByteStreamControllerFillPullIntoDescriptorFromQueue( - controller, - pullIntoDescriptor, - ) { - const elementSize = pullIntoDescriptor.elementSize; - const currentAlignedBytes = pullIntoDescriptor.bytesFilled - - (pullIntoDescriptor.bytesFilled % elementSize); - const maxBytesToCopy = MathMin( - controller[_queueTotalSize], - pullIntoDescriptor.byteLength - pullIntoDescriptor.bytesFilled, - ); - const maxBytesFilled = pullIntoDescriptor.bytesFilled + maxBytesToCopy; - const maxAlignedBytes = maxBytesFilled - (maxBytesFilled % elementSize); - let totalBytesToCopyRemaining = maxBytesToCopy; - let ready = false; - if (maxAlignedBytes > currentAlignedBytes) { - totalBytesToCopyRemaining = maxAlignedBytes - - pullIntoDescriptor.bytesFilled; - ready = true; - } - const queue = controller[_queue]; - while (totalBytesToCopyRemaining > 0) { - const headOfQueue = queue[0]; - const bytesToCopy = MathMin( - totalBytesToCopyRemaining, - headOfQueue.byteLength, - ); - const destStart = pullIntoDescriptor.byteOffset + - pullIntoDescriptor.bytesFilled; - - const destBuffer = new Uint8Array( - pullIntoDescriptor.buffer, - destStart, - bytesToCopy, - ); - const srcBuffer = new Uint8Array( - headOfQueue.buffer, - headOfQueue.byteOffset, - bytesToCopy, - ); - destBuffer.set(srcBuffer); - - if (headOfQueue.byteLength === bytesToCopy) { - ArrayPrototypeShift(queue); - } else { - headOfQueue.byteOffset += bytesToCopy; - headOfQueue.byteLength -= bytesToCopy; - } - controller[_queueTotalSize] -= bytesToCopy; - readableByteStreamControllerFillHeadPullIntoDescriptor( - controller, - bytesToCopy, - pullIntoDescriptor, + if (transformerDict.flush !== undefined) { + flushAlgorithm = (controller) => + webidl.invokeCallbackFunction( + transformerDict.flush, + [controller], + transformer, + webidl.converters["Promise"], + { + prefix: + "Failed to call 'flushAlgorithm' on 'TransformStreamDefaultController'", + returnsPromise: true, + }, ); - totalBytesToCopyRemaining -= bytesToCopy; - } - if (!ready) { - assert(controller[_queueTotalSize] === 0); - assert(pullIntoDescriptor.bytesFilled > 0); - assert(pullIntoDescriptor.bytesFilled < pullIntoDescriptor.elementSize); - } - return ready; } - - /** - * @param {ReadableByteStreamController} controller - * @param {ReadRequest} readRequest - * @returns {void} - */ - function readableByteStreamControllerFillReadRequestFromQueue( + setUpTransformStreamDefaultController( + stream, controller, - readRequest, - ) { - assert(controller[_queueTotalSize] > 0); - const entry = ArrayPrototypeShift(controller[_queue]); - controller[_queueTotalSize] -= entry.byteLength; - readableByteStreamControllerHandleQueueDrain(controller); - const view = new Uint8Array( - entry.buffer, - entry.byteOffset, - entry.byteLength, - ); - readRequest.chunkSteps(view); - } - - /** - * @param {ReadableByteStreamController} controller - * @param {number} size - * @param {PullIntoDescriptor} pullIntoDescriptor - * @returns {void} - */ - function readableByteStreamControllerFillHeadPullIntoDescriptor( + transformAlgorithm, + flushAlgorithm, + ); +} + +/** + * @template W + * @param {WritableStream} stream + * @param {WritableStreamDefaultController} controller + * @param {(controller: WritableStreamDefaultController) => Promise} startAlgorithm + * @param {(chunk: W, controller: WritableStreamDefaultController) => Promise} writeAlgorithm + * @param {() => Promise} closeAlgorithm + * @param {(reason?: any) => Promise} abortAlgorithm + * @param {number} highWaterMark + * @param {(chunk: W) => number} sizeAlgorithm + */ +function setUpWritableStreamDefaultController( + stream, + controller, + startAlgorithm, + writeAlgorithm, + closeAlgorithm, + abortAlgorithm, + highWaterMark, + sizeAlgorithm, +) { + assert(isWritableStream(stream)); + assert(stream[_controller] === undefined); + controller[_stream] = stream; + stream[_controller] = controller; + resetQueue(controller); + controller[_signal] = newSignal(); + controller[_started] = false; + controller[_strategySizeAlgorithm] = sizeAlgorithm; + controller[_strategyHWM] = highWaterMark; + controller[_writeAlgorithm] = writeAlgorithm; + controller[_closeAlgorithm] = closeAlgorithm; + controller[_abortAlgorithm] = abortAlgorithm; + const backpressure = writableStreamDefaultControllerGetBackpressure( controller, - size, - pullIntoDescriptor, - ) { - assert( - controller[_pendingPullIntos].length === 0 || - controller[_pendingPullIntos][0] === pullIntoDescriptor, - ); - assert(controller[_byobRequest] === null); - pullIntoDescriptor.bytesFilled += size; + ); + writableStreamUpdateBackpressure(stream, backpressure); + const startResult = startAlgorithm(controller); + const startPromise = resolvePromiseWith(startResult); + uponPromise(startPromise, () => { + assert(stream[_state] === "writable" || stream[_state] === "erroring"); + controller[_started] = true; + writableStreamDefaultControllerAdvanceQueueIfNeeded(controller); + }, (r) => { + assert(stream[_state] === "writable" || stream[_state] === "erroring"); + controller[_started] = true; + writableStreamDealWithRejection(stream, r); + }); +} + +/** + * @template W + * @param {WritableStream} stream + * @param {UnderlyingSink} underlyingSink + * @param {UnderlyingSink} underlyingSinkDict + * @param {number} highWaterMark + * @param {(chunk: W) => number} sizeAlgorithm + */ +function setUpWritableStreamDefaultControllerFromUnderlyingSink( + stream, + underlyingSink, + underlyingSinkDict, + highWaterMark, + sizeAlgorithm, +) { + const controller = webidl.createBranded(WritableStreamDefaultController); + /** @type {(controller: WritableStreamDefaultController) => any} */ + let startAlgorithm = () => undefined; + /** @type {(chunk: W, controller: WritableStreamDefaultController) => Promise} */ + let writeAlgorithm = () => resolvePromiseWith(undefined); + let closeAlgorithm = () => resolvePromiseWith(undefined); + /** @type {(reason?: any) => Promise} */ + let abortAlgorithm = () => resolvePromiseWith(undefined); + + if (underlyingSinkDict.start !== undefined) { + startAlgorithm = () => + webidl.invokeCallbackFunction( + underlyingSinkDict.start, + [controller], + underlyingSink, + webidl.converters.any, + { + prefix: + "Failed to call 'startAlgorithm' on 'WritableStreamDefaultController'", + }, + ); } - - /** - * @param {PullIntoDescriptor} pullIntoDescriptor - * @returns {ArrayBufferView} - */ - function readableByteStreamControllerConvertPullIntoDescriptor( - pullIntoDescriptor, - ) { - const bytesFilled = pullIntoDescriptor.bytesFilled; - const elementSize = pullIntoDescriptor.elementSize; - assert(bytesFilled <= pullIntoDescriptor.byteLength); - assert((bytesFilled % elementSize) === 0); - const buffer = transferArrayBuffer(pullIntoDescriptor.buffer); - return new pullIntoDescriptor.viewConstructor( - buffer, - pullIntoDescriptor.byteOffset, - bytesFilled / elementSize, - ); + if (underlyingSinkDict.write !== undefined) { + writeAlgorithm = (chunk) => + webidl.invokeCallbackFunction( + underlyingSinkDict.write, + [chunk, controller], + underlyingSink, + webidl.converters["Promise"], + { + prefix: + "Failed to call 'writeAlgorithm' on 'WritableStreamDefaultController'", + returnsPromise: true, + }, + ); } - - /** - * @template R - * @param {ReadableStreamDefaultReader} reader - * @param {ReadRequest} readRequest - * @returns {void} - */ - function readableStreamDefaultReaderRead(reader, readRequest) { - const stream = reader[_stream]; - assert(stream); - stream[_disturbed] = true; - if (stream[_state] === "closed") { - readRequest.closeSteps(); - } else if (stream[_state] === "errored") { - readRequest.errorSteps(stream[_storedError]); - } else { - assert(stream[_state] === "readable"); - stream[_controller][_pullSteps](readRequest); - } + if (underlyingSinkDict.close !== undefined) { + closeAlgorithm = () => + webidl.invokeCallbackFunction( + underlyingSinkDict.close, + [], + underlyingSink, + webidl.converters["Promise"], + { + prefix: + "Failed to call 'closeAlgorithm' on 'WritableStreamDefaultController'", + returnsPromise: true, + }, + ); } - - /** - * @template R - * @param {ReadableStreamDefaultReader} reader - */ - function readableStreamDefaultReaderRelease(reader) { - readableStreamReaderGenericRelease(reader); - const e = new TypeError("The reader was released."); - readableStreamDefaultReaderErrorReadRequests(reader, e); - } - - /** - * @template R - * @param {ReadableStream} stream - * @param {any} e - */ - function readableStreamError(stream, e) { - assert(stream[_state] === "readable"); - stream[_state] = "errored"; - stream[_storedError] = e; - /** @type {ReadableStreamDefaultReader | undefined} */ - const reader = stream[_reader]; - if (reader === undefined) { - return; - } - /** @type {Deferred} */ - const closedPromise = reader[_closedPromise]; - closedPromise.reject(e); - setPromiseIsHandledToTrue(closedPromise.promise); - if (isReadableStreamDefaultReader(reader)) { - readableStreamDefaultReaderErrorReadRequests(reader, e); - } else { - assert(isReadableStreamBYOBReader(reader)); - readableStreamBYOBReaderErrorReadIntoRequests(reader, e); - } - } - - /** - * @template R - * @param {ReadableStream} stream - * @param {R} chunk - * @param {boolean} done - */ - function readableStreamFulfillReadIntoRequest(stream, chunk, done) { - assert(readableStreamHasBYOBReader(stream)); - /** @type {ReadableStreamDefaultReader} */ - const reader = stream[_reader]; - assert(reader[_readIntoRequests].length !== 0); - /** @type {ReadIntoRequest} */ - const readIntoRequest = ArrayPrototypeShift(reader[_readIntoRequests]); - if (done) { - readIntoRequest.closeSteps(chunk); - } else { - readIntoRequest.chunkSteps(chunk); - } - } - - /** - * @template R - * @param {ReadableStream} stream - * @param {R} chunk - * @param {boolean} done - */ - function readableStreamFulfillReadRequest(stream, chunk, done) { - assert(readableStreamHasDefaultReader(stream) === true); - /** @type {ReadableStreamDefaultReader} */ - const reader = stream[_reader]; - assert(reader[_readRequests].length); - /** @type {ReadRequest} */ - const readRequest = ArrayPrototypeShift(reader[_readRequests]); - if (done) { - readRequest.closeSteps(); - } else { - readRequest.chunkSteps(chunk); - } - } - - /** - * @param {ReadableStream} stream - * @return {number} - */ - function readableStreamGetNumReadIntoRequests(stream) { - assert(readableStreamHasBYOBReader(stream) === true); - return stream[_reader][_readIntoRequests].length; - } - - /** - * @param {ReadableStream} stream - * @return {number} - */ - function readableStreamGetNumReadRequests(stream) { - assert(readableStreamHasDefaultReader(stream) === true); - return stream[_reader][_readRequests].length; - } - - /** - * @param {ReadableStream} stream - * @returns {boolean} - */ - function readableStreamHasBYOBReader(stream) { - const reader = stream[_reader]; - if (reader === undefined) { - return false; - } - if (isReadableStreamBYOBReader(reader)) { - return true; - } - return false; - } - - /** - * @param {ReadableStream} stream - * @returns {boolean} - */ - function readableStreamHasDefaultReader(stream) { - const reader = stream[_reader]; - if (reader === undefined) { - return false; - } - if (isReadableStreamDefaultReader(reader)) { - return true; - } - return false; - } - - /** - * @template T - * @param {ReadableStream} source - * @param {WritableStream} dest - * @param {boolean} preventClose - * @param {boolean} preventAbort - * @param {boolean} preventCancel - * @param {AbortSignal=} signal - * @returns {Promise} - */ - function readableStreamPipeTo( - source, - dest, - preventClose, - preventAbort, - preventCancel, - signal, - ) { - assert(isReadableStream(source)); - assert(isWritableStream(dest)); - assert( - typeof preventClose === "boolean" && typeof preventAbort === "boolean" && - typeof preventCancel === "boolean", - ); - assert( - signal === undefined || - ObjectPrototypeIsPrototypeOf(AbortSignalPrototype, signal), - ); - assert(!isReadableStreamLocked(source)); - assert(!isWritableStreamLocked(dest)); - // We use acquireReadableStreamDefaultReader even in case of ReadableByteStreamController - // as the spec allows us, and the only reason to use BYOBReader is to do some smart things - // with it, but the spec does not specify what things, so to simplify we stick to DefaultReader. - const reader = acquireReadableStreamDefaultReader(source); - const writer = acquireWritableStreamDefaultWriter(dest); - source[_disturbed] = true; - let shuttingDown = false; - let currentWrite = resolvePromiseWith(undefined); - /** @type {Deferred} */ - const promise = new Deferred(); - /** @type {() => void} */ - let abortAlgorithm; - if (signal) { - abortAlgorithm = () => { - const error = signal.reason; - /** @type {Array<() => Promise>} */ - const actions = []; - if (preventAbort === false) { - ArrayPrototypePush(actions, () => { - if (dest[_state] === "writable") { - return writableStreamAbort(dest, error); - } else { - return resolvePromiseWith(undefined); - } - }); - } - if (preventCancel === false) { - ArrayPrototypePush(actions, () => { - if (source[_state] === "readable") { - return readableStreamCancel(source, error); - } else { - return resolvePromiseWith(undefined); - } - }); - } - shutdownWithAction( - () => - SafePromiseAll(ArrayPrototypeMap(actions, (action) => action())), - true, - error, - ); - }; - - if (signal.aborted) { - abortAlgorithm(); - return promise.promise; - } - signal[add](abortAlgorithm); - } - - function pipeLoop() { - return new Promise((resolveLoop, rejectLoop) => { - /** @param {boolean} done */ - function next(done) { - if (done) { - resolveLoop(); - } else { - uponPromise(pipeStep(), next, rejectLoop); - } - } - next(false); - }); - } - - /** @returns {Promise} */ - function pipeStep() { - if (shuttingDown === true) { - return resolvePromiseWith(true); - } - - return transformPromiseWith(writer[_readyPromise].promise, () => { - return new Promise((resolveRead, rejectRead) => { - readableStreamDefaultReaderRead( - reader, - { - chunkSteps(chunk) { - currentWrite = transformPromiseWith( - writableStreamDefaultWriterWrite(writer, chunk), - undefined, - () => {}, - ); - resolveRead(false); - }, - closeSteps() { - resolveRead(true); - }, - errorSteps: rejectRead, - }, - ); - }); - }); - } - - isOrBecomesErrored( - source, - reader[_closedPromise].promise, - (storedError) => { - if (preventAbort === false) { - shutdownWithAction( - () => writableStreamAbort(dest, storedError), - true, - storedError, - ); - } else { - shutdown(true, storedError); - } - }, - ); - - isOrBecomesErrored(dest, writer[_closedPromise].promise, (storedError) => { - if (preventCancel === false) { - shutdownWithAction( - () => readableStreamCancel(source, storedError), - true, - storedError, - ); - } else { - shutdown(true, storedError); - } - }); - - isOrBecomesClosed(source, reader[_closedPromise].promise, () => { - if (preventClose === false) { - shutdownWithAction(() => - writableStreamDefaultWriterCloseWithErrorPropagation(writer) - ); - } else { - shutdown(); - } - }); - - if ( - writableStreamCloseQueuedOrInFlight(dest) === true || - dest[_state] === "closed" - ) { - const destClosed = new TypeError( - "The destination writable stream closed before all the data could be piped to it.", - ); - if (preventCancel === false) { - shutdownWithAction( - () => readableStreamCancel(source, destClosed), - true, - destClosed, - ); - } else { - shutdown(true, destClosed); - } - } - - setPromiseIsHandledToTrue(pipeLoop()); - - return promise.promise; - - /** @returns {Promise} */ - function waitForWritesToFinish() { - const oldCurrentWrite = currentWrite; - return transformPromiseWith( - currentWrite, - () => - oldCurrentWrite !== currentWrite - ? waitForWritesToFinish() - : undefined, - ); - } - - /** - * @param {ReadableStream | WritableStream} stream - * @param {Promise} promise - * @param {(e: any) => void} action - */ - function isOrBecomesErrored(stream, promise, action) { - if (stream[_state] === "errored") { - action(stream[_storedError]); - } else { - uponRejection(promise, action); - } - } - - /** - * @param {ReadableStream} stream - * @param {Promise} promise - * @param {() => void} action - */ - function isOrBecomesClosed(stream, promise, action) { - if (stream[_state] === "closed") { - action(); - } else { - uponFulfillment(promise, action); - } - } - - /** - * @param {() => Promise} action - * @param {boolean=} originalIsError - * @param {any=} originalError - */ - function shutdownWithAction(action, originalIsError, originalError) { - function doTheRest() { - uponPromise( - action(), - () => finalize(originalIsError, originalError), - (newError) => finalize(true, newError), - ); - } - - if (shuttingDown === true) { - return; - } - shuttingDown = true; - - if ( - dest[_state] === "writable" && - writableStreamCloseQueuedOrInFlight(dest) === false - ) { - uponFulfillment(waitForWritesToFinish(), doTheRest); - } else { - doTheRest(); - } - } - - /** - * @param {boolean=} isError - * @param {any=} error - */ - function shutdown(isError, error) { - if (shuttingDown) { - return; - } - shuttingDown = true; - if ( - dest[_state] === "writable" && - writableStreamCloseQueuedOrInFlight(dest) === false - ) { - uponFulfillment( - waitForWritesToFinish(), - () => finalize(isError, error), - ); - } else { - finalize(isError, error); - } - } - - /** - * @param {boolean=} isError - * @param {any=} error - */ - function finalize(isError, error) { - writableStreamDefaultWriterRelease(writer); - readableStreamDefaultReaderRelease(reader); - - if (signal !== undefined) { - signal[remove](abortAlgorithm); - } - if (isError) { - promise.reject(error); - } else { - promise.resolve(undefined); - } - } - } - - /** - * @param {ReadableStreamGenericReader | ReadableStreamBYOBReader} reader - * @param {any} reason - * @returns {Promise} - */ - function readableStreamReaderGenericCancel(reader, reason) { - const stream = reader[_stream]; - assert(stream !== undefined); - return readableStreamCancel(stream, reason); - } - - /** - * @template R - * @param {ReadableStreamDefaultReader | ReadableStreamBYOBReader} reader - * @param {ReadableStream} stream - */ - function readableStreamReaderGenericInitialize(reader, stream) { - reader[_stream] = stream; - stream[_reader] = reader; - if (stream[_state] === "readable") { - reader[_closedPromise] = new Deferred(); - } else if (stream[_state] === "closed") { - reader[_closedPromise] = new Deferred(); - reader[_closedPromise].resolve(undefined); - } else { - assert(stream[_state] === "errored"); - reader[_closedPromise] = new Deferred(); - reader[_closedPromise].reject(stream[_storedError]); - setPromiseIsHandledToTrue(reader[_closedPromise].promise); - } - } - - /** - * @template R - * @param {ReadableStreamGenericReader | ReadableStreamBYOBReader} reader - */ - function readableStreamReaderGenericRelease(reader) { - const stream = reader[_stream]; - assert(stream !== undefined); - assert(stream[_reader] === reader); - if (stream[_state] === "readable") { - reader[_closedPromise].reject( - new TypeError( - "Reader was released and can no longer be used to monitor the stream's closedness.", - ), - ); - } else { - reader[_closedPromise] = new Deferred(); - reader[_closedPromise].reject( - new TypeError( - "Reader was released and can no longer be used to monitor the stream's closedness.", - ), - ); - } - setPromiseIsHandledToTrue(reader[_closedPromise].promise); - stream[_controller][_releaseSteps](); - stream[_reader] = undefined; - reader[_stream] = undefined; - } - - /** - * @param {ReadableStreamBYOBReader} reader - * @param {any} e - */ - function readableStreamBYOBReaderErrorReadIntoRequests(reader, e) { - const readIntoRequests = reader[_readIntoRequests]; - reader[_readIntoRequests] = []; - for (let i = 0; i < readIntoRequests.length; ++i) { - const readIntoRequest = readIntoRequests[i]; - readIntoRequest.errorSteps(e); - } - } - - /** - * @template R - * @param {ReadableStream} stream - * @param {boolean} cloneForBranch2 - * @returns {[ReadableStream, ReadableStream]} - */ - function readableStreamTee(stream, cloneForBranch2) { - assert(isReadableStream(stream)); - assert(typeof cloneForBranch2 === "boolean"); - if ( - ObjectPrototypeIsPrototypeOf( - ReadableByteStreamControllerPrototype, - stream[_controller], - ) - ) { - return readableByteStreamTee(stream); - } else { - return readableStreamDefaultTee(stream, cloneForBranch2); - } - } - - /** - * @template R - * @param {ReadableStream} stream - * @param {boolean} cloneForBranch2 - * @returns {[ReadableStream, ReadableStream]} - */ - function readableStreamDefaultTee(stream, cloneForBranch2) { - assert(isReadableStream(stream)); - assert(typeof cloneForBranch2 === "boolean"); - const reader = acquireReadableStreamDefaultReader(stream); - let reading = false; - let readAgain = false; - let canceled1 = false; - let canceled2 = false; - /** @type {any} */ - let reason1; - /** @type {any} */ - let reason2; - /** @type {ReadableStream} */ - // deno-lint-ignore prefer-const - let branch1; - /** @type {ReadableStream} */ - // deno-lint-ignore prefer-const - let branch2; - - /** @type {Deferred} */ - const cancelPromise = new Deferred(); - - function pullAlgorithm() { - if (reading === true) { - readAgain = true; - return resolvePromiseWith(undefined); - } - reading = true; - /** @type {ReadRequest} */ - const readRequest = { - chunkSteps(value) { - queueMicrotask(() => { - readAgain = false; - const value1 = value; - const value2 = value; - - // TODO(lucacasonato): respect clonedForBranch2. - - if (canceled1 === false) { - readableStreamDefaultControllerEnqueue( - /** @type {ReadableStreamDefaultController} */ branch1[ - _controller - ], - value1, - ); - } - if (canceled2 === false) { - readableStreamDefaultControllerEnqueue( - /** @type {ReadableStreamDefaultController} */ branch2[ - _controller - ], - value2, - ); - } - - reading = false; - if (readAgain === true) { - pullAlgorithm(); - } - }); - }, - closeSteps() { - reading = false; - if (canceled1 === false) { - readableStreamDefaultControllerClose( - /** @type {ReadableStreamDefaultController} */ branch1[ - _controller - ], - ); - } - if (canceled2 === false) { - readableStreamDefaultControllerClose( - /** @type {ReadableStreamDefaultController} */ branch2[ - _controller - ], - ); - } - if (canceled1 === false || canceled2 === false) { - cancelPromise.resolve(undefined); - } - }, - errorSteps() { - reading = false; - }, - }; - readableStreamDefaultReaderRead(reader, readRequest); - return resolvePromiseWith(undefined); - } - - /** - * @param {any} reason - * @returns {Promise} - */ - function cancel1Algorithm(reason) { - canceled1 = true; - reason1 = reason; - if (canceled2 === true) { - const compositeReason = [reason1, reason2]; - const cancelResult = readableStreamCancel(stream, compositeReason); - cancelPromise.resolve(cancelResult); - } - return cancelPromise.promise; - } - - /** - * @param {any} reason - * @returns {Promise} - */ - function cancel2Algorithm(reason) { - canceled2 = true; - reason2 = reason; - if (canceled1 === true) { - const compositeReason = [reason1, reason2]; - const cancelResult = readableStreamCancel(stream, compositeReason); - cancelPromise.resolve(cancelResult); - } - return cancelPromise.promise; - } - - function startAlgorithm() {} - - branch1 = createReadableStream( - startAlgorithm, - pullAlgorithm, - cancel1Algorithm, - ); - branch2 = createReadableStream( - startAlgorithm, - pullAlgorithm, - cancel2Algorithm, - ); - - uponRejection(reader[_closedPromise].promise, (r) => { - readableStreamDefaultControllerError( - /** @type {ReadableStreamDefaultController} */ branch1[ - _controller - ], - r, - ); - readableStreamDefaultControllerError( - /** @type {ReadableStreamDefaultController} */ branch2[ - _controller - ], - r, - ); - if (canceled1 === false || canceled2 === false) { - cancelPromise.resolve(undefined); - } - }); - - return [branch1, branch2]; - } - - /** - * @template R - * @param {ReadableStream} stream - * @returns {[ReadableStream, ReadableStream]} - */ - function readableByteStreamTee(stream) { - assert(isReadableStream(stream)); - assert( - ObjectPrototypeIsPrototypeOf( - ReadableByteStreamControllerPrototype, - stream[_controller], - ), - ); - let reader = acquireReadableStreamDefaultReader(stream); - let reading = false; - let readAgainForBranch1 = false; - let readAgainForBranch2 = false; - let canceled1 = false; - let canceled2 = false; - let reason1 = undefined; - let reason2 = undefined; - let branch1 = undefined; - let branch2 = undefined; - /** @type {Deferred} */ - const cancelPromise = new Deferred(); - - /** - * @param {ReadableStreamBYOBReader} thisReader - */ - function forwardReaderError(thisReader) { - PromisePrototypeCatch(thisReader[_closedPromise].promise, (e) => { - if (thisReader !== reader) { - return; - } - readableByteStreamControllerError(branch1[_controller], e); - readableByteStreamControllerError(branch2[_controller], e); - if (!canceled1 || !canceled2) { - cancelPromise.resolve(undefined); - } - }); - } - - function pullWithDefaultReader() { - if (isReadableStreamBYOBReader(reader)) { - assert(reader[_readIntoRequests].length === 0); - readableStreamBYOBReaderRelease(reader); - reader = acquireReadableStreamDefaultReader(stream); - forwardReaderError(reader); - } - - /** @type {ReadRequest} */ - const readRequest = { - chunkSteps(chunk) { - queueMicrotask(() => { - readAgainForBranch1 = false; - readAgainForBranch2 = false; - const chunk1 = chunk; - let chunk2 = chunk; - if (!canceled1 && !canceled2) { - try { - chunk2 = cloneAsUint8Array(chunk); - } catch (e) { - readableByteStreamControllerError(branch1[_controller], e); - readableByteStreamControllerError(branch2[_controller], e); - cancelPromise.resolve(readableStreamCancel(stream, e)); - return; - } - } - if (!canceled1) { - readableByteStreamControllerEnqueue(branch1[_controller], chunk1); - } - if (!canceled2) { - readableByteStreamControllerEnqueue(branch2[_controller], chunk2); - } - reading = false; - if (readAgainForBranch1) { - pull1Algorithm(); - } else if (readAgainForBranch2) { - pull2Algorithm(); - } - }); - }, - closeSteps() { - reading = false; - if (!canceled1) { - readableByteStreamControllerClose(branch1[_controller]); - } - if (!canceled2) { - readableByteStreamControllerClose(branch2[_controller]); - } - if (branch1[_controller][_pendingPullIntos].length !== 0) { - readableByteStreamControllerRespond(branch1[_controller], 0); - } - if (branch2[_controller][_pendingPullIntos].length !== 0) { - readableByteStreamControllerRespond(branch2[_controller], 0); - } - if (!canceled1 || !canceled2) { - cancelPromise.resolve(undefined); - } - }, - errorSteps() { - reading = false; - }, - }; - readableStreamDefaultReaderRead(reader, readRequest); - } - - function pullWithBYOBReader(view, forBranch2) { - if (isReadableStreamDefaultReader(reader)) { - assert(reader[_readRequests].length === 0); - readableStreamDefaultReaderRelease(reader); - reader = acquireReadableStreamBYOBReader(stream); - forwardReaderError(reader); - } - const byobBranch = forBranch2 ? branch2 : branch1; - const otherBranch = forBranch2 ? branch1 : branch2; - - /** @type {ReadIntoRequest} */ - const readIntoRequest = { - chunkSteps(chunk) { - queueMicrotask(() => { - readAgainForBranch1 = false; - readAgainForBranch2 = false; - const byobCanceled = forBranch2 ? canceled2 : canceled1; - const otherCanceled = forBranch2 ? canceled1 : canceled2; - if (!otherCanceled) { - let clonedChunk; - try { - clonedChunk = cloneAsUint8Array(chunk); - } catch (e) { - readableByteStreamControllerError(byobBranch[_controller], e); - readableByteStreamControllerError(otherBranch[_controller], e); - cancelPromise.resolve(readableStreamCancel(stream, e)); - return; - } - if (!byobCanceled) { - readableByteStreamControllerRespondWithNewView( - byobBranch[_controller], - chunk, - ); - } - readableByteStreamControllerEnqueue( - otherBranch[_controller], - clonedChunk, - ); - } else if (!byobCanceled) { - readableByteStreamControllerRespondWithNewView( - byobBranch[_controller], - chunk, - ); - } - reading = false; - if (readAgainForBranch1) { - pull1Algorithm(); - } else if (readAgainForBranch2) { - pull2Algorithm(); - } - }); - }, - closeSteps(chunk) { - reading = false; - const byobCanceled = forBranch2 ? canceled2 : canceled1; - const otherCanceled = forBranch2 ? canceled1 : canceled2; - if (!byobCanceled) { - readableByteStreamControllerClose(byobBranch[_controller]); - } - if (!otherCanceled) { - readableByteStreamControllerClose(otherBranch[_controller]); - } - if (chunk !== undefined) { - assert(chunk.byteLength === 0); - if (!byobCanceled) { - readableByteStreamControllerRespondWithNewView( - byobBranch[_controller], - chunk, - ); - } - if ( - !otherCanceled && - otherBranch[_controller][_pendingPullIntos].length !== 0 - ) { - readableByteStreamControllerRespond(otherBranch[_controller], 0); - } - } - if (!byobCanceled || !otherCanceled) { - cancelPromise.resolve(undefined); - } - }, - errorSteps() { - reading = false; - }, - }; - readableStreamBYOBReaderRead(reader, view, readIntoRequest); - } - - function pull1Algorithm() { - if (reading) { - readAgainForBranch1 = true; - return PromiseResolve(undefined); - } - reading = true; - const byobRequest = readableByteStreamControllerGetBYOBRequest( - branch1[_controller], - ); - if (byobRequest === null) { - pullWithDefaultReader(); - } else { - pullWithBYOBReader(byobRequest[_view], false); - } - return PromiseResolve(undefined); - } - - function pull2Algorithm() { - if (reading) { - readAgainForBranch2 = true; - return PromiseResolve(undefined); - } - reading = true; - const byobRequest = readableByteStreamControllerGetBYOBRequest( - branch2[_controller], - ); - if (byobRequest === null) { - pullWithDefaultReader(); - } else { - pullWithBYOBReader(byobRequest[_view], true); - } - return PromiseResolve(undefined); - } - - function cancel1Algorithm(reason) { - canceled1 = true; - reason1 = reason; - if (canceled2) { - const compositeReason = [reason1, reason2]; - const cancelResult = readableStreamCancel(stream, compositeReason); - cancelPromise.resolve(cancelResult); - } - return cancelPromise.promise; - } - - function cancel2Algorithm(reason) { - canceled2 = true; - reason2 = reason; - if (canceled1) { - const compositeReason = [reason1, reason2]; - const cancelResult = readableStreamCancel(stream, compositeReason); - cancelPromise.resolve(cancelResult); - } - return cancelPromise.promise; - } - - function startAlgorithm() { - return undefined; - } - - branch1 = createReadableByteStream( - startAlgorithm, - pull1Algorithm, - cancel1Algorithm, - ); - branch2 = createReadableByteStream( - startAlgorithm, - pull2Algorithm, - cancel2Algorithm, - ); - - branch1[_original] = stream; - branch2[_original] = stream; - - forwardReaderError(reader); - return [branch1, branch2]; - } - - /** - * @param {ReadableStream} stream - * @param {ReadableByteStreamController} controller - * @param {() => void} startAlgorithm - * @param {() => Promise} pullAlgorithm - * @param {(reason: any) => Promise} cancelAlgorithm - * @param {number} highWaterMark - * @param {number | undefined} autoAllocateChunkSize - */ - function setUpReadableByteStreamController( - stream, - controller, - startAlgorithm, - pullAlgorithm, - cancelAlgorithm, - highWaterMark, - autoAllocateChunkSize, - ) { - assert(stream[_controller] === undefined); - if (autoAllocateChunkSize !== undefined) { - assert(NumberIsInteger(autoAllocateChunkSize)); - assert(autoAllocateChunkSize >= 0); - } - controller[_stream] = stream; - controller[_pullAgain] = controller[_pulling] = false; - controller[_byobRequest] = null; - resetQueue(controller); - controller[_closeRequested] = controller[_started] = false; - controller[_strategyHWM] = highWaterMark; - controller[_pullAlgorithm] = pullAlgorithm; - controller[_cancelAlgorithm] = cancelAlgorithm; - controller[_autoAllocateChunkSize] = autoAllocateChunkSize; - controller[_pendingPullIntos] = []; - stream[_controller] = controller; - const startResult = startAlgorithm(); - const startPromise = resolvePromiseWith(startResult); - setPromiseIsHandledToTrue( - PromisePrototypeThen( - startPromise, - () => { - controller[_started] = true; - assert(controller[_pulling] === false); - assert(controller[_pullAgain] === false); - readableByteStreamControllerCallPullIfNeeded(controller); - }, - (r) => { - readableByteStreamControllerError(controller, r); - }, - ), - ); - } - - /** - * @param {ReadableStream} stream - * @param {UnderlyingSource} underlyingSource - * @param {UnderlyingSource} underlyingSourceDict - * @param {number} highWaterMark - */ - function setUpReadableByteStreamControllerFromUnderlyingSource( - stream, - underlyingSource, - underlyingSourceDict, - highWaterMark, - ) { - const controller = webidl.createBranded(ReadableByteStreamController); - /** @type {() => void} */ - let startAlgorithm = () => undefined; - /** @type {() => Promise} */ - let pullAlgorithm = () => resolvePromiseWith(undefined); - /** @type {(reason: any) => Promise} */ - let cancelAlgorithm = (_reason) => resolvePromiseWith(undefined); - if (underlyingSourceDict.start !== undefined) { - startAlgorithm = () => - webidl.invokeCallbackFunction( - underlyingSourceDict.start, - [controller], - underlyingSource, - webidl.converters.any, - { - prefix: - "Failed to call 'startAlgorithm' on 'ReadableByteStreamController'", - }, - ); - } - if (underlyingSourceDict.pull !== undefined) { - pullAlgorithm = () => - webidl.invokeCallbackFunction( - underlyingSourceDict.pull, - [controller], - underlyingSource, - webidl.converters["Promise"], - { - prefix: - "Failed to call 'pullAlgorithm' on 'ReadableByteStreamController'", - returnsPromise: true, - }, - ); - } - if (underlyingSourceDict.cancel !== undefined) { - cancelAlgorithm = (reason) => - webidl.invokeCallbackFunction( - underlyingSourceDict.cancel, - [reason], - underlyingSource, - webidl.converters["Promise"], - { - prefix: - "Failed to call 'cancelAlgorithm' on 'ReadableByteStreamController'", - returnsPromise: true, - }, - ); - } - const autoAllocateChunkSize = underlyingSourceDict["autoAllocateChunkSize"]; - if (autoAllocateChunkSize === 0) { - throw new TypeError("autoAllocateChunkSize must be greater than 0"); - } - setUpReadableByteStreamController( - stream, - controller, - startAlgorithm, - pullAlgorithm, - cancelAlgorithm, - highWaterMark, - autoAllocateChunkSize, - ); - } - - /** - * @template R - * @param {ReadableStream} stream - * @param {ReadableStreamDefaultController} controller - * @param {(controller: ReadableStreamDefaultController) => void | Promise} startAlgorithm - * @param {(controller: ReadableStreamDefaultController) => Promise} pullAlgorithm - * @param {(reason: any) => Promise} cancelAlgorithm - * @param {number} highWaterMark - * @param {(chunk: R) => number} sizeAlgorithm - */ - function setUpReadableStreamDefaultController( - stream, - controller, - startAlgorithm, - pullAlgorithm, - cancelAlgorithm, - highWaterMark, - sizeAlgorithm, - ) { - assert(stream[_controller] === undefined); - controller[_stream] = stream; - resetQueue(controller); - controller[_started] = - controller[_closeRequested] = - controller[_pullAgain] = - controller[_pulling] = - false; - controller[_strategySizeAlgorithm] = sizeAlgorithm; - controller[_strategyHWM] = highWaterMark; - controller[_pullAlgorithm] = pullAlgorithm; - controller[_cancelAlgorithm] = cancelAlgorithm; - stream[_controller] = controller; - const startResult = startAlgorithm(controller); - const startPromise = resolvePromiseWith(startResult); - uponPromise(startPromise, () => { - controller[_started] = true; - assert(controller[_pulling] === false); - assert(controller[_pullAgain] === false); - readableStreamDefaultControllerCallPullIfNeeded(controller); - }, (r) => { - readableStreamDefaultControllerError(controller, r); - }); - } - - /** - * @template R - * @param {ReadableStream} stream - * @param {UnderlyingSource} underlyingSource - * @param {UnderlyingSource} underlyingSourceDict - * @param {number} highWaterMark - * @param {(chunk: R) => number} sizeAlgorithm - */ - function setUpReadableStreamDefaultControllerFromUnderlyingSource( - stream, - underlyingSource, - underlyingSourceDict, - highWaterMark, - sizeAlgorithm, - ) { - const controller = webidl.createBranded(ReadableStreamDefaultController); - /** @type {() => Promise} */ - let startAlgorithm = () => undefined; - /** @type {() => Promise} */ - let pullAlgorithm = () => resolvePromiseWith(undefined); - /** @type {(reason?: any) => Promise} */ - let cancelAlgorithm = () => resolvePromiseWith(undefined); - if (underlyingSourceDict.start !== undefined) { - startAlgorithm = () => - webidl.invokeCallbackFunction( - underlyingSourceDict.start, - [controller], - underlyingSource, - webidl.converters.any, - { - prefix: - "Failed to call 'startAlgorithm' on 'ReadableStreamDefaultController'", - }, - ); - } - if (underlyingSourceDict.pull !== undefined) { - pullAlgorithm = () => - webidl.invokeCallbackFunction( - underlyingSourceDict.pull, - [controller], - underlyingSource, - webidl.converters["Promise"], - { - prefix: - "Failed to call 'pullAlgorithm' on 'ReadableStreamDefaultController'", - returnsPromise: true, - }, - ); - } - if (underlyingSourceDict.cancel !== undefined) { - cancelAlgorithm = (reason) => - webidl.invokeCallbackFunction( - underlyingSourceDict.cancel, - [reason], - underlyingSource, - webidl.converters["Promise"], - { - prefix: - "Failed to call 'cancelAlgorithm' on 'ReadableStreamDefaultController'", - returnsPromise: true, - }, - ); - } - setUpReadableStreamDefaultController( - stream, - controller, - startAlgorithm, - pullAlgorithm, - cancelAlgorithm, - highWaterMark, - sizeAlgorithm, - ); - } - - /** - * @template R - * @param {ReadableStreamBYOBReader} reader - * @param {ReadableStream} stream - */ - function setUpReadableStreamBYOBReader(reader, stream) { - if (isReadableStreamLocked(stream)) { - throw new TypeError("ReadableStream is locked."); - } - if ( - !(ObjectPrototypeIsPrototypeOf( - ReadableByteStreamControllerPrototype, - stream[_controller], - )) - ) { - throw new TypeError("Cannot use a BYOB reader with a non-byte stream"); - } - readableStreamReaderGenericInitialize(reader, stream); - reader[_readIntoRequests] = []; - } - - /** - * @template R - * @param {ReadableStreamDefaultReader} reader - * @param {ReadableStream} stream - */ - function setUpReadableStreamDefaultReader(reader, stream) { - if (isReadableStreamLocked(stream)) { - throw new TypeError("ReadableStream is locked."); - } - readableStreamReaderGenericInitialize(reader, stream); - reader[_readRequests] = []; - } - - /** - * @template O - * @param {TransformStream} stream - * @param {TransformStreamDefaultController} controller - * @param {(chunk: O, controller: TransformStreamDefaultController) => Promise} transformAlgorithm - * @param {(controller: TransformStreamDefaultController) => Promise} flushAlgorithm - */ - function setUpTransformStreamDefaultController( - stream, - controller, - transformAlgorithm, - flushAlgorithm, - ) { - assert(ObjectPrototypeIsPrototypeOf(TransformStreamPrototype, stream)); - assert(stream[_controller] === undefined); - controller[_stream] = stream; - stream[_controller] = controller; - controller[_transformAlgorithm] = transformAlgorithm; - controller[_flushAlgorithm] = flushAlgorithm; - } - - /** - * @template I - * @template O - * @param {TransformStream} stream - * @param {Transformer} transformer - * @param {Transformer} transformerDict - */ - function setUpTransformStreamDefaultControllerFromTransformer( - stream, - transformer, - transformerDict, - ) { - /** @type {TransformStreamDefaultController} */ - const controller = webidl.createBranded(TransformStreamDefaultController); - /** @type {(chunk: O, controller: TransformStreamDefaultController) => Promise} */ - let transformAlgorithm = (chunk) => { - try { - transformStreamDefaultControllerEnqueue(controller, chunk); - } catch (e) { - return PromiseReject(e); - } - return resolvePromiseWith(undefined); - }; - /** @type {(controller: TransformStreamDefaultController) => Promise} */ - let flushAlgorithm = () => resolvePromiseWith(undefined); - if (transformerDict.transform !== undefined) { - transformAlgorithm = (chunk, controller) => - webidl.invokeCallbackFunction( - transformerDict.transform, - [chunk, controller], - transformer, - webidl.converters["Promise"], - { - prefix: - "Failed to call 'transformAlgorithm' on 'TransformStreamDefaultController'", - returnsPromise: true, - }, - ); - } - if (transformerDict.flush !== undefined) { - flushAlgorithm = (controller) => - webidl.invokeCallbackFunction( - transformerDict.flush, - [controller], - transformer, - webidl.converters["Promise"], - { - prefix: - "Failed to call 'flushAlgorithm' on 'TransformStreamDefaultController'", - returnsPromise: true, - }, - ); - } - setUpTransformStreamDefaultController( - stream, - controller, - transformAlgorithm, - flushAlgorithm, - ); - } - - /** - * @template W - * @param {WritableStream} stream - * @param {WritableStreamDefaultController} controller - * @param {(controller: WritableStreamDefaultController) => Promise} startAlgorithm - * @param {(chunk: W, controller: WritableStreamDefaultController) => Promise} writeAlgorithm - * @param {() => Promise} closeAlgorithm - * @param {(reason?: any) => Promise} abortAlgorithm - * @param {number} highWaterMark - * @param {(chunk: W) => number} sizeAlgorithm - */ - function setUpWritableStreamDefaultController( - stream, - controller, - startAlgorithm, - writeAlgorithm, - closeAlgorithm, - abortAlgorithm, - highWaterMark, - sizeAlgorithm, - ) { - assert(isWritableStream(stream)); - assert(stream[_controller] === undefined); - controller[_stream] = stream; - stream[_controller] = controller; - resetQueue(controller); - controller[_signal] = newSignal(); - controller[_started] = false; - controller[_strategySizeAlgorithm] = sizeAlgorithm; - controller[_strategyHWM] = highWaterMark; - controller[_writeAlgorithm] = writeAlgorithm; - controller[_closeAlgorithm] = closeAlgorithm; - controller[_abortAlgorithm] = abortAlgorithm; - const backpressure = writableStreamDefaultControllerGetBackpressure( - controller, - ); - writableStreamUpdateBackpressure(stream, backpressure); - const startResult = startAlgorithm(controller); - const startPromise = resolvePromiseWith(startResult); - uponPromise(startPromise, () => { - assert(stream[_state] === "writable" || stream[_state] === "erroring"); - controller[_started] = true; - writableStreamDefaultControllerAdvanceQueueIfNeeded(controller); - }, (r) => { - assert(stream[_state] === "writable" || stream[_state] === "erroring"); - controller[_started] = true; - writableStreamDealWithRejection(stream, r); - }); - } - - /** - * @template W - * @param {WritableStream} stream - * @param {UnderlyingSink} underlyingSink - * @param {UnderlyingSink} underlyingSinkDict - * @param {number} highWaterMark - * @param {(chunk: W) => number} sizeAlgorithm - */ - function setUpWritableStreamDefaultControllerFromUnderlyingSink( - stream, - underlyingSink, - underlyingSinkDict, - highWaterMark, - sizeAlgorithm, - ) { - const controller = webidl.createBranded(WritableStreamDefaultController); - /** @type {(controller: WritableStreamDefaultController) => any} */ - let startAlgorithm = () => undefined; - /** @type {(chunk: W, controller: WritableStreamDefaultController) => Promise} */ - let writeAlgorithm = () => resolvePromiseWith(undefined); - let closeAlgorithm = () => resolvePromiseWith(undefined); - /** @type {(reason?: any) => Promise} */ - let abortAlgorithm = () => resolvePromiseWith(undefined); - - if (underlyingSinkDict.start !== undefined) { - startAlgorithm = () => - webidl.invokeCallbackFunction( - underlyingSinkDict.start, - [controller], - underlyingSink, - webidl.converters.any, - { - prefix: - "Failed to call 'startAlgorithm' on 'WritableStreamDefaultController'", - }, - ); - } - if (underlyingSinkDict.write !== undefined) { - writeAlgorithm = (chunk) => - webidl.invokeCallbackFunction( - underlyingSinkDict.write, - [chunk, controller], - underlyingSink, - webidl.converters["Promise"], - { - prefix: - "Failed to call 'writeAlgorithm' on 'WritableStreamDefaultController'", - returnsPromise: true, - }, - ); - } - if (underlyingSinkDict.close !== undefined) { - closeAlgorithm = () => - webidl.invokeCallbackFunction( - underlyingSinkDict.close, - [], - underlyingSink, - webidl.converters["Promise"], - { - prefix: - "Failed to call 'closeAlgorithm' on 'WritableStreamDefaultController'", - returnsPromise: true, - }, - ); - } - if (underlyingSinkDict.abort !== undefined) { - abortAlgorithm = (reason) => - webidl.invokeCallbackFunction( - underlyingSinkDict.abort, - [reason], - underlyingSink, - webidl.converters["Promise"], - { - prefix: - "Failed to call 'abortAlgorithm' on 'WritableStreamDefaultController'", - returnsPromise: true, - }, - ); - } - setUpWritableStreamDefaultController( - stream, - controller, - startAlgorithm, - writeAlgorithm, - closeAlgorithm, - abortAlgorithm, - highWaterMark, - sizeAlgorithm, - ); - } - - /** - * @template W - * @param {WritableStreamDefaultWriter} writer - * @param {WritableStream} stream - */ - function setUpWritableStreamDefaultWriter(writer, stream) { - if (isWritableStreamLocked(stream) === true) { - throw new TypeError("The stream is already locked."); - } - writer[_stream] = stream; - stream[_writer] = writer; - const state = stream[_state]; - if (state === "writable") { - if ( - writableStreamCloseQueuedOrInFlight(stream) === false && - stream[_backpressure] === true - ) { - writer[_readyPromise] = new Deferred(); - } else { - writer[_readyPromise] = new Deferred(); - writer[_readyPromise].resolve(undefined); - } - writer[_closedPromise] = new Deferred(); - } else if (state === "erroring") { - writer[_readyPromise] = new Deferred(); - writer[_readyPromise].reject(stream[_storedError]); - setPromiseIsHandledToTrue(writer[_readyPromise].promise); - writer[_closedPromise] = new Deferred(); - } else if (state === "closed") { - writer[_readyPromise] = new Deferred(); - writer[_readyPromise].resolve(undefined); - writer[_closedPromise] = new Deferred(); - writer[_closedPromise].resolve(undefined); - } else { - assert(state === "errored"); - const storedError = stream[_storedError]; - writer[_readyPromise] = new Deferred(); - writer[_readyPromise].reject(storedError); - setPromiseIsHandledToTrue(writer[_readyPromise].promise); - writer[_closedPromise] = new Deferred(); - writer[_closedPromise].reject(storedError); - setPromiseIsHandledToTrue(writer[_closedPromise].promise); - } - } - - /** @param {TransformStreamDefaultController} controller */ - function transformStreamDefaultControllerClearAlgorithms(controller) { - controller[_transformAlgorithm] = undefined; - controller[_flushAlgorithm] = undefined; - } - - /** - * @template O - * @param {TransformStreamDefaultController} controller - * @param {O} chunk - */ - function transformStreamDefaultControllerEnqueue(controller, chunk) { - const stream = controller[_stream]; - const readableController = stream[_readable][_controller]; - if ( - readableStreamDefaultControllerCanCloseOrEnqueue( - /** @type {ReadableStreamDefaultController} */ readableController, - ) === false - ) { - throw new TypeError("Readable stream is unavailable."); - } - try { - readableStreamDefaultControllerEnqueue( - /** @type {ReadableStreamDefaultController} */ readableController, - chunk, - ); - } catch (e) { - transformStreamErrorWritableAndUnblockWrite(stream, e); - throw stream[_readable][_storedError]; - } - const backpressure = readableStreamDefaultcontrollerHasBackpressure( - /** @type {ReadableStreamDefaultController} */ readableController, - ); - if (backpressure !== stream[_backpressure]) { - assert(backpressure === true); - transformStreamSetBackpressure(stream, true); - } - } - - /** - * @param {TransformStreamDefaultController} controller - * @param {any=} e - */ - function transformStreamDefaultControllerError(controller, e) { - transformStreamError(controller[_stream], e); - } - - /** - * @template O - * @param {TransformStreamDefaultController} controller - * @param {any} chunk - * @returns {Promise} - */ - function transformStreamDefaultControllerPerformTransform(controller, chunk) { - const transformPromise = controller[_transformAlgorithm](chunk, controller); - return transformPromiseWith(transformPromise, undefined, (r) => { - transformStreamError(controller[_stream], r); - throw r; - }); - } - - /** @param {TransformStreamDefaultController} controller */ - function transformStreamDefaultControllerTerminate(controller) { - const stream = controller[_stream]; - const readableController = stream[_readable][_controller]; - readableStreamDefaultControllerClose( - /** @type {ReadableStreamDefaultController} */ readableController, - ); - const error = new TypeError("The stream has been terminated."); - transformStreamErrorWritableAndUnblockWrite(stream, error); - } - - /** - * @param {TransformStream} stream - * @param {any=} reason - * @returns {Promise} - */ - function transformStreamDefaultSinkAbortAlgorithm(stream, reason) { - transformStreamError(stream, reason); - return resolvePromiseWith(undefined); - } - - /** - * @template I - * @template O - * @param {TransformStream} stream - * @returns {Promise} - */ - function transformStreamDefaultSinkCloseAlgorithm(stream) { - const readable = stream[_readable]; - const controller = stream[_controller]; - const flushPromise = controller[_flushAlgorithm](controller); - transformStreamDefaultControllerClearAlgorithms(controller); - return transformPromiseWith(flushPromise, () => { - if (readable[_state] === "errored") { - throw readable[_storedError]; - } - readableStreamDefaultControllerClose( - /** @type {ReadableStreamDefaultController} */ readable[_controller], - ); - }, (r) => { - transformStreamError(stream, r); - throw readable[_storedError]; - }); - } - - /** - * @template I - * @template O - * @param {TransformStream} stream - * @param {I} chunk - * @returns {Promise} - */ - function transformStreamDefaultSinkWriteAlgorithm(stream, chunk) { - assert(stream[_writable][_state] === "writable"); - const controller = stream[_controller]; - if (stream[_backpressure] === true) { - const backpressureChangePromise = stream[_backpressureChangePromise]; - assert(backpressureChangePromise !== undefined); - return transformPromiseWith(backpressureChangePromise.promise, () => { - const writable = stream[_writable]; - const state = writable[_state]; - if (state === "erroring") { - throw writable[_storedError]; - } - assert(state === "writable"); - return transformStreamDefaultControllerPerformTransform( - controller, - chunk, - ); - }); - } - return transformStreamDefaultControllerPerformTransform(controller, chunk); - } - - /** - * @param {TransformStream} stream - * @returns {Promise} - */ - function transformStreamDefaultSourcePullAlgorithm(stream) { - assert(stream[_backpressure] === true); - assert(stream[_backpressureChangePromise] !== undefined); - transformStreamSetBackpressure(stream, false); - return stream[_backpressureChangePromise].promise; - } - - /** - * @param {TransformStream} stream - * @param {any=} e - */ - function transformStreamError(stream, e) { - readableStreamDefaultControllerError( - /** @type {ReadableStreamDefaultController} */ stream[_readable][ - _controller - ], - e, - ); - transformStreamErrorWritableAndUnblockWrite(stream, e); - } - - /** - * @param {TransformStream} stream - * @param {any=} e - */ - function transformStreamErrorWritableAndUnblockWrite(stream, e) { - transformStreamDefaultControllerClearAlgorithms(stream[_controller]); - writableStreamDefaultControllerErrorIfNeeded( - stream[_writable][_controller], - e, - ); - if (stream[_backpressure] === true) { - transformStreamSetBackpressure(stream, false); - } - } - - /** - * @param {TransformStream} stream - * @param {boolean} backpressure - */ - function transformStreamSetBackpressure(stream, backpressure) { - assert(stream[_backpressure] !== backpressure); - if (stream[_backpressureChangePromise] !== undefined) { - stream[_backpressureChangePromise].resolve(undefined); - } - stream[_backpressureChangePromise] = new Deferred(); - stream[_backpressure] = backpressure; - } - - /** - * @param {WritableStream} stream - * @param {any=} reason - * @returns {Promise} - */ - function writableStreamAbort(stream, reason) { - const state = stream[_state]; - if (state === "closed" || state === "errored") { - return resolvePromiseWith(undefined); - } - stream[_controller][_signal][signalAbort](reason); - if (state === "closed" || state === "errored") { - return resolvePromiseWith(undefined); - } - if (stream[_pendingAbortRequest] !== undefined) { - return stream[_pendingAbortRequest].deferred.promise; - } - assert(state === "writable" || state === "erroring"); - let wasAlreadyErroring = false; - if (state === "erroring") { - wasAlreadyErroring = true; - reason = undefined; - } - /** Deferred */ - const deferred = new Deferred(); - stream[_pendingAbortRequest] = { - deferred, - reason, - wasAlreadyErroring, - }; - if (wasAlreadyErroring === false) { - writableStreamStartErroring(stream, reason); - } - return deferred.promise; - } - - /** - * @param {WritableStream} stream - * @returns {Promise} - */ - function writableStreamAddWriteRequest(stream) { - assert(isWritableStreamLocked(stream) === true); - assert(stream[_state] === "writable"); - /** @type {Deferred} */ - const deferred = new Deferred(); - ArrayPrototypePush(stream[_writeRequests], deferred); - return deferred.promise; - } - - /** - * @param {WritableStream} stream - * @returns {Promise} - */ - function writableStreamClose(stream) { - const state = stream[_state]; - if (state === "closed" || state === "errored") { - return PromiseReject( - new TypeError("Writable stream is closed or errored."), - ); - } - assert(state === "writable" || state === "erroring"); - assert(writableStreamCloseQueuedOrInFlight(stream) === false); - /** @type {Deferred} */ - const deferred = new Deferred(); - stream[_closeRequest] = deferred; - const writer = stream[_writer]; - if ( - writer !== undefined && stream[_backpressure] === true && - state === "writable" - ) { - writer[_readyPromise].resolve(undefined); - } - writableStreamDefaultControllerClose(stream[_controller]); - return deferred.promise; - } - - /** - * @param {WritableStream} stream - * @returns {boolean} - */ - function writableStreamCloseQueuedOrInFlight(stream) { - if ( - stream[_closeRequest] === undefined && - stream[_inFlightCloseRequest] === undefined - ) { - return false; - } - return true; - } - - /** - * @param {WritableStream} stream - * @param {any=} error - */ - function writableStreamDealWithRejection(stream, error) { - const state = stream[_state]; - if (state === "writable") { - writableStreamStartErroring(stream, error); - return; - } - assert(state === "erroring"); - writableStreamFinishErroring(stream); - } - - /** - * @template W - * @param {WritableStreamDefaultController} controller - */ - function writableStreamDefaultControllerAdvanceQueueIfNeeded(controller) { - const stream = controller[_stream]; - if (controller[_started] === false) { - return; - } - if (stream[_inFlightWriteRequest] !== undefined) { - return; - } - const state = stream[_state]; - assert(state !== "closed" && state !== "errored"); - if (state === "erroring") { - writableStreamFinishErroring(stream); - return; - } - if (controller[_queue].length === 0) { - return; - } - const value = peekQueueValue(controller); - if (value === _close) { - writableStreamDefaultControllerProcessClose(controller); - } else { - writableStreamDefaultControllerProcessWrite(controller, value); - } - } - - function writableStreamDefaultControllerClearAlgorithms(controller) { - controller[_writeAlgorithm] = undefined; - controller[_closeAlgorithm] = undefined; - controller[_abortAlgorithm] = undefined; - controller[_strategySizeAlgorithm] = undefined; - } - - /** @param {WritableStreamDefaultController} controller */ - function writableStreamDefaultControllerClose(controller) { - enqueueValueWithSize(controller, _close, 0); - writableStreamDefaultControllerAdvanceQueueIfNeeded(controller); - } - - /** - * @param {WritableStreamDefaultController} controller - * @param {any} error - */ - function writableStreamDefaultControllerError(controller, error) { - const stream = controller[_stream]; - assert(stream[_state] === "writable"); - writableStreamDefaultControllerClearAlgorithms(controller); - writableStreamStartErroring(stream, error); - } - - /** - * @param {WritableStreamDefaultController} controller - * @param {any} error - */ - function writableStreamDefaultControllerErrorIfNeeded(controller, error) { - if (controller[_stream][_state] === "writable") { - writableStreamDefaultControllerError(controller, error); - } - } - - /** - * @param {WritableStreamDefaultController} controller - * @returns {boolean} - */ - function writableStreamDefaultControllerGetBackpressure(controller) { - const desiredSize = writableStreamDefaultControllerGetDesiredSize( - controller, - ); - return desiredSize <= 0; - } - - /** - * @template W - * @param {WritableStreamDefaultController} controller - * @param {W} chunk - * @returns {number} - */ - function writableStreamDefaultControllerGetChunkSize(controller, chunk) { - let value; - try { - value = controller[_strategySizeAlgorithm](chunk); - } catch (e) { - writableStreamDefaultControllerErrorIfNeeded(controller, e); - return 1; - } - return value; - } - - /** - * @param {WritableStreamDefaultController} controller - * @returns {number} - */ - function writableStreamDefaultControllerGetDesiredSize(controller) { - return controller[_strategyHWM] - controller[_queueTotalSize]; - } - - /** @param {WritableStreamDefaultController} controller */ - function writableStreamDefaultControllerProcessClose(controller) { - const stream = controller[_stream]; - writableStreamMarkCloseRequestInFlight(stream); - dequeueValue(controller); - assert(controller[_queue].length === 0); - const sinkClosePromise = controller[_closeAlgorithm](); - writableStreamDefaultControllerClearAlgorithms(controller); - uponPromise(sinkClosePromise, () => { - writableStreamFinishInFlightClose(stream); - }, (reason) => { - writableStreamFinishInFlightCloseWithError(stream, reason); - }); - } - - /** - * @template W - * @param {WritableStreamDefaultController} controller - * @param {W} chunk - */ - function writableStreamDefaultControllerProcessWrite(controller, chunk) { - const stream = controller[_stream]; - writableStreamMarkFirstWriteRequestInFlight(stream); - const sinkWritePromise = controller[_writeAlgorithm](chunk, controller); - uponPromise(sinkWritePromise, () => { - writableStreamFinishInFlightWrite(stream); - const state = stream[_state]; - assert(state === "writable" || state === "erroring"); - dequeueValue(controller); - if ( - writableStreamCloseQueuedOrInFlight(stream) === false && - state === "writable" - ) { - const backpressure = writableStreamDefaultControllerGetBackpressure( - controller, - ); - writableStreamUpdateBackpressure(stream, backpressure); - } - writableStreamDefaultControllerAdvanceQueueIfNeeded(controller); - }, (reason) => { - if (stream[_state] === "writable") { - writableStreamDefaultControllerClearAlgorithms(controller); - } - writableStreamFinishInFlightWriteWithError(stream, reason); - }); - } - - /** - * @template W - * @param {WritableStreamDefaultController} controller - * @param {W} chunk - * @param {number} chunkSize - */ - function writableStreamDefaultControllerWrite(controller, chunk, chunkSize) { - try { - enqueueValueWithSize(controller, chunk, chunkSize); - } catch (e) { - writableStreamDefaultControllerErrorIfNeeded(controller, e); - return; - } - const stream = controller[_stream]; - if ( - writableStreamCloseQueuedOrInFlight(stream) === false && - stream[_state] === "writable" - ) { - const backpressure = writableStreamDefaultControllerGetBackpressure( - controller, + if (underlyingSinkDict.abort !== undefined) { + abortAlgorithm = (reason) => + webidl.invokeCallbackFunction( + underlyingSinkDict.abort, + [reason], + underlyingSink, + webidl.converters["Promise"], + { + prefix: + "Failed to call 'abortAlgorithm' on 'WritableStreamDefaultController'", + returnsPromise: true, + }, ); - writableStreamUpdateBackpressure(stream, backpressure); - } - writableStreamDefaultControllerAdvanceQueueIfNeeded(controller); - } - - /** - * @param {WritableStreamDefaultWriter} writer - * @param {any=} reason - * @returns {Promise} - */ - function writableStreamDefaultWriterAbort(writer, reason) { - const stream = writer[_stream]; - assert(stream !== undefined); - return writableStreamAbort(stream, reason); - } - - /** - * @param {WritableStreamDefaultWriter} writer - * @returns {Promise} - */ - function writableStreamDefaultWriterClose(writer) { - const stream = writer[_stream]; - assert(stream !== undefined); - return writableStreamClose(stream); } - - /** - * @param {WritableStreamDefaultWriter} writer - * @returns {Promise} - */ - function writableStreamDefaultWriterCloseWithErrorPropagation(writer) { - const stream = writer[_stream]; - assert(stream !== undefined); - const state = stream[_state]; + setUpWritableStreamDefaultController( + stream, + controller, + startAlgorithm, + writeAlgorithm, + closeAlgorithm, + abortAlgorithm, + highWaterMark, + sizeAlgorithm, + ); +} + +/** + * @template W + * @param {WritableStreamDefaultWriter} writer + * @param {WritableStream} stream + */ +function setUpWritableStreamDefaultWriter(writer, stream) { + if (isWritableStreamLocked(stream) === true) { + throw new TypeError("The stream is already locked."); + } + writer[_stream] = stream; + stream[_writer] = writer; + const state = stream[_state]; + if (state === "writable") { if ( - writableStreamCloseQueuedOrInFlight(stream) === true || state === "closed" + writableStreamCloseQueuedOrInFlight(stream) === false && + stream[_backpressure] === true ) { - return resolvePromiseWith(undefined); - } - if (state === "errored") { - return PromiseReject(stream[_storedError]); - } - assert(state === "writable" || state === "erroring"); - return writableStreamDefaultWriterClose(writer); - } - - /** - * @param {WritableStreamDefaultWriter} writer - * @param {any=} error - */ - function writableStreamDefaultWriterEnsureClosedPromiseRejected( - writer, - error, - ) { - if (writer[_closedPromise].state === "pending") { - writer[_closedPromise].reject(error); - } else { - writer[_closedPromise] = new Deferred(); - writer[_closedPromise].reject(error); - } - setPromiseIsHandledToTrue(writer[_closedPromise].promise); - } - - /** - * @param {WritableStreamDefaultWriter} writer - * @param {any=} error - */ - function writableStreamDefaultWriterEnsureReadyPromiseRejected( - writer, - error, - ) { - if (writer[_readyPromise].state === "pending") { - writer[_readyPromise].reject(error); + writer[_readyPromise] = new Deferred(); } else { writer[_readyPromise] = new Deferred(); - writer[_readyPromise].reject(error); + writer[_readyPromise].resolve(undefined); } + writer[_closedPromise] = new Deferred(); + } else if (state === "erroring") { + writer[_readyPromise] = new Deferred(); + writer[_readyPromise].reject(stream[_storedError]); + setPromiseIsHandledToTrue(writer[_readyPromise].promise); + writer[_closedPromise] = new Deferred(); + } else if (state === "closed") { + writer[_readyPromise] = new Deferred(); + writer[_readyPromise].resolve(undefined); + writer[_closedPromise] = new Deferred(); + writer[_closedPromise].resolve(undefined); + } else { + assert(state === "errored"); + const storedError = stream[_storedError]; + writer[_readyPromise] = new Deferred(); + writer[_readyPromise].reject(storedError); setPromiseIsHandledToTrue(writer[_readyPromise].promise); + writer[_closedPromise] = new Deferred(); + writer[_closedPromise].reject(storedError); + setPromiseIsHandledToTrue(writer[_closedPromise].promise); } - - /** - * @param {WritableStreamDefaultWriter} writer - * @returns {number | null} - */ - function writableStreamDefaultWriterGetDesiredSize(writer) { - const stream = writer[_stream]; - const state = stream[_state]; - if (state === "errored" || state === "erroring") { - return null; - } - if (state === "closed") { - return 0; - } - return writableStreamDefaultControllerGetDesiredSize(stream[_controller]); +} + +/** @param {TransformStreamDefaultController} controller */ +function transformStreamDefaultControllerClearAlgorithms(controller) { + controller[_transformAlgorithm] = undefined; + controller[_flushAlgorithm] = undefined; +} + +/** + * @template O + * @param {TransformStreamDefaultController} controller + * @param {O} chunk + */ +function transformStreamDefaultControllerEnqueue(controller, chunk) { + const stream = controller[_stream]; + const readableController = stream[_readable][_controller]; + if ( + readableStreamDefaultControllerCanCloseOrEnqueue( + /** @type {ReadableStreamDefaultController} */ readableController, + ) === false + ) { + throw new TypeError("Readable stream is unavailable."); } - - /** @param {WritableStreamDefaultWriter} writer */ - function writableStreamDefaultWriterRelease(writer) { - const stream = writer[_stream]; - assert(stream !== undefined); - assert(stream[_writer] === writer); - const releasedError = new TypeError( - "The writer has already been released.", + try { + readableStreamDefaultControllerEnqueue( + /** @type {ReadableStreamDefaultController} */ readableController, + chunk, ); - writableStreamDefaultWriterEnsureReadyPromiseRejected( - writer, - releasedError, + } catch (e) { + transformStreamErrorWritableAndUnblockWrite(stream, e); + throw stream[_readable][_storedError]; + } + const backpressure = readableStreamDefaultcontrollerHasBackpressure( + /** @type {ReadableStreamDefaultController} */ readableController, + ); + if (backpressure !== stream[_backpressure]) { + assert(backpressure === true); + transformStreamSetBackpressure(stream, true); + } +} + +/** + * @param {TransformStreamDefaultController} controller + * @param {any=} e + */ +function transformStreamDefaultControllerError(controller, e) { + transformStreamError(controller[_stream], e); +} + +/** + * @template O + * @param {TransformStreamDefaultController} controller + * @param {any} chunk + * @returns {Promise} + */ +function transformStreamDefaultControllerPerformTransform(controller, chunk) { + const transformPromise = controller[_transformAlgorithm](chunk, controller); + return transformPromiseWith(transformPromise, undefined, (r) => { + transformStreamError(controller[_stream], r); + throw r; + }); +} + +/** @param {TransformStreamDefaultController} controller */ +function transformStreamDefaultControllerTerminate(controller) { + const stream = controller[_stream]; + const readableController = stream[_readable][_controller]; + readableStreamDefaultControllerClose( + /** @type {ReadableStreamDefaultController} */ readableController, + ); + const error = new TypeError("The stream has been terminated."); + transformStreamErrorWritableAndUnblockWrite(stream, error); +} + +/** + * @param {TransformStream} stream + * @param {any=} reason + * @returns {Promise} + */ +function transformStreamDefaultSinkAbortAlgorithm(stream, reason) { + transformStreamError(stream, reason); + return resolvePromiseWith(undefined); +} + +/** + * @template I + * @template O + * @param {TransformStream} stream + * @returns {Promise} + */ +function transformStreamDefaultSinkCloseAlgorithm(stream) { + const readable = stream[_readable]; + const controller = stream[_controller]; + const flushPromise = controller[_flushAlgorithm](controller); + transformStreamDefaultControllerClearAlgorithms(controller); + return transformPromiseWith(flushPromise, () => { + if (readable[_state] === "errored") { + throw readable[_storedError]; + } + readableStreamDefaultControllerClose( + /** @type {ReadableStreamDefaultController} */ readable[_controller], ); - writableStreamDefaultWriterEnsureClosedPromiseRejected( - writer, - releasedError, + }, (r) => { + transformStreamError(stream, r); + throw readable[_storedError]; + }); +} + +/** + * @template I + * @template O + * @param {TransformStream} stream + * @param {I} chunk + * @returns {Promise} + */ +function transformStreamDefaultSinkWriteAlgorithm(stream, chunk) { + assert(stream[_writable][_state] === "writable"); + const controller = stream[_controller]; + if (stream[_backpressure] === true) { + const backpressureChangePromise = stream[_backpressureChangePromise]; + assert(backpressureChangePromise !== undefined); + return transformPromiseWith(backpressureChangePromise.promise, () => { + const writable = stream[_writable]; + const state = writable[_state]; + if (state === "erroring") { + throw writable[_storedError]; + } + assert(state === "writable"); + return transformStreamDefaultControllerPerformTransform( + controller, + chunk, + ); + }); + } + return transformStreamDefaultControllerPerformTransform(controller, chunk); +} + +/** + * @param {TransformStream} stream + * @returns {Promise} + */ +function transformStreamDefaultSourcePullAlgorithm(stream) { + assert(stream[_backpressure] === true); + assert(stream[_backpressureChangePromise] !== undefined); + transformStreamSetBackpressure(stream, false); + return stream[_backpressureChangePromise].promise; +} + +/** + * @param {TransformStream} stream + * @param {any=} e + */ +function transformStreamError(stream, e) { + readableStreamDefaultControllerError( + /** @type {ReadableStreamDefaultController} */ stream[_readable][ + _controller + ], + e, + ); + transformStreamErrorWritableAndUnblockWrite(stream, e); +} + +/** + * @param {TransformStream} stream + * @param {any=} e + */ +function transformStreamErrorWritableAndUnblockWrite(stream, e) { + transformStreamDefaultControllerClearAlgorithms(stream[_controller]); + writableStreamDefaultControllerErrorIfNeeded( + stream[_writable][_controller], + e, + ); + if (stream[_backpressure] === true) { + transformStreamSetBackpressure(stream, false); + } +} + +/** + * @param {TransformStream} stream + * @param {boolean} backpressure + */ +function transformStreamSetBackpressure(stream, backpressure) { + assert(stream[_backpressure] !== backpressure); + if (stream[_backpressureChangePromise] !== undefined) { + stream[_backpressureChangePromise].resolve(undefined); + } + stream[_backpressureChangePromise] = new Deferred(); + stream[_backpressure] = backpressure; +} + +/** + * @param {WritableStream} stream + * @param {any=} reason + * @returns {Promise} + */ +function writableStreamAbort(stream, reason) { + const state = stream[_state]; + if (state === "closed" || state === "errored") { + return resolvePromiseWith(undefined); + } + stream[_controller][_signal][signalAbort](reason); + if (state === "closed" || state === "errored") { + return resolvePromiseWith(undefined); + } + if (stream[_pendingAbortRequest] !== undefined) { + return stream[_pendingAbortRequest].deferred.promise; + } + assert(state === "writable" || state === "erroring"); + let wasAlreadyErroring = false; + if (state === "erroring") { + wasAlreadyErroring = true; + reason = undefined; + } + /** Deferred */ + const deferred = new Deferred(); + stream[_pendingAbortRequest] = { + deferred, + reason, + wasAlreadyErroring, + }; + if (wasAlreadyErroring === false) { + writableStreamStartErroring(stream, reason); + } + return deferred.promise; +} + +/** + * @param {WritableStream} stream + * @returns {Promise} + */ +function writableStreamAddWriteRequest(stream) { + assert(isWritableStreamLocked(stream) === true); + assert(stream[_state] === "writable"); + /** @type {Deferred} */ + const deferred = new Deferred(); + ArrayPrototypePush(stream[_writeRequests], deferred); + return deferred.promise; +} + +/** + * @param {WritableStream} stream + * @returns {Promise} + */ +function writableStreamClose(stream) { + const state = stream[_state]; + if (state === "closed" || state === "errored") { + return PromiseReject( + new TypeError("Writable stream is closed or errored."), ); - stream[_writer] = undefined; - writer[_stream] = undefined; } + assert(state === "writable" || state === "erroring"); + assert(writableStreamCloseQueuedOrInFlight(stream) === false); + /** @type {Deferred} */ + const deferred = new Deferred(); + stream[_closeRequest] = deferred; + const writer = stream[_writer]; + if ( + writer !== undefined && stream[_backpressure] === true && + state === "writable" + ) { + writer[_readyPromise].resolve(undefined); + } + writableStreamDefaultControllerClose(stream[_controller]); + return deferred.promise; +} + +/** + * @param {WritableStream} stream + * @returns {boolean} + */ +function writableStreamCloseQueuedOrInFlight(stream) { + if ( + stream[_closeRequest] === undefined && + stream[_inFlightCloseRequest] === undefined + ) { + return false; + } + return true; +} - /** - * @template W - * @param {WritableStreamDefaultWriter} writer - * @param {W} chunk - * @returns {Promise} - */ - function writableStreamDefaultWriterWrite(writer, chunk) { - const stream = writer[_stream]; - assert(stream !== undefined); - const controller = stream[_controller]; - const chunkSize = writableStreamDefaultControllerGetChunkSize( - controller, - chunk, - ); - if (stream !== writer[_stream]) { - return PromiseReject(new TypeError("Writer's stream is unexpected.")); - } +/** + * @param {WritableStream} stream + * @param {any=} error + */ +function writableStreamDealWithRejection(stream, error) { + const state = stream[_state]; + if (state === "writable") { + writableStreamStartErroring(stream, error); + return; + } + assert(state === "erroring"); + writableStreamFinishErroring(stream); +} + +/** + * @template W + * @param {WritableStreamDefaultController} controller + */ +function writableStreamDefaultControllerAdvanceQueueIfNeeded(controller) { + const stream = controller[_stream]; + if (controller[_started] === false) { + return; + } + if (stream[_inFlightWriteRequest] !== undefined) { + return; + } + const state = stream[_state]; + assert(state !== "closed" && state !== "errored"); + if (state === "erroring") { + writableStreamFinishErroring(stream); + return; + } + if (controller[_queue].length === 0) { + return; + } + const value = peekQueueValue(controller); + if (value === _close) { + writableStreamDefaultControllerProcessClose(controller); + } else { + writableStreamDefaultControllerProcessWrite(controller, value); + } +} + +function writableStreamDefaultControllerClearAlgorithms(controller) { + controller[_writeAlgorithm] = undefined; + controller[_closeAlgorithm] = undefined; + controller[_abortAlgorithm] = undefined; + controller[_strategySizeAlgorithm] = undefined; +} + +/** @param {WritableStreamDefaultController} controller */ +function writableStreamDefaultControllerClose(controller) { + enqueueValueWithSize(controller, _close, 0); + writableStreamDefaultControllerAdvanceQueueIfNeeded(controller); +} + +/** + * @param {WritableStreamDefaultController} controller + * @param {any} error + */ +function writableStreamDefaultControllerError(controller, error) { + const stream = controller[_stream]; + assert(stream[_state] === "writable"); + writableStreamDefaultControllerClearAlgorithms(controller); + writableStreamStartErroring(stream, error); +} + +/** + * @param {WritableStreamDefaultController} controller + * @param {any} error + */ +function writableStreamDefaultControllerErrorIfNeeded(controller, error) { + if (controller[_stream][_state] === "writable") { + writableStreamDefaultControllerError(controller, error); + } +} + +/** + * @param {WritableStreamDefaultController} controller + * @returns {boolean} + */ +function writableStreamDefaultControllerGetBackpressure(controller) { + const desiredSize = writableStreamDefaultControllerGetDesiredSize( + controller, + ); + return desiredSize <= 0; +} + +/** + * @template W + * @param {WritableStreamDefaultController} controller + * @param {W} chunk + * @returns {number} + */ +function writableStreamDefaultControllerGetChunkSize(controller, chunk) { + let value; + try { + value = controller[_strategySizeAlgorithm](chunk); + } catch (e) { + writableStreamDefaultControllerErrorIfNeeded(controller, e); + return 1; + } + return value; +} + +/** + * @param {WritableStreamDefaultController} controller + * @returns {number} + */ +function writableStreamDefaultControllerGetDesiredSize(controller) { + return controller[_strategyHWM] - controller[_queueTotalSize]; +} + +/** @param {WritableStreamDefaultController} controller */ +function writableStreamDefaultControllerProcessClose(controller) { + const stream = controller[_stream]; + writableStreamMarkCloseRequestInFlight(stream); + dequeueValue(controller); + assert(controller[_queue].length === 0); + const sinkClosePromise = controller[_closeAlgorithm](); + writableStreamDefaultControllerClearAlgorithms(controller); + uponPromise(sinkClosePromise, () => { + writableStreamFinishInFlightClose(stream); + }, (reason) => { + writableStreamFinishInFlightCloseWithError(stream, reason); + }); +} + +/** + * @template W + * @param {WritableStreamDefaultController} controller + * @param {W} chunk + */ +function writableStreamDefaultControllerProcessWrite(controller, chunk) { + const stream = controller[_stream]; + writableStreamMarkFirstWriteRequestInFlight(stream); + const sinkWritePromise = controller[_writeAlgorithm](chunk, controller); + uponPromise(sinkWritePromise, () => { + writableStreamFinishInFlightWrite(stream); const state = stream[_state]; - if (state === "errored") { - return PromiseReject(stream[_storedError]); - } + assert(state === "writable" || state === "erroring"); + dequeueValue(controller); if ( - writableStreamCloseQueuedOrInFlight(stream) === true || state === "closed" + writableStreamCloseQueuedOrInFlight(stream) === false && + state === "writable" ) { - return PromiseReject( - new TypeError("The stream is closing or is closed."), + const backpressure = writableStreamDefaultControllerGetBackpressure( + controller, ); + writableStreamUpdateBackpressure(stream, backpressure); } - if (state === "erroring") { - return PromiseReject(stream[_storedError]); + writableStreamDefaultControllerAdvanceQueueIfNeeded(controller); + }, (reason) => { + if (stream[_state] === "writable") { + writableStreamDefaultControllerClearAlgorithms(controller); } - assert(state === "writable"); - const promise = writableStreamAddWriteRequest(stream); - writableStreamDefaultControllerWrite(controller, chunk, chunkSize); - return promise; + writableStreamFinishInFlightWriteWithError(stream, reason); + }); +} + +/** + * @template W + * @param {WritableStreamDefaultController} controller + * @param {W} chunk + * @param {number} chunkSize + */ +function writableStreamDefaultControllerWrite(controller, chunk, chunkSize) { + try { + enqueueValueWithSize(controller, chunk, chunkSize); + } catch (e) { + writableStreamDefaultControllerErrorIfNeeded(controller, e); + return; + } + const stream = controller[_stream]; + if ( + writableStreamCloseQueuedOrInFlight(stream) === false && + stream[_state] === "writable" + ) { + const backpressure = writableStreamDefaultControllerGetBackpressure( + controller, + ); + writableStreamUpdateBackpressure(stream, backpressure); } - - /** @param {WritableStream} stream */ - function writableStreamFinishErroring(stream) { - assert(stream[_state] === "erroring"); - assert(writableStreamHasOperationMarkedInFlight(stream) === false); - stream[_state] = "errored"; - stream[_controller][_errorSteps](); - const storedError = stream[_storedError]; - const writeRequests = stream[_writeRequests]; - for (let i = 0; i < writeRequests.length; ++i) { - const writeRequest = writeRequests[i]; - writeRequest.reject(storedError); - } - stream[_writeRequests] = []; - if (stream[_pendingAbortRequest] === undefined) { - writableStreamRejectCloseAndClosedPromiseIfNeeded(stream); - return; - } - const abortRequest = stream[_pendingAbortRequest]; - stream[_pendingAbortRequest] = undefined; - if (abortRequest.wasAlreadyErroring === true) { - abortRequest.deferred.reject(storedError); - writableStreamRejectCloseAndClosedPromiseIfNeeded(stream); - return; - } - const promise = stream[_controller][_abortSteps](abortRequest.reason); - uponPromise(promise, () => { - abortRequest.deferred.resolve(undefined); - writableStreamRejectCloseAndClosedPromiseIfNeeded(stream); - }, (reason) => { - abortRequest.deferred.reject(reason); - writableStreamRejectCloseAndClosedPromiseIfNeeded(stream); - }); + writableStreamDefaultControllerAdvanceQueueIfNeeded(controller); +} + +/** + * @param {WritableStreamDefaultWriter} writer + * @param {any=} reason + * @returns {Promise} + */ +function writableStreamDefaultWriterAbort(writer, reason) { + const stream = writer[_stream]; + assert(stream !== undefined); + return writableStreamAbort(stream, reason); +} + +/** + * @param {WritableStreamDefaultWriter} writer + * @returns {Promise} + */ +function writableStreamDefaultWriterClose(writer) { + const stream = writer[_stream]; + assert(stream !== undefined); + return writableStreamClose(stream); +} + +/** + * @param {WritableStreamDefaultWriter} writer + * @returns {Promise} + */ +function writableStreamDefaultWriterCloseWithErrorPropagation(writer) { + const stream = writer[_stream]; + assert(stream !== undefined); + const state = stream[_state]; + if ( + writableStreamCloseQueuedOrInFlight(stream) === true || state === "closed" + ) { + return resolvePromiseWith(undefined); } - - /** @param {WritableStream} stream */ - function writableStreamFinishInFlightClose(stream) { - assert(stream[_inFlightCloseRequest] !== undefined); - stream[_inFlightCloseRequest].resolve(undefined); - stream[_inFlightCloseRequest] = undefined; - const state = stream[_state]; - assert(state === "writable" || state === "erroring"); - if (state === "erroring") { - stream[_storedError] = undefined; - if (stream[_pendingAbortRequest] !== undefined) { - stream[_pendingAbortRequest].deferred.resolve(undefined); - stream[_pendingAbortRequest] = undefined; - } - } - stream[_state] = "closed"; - const writer = stream[_writer]; - if (writer !== undefined) { - writer[_closedPromise].resolve(undefined); - } - assert(stream[_pendingAbortRequest] === undefined); - assert(stream[_storedError] === undefined); + if (state === "errored") { + return PromiseReject(stream[_storedError]); + } + assert(state === "writable" || state === "erroring"); + return writableStreamDefaultWriterClose(writer); +} + +/** + * @param {WritableStreamDefaultWriter} writer + * @param {any=} error + */ +function writableStreamDefaultWriterEnsureClosedPromiseRejected( + writer, + error, +) { + if (writer[_closedPromise].state === "pending") { + writer[_closedPromise].reject(error); + } else { + writer[_closedPromise] = new Deferred(); + writer[_closedPromise].reject(error); + } + setPromiseIsHandledToTrue(writer[_closedPromise].promise); +} + +/** + * @param {WritableStreamDefaultWriter} writer + * @param {any=} error + */ +function writableStreamDefaultWriterEnsureReadyPromiseRejected( + writer, + error, +) { + if (writer[_readyPromise].state === "pending") { + writer[_readyPromise].reject(error); + } else { + writer[_readyPromise] = new Deferred(); + writer[_readyPromise].reject(error); + } + setPromiseIsHandledToTrue(writer[_readyPromise].promise); +} + +/** + * @param {WritableStreamDefaultWriter} writer + * @returns {number | null} + */ +function writableStreamDefaultWriterGetDesiredSize(writer) { + const stream = writer[_stream]; + const state = stream[_state]; + if (state === "errored" || state === "erroring") { + return null; + } + if (state === "closed") { + return 0; + } + return writableStreamDefaultControllerGetDesiredSize(stream[_controller]); +} + +/** @param {WritableStreamDefaultWriter} writer */ +function writableStreamDefaultWriterRelease(writer) { + const stream = writer[_stream]; + assert(stream !== undefined); + assert(stream[_writer] === writer); + const releasedError = new TypeError( + "The writer has already been released.", + ); + writableStreamDefaultWriterEnsureReadyPromiseRejected( + writer, + releasedError, + ); + writableStreamDefaultWriterEnsureClosedPromiseRejected( + writer, + releasedError, + ); + stream[_writer] = undefined; + writer[_stream] = undefined; +} + +/** + * @template W + * @param {WritableStreamDefaultWriter} writer + * @param {W} chunk + * @returns {Promise} + */ +function writableStreamDefaultWriterWrite(writer, chunk) { + const stream = writer[_stream]; + assert(stream !== undefined); + const controller = stream[_controller]; + const chunkSize = writableStreamDefaultControllerGetChunkSize( + controller, + chunk, + ); + if (stream !== writer[_stream]) { + return PromiseReject(new TypeError("Writer's stream is unexpected.")); } - - /** - * @param {WritableStream} stream - * @param {any=} error - */ - function writableStreamFinishInFlightCloseWithError(stream, error) { - assert(stream[_inFlightCloseRequest] !== undefined); - stream[_inFlightCloseRequest].reject(error); - stream[_inFlightCloseRequest] = undefined; - assert(stream[_state] === "writable" || stream[_state] === "erroring"); + const state = stream[_state]; + if (state === "errored") { + return PromiseReject(stream[_storedError]); + } + if ( + writableStreamCloseQueuedOrInFlight(stream) === true || state === "closed" + ) { + return PromiseReject( + new TypeError("The stream is closing or is closed."), + ); + } + if (state === "erroring") { + return PromiseReject(stream[_storedError]); + } + assert(state === "writable"); + const promise = writableStreamAddWriteRequest(stream); + writableStreamDefaultControllerWrite(controller, chunk, chunkSize); + return promise; +} + +/** @param {WritableStream} stream */ +function writableStreamFinishErroring(stream) { + assert(stream[_state] === "erroring"); + assert(writableStreamHasOperationMarkedInFlight(stream) === false); + stream[_state] = "errored"; + stream[_controller][_errorSteps](); + const storedError = stream[_storedError]; + const writeRequests = stream[_writeRequests]; + for (let i = 0; i < writeRequests.length; ++i) { + const writeRequest = writeRequests[i]; + writeRequest.reject(storedError); + } + stream[_writeRequests] = []; + if (stream[_pendingAbortRequest] === undefined) { + writableStreamRejectCloseAndClosedPromiseIfNeeded(stream); + return; + } + const abortRequest = stream[_pendingAbortRequest]; + stream[_pendingAbortRequest] = undefined; + if (abortRequest.wasAlreadyErroring === true) { + abortRequest.deferred.reject(storedError); + writableStreamRejectCloseAndClosedPromiseIfNeeded(stream); + return; + } + const promise = stream[_controller][_abortSteps](abortRequest.reason); + uponPromise(promise, () => { + abortRequest.deferred.resolve(undefined); + writableStreamRejectCloseAndClosedPromiseIfNeeded(stream); + }, (reason) => { + abortRequest.deferred.reject(reason); + writableStreamRejectCloseAndClosedPromiseIfNeeded(stream); + }); +} + +/** @param {WritableStream} stream */ +function writableStreamFinishInFlightClose(stream) { + assert(stream[_inFlightCloseRequest] !== undefined); + stream[_inFlightCloseRequest].resolve(undefined); + stream[_inFlightCloseRequest] = undefined; + const state = stream[_state]; + assert(state === "writable" || state === "erroring"); + if (state === "erroring") { + stream[_storedError] = undefined; if (stream[_pendingAbortRequest] !== undefined) { - stream[_pendingAbortRequest].deferred.reject(error); + stream[_pendingAbortRequest].deferred.resolve(undefined); stream[_pendingAbortRequest] = undefined; } - writableStreamDealWithRejection(stream, error); - } - - /** @param {WritableStream} stream */ - function writableStreamFinishInFlightWrite(stream) { - assert(stream[_inFlightWriteRequest] !== undefined); - stream[_inFlightWriteRequest].resolve(undefined); - stream[_inFlightWriteRequest] = undefined; } - - /** - * @param {WritableStream} stream - * @param {any=} error - */ - function writableStreamFinishInFlightWriteWithError(stream, error) { - assert(stream[_inFlightWriteRequest] !== undefined); - stream[_inFlightWriteRequest].reject(error); - stream[_inFlightWriteRequest] = undefined; - assert(stream[_state] === "writable" || stream[_state] === "erroring"); - writableStreamDealWithRejection(stream, error); + stream[_state] = "closed"; + const writer = stream[_writer]; + if (writer !== undefined) { + writer[_closedPromise].resolve(undefined); + } + assert(stream[_pendingAbortRequest] === undefined); + assert(stream[_storedError] === undefined); +} + +/** + * @param {WritableStream} stream + * @param {any=} error + */ +function writableStreamFinishInFlightCloseWithError(stream, error) { + assert(stream[_inFlightCloseRequest] !== undefined); + stream[_inFlightCloseRequest].reject(error); + stream[_inFlightCloseRequest] = undefined; + assert(stream[_state] === "writable" || stream[_state] === "erroring"); + if (stream[_pendingAbortRequest] !== undefined) { + stream[_pendingAbortRequest].deferred.reject(error); + stream[_pendingAbortRequest] = undefined; } - - /** - * @param {WritableStream} stream - * @returns {boolean} - */ - function writableStreamHasOperationMarkedInFlight(stream) { - if ( - stream[_inFlightWriteRequest] === undefined && - stream[_inFlightCloseRequest] === undefined - ) { - return false; - } - return true; + writableStreamDealWithRejection(stream, error); +} + +/** @param {WritableStream} stream */ +function writableStreamFinishInFlightWrite(stream) { + assert(stream[_inFlightWriteRequest] !== undefined); + stream[_inFlightWriteRequest].resolve(undefined); + stream[_inFlightWriteRequest] = undefined; +} + +/** + * @param {WritableStream} stream + * @param {any=} error + */ +function writableStreamFinishInFlightWriteWithError(stream, error) { + assert(stream[_inFlightWriteRequest] !== undefined); + stream[_inFlightWriteRequest].reject(error); + stream[_inFlightWriteRequest] = undefined; + assert(stream[_state] === "writable" || stream[_state] === "erroring"); + writableStreamDealWithRejection(stream, error); +} + +/** + * @param {WritableStream} stream + * @returns {boolean} + */ +function writableStreamHasOperationMarkedInFlight(stream) { + if ( + stream[_inFlightWriteRequest] === undefined && + stream[_inFlightCloseRequest] === undefined + ) { + return false; } - - /** @param {WritableStream} stream */ - function writableStreamMarkCloseRequestInFlight(stream) { + return true; +} + +/** @param {WritableStream} stream */ +function writableStreamMarkCloseRequestInFlight(stream) { + assert(stream[_inFlightCloseRequest] === undefined); + assert(stream[_closeRequest] !== undefined); + stream[_inFlightCloseRequest] = stream[_closeRequest]; + stream[_closeRequest] = undefined; +} + +/** + * @template W + * @param {WritableStream} stream + */ +function writableStreamMarkFirstWriteRequestInFlight(stream) { + assert(stream[_inFlightWriteRequest] === undefined); + assert(stream[_writeRequests].length); + const writeRequest = stream[_writeRequests].shift(); + stream[_inFlightWriteRequest] = writeRequest; +} + +/** @param {WritableStream} stream */ +function writableStreamRejectCloseAndClosedPromiseIfNeeded(stream) { + assert(stream[_state] === "errored"); + if (stream[_closeRequest] !== undefined) { assert(stream[_inFlightCloseRequest] === undefined); - assert(stream[_closeRequest] !== undefined); - stream[_inFlightCloseRequest] = stream[_closeRequest]; + stream[_closeRequest].reject(stream[_storedError]); stream[_closeRequest] = undefined; } - - /** - * @template W - * @param {WritableStream} stream - */ - function writableStreamMarkFirstWriteRequestInFlight(stream) { - assert(stream[_inFlightWriteRequest] === undefined); - assert(stream[_writeRequests].length); - const writeRequest = stream[_writeRequests].shift(); - stream[_inFlightWriteRequest] = writeRequest; + const writer = stream[_writer]; + if (writer !== undefined) { + writer[_closedPromise].reject(stream[_storedError]); + setPromiseIsHandledToTrue(writer[_closedPromise].promise); } - - /** @param {WritableStream} stream */ - function writableStreamRejectCloseAndClosedPromiseIfNeeded(stream) { - assert(stream[_state] === "errored"); - if (stream[_closeRequest] !== undefined) { - assert(stream[_inFlightCloseRequest] === undefined); - stream[_closeRequest].reject(stream[_storedError]); - stream[_closeRequest] = undefined; - } - const writer = stream[_writer]; - if (writer !== undefined) { - writer[_closedPromise].reject(stream[_storedError]); - setPromiseIsHandledToTrue(writer[_closedPromise].promise); - } +} + +/** + * @param {WritableStream} stream + * @param {any=} reason + */ +function writableStreamStartErroring(stream, reason) { + assert(stream[_storedError] === undefined); + assert(stream[_state] === "writable"); + const controller = stream[_controller]; + assert(controller !== undefined); + stream[_state] = "erroring"; + stream[_storedError] = reason; + const writer = stream[_writer]; + if (writer !== undefined) { + writableStreamDefaultWriterEnsureReadyPromiseRejected(writer, reason); + } + if ( + writableStreamHasOperationMarkedInFlight(stream) === false && + controller[_started] === true + ) { + writableStreamFinishErroring(stream); } - - /** - * @param {WritableStream} stream - * @param {any=} reason - */ - function writableStreamStartErroring(stream, reason) { - assert(stream[_storedError] === undefined); - assert(stream[_state] === "writable"); - const controller = stream[_controller]; - assert(controller !== undefined); - stream[_state] = "erroring"; - stream[_storedError] = reason; - const writer = stream[_writer]; - if (writer !== undefined) { - writableStreamDefaultWriterEnsureReadyPromiseRejected(writer, reason); - } - if ( - writableStreamHasOperationMarkedInFlight(stream) === false && - controller[_started] === true - ) { - writableStreamFinishErroring(stream); +} + +/** + * @param {WritableStream} stream + * @param {boolean} backpressure + */ +function writableStreamUpdateBackpressure(stream, backpressure) { + assert(stream[_state] === "writable"); + assert(writableStreamCloseQueuedOrInFlight(stream) === false); + const writer = stream[_writer]; + if (writer !== undefined && backpressure !== stream[_backpressure]) { + if (backpressure === true) { + writer[_readyPromise] = new Deferred(); + } else { + assert(backpressure === false); + writer[_readyPromise].resolve(undefined); } } - - /** - * @param {WritableStream} stream - * @param {boolean} backpressure - */ - function writableStreamUpdateBackpressure(stream, backpressure) { - assert(stream[_state] === "writable"); - assert(writableStreamCloseQueuedOrInFlight(stream) === false); - const writer = stream[_writer]; - if (writer !== undefined && backpressure !== stream[_backpressure]) { - if (backpressure === true) { - writer[_readyPromise] = new Deferred(); - } else { - assert(backpressure === false); - writer[_readyPromise].resolve(undefined); + stream[_backpressure] = backpressure; +} + +/** + * @template T + * @param {T} value + * @param {boolean} done + * @returns {IteratorResult} + */ +function createIteratorResult(value, done) { + const result = ObjectCreate(ObjectPrototype); + ObjectDefineProperties(result, { + value: { value, writable: true, enumerable: true, configurable: true }, + done: { + value: done, + writable: true, + enumerable: true, + configurable: true, + }, + }); + return result; +} + +/** @type {AsyncIterator} */ +const asyncIteratorPrototype = ObjectGetPrototypeOf(AsyncGeneratorPrototype); + +const _iteratorNext = Symbol("[[iteratorNext]]"); +const _iteratorFinished = Symbol("[[iteratorFinished]]"); + +/** @type {AsyncIterator} */ +const readableStreamAsyncIteratorPrototype = ObjectSetPrototypeOf({ + /** @returns {Promise>} */ + next() { + /** @type {ReadableStreamDefaultReader} */ + const reader = this[_reader]; + function nextSteps() { + if (reader[_iteratorFinished]) { + return PromiseResolve(createIteratorResult(undefined, true)); } - } - stream[_backpressure] = backpressure; - } - - /** - * @template T - * @param {T} value - * @param {boolean} done - * @returns {IteratorResult} - */ - function createIteratorResult(value, done) { - const result = ObjectCreate(ObjectPrototype); - ObjectDefineProperties(result, { - value: { value, writable: true, enumerable: true, configurable: true }, - done: { - value: done, - writable: true, - enumerable: true, - configurable: true, - }, - }); - return result; - } - - /** @type {AsyncIterator} */ - const asyncIteratorPrototype = ObjectGetPrototypeOf(AsyncGeneratorPrototype); - const _iteratorNext = Symbol("[[iteratorNext]]"); - const _iteratorFinished = Symbol("[[iteratorFinished]]"); - - /** @type {AsyncIterator} */ - const readableStreamAsyncIteratorPrototype = ObjectSetPrototypeOf({ - /** @returns {Promise>} */ - next() { - /** @type {ReadableStreamDefaultReader} */ - const reader = this[_reader]; - function nextSteps() { - if (reader[_iteratorFinished]) { - return PromiseResolve(createIteratorResult(undefined, true)); - } - - if (reader[_stream] === undefined) { - return PromiseReject( - new TypeError( - "Cannot get the next iteration result once the reader has been released.", - ), - ); - } - - /** @type {Deferred>} */ - const promise = new Deferred(); - /** @type {ReadRequest} */ - const readRequest = { - chunkSteps(chunk) { - promise.resolve(createIteratorResult(chunk, false)); - }, - closeSteps() { - readableStreamDefaultReaderRelease(reader); - promise.resolve(createIteratorResult(undefined, true)); - }, - errorSteps(e) { - readableStreamDefaultReaderRelease(reader); - promise.reject(e); - }, - }; - - readableStreamDefaultReaderRead(reader, readRequest); - return PromisePrototypeThen(promise.promise, (result) => { - reader[_iteratorNext] = null; - if (result.done === true) { - reader[_iteratorFinished] = true; - return createIteratorResult(undefined, true); - } - return result; - }, (reason) => { - reader[_iteratorNext] = null; - reader[_iteratorFinished] = true; - throw reason; - }); + if (reader[_stream] === undefined) { + return PromiseReject( + new TypeError( + "Cannot get the next iteration result once the reader has been released.", + ), + ); } - reader[_iteratorNext] = reader[_iteratorNext] - ? PromisePrototypeThen(reader[_iteratorNext], nextSteps, nextSteps) - : nextSteps(); - - return reader[_iteratorNext]; - }, - /** - * @param {unknown} arg - * @returns {Promise>} - */ - return(arg) { - /** @type {ReadableStreamDefaultReader} */ - const reader = this[_reader]; - const returnSteps = () => { - if (reader[_iteratorFinished]) { - return PromiseResolve(createIteratorResult(arg, true)); - } - reader[_iteratorFinished] = true; - - if (reader[_stream] === undefined) { - return PromiseResolve(createIteratorResult(undefined, true)); - } - assert(reader[_readRequests].length === 0); - if (this[_preventCancel] === false) { - const result = readableStreamReaderGenericCancel(reader, arg); + /** @type {Deferred>} */ + const promise = new Deferred(); + /** @type {ReadRequest} */ + const readRequest = { + chunkSteps(chunk) { + promise.resolve(createIteratorResult(chunk, false)); + }, + closeSteps() { readableStreamDefaultReaderRelease(reader); - return result; - } - readableStreamDefaultReaderRelease(reader); - return PromiseResolve(createIteratorResult(undefined, true)); + promise.resolve(createIteratorResult(undefined, true)); + }, + errorSteps(e) { + readableStreamDefaultReaderRelease(reader); + promise.reject(e); + }, }; - const returnPromise = reader[_iteratorNext] - ? PromisePrototypeThen(reader[_iteratorNext], returnSteps, returnSteps) - : returnSteps(); - return PromisePrototypeThen( - returnPromise, - () => createIteratorResult(arg, true), - ); - }, - }, asyncIteratorPrototype); - - class ByteLengthQueuingStrategy { - /** @param {{ highWaterMark: number }} init */ - constructor(init) { - const prefix = "Failed to construct 'ByteLengthQueuingStrategy'"; - webidl.requiredArguments(arguments.length, 1, { prefix }); - init = webidl.converters.QueuingStrategyInit(init, { - prefix, - context: "Argument 1", + readableStreamDefaultReaderRead(reader, readRequest); + return PromisePrototypeThen(promise.promise, (result) => { + reader[_iteratorNext] = null; + if (result.done === true) { + reader[_iteratorFinished] = true; + return createIteratorResult(undefined, true); + } + return result; + }, (reason) => { + reader[_iteratorNext] = null; + reader[_iteratorFinished] = true; + throw reason; }); - this[webidl.brand] = webidl.brand; - this[_globalObject] = window; - this[_highWaterMark] = init.highWaterMark; - } - - /** @returns {number} */ - get highWaterMark() { - webidl.assertBranded(this, ByteLengthQueuingStrategyPrototype); - return this[_highWaterMark]; } - /** @returns {(chunk: ArrayBufferView) => number} */ - get size() { - webidl.assertBranded(this, ByteLengthQueuingStrategyPrototype); - initializeByteLengthSizeFunction(this[_globalObject]); - return WeakMapPrototypeGet(byteSizeFunctionWeakMap, this[_globalObject]); - } - - [SymbolFor("Deno.customInspect")](inspect) { - return inspect(consoleInternal.createFilteredInspectProxy({ - object: this, - evaluate: ObjectPrototypeIsPrototypeOf( - ByteLengthQueuingStrategyPrototype, - this, - ), - keys: [ - "highWaterMark", - "size", - ], - })); - } - } + reader[_iteratorNext] = reader[_iteratorNext] + ? PromisePrototypeThen(reader[_iteratorNext], nextSteps, nextSteps) + : nextSteps(); - webidl.configurePrototype(ByteLengthQueuingStrategy); - const ByteLengthQueuingStrategyPrototype = - ByteLengthQueuingStrategy.prototype; + return reader[_iteratorNext]; + }, + /** + * @param {unknown} arg + * @returns {Promise>} + */ + return(arg) { + /** @type {ReadableStreamDefaultReader} */ + const reader = this[_reader]; + const returnSteps = () => { + if (reader[_iteratorFinished]) { + return PromiseResolve(createIteratorResult(arg, true)); + } + reader[_iteratorFinished] = true; - /** @type {WeakMap number>} */ - const byteSizeFunctionWeakMap = new WeakMap(); + if (reader[_stream] === undefined) { + return PromiseResolve(createIteratorResult(undefined, true)); + } + assert(reader[_readRequests].length === 0); + if (this[_preventCancel] === false) { + const result = readableStreamReaderGenericCancel(reader, arg); + readableStreamDefaultReaderRelease(reader); + return result; + } + readableStreamDefaultReaderRelease(reader); + return PromiseResolve(createIteratorResult(undefined, true)); + }; - function initializeByteLengthSizeFunction(globalObject) { - if (WeakMapPrototypeHas(byteSizeFunctionWeakMap, globalObject)) { - return; - } - const size = (chunk) => chunk.byteLength; - WeakMapPrototypeSet(byteSizeFunctionWeakMap, globalObject, size); + const returnPromise = reader[_iteratorNext] + ? PromisePrototypeThen(reader[_iteratorNext], returnSteps, returnSteps) + : returnSteps(); + return PromisePrototypeThen( + returnPromise, + () => createIteratorResult(arg, true), + ); + }, +}, asyncIteratorPrototype); + +class ByteLengthQueuingStrategy { + /** @param {{ highWaterMark: number }} init */ + constructor(init) { + const prefix = "Failed to construct 'ByteLengthQueuingStrategy'"; + webidl.requiredArguments(arguments.length, 1, { prefix }); + init = webidl.converters.QueuingStrategyInit(init, { + prefix, + context: "Argument 1", + }); + this[webidl.brand] = webidl.brand; + this[_globalObject] = globalThis; + this[_highWaterMark] = init.highWaterMark; } - class CountQueuingStrategy { - /** @param {{ highWaterMark: number }} init */ - constructor(init) { - const prefix = "Failed to construct 'CountQueuingStrategy'"; - webidl.requiredArguments(arguments.length, 1, { prefix }); - init = webidl.converters.QueuingStrategyInit(init, { - prefix, - context: "Argument 1", - }); - this[webidl.brand] = webidl.brand; - this[_globalObject] = window; - this[_highWaterMark] = init.highWaterMark; - } - - /** @returns {number} */ - get highWaterMark() { - webidl.assertBranded(this, CountQueuingStrategyPrototype); - return this[_highWaterMark]; - } + /** @returns {number} */ + get highWaterMark() { + webidl.assertBranded(this, ByteLengthQueuingStrategyPrototype); + return this[_highWaterMark]; + } - /** @returns {(chunk: any) => 1} */ - get size() { - webidl.assertBranded(this, CountQueuingStrategyPrototype); - initializeCountSizeFunction(this[_globalObject]); - return WeakMapPrototypeGet(countSizeFunctionWeakMap, this[_globalObject]); - } + /** @returns {(chunk: ArrayBufferView) => number} */ + get size() { + webidl.assertBranded(this, ByteLengthQueuingStrategyPrototype); + initializeByteLengthSizeFunction(this[_globalObject]); + return WeakMapPrototypeGet(byteSizeFunctionWeakMap, this[_globalObject]); + } - [SymbolFor("Deno.customInspect")](inspect) { - return inspect(consoleInternal.createFilteredInspectProxy({ - object: this, - evaluate: ObjectPrototypeIsPrototypeOf( - CountQueuingStrategyPrototype, - this, - ), - keys: [ - "highWaterMark", - "size", - ], - })); - } + [SymbolFor("Deno.customInspect")](inspect) { + return inspect(createFilteredInspectProxy({ + object: this, + evaluate: ObjectPrototypeIsPrototypeOf( + ByteLengthQueuingStrategyPrototype, + this, + ), + keys: [ + "highWaterMark", + "size", + ], + })); } +} - webidl.configurePrototype(CountQueuingStrategy); - const CountQueuingStrategyPrototype = CountQueuingStrategy.prototype; +webidl.configurePrototype(ByteLengthQueuingStrategy); +const ByteLengthQueuingStrategyPrototype = ByteLengthQueuingStrategy.prototype; - /** @type {WeakMap 1>} */ - const countSizeFunctionWeakMap = new WeakMap(); +/** @type {WeakMap number>} */ +const byteSizeFunctionWeakMap = new WeakMap(); - /** @param {typeof globalThis} globalObject */ - function initializeCountSizeFunction(globalObject) { - if (WeakMapPrototypeHas(countSizeFunctionWeakMap, globalObject)) { - return; - } - const size = () => 1; - WeakMapPrototypeSet(countSizeFunctionWeakMap, globalObject, size); - } - - const _resourceBacking = Symbol("[[resourceBacking]]"); - // This distinction exists to prevent unrefable streams being used in - // regular fast streams that are unaware of refability - const _resourceBackingUnrefable = Symbol("[[resourceBackingUnrefable]]"); - /** @template R */ - class ReadableStream { - /** @type {ReadableStreamDefaultController | ReadableByteStreamController} */ - [_controller]; - /** @type {boolean} */ - [_detached]; - /** @type {boolean} */ - [_disturbed]; - /** @type {ReadableStreamDefaultReader | ReadableStreamBYOBReader} */ - [_reader]; - /** @type {"readable" | "closed" | "errored"} */ - [_state]; - /** @type {any} */ - [_storedError]; - /** @type {{ rid: number, autoClose: boolean } | null} */ - [_resourceBacking] = null; - - /** - * @param {UnderlyingSource=} underlyingSource - * @param {QueuingStrategy=} strategy - */ - constructor(underlyingSource = undefined, strategy = undefined) { - const prefix = "Failed to construct 'ReadableStream'"; - if (underlyingSource !== undefined) { - underlyingSource = webidl.converters.object(underlyingSource, { - prefix, - context: "Argument 1", - }); - } else { - underlyingSource = null; - } - if (strategy !== undefined) { - strategy = webidl.converters.QueuingStrategy(strategy, { - prefix, - context: "Argument 2", - }); - } else { - strategy = {}; - } - this[webidl.brand] = webidl.brand; - let underlyingSourceDict = {}; - if (underlyingSource !== undefined) { - underlyingSourceDict = webidl.converters.UnderlyingSource( - underlyingSource, - { prefix, context: "underlyingSource" }, - ); - } - initializeReadableStream(this); - if (underlyingSourceDict.type === "bytes") { - if (strategy.size !== undefined) { - throw new RangeError( - `${prefix}: When underlying source is "bytes", strategy.size must be undefined.`, - ); - } - const highWaterMark = extractHighWaterMark(strategy, 0); - setUpReadableByteStreamControllerFromUnderlyingSource( - // @ts-ignore cannot easily assert this is ReadableStream - this, - underlyingSource, - underlyingSourceDict, - highWaterMark, - ); - } else { - assert(!(ReflectHas(underlyingSourceDict, "type"))); - const sizeAlgorithm = extractSizeAlgorithm(strategy); - const highWaterMark = extractHighWaterMark(strategy, 1); - setUpReadableStreamDefaultControllerFromUnderlyingSource( - this, - underlyingSource, - underlyingSourceDict, - highWaterMark, - sizeAlgorithm, - ); - } - } +function initializeByteLengthSizeFunction(globalObject) { + if (WeakMapPrototypeHas(byteSizeFunctionWeakMap, globalObject)) { + return; + } + const size = (chunk) => chunk.byteLength; + WeakMapPrototypeSet(byteSizeFunctionWeakMap, globalObject, size); +} - /** @returns {boolean} */ - get locked() { - webidl.assertBranded(this, ReadableStreamPrototype); - return isReadableStreamLocked(this); - } +class CountQueuingStrategy { + /** @param {{ highWaterMark: number }} init */ + constructor(init) { + const prefix = "Failed to construct 'CountQueuingStrategy'"; + webidl.requiredArguments(arguments.length, 1, { prefix }); + init = webidl.converters.QueuingStrategyInit(init, { + prefix, + context: "Argument 1", + }); + this[webidl.brand] = webidl.brand; + this[_globalObject] = globalThis; + this[_highWaterMark] = init.highWaterMark; + } - /** - * @param {any=} reason - * @returns {Promise} - */ - cancel(reason = undefined) { - try { - webidl.assertBranded(this, ReadableStreamPrototype); - if (reason !== undefined) { - reason = webidl.converters.any(reason); - } - } catch (err) { - return PromiseReject(err); - } - if (isReadableStreamLocked(this)) { - return PromiseReject( - new TypeError("Cannot cancel a locked ReadableStream."), - ); - } - return readableStreamCancel(this, reason); - } + /** @returns {number} */ + get highWaterMark() { + webidl.assertBranded(this, CountQueuingStrategyPrototype); + return this[_highWaterMark]; + } - /** - * @param {ReadableStreamGetReaderOptions=} options - * @returns {ReadableStreamDefaultReader | ReadableStreamBYOBReader} - */ - getReader(options = undefined) { - webidl.assertBranded(this, ReadableStreamPrototype); - const prefix = "Failed to execute 'getReader' on 'ReadableStream'"; - if (options !== undefined) { - options = webidl.converters.ReadableStreamGetReaderOptions(options, { - prefix, - context: "Argument 1", - }); - } else { - options = {}; - } - if (options.mode === undefined) { - return acquireReadableStreamDefaultReader(this); - } else { - assert(options.mode === "byob"); - return acquireReadableStreamBYOBReader(this); - } - } + /** @returns {(chunk: any) => 1} */ + get size() { + webidl.assertBranded(this, CountQueuingStrategyPrototype); + initializeCountSizeFunction(this[_globalObject]); + return WeakMapPrototypeGet(countSizeFunctionWeakMap, this[_globalObject]); + } - /** - * @template T - * @param {{ readable: ReadableStream, writable: WritableStream }} transform - * @param {PipeOptions=} options - * @returns {ReadableStream} - */ - pipeThrough(transform, options = {}) { - webidl.assertBranded(this, ReadableStreamPrototype); - const prefix = "Failed to execute 'pipeThrough' on 'ReadableStream'"; - webidl.requiredArguments(arguments.length, 1, { prefix }); - transform = webidl.converters.ReadableWritablePair(transform, { + [SymbolFor("Deno.customInspect")](inspect) { + return inspect(createFilteredInspectProxy({ + object: this, + evaluate: ObjectPrototypeIsPrototypeOf( + CountQueuingStrategyPrototype, + this, + ), + keys: [ + "highWaterMark", + "size", + ], + })); + } +} + +webidl.configurePrototype(CountQueuingStrategy); +const CountQueuingStrategyPrototype = CountQueuingStrategy.prototype; + +/** @type {WeakMap 1>} */ +const countSizeFunctionWeakMap = new WeakMap(); + +/** @param {typeof globalThis} globalObject */ +function initializeCountSizeFunction(globalObject) { + if (WeakMapPrototypeHas(countSizeFunctionWeakMap, globalObject)) { + return; + } + const size = () => 1; + WeakMapPrototypeSet(countSizeFunctionWeakMap, globalObject, size); +} + +const _resourceBacking = Symbol("[[resourceBacking]]"); +// This distinction exists to prevent unrefable streams being used in +// regular fast streams that are unaware of refability +const _resourceBackingUnrefable = Symbol("[[resourceBackingUnrefable]]"); +/** @template R */ +class ReadableStream { + /** @type {ReadableStreamDefaultController | ReadableByteStreamController} */ + [_controller]; + /** @type {boolean} */ + [_detached]; + /** @type {boolean} */ + [_disturbed]; + /** @type {ReadableStreamDefaultReader | ReadableStreamBYOBReader} */ + [_reader]; + /** @type {"readable" | "closed" | "errored"} */ + [_state]; + /** @type {any} */ + [_storedError]; + /** @type {{ rid: number, autoClose: boolean } | null} */ + [_resourceBacking] = null; + + /** + * @param {UnderlyingSource=} underlyingSource + * @param {QueuingStrategy=} strategy + */ + constructor(underlyingSource = undefined, strategy = undefined) { + const prefix = "Failed to construct 'ReadableStream'"; + if (underlyingSource !== undefined) { + underlyingSource = webidl.converters.object(underlyingSource, { prefix, context: "Argument 1", }); - options = webidl.converters.StreamPipeOptions(options, { + } else { + underlyingSource = null; + } + if (strategy !== undefined) { + strategy = webidl.converters.QueuingStrategy(strategy, { prefix, context: "Argument 2", }); - const { readable, writable } = transform; - const { preventClose, preventAbort, preventCancel, signal } = options; - if (isReadableStreamLocked(this)) { - throw new TypeError("ReadableStream is already locked."); - } - if (isWritableStreamLocked(writable)) { - throw new TypeError("Target WritableStream is already locked."); - } - const promise = readableStreamPipeTo( - this, - writable, - preventClose, - preventAbort, - preventCancel, - signal, + } else { + strategy = {}; + } + this[webidl.brand] = webidl.brand; + let underlyingSourceDict = {}; + if (underlyingSource !== undefined) { + underlyingSourceDict = webidl.converters.UnderlyingSource( + underlyingSource, + { prefix, context: "underlyingSource" }, ); - setPromiseIsHandledToTrue(promise); - return readable; } - - /** - * @param {WritableStream} destination - * @param {PipeOptions=} options - * @returns {Promise} - */ - pipeTo(destination, options = {}) { - try { - webidl.assertBranded(this, ReadableStreamPrototype); - const prefix = "Failed to execute 'pipeTo' on 'ReadableStream'"; - webidl.requiredArguments(arguments.length, 1, { prefix }); - destination = webidl.converters.WritableStream(destination, { - prefix, - context: "Argument 1", - }); - options = webidl.converters.StreamPipeOptions(options, { - prefix, - context: "Argument 2", - }); - } catch (err) { - return PromiseReject(err); - } - const { preventClose, preventAbort, preventCancel, signal } = options; - if (isReadableStreamLocked(this)) { - return PromiseReject( - new TypeError("ReadableStream is already locked."), - ); - } - if (isWritableStreamLocked(destination)) { - return PromiseReject( - new TypeError("destination WritableStream is already locked."), + initializeReadableStream(this); + if (underlyingSourceDict.type === "bytes") { + if (strategy.size !== undefined) { + throw new RangeError( + `${prefix}: When underlying source is "bytes", strategy.size must be undefined.`, ); } - return readableStreamPipeTo( + const highWaterMark = extractHighWaterMark(strategy, 0); + setUpReadableByteStreamControllerFromUnderlyingSource( + // @ts-ignore cannot easily assert this is ReadableStream + this, + underlyingSource, + underlyingSourceDict, + highWaterMark, + ); + } else { + assert(!(ReflectHas(underlyingSourceDict, "type"))); + const sizeAlgorithm = extractSizeAlgorithm(strategy); + const highWaterMark = extractHighWaterMark(strategy, 1); + setUpReadableStreamDefaultControllerFromUnderlyingSource( this, - destination, - preventClose, - preventAbort, - preventCancel, - signal, + underlyingSource, + underlyingSourceDict, + highWaterMark, + sizeAlgorithm, ); } + } + + /** @returns {boolean} */ + get locked() { + webidl.assertBranded(this, ReadableStreamPrototype); + return isReadableStreamLocked(this); + } - /** @returns {[ReadableStream, ReadableStream]} */ - tee() { + /** + * @param {any=} reason + * @returns {Promise} + */ + cancel(reason = undefined) { + try { webidl.assertBranded(this, ReadableStreamPrototype); - return readableStreamTee(this, false); + if (reason !== undefined) { + reason = webidl.converters.any(reason); + } + } catch (err) { + return PromiseReject(err); + } + if (isReadableStreamLocked(this)) { + return PromiseReject( + new TypeError("Cannot cancel a locked ReadableStream."), + ); } + return readableStreamCancel(this, reason); + } - // TODO(lucacasonato): should be moved to webidl crate - /** - * @param {ReadableStreamIteratorOptions=} options - * @returns {AsyncIterableIterator} - */ - values(options = {}) { - webidl.assertBranded(this, ReadableStreamPrototype); - const prefix = "Failed to execute 'values' on 'ReadableStream'"; - options = webidl.converters.ReadableStreamIteratorOptions(options, { + /** + * @param {ReadableStreamGetReaderOptions=} options + * @returns {ReadableStreamDefaultReader | ReadableStreamBYOBReader} + */ + getReader(options = undefined) { + webidl.assertBranded(this, ReadableStreamPrototype); + const prefix = "Failed to execute 'getReader' on 'ReadableStream'"; + if (options !== undefined) { + options = webidl.converters.ReadableStreamGetReaderOptions(options, { prefix, context: "Argument 1", }); - /** @type {AsyncIterableIterator} */ - const iterator = ObjectCreate(readableStreamAsyncIteratorPrototype); - const reader = acquireReadableStreamDefaultReader(this); - iterator[_reader] = reader; - iterator[_preventCancel] = options.preventCancel; - return iterator; + } else { + options = {}; } - - [SymbolFor("Deno.privateCustomInspect")](inspect) { - return `${this.constructor.name} ${inspect({ locked: this.locked })}`; + if (options.mode === undefined) { + return acquireReadableStreamDefaultReader(this); + } else { + assert(options.mode === "byob"); + return acquireReadableStreamBYOBReader(this); } } - // TODO(lucacasonato): should be moved to webidl crate - ReadableStream.prototype[SymbolAsyncIterator] = - ReadableStream.prototype.values; - ObjectDefineProperty(ReadableStream.prototype, SymbolAsyncIterator, { - writable: true, - enumerable: false, - configurable: true, - }); - - webidl.configurePrototype(ReadableStream); - const ReadableStreamPrototype = ReadableStream.prototype; - - function errorReadableStream(stream, e) { - readableStreamDefaultControllerError(stream[_controller], e); + /** + * @template T + * @param {{ readable: ReadableStream, writable: WritableStream }} transform + * @param {PipeOptions=} options + * @returns {ReadableStream} + */ + pipeThrough(transform, options = {}) { + webidl.assertBranded(this, ReadableStreamPrototype); + const prefix = "Failed to execute 'pipeThrough' on 'ReadableStream'"; + webidl.requiredArguments(arguments.length, 1, { prefix }); + transform = webidl.converters.ReadableWritablePair(transform, { + prefix, + context: "Argument 1", + }); + options = webidl.converters.StreamPipeOptions(options, { + prefix, + context: "Argument 2", + }); + const { readable, writable } = transform; + const { preventClose, preventAbort, preventCancel, signal } = options; + if (isReadableStreamLocked(this)) { + throw new TypeError("ReadableStream is already locked."); + } + if (isWritableStreamLocked(writable)) { + throw new TypeError("Target WritableStream is already locked."); + } + const promise = readableStreamPipeTo( + this, + writable, + preventClose, + preventAbort, + preventCancel, + signal, + ); + setPromiseIsHandledToTrue(promise); + return readable; } - /** @template R */ - class ReadableStreamDefaultReader { - /** @type {Deferred} */ - [_closedPromise]; - /** @type {ReadableStream | undefined} */ - [_stream]; - /** @type {ReadRequest[]} */ - [_readRequests]; - - /** @param {ReadableStream} stream */ - constructor(stream) { - const prefix = "Failed to construct 'ReadableStreamDefaultReader'"; + /** + * @param {WritableStream} destination + * @param {PipeOptions=} options + * @returns {Promise} + */ + pipeTo(destination, options = {}) { + try { + webidl.assertBranded(this, ReadableStreamPrototype); + const prefix = "Failed to execute 'pipeTo' on 'ReadableStream'"; webidl.requiredArguments(arguments.length, 1, { prefix }); - stream = webidl.converters.ReadableStream(stream, { + destination = webidl.converters.WritableStream(destination, { prefix, context: "Argument 1", }); - this[webidl.brand] = webidl.brand; - setUpReadableStreamDefaultReader(this, stream); + options = webidl.converters.StreamPipeOptions(options, { + prefix, + context: "Argument 2", + }); + } catch (err) { + return PromiseReject(err); } - - /** @returns {Promise>} */ - read() { - try { - webidl.assertBranded(this, ReadableStreamDefaultReaderPrototype); - } catch (err) { - return PromiseReject(err); - } - if (this[_stream] === undefined) { - return PromiseReject( - new TypeError("Reader has no associated stream."), - ); - } - /** @type {Deferred>} */ - const promise = new Deferred(); - /** @type {ReadRequest} */ - const readRequest = { - chunkSteps(chunk) { - promise.resolve({ value: chunk, done: false }); - }, - closeSteps() { - promise.resolve({ value: undefined, done: true }); - }, - errorSteps(e) { - promise.reject(e); - }, - }; - readableStreamDefaultReaderRead(this, readRequest); - return promise.promise; + const { preventClose, preventAbort, preventCancel, signal } = options; + if (isReadableStreamLocked(this)) { + return PromiseReject( + new TypeError("ReadableStream is already locked."), + ); + } + if (isWritableStreamLocked(destination)) { + return PromiseReject( + new TypeError("destination WritableStream is already locked."), + ); } + return readableStreamPipeTo( + this, + destination, + preventClose, + preventAbort, + preventCancel, + signal, + ); + } + + /** @returns {[ReadableStream, ReadableStream]} */ + tee() { + webidl.assertBranded(this, ReadableStreamPrototype); + return readableStreamTee(this, false); + } - /** @returns {void} */ - releaseLock() { + // TODO(lucacasonato): should be moved to webidl crate + /** + * @param {ReadableStreamIteratorOptions=} options + * @returns {AsyncIterableIterator} + */ + values(options = {}) { + webidl.assertBranded(this, ReadableStreamPrototype); + const prefix = "Failed to execute 'values' on 'ReadableStream'"; + options = webidl.converters.ReadableStreamIteratorOptions(options, { + prefix, + context: "Argument 1", + }); + /** @type {AsyncIterableIterator} */ + const iterator = ObjectCreate(readableStreamAsyncIteratorPrototype); + const reader = acquireReadableStreamDefaultReader(this); + iterator[_reader] = reader; + iterator[_preventCancel] = options.preventCancel; + return iterator; + } + + [SymbolFor("Deno.privateCustomInspect")](inspect) { + return `${this.constructor.name} ${inspect({ locked: this.locked })}`; + } +} + +// TODO(lucacasonato): should be moved to webidl crate +ReadableStream.prototype[SymbolAsyncIterator] = ReadableStream.prototype.values; +ObjectDefineProperty(ReadableStream.prototype, SymbolAsyncIterator, { + writable: true, + enumerable: false, + configurable: true, +}); + +webidl.configurePrototype(ReadableStream); +const ReadableStreamPrototype = ReadableStream.prototype; + +function errorReadableStream(stream, e) { + readableStreamDefaultControllerError(stream[_controller], e); +} + +/** @template R */ +class ReadableStreamDefaultReader { + /** @type {Deferred} */ + [_closedPromise]; + /** @type {ReadableStream | undefined} */ + [_stream]; + /** @type {ReadRequest[]} */ + [_readRequests]; + + /** @param {ReadableStream} stream */ + constructor(stream) { + const prefix = "Failed to construct 'ReadableStreamDefaultReader'"; + webidl.requiredArguments(arguments.length, 1, { prefix }); + stream = webidl.converters.ReadableStream(stream, { + prefix, + context: "Argument 1", + }); + this[webidl.brand] = webidl.brand; + setUpReadableStreamDefaultReader(this, stream); + } + + /** @returns {Promise>} */ + read() { + try { webidl.assertBranded(this, ReadableStreamDefaultReaderPrototype); - if (this[_stream] === undefined) { - return; - } - readableStreamDefaultReaderRelease(this); + } catch (err) { + return PromiseReject(err); + } + if (this[_stream] === undefined) { + return PromiseReject( + new TypeError("Reader has no associated stream."), + ); } + /** @type {Deferred>} */ + const promise = new Deferred(); + /** @type {ReadRequest} */ + const readRequest = { + chunkSteps(chunk) { + promise.resolve({ value: chunk, done: false }); + }, + closeSteps() { + promise.resolve({ value: undefined, done: true }); + }, + errorSteps(e) { + promise.reject(e); + }, + }; + readableStreamDefaultReaderRead(this, readRequest); + return promise.promise; + } - get closed() { - try { - webidl.assertBranded(this, ReadableStreamDefaultReaderPrototype); - } catch (err) { - return PromiseReject(err); - } - return this[_closedPromise].promise; + /** @returns {void} */ + releaseLock() { + webidl.assertBranded(this, ReadableStreamDefaultReaderPrototype); + if (this[_stream] === undefined) { + return; } + readableStreamDefaultReaderRelease(this); + } - /** - * @param {any} reason - * @returns {Promise} - */ - cancel(reason = undefined) { - try { - webidl.assertBranded(this, ReadableStreamDefaultReaderPrototype); - if (reason !== undefined) { - reason = webidl.converters.any(reason); - } - } catch (err) { - return PromiseReject(err); - } + get closed() { + try { + webidl.assertBranded(this, ReadableStreamDefaultReaderPrototype); + } catch (err) { + return PromiseReject(err); + } + return this[_closedPromise].promise; + } - if (this[_stream] === undefined) { - return PromiseReject( - new TypeError("Reader has no associated stream."), - ); + /** + * @param {any} reason + * @returns {Promise} + */ + cancel(reason = undefined) { + try { + webidl.assertBranded(this, ReadableStreamDefaultReaderPrototype); + if (reason !== undefined) { + reason = webidl.converters.any(reason); } - return readableStreamReaderGenericCancel(this, reason); + } catch (err) { + return PromiseReject(err); } - [SymbolFor("Deno.privateCustomInspect")](inspect) { - return `${this.constructor.name} ${inspect({ closed: this.closed })}`; + if (this[_stream] === undefined) { + return PromiseReject( + new TypeError("Reader has no associated stream."), + ); } + return readableStreamReaderGenericCancel(this, reason); } - webidl.configurePrototype(ReadableStreamDefaultReader); - const ReadableStreamDefaultReaderPrototype = - ReadableStreamDefaultReader.prototype; + [SymbolFor("Deno.privateCustomInspect")](inspect) { + return `${this.constructor.name} ${inspect({ closed: this.closed })}`; + } +} - /** @template R */ - class ReadableStreamBYOBReader { - /** @type {Deferred} */ - [_closedPromise]; - /** @type {ReadableStream | undefined} */ - [_stream]; - /** @type {ReadIntoRequest[]} */ - [_readIntoRequests]; - - /** @param {ReadableStream} stream */ - constructor(stream) { - const prefix = "Failed to construct 'ReadableStreamBYOBReader'"; - webidl.requiredArguments(arguments.length, 1, { prefix }); - stream = webidl.converters.ReadableStream(stream, { +webidl.configurePrototype(ReadableStreamDefaultReader); +const ReadableStreamDefaultReaderPrototype = + ReadableStreamDefaultReader.prototype; + +/** @template R */ +class ReadableStreamBYOBReader { + /** @type {Deferred} */ + [_closedPromise]; + /** @type {ReadableStream | undefined} */ + [_stream]; + /** @type {ReadIntoRequest[]} */ + [_readIntoRequests]; + + /** @param {ReadableStream} stream */ + constructor(stream) { + const prefix = "Failed to construct 'ReadableStreamBYOBReader'"; + webidl.requiredArguments(arguments.length, 1, { prefix }); + stream = webidl.converters.ReadableStream(stream, { + prefix, + context: "Argument 1", + }); + this[webidl.brand] = webidl.brand; + setUpReadableStreamBYOBReader(this, stream); + } + + /** + * @param {ArrayBufferView} view + * @returns {Promise} + */ + read(view) { + try { + webidl.assertBranded(this, ReadableStreamBYOBReaderPrototype); + const prefix = "Failed to execute 'read' on 'ReadableStreamBYOBReader'"; + view = webidl.converters.ArrayBufferView(view, { prefix, context: "Argument 1", }); - this[webidl.brand] = webidl.brand; - setUpReadableStreamBYOBReader(this, stream); + } catch (err) { + return PromiseReject(err); } - /** - * @param {ArrayBufferView} view - * @returns {Promise} - */ - read(view) { - try { - webidl.assertBranded(this, ReadableStreamBYOBReaderPrototype); - const prefix = "Failed to execute 'read' on 'ReadableStreamBYOBReader'"; - view = webidl.converters.ArrayBufferView(view, { - prefix, - context: "Argument 1", - }); - } catch (err) { - return PromiseReject(err); - } - - if (view.byteLength === 0) { - return PromiseReject( - new TypeError("view must have non-zero byteLength"), - ); - } - if (view.buffer.byteLength === 0) { - return PromiseReject( - new TypeError("view's buffer must have non-zero byteLength"), - ); - } - if (isDetachedBuffer(view.buffer)) { - return PromiseReject( - new TypeError("view's buffer has been detached"), - ); - } - if (this[_stream] === undefined) { - return PromiseReject( - new TypeError("Reader has no associated stream."), - ); - } - /** @type {Deferred} */ - const promise = new Deferred(); - /** @type {ReadIntoRequest} */ - const readIntoRequest = { - chunkSteps(chunk) { - promise.resolve({ value: chunk, done: false }); - }, - closeSteps(chunk) { - promise.resolve({ value: chunk, done: true }); - }, - errorSteps(e) { - promise.reject(e); - }, - }; - readableStreamBYOBReaderRead(this, view, readIntoRequest); - return promise.promise; + if (view.byteLength === 0) { + return PromiseReject( + new TypeError("view must have non-zero byteLength"), + ); + } + if (view.buffer.byteLength === 0) { + return PromiseReject( + new TypeError("view's buffer must have non-zero byteLength"), + ); + } + if (isDetachedBuffer(view.buffer)) { + return PromiseReject( + new TypeError("view's buffer has been detached"), + ); + } + if (this[_stream] === undefined) { + return PromiseReject( + new TypeError("Reader has no associated stream."), + ); + } + /** @type {Deferred} */ + const promise = new Deferred(); + /** @type {ReadIntoRequest} */ + const readIntoRequest = { + chunkSteps(chunk) { + promise.resolve({ value: chunk, done: false }); + }, + closeSteps(chunk) { + promise.resolve({ value: chunk, done: true }); + }, + errorSteps(e) { + promise.reject(e); + }, + }; + readableStreamBYOBReaderRead(this, view, readIntoRequest); + return promise.promise; + } + + /** @returns {void} */ + releaseLock() { + webidl.assertBranded(this, ReadableStreamBYOBReaderPrototype); + if (this[_stream] === undefined) { + return; } + readableStreamBYOBReaderRelease(this); + } - /** @returns {void} */ - releaseLock() { + get closed() { + try { webidl.assertBranded(this, ReadableStreamBYOBReaderPrototype); - if (this[_stream] === undefined) { - return; - } - readableStreamBYOBReaderRelease(this); + } catch (err) { + return PromiseReject(err); } + return this[_closedPromise].promise; + } - get closed() { - try { - webidl.assertBranded(this, ReadableStreamBYOBReaderPrototype); - } catch (err) { - return PromiseReject(err); + /** + * @param {any} reason + * @returns {Promise} + */ + cancel(reason = undefined) { + try { + webidl.assertBranded(this, ReadableStreamBYOBReaderPrototype); + if (reason !== undefined) { + reason = webidl.converters.any(reason); } - return this[_closedPromise].promise; + } catch (err) { + return PromiseReject(err); } - /** - * @param {any} reason - * @returns {Promise} - */ - cancel(reason = undefined) { - try { - webidl.assertBranded(this, ReadableStreamBYOBReaderPrototype); - if (reason !== undefined) { - reason = webidl.converters.any(reason); - } - } catch (err) { - return PromiseReject(err); - } - - if (this[_stream] === undefined) { - return PromiseReject( - new TypeError("Reader has no associated stream."), - ); - } - return readableStreamReaderGenericCancel(this, reason); + if (this[_stream] === undefined) { + return PromiseReject( + new TypeError("Reader has no associated stream."), + ); } + return readableStreamReaderGenericCancel(this, reason); + } - [SymbolFor("Deno.privateCustomInspect")](inspect) { - return `${this.constructor.name} ${inspect({ closed: this.closed })}`; - } + [SymbolFor("Deno.privateCustomInspect")](inspect) { + return `${this.constructor.name} ${inspect({ closed: this.closed })}`; } +} - webidl.configurePrototype(ReadableStreamBYOBReader); - const ReadableStreamBYOBReaderPrototype = ReadableStreamBYOBReader.prototype; +webidl.configurePrototype(ReadableStreamBYOBReader); +const ReadableStreamBYOBReaderPrototype = ReadableStreamBYOBReader.prototype; - class ReadableStreamBYOBRequest { - /** @type {ReadableByteStreamController} */ - [_controller]; - /** @type {ArrayBufferView | null} */ - [_view]; +class ReadableStreamBYOBRequest { + /** @type {ReadableByteStreamController} */ + [_controller]; + /** @type {ArrayBufferView | null} */ + [_view]; - /** @returns {ArrayBufferView | null} */ - get view() { - webidl.assertBranded(this, ReadableStreamBYOBRequestPrototype); - return this[_view]; - } + /** @returns {ArrayBufferView | null} */ + get view() { + webidl.assertBranded(this, ReadableStreamBYOBRequestPrototype); + return this[_view]; + } - constructor() { - webidl.illegalConstructor(); - } + constructor() { + webidl.illegalConstructor(); + } - respond(bytesWritten) { - webidl.assertBranded(this, ReadableStreamBYOBRequestPrototype); - const prefix = - "Failed to execute 'respond' on 'ReadableStreamBYOBRequest'"; - webidl.requiredArguments(arguments.length, 1, { prefix }); - bytesWritten = webidl.converters["unsigned long long"](bytesWritten, { - enforceRange: true, - prefix, - context: "Argument 1", - }); + respond(bytesWritten) { + webidl.assertBranded(this, ReadableStreamBYOBRequestPrototype); + const prefix = "Failed to execute 'respond' on 'ReadableStreamBYOBRequest'"; + webidl.requiredArguments(arguments.length, 1, { prefix }); + bytesWritten = webidl.converters["unsigned long long"](bytesWritten, { + enforceRange: true, + prefix, + context: "Argument 1", + }); - if (this[_controller] === undefined) { - throw new TypeError("This BYOB request has been invalidated"); - } - if (isDetachedBuffer(this[_view].buffer)) { - throw new TypeError( - "The BYOB request's buffer has been detached and so cannot be used as a response", - ); - } - assert(this[_view].byteLength > 0); - assert(this[_view].buffer.byteLength > 0); - readableByteStreamControllerRespond(this[_controller], bytesWritten); + if (this[_controller] === undefined) { + throw new TypeError("This BYOB request has been invalidated"); } - - respondWithNewView(view) { - webidl.assertBranded(this, ReadableStreamBYOBRequestPrototype); - const prefix = - "Failed to execute 'respondWithNewView' on 'ReadableStreamBYOBRequest'"; - webidl.requiredArguments(arguments.length, 1, { prefix }); - view = webidl.converters.ArrayBufferView(view, { - prefix, - context: "Argument 1", - }); - - if (this[_controller] === undefined) { - throw new TypeError("This BYOB request has been invalidated"); - } - if (isDetachedBuffer(view.buffer)) { - throw new TypeError( - "The given view's buffer has been detached and so cannot be used as a response", - ); - } - readableByteStreamControllerRespondWithNewView(this[_controller], view); - } - } - - webidl.configurePrototype(ReadableStreamBYOBRequest); - const ReadableStreamBYOBRequestPrototype = - ReadableStreamBYOBRequest.prototype; - - class ReadableByteStreamController { - /** @type {number | undefined} */ - [_autoAllocateChunkSize]; - /** @type {ReadableStreamBYOBRequest | null} */ - [_byobRequest]; - /** @type {(reason: any) => Promise} */ - [_cancelAlgorithm]; - /** @type {boolean} */ - [_closeRequested]; - /** @type {boolean} */ - [_pullAgain]; - /** @type {(controller: this) => Promise} */ - [_pullAlgorithm]; - /** @type {boolean} */ - [_pulling]; - /** @type {PullIntoDescriptor[]} */ - [_pendingPullIntos]; - /** @type {ReadableByteStreamQueueEntry[]} */ - [_queue]; - /** @type {number} */ - [_queueTotalSize]; - /** @type {boolean} */ - [_started]; - /** @type {number} */ - [_strategyHWM]; - /** @type {ReadableStream} */ - [_stream]; - - constructor() { - webidl.illegalConstructor(); + if (isDetachedBuffer(this[_view].buffer)) { + throw new TypeError( + "The BYOB request's buffer has been detached and so cannot be used as a response", + ); } + assert(this[_view].byteLength > 0); + assert(this[_view].buffer.byteLength > 0); + readableByteStreamControllerRespond(this[_controller], bytesWritten); + } - /** @returns {ReadableStreamBYOBRequest | null} */ - get byobRequest() { - webidl.assertBranded(this, ReadableByteStreamControllerPrototype); - return readableByteStreamControllerGetBYOBRequest(this); - } + respondWithNewView(view) { + webidl.assertBranded(this, ReadableStreamBYOBRequestPrototype); + const prefix = + "Failed to execute 'respondWithNewView' on 'ReadableStreamBYOBRequest'"; + webidl.requiredArguments(arguments.length, 1, { prefix }); + view = webidl.converters.ArrayBufferView(view, { + prefix, + context: "Argument 1", + }); - /** @returns {number | null} */ - get desiredSize() { - webidl.assertBranded(this, ReadableByteStreamControllerPrototype); - return readableByteStreamControllerGetDesiredSize(this); + if (this[_controller] === undefined) { + throw new TypeError("This BYOB request has been invalidated"); } - - /** @returns {void} */ - close() { - webidl.assertBranded(this, ReadableByteStreamControllerPrototype); - if (this[_closeRequested] === true) { - throw new TypeError("Closed already requested."); - } - if (this[_stream][_state] !== "readable") { - throw new TypeError( - "ReadableByteStreamController's stream is not in a readable state.", - ); - } - readableByteStreamControllerClose(this); + if (isDetachedBuffer(view.buffer)) { + throw new TypeError( + "The given view's buffer has been detached and so cannot be used as a response", + ); + } + readableByteStreamControllerRespondWithNewView(this[_controller], view); + } +} + +webidl.configurePrototype(ReadableStreamBYOBRequest); +const ReadableStreamBYOBRequestPrototype = ReadableStreamBYOBRequest.prototype; + +class ReadableByteStreamController { + /** @type {number | undefined} */ + [_autoAllocateChunkSize]; + /** @type {ReadableStreamBYOBRequest | null} */ + [_byobRequest]; + /** @type {(reason: any) => Promise} */ + [_cancelAlgorithm]; + /** @type {boolean} */ + [_closeRequested]; + /** @type {boolean} */ + [_pullAgain]; + /** @type {(controller: this) => Promise} */ + [_pullAlgorithm]; + /** @type {boolean} */ + [_pulling]; + /** @type {PullIntoDescriptor[]} */ + [_pendingPullIntos]; + /** @type {ReadableByteStreamQueueEntry[]} */ + [_queue]; + /** @type {number} */ + [_queueTotalSize]; + /** @type {boolean} */ + [_started]; + /** @type {number} */ + [_strategyHWM]; + /** @type {ReadableStream} */ + [_stream]; + + constructor() { + webidl.illegalConstructor(); + } + + /** @returns {ReadableStreamBYOBRequest | null} */ + get byobRequest() { + webidl.assertBranded(this, ReadableByteStreamControllerPrototype); + return readableByteStreamControllerGetBYOBRequest(this); + } + + /** @returns {number | null} */ + get desiredSize() { + webidl.assertBranded(this, ReadableByteStreamControllerPrototype); + return readableByteStreamControllerGetDesiredSize(this); + } + + /** @returns {void} */ + close() { + webidl.assertBranded(this, ReadableByteStreamControllerPrototype); + if (this[_closeRequested] === true) { + throw new TypeError("Closed already requested."); + } + if (this[_stream][_state] !== "readable") { + throw new TypeError( + "ReadableByteStreamController's stream is not in a readable state.", + ); } + readableByteStreamControllerClose(this); + } - /** - * @param {ArrayBufferView} chunk - * @returns {void} - */ - enqueue(chunk) { - webidl.assertBranded(this, ReadableByteStreamControllerPrototype); - const prefix = - "Failed to execute 'enqueue' on 'ReadableByteStreamController'"; - webidl.requiredArguments(arguments.length, 1, { prefix }); - const arg1 = "Argument 1"; - chunk = webidl.converters.ArrayBufferView(chunk, { + /** + * @param {ArrayBufferView} chunk + * @returns {void} + */ + enqueue(chunk) { + webidl.assertBranded(this, ReadableByteStreamControllerPrototype); + const prefix = + "Failed to execute 'enqueue' on 'ReadableByteStreamController'"; + webidl.requiredArguments(arguments.length, 1, { prefix }); + const arg1 = "Argument 1"; + chunk = webidl.converters.ArrayBufferView(chunk, { + prefix, + context: arg1, + }); + if (chunk.byteLength === 0) { + throw webidl.makeException(TypeError, "length must be non-zero", { prefix, context: arg1, }); - if (chunk.byteLength === 0) { - throw webidl.makeException(TypeError, "length must be non-zero", { - prefix, - context: arg1, - }); - } - if (chunk.buffer.byteLength === 0) { - throw webidl.makeException( - TypeError, - "buffer length must be non-zero", - { prefix, context: arg1 }, - ); - } - if (this[_closeRequested] === true) { - throw new TypeError( - "Cannot enqueue chunk after a close has been requested.", - ); - } - if (this[_stream][_state] !== "readable") { - throw new TypeError( - "Cannot enqueue chunk when underlying stream is not readable.", - ); - } - return readableByteStreamControllerEnqueue(this, chunk); } + if (chunk.buffer.byteLength === 0) { + throw webidl.makeException( + TypeError, + "buffer length must be non-zero", + { prefix, context: arg1 }, + ); + } + if (this[_closeRequested] === true) { + throw new TypeError( + "Cannot enqueue chunk after a close has been requested.", + ); + } + if (this[_stream][_state] !== "readable") { + throw new TypeError( + "Cannot enqueue chunk when underlying stream is not readable.", + ); + } + return readableByteStreamControllerEnqueue(this, chunk); + } - /** - * @param {any=} e - * @returns {void} - */ - error(e = undefined) { - webidl.assertBranded(this, ReadableByteStreamControllerPrototype); - if (e !== undefined) { - e = webidl.converters.any(e); - } - readableByteStreamControllerError(this, e); + /** + * @param {any=} e + * @returns {void} + */ + error(e = undefined) { + webidl.assertBranded(this, ReadableByteStreamControllerPrototype); + if (e !== undefined) { + e = webidl.converters.any(e); } + readableByteStreamControllerError(this, e); + } - [SymbolFor("Deno.customInspect")](inspect) { - return inspect(consoleInternal.createFilteredInspectProxy({ - object: this, - evaluate: ObjectPrototypeIsPrototypeOf( - ReadableByteStreamControllerPrototype, - this, - ), - keys: ["desiredSize"], - })); - } - - /** - * @param {any} reason - * @returns {Promise} - */ - [_cancelSteps](reason) { - readableByteStreamControllerClearPendingPullIntos(this); - resetQueue(this); - const result = this[_cancelAlgorithm](reason); - readableByteStreamControllerClearAlgorithms(this); - return result; - } - - /** - * @param {ReadRequest} readRequest - * @returns {void} - */ - [_pullSteps](readRequest) { - /** @type {ReadableStream} */ - const stream = this[_stream]; - assert(readableStreamHasDefaultReader(stream)); - if (this[_queueTotalSize] > 0) { - assert(readableStreamGetNumReadRequests(stream) === 0); - readableByteStreamControllerFillReadRequestFromQueue(this, readRequest); + [SymbolFor("Deno.customInspect")](inspect) { + return inspect(createFilteredInspectProxy({ + object: this, + evaluate: ObjectPrototypeIsPrototypeOf( + ReadableByteStreamControllerPrototype, + this, + ), + keys: ["desiredSize"], + })); + } + + /** + * @param {any} reason + * @returns {Promise} + */ + [_cancelSteps](reason) { + readableByteStreamControllerClearPendingPullIntos(this); + resetQueue(this); + const result = this[_cancelAlgorithm](reason); + readableByteStreamControllerClearAlgorithms(this); + return result; + } + + /** + * @param {ReadRequest} readRequest + * @returns {void} + */ + [_pullSteps](readRequest) { + /** @type {ReadableStream} */ + const stream = this[_stream]; + assert(readableStreamHasDefaultReader(stream)); + if (this[_queueTotalSize] > 0) { + assert(readableStreamGetNumReadRequests(stream) === 0); + readableByteStreamControllerFillReadRequestFromQueue(this, readRequest); + return; + } + const autoAllocateChunkSize = this[_autoAllocateChunkSize]; + if (autoAllocateChunkSize !== undefined) { + let buffer; + try { + buffer = new ArrayBuffer(autoAllocateChunkSize); + } catch (e) { + readRequest.errorSteps(e); return; } - const autoAllocateChunkSize = this[_autoAllocateChunkSize]; - if (autoAllocateChunkSize !== undefined) { - let buffer; - try { - buffer = new ArrayBuffer(autoAllocateChunkSize); - } catch (e) { - readRequest.errorSteps(e); - return; - } - /** @type {PullIntoDescriptor} */ - const pullIntoDescriptor = { - buffer, - bufferByteLength: autoAllocateChunkSize, - byteOffset: 0, - byteLength: autoAllocateChunkSize, - bytesFilled: 0, - elementSize: 1, - viewConstructor: Uint8Array, - readerType: "default", - }; - ArrayPrototypePush(this[_pendingPullIntos], pullIntoDescriptor); - } - readableStreamAddReadRequest(stream, readRequest); - readableByteStreamControllerCallPullIfNeeded(this); + /** @type {PullIntoDescriptor} */ + const pullIntoDescriptor = { + buffer, + bufferByteLength: autoAllocateChunkSize, + byteOffset: 0, + byteLength: autoAllocateChunkSize, + bytesFilled: 0, + elementSize: 1, + viewConstructor: Uint8Array, + readerType: "default", + }; + ArrayPrototypePush(this[_pendingPullIntos], pullIntoDescriptor); } + readableStreamAddReadRequest(stream, readRequest); + readableByteStreamControllerCallPullIfNeeded(this); + } - [_releaseSteps]() { - if (this[_pendingPullIntos].length !== 0) { - /** @type {PullIntoDescriptor} */ - const firstPendingPullInto = this[_pendingPullIntos][0]; - firstPendingPullInto.readerType = "none"; - this[_pendingPullIntos] = [firstPendingPullInto]; - } + [_releaseSteps]() { + if (this[_pendingPullIntos].length !== 0) { + /** @type {PullIntoDescriptor} */ + const firstPendingPullInto = this[_pendingPullIntos][0]; + firstPendingPullInto.readerType = "none"; + this[_pendingPullIntos] = [firstPendingPullInto]; } } +} - webidl.configurePrototype(ReadableByteStreamController); - const ReadableByteStreamControllerPrototype = - ReadableByteStreamController.prototype; - - /** @template R */ - class ReadableStreamDefaultController { - /** @type {(reason: any) => Promise} */ - [_cancelAlgorithm]; - /** @type {boolean} */ - [_closeRequested]; - /** @type {boolean} */ - [_pullAgain]; - /** @type {(controller: this) => Promise} */ - [_pullAlgorithm]; - /** @type {boolean} */ - [_pulling]; - /** @type {Array>} */ - [_queue]; - /** @type {number} */ - [_queueTotalSize]; - /** @type {boolean} */ - [_started]; - /** @type {number} */ - [_strategyHWM]; - /** @type {(chunk: R) => number} */ - [_strategySizeAlgorithm]; - /** @type {ReadableStream} */ - [_stream]; - - constructor() { - webidl.illegalConstructor(); - } - - /** @returns {number | null} */ - get desiredSize() { - webidl.assertBranded(this, ReadableStreamDefaultControllerPrototype); - return readableStreamDefaultControllerGetDesiredSize(this); - } - - /** @returns {void} */ - close() { - webidl.assertBranded(this, ReadableStreamDefaultControllerPrototype); - if (readableStreamDefaultControllerCanCloseOrEnqueue(this) === false) { - throw new TypeError("The stream controller cannot close or enqueue."); - } - readableStreamDefaultControllerClose(this); +webidl.configurePrototype(ReadableByteStreamController); +const ReadableByteStreamControllerPrototype = + ReadableByteStreamController.prototype; + +/** @template R */ +class ReadableStreamDefaultController { + /** @type {(reason: any) => Promise} */ + [_cancelAlgorithm]; + /** @type {boolean} */ + [_closeRequested]; + /** @type {boolean} */ + [_pullAgain]; + /** @type {(controller: this) => Promise} */ + [_pullAlgorithm]; + /** @type {boolean} */ + [_pulling]; + /** @type {Array>} */ + [_queue]; + /** @type {number} */ + [_queueTotalSize]; + /** @type {boolean} */ + [_started]; + /** @type {number} */ + [_strategyHWM]; + /** @type {(chunk: R) => number} */ + [_strategySizeAlgorithm]; + /** @type {ReadableStream} */ + [_stream]; + + constructor() { + webidl.illegalConstructor(); + } + + /** @returns {number | null} */ + get desiredSize() { + webidl.assertBranded(this, ReadableStreamDefaultControllerPrototype); + return readableStreamDefaultControllerGetDesiredSize(this); + } + + /** @returns {void} */ + close() { + webidl.assertBranded(this, ReadableStreamDefaultControllerPrototype); + if (readableStreamDefaultControllerCanCloseOrEnqueue(this) === false) { + throw new TypeError("The stream controller cannot close or enqueue."); } + readableStreamDefaultControllerClose(this); + } - /** - * @param {R} chunk - * @returns {void} - */ - enqueue(chunk = undefined) { - webidl.assertBranded(this, ReadableStreamDefaultControllerPrototype); - if (chunk !== undefined) { - chunk = webidl.converters.any(chunk); - } - if (readableStreamDefaultControllerCanCloseOrEnqueue(this) === false) { - throw new TypeError("The stream controller cannot close or enqueue."); - } - readableStreamDefaultControllerEnqueue(this, chunk); + /** + * @param {R} chunk + * @returns {void} + */ + enqueue(chunk = undefined) { + webidl.assertBranded(this, ReadableStreamDefaultControllerPrototype); + if (chunk !== undefined) { + chunk = webidl.converters.any(chunk); + } + if (readableStreamDefaultControllerCanCloseOrEnqueue(this) === false) { + throw new TypeError("The stream controller cannot close or enqueue."); } + readableStreamDefaultControllerEnqueue(this, chunk); + } - /** - * @param {any=} e - * @returns {void} - */ - error(e = undefined) { - webidl.assertBranded(this, ReadableStreamDefaultControllerPrototype); - if (e !== undefined) { - e = webidl.converters.any(e); - } - readableStreamDefaultControllerError(this, e); + /** + * @param {any=} e + * @returns {void} + */ + error(e = undefined) { + webidl.assertBranded(this, ReadableStreamDefaultControllerPrototype); + if (e !== undefined) { + e = webidl.converters.any(e); } + readableStreamDefaultControllerError(this, e); + } - [SymbolFor("Deno.customInspect")](inspect) { - return inspect(consoleInternal.createFilteredInspectProxy({ - object: this, - evaluate: ObjectPrototypeIsPrototypeOf( - ReadableStreamDefaultController.prototype, - this, - ), - keys: ["desiredSize"], - })); - } - - /** - * @param {any} reason - * @returns {Promise} - */ - [_cancelSteps](reason) { - resetQueue(this); - const result = this[_cancelAlgorithm](reason); - readableStreamDefaultControllerClearAlgorithms(this); - return result; - } - - /** - * @param {ReadRequest} readRequest - * @returns {void} - */ - [_pullSteps](readRequest) { - const stream = this[_stream]; - if (this[_queue].length) { - const chunk = dequeueValue(this); - if (this[_closeRequested] && this[_queue].length === 0) { - readableStreamDefaultControllerClearAlgorithms(this); - readableStreamClose(stream); - } else { - readableStreamDefaultControllerCallPullIfNeeded(this); - } - readRequest.chunkSteps(chunk); + [SymbolFor("Deno.customInspect")](inspect) { + return inspect(createFilteredInspectProxy({ + object: this, + evaluate: ObjectPrototypeIsPrototypeOf( + ReadableStreamDefaultController.prototype, + this, + ), + keys: ["desiredSize"], + })); + } + + /** + * @param {any} reason + * @returns {Promise} + */ + [_cancelSteps](reason) { + resetQueue(this); + const result = this[_cancelAlgorithm](reason); + readableStreamDefaultControllerClearAlgorithms(this); + return result; + } + + /** + * @param {ReadRequest} readRequest + * @returns {void} + */ + [_pullSteps](readRequest) { + const stream = this[_stream]; + if (this[_queue].length) { + const chunk = dequeueValue(this); + if (this[_closeRequested] && this[_queue].length === 0) { + readableStreamDefaultControllerClearAlgorithms(this); + readableStreamClose(stream); } else { - readableStreamAddReadRequest(stream, readRequest); readableStreamDefaultControllerCallPullIfNeeded(this); } + readRequest.chunkSteps(chunk); + } else { + readableStreamAddReadRequest(stream, readRequest); + readableStreamDefaultControllerCallPullIfNeeded(this); } + } - [_releaseSteps]() { - return; - } + [_releaseSteps]() { + return; } +} - webidl.configurePrototype(ReadableStreamDefaultController); - const ReadableStreamDefaultControllerPrototype = - ReadableStreamDefaultController.prototype; +webidl.configurePrototype(ReadableStreamDefaultController); +const ReadableStreamDefaultControllerPrototype = + ReadableStreamDefaultController.prototype; + +/** + * @template I + * @template O + */ +class TransformStream { + /** @type {boolean} */ + [_backpressure]; + /** @type {Deferred} */ + [_backpressureChangePromise]; + /** @type {TransformStreamDefaultController} */ + [_controller]; + /** @type {boolean} */ + [_detached]; + /** @type {ReadableStream} */ + [_readable]; + /** @type {WritableStream} */ + [_writable]; /** - * @template I - * @template O + * @param {Transformer} transformer + * @param {QueuingStrategy} writableStrategy + * @param {QueuingStrategy} readableStrategy */ - class TransformStream { - /** @type {boolean} */ - [_backpressure]; - /** @type {Deferred} */ - [_backpressureChangePromise]; - /** @type {TransformStreamDefaultController} */ - [_controller]; - /** @type {boolean} */ - [_detached]; - /** @type {ReadableStream} */ - [_readable]; - /** @type {WritableStream} */ - [_writable]; - - /** - * @param {Transformer} transformer - * @param {QueuingStrategy} writableStrategy - * @param {QueuingStrategy} readableStrategy - */ - constructor( - transformer = undefined, - writableStrategy = {}, - readableStrategy = {}, - ) { - const prefix = "Failed to construct 'TransformStream'"; - if (transformer !== undefined) { - transformer = webidl.converters.object(transformer, { - prefix, - context: "Argument 1", - }); - } - writableStrategy = webidl.converters.QueuingStrategy(writableStrategy, { - prefix, - context: "Argument 2", - }); - readableStrategy = webidl.converters.QueuingStrategy(readableStrategy, { - prefix, - context: "Argument 2", - }); - this[webidl.brand] = webidl.brand; - if (transformer === undefined) { - transformer = null; - } - const transformerDict = webidl.converters.Transformer(transformer, { + constructor( + transformer = undefined, + writableStrategy = {}, + readableStrategy = {}, + ) { + const prefix = "Failed to construct 'TransformStream'"; + if (transformer !== undefined) { + transformer = webidl.converters.object(transformer, { prefix, - context: "transformer", + context: "Argument 1", }); - if (transformerDict.readableType !== undefined) { - throw new RangeError( - `${prefix}: readableType transformers not supported.`, - ); - } - if (transformerDict.writableType !== undefined) { - throw new RangeError( - `${prefix}: writableType transformers not supported.`, - ); - } - const readableHighWaterMark = extractHighWaterMark(readableStrategy, 0); - const readableSizeAlgorithm = extractSizeAlgorithm(readableStrategy); - const writableHighWaterMark = extractHighWaterMark(writableStrategy, 1); - const writableSizeAlgorithm = extractSizeAlgorithm(writableStrategy); - /** @type {Deferred} */ - const startPromise = new Deferred(); - initializeTransformStream( - this, - startPromise, - writableHighWaterMark, - writableSizeAlgorithm, - readableHighWaterMark, - readableSizeAlgorithm, + } + writableStrategy = webidl.converters.QueuingStrategy(writableStrategy, { + prefix, + context: "Argument 2", + }); + readableStrategy = webidl.converters.QueuingStrategy(readableStrategy, { + prefix, + context: "Argument 2", + }); + this[webidl.brand] = webidl.brand; + if (transformer === undefined) { + transformer = null; + } + const transformerDict = webidl.converters.Transformer(transformer, { + prefix, + context: "transformer", + }); + if (transformerDict.readableType !== undefined) { + throw new RangeError( + `${prefix}: readableType transformers not supported.`, ); - setUpTransformStreamDefaultControllerFromTransformer( - this, - transformer, - transformerDict, + } + if (transformerDict.writableType !== undefined) { + throw new RangeError( + `${prefix}: writableType transformers not supported.`, ); - if (transformerDict.start) { - startPromise.resolve( - webidl.invokeCallbackFunction( - transformerDict.start, - [this[_controller]], - transformer, - webidl.converters.any, - { - prefix: - "Failed to call 'start' on 'TransformStreamDefaultController'", - }, - ), - ); - } else { - startPromise.resolve(undefined); - } } - - /** @returns {ReadableStream} */ - get readable() { - webidl.assertBranded(this, TransformStreamPrototype); - return this[_readable]; + const readableHighWaterMark = extractHighWaterMark(readableStrategy, 0); + const readableSizeAlgorithm = extractSizeAlgorithm(readableStrategy); + const writableHighWaterMark = extractHighWaterMark(writableStrategy, 1); + const writableSizeAlgorithm = extractSizeAlgorithm(writableStrategy); + /** @type {Deferred} */ + const startPromise = new Deferred(); + initializeTransformStream( + this, + startPromise, + writableHighWaterMark, + writableSizeAlgorithm, + readableHighWaterMark, + readableSizeAlgorithm, + ); + setUpTransformStreamDefaultControllerFromTransformer( + this, + transformer, + transformerDict, + ); + if (transformerDict.start) { + startPromise.resolve( + webidl.invokeCallbackFunction( + transformerDict.start, + [this[_controller]], + transformer, + webidl.converters.any, + { + prefix: + "Failed to call 'start' on 'TransformStreamDefaultController'", + }, + ), + ); + } else { + startPromise.resolve(undefined); } + } + + /** @returns {ReadableStream} */ + get readable() { + webidl.assertBranded(this, TransformStreamPrototype); + return this[_readable]; + } + + /** @returns {WritableStream} */ + get writable() { + webidl.assertBranded(this, TransformStreamPrototype); + return this[_writable]; + } + + [SymbolFor("Deno.privateCustomInspect")](inspect) { + return `${this.constructor.name} ${ + inspect({ readable: this.readable, writable: this.writable }) + }`; + } +} + +webidl.configurePrototype(TransformStream); +const TransformStreamPrototype = TransformStream.prototype; + +/** @template O */ +class TransformStreamDefaultController { + /** @type {(controller: this) => Promise} */ + [_flushAlgorithm]; + /** @type {TransformStream} */ + [_stream]; + /** @type {(chunk: O, controller: this) => Promise} */ + [_transformAlgorithm]; + + constructor() { + webidl.illegalConstructor(); + } + + /** @returns {number | null} */ + get desiredSize() { + webidl.assertBranded(this, TransformStreamDefaultController.prototype); + const readableController = this[_stream][_readable][_controller]; + return readableStreamDefaultControllerGetDesiredSize( + /** @type {ReadableStreamDefaultController} */ readableController, + ); + } - /** @returns {WritableStream} */ - get writable() { - webidl.assertBranded(this, TransformStreamPrototype); - return this[_writable]; + /** + * @param {O} chunk + * @returns {void} + */ + enqueue(chunk = undefined) { + webidl.assertBranded(this, TransformStreamDefaultController.prototype); + if (chunk !== undefined) { + chunk = webidl.converters.any(chunk); } + transformStreamDefaultControllerEnqueue(this, chunk); + } - [SymbolFor("Deno.privateCustomInspect")](inspect) { - return `${this.constructor.name} ${ - inspect({ readable: this.readable, writable: this.writable }) - }`; + /** + * @param {any=} reason + * @returns {void} + */ + error(reason = undefined) { + webidl.assertBranded(this, TransformStreamDefaultController.prototype); + if (reason !== undefined) { + reason = webidl.converters.any(reason); } + transformStreamDefaultControllerError(this, reason); } - webidl.configurePrototype(TransformStream); - const TransformStreamPrototype = TransformStream.prototype; - - /** @template O */ - class TransformStreamDefaultController { - /** @type {(controller: this) => Promise} */ - [_flushAlgorithm]; - /** @type {TransformStream} */ - [_stream]; - /** @type {(chunk: O, controller: this) => Promise} */ - [_transformAlgorithm]; + /** @returns {void} */ + terminate() { + webidl.assertBranded(this, TransformStreamDefaultControllerPrototype); + transformStreamDefaultControllerTerminate(this); + } - constructor() { - webidl.illegalConstructor(); + [SymbolFor("Deno.customInspect")](inspect) { + return inspect(createFilteredInspectProxy({ + object: this, + evaluate: ObjectPrototypeIsPrototypeOf( + TransformStreamDefaultController.prototype, + this, + ), + keys: ["desiredSize"], + })); + } +} + +webidl.configurePrototype(TransformStreamDefaultController); +const TransformStreamDefaultControllerPrototype = + TransformStreamDefaultController.prototype; + +/** @template W */ +class WritableStream { + /** @type {boolean} */ + [_backpressure]; + /** @type {Deferred | undefined} */ + [_closeRequest]; + /** @type {WritableStreamDefaultController} */ + [_controller]; + /** @type {boolean} */ + [_detached]; + /** @type {Deferred | undefined} */ + [_inFlightWriteRequest]; + /** @type {Deferred | undefined} */ + [_inFlightCloseRequest]; + /** @type {PendingAbortRequest | undefined} */ + [_pendingAbortRequest]; + /** @type {"writable" | "closed" | "erroring" | "errored"} */ + [_state]; + /** @type {any} */ + [_storedError]; + /** @type {WritableStreamDefaultWriter} */ + [_writer]; + /** @type {Deferred[]} */ + [_writeRequests]; + + /** + * @param {UnderlyingSink=} underlyingSink + * @param {QueuingStrategy=} strategy + */ + constructor(underlyingSink = undefined, strategy = {}) { + const prefix = "Failed to construct 'WritableStream'"; + if (underlyingSink !== undefined) { + underlyingSink = webidl.converters.object(underlyingSink, { + prefix, + context: "Argument 1", + }); } - - /** @returns {number | null} */ - get desiredSize() { - webidl.assertBranded(this, TransformStreamDefaultController.prototype); - const readableController = this[_stream][_readable][_controller]; - return readableStreamDefaultControllerGetDesiredSize( - /** @type {ReadableStreamDefaultController} */ readableController, + strategy = webidl.converters.QueuingStrategy(strategy, { + prefix, + context: "Argument 2", + }); + this[webidl.brand] = webidl.brand; + if (underlyingSink === undefined) { + underlyingSink = null; + } + const underlyingSinkDict = webidl.converters.UnderlyingSink( + underlyingSink, + { prefix, context: "underlyingSink" }, + ); + if (underlyingSinkDict.type != null) { + throw new RangeError( + `${prefix}: WritableStream does not support 'type' in the underlying sink.`, ); } + initializeWritableStream(this); + const sizeAlgorithm = extractSizeAlgorithm(strategy); + const highWaterMark = extractHighWaterMark(strategy, 1); + setUpWritableStreamDefaultControllerFromUnderlyingSink( + this, + underlyingSink, + underlyingSinkDict, + highWaterMark, + sizeAlgorithm, + ); + } - /** - * @param {O} chunk - * @returns {void} - */ - enqueue(chunk = undefined) { - webidl.assertBranded(this, TransformStreamDefaultController.prototype); - if (chunk !== undefined) { - chunk = webidl.converters.any(chunk); - } - transformStreamDefaultControllerEnqueue(this, chunk); - } + /** @returns {boolean} */ + get locked() { + webidl.assertBranded(this, WritableStreamPrototype); + return isWritableStreamLocked(this); + } - /** - * @param {any=} reason - * @returns {void} - */ - error(reason = undefined) { - webidl.assertBranded(this, TransformStreamDefaultController.prototype); - if (reason !== undefined) { - reason = webidl.converters.any(reason); - } - transformStreamDefaultControllerError(this, reason); + /** + * @param {any=} reason + * @returns {Promise} + */ + abort(reason = undefined) { + try { + webidl.assertBranded(this, WritableStreamPrototype); + } catch (err) { + return PromiseReject(err); } - - /** @returns {void} */ - terminate() { - webidl.assertBranded(this, TransformStreamDefaultControllerPrototype); - transformStreamDefaultControllerTerminate(this); + if (reason !== undefined) { + reason = webidl.converters.any(reason); } - - [SymbolFor("Deno.customInspect")](inspect) { - return inspect(consoleInternal.createFilteredInspectProxy({ - object: this, - evaluate: ObjectPrototypeIsPrototypeOf( - TransformStreamDefaultController.prototype, - this, + if (isWritableStreamLocked(this)) { + return PromiseReject( + new TypeError( + "The writable stream is locked, therefore cannot be aborted.", ), - keys: ["desiredSize"], - })); - } - } - - webidl.configurePrototype(TransformStreamDefaultController); - const TransformStreamDefaultControllerPrototype = - TransformStreamDefaultController.prototype; - - /** @template W */ - class WritableStream { - /** @type {boolean} */ - [_backpressure]; - /** @type {Deferred | undefined} */ - [_closeRequest]; - /** @type {WritableStreamDefaultController} */ - [_controller]; - /** @type {boolean} */ - [_detached]; - /** @type {Deferred | undefined} */ - [_inFlightWriteRequest]; - /** @type {Deferred | undefined} */ - [_inFlightCloseRequest]; - /** @type {PendingAbortRequest | undefined} */ - [_pendingAbortRequest]; - /** @type {"writable" | "closed" | "erroring" | "errored"} */ - [_state]; - /** @type {any} */ - [_storedError]; - /** @type {WritableStreamDefaultWriter} */ - [_writer]; - /** @type {Deferred[]} */ - [_writeRequests]; - - /** - * @param {UnderlyingSink=} underlyingSink - * @param {QueuingStrategy=} strategy - */ - constructor(underlyingSink = undefined, strategy = {}) { - const prefix = "Failed to construct 'WritableStream'"; - if (underlyingSink !== undefined) { - underlyingSink = webidl.converters.object(underlyingSink, { - prefix, - context: "Argument 1", - }); - } - strategy = webidl.converters.QueuingStrategy(strategy, { - prefix, - context: "Argument 2", - }); - this[webidl.brand] = webidl.brand; - if (underlyingSink === undefined) { - underlyingSink = null; - } - const underlyingSinkDict = webidl.converters.UnderlyingSink( - underlyingSink, - { prefix, context: "underlyingSink" }, - ); - if (underlyingSinkDict.type != null) { - throw new RangeError( - `${prefix}: WritableStream does not support 'type' in the underlying sink.`, - ); - } - initializeWritableStream(this); - const sizeAlgorithm = extractSizeAlgorithm(strategy); - const highWaterMark = extractHighWaterMark(strategy, 1); - setUpWritableStreamDefaultControllerFromUnderlyingSink( - this, - underlyingSink, - underlyingSinkDict, - highWaterMark, - sizeAlgorithm, ); } + return writableStreamAbort(this, reason); + } - /** @returns {boolean} */ - get locked() { + /** @returns {Promise} */ + close() { + try { webidl.assertBranded(this, WritableStreamPrototype); - return isWritableStreamLocked(this); + } catch (err) { + return PromiseReject(err); } - - /** - * @param {any=} reason - * @returns {Promise} - */ - abort(reason = undefined) { - try { - webidl.assertBranded(this, WritableStreamPrototype); - } catch (err) { - return PromiseReject(err); - } - if (reason !== undefined) { - reason = webidl.converters.any(reason); - } - if (isWritableStreamLocked(this)) { - return PromiseReject( - new TypeError( - "The writable stream is locked, therefore cannot be aborted.", - ), - ); - } - return writableStreamAbort(this, reason); + if (isWritableStreamLocked(this)) { + return PromiseReject( + new TypeError( + "The writable stream is locked, therefore cannot be closed.", + ), + ); } - - /** @returns {Promise} */ - close() { - try { - webidl.assertBranded(this, WritableStreamPrototype); - } catch (err) { - return PromiseReject(err); - } - if (isWritableStreamLocked(this)) { - return PromiseReject( - new TypeError( - "The writable stream is locked, therefore cannot be closed.", - ), - ); - } - if (writableStreamCloseQueuedOrInFlight(this) === true) { - return PromiseReject( - new TypeError("The writable stream is already closing."), - ); - } - return writableStreamClose(this); + if (writableStreamCloseQueuedOrInFlight(this) === true) { + return PromiseReject( + new TypeError("The writable stream is already closing."), + ); } + return writableStreamClose(this); + } - /** @returns {WritableStreamDefaultWriter} */ - getWriter() { - webidl.assertBranded(this, WritableStreamPrototype); - return acquireWritableStreamDefaultWriter(this); - } + /** @returns {WritableStreamDefaultWriter} */ + getWriter() { + webidl.assertBranded(this, WritableStreamPrototype); + return acquireWritableStreamDefaultWriter(this); + } - [SymbolFor("Deno.privateCustomInspect")](inspect) { - return `${this.constructor.name} ${inspect({ locked: this.locked })}`; - } + [SymbolFor("Deno.privateCustomInspect")](inspect) { + return `${this.constructor.name} ${inspect({ locked: this.locked })}`; } +} - webidl.configurePrototype(WritableStream); - const WritableStreamPrototype = WritableStream.prototype; +webidl.configurePrototype(WritableStream); +const WritableStreamPrototype = WritableStream.prototype; - /** @template W */ - class WritableStreamDefaultWriter { - /** @type {Deferred} */ - [_closedPromise]; +/** @template W */ +class WritableStreamDefaultWriter { + /** @type {Deferred} */ + [_closedPromise]; - /** @type {Deferred} */ - [_readyPromise]; + /** @type {Deferred} */ + [_readyPromise]; - /** @type {WritableStream} */ - [_stream]; + /** @type {WritableStream} */ + [_stream]; - /** - * @param {WritableStream} stream - */ - constructor(stream) { - const prefix = "Failed to construct 'WritableStreamDefaultWriter'"; - webidl.requiredArguments(arguments.length, 1, { prefix }); - stream = webidl.converters.WritableStream(stream, { - prefix, - context: "Argument 1", - }); - this[webidl.brand] = webidl.brand; - setUpWritableStreamDefaultWriter(this, stream); + /** + * @param {WritableStream} stream + */ + constructor(stream) { + const prefix = "Failed to construct 'WritableStreamDefaultWriter'"; + webidl.requiredArguments(arguments.length, 1, { prefix }); + stream = webidl.converters.WritableStream(stream, { + prefix, + context: "Argument 1", + }); + this[webidl.brand] = webidl.brand; + setUpWritableStreamDefaultWriter(this, stream); + } + + /** @returns {Promise} */ + get closed() { + try { + webidl.assertBranded(this, WritableStreamDefaultWriterPrototype); + } catch (err) { + return PromiseReject(err); } + return this[_closedPromise].promise; + } - /** @returns {Promise} */ - get closed() { - try { - webidl.assertBranded(this, WritableStreamDefaultWriterPrototype); - } catch (err) { - return PromiseReject(err); - } - return this[_closedPromise].promise; + /** @returns {number} */ + get desiredSize() { + webidl.assertBranded(this, WritableStreamDefaultWriterPrototype); + if (this[_stream] === undefined) { + throw new TypeError( + "A writable stream is not associated with the writer.", + ); } + return writableStreamDefaultWriterGetDesiredSize(this); + } - /** @returns {number} */ - get desiredSize() { + /** @returns {Promise} */ + get ready() { + try { webidl.assertBranded(this, WritableStreamDefaultWriterPrototype); - if (this[_stream] === undefined) { - throw new TypeError( - "A writable stream is not associated with the writer.", - ); - } - return writableStreamDefaultWriterGetDesiredSize(this); + } catch (err) { + return PromiseReject(err); } + return this[_readyPromise].promise; + } - /** @returns {Promise} */ - get ready() { - try { - webidl.assertBranded(this, WritableStreamDefaultWriterPrototype); - } catch (err) { - return PromiseReject(err); - } - return this[_readyPromise].promise; + /** + * @param {any} reason + * @returns {Promise} + */ + abort(reason = undefined) { + try { + webidl.assertBranded(this, WritableStreamDefaultWriterPrototype); + } catch (err) { + return PromiseReject(err); } - - /** - * @param {any} reason - * @returns {Promise} - */ - abort(reason = undefined) { - try { - webidl.assertBranded(this, WritableStreamDefaultWriterPrototype); - } catch (err) { - return PromiseReject(err); - } - if (reason !== undefined) { - reason = webidl.converters.any(reason); - } - if (this[_stream] === undefined) { - return PromiseReject( - new TypeError("A writable stream is not associated with the writer."), - ); - } - return writableStreamDefaultWriterAbort(this, reason); + if (reason !== undefined) { + reason = webidl.converters.any(reason); } - - /** @returns {Promise} */ - close() { - try { - webidl.assertBranded(this, WritableStreamDefaultWriterPrototype); - } catch (err) { - return PromiseReject(err); - } - const stream = this[_stream]; - if (stream === undefined) { - return PromiseReject( - new TypeError("A writable stream is not associated with the writer."), - ); - } - if (writableStreamCloseQueuedOrInFlight(stream) === true) { - return PromiseReject( - new TypeError("The associated stream is already closing."), - ); - } - return writableStreamDefaultWriterClose(this); + if (this[_stream] === undefined) { + return PromiseReject( + new TypeError("A writable stream is not associated with the writer."), + ); } + return writableStreamDefaultWriterAbort(this, reason); + } - /** @returns {void} */ - releaseLock() { + /** @returns {Promise} */ + close() { + try { webidl.assertBranded(this, WritableStreamDefaultWriterPrototype); - const stream = this[_stream]; - if (stream === undefined) { - return; - } - assert(stream[_writer] !== undefined); - writableStreamDefaultWriterRelease(this); + } catch (err) { + return PromiseReject(err); } + const stream = this[_stream]; + if (stream === undefined) { + return PromiseReject( + new TypeError("A writable stream is not associated with the writer."), + ); + } + if (writableStreamCloseQueuedOrInFlight(stream) === true) { + return PromiseReject( + new TypeError("The associated stream is already closing."), + ); + } + return writableStreamDefaultWriterClose(this); + } - /** - * @param {W} chunk - * @returns {Promise} - */ - write(chunk = undefined) { - try { - webidl.assertBranded(this, WritableStreamDefaultWriterPrototype); - if (chunk !== undefined) { - chunk = webidl.converters.any(chunk); - } - } catch (err) { - return PromiseReject(err); - } - if (this[_stream] === undefined) { - return PromiseReject( - new TypeError("A writable stream is not associate with the writer."), - ); - } - return writableStreamDefaultWriterWrite(this, chunk); + /** @returns {void} */ + releaseLock() { + webidl.assertBranded(this, WritableStreamDefaultWriterPrototype); + const stream = this[_stream]; + if (stream === undefined) { + return; } + assert(stream[_writer] !== undefined); + writableStreamDefaultWriterRelease(this); + } - [SymbolFor("Deno.customInspect")](inspect) { - return inspect(consoleInternal.createFilteredInspectProxy({ - object: this, - evaluate: ObjectPrototypeIsPrototypeOf( - WritableStreamDefaultWriter.prototype, - this, - ), - keys: [ - "closed", - "desiredSize", - "ready", - ], - })); - } - } - - webidl.configurePrototype(WritableStreamDefaultWriter); - const WritableStreamDefaultWriterPrototype = - WritableStreamDefaultWriter.prototype; - - /** @template W */ - class WritableStreamDefaultController { - /** @type {(reason?: any) => Promise} */ - [_abortAlgorithm]; - /** @type {() => Promise} */ - [_closeAlgorithm]; - /** @type {ValueWithSize[]} */ - [_queue]; - /** @type {number} */ - [_queueTotalSize]; - /** @type {boolean} */ - [_started]; - /** @type {number} */ - [_strategyHWM]; - /** @type {(chunk: W) => number} */ - [_strategySizeAlgorithm]; - /** @type {WritableStream} */ - [_stream]; - /** @type {(chunk: W, controller: this) => Promise} */ - [_writeAlgorithm]; - /** @type {AbortSignal} */ - [_signal]; - - get signal() { - webidl.assertBranded(this, WritableStreamDefaultControllerPrototype); - return this[_signal]; - } - - constructor() { - webidl.illegalConstructor(); - } - - /** - * @param {any=} e - * @returns {void} - */ - error(e = undefined) { - webidl.assertBranded(this, WritableStreamDefaultControllerPrototype); - if (e !== undefined) { - e = webidl.converters.any(e); - } - const state = this[_stream][_state]; - if (state !== "writable") { - return; + /** + * @param {W} chunk + * @returns {Promise} + */ + write(chunk = undefined) { + try { + webidl.assertBranded(this, WritableStreamDefaultWriterPrototype); + if (chunk !== undefined) { + chunk = webidl.converters.any(chunk); } - writableStreamDefaultControllerError(this, e); + } catch (err) { + return PromiseReject(err); } - - [SymbolFor("Deno.customInspect")](inspect) { - return inspect(consoleInternal.createFilteredInspectProxy({ - object: this, - evaluate: ObjectPrototypeIsPrototypeOf( - WritableStreamDefaultController.prototype, - this, - ), - keys: [], - })); + if (this[_stream] === undefined) { + return PromiseReject( + new TypeError("A writable stream is not associate with the writer."), + ); } + return writableStreamDefaultWriterWrite(this, chunk); + } - /** - * @param {any=} reason - * @returns {Promise} - */ - [_abortSteps](reason) { - const result = this[_abortAlgorithm](reason); - writableStreamDefaultControllerClearAlgorithms(this); - return result; - } + [SymbolFor("Deno.customInspect")](inspect) { + return inspect(createFilteredInspectProxy({ + object: this, + evaluate: ObjectPrototypeIsPrototypeOf( + WritableStreamDefaultWriter.prototype, + this, + ), + keys: [ + "closed", + "desiredSize", + "ready", + ], + })); + } +} + +webidl.configurePrototype(WritableStreamDefaultWriter); +const WritableStreamDefaultWriterPrototype = + WritableStreamDefaultWriter.prototype; + +/** @template W */ +class WritableStreamDefaultController { + /** @type {(reason?: any) => Promise} */ + [_abortAlgorithm]; + /** @type {() => Promise} */ + [_closeAlgorithm]; + /** @type {ValueWithSize[]} */ + [_queue]; + /** @type {number} */ + [_queueTotalSize]; + /** @type {boolean} */ + [_started]; + /** @type {number} */ + [_strategyHWM]; + /** @type {(chunk: W) => number} */ + [_strategySizeAlgorithm]; + /** @type {WritableStream} */ + [_stream]; + /** @type {(chunk: W, controller: this) => Promise} */ + [_writeAlgorithm]; + /** @type {AbortSignal} */ + [_signal]; + + get signal() { + webidl.assertBranded(this, WritableStreamDefaultControllerPrototype); + return this[_signal]; + } + + constructor() { + webidl.illegalConstructor(); + } - [_errorSteps]() { - resetQueue(this); + /** + * @param {any=} e + * @returns {void} + */ + error(e = undefined) { + webidl.assertBranded(this, WritableStreamDefaultControllerPrototype); + if (e !== undefined) { + e = webidl.converters.any(e); + } + const state = this[_stream][_state]; + if (state !== "writable") { + return; } + writableStreamDefaultControllerError(this, e); } - webidl.configurePrototype(WritableStreamDefaultController); - const WritableStreamDefaultControllerPrototype = - WritableStreamDefaultController.prototype; + [SymbolFor("Deno.customInspect")](inspect) { + return inspect(createFilteredInspectProxy({ + object: this, + evaluate: ObjectPrototypeIsPrototypeOf( + WritableStreamDefaultController.prototype, + this, + ), + keys: [], + })); + } /** - * @param {ReadableStream} stream + * @param {any=} reason + * @returns {Promise} */ - function createProxy(stream) { - return stream.pipeThrough(new TransformStream()); + [_abortSteps](reason) { + const result = this[_abortAlgorithm](reason); + writableStreamDefaultControllerClearAlgorithms(this); + return result; } - webidl.converters.ReadableStream = webidl - .createInterfaceConverter("ReadableStream", ReadableStream.prototype); - webidl.converters.WritableStream = webidl - .createInterfaceConverter("WritableStream", WritableStream.prototype); + [_errorSteps]() { + resetQueue(this); + } +} - webidl.converters.ReadableStreamType = webidl.createEnumConverter( - "ReadableStreamType", - ["bytes"], - ); +webidl.configurePrototype(WritableStreamDefaultController); +const WritableStreamDefaultControllerPrototype = + WritableStreamDefaultController.prototype; - webidl.converters.UnderlyingSource = webidl - .createDictionaryConverter("UnderlyingSource", [ - { - key: "start", - converter: webidl.converters.Function, - }, - { - key: "pull", - converter: webidl.converters.Function, - }, - { - key: "cancel", - converter: webidl.converters.Function, - }, - { - key: "type", - converter: webidl.converters.ReadableStreamType, - }, - { - key: "autoAllocateChunkSize", - converter: (V, opts) => - webidl.converters["unsigned long long"](V, { - ...opts, - enforceRange: true, - }), - }, - ]); - webidl.converters.UnderlyingSink = webidl - .createDictionaryConverter("UnderlyingSink", [ - { - key: "start", - converter: webidl.converters.Function, - }, - { - key: "write", - converter: webidl.converters.Function, - }, - { - key: "close", - converter: webidl.converters.Function, - }, - { - key: "abort", - converter: webidl.converters.Function, - }, - { - key: "type", - converter: webidl.converters.any, - }, - ]); - webidl.converters.Transformer = webidl - .createDictionaryConverter("Transformer", [ - { - key: "start", - converter: webidl.converters.Function, - }, - { - key: "transform", - converter: webidl.converters.Function, - }, - { - key: "flush", - converter: webidl.converters.Function, - }, - { - key: "readableType", - converter: webidl.converters.any, - }, - { - key: "writableType", - converter: webidl.converters.any, - }, - ]); - webidl.converters.QueuingStrategy = webidl - .createDictionaryConverter("QueuingStrategy", [ - { - key: "highWaterMark", - converter: webidl.converters["unrestricted double"], - }, - { - key: "size", - converter: webidl.converters.Function, - }, - ]); - webidl.converters.QueuingStrategyInit = webidl - .createDictionaryConverter("QueuingStrategyInit", [ - { - key: "highWaterMark", - converter: webidl.converters["unrestricted double"], - required: true, - }, - ]); - - webidl.converters.ReadableStreamIteratorOptions = webidl - .createDictionaryConverter("ReadableStreamIteratorOptions", [ - { - key: "preventCancel", - defaultValue: false, - converter: webidl.converters.boolean, - }, - ]); - - webidl.converters.ReadableStreamReaderMode = webidl - .createEnumConverter("ReadableStreamReaderMode", ["byob"]); - webidl.converters.ReadableStreamGetReaderOptions = webidl - .createDictionaryConverter("ReadableStreamGetReaderOptions", [{ - key: "mode", - converter: webidl.converters.ReadableStreamReaderMode, - }]); - - webidl.converters.ReadableWritablePair = webidl - .createDictionaryConverter("ReadableWritablePair", [ - { - key: "readable", - converter: webidl.converters.ReadableStream, - required: true, - }, - { - key: "writable", - converter: webidl.converters.WritableStream, - required: true, - }, - ]); - webidl.converters.StreamPipeOptions = webidl - .createDictionaryConverter("StreamPipeOptions", [ - { - key: "preventClose", - defaultValue: false, - converter: webidl.converters.boolean, - }, - { - key: "preventAbort", - defaultValue: false, - converter: webidl.converters.boolean, - }, - { - key: "preventCancel", - defaultValue: false, - converter: webidl.converters.boolean, - }, - { key: "signal", converter: webidl.converters.AbortSignal }, - ]); - - window.__bootstrap.streams = { - // Non-Public - _state, - isReadableStreamDisturbed, - errorReadableStream, - createProxy, - writableStreamClose, - readableStreamClose, - readableStreamCollectIntoUint8Array, - readableStreamDisturb, - readableStreamForRid, - readableStreamForRidUnrefable, - readableStreamForRidUnrefableRef, - readableStreamForRidUnrefableUnref, - readableStreamThrowIfErrored, - getReadableStreamResourceBacking, - writableStreamForRid, - getWritableStreamResourceBacking, - Deferred, - // Exposed in global runtime scope - ByteLengthQueuingStrategy, - CountQueuingStrategy, - ReadableStream, - ReadableStreamPrototype, - ReadableStreamDefaultReader, - TransformStream, - WritableStream, - WritableStreamDefaultWriter, - WritableStreamDefaultController, - ReadableByteStreamController, - ReadableStreamBYOBReader, - ReadableStreamBYOBRequest, - ReadableStreamDefaultController, - TransformStreamDefaultController, - }; -})(this); +/** + * @param {ReadableStream} stream + */ +function createProxy(stream) { + return stream.pipeThrough(new TransformStream()); +} + +webidl.converters.ReadableStream = webidl + .createInterfaceConverter("ReadableStream", ReadableStream.prototype); +webidl.converters.WritableStream = webidl + .createInterfaceConverter("WritableStream", WritableStream.prototype); + +webidl.converters.ReadableStreamType = webidl.createEnumConverter( + "ReadableStreamType", + ["bytes"], +); + +webidl.converters.UnderlyingSource = webidl + .createDictionaryConverter("UnderlyingSource", [ + { + key: "start", + converter: webidl.converters.Function, + }, + { + key: "pull", + converter: webidl.converters.Function, + }, + { + key: "cancel", + converter: webidl.converters.Function, + }, + { + key: "type", + converter: webidl.converters.ReadableStreamType, + }, + { + key: "autoAllocateChunkSize", + converter: (V, opts) => + webidl.converters["unsigned long long"](V, { + ...opts, + enforceRange: true, + }), + }, + ]); +webidl.converters.UnderlyingSink = webidl + .createDictionaryConverter("UnderlyingSink", [ + { + key: "start", + converter: webidl.converters.Function, + }, + { + key: "write", + converter: webidl.converters.Function, + }, + { + key: "close", + converter: webidl.converters.Function, + }, + { + key: "abort", + converter: webidl.converters.Function, + }, + { + key: "type", + converter: webidl.converters.any, + }, + ]); +webidl.converters.Transformer = webidl + .createDictionaryConverter("Transformer", [ + { + key: "start", + converter: webidl.converters.Function, + }, + { + key: "transform", + converter: webidl.converters.Function, + }, + { + key: "flush", + converter: webidl.converters.Function, + }, + { + key: "readableType", + converter: webidl.converters.any, + }, + { + key: "writableType", + converter: webidl.converters.any, + }, + ]); +webidl.converters.QueuingStrategy = webidl + .createDictionaryConverter("QueuingStrategy", [ + { + key: "highWaterMark", + converter: webidl.converters["unrestricted double"], + }, + { + key: "size", + converter: webidl.converters.Function, + }, + ]); +webidl.converters.QueuingStrategyInit = webidl + .createDictionaryConverter("QueuingStrategyInit", [ + { + key: "highWaterMark", + converter: webidl.converters["unrestricted double"], + required: true, + }, + ]); + +webidl.converters.ReadableStreamIteratorOptions = webidl + .createDictionaryConverter("ReadableStreamIteratorOptions", [ + { + key: "preventCancel", + defaultValue: false, + converter: webidl.converters.boolean, + }, + ]); + +webidl.converters.ReadableStreamReaderMode = webidl + .createEnumConverter("ReadableStreamReaderMode", ["byob"]); +webidl.converters.ReadableStreamGetReaderOptions = webidl + .createDictionaryConverter("ReadableStreamGetReaderOptions", [{ + key: "mode", + converter: webidl.converters.ReadableStreamReaderMode, + }]); + +webidl.converters.ReadableWritablePair = webidl + .createDictionaryConverter("ReadableWritablePair", [ + { + key: "readable", + converter: webidl.converters.ReadableStream, + required: true, + }, + { + key: "writable", + converter: webidl.converters.WritableStream, + required: true, + }, + ]); +webidl.converters.StreamPipeOptions = webidl + .createDictionaryConverter("StreamPipeOptions", [ + { + key: "preventClose", + defaultValue: false, + converter: webidl.converters.boolean, + }, + { + key: "preventAbort", + defaultValue: false, + converter: webidl.converters.boolean, + }, + { + key: "preventCancel", + defaultValue: false, + converter: webidl.converters.boolean, + }, + { key: "signal", converter: webidl.converters.AbortSignal }, + ]); + +export { + // Non-Public + _state, + // Exposed in global runtime scope + ByteLengthQueuingStrategy, + CountQueuingStrategy, + createProxy, + Deferred, + errorReadableStream, + getReadableStreamResourceBacking, + getWritableStreamResourceBacking, + isReadableStreamDisturbed, + ReadableByteStreamController, + ReadableStream, + ReadableStreamBYOBReader, + ReadableStreamBYOBRequest, + readableStreamClose, + readableStreamCollectIntoUint8Array, + ReadableStreamDefaultController, + ReadableStreamDefaultReader, + readableStreamDisturb, + readableStreamForRid, + readableStreamForRidUnrefable, + readableStreamForRidUnrefableRef, + readableStreamForRidUnrefableUnref, + ReadableStreamPrototype, + readableStreamThrowIfErrored, + TransformStream, + TransformStreamDefaultController, + WritableStream, + writableStreamClose, + WritableStreamDefaultController, + WritableStreamDefaultWriter, + writableStreamForRid, +}; diff --git a/ext/web/08_text_encoding.js b/ext/web/08_text_encoding.js index 8de7b949f2826a..571b9b62826bac 100644 --- a/ext/web/08_text_encoding.js +++ b/ext/web/08_text_encoding.js @@ -9,437 +9,434 @@ /// /// -"use strict"; - -((window) => { - const core = Deno.core; - const ops = core.ops; - const webidl = window.__bootstrap.webidl; - const { - PromiseReject, - PromiseResolve, - // TODO(lucacasonato): add SharedArrayBuffer to primordials - // SharedArrayBufferPrototype - StringPrototypeCharCodeAt, - StringPrototypeSlice, - TypedArrayPrototypeSubarray, - Uint8Array, - ObjectPrototypeIsPrototypeOf, - ArrayBufferIsView, - Uint32Array, - } = window.__bootstrap.primordials; - - class TextDecoder { - /** @type {string} */ - #encoding; - /** @type {boolean} */ - #fatal; - /** @type {boolean} */ - #ignoreBOM; - /** @type {boolean} */ - #utf8SinglePass; - - /** @type {number | null} */ - #rid = null; - - /** - * @param {string} label - * @param {TextDecoderOptions} options - */ - constructor(label = "utf-8", options = {}) { - const prefix = "Failed to construct 'TextDecoder'"; - label = webidl.converters.DOMString(label, { +const core = globalThis.Deno.core; +const ops = core.ops; +import * as webidl from "internal:deno_webidl/00_webidl.js"; +const primordials = globalThis.__bootstrap.primordials; +const { + PromiseReject, + PromiseResolve, + // TODO(lucacasonato): add SharedArrayBuffer to primordials + // SharedArrayBufferPrototype + StringPrototypeCharCodeAt, + StringPrototypeSlice, + TypedArrayPrototypeSubarray, + Uint8Array, + ObjectPrototypeIsPrototypeOf, + ArrayBufferIsView, + Uint32Array, +} = primordials; + +class TextDecoder { + /** @type {string} */ + #encoding; + /** @type {boolean} */ + #fatal; + /** @type {boolean} */ + #ignoreBOM; + /** @type {boolean} */ + #utf8SinglePass; + + /** @type {number | null} */ + #rid = null; + + /** + * @param {string} label + * @param {TextDecoderOptions} options + */ + constructor(label = "utf-8", options = {}) { + const prefix = "Failed to construct 'TextDecoder'"; + label = webidl.converters.DOMString(label, { + prefix, + context: "Argument 1", + }); + options = webidl.converters.TextDecoderOptions(options, { + prefix, + context: "Argument 2", + }); + const encoding = ops.op_encoding_normalize_label(label); + this.#encoding = encoding; + this.#fatal = options.fatal; + this.#ignoreBOM = options.ignoreBOM; + this.#utf8SinglePass = encoding === "utf-8" && !options.fatal; + this[webidl.brand] = webidl.brand; + } + + /** @returns {string} */ + get encoding() { + webidl.assertBranded(this, TextDecoderPrototype); + return this.#encoding; + } + + /** @returns {boolean} */ + get fatal() { + webidl.assertBranded(this, TextDecoderPrototype); + return this.#fatal; + } + + /** @returns {boolean} */ + get ignoreBOM() { + webidl.assertBranded(this, TextDecoderPrototype); + return this.#ignoreBOM; + } + + /** + * @param {BufferSource} [input] + * @param {TextDecodeOptions} options + */ + decode(input = new Uint8Array(), options = undefined) { + webidl.assertBranded(this, TextDecoderPrototype); + const prefix = "Failed to execute 'decode' on 'TextDecoder'"; + if (input !== undefined) { + input = webidl.converters.BufferSource(input, { prefix, context: "Argument 1", + allowShared: true, }); - options = webidl.converters.TextDecoderOptions(options, { + } + let stream = false; + if (options !== undefined) { + options = webidl.converters.TextDecodeOptions(options, { prefix, context: "Argument 2", }); - const encoding = ops.op_encoding_normalize_label(label); - this.#encoding = encoding; - this.#fatal = options.fatal; - this.#ignoreBOM = options.ignoreBOM; - this.#utf8SinglePass = encoding === "utf-8" && !options.fatal; - this[webidl.brand] = webidl.brand; + stream = options.stream; } - /** @returns {string} */ - get encoding() { - webidl.assertBranded(this, TextDecoderPrototype); - return this.#encoding; - } - - /** @returns {boolean} */ - get fatal() { - webidl.assertBranded(this, TextDecoderPrototype); - return this.#fatal; - } - - /** @returns {boolean} */ - get ignoreBOM() { - webidl.assertBranded(this, TextDecoderPrototype); - return this.#ignoreBOM; - } - - /** - * @param {BufferSource} [input] - * @param {TextDecodeOptions} options - */ - decode(input = new Uint8Array(), options = undefined) { - webidl.assertBranded(this, TextDecoderPrototype); - const prefix = "Failed to execute 'decode' on 'TextDecoder'"; - if (input !== undefined) { - input = webidl.converters.BufferSource(input, { - prefix, - context: "Argument 1", - allowShared: true, - }); - } - let stream = false; - if (options !== undefined) { - options = webidl.converters.TextDecodeOptions(options, { - prefix, - context: "Argument 2", - }); - stream = options.stream; + try { + // Note from spec: implementations are strongly encouraged to use an implementation strategy that avoids this copy. + // When doing so they will have to make sure that changes to input do not affect future calls to decode(). + if ( + ObjectPrototypeIsPrototypeOf( + // deno-lint-ignore prefer-primordials + SharedArrayBuffer.prototype, + input || input.buffer, + ) + ) { + // We clone the data into a non-shared ArrayBuffer so we can pass it + // to Rust. + // `input` is now a Uint8Array, and calling the TypedArray constructor + // with a TypedArray argument copies the data. + if (ArrayBufferIsView(input)) { + input = new Uint8Array( + input.buffer, + input.byteOffset, + input.byteLength, + ); + } else { + input = new Uint8Array(input); + } } - try { - // Note from spec: implementations are strongly encouraged to use an implementation strategy that avoids this copy. - // When doing so they will have to make sure that changes to input do not affect future calls to decode(). - if ( - ObjectPrototypeIsPrototypeOf( - // deno-lint-ignore prefer-primordials - SharedArrayBuffer.prototype, - input || input.buffer, - ) - ) { - // We clone the data into a non-shared ArrayBuffer so we can pass it - // to Rust. - // `input` is now a Uint8Array, and calling the TypedArray constructor - // with a TypedArray argument copies the data. - if (ArrayBufferIsView(input)) { - input = new Uint8Array( - input.buffer, - input.byteOffset, - input.byteLength, - ); - } else { - input = new Uint8Array(input); - } + // Fast path for single pass encoding. + if (!stream && this.#rid === null) { + // Fast path for utf8 single pass encoding. + if (this.#utf8SinglePass) { + return ops.op_encoding_decode_utf8(input, this.#ignoreBOM); } - // Fast path for single pass encoding. - if (!stream && this.#rid === null) { - // Fast path for utf8 single pass encoding. - if (this.#utf8SinglePass) { - return ops.op_encoding_decode_utf8(input, this.#ignoreBOM); - } - - return ops.op_encoding_decode_single( - input, - this.#encoding, - this.#fatal, - this.#ignoreBOM, - ); - } + return ops.op_encoding_decode_single( + input, + this.#encoding, + this.#fatal, + this.#ignoreBOM, + ); + } - if (this.#rid === null) { - this.#rid = ops.op_encoding_new_decoder( - this.#encoding, - this.#fatal, - this.#ignoreBOM, - ); - } - return ops.op_encoding_decode(input, this.#rid, stream); - } finally { - if (!stream && this.#rid !== null) { - core.close(this.#rid); - this.#rid = null; - } + if (this.#rid === null) { + this.#rid = ops.op_encoding_new_decoder( + this.#encoding, + this.#fatal, + this.#ignoreBOM, + ); + } + return ops.op_encoding_decode(input, this.#rid, stream); + } finally { + if (!stream && this.#rid !== null) { + core.close(this.#rid); + this.#rid = null; } } } +} - webidl.configurePrototype(TextDecoder); - const TextDecoderPrototype = TextDecoder.prototype; +webidl.configurePrototype(TextDecoder); +const TextDecoderPrototype = TextDecoder.prototype; - class TextEncoder { - constructor() { - this[webidl.brand] = webidl.brand; - } +class TextEncoder { + constructor() { + this[webidl.brand] = webidl.brand; + } - /** @returns {string} */ - get encoding() { - webidl.assertBranded(this, TextEncoderPrototype); - return "utf-8"; - } + /** @returns {string} */ + get encoding() { + webidl.assertBranded(this, TextEncoderPrototype); + return "utf-8"; + } - /** - * @param {string} input - * @returns {Uint8Array} - */ - encode(input = "") { - webidl.assertBranded(this, TextEncoderPrototype); - const prefix = "Failed to execute 'encode' on 'TextEncoder'"; - // The WebIDL type of `input` is `USVString`, but `core.encode` already - // converts lone surrogates to the replacement character. - input = webidl.converters.DOMString(input, { - prefix, - context: "Argument 1", - }); - return core.encode(input); - } + /** + * @param {string} input + * @returns {Uint8Array} + */ + encode(input = "") { + webidl.assertBranded(this, TextEncoderPrototype); + const prefix = "Failed to execute 'encode' on 'TextEncoder'"; + // The WebIDL type of `input` is `USVString`, but `core.encode` already + // converts lone surrogates to the replacement character. + input = webidl.converters.DOMString(input, { + prefix, + context: "Argument 1", + }); + return core.encode(input); + } - /** - * @param {string} source - * @param {Uint8Array} destination - * @returns {TextEncoderEncodeIntoResult} - */ - encodeInto(source, destination) { - webidl.assertBranded(this, TextEncoderPrototype); - const prefix = "Failed to execute 'encodeInto' on 'TextEncoder'"; - // The WebIDL type of `source` is `USVString`, but the ops bindings - // already convert lone surrogates to the replacement character. - source = webidl.converters.DOMString(source, { - prefix, - context: "Argument 1", - }); - destination = webidl.converters.Uint8Array(destination, { - prefix, - context: "Argument 2", - allowShared: true, - }); - ops.op_encoding_encode_into(source, destination, encodeIntoBuf); - return { - read: encodeIntoBuf[0], - written: encodeIntoBuf[1], - }; - } + /** + * @param {string} source + * @param {Uint8Array} destination + * @returns {TextEncoderEncodeIntoResult} + */ + encodeInto(source, destination) { + webidl.assertBranded(this, TextEncoderPrototype); + const prefix = "Failed to execute 'encodeInto' on 'TextEncoder'"; + // The WebIDL type of `source` is `USVString`, but the ops bindings + // already convert lone surrogates to the replacement character. + source = webidl.converters.DOMString(source, { + prefix, + context: "Argument 1", + }); + destination = webidl.converters.Uint8Array(destination, { + prefix, + context: "Argument 2", + allowShared: true, + }); + ops.op_encoding_encode_into(source, destination, encodeIntoBuf); + return { + read: encodeIntoBuf[0], + written: encodeIntoBuf[1], + }; } +} - const encodeIntoBuf = new Uint32Array(2); +const encodeIntoBuf = new Uint32Array(2); - webidl.configurePrototype(TextEncoder); - const TextEncoderPrototype = TextEncoder.prototype; +webidl.configurePrototype(TextEncoder); +const TextEncoderPrototype = TextEncoder.prototype; - class TextDecoderStream { - /** @type {TextDecoder} */ - #decoder; - /** @type {TransformStream} */ - #transform; +class TextDecoderStream { + /** @type {TextDecoder} */ + #decoder; + /** @type {TransformStream} */ + #transform; - /** - * @param {string} label - * @param {TextDecoderOptions} options - */ - constructor(label = "utf-8", options = {}) { - const prefix = "Failed to construct 'TextDecoderStream'"; - label = webidl.converters.DOMString(label, { - prefix, - context: "Argument 1", - }); - options = webidl.converters.TextDecoderOptions(options, { - prefix, - context: "Argument 2", - }); - this.#decoder = new TextDecoder(label, options); - this.#transform = new TransformStream({ - // The transform and flush functions need access to TextDecoderStream's - // `this`, so they are defined as functions rather than methods. - transform: (chunk, controller) => { - try { - chunk = webidl.converters.BufferSource(chunk, { - allowShared: true, - }); - const decoded = this.#decoder.decode(chunk, { stream: true }); - if (decoded) { - controller.enqueue(decoded); - } - return PromiseResolve(); - } catch (err) { - return PromiseReject(err); + /** + * @param {string} label + * @param {TextDecoderOptions} options + */ + constructor(label = "utf-8", options = {}) { + const prefix = "Failed to construct 'TextDecoderStream'"; + label = webidl.converters.DOMString(label, { + prefix, + context: "Argument 1", + }); + options = webidl.converters.TextDecoderOptions(options, { + prefix, + context: "Argument 2", + }); + this.#decoder = new TextDecoder(label, options); + this.#transform = new TransformStream({ + // The transform and flush functions need access to TextDecoderStream's + // `this`, so they are defined as functions rather than methods. + transform: (chunk, controller) => { + try { + chunk = webidl.converters.BufferSource(chunk, { + allowShared: true, + }); + const decoded = this.#decoder.decode(chunk, { stream: true }); + if (decoded) { + controller.enqueue(decoded); } - }, - flush: (controller) => { - try { - const final = this.#decoder.decode(); - if (final) { - controller.enqueue(final); - } - return PromiseResolve(); - } catch (err) { - return PromiseReject(err); + return PromiseResolve(); + } catch (err) { + return PromiseReject(err); + } + }, + flush: (controller) => { + try { + const final = this.#decoder.decode(); + if (final) { + controller.enqueue(final); } - }, - }); - this[webidl.brand] = webidl.brand; - } - - /** @returns {string} */ - get encoding() { - webidl.assertBranded(this, TextDecoderStreamPrototype); - return this.#decoder.encoding; - } + return PromiseResolve(); + } catch (err) { + return PromiseReject(err); + } + }, + }); + this[webidl.brand] = webidl.brand; + } - /** @returns {boolean} */ - get fatal() { - webidl.assertBranded(this, TextDecoderStreamPrototype); - return this.#decoder.fatal; - } + /** @returns {string} */ + get encoding() { + webidl.assertBranded(this, TextDecoderStreamPrototype); + return this.#decoder.encoding; + } - /** @returns {boolean} */ - get ignoreBOM() { - webidl.assertBranded(this, TextDecoderStreamPrototype); - return this.#decoder.ignoreBOM; - } + /** @returns {boolean} */ + get fatal() { + webidl.assertBranded(this, TextDecoderStreamPrototype); + return this.#decoder.fatal; + } - /** @returns {ReadableStream} */ - get readable() { - webidl.assertBranded(this, TextDecoderStreamPrototype); - return this.#transform.readable; - } + /** @returns {boolean} */ + get ignoreBOM() { + webidl.assertBranded(this, TextDecoderStreamPrototype); + return this.#decoder.ignoreBOM; + } - /** @returns {WritableStream} */ - get writable() { - webidl.assertBranded(this, TextDecoderStreamPrototype); - return this.#transform.writable; - } + /** @returns {ReadableStream} */ + get readable() { + webidl.assertBranded(this, TextDecoderStreamPrototype); + return this.#transform.readable; } - webidl.configurePrototype(TextDecoderStream); - const TextDecoderStreamPrototype = TextDecoderStream.prototype; - - class TextEncoderStream { - /** @type {string | null} */ - #pendingHighSurrogate = null; - /** @type {TransformStream} */ - #transform; - - constructor() { - this.#transform = new TransformStream({ - // The transform and flush functions need access to TextEncoderStream's - // `this`, so they are defined as functions rather than methods. - transform: (chunk, controller) => { - try { - chunk = webidl.converters.DOMString(chunk); - if (chunk === "") { - return PromiseResolve(); - } - if (this.#pendingHighSurrogate !== null) { - chunk = this.#pendingHighSurrogate + chunk; - } - const lastCodeUnit = StringPrototypeCharCodeAt( - chunk, - chunk.length - 1, - ); - if (0xD800 <= lastCodeUnit && lastCodeUnit <= 0xDBFF) { - this.#pendingHighSurrogate = StringPrototypeSlice(chunk, -1); - chunk = StringPrototypeSlice(chunk, 0, -1); - } else { - this.#pendingHighSurrogate = null; - } - if (chunk) { - controller.enqueue(core.encode(chunk)); - } + /** @returns {WritableStream} */ + get writable() { + webidl.assertBranded(this, TextDecoderStreamPrototype); + return this.#transform.writable; + } +} + +webidl.configurePrototype(TextDecoderStream); +const TextDecoderStreamPrototype = TextDecoderStream.prototype; + +class TextEncoderStream { + /** @type {string | null} */ + #pendingHighSurrogate = null; + /** @type {TransformStream} */ + #transform; + + constructor() { + this.#transform = new TransformStream({ + // The transform and flush functions need access to TextEncoderStream's + // `this`, so they are defined as functions rather than methods. + transform: (chunk, controller) => { + try { + chunk = webidl.converters.DOMString(chunk); + if (chunk === "") { return PromiseResolve(); - } catch (err) { - return PromiseReject(err); } - }, - flush: (controller) => { - try { - if (this.#pendingHighSurrogate !== null) { - controller.enqueue(new Uint8Array([0xEF, 0xBF, 0xBD])); - } - return PromiseResolve(); - } catch (err) { - return PromiseReject(err); + if (this.#pendingHighSurrogate !== null) { + chunk = this.#pendingHighSurrogate + chunk; } - }, - }); - this[webidl.brand] = webidl.brand; - } - - /** @returns {string} */ - get encoding() { - webidl.assertBranded(this, TextEncoderStreamPrototype); - return "utf-8"; - } - - /** @returns {ReadableStream} */ - get readable() { - webidl.assertBranded(this, TextEncoderStreamPrototype); - return this.#transform.readable; - } - - /** @returns {WritableStream} */ - get writable() { - webidl.assertBranded(this, TextEncoderStreamPrototype); - return this.#transform.writable; - } - } - - webidl.configurePrototype(TextEncoderStream); - const TextEncoderStreamPrototype = TextEncoderStream.prototype; - - webidl.converters.TextDecoderOptions = webidl.createDictionaryConverter( - "TextDecoderOptions", - [ - { - key: "fatal", - converter: webidl.converters.boolean, - defaultValue: false, - }, - { - key: "ignoreBOM", - converter: webidl.converters.boolean, - defaultValue: false, + const lastCodeUnit = StringPrototypeCharCodeAt( + chunk, + chunk.length - 1, + ); + if (0xD800 <= lastCodeUnit && lastCodeUnit <= 0xDBFF) { + this.#pendingHighSurrogate = StringPrototypeSlice(chunk, -1); + chunk = StringPrototypeSlice(chunk, 0, -1); + } else { + this.#pendingHighSurrogate = null; + } + if (chunk) { + controller.enqueue(core.encode(chunk)); + } + return PromiseResolve(); + } catch (err) { + return PromiseReject(err); + } }, - ], - ); - webidl.converters.TextDecodeOptions = webidl.createDictionaryConverter( - "TextDecodeOptions", - [ - { - key: "stream", - converter: webidl.converters.boolean, - defaultValue: false, + flush: (controller) => { + try { + if (this.#pendingHighSurrogate !== null) { + controller.enqueue(new Uint8Array([0xEF, 0xBF, 0xBD])); + } + return PromiseResolve(); + } catch (err) { + return PromiseReject(err); + } }, - ], - ); + }); + this[webidl.brand] = webidl.brand; + } - /** - * @param {Uint8Array} bytes - */ - function decode(bytes, encoding) { - const BOMEncoding = BOMSniff(bytes); - if (BOMEncoding !== null) { - encoding = BOMEncoding; - const start = BOMEncoding === "UTF-8" ? 3 : 2; - bytes = TypedArrayPrototypeSubarray(bytes, start); - } - return new TextDecoder(encoding).decode(bytes); + /** @returns {string} */ + get encoding() { + webidl.assertBranded(this, TextEncoderStreamPrototype); + return "utf-8"; } - /** - * @param {Uint8Array} bytes - */ - function BOMSniff(bytes) { - if (bytes[0] === 0xEF && bytes[1] === 0xBB && bytes[2] === 0xBF) { - return "UTF-8"; - } - if (bytes[0] === 0xFE && bytes[1] === 0xFF) return "UTF-16BE"; - if (bytes[0] === 0xFF && bytes[1] === 0xFE) return "UTF-16LE"; - return null; + /** @returns {ReadableStream} */ + get readable() { + webidl.assertBranded(this, TextEncoderStreamPrototype); + return this.#transform.readable; } - window.__bootstrap.encoding = { - TextEncoder, - TextDecoder, - TextEncoderStream, - TextDecoderStream, - decode, - }; -})(this); + /** @returns {WritableStream} */ + get writable() { + webidl.assertBranded(this, TextEncoderStreamPrototype); + return this.#transform.writable; + } +} + +webidl.configurePrototype(TextEncoderStream); +const TextEncoderStreamPrototype = TextEncoderStream.prototype; + +webidl.converters.TextDecoderOptions = webidl.createDictionaryConverter( + "TextDecoderOptions", + [ + { + key: "fatal", + converter: webidl.converters.boolean, + defaultValue: false, + }, + { + key: "ignoreBOM", + converter: webidl.converters.boolean, + defaultValue: false, + }, + ], +); +webidl.converters.TextDecodeOptions = webidl.createDictionaryConverter( + "TextDecodeOptions", + [ + { + key: "stream", + converter: webidl.converters.boolean, + defaultValue: false, + }, + ], +); + +/** + * @param {Uint8Array} bytes + */ +function decode(bytes, encoding) { + const BOMEncoding = BOMSniff(bytes); + if (BOMEncoding !== null) { + encoding = BOMEncoding; + const start = BOMEncoding === "UTF-8" ? 3 : 2; + bytes = TypedArrayPrototypeSubarray(bytes, start); + } + return new TextDecoder(encoding).decode(bytes); +} + +/** + * @param {Uint8Array} bytes + */ +function BOMSniff(bytes) { + if (bytes[0] === 0xEF && bytes[1] === 0xBB && bytes[2] === 0xBF) { + return "UTF-8"; + } + if (bytes[0] === 0xFE && bytes[1] === 0xFF) return "UTF-16BE"; + if (bytes[0] === 0xFF && bytes[1] === 0xFE) return "UTF-16LE"; + return null; +} + +export { + decode, + TextDecoder, + TextDecoderStream, + TextEncoder, + TextEncoderStream, +}; diff --git a/ext/web/09_file.js b/ext/web/09_file.js index ecdce3e6a439ae..b44537dd492d80 100644 --- a/ext/web/09_file.js +++ b/ext/web/09_file.js @@ -9,630 +9,628 @@ /// /// /// -"use strict"; - -((window) => { - const core = window.Deno.core; - const ops = core.ops; - const webidl = window.__bootstrap.webidl; - const { - ArrayBufferPrototype, - ArrayBufferPrototypeSlice, - ArrayBufferIsView, - ArrayPrototypePush, - AsyncGeneratorPrototypeNext, - Date, - DatePrototypeGetTime, - FinalizationRegistry, - MathMax, - MathMin, - ObjectPrototypeIsPrototypeOf, - RegExpPrototypeTest, - // TODO(lucacasonato): add SharedArrayBuffer to primordials - // SharedArrayBufferPrototype - StringPrototypeCharAt, - StringPrototypeToLowerCase, - StringPrototypeSlice, - Symbol, - SymbolFor, - TypedArrayPrototypeSet, - TypeError, - Uint8Array, - } = window.__bootstrap.primordials; - const consoleInternal = window.__bootstrap.console; - - // TODO(lucacasonato): this needs to not be hardcoded and instead depend on - // host os. - const isWindows = false; - /** - * @param {string} input - * @param {number} position - * @returns {{result: string, position: number}} - */ - function collectCodepointsNotCRLF(input, position) { - // See https://w3c.github.io/FileAPI/#convert-line-endings-to-native and - // https://infra.spec.whatwg.org/#collect-a-sequence-of-code-points - const start = position; - for ( - let c = StringPrototypeCharAt(input, position); - position < input.length && !(c === "\r" || c === "\n"); - c = StringPrototypeCharAt(input, ++position) +const core = globalThis.Deno.core; +const ops = core.ops; +import * as webidl from "internal:deno_webidl/00_webidl.js"; +const primordials = globalThis.__bootstrap.primordials; +const { + ArrayBufferPrototype, + ArrayBufferPrototypeSlice, + ArrayBufferIsView, + ArrayPrototypePush, + AsyncGeneratorPrototypeNext, + Date, + DatePrototypeGetTime, + FinalizationRegistry, + MathMax, + MathMin, + ObjectPrototypeIsPrototypeOf, + RegExpPrototypeTest, + // TODO(lucacasonato): add SharedArrayBuffer to primordials + // SharedArrayBufferPrototype + StringPrototypeCharAt, + StringPrototypeToLowerCase, + StringPrototypeSlice, + Symbol, + SymbolFor, + TypedArrayPrototypeSet, + TypeError, + Uint8Array, +} = primordials; +import { createFilteredInspectProxy } from "internal:deno_console/02_console.js"; + +// TODO(lucacasonato): this needs to not be hardcoded and instead depend on +// host os. +const isWindows = false; + +/** + * @param {string} input + * @param {number} position + * @returns {{result: string, position: number}} + */ +function collectCodepointsNotCRLF(input, position) { + // See https://w3c.github.io/FileAPI/#convert-line-endings-to-native and + // https://infra.spec.whatwg.org/#collect-a-sequence-of-code-points + const start = position; + for ( + let c = StringPrototypeCharAt(input, position); + position < input.length && !(c === "\r" || c === "\n"); + c = StringPrototypeCharAt(input, ++position) + ); + return { result: StringPrototypeSlice(input, start, position), position }; +} + +/** + * @param {string} s + * @returns {string} + */ +function convertLineEndingsToNative(s) { + const nativeLineEnding = isWindows ? "\r\n" : "\n"; + + let { result, position } = collectCodepointsNotCRLF(s, 0); + + while (position < s.length) { + const codePoint = StringPrototypeCharAt(s, position); + if (codePoint === "\r") { + result += nativeLineEnding; + position++; + if ( + position < s.length && StringPrototypeCharAt(s, position) === "\n" + ) { + position++; + } + } else if (codePoint === "\n") { + position++; + result += nativeLineEnding; + } + const { result: token, position: newPosition } = collectCodepointsNotCRLF( + s, + position, ); - return { result: StringPrototypeSlice(input, start, position), position }; + position = newPosition; + result += token; } - /** - * @param {string} s - * @returns {string} - */ - function convertLineEndingsToNative(s) { - const nativeLineEnding = isWindows ? "\r\n" : "\n"; - - let { result, position } = collectCodepointsNotCRLF(s, 0); + return result; +} - while (position < s.length) { - const codePoint = StringPrototypeCharAt(s, position); - if (codePoint === "\r") { - result += nativeLineEnding; - position++; - if ( - position < s.length && StringPrototypeCharAt(s, position) === "\n" - ) { - position++; - } - } else if (codePoint === "\n") { - position++; - result += nativeLineEnding; - } - const { result: token, position: newPosition } = collectCodepointsNotCRLF( - s, - position, +/** @param {(BlobReference | Blob)[]} parts */ +async function* toIterator(parts) { + for (let i = 0; i < parts.length; ++i) { + yield* parts[i].stream(); + } +} + +/** @typedef {BufferSource | Blob | string} BlobPart */ + +/** + * @param {BlobPart[]} parts + * @param {string} endings + * @returns {{ parts: (BlobReference|Blob)[], size: number }} + */ +function processBlobParts(parts, endings) { + /** @type {(BlobReference|Blob)[]} */ + const processedParts = []; + let size = 0; + for (let i = 0; i < parts.length; ++i) { + const element = parts[i]; + if (ObjectPrototypeIsPrototypeOf(ArrayBufferPrototype, element)) { + const chunk = new Uint8Array(ArrayBufferPrototypeSlice(element, 0)); + ArrayPrototypePush(processedParts, BlobReference.fromUint8Array(chunk)); + size += element.byteLength; + } else if (ArrayBufferIsView(element)) { + const chunk = new Uint8Array( + element.buffer, + element.byteOffset, + element.byteLength, ); - position = newPosition; - result += token; + size += element.byteLength; + ArrayPrototypePush(processedParts, BlobReference.fromUint8Array(chunk)); + } else if (ObjectPrototypeIsPrototypeOf(BlobPrototype, element)) { + ArrayPrototypePush(processedParts, element); + size += element.size; + } else if (typeof element === "string") { + const chunk = core.encode( + endings == "native" ? convertLineEndingsToNative(element) : element, + ); + size += chunk.byteLength; + ArrayPrototypePush(processedParts, BlobReference.fromUint8Array(chunk)); + } else { + throw new TypeError("Unreachable code (invalid element type)"); } - - return result; } - - /** @param {(BlobReference | Blob)[]} parts */ - async function* toIterator(parts) { - for (let i = 0; i < parts.length; ++i) { - yield* parts[i].stream(); + return { parts: processedParts, size }; +} + +/** + * @param {string} str + * @returns {string} + */ +function normalizeType(str) { + let normalizedType = str; + if (!RegExpPrototypeTest(/^[\x20-\x7E]*$/, str)) { + normalizedType = ""; + } + return StringPrototypeToLowerCase(normalizedType); +} + +/** + * Get all Parts as a flat array containing all references + * @param {Blob} blob + * @param {string[]} bag + * @returns {string[]} + */ +function getParts(blob, bag = []) { + const parts = blob[_parts]; + for (let i = 0; i < parts.length; ++i) { + const part = parts[i]; + if (ObjectPrototypeIsPrototypeOf(BlobPrototype, part)) { + getParts(part, bag); + } else { + ArrayPrototypePush(bag, part._id); } } + return bag; +} - /** @typedef {BufferSource | Blob | string} BlobPart */ +const _type = Symbol("Type"); +const _size = Symbol("Size"); +const _parts = Symbol("Parts"); + +class Blob { + [_type] = ""; + [_size] = 0; + [_parts]; /** - * @param {BlobPart[]} parts - * @param {string} endings - * @returns {{ parts: (BlobReference|Blob)[], size: number }} + * @param {BlobPart[]} blobParts + * @param {BlobPropertyBag} options */ - function processBlobParts(parts, endings) { - /** @type {(BlobReference|Blob)[]} */ - const processedParts = []; - let size = 0; - for (let i = 0; i < parts.length; ++i) { - const element = parts[i]; - if (ObjectPrototypeIsPrototypeOf(ArrayBufferPrototype, element)) { - const chunk = new Uint8Array(ArrayBufferPrototypeSlice(element, 0)); - ArrayPrototypePush(processedParts, BlobReference.fromUint8Array(chunk)); - size += element.byteLength; - } else if (ArrayBufferIsView(element)) { - const chunk = new Uint8Array( - element.buffer, - element.byteOffset, - element.byteLength, - ); - size += element.byteLength; - ArrayPrototypePush(processedParts, BlobReference.fromUint8Array(chunk)); - } else if (ObjectPrototypeIsPrototypeOf(BlobPrototype, element)) { - ArrayPrototypePush(processedParts, element); - size += element.size; - } else if (typeof element === "string") { - const chunk = core.encode( - endings == "native" ? convertLineEndingsToNative(element) : element, - ); - size += chunk.byteLength; - ArrayPrototypePush(processedParts, BlobReference.fromUint8Array(chunk)); - } else { - throw new TypeError("Unreachable code (invalid element type)"); - } - } - return { parts: processedParts, size }; + constructor(blobParts = [], options = {}) { + const prefix = "Failed to construct 'Blob'"; + blobParts = webidl.converters["sequence"](blobParts, { + context: "Argument 1", + prefix, + }); + options = webidl.converters["BlobPropertyBag"](options, { + context: "Argument 2", + prefix, + }); + + this[webidl.brand] = webidl.brand; + + const { parts, size } = processBlobParts( + blobParts, + options.endings, + ); + + this[_parts] = parts; + this[_size] = size; + this[_type] = normalizeType(options.type); } - /** - * @param {string} str - * @returns {string} - */ - function normalizeType(str) { - let normalizedType = str; - if (!RegExpPrototypeTest(/^[\x20-\x7E]*$/, str)) { - normalizedType = ""; - } - return StringPrototypeToLowerCase(normalizedType); + /** @returns {number} */ + get size() { + webidl.assertBranded(this, BlobPrototype); + return this[_size]; } - /** - * Get all Parts as a flat array containing all references - * @param {Blob} blob - * @param {string[]} bag - * @returns {string[]} - */ - function getParts(blob, bag = []) { - const parts = blob[_parts]; - for (let i = 0; i < parts.length; ++i) { - const part = parts[i]; - if (ObjectPrototypeIsPrototypeOf(BlobPrototype, part)) { - getParts(part, bag); - } else { - ArrayPrototypePush(bag, part._id); - } - } - return bag; + /** @returns {string} */ + get type() { + webidl.assertBranded(this, BlobPrototype); + return this[_type]; } - const _type = Symbol("Type"); - const _size = Symbol("Size"); - const _parts = Symbol("Parts"); - - class Blob { - [_type] = ""; - [_size] = 0; - [_parts]; - - /** - * @param {BlobPart[]} blobParts - * @param {BlobPropertyBag} options - */ - constructor(blobParts = [], options = {}) { - const prefix = "Failed to construct 'Blob'"; - blobParts = webidl.converters["sequence"](blobParts, { + /** + * @param {number} [start] + * @param {number} [end] + * @param {string} [contentType] + * @returns {Blob} + */ + slice(start = undefined, end = undefined, contentType = undefined) { + webidl.assertBranded(this, BlobPrototype); + const prefix = "Failed to execute 'slice' on 'Blob'"; + if (start !== undefined) { + start = webidl.converters["long long"](start, { + clamp: true, context: "Argument 1", prefix, }); - options = webidl.converters["BlobPropertyBag"](options, { + } + if (end !== undefined) { + end = webidl.converters["long long"](end, { + clamp: true, context: "Argument 2", prefix, }); - - this[webidl.brand] = webidl.brand; - - const { parts, size } = processBlobParts( - blobParts, - options.endings, - ); - - this[_parts] = parts; - this[_size] = size; - this[_type] = normalizeType(options.type); } - - /** @returns {number} */ - get size() { - webidl.assertBranded(this, BlobPrototype); - return this[_size]; - } - - /** @returns {string} */ - get type() { - webidl.assertBranded(this, BlobPrototype); - return this[_type]; + if (contentType !== undefined) { + contentType = webidl.converters["DOMString"](contentType, { + context: "Argument 3", + prefix, + }); } - /** - * @param {number} [start] - * @param {number} [end] - * @param {string} [contentType] - * @returns {Blob} - */ - slice(start = undefined, end = undefined, contentType = undefined) { - webidl.assertBranded(this, BlobPrototype); - const prefix = "Failed to execute 'slice' on 'Blob'"; - if (start !== undefined) { - start = webidl.converters["long long"](start, { - clamp: true, - context: "Argument 1", - prefix, - }); - } - if (end !== undefined) { - end = webidl.converters["long long"](end, { - clamp: true, - context: "Argument 2", - prefix, - }); - } - if (contentType !== undefined) { - contentType = webidl.converters["DOMString"](contentType, { - context: "Argument 3", - prefix, - }); - } - - // deno-lint-ignore no-this-alias - const O = this; - /** @type {number} */ - let relativeStart; - if (start === undefined) { - relativeStart = 0; + // deno-lint-ignore no-this-alias + const O = this; + /** @type {number} */ + let relativeStart; + if (start === undefined) { + relativeStart = 0; + } else { + if (start < 0) { + relativeStart = MathMax(O.size + start, 0); } else { - if (start < 0) { - relativeStart = MathMax(O.size + start, 0); - } else { - relativeStart = MathMin(start, O.size); - } + relativeStart = MathMin(start, O.size); } - /** @type {number} */ - let relativeEnd; - if (end === undefined) { - relativeEnd = O.size; + } + /** @type {number} */ + let relativeEnd; + if (end === undefined) { + relativeEnd = O.size; + } else { + if (end < 0) { + relativeEnd = MathMax(O.size + end, 0); } else { - if (end < 0) { - relativeEnd = MathMax(O.size + end, 0); - } else { - relativeEnd = MathMin(end, O.size); - } + relativeEnd = MathMin(end, O.size); } + } - const span = MathMax(relativeEnd - relativeStart, 0); - const blobParts = []; - let added = 0; - - const parts = this[_parts]; - for (let i = 0; i < parts.length; ++i) { - const part = parts[i]; - // don't add the overflow to new blobParts - if (added >= span) { - // Could maybe be possible to remove variable `added` - // and only use relativeEnd? - break; - } - const size = part.size; - if (relativeStart && size <= relativeStart) { - // Skip the beginning and change the relative - // start & end position as we skip the unwanted parts - relativeStart -= size; - relativeEnd -= size; - } else { - const chunk = part.slice( - relativeStart, - MathMin(part.size, relativeEnd), - ); - added += chunk.size; - relativeEnd -= part.size; - ArrayPrototypePush(blobParts, chunk); - relativeStart = 0; // All next sequential parts should start at 0 - } - } + const span = MathMax(relativeEnd - relativeStart, 0); + const blobParts = []; + let added = 0; - /** @type {string} */ - let relativeContentType; - if (contentType === undefined) { - relativeContentType = ""; + const parts = this[_parts]; + for (let i = 0; i < parts.length; ++i) { + const part = parts[i]; + // don't add the overflow to new blobParts + if (added >= span) { + // Could maybe be possible to remove variable `added` + // and only use relativeEnd? + break; + } + const size = part.size; + if (relativeStart && size <= relativeStart) { + // Skip the beginning and change the relative + // start & end position as we skip the unwanted parts + relativeStart -= size; + relativeEnd -= size; } else { - relativeContentType = normalizeType(contentType); + const chunk = part.slice( + relativeStart, + MathMin(part.size, relativeEnd), + ); + added += chunk.size; + relativeEnd -= part.size; + ArrayPrototypePush(blobParts, chunk); + relativeStart = 0; // All next sequential parts should start at 0 } - - const blob = new Blob([], { type: relativeContentType }); - blob[_parts] = blobParts; - blob[_size] = span; - return blob; } - /** - * @returns {ReadableStream} - */ - stream() { - webidl.assertBranded(this, BlobPrototype); - const partIterator = toIterator(this[_parts]); - const stream = new ReadableStream({ - type: "bytes", - /** @param {ReadableByteStreamController} controller */ - async pull(controller) { - while (true) { - const { value, done } = await AsyncGeneratorPrototypeNext( - partIterator, - ); - if (done) return controller.close(); - if (value.byteLength > 0) { - return controller.enqueue(value); - } - } - }, - }); - return stream; + /** @type {string} */ + let relativeContentType; + if (contentType === undefined) { + relativeContentType = ""; + } else { + relativeContentType = normalizeType(contentType); } - /** - * @returns {Promise} - */ - async text() { - webidl.assertBranded(this, BlobPrototype); - const buffer = await this.#u8Array(this.size); - return core.decode(buffer); - } + const blob = new Blob([], { type: relativeContentType }); + blob[_parts] = blobParts; + blob[_size] = span; + return blob; + } - async #u8Array(size) { - const bytes = new Uint8Array(size); - const partIterator = toIterator(this[_parts]); - let offset = 0; - while (true) { - const { value, done } = await AsyncGeneratorPrototypeNext( - partIterator, - ); - if (done) break; - const byteLength = value.byteLength; - if (byteLength > 0) { - TypedArrayPrototypeSet(bytes, value, offset); - offset += byteLength; + /** + * @returns {ReadableStream} + */ + stream() { + webidl.assertBranded(this, BlobPrototype); + const partIterator = toIterator(this[_parts]); + const stream = new ReadableStream({ + type: "bytes", + /** @param {ReadableByteStreamController} controller */ + async pull(controller) { + while (true) { + const { value, done } = await AsyncGeneratorPrototypeNext( + partIterator, + ); + if (done) return controller.close(); + if (value.byteLength > 0) { + return controller.enqueue(value); + } } - } - return bytes; - } - - /** - * @returns {Promise} - */ - async arrayBuffer() { - webidl.assertBranded(this, BlobPrototype); - const buf = await this.#u8Array(this.size); - return buf.buffer; - } - - [SymbolFor("Deno.customInspect")](inspect) { - return inspect(consoleInternal.createFilteredInspectProxy({ - object: this, - evaluate: ObjectPrototypeIsPrototypeOf(BlobPrototype, this), - keys: [ - "size", - "type", - ], - })); - } + }, + }); + return stream; } - webidl.configurePrototype(Blob); - const BlobPrototype = Blob.prototype; + /** + * @returns {Promise} + */ + async text() { + webidl.assertBranded(this, BlobPrototype); + const buffer = await this.#u8Array(this.size); + return core.decode(buffer); + } - webidl.converters["Blob"] = webidl.createInterfaceConverter( - "Blob", - Blob.prototype, - ); - webidl.converters["BlobPart"] = (V, opts) => { - // Union for ((ArrayBuffer or ArrayBufferView) or Blob or USVString) - if (typeof V == "object") { - if (ObjectPrototypeIsPrototypeOf(BlobPrototype, V)) { - return webidl.converters["Blob"](V, opts); - } - if ( - ObjectPrototypeIsPrototypeOf(ArrayBufferPrototype, V) || - // deno-lint-ignore prefer-primordials - ObjectPrototypeIsPrototypeOf(SharedArrayBuffer.prototype, V) - ) { - return webidl.converters["ArrayBuffer"](V, opts); - } - if (ArrayBufferIsView(V)) { - return webidl.converters["ArrayBufferView"](V, opts); + async #u8Array(size) { + const bytes = new Uint8Array(size); + const partIterator = toIterator(this[_parts]); + let offset = 0; + while (true) { + const { value, done } = await AsyncGeneratorPrototypeNext( + partIterator, + ); + if (done) break; + const byteLength = value.byteLength; + if (byteLength > 0) { + TypedArrayPrototypeSet(bytes, value, offset); + offset += byteLength; } } - // BlobPart is passed to processBlobParts after conversion, which calls core.encode() - // on the string. - // core.encode() is equivalent to USVString normalization. - return webidl.converters["DOMString"](V, opts); - }; - webidl.converters["sequence"] = webidl.createSequenceConverter( - webidl.converters["BlobPart"], - ); - webidl.converters["EndingType"] = webidl.createEnumConverter("EndingType", [ - "transparent", - "native", - ]); - const blobPropertyBagDictionary = [ - { - key: "type", - converter: webidl.converters["DOMString"], - defaultValue: "", - }, - { - key: "endings", - converter: webidl.converters["EndingType"], - defaultValue: "transparent", - }, - ]; - webidl.converters["BlobPropertyBag"] = webidl.createDictionaryConverter( - "BlobPropertyBag", - blobPropertyBagDictionary, - ); - - const _Name = Symbol("[[Name]]"); - const _LastModified = Symbol("[[LastModified]]"); - - class File extends Blob { - /** @type {string} */ - [_Name]; - /** @type {number} */ - [_LastModified]; - - /** - * @param {BlobPart[]} fileBits - * @param {string} fileName - * @param {FilePropertyBag} options - */ - constructor(fileBits, fileName, options = {}) { - const prefix = "Failed to construct 'File'"; - webidl.requiredArguments(arguments.length, 2, { prefix }); - - fileBits = webidl.converters["sequence"](fileBits, { - context: "Argument 1", - prefix, - }); - fileName = webidl.converters["USVString"](fileName, { - context: "Argument 2", - prefix, - }); - options = webidl.converters["FilePropertyBag"](options, { - context: "Argument 3", - prefix, - }); + return bytes; + } - super(fileBits, options); + /** + * @returns {Promise} + */ + async arrayBuffer() { + webidl.assertBranded(this, BlobPrototype); + const buf = await this.#u8Array(this.size); + return buf.buffer; + } - /** @type {string} */ - this[_Name] = fileName; - if (options.lastModified === undefined) { - /** @type {number} */ - this[_LastModified] = DatePrototypeGetTime(new Date()); - } else { - /** @type {number} */ - this[_LastModified] = options.lastModified; - } + [SymbolFor("Deno.customInspect")](inspect) { + return inspect(createFilteredInspectProxy({ + object: this, + evaluate: ObjectPrototypeIsPrototypeOf(BlobPrototype, this), + keys: [ + "size", + "type", + ], + })); + } +} + +webidl.configurePrototype(Blob); +const BlobPrototype = Blob.prototype; + +webidl.converters["Blob"] = webidl.createInterfaceConverter( + "Blob", + Blob.prototype, +); +webidl.converters["BlobPart"] = (V, opts) => { + // Union for ((ArrayBuffer or ArrayBufferView) or Blob or USVString) + if (typeof V == "object") { + if (ObjectPrototypeIsPrototypeOf(BlobPrototype, V)) { + return webidl.converters["Blob"](V, opts); } - - /** @returns {string} */ - get name() { - webidl.assertBranded(this, FilePrototype); - return this[_Name]; + if ( + ObjectPrototypeIsPrototypeOf(ArrayBufferPrototype, V) || + // deno-lint-ignore prefer-primordials + ObjectPrototypeIsPrototypeOf(SharedArrayBuffer.prototype, V) + ) { + return webidl.converters["ArrayBuffer"](V, opts); } - - /** @returns {number} */ - get lastModified() { - webidl.assertBranded(this, FilePrototype); - return this[_LastModified]; + if (ArrayBufferIsView(V)) { + return webidl.converters["ArrayBufferView"](V, opts); } } - - webidl.configurePrototype(File); - const FilePrototype = File.prototype; - - webidl.converters["FilePropertyBag"] = webidl.createDictionaryConverter( - "FilePropertyBag", - blobPropertyBagDictionary, - [ - { - key: "lastModified", - converter: webidl.converters["long long"], - }, - ], - ); - - // A finalization registry to deallocate a blob part when its JS reference is - // garbage collected. - const registry = new FinalizationRegistry((uuid) => { - ops.op_blob_remove_part(uuid); - }); - - // TODO(lucacasonato): get a better stream from Rust in BlobReference#stream + // BlobPart is passed to processBlobParts after conversion, which calls core.encode() + // on the string. + // core.encode() is equivalent to USVString normalization. + return webidl.converters["DOMString"](V, opts); +}; +webidl.converters["sequence"] = webidl.createSequenceConverter( + webidl.converters["BlobPart"], +); +webidl.converters["EndingType"] = webidl.createEnumConverter("EndingType", [ + "transparent", + "native", +]); +const blobPropertyBagDictionary = [ + { + key: "type", + converter: webidl.converters["DOMString"], + defaultValue: "", + }, + { + key: "endings", + converter: webidl.converters["EndingType"], + defaultValue: "transparent", + }, +]; +webidl.converters["BlobPropertyBag"] = webidl.createDictionaryConverter( + "BlobPropertyBag", + blobPropertyBagDictionary, +); + +const _Name = Symbol("[[Name]]"); +const _LastModified = Symbol("[[LastModified]]"); + +class File extends Blob { + /** @type {string} */ + [_Name]; + /** @type {number} */ + [_LastModified]; /** - * An opaque reference to a blob part in Rust. This could be backed by a file, - * in memory storage, or something else. + * @param {BlobPart[]} fileBits + * @param {string} fileName + * @param {FilePropertyBag} options */ - class BlobReference { - /** - * Don't use directly. Use `BlobReference.fromUint8Array`. - * @param {string} id - * @param {number} size - */ - constructor(id, size) { - this._id = id; - this.size = size; - registry.register(this, id); - } + constructor(fileBits, fileName, options = {}) { + const prefix = "Failed to construct 'File'"; + webidl.requiredArguments(arguments.length, 2, { prefix }); + + fileBits = webidl.converters["sequence"](fileBits, { + context: "Argument 1", + prefix, + }); + fileName = webidl.converters["USVString"](fileName, { + context: "Argument 2", + prefix, + }); + options = webidl.converters["FilePropertyBag"](options, { + context: "Argument 3", + prefix, + }); + + super(fileBits, options); - /** - * Create a new blob part from a Uint8Array. - * - * @param {Uint8Array} data - * @returns {BlobReference} - */ - static fromUint8Array(data) { - const id = ops.op_blob_create_part(data); - return new BlobReference(id, data.byteLength); + /** @type {string} */ + this[_Name] = fileName; + if (options.lastModified === undefined) { + /** @type {number} */ + this[_LastModified] = DatePrototypeGetTime(new Date()); + } else { + /** @type {number} */ + this[_LastModified] = options.lastModified; } + } - /** - * Create a new BlobReference by slicing this BlobReference. This is a copy - * free operation - the sliced reference will still reference the original - * underlying bytes. - * - * @param {number} start - * @param {number} end - * @returns {BlobReference} - */ - slice(start, end) { - const size = end - start; - const id = ops.op_blob_slice_part(this._id, { - start, - len: size, - }); - return new BlobReference(id, size); - } + /** @returns {string} */ + get name() { + webidl.assertBranded(this, FilePrototype); + return this[_Name]; + } - /** - * Read the entire contents of the reference blob. - * @returns {AsyncGenerator} - */ - async *stream() { - yield core.opAsync("op_blob_read_part", this._id); - - // let position = 0; - // const end = this.size; - // while (position !== end) { - // const size = MathMin(end - position, 65536); - // const chunk = this.slice(position, position + size); - // position += chunk.size; - // yield core.opAsync("op_blob_read_part", chunk._id); - // } - } + /** @returns {number} */ + get lastModified() { + webidl.assertBranded(this, FilePrototype); + return this[_LastModified]; } +} +webidl.configurePrototype(File); +const FilePrototype = File.prototype; + +webidl.converters["FilePropertyBag"] = webidl.createDictionaryConverter( + "FilePropertyBag", + blobPropertyBagDictionary, + [ + { + key: "lastModified", + converter: webidl.converters["long long"], + }, + ], +); + +// A finalization registry to deallocate a blob part when its JS reference is +// garbage collected. +const registry = new FinalizationRegistry((uuid) => { + ops.op_blob_remove_part(uuid); +}); + +// TODO(lucacasonato): get a better stream from Rust in BlobReference#stream + +/** + * An opaque reference to a blob part in Rust. This could be backed by a file, + * in memory storage, or something else. + */ +class BlobReference { /** - * Construct a new Blob object from an object URL. - * - * This new object will not duplicate data in memory with the original Blob - * object from which this URL was created or with other Blob objects created - * from the same URL, but they will be different objects. + * Don't use directly. Use `BlobReference.fromUint8Array`. + * @param {string} id + * @param {number} size + */ + constructor(id, size) { + this._id = id; + this.size = size; + registry.register(this, id); + } + + /** + * Create a new blob part from a Uint8Array. * - * The object returned from this function will not be a File object, even if - * the original object from which the object URL was constructed was one. This - * means that the `name` and `lastModified` properties are lost. + * @param {Uint8Array} data + * @returns {BlobReference} + */ + static fromUint8Array(data) { + const id = ops.op_blob_create_part(data); + return new BlobReference(id, data.byteLength); + } + + /** + * Create a new BlobReference by slicing this BlobReference. This is a copy + * free operation - the sliced reference will still reference the original + * underlying bytes. * - * @param {string} url - * @returns {Blob | null} + * @param {number} start + * @param {number} end + * @returns {BlobReference} */ - function blobFromObjectUrl(url) { - const blobData = ops.op_blob_from_object_url(url); - if (blobData === null) { - return null; - } + slice(start, end) { + const size = end - start; + const id = ops.op_blob_slice_part(this._id, { + start, + len: size, + }); + return new BlobReference(id, size); + } - /** @type {BlobReference[]} */ - const parts = []; - let totalSize = 0; + /** + * Read the entire contents of the reference blob. + * @returns {AsyncGenerator} + */ + async *stream() { + yield core.opAsync("op_blob_read_part", this._id); + + // let position = 0; + // const end = this.size; + // while (position !== end) { + // const size = MathMin(end - position, 65536); + // const chunk = this.slice(position, position + size); + // position += chunk.size; + // yield core.opAsync("op_blob_read_part", chunk._id); + // } + } +} + +/** + * Construct a new Blob object from an object URL. + * + * This new object will not duplicate data in memory with the original Blob + * object from which this URL was created or with other Blob objects created + * from the same URL, but they will be different objects. + * + * The object returned from this function will not be a File object, even if + * the original object from which the object URL was constructed was one. This + * means that the `name` and `lastModified` properties are lost. + * + * @param {string} url + * @returns {Blob | null} + */ +function blobFromObjectUrl(url) { + const blobData = ops.op_blob_from_object_url(url); + if (blobData === null) { + return null; + } - for (let i = 0; i < blobData.parts.length; ++i) { - const { uuid, size } = blobData.parts[i]; - ArrayPrototypePush(parts, new BlobReference(uuid, size)); - totalSize += size; - } + /** @type {BlobReference[]} */ + const parts = []; + let totalSize = 0; - const blob = webidl.createBranded(Blob); - blob[_type] = blobData.media_type; - blob[_size] = totalSize; - blob[_parts] = parts; - return blob; + for (let i = 0; i < blobData.parts.length; ++i) { + const { uuid, size } = blobData.parts[i]; + ArrayPrototypePush(parts, new BlobReference(uuid, size)); + totalSize += size; } - window.__bootstrap.file = { - blobFromObjectUrl, - getParts, - Blob, - BlobPrototype, - File, - FilePrototype, - }; -})(this); + const blob = webidl.createBranded(Blob); + blob[_type] = blobData.media_type; + blob[_size] = totalSize; + blob[_parts] = parts; + return blob; +} + +export { + Blob, + blobFromObjectUrl, + BlobPrototype, + File, + FilePrototype, + getParts, +}; diff --git a/ext/web/10_filereader.js b/ext/web/10_filereader.js index fb119f43ec858f..8ff36494b95a41 100644 --- a/ext/web/10_filereader.js +++ b/ext/web/10_filereader.js @@ -10,487 +10,482 @@ /// /// -"use strict"; - -((window) => { - const core = window.Deno.core; - const webidl = window.__bootstrap.webidl; - const { forgivingBase64Encode } = window.__bootstrap.infra; - const { ProgressEvent } = window.__bootstrap.event; - const { EventTarget } = window.__bootstrap.eventTarget; - const { decode, TextDecoder } = window.__bootstrap.encoding; - const { parseMimeType } = window.__bootstrap.mimesniff; - const { DOMException } = window.__bootstrap.domException; - const { - ArrayPrototypePush, - ArrayPrototypeReduce, - FunctionPrototypeCall, - Map, - MapPrototypeGet, - MapPrototypeSet, - ObjectDefineProperty, - ObjectPrototypeIsPrototypeOf, - queueMicrotask, - SafeArrayIterator, - Symbol, - TypedArrayPrototypeSet, - TypeError, - Uint8Array, - Uint8ArrayPrototype, - } = window.__bootstrap.primordials; - - const state = Symbol("[[state]]"); - const result = Symbol("[[result]]"); - const error = Symbol("[[error]]"); - const aborted = Symbol("[[aborted]]"); - const handlerSymbol = Symbol("eventHandlers"); - - class FileReader extends EventTarget { - /** @type {"empty" | "loading" | "done"} */ - [state] = "empty"; - /** @type {null | string | ArrayBuffer} */ - [result] = null; - /** @type {null | DOMException} */ - [error] = null; - /** @type {null | {aborted: boolean}} */ - [aborted] = null; - - /** - * @param {Blob} blob - * @param {{kind: "ArrayBuffer" | "Text" | "DataUrl" | "BinaryString", encoding?: string}} readtype - */ - #readOperation(blob, readtype) { - // 1. If fr’s state is "loading", throw an InvalidStateError DOMException. - if (this[state] === "loading") { - throw new DOMException( - "Invalid FileReader state.", - "InvalidStateError", - ); - } - // 2. Set fr’s state to "loading". - this[state] = "loading"; - // 3. Set fr’s result to null. - this[result] = null; - // 4. Set fr’s error to null. - this[error] = null; - - // We set this[aborted] to a new object, and keep track of it in a - // separate variable, so if a new read operation starts while there are - // remaining tasks from a previous aborted operation, the new operation - // will run while the tasks from the previous one are still aborted. - const abortedState = this[aborted] = { aborted: false }; - - // 5. Let stream be the result of calling get stream on blob. - const stream /*: ReadableStream*/ = blob.stream(); - - // 6. Let reader be the result of getting a reader from stream. - const reader = stream.getReader(); - - // 7. Let bytes be an empty byte sequence. - /** @type {Uint8Array[]} */ - const chunks = []; - - // 8. Let chunkPromise be the result of reading a chunk from stream with reader. - let chunkPromise = reader.read(); - - // 9. Let isFirstChunk be true. - let isFirstChunk = true; - - // 10 in parallel while true - (async () => { - while (!abortedState.aborted) { - // 1. Wait for chunkPromise to be fulfilled or rejected. - try { - const chunk = await chunkPromise; - if (abortedState.aborted) return; - - // 2. If chunkPromise is fulfilled, and isFirstChunk is true, queue a task to fire a progress event called loadstart at fr. - if (isFirstChunk) { +const core = globalThis.Deno.core; +const ops = core.ops; +import * as webidl from "internal:deno_webidl/00_webidl.js"; +const primordials = globalThis.__bootstrap.primordials; +import { forgivingBase64Encode } from "internal:deno_web/00_infra.js"; +import { EventTarget, ProgressEvent } from "internal:deno_web/02_event.js"; +import { decode, TextDecoder } from "internal:deno_web/08_text_encoding.js"; +import { parseMimeType } from "internal:deno_web/01_mimesniff.js"; +import DOMException from "internal:deno_web/01_dom_exception.js"; +const { + ArrayPrototypePush, + ArrayPrototypeReduce, + FunctionPrototypeCall, + Map, + MapPrototypeGet, + MapPrototypeSet, + ObjectDefineProperty, + ObjectPrototypeIsPrototypeOf, + queueMicrotask, + SafeArrayIterator, + Symbol, + TypedArrayPrototypeSet, + TypeError, + Uint8Array, + Uint8ArrayPrototype, +} = primordials; + +const state = Symbol("[[state]]"); +const result = Symbol("[[result]]"); +const error = Symbol("[[error]]"); +const aborted = Symbol("[[aborted]]"); +const handlerSymbol = Symbol("eventHandlers"); + +class FileReader extends EventTarget { + /** @type {"empty" | "loading" | "done"} */ + [state] = "empty"; + /** @type {null | string | ArrayBuffer} */ + [result] = null; + /** @type {null | DOMException} */ + [error] = null; + /** @type {null | {aborted: boolean}} */ + [aborted] = null; + + /** + * @param {Blob} blob + * @param {{kind: "ArrayBuffer" | "Text" | "DataUrl" | "BinaryString", encoding?: string}} readtype + */ + #readOperation(blob, readtype) { + // 1. If fr’s state is "loading", throw an InvalidStateError DOMException. + if (this[state] === "loading") { + throw new DOMException( + "Invalid FileReader state.", + "InvalidStateError", + ); + } + // 2. Set fr’s state to "loading". + this[state] = "loading"; + // 3. Set fr’s result to null. + this[result] = null; + // 4. Set fr’s error to null. + this[error] = null; + + // We set this[aborted] to a new object, and keep track of it in a + // separate variable, so if a new read operation starts while there are + // remaining tasks from a previous aborted operation, the new operation + // will run while the tasks from the previous one are still aborted. + const abortedState = this[aborted] = { aborted: false }; + + // 5. Let stream be the result of calling get stream on blob. + const stream /*: ReadableStream*/ = blob.stream(); + + // 6. Let reader be the result of getting a reader from stream. + const reader = stream.getReader(); + + // 7. Let bytes be an empty byte sequence. + /** @type {Uint8Array[]} */ + const chunks = []; + + // 8. Let chunkPromise be the result of reading a chunk from stream with reader. + let chunkPromise = reader.read(); + + // 9. Let isFirstChunk be true. + let isFirstChunk = true; + + // 10 in parallel while true + (async () => { + while (!abortedState.aborted) { + // 1. Wait for chunkPromise to be fulfilled or rejected. + try { + const chunk = await chunkPromise; + if (abortedState.aborted) return; + + // 2. If chunkPromise is fulfilled, and isFirstChunk is true, queue a task to fire a progress event called loadstart at fr. + if (isFirstChunk) { + // TODO(lucacasonato): this is wrong, should be HTML "queue a task" + queueMicrotask(() => { + if (abortedState.aborted) return; + // fire a progress event for loadstart + const ev = new ProgressEvent("loadstart", {}); + this.dispatchEvent(ev); + }); + } + // 3. Set isFirstChunk to false. + isFirstChunk = false; + + // 4. If chunkPromise is fulfilled with an object whose done property is false + // and whose value property is a Uint8Array object, run these steps: + if ( + !chunk.done && + ObjectPrototypeIsPrototypeOf(Uint8ArrayPrototype, chunk.value) + ) { + ArrayPrototypePush(chunks, chunk.value); + + // TODO(bartlomieju): (only) If roughly 50ms have passed since last progress + { + const size = ArrayPrototypeReduce( + chunks, + (p, i) => p + i.byteLength, + 0, + ); + const ev = new ProgressEvent("progress", { + loaded: size, + }); // TODO(lucacasonato): this is wrong, should be HTML "queue a task" queueMicrotask(() => { if (abortedState.aborted) return; - // fire a progress event for loadstart - const ev = new ProgressEvent("loadstart", {}); this.dispatchEvent(ev); }); } - // 3. Set isFirstChunk to false. - isFirstChunk = false; - - // 4. If chunkPromise is fulfilled with an object whose done property is false - // and whose value property is a Uint8Array object, run these steps: - if ( - !chunk.done && - ObjectPrototypeIsPrototypeOf(Uint8ArrayPrototype, chunk.value) - ) { - ArrayPrototypePush(chunks, chunk.value); - - // TODO(bartlomieju): (only) If roughly 50ms have passed since last progress - { - const size = ArrayPrototypeReduce( - chunks, - (p, i) => p + i.byteLength, - 0, - ); - const ev = new ProgressEvent("progress", { - loaded: size, - }); - // TODO(lucacasonato): this is wrong, should be HTML "queue a task" - queueMicrotask(() => { - if (abortedState.aborted) return; - this.dispatchEvent(ev); - }); - } - chunkPromise = reader.read(); - } // 5 Otherwise, if chunkPromise is fulfilled with an object whose done property is true, queue a task to run the following steps and abort this algorithm: - else if (chunk.done === true) { - // TODO(lucacasonato): this is wrong, should be HTML "queue a task" - queueMicrotask(() => { - if (abortedState.aborted) return; - // 1. Set fr’s state to "done". - this[state] = "done"; - // 2. Let result be the result of package data given bytes, type, blob’s type, and encodingName. - const size = ArrayPrototypeReduce( - chunks, - (p, i) => p + i.byteLength, - 0, - ); - const bytes = new Uint8Array(size); - let offs = 0; - for (let i = 0; i < chunks.length; ++i) { - const chunk = chunks[i]; - TypedArrayPrototypeSet(bytes, chunk, offs); - offs += chunk.byteLength; + chunkPromise = reader.read(); + } // 5 Otherwise, if chunkPromise is fulfilled with an object whose done property is true, queue a task to run the following steps and abort this algorithm: + else if (chunk.done === true) { + // TODO(lucacasonato): this is wrong, should be HTML "queue a task" + queueMicrotask(() => { + if (abortedState.aborted) return; + // 1. Set fr’s state to "done". + this[state] = "done"; + // 2. Let result be the result of package data given bytes, type, blob’s type, and encodingName. + const size = ArrayPrototypeReduce( + chunks, + (p, i) => p + i.byteLength, + 0, + ); + const bytes = new Uint8Array(size); + let offs = 0; + for (let i = 0; i < chunks.length; ++i) { + const chunk = chunks[i]; + TypedArrayPrototypeSet(bytes, chunk, offs); + offs += chunk.byteLength; + } + switch (readtype.kind) { + case "ArrayBuffer": { + this[result] = bytes.buffer; + break; } - switch (readtype.kind) { - case "ArrayBuffer": { - this[result] = bytes.buffer; - break; - } - case "BinaryString": - this[result] = core.ops.op_encode_binary_string(bytes); - break; - case "Text": { - let decoder = undefined; - if (readtype.encoding) { - try { - decoder = new TextDecoder(readtype.encoding); - } catch { - // don't care about the error - } + case "BinaryString": + this[result] = ops.op_encode_binary_string(bytes); + break; + case "Text": { + let decoder = undefined; + if (readtype.encoding) { + try { + decoder = new TextDecoder(readtype.encoding); + } catch { + // don't care about the error } - if (decoder === undefined) { - const mimeType = parseMimeType(blob.type); - if (mimeType) { - const charset = MapPrototypeGet( - mimeType.parameters, - "charset", - ); - if (charset) { - try { - decoder = new TextDecoder(charset); - } catch { - // don't care about the error - } + } + if (decoder === undefined) { + const mimeType = parseMimeType(blob.type); + if (mimeType) { + const charset = MapPrototypeGet( + mimeType.parameters, + "charset", + ); + if (charset) { + try { + decoder = new TextDecoder(charset); + } catch { + // don't care about the error } } } - if (decoder === undefined) { - decoder = new TextDecoder(); - } - this[result] = decode(bytes, decoder.encoding); - break; } - case "DataUrl": { - const mediaType = blob.type || "application/octet-stream"; - this[result] = `data:${mediaType};base64,${ - forgivingBase64Encode(bytes) - }`; - break; + if (decoder === undefined) { + decoder = new TextDecoder(); } + this[result] = decode(bytes, decoder.encoding); + break; } - // 4.2 Fire a progress event called load at the fr. - { - const ev = new ProgressEvent("load", { - lengthComputable: true, - loaded: size, - total: size, - }); - this.dispatchEvent(ev); - } - - // 5. If fr’s state is not "loading", fire a progress event called loadend at the fr. - //Note: Event handler for the load or error events could have started another load, if that happens the loadend event for this load is not fired. - if (this[state] !== "loading") { - const ev = new ProgressEvent("loadend", { - lengthComputable: true, - loaded: size, - total: size, - }); - this.dispatchEvent(ev); + case "DataUrl": { + const mediaType = blob.type || "application/octet-stream"; + this[result] = `data:${mediaType};base64,${ + forgivingBase64Encode(bytes) + }`; + break; } - }); - break; - } - } catch (err) { - // TODO(lucacasonato): this is wrong, should be HTML "queue a task" - queueMicrotask(() => { - if (abortedState.aborted) return; - - // chunkPromise rejected - this[state] = "done"; - this[error] = err; - + } + // 4.2 Fire a progress event called load at the fr. { - const ev = new ProgressEvent("error", {}); + const ev = new ProgressEvent("load", { + lengthComputable: true, + loaded: size, + total: size, + }); this.dispatchEvent(ev); } - //If fr’s state is not "loading", fire a progress event called loadend at fr. - //Note: Event handler for the error event could have started another load, if that happens the loadend event for this load is not fired. + // 5. If fr’s state is not "loading", fire a progress event called loadend at the fr. + //Note: Event handler for the load or error events could have started another load, if that happens the loadend event for this load is not fired. if (this[state] !== "loading") { - const ev = new ProgressEvent("loadend", {}); + const ev = new ProgressEvent("loadend", { + lengthComputable: true, + loaded: size, + total: size, + }); this.dispatchEvent(ev); } }); break; } - } - })(); - } + } catch (err) { + // TODO(lucacasonato): this is wrong, should be HTML "queue a task" + queueMicrotask(() => { + if (abortedState.aborted) return; - #getEventHandlerFor(name) { - webidl.assertBranded(this, FileReaderPrototype); + // chunkPromise rejected + this[state] = "done"; + this[error] = err; - const maybeMap = this[handlerSymbol]; - if (!maybeMap) return null; + { + const ev = new ProgressEvent("error", {}); + this.dispatchEvent(ev); + } - return MapPrototypeGet(maybeMap, name)?.handler ?? null; - } + //If fr’s state is not "loading", fire a progress event called loadend at fr. + //Note: Event handler for the error event could have started another load, if that happens the loadend event for this load is not fired. + if (this[state] !== "loading") { + const ev = new ProgressEvent("loadend", {}); + this.dispatchEvent(ev); + } + }); + break; + } + } + })(); + } - #setEventHandlerFor(name, value) { - webidl.assertBranded(this, FileReaderPrototype); + #getEventHandlerFor(name) { + webidl.assertBranded(this, FileReaderPrototype); - if (!this[handlerSymbol]) { - this[handlerSymbol] = new Map(); - } - let handlerWrapper = MapPrototypeGet(this[handlerSymbol], name); - if (handlerWrapper) { - handlerWrapper.handler = value; - } else { - handlerWrapper = makeWrappedHandler(value); - this.addEventListener(name, handlerWrapper); - } + const maybeMap = this[handlerSymbol]; + if (!maybeMap) return null; - MapPrototypeSet(this[handlerSymbol], name, handlerWrapper); - } + return MapPrototypeGet(maybeMap, name)?.handler ?? null; + } - constructor() { - super(); - this[webidl.brand] = webidl.brand; - } + #setEventHandlerFor(name, value) { + webidl.assertBranded(this, FileReaderPrototype); - /** @returns {number} */ - get readyState() { - webidl.assertBranded(this, FileReaderPrototype); - switch (this[state]) { - case "empty": - return FileReader.EMPTY; - case "loading": - return FileReader.LOADING; - case "done": - return FileReader.DONE; - default: - throw new TypeError("Invalid state"); - } + if (!this[handlerSymbol]) { + this[handlerSymbol] = new Map(); } - - get result() { - webidl.assertBranded(this, FileReaderPrototype); - return this[result]; + let handlerWrapper = MapPrototypeGet(this[handlerSymbol], name); + if (handlerWrapper) { + handlerWrapper.handler = value; + } else { + handlerWrapper = makeWrappedHandler(value); + this.addEventListener(name, handlerWrapper); } - get error() { - webidl.assertBranded(this, FileReaderPrototype); - return this[error]; + MapPrototypeSet(this[handlerSymbol], name, handlerWrapper); + } + + constructor() { + super(); + this[webidl.brand] = webidl.brand; + } + + /** @returns {number} */ + get readyState() { + webidl.assertBranded(this, FileReaderPrototype); + switch (this[state]) { + case "empty": + return FileReader.EMPTY; + case "loading": + return FileReader.LOADING; + case "done": + return FileReader.DONE; + default: + throw new TypeError("Invalid state"); } + } - abort() { - webidl.assertBranded(this, FileReaderPrototype); - // If context object's state is "empty" or if context object's state is "done" set context object's result to null and terminate this algorithm. - if ( - this[state] === "empty" || - this[state] === "done" - ) { - this[result] = null; - return; - } - // If context object's state is "loading" set context object's state to "done" and set context object's result to null. - if (this[state] === "loading") { - this[state] = "done"; - this[result] = null; - } - // If there are any tasks from the context object on the file reading task source in an affiliated task queue, then remove those tasks from that task queue. - // Terminate the algorithm for the read method being processed. - if (this[aborted] !== null) { - this[aborted].aborted = true; - } + get result() { + webidl.assertBranded(this, FileReaderPrototype); + return this[result]; + } - // Fire a progress event called abort at the context object. - const ev = new ProgressEvent("abort", {}); - this.dispatchEvent(ev); + get error() { + webidl.assertBranded(this, FileReaderPrototype); + return this[error]; + } - // If context object's state is not "loading", fire a progress event called loadend at the context object. - if (this[state] !== "loading") { - const ev = new ProgressEvent("loadend", {}); - this.dispatchEvent(ev); - } + abort() { + webidl.assertBranded(this, FileReaderPrototype); + // If context object's state is "empty" or if context object's state is "done" set context object's result to null and terminate this algorithm. + if ( + this[state] === "empty" || + this[state] === "done" + ) { + this[result] = null; + return; } - - /** @param {Blob} blob */ - readAsArrayBuffer(blob) { - webidl.assertBranded(this, FileReaderPrototype); - const prefix = "Failed to execute 'readAsArrayBuffer' on 'FileReader'"; - webidl.requiredArguments(arguments.length, 1, { prefix }); - this.#readOperation(blob, { kind: "ArrayBuffer" }); + // If context object's state is "loading" set context object's state to "done" and set context object's result to null. + if (this[state] === "loading") { + this[state] = "done"; + this[result] = null; } - - /** @param {Blob} blob */ - readAsBinaryString(blob) { - webidl.assertBranded(this, FileReaderPrototype); - const prefix = "Failed to execute 'readAsBinaryString' on 'FileReader'"; - webidl.requiredArguments(arguments.length, 1, { prefix }); - // alias for readAsArrayBuffer - this.#readOperation(blob, { kind: "BinaryString" }); + // If there are any tasks from the context object on the file reading task source in an affiliated task queue, then remove those tasks from that task queue. + // Terminate the algorithm for the read method being processed. + if (this[aborted] !== null) { + this[aborted].aborted = true; } - /** @param {Blob} blob */ - readAsDataURL(blob) { - webidl.assertBranded(this, FileReaderPrototype); - const prefix = "Failed to execute 'readAsDataURL' on 'FileReader'"; - webidl.requiredArguments(arguments.length, 1, { prefix }); - // alias for readAsArrayBuffer - this.#readOperation(blob, { kind: "DataUrl" }); - } + // Fire a progress event called abort at the context object. + const ev = new ProgressEvent("abort", {}); + this.dispatchEvent(ev); - /** - * @param {Blob} blob - * @param {string} [encoding] - */ - readAsText(blob, encoding = undefined) { - webidl.assertBranded(this, FileReaderPrototype); - const prefix = "Failed to execute 'readAsText' on 'FileReader'"; - webidl.requiredArguments(arguments.length, 1, { prefix }); - if (encoding !== undefined) { - encoding = webidl.converters["DOMString"](encoding, { - prefix, - context: "Argument 2", - }); - } - // alias for readAsArrayBuffer - this.#readOperation(blob, { kind: "Text", encoding }); + // If context object's state is not "loading", fire a progress event called loadend at the context object. + if (this[state] !== "loading") { + const ev = new ProgressEvent("loadend", {}); + this.dispatchEvent(ev); } + } - get onerror() { - return this.#getEventHandlerFor("error"); - } - set onerror(value) { - this.#setEventHandlerFor("error", value); - } + /** @param {Blob} blob */ + readAsArrayBuffer(blob) { + webidl.assertBranded(this, FileReaderPrototype); + const prefix = "Failed to execute 'readAsArrayBuffer' on 'FileReader'"; + webidl.requiredArguments(arguments.length, 1, { prefix }); + this.#readOperation(blob, { kind: "ArrayBuffer" }); + } - get onloadstart() { - return this.#getEventHandlerFor("loadstart"); - } - set onloadstart(value) { - this.#setEventHandlerFor("loadstart", value); - } + /** @param {Blob} blob */ + readAsBinaryString(blob) { + webidl.assertBranded(this, FileReaderPrototype); + const prefix = "Failed to execute 'readAsBinaryString' on 'FileReader'"; + webidl.requiredArguments(arguments.length, 1, { prefix }); + // alias for readAsArrayBuffer + this.#readOperation(blob, { kind: "BinaryString" }); + } - get onload() { - return this.#getEventHandlerFor("load"); - } - set onload(value) { - this.#setEventHandlerFor("load", value); - } + /** @param {Blob} blob */ + readAsDataURL(blob) { + webidl.assertBranded(this, FileReaderPrototype); + const prefix = "Failed to execute 'readAsDataURL' on 'FileReader'"; + webidl.requiredArguments(arguments.length, 1, { prefix }); + // alias for readAsArrayBuffer + this.#readOperation(blob, { kind: "DataUrl" }); + } - get onloadend() { - return this.#getEventHandlerFor("loadend"); - } - set onloadend(value) { - this.#setEventHandlerFor("loadend", value); + /** + * @param {Blob} blob + * @param {string} [encoding] + */ + readAsText(blob, encoding = undefined) { + webidl.assertBranded(this, FileReaderPrototype); + const prefix = "Failed to execute 'readAsText' on 'FileReader'"; + webidl.requiredArguments(arguments.length, 1, { prefix }); + if (encoding !== undefined) { + encoding = webidl.converters["DOMString"](encoding, { + prefix, + context: "Argument 2", + }); } + // alias for readAsArrayBuffer + this.#readOperation(blob, { kind: "Text", encoding }); + } - get onprogress() { - return this.#getEventHandlerFor("progress"); - } - set onprogress(value) { - this.#setEventHandlerFor("progress", value); - } + get onerror() { + return this.#getEventHandlerFor("error"); + } + set onerror(value) { + this.#setEventHandlerFor("error", value); + } - get onabort() { - return this.#getEventHandlerFor("abort"); - } - set onabort(value) { - this.#setEventHandlerFor("abort", value); - } + get onloadstart() { + return this.#getEventHandlerFor("loadstart"); + } + set onloadstart(value) { + this.#setEventHandlerFor("loadstart", value); } - webidl.configurePrototype(FileReader); - const FileReaderPrototype = FileReader.prototype; - - ObjectDefineProperty(FileReader, "EMPTY", { - writable: false, - enumerable: true, - configurable: false, - value: 0, - }); - ObjectDefineProperty(FileReader, "LOADING", { - writable: false, - enumerable: true, - configurable: false, - value: 1, - }); - ObjectDefineProperty(FileReader, "DONE", { - writable: false, - enumerable: true, - configurable: false, - value: 2, - }); - ObjectDefineProperty(FileReader.prototype, "EMPTY", { - writable: false, - enumerable: true, - configurable: false, - value: 0, - }); - ObjectDefineProperty(FileReader.prototype, "LOADING", { - writable: false, - enumerable: true, - configurable: false, - value: 1, - }); - ObjectDefineProperty(FileReader.prototype, "DONE", { - writable: false, - enumerable: true, - configurable: false, - value: 2, - }); - - function makeWrappedHandler(handler) { - function wrappedHandler(...args) { - if (typeof wrappedHandler.handler !== "function") { - return; - } - return FunctionPrototypeCall( - wrappedHandler.handler, - this, - ...new SafeArrayIterator(args), - ); + get onload() { + return this.#getEventHandlerFor("load"); + } + set onload(value) { + this.#setEventHandlerFor("load", value); + } + + get onloadend() { + return this.#getEventHandlerFor("loadend"); + } + set onloadend(value) { + this.#setEventHandlerFor("loadend", value); + } + + get onprogress() { + return this.#getEventHandlerFor("progress"); + } + set onprogress(value) { + this.#setEventHandlerFor("progress", value); + } + + get onabort() { + return this.#getEventHandlerFor("abort"); + } + set onabort(value) { + this.#setEventHandlerFor("abort", value); + } +} + +webidl.configurePrototype(FileReader); +const FileReaderPrototype = FileReader.prototype; + +ObjectDefineProperty(FileReader, "EMPTY", { + writable: false, + enumerable: true, + configurable: false, + value: 0, +}); +ObjectDefineProperty(FileReader, "LOADING", { + writable: false, + enumerable: true, + configurable: false, + value: 1, +}); +ObjectDefineProperty(FileReader, "DONE", { + writable: false, + enumerable: true, + configurable: false, + value: 2, +}); +ObjectDefineProperty(FileReader.prototype, "EMPTY", { + writable: false, + enumerable: true, + configurable: false, + value: 0, +}); +ObjectDefineProperty(FileReader.prototype, "LOADING", { + writable: false, + enumerable: true, + configurable: false, + value: 1, +}); +ObjectDefineProperty(FileReader.prototype, "DONE", { + writable: false, + enumerable: true, + configurable: false, + value: 2, +}); + +function makeWrappedHandler(handler) { + function wrappedHandler(...args) { + if (typeof wrappedHandler.handler !== "function") { + return; } - wrappedHandler.handler = handler; - return wrappedHandler; + return FunctionPrototypeCall( + wrappedHandler.handler, + this, + ...new SafeArrayIterator(args), + ); } + wrappedHandler.handler = handler; + return wrappedHandler; +} - window.__bootstrap.fileReader = { - FileReader, - }; -})(this); +export { FileReader }; diff --git a/ext/web/11_blob_url.js b/ext/web/11_blob_url.js index a51a1e71851a47..205d0851fb5c8e 100644 --- a/ext/web/11_blob_url.js +++ b/ext/web/11_blob_url.js @@ -10,50 +10,42 @@ /// /// /// -"use strict"; -((window) => { - const core = Deno.core; - const ops = core.ops; - const webidl = window.__bootstrap.webidl; - const { getParts } = window.__bootstrap.file; - const { URL } = window.__bootstrap.url; - - /** - * @param {Blob} blob - * @returns {string} - */ - function createObjectURL(blob) { - const prefix = "Failed to execute 'createObjectURL' on 'URL'"; - webidl.requiredArguments(arguments.length, 1, { prefix }); - blob = webidl.converters["Blob"](blob, { - context: "Argument 1", - prefix, - }); - - const url = ops.op_blob_create_object_url( - blob.type, - getParts(blob), - ); - - return url; - } - - /** - * @param {string} url - * @returns {void} - */ - function revokeObjectURL(url) { - const prefix = "Failed to execute 'revokeObjectURL' on 'URL'"; - webidl.requiredArguments(arguments.length, 1, { prefix }); - url = webidl.converters["DOMString"](url, { - context: "Argument 1", - prefix, - }); - - ops.op_blob_revoke_object_url(url); - } - - URL.createObjectURL = createObjectURL; - URL.revokeObjectURL = revokeObjectURL; -})(globalThis); +const core = globalThis.Deno.core; +const ops = core.ops; +import * as webidl from "internal:deno_webidl/00_webidl.js"; +import { getParts } from "internal:deno_web/09_file.js"; +import { URL } from "internal:deno_url/00_url.js"; + +/** + * @param {Blob} blob + * @returns {string} + */ +function createObjectURL(blob) { + const prefix = "Failed to execute 'createObjectURL' on 'URL'"; + webidl.requiredArguments(arguments.length, 1, { prefix }); + blob = webidl.converters["Blob"](blob, { + context: "Argument 1", + prefix, + }); + + return ops.op_blob_create_object_url(blob.type, getParts(blob)); +} + +/** + * @param {string} url + * @returns {void} + */ +function revokeObjectURL(url) { + const prefix = "Failed to execute 'revokeObjectURL' on 'URL'"; + webidl.requiredArguments(arguments.length, 1, { prefix }); + url = webidl.converters["DOMString"](url, { + context: "Argument 1", + prefix, + }); + + ops.op_blob_revoke_object_url(url); +} + +URL.createObjectURL = createObjectURL; +URL.revokeObjectURL = revokeObjectURL; diff --git a/ext/web/12_location.js b/ext/web/12_location.js index 964ca591ecdd50..6a15c081159f94 100644 --- a/ext/web/12_location.js +++ b/ext/web/12_location.js @@ -1,403 +1,410 @@ // Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. -"use strict"; /// -((window) => { - const { URL } = window.__bootstrap.url; - const { DOMException } = window.__bootstrap.domException; - const { - Error, - ObjectDefineProperties, - Symbol, - SymbolFor, - SymbolToStringTag, - TypeError, - WeakMap, - WeakMapPrototypeGet, - WeakMapPrototypeSet, - } = window.__bootstrap.primordials; +import { URL } from "internal:deno_url/00_url.js"; +import DOMException from "internal:deno_web/01_dom_exception.js"; +const primordials = globalThis.__bootstrap.primordials; +const { + Error, + ObjectDefineProperties, + Symbol, + SymbolFor, + SymbolToStringTag, + TypeError, + WeakMap, + WeakMapPrototypeGet, + WeakMapPrototypeSet, +} = primordials; - const locationConstructorKey = Symbol("locationConstuctorKey"); +const locationConstructorKey = Symbol("locationConstuctorKey"); - // The differences between the definitions of `Location` and `WorkerLocation` - // are because of the `LegacyUnforgeable` attribute only specified upon - // `Location`'s properties. See: - // - https://html.spec.whatwg.org/multipage/history.html#the-location-interface - // - https://heycam.github.io/webidl/#LegacyUnforgeable - class Location { - constructor(href = null, key = null) { - if (key != locationConstructorKey) { - throw new TypeError("Illegal constructor."); - } - const url = new URL(href); - url.username = ""; - url.password = ""; - ObjectDefineProperties(this, { - hash: { - get() { - return url.hash; - }, - set() { - throw new DOMException( - `Cannot set "location.hash".`, - "NotSupportedError", - ); - }, - enumerable: true, +// The differences between the definitions of `Location` and `WorkerLocation` +// are because of the `LegacyUnforgeable` attribute only specified upon +// `Location`'s properties. See: +// - https://html.spec.whatwg.org/multipage/history.html#the-location-interface +// - https://heycam.github.io/webidl/#LegacyUnforgeable +class Location { + constructor(href = null, key = null) { + if (key != locationConstructorKey) { + throw new TypeError("Illegal constructor."); + } + const url = new URL(href); + url.username = ""; + url.password = ""; + ObjectDefineProperties(this, { + hash: { + get() { + return url.hash; }, - host: { - get() { - return url.host; - }, - set() { - throw new DOMException( - `Cannot set "location.host".`, - "NotSupportedError", - ); - }, - enumerable: true, + set() { + throw new DOMException( + `Cannot set "location.hash".`, + "NotSupportedError", + ); }, - hostname: { - get() { - return url.hostname; - }, - set() { - throw new DOMException( - `Cannot set "location.hostname".`, - "NotSupportedError", - ); - }, - enumerable: true, + enumerable: true, + }, + host: { + get() { + return url.host; }, - href: { - get() { - return url.href; - }, - set() { - throw new DOMException( - `Cannot set "location.href".`, - "NotSupportedError", - ); - }, - enumerable: true, + set() { + throw new DOMException( + `Cannot set "location.host".`, + "NotSupportedError", + ); }, - origin: { - get() { - return url.origin; - }, - enumerable: true, + enumerable: true, + }, + hostname: { + get() { + return url.hostname; }, - pathname: { - get() { - return url.pathname; - }, - set() { - throw new DOMException( - `Cannot set "location.pathname".`, - "NotSupportedError", - ); - }, - enumerable: true, + set() { + throw new DOMException( + `Cannot set "location.hostname".`, + "NotSupportedError", + ); }, - port: { - get() { - return url.port; - }, - set() { - throw new DOMException( - `Cannot set "location.port".`, - "NotSupportedError", - ); - }, - enumerable: true, + enumerable: true, + }, + href: { + get() { + return url.href; }, - protocol: { - get() { - return url.protocol; - }, - set() { - throw new DOMException( - `Cannot set "location.protocol".`, - "NotSupportedError", - ); - }, - enumerable: true, + set() { + throw new DOMException( + `Cannot set "location.href".`, + "NotSupportedError", + ); }, - search: { - get() { - return url.search; - }, - set() { - throw new DOMException( - `Cannot set "location.search".`, - "NotSupportedError", - ); - }, - enumerable: true, + enumerable: true, + }, + origin: { + get() { + return url.origin; }, - ancestorOrigins: { - get() { - // TODO(nayeemrmn): Replace with a `DOMStringList` instance. - return { - length: 0, - item: () => null, - contains: () => false, - }; - }, - enumerable: true, + enumerable: true, + }, + pathname: { + get() { + return url.pathname; }, - assign: { - value: function assign() { - throw new DOMException( - `Cannot call "location.assign()".`, - "NotSupportedError", - ); - }, - enumerable: true, + set() { + throw new DOMException( + `Cannot set "location.pathname".`, + "NotSupportedError", + ); }, - reload: { - value: function reload() { - throw new DOMException( - `Cannot call "location.reload()".`, - "NotSupportedError", - ); - }, - enumerable: true, + enumerable: true, + }, + port: { + get() { + return url.port; }, - replace: { - value: function replace() { - throw new DOMException( - `Cannot call "location.replace()".`, - "NotSupportedError", - ); - }, - enumerable: true, + set() { + throw new DOMException( + `Cannot set "location.port".`, + "NotSupportedError", + ); }, - toString: { - value: function toString() { - return url.href; - }, - enumerable: true, + enumerable: true, + }, + protocol: { + get() { + return url.protocol; }, - [SymbolFor("Deno.privateCustomInspect")]: { - value: function (inspect) { - const object = { - hash: this.hash, - host: this.host, - hostname: this.hostname, - href: this.href, - origin: this.origin, - pathname: this.pathname, - port: this.port, - protocol: this.protocol, - search: this.search, - }; - return `${this.constructor.name} ${inspect(object)}`; - }, + set() { + throw new DOMException( + `Cannot set "location.protocol".`, + "NotSupportedError", + ); }, - }); - } + enumerable: true, + }, + search: { + get() { + return url.search; + }, + set() { + throw new DOMException( + `Cannot set "location.search".`, + "NotSupportedError", + ); + }, + enumerable: true, + }, + ancestorOrigins: { + get() { + // TODO(nayeemrmn): Replace with a `DOMStringList` instance. + return { + length: 0, + item: () => null, + contains: () => false, + }; + }, + enumerable: true, + }, + assign: { + value: function assign() { + throw new DOMException( + `Cannot call "location.assign()".`, + "NotSupportedError", + ); + }, + enumerable: true, + }, + reload: { + value: function reload() { + throw new DOMException( + `Cannot call "location.reload()".`, + "NotSupportedError", + ); + }, + enumerable: true, + }, + replace: { + value: function replace() { + throw new DOMException( + `Cannot call "location.replace()".`, + "NotSupportedError", + ); + }, + enumerable: true, + }, + toString: { + value: function toString() { + return url.href; + }, + enumerable: true, + }, + [SymbolFor("Deno.privateCustomInspect")]: { + value: function (inspect) { + const object = { + hash: this.hash, + host: this.host, + hostname: this.hostname, + href: this.href, + origin: this.origin, + pathname: this.pathname, + port: this.port, + protocol: this.protocol, + search: this.search, + }; + return `${this.constructor.name} ${inspect(object)}`; + }, + }, + }); } +} - ObjectDefineProperties(Location.prototype, { - [SymbolToStringTag]: { - value: "Location", - configurable: true, - }, - }); +ObjectDefineProperties(Location.prototype, { + [SymbolToStringTag]: { + value: "Location", + configurable: true, + }, +}); - const workerLocationUrls = new WeakMap(); +const workerLocationUrls = new WeakMap(); - class WorkerLocation { - constructor(href = null, key = null) { - if (key != locationConstructorKey) { - throw new TypeError("Illegal constructor."); - } - const url = new URL(href); - url.username = ""; - url.password = ""; - WeakMapPrototypeSet(workerLocationUrls, this, url); +class WorkerLocation { + constructor(href = null, key = null) { + if (key != locationConstructorKey) { + throw new TypeError("Illegal constructor."); } + const url = new URL(href); + url.username = ""; + url.password = ""; + WeakMapPrototypeSet(workerLocationUrls, this, url); } +} - ObjectDefineProperties(WorkerLocation.prototype, { - hash: { - get() { - const url = WeakMapPrototypeGet(workerLocationUrls, this); - if (url == null) { - throw new TypeError("Illegal invocation."); - } - return url.hash; - }, - configurable: true, - enumerable: true, - }, - host: { - get() { - const url = WeakMapPrototypeGet(workerLocationUrls, this); - if (url == null) { - throw new TypeError("Illegal invocation."); - } - return url.host; - }, - configurable: true, - enumerable: true, +ObjectDefineProperties(WorkerLocation.prototype, { + hash: { + get() { + const url = WeakMapPrototypeGet(workerLocationUrls, this); + if (url == null) { + throw new TypeError("Illegal invocation."); + } + return url.hash; }, - hostname: { - get() { - const url = WeakMapPrototypeGet(workerLocationUrls, this); - if (url == null) { - throw new TypeError("Illegal invocation."); - } - return url.hostname; - }, - configurable: true, - enumerable: true, + configurable: true, + enumerable: true, + }, + host: { + get() { + const url = WeakMapPrototypeGet(workerLocationUrls, this); + if (url == null) { + throw new TypeError("Illegal invocation."); + } + return url.host; }, - href: { - get() { - const url = WeakMapPrototypeGet(workerLocationUrls, this); - if (url == null) { - throw new TypeError("Illegal invocation."); - } - return url.href; - }, - configurable: true, - enumerable: true, + configurable: true, + enumerable: true, + }, + hostname: { + get() { + const url = WeakMapPrototypeGet(workerLocationUrls, this); + if (url == null) { + throw new TypeError("Illegal invocation."); + } + return url.hostname; }, - origin: { - get() { - const url = WeakMapPrototypeGet(workerLocationUrls, this); - if (url == null) { - throw new TypeError("Illegal invocation."); - } - return url.origin; - }, - configurable: true, - enumerable: true, + configurable: true, + enumerable: true, + }, + href: { + get() { + const url = WeakMapPrototypeGet(workerLocationUrls, this); + if (url == null) { + throw new TypeError("Illegal invocation."); + } + return url.href; }, - pathname: { - get() { - const url = WeakMapPrototypeGet(workerLocationUrls, this); - if (url == null) { - throw new TypeError("Illegal invocation."); - } - return url.pathname; - }, - configurable: true, - enumerable: true, + configurable: true, + enumerable: true, + }, + origin: { + get() { + const url = WeakMapPrototypeGet(workerLocationUrls, this); + if (url == null) { + throw new TypeError("Illegal invocation."); + } + return url.origin; }, - port: { - get() { - const url = WeakMapPrototypeGet(workerLocationUrls, this); - if (url == null) { - throw new TypeError("Illegal invocation."); - } - return url.port; - }, - configurable: true, - enumerable: true, + configurable: true, + enumerable: true, + }, + pathname: { + get() { + const url = WeakMapPrototypeGet(workerLocationUrls, this); + if (url == null) { + throw new TypeError("Illegal invocation."); + } + return url.pathname; }, - protocol: { - get() { - const url = WeakMapPrototypeGet(workerLocationUrls, this); - if (url == null) { - throw new TypeError("Illegal invocation."); - } - return url.protocol; - }, - configurable: true, - enumerable: true, + configurable: true, + enumerable: true, + }, + port: { + get() { + const url = WeakMapPrototypeGet(workerLocationUrls, this); + if (url == null) { + throw new TypeError("Illegal invocation."); + } + return url.port; }, - search: { - get() { - const url = WeakMapPrototypeGet(workerLocationUrls, this); - if (url == null) { - throw new TypeError("Illegal invocation."); - } - return url.search; - }, - configurable: true, - enumerable: true, + configurable: true, + enumerable: true, + }, + protocol: { + get() { + const url = WeakMapPrototypeGet(workerLocationUrls, this); + if (url == null) { + throw new TypeError("Illegal invocation."); + } + return url.protocol; }, - toString: { - value: function toString() { - const url = WeakMapPrototypeGet(workerLocationUrls, this); - if (url == null) { - throw new TypeError("Illegal invocation."); - } - return url.href; - }, - configurable: true, - enumerable: true, - writable: true, + configurable: true, + enumerable: true, + }, + search: { + get() { + const url = WeakMapPrototypeGet(workerLocationUrls, this); + if (url == null) { + throw new TypeError("Illegal invocation."); + } + return url.search; }, - [SymbolToStringTag]: { - value: "WorkerLocation", - configurable: true, + configurable: true, + enumerable: true, + }, + toString: { + value: function toString() { + const url = WeakMapPrototypeGet(workerLocationUrls, this); + if (url == null) { + throw new TypeError("Illegal invocation."); + } + return url.href; }, - [SymbolFor("Deno.privateCustomInspect")]: { - value: function (inspect) { - const object = { - hash: this.hash, - host: this.host, - hostname: this.hostname, - href: this.href, - origin: this.origin, - pathname: this.pathname, - port: this.port, - protocol: this.protocol, - search: this.search, - }; - return `${this.constructor.name} ${inspect(object)}`; - }, + configurable: true, + enumerable: true, + writable: true, + }, + [SymbolToStringTag]: { + value: "WorkerLocation", + configurable: true, + }, + [SymbolFor("Deno.privateCustomInspect")]: { + value: function (inspect) { + const object = { + hash: this.hash, + host: this.host, + hostname: this.hostname, + href: this.href, + origin: this.origin, + pathname: this.pathname, + port: this.port, + protocol: this.protocol, + search: this.search, + }; + return `${this.constructor.name} ${inspect(object)}`; }, - }); + }, +}); - let location = undefined; - let workerLocation = undefined; +let location = undefined; +let workerLocation = undefined; - function setLocationHref(href) { - location = new Location(href, locationConstructorKey); - workerLocation = new WorkerLocation(href, locationConstructorKey); - } +function setLocationHref(href) { + location = new Location(href, locationConstructorKey); + workerLocation = new WorkerLocation(href, locationConstructorKey); +} - window.__bootstrap.location = { - locationConstructorDescriptor: { - value: Location, - configurable: true, - writable: true, - }, - workerLocationConstructorDescriptor: { - value: WorkerLocation, - configurable: true, - writable: true, - }, - locationDescriptor: { - get() { - return location; - }, - set() { - throw new DOMException(`Cannot set "location".`, "NotSupportedError"); - }, - enumerable: true, - }, - workerLocationDescriptor: { - get() { - if (workerLocation == null) { - throw new Error( - `Assertion: "globalThis.location" must be defined in a worker.`, - ); - } - return workerLocation; - }, - configurable: true, - enumerable: true, - }, - setLocationHref, - getLocationHref() { - return location?.href; - }, - }; -})(this); +function getLocationHref() { + return location?.href; +} + +const locationConstructorDescriptor = { + value: Location, + configurable: true, + writable: true, +}; + +const workerLocationConstructorDescriptor = { + value: WorkerLocation, + configurable: true, + writable: true, +}; + +const locationDescriptor = { + get() { + return location; + }, + set() { + throw new DOMException(`Cannot set "location".`, "NotSupportedError"); + }, + enumerable: true, +}; +const workerLocationDescriptor = { + get() { + if (workerLocation == null) { + throw new Error( + `Assertion: "globalThis.location" must be defined in a worker.`, + ); + } + return workerLocation; + }, + configurable: true, + enumerable: true, +}; + +export { + getLocationHref, + locationConstructorDescriptor, + locationDescriptor, + setLocationHref, + workerLocationConstructorDescriptor, + workerLocationDescriptor, +}; diff --git a/ext/web/13_message_port.js b/ext/web/13_message_port.js index 7ab2beb8266416..dafb2b782d9887 100644 --- a/ext/web/13_message_port.js +++ b/ext/web/13_message_port.js @@ -6,338 +6,339 @@ /// /// -"use strict"; - -((window) => { - const core = window.Deno.core; - const { InterruptedPrototype, ops } = core; - const webidl = window.__bootstrap.webidl; - const { EventTarget, setEventTargetData } = window.__bootstrap.eventTarget; - const { MessageEvent, defineEventHandler } = window.__bootstrap.event; - const { DOMException } = window.__bootstrap.domException; - const { - ArrayBufferPrototype, - ArrayPrototypeFilter, - ArrayPrototypeIncludes, - ArrayPrototypePush, - ObjectPrototypeIsPrototypeOf, - ObjectSetPrototypeOf, - Symbol, - SymbolFor, - SymbolIterator, - TypeError, - } = window.__bootstrap.primordials; - - class MessageChannel { - /** @type {MessagePort} */ - #port1; - /** @type {MessagePort} */ - #port2; - - constructor() { - this[webidl.brand] = webidl.brand; - const { 0: port1Id, 1: port2Id } = opCreateEntangledMessagePort(); - const port1 = createMessagePort(port1Id); - const port2 = createMessagePort(port2Id); - this.#port1 = port1; - this.#port2 = port2; - } - - get port1() { - webidl.assertBranded(this, MessageChannelPrototype); - return this.#port1; - } - - get port2() { - webidl.assertBranded(this, MessageChannelPrototype); - return this.#port2; - } +const core = globalThis.Deno.core; +const { InterruptedPrototype, ops } = core; +import * as webidl from "internal:deno_webidl/00_webidl.js"; +import { + defineEventHandler, + EventTarget, + MessageEvent, + setEventTargetData, +} from "internal:deno_web/02_event.js"; +import DOMException from "internal:deno_web/01_dom_exception.js"; +const primordials = globalThis.__bootstrap.primordials; +const { + ArrayBufferPrototype, + ArrayPrototypeFilter, + ArrayPrototypeIncludes, + ArrayPrototypePush, + ObjectPrototypeIsPrototypeOf, + ObjectSetPrototypeOf, + Symbol, + SymbolFor, + SymbolIterator, + TypeError, +} = primordials; + +class MessageChannel { + /** @type {MessagePort} */ + #port1; + /** @type {MessagePort} */ + #port2; + + constructor() { + this[webidl.brand] = webidl.brand; + const { 0: port1Id, 1: port2Id } = opCreateEntangledMessagePort(); + const port1 = createMessagePort(port1Id); + const port2 = createMessagePort(port2Id); + this.#port1 = port1; + this.#port2 = port2; + } - [SymbolFor("Deno.inspect")](inspect) { - return `MessageChannel ${ - inspect({ port1: this.port1, port2: this.port2 }) - }`; - } + get port1() { + webidl.assertBranded(this, MessageChannelPrototype); + return this.#port1; } - webidl.configurePrototype(MessageChannel); - const MessageChannelPrototype = MessageChannel.prototype; + get port2() { + webidl.assertBranded(this, MessageChannelPrototype); + return this.#port2; + } - const _id = Symbol("id"); - const _enabled = Symbol("enabled"); + [SymbolFor("Deno.inspect")](inspect) { + return `MessageChannel ${ + inspect({ port1: this.port1, port2: this.port2 }) + }`; + } +} + +webidl.configurePrototype(MessageChannel); +const MessageChannelPrototype = MessageChannel.prototype; + +const _id = Symbol("id"); +const _enabled = Symbol("enabled"); + +/** + * @param {number} id + * @returns {MessagePort} + */ +function createMessagePort(id) { + const port = core.createHostObject(); + ObjectSetPrototypeOf(port, MessagePortPrototype); + port[webidl.brand] = webidl.brand; + setEventTargetData(port); + port[_id] = id; + return port; +} + +class MessagePort extends EventTarget { + /** @type {number | null} */ + [_id] = null; + /** @type {boolean} */ + [_enabled] = false; + + constructor() { + super(); + webidl.illegalConstructor(); + } /** - * @param {number} id - * @returns {MessagePort} + * @param {any} message + * @param {object[] | StructuredSerializeOptions} transferOrOptions */ - function createMessagePort(id) { - const port = core.createHostObject(); - ObjectSetPrototypeOf(port, MessagePortPrototype); - port[webidl.brand] = webidl.brand; - setEventTargetData(port); - port[_id] = id; - return port; - } - - class MessagePort extends EventTarget { - /** @type {number | null} */ - [_id] = null; - /** @type {boolean} */ - [_enabled] = false; - - constructor() { - super(); - webidl.illegalConstructor(); + postMessage(message, transferOrOptions = {}) { + webidl.assertBranded(this, MessagePortPrototype); + const prefix = "Failed to execute 'postMessage' on 'MessagePort'"; + webidl.requiredArguments(arguments.length, 1, { prefix }); + message = webidl.converters.any(message); + let options; + if ( + webidl.type(transferOrOptions) === "Object" && + transferOrOptions !== undefined && + transferOrOptions[SymbolIterator] !== undefined + ) { + const transfer = webidl.converters["sequence"]( + transferOrOptions, + { prefix, context: "Argument 2" }, + ); + options = { transfer }; + } else { + options = webidl.converters.StructuredSerializeOptions( + transferOrOptions, + { + prefix, + context: "Argument 2", + }, + ); } - - /** - * @param {any} message - * @param {object[] | StructuredSerializeOptions} transferOrOptions - */ - postMessage(message, transferOrOptions = {}) { - webidl.assertBranded(this, MessagePortPrototype); - const prefix = "Failed to execute 'postMessage' on 'MessagePort'"; - webidl.requiredArguments(arguments.length, 1, { prefix }); - message = webidl.converters.any(message); - let options; - if ( - webidl.type(transferOrOptions) === "Object" && - transferOrOptions !== undefined && - transferOrOptions[SymbolIterator] !== undefined - ) { - const transfer = webidl.converters["sequence"]( - transferOrOptions, - { prefix, context: "Argument 2" }, - ); - options = { transfer }; - } else { - options = webidl.converters.StructuredSerializeOptions( - transferOrOptions, - { - prefix, - context: "Argument 2", - }, - ); - } - const { transfer } = options; - if (ArrayPrototypeIncludes(transfer, this)) { - throw new DOMException("Can not tranfer self", "DataCloneError"); - } - const data = serializeJsMessageData(message, transfer); - if (this[_id] === null) return; - ops.op_message_port_post_message(this[_id], data); + const { transfer } = options; + if (ArrayPrototypeIncludes(transfer, this)) { + throw new DOMException("Can not tranfer self", "DataCloneError"); } + const data = serializeJsMessageData(message, transfer); + if (this[_id] === null) return; + ops.op_message_port_post_message(this[_id], data); + } - start() { - webidl.assertBranded(this, MessagePortPrototype); - if (this[_enabled]) return; - (async () => { - this[_enabled] = true; - while (true) { - if (this[_id] === null) break; - let data; - try { - data = await core.opAsync( - "op_message_port_recv_message", - this[_id], - ); - } catch (err) { - if (ObjectPrototypeIsPrototypeOf(InterruptedPrototype, err)) break; - throw err; - } - if (data === null) break; - let message, transferables; - try { - const v = deserializeJsMessageData(data); - message = v[0]; - transferables = v[1]; - } catch (err) { - const event = new MessageEvent("messageerror", { data: err }); - this.dispatchEvent(event); - return; - } - const event = new MessageEvent("message", { - data: message, - ports: ArrayPrototypeFilter( - transferables, - (t) => ObjectPrototypeIsPrototypeOf(MessagePortPrototype, t), - ), - }); + start() { + webidl.assertBranded(this, MessagePortPrototype); + if (this[_enabled]) return; + (async () => { + this[_enabled] = true; + while (true) { + if (this[_id] === null) break; + let data; + try { + data = await core.opAsync( + "op_message_port_recv_message", + this[_id], + ); + } catch (err) { + if (ObjectPrototypeIsPrototypeOf(InterruptedPrototype, err)) break; + throw err; + } + if (data === null) break; + let message, transferables; + try { + const v = deserializeJsMessageData(data); + message = v[0]; + transferables = v[1]; + } catch (err) { + const event = new MessageEvent("messageerror", { data: err }); this.dispatchEvent(event); + return; } - this[_enabled] = false; - })(); - } - - close() { - webidl.assertBranded(this, MessagePortPrototype); - if (this[_id] !== null) { - core.close(this[_id]); - this[_id] = null; + const event = new MessageEvent("message", { + data: message, + ports: ArrayPrototypeFilter( + transferables, + (t) => ObjectPrototypeIsPrototypeOf(MessagePortPrototype, t), + ), + }); + this.dispatchEvent(event); } - } + this[_enabled] = false; + })(); } - defineEventHandler(MessagePort.prototype, "message", function (self) { - self.start(); - }); - defineEventHandler(MessagePort.prototype, "messageerror"); - - webidl.configurePrototype(MessagePort); - const MessagePortPrototype = MessagePort.prototype; - - /** - * @returns {[number, number]} - */ - function opCreateEntangledMessagePort() { - return ops.op_message_port_create_entangled(); + close() { + webidl.assertBranded(this, MessagePortPrototype); + if (this[_id] !== null) { + core.close(this[_id]); + this[_id] = null; + } } - - /** - * @param {globalThis.__bootstrap.messagePort.MessageData} messageData - * @returns {[any, object[]]} - */ - function deserializeJsMessageData(messageData) { - /** @type {object[]} */ - const transferables = []; - const hostObjects = []; - const arrayBufferIdsInTransferables = []; - const transferredArrayBuffers = []; - - for (let i = 0; i < messageData.transferables.length; ++i) { - const transferable = messageData.transferables[i]; - switch (transferable.kind) { - case "messagePort": { - const port = createMessagePort(transferable.data); - ArrayPrototypePush(transferables, port); - ArrayPrototypePush(hostObjects, port); - break; - } - case "arrayBuffer": { - ArrayPrototypePush(transferredArrayBuffers, transferable.data); - const index = ArrayPrototypePush(transferables, null); - ArrayPrototypePush(arrayBufferIdsInTransferables, index); - break; - } - default: - throw new TypeError("Unreachable"); +} + +defineEventHandler(MessagePort.prototype, "message", function (self) { + self.start(); +}); +defineEventHandler(MessagePort.prototype, "messageerror"); + +webidl.configurePrototype(MessagePort); +const MessagePortPrototype = MessagePort.prototype; + +/** + * @returns {[number, number]} + */ +function opCreateEntangledMessagePort() { + return ops.op_message_port_create_entangled(); +} + +/** + * @param {messagePort.MessageData} messageData + * @returns {[any, object[]]} + */ +function deserializeJsMessageData(messageData) { + /** @type {object[]} */ + const transferables = []; + const hostObjects = []; + const arrayBufferIdsInTransferables = []; + const transferredArrayBuffers = []; + + for (let i = 0; i < messageData.transferables.length; ++i) { + const transferable = messageData.transferables[i]; + switch (transferable.kind) { + case "messagePort": { + const port = createMessagePort(transferable.data); + ArrayPrototypePush(transferables, port); + ArrayPrototypePush(hostObjects, port); + break; + } + case "arrayBuffer": { + ArrayPrototypePush(transferredArrayBuffers, transferable.data); + const index = ArrayPrototypePush(transferables, null); + ArrayPrototypePush(arrayBufferIdsInTransferables, index); + break; } + default: + throw new TypeError("Unreachable"); } + } - const data = core.deserialize(messageData.data, { - hostObjects, - transferredArrayBuffers, - }); - - for (let i = 0; i < arrayBufferIdsInTransferables.length; ++i) { - const id = arrayBufferIdsInTransferables[i]; - transferables[id] = transferredArrayBuffers[i]; - } + const data = core.deserialize(messageData.data, { + hostObjects, + transferredArrayBuffers, + }); - return [data, transferables]; + for (let i = 0; i < arrayBufferIdsInTransferables.length; ++i) { + const id = arrayBufferIdsInTransferables[i]; + transferables[id] = transferredArrayBuffers[i]; } - /** - * @param {any} data - * @param {object[]} transferables - * @returns {globalThis.__bootstrap.messagePort.MessageData} - */ - function serializeJsMessageData(data, transferables) { - const transferredArrayBuffers = []; - for (let i = 0, j = 0; i < transferables.length; i++) { - const ab = transferables[i]; - if (ObjectPrototypeIsPrototypeOf(ArrayBufferPrototype, ab)) { - if (ab.byteLength === 0 && core.ops.op_arraybuffer_was_detached(ab)) { - throw new DOMException( - `ArrayBuffer at index ${j} is already detached`, - "DataCloneError", - ); - } - j++; - transferredArrayBuffers.push(ab); + return [data, transferables]; +} + +/** + * @param {any} data + * @param {object[]} transferables + * @returns {messagePort.MessageData} + */ +function serializeJsMessageData(data, transferables) { + const transferredArrayBuffers = []; + for (let i = 0, j = 0; i < transferables.length; i++) { + const ab = transferables[i]; + if (ObjectPrototypeIsPrototypeOf(ArrayBufferPrototype, ab)) { + if (ab.byteLength === 0 && ops.op_arraybuffer_was_detached(ab)) { + throw new DOMException( + `ArrayBuffer at index ${j} is already detached`, + "DataCloneError", + ); } + j++; + transferredArrayBuffers.push(ab); } + } - const serializedData = core.serialize(data, { - hostObjects: ArrayPrototypeFilter( - transferables, - (a) => ObjectPrototypeIsPrototypeOf(MessagePortPrototype, a), - ), - transferredArrayBuffers, - }, (err) => { - throw new DOMException(err, "DataCloneError"); - }); - - /** @type {globalThis.__bootstrap.messagePort.Transferable[]} */ - const serializedTransferables = []; + const serializedData = core.serialize(data, { + hostObjects: ArrayPrototypeFilter( + transferables, + (a) => ObjectPrototypeIsPrototypeOf(MessagePortPrototype, a), + ), + transferredArrayBuffers, + }, (err) => { + throw new DOMException(err, "DataCloneError"); + }); - let arrayBufferI = 0; - for (let i = 0; i < transferables.length; ++i) { - const transferable = transferables[i]; - if (ObjectPrototypeIsPrototypeOf(MessagePortPrototype, transferable)) { - webidl.assertBranded(transferable, MessagePortPrototype); - const id = transferable[_id]; - if (id === null) { - throw new DOMException( - "Can not transfer disentangled message port", - "DataCloneError", - ); - } - transferable[_id] = null; - ArrayPrototypePush(serializedTransferables, { - kind: "messagePort", - data: id, - }); - } else if ( - ObjectPrototypeIsPrototypeOf(ArrayBufferPrototype, transferable) - ) { - ArrayPrototypePush(serializedTransferables, { - kind: "arrayBuffer", - data: transferredArrayBuffers[arrayBufferI], - }); - arrayBufferI++; - } else { - throw new DOMException("Value not transferable", "DataCloneError"); + /** @type {messagePort.Transferable[]} */ + const serializedTransferables = []; + + let arrayBufferI = 0; + for (let i = 0; i < transferables.length; ++i) { + const transferable = transferables[i]; + if (ObjectPrototypeIsPrototypeOf(MessagePortPrototype, transferable)) { + webidl.assertBranded(transferable, MessagePortPrototype); + const id = transferable[_id]; + if (id === null) { + throw new DOMException( + "Can not transfer disentangled message port", + "DataCloneError", + ); } + transferable[_id] = null; + ArrayPrototypePush(serializedTransferables, { + kind: "messagePort", + data: id, + }); + } else if ( + ObjectPrototypeIsPrototypeOf(ArrayBufferPrototype, transferable) + ) { + ArrayPrototypePush(serializedTransferables, { + kind: "arrayBuffer", + data: transferredArrayBuffers[arrayBufferI], + }); + arrayBufferI++; + } else { + throw new DOMException("Value not transferable", "DataCloneError"); } - - return { - data: serializedData, - transferables: serializedTransferables, - }; } - webidl.converters.StructuredSerializeOptions = webidl - .createDictionaryConverter( - "StructuredSerializeOptions", - [ - { - key: "transfer", - converter: webidl.converters["sequence"], - get defaultValue() { - return []; - }, - }, - ], - ); - - function structuredClone(value, options) { - const prefix = "Failed to execute 'structuredClone'"; - webidl.requiredArguments(arguments.length, 1, { prefix }); - options = webidl.converters.StructuredSerializeOptions(options, { - prefix, - context: "Argument 2", - }); - const messageData = serializeJsMessageData(value, options.transfer); - return deserializeJsMessageData(messageData)[0]; - } - - window.__bootstrap.messagePort = { - MessageChannel, - MessagePort, - MessagePortPrototype, - deserializeJsMessageData, - serializeJsMessageData, - structuredClone, + return { + data: serializedData, + transferables: serializedTransferables, }; -})(globalThis); +} + +webidl.converters.StructuredSerializeOptions = webidl + .createDictionaryConverter( + "StructuredSerializeOptions", + [ + { + key: "transfer", + converter: webidl.converters["sequence"], + get defaultValue() { + return []; + }, + }, + ], + ); + +function structuredClone(value, options) { + const prefix = "Failed to execute 'structuredClone'"; + webidl.requiredArguments(arguments.length, 1, { prefix }); + options = webidl.converters.StructuredSerializeOptions(options, { + prefix, + context: "Argument 2", + }); + const messageData = serializeJsMessageData(value, options.transfer); + return deserializeJsMessageData(messageData)[0]; +} + +export { + deserializeJsMessageData, + MessageChannel, + MessagePort, + MessagePortPrototype, + serializeJsMessageData, + structuredClone, +}; diff --git a/ext/web/14_compression.js b/ext/web/14_compression.js index 338f8c803453fa..3998709cec4a7d 100644 --- a/ext/web/14_compression.js +++ b/ext/web/14_compression.js @@ -5,127 +5,120 @@ /// /// -"use strict"; - -((window) => { - const core = window.Deno.core; - const ops = core.ops; - const webidl = window.__bootstrap.webidl; - const { TransformStream } = window.__bootstrap.streams; - - webidl.converters.CompressionFormat = webidl.createEnumConverter( - "CompressionFormat", - [ - "deflate", - "deflate-raw", - "gzip", - ], - ); - - class CompressionStream { - #transform; - - constructor(format) { - const prefix = "Failed to construct 'CompressionStream'"; - webidl.requiredArguments(arguments.length, 1, { prefix }); - format = webidl.converters.CompressionFormat(format, { - prefix, - context: "Argument 1", - }); - - const rid = ops.op_compression_new(format, false); - - this.#transform = new TransformStream({ - transform(chunk, controller) { - chunk = webidl.converters.BufferSource(chunk, { - prefix, - context: "chunk", - }); - const output = ops.op_compression_write( - rid, - chunk, - ); - maybeEnqueue(controller, output); - }, - flush(controller) { - const output = ops.op_compression_finish(rid); - maybeEnqueue(controller, output); - }, - }); - - this[webidl.brand] = webidl.brand; - } - - get readable() { - webidl.assertBranded(this, CompressionStreamPrototype); - return this.#transform.readable; - } - - get writable() { - webidl.assertBranded(this, CompressionStreamPrototype); - return this.#transform.writable; - } +const core = globalThis.Deno.core; +const ops = core.ops; +import * as webidl from "internal:deno_webidl/00_webidl.js"; +import { TransformStream } from "internal:deno_web/06_streams.js"; + +webidl.converters.CompressionFormat = webidl.createEnumConverter( + "CompressionFormat", + [ + "deflate", + "deflate-raw", + "gzip", + ], +); + +class CompressionStream { + #transform; + + constructor(format) { + const prefix = "Failed to construct 'CompressionStream'"; + webidl.requiredArguments(arguments.length, 1, { prefix }); + format = webidl.converters.CompressionFormat(format, { + prefix, + context: "Argument 1", + }); + + const rid = ops.op_compression_new(format, false); + + this.#transform = new TransformStream({ + transform(chunk, controller) { + chunk = webidl.converters.BufferSource(chunk, { + prefix, + context: "chunk", + }); + const output = ops.op_compression_write( + rid, + chunk, + ); + maybeEnqueue(controller, output); + }, + flush(controller) { + const output = ops.op_compression_finish(rid); + maybeEnqueue(controller, output); + }, + }); + + this[webidl.brand] = webidl.brand; } - webidl.configurePrototype(CompressionStream); - const CompressionStreamPrototype = CompressionStream.prototype; - - class DecompressionStream { - #transform; - - constructor(format) { - const prefix = "Failed to construct 'DecompressionStream'"; - webidl.requiredArguments(arguments.length, 1, { prefix }); - format = webidl.converters.CompressionFormat(format, { - prefix, - context: "Argument 1", - }); - - const rid = ops.op_compression_new(format, true); - - this.#transform = new TransformStream({ - transform(chunk, controller) { - chunk = webidl.converters.BufferSource(chunk, { - prefix, - context: "chunk", - }); - const output = ops.op_compression_write( - rid, - chunk, - ); - maybeEnqueue(controller, output); - }, - flush(controller) { - const output = ops.op_compression_finish(rid); - maybeEnqueue(controller, output); - }, - }); - - this[webidl.brand] = webidl.brand; - } - - get readable() { - webidl.assertBranded(this, DecompressionStreamPrototype); - return this.#transform.readable; - } - - get writable() { - webidl.assertBranded(this, DecompressionStreamPrototype); - return this.#transform.writable; - } + get readable() { + webidl.assertBranded(this, CompressionStreamPrototype); + return this.#transform.readable; } - function maybeEnqueue(controller, output) { - if (output && output.byteLength > 0) { - controller.enqueue(output); - } + get writable() { + webidl.assertBranded(this, CompressionStreamPrototype); + return this.#transform.writable; } +} + +webidl.configurePrototype(CompressionStream); +const CompressionStreamPrototype = CompressionStream.prototype; + +class DecompressionStream { + #transform; + + constructor(format) { + const prefix = "Failed to construct 'DecompressionStream'"; + webidl.requiredArguments(arguments.length, 1, { prefix }); + format = webidl.converters.CompressionFormat(format, { + prefix, + context: "Argument 1", + }); + + const rid = ops.op_compression_new(format, true); + + this.#transform = new TransformStream({ + transform(chunk, controller) { + chunk = webidl.converters.BufferSource(chunk, { + prefix, + context: "chunk", + }); + const output = ops.op_compression_write( + rid, + chunk, + ); + maybeEnqueue(controller, output); + }, + flush(controller) { + const output = ops.op_compression_finish(rid); + maybeEnqueue(controller, output); + }, + }); + + this[webidl.brand] = webidl.brand; + } + + get readable() { + webidl.assertBranded(this, DecompressionStreamPrototype); + return this.#transform.readable; + } + + get writable() { + webidl.assertBranded(this, DecompressionStreamPrototype); + return this.#transform.writable; + } +} + +function maybeEnqueue(controller, output) { + if (output && output.byteLength > 0) { + controller.enqueue(output); + } +} - webidl.configurePrototype(DecompressionStream); - const DecompressionStreamPrototype = DecompressionStream.prototype; +webidl.configurePrototype(DecompressionStream); +const DecompressionStreamPrototype = DecompressionStream.prototype; - window.__bootstrap.compression = { - CompressionStream, - DecompressionStream, - }; -})(globalThis); +export { CompressionStream, DecompressionStream }; diff --git a/ext/web/15_performance.js b/ext/web/15_performance.js index 9107ce75b84ca7..b1c4eb513c31cc 100644 --- a/ext/web/15_performance.js +++ b/ext/web/15_performance.js @@ -1,594 +1,594 @@ // Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. -"use strict"; - -((window) => { - const { - ArrayPrototypeFilter, - ArrayPrototypeFind, - ArrayPrototypePush, - ArrayPrototypeReverse, - ArrayPrototypeSlice, - ObjectKeys, - ObjectPrototypeIsPrototypeOf, - ReflectHas, - Symbol, - SymbolFor, - TypeError, - } = window.__bootstrap.primordials; - - const { webidl, structuredClone } = window.__bootstrap; - const consoleInternal = window.__bootstrap.console; - const { EventTarget } = window.__bootstrap.eventTarget; - const { opNow } = window.__bootstrap.timers; - const { DOMException } = window.__bootstrap.domException; - - const illegalConstructorKey = Symbol("illegalConstructorKey"); - const customInspect = SymbolFor("Deno.customInspect"); - let performanceEntries = []; - let timeOrigin; - - webidl.converters["PerformanceMarkOptions"] = webidl - .createDictionaryConverter( - "PerformanceMarkOptions", - [ - { - key: "detail", - converter: webidl.converters.any, - }, - { - key: "startTime", - converter: webidl.converters.DOMHighResTimeStamp, - }, - ], - ); - - webidl.converters["DOMString or DOMHighResTimeStamp"] = (V, opts) => { - if (webidl.type(V) === "Number" && V !== null) { - return webidl.converters.DOMHighResTimeStamp(V, opts); - } - return webidl.converters.DOMString(V, opts); - }; - - webidl.converters["PerformanceMeasureOptions"] = webidl - .createDictionaryConverter( - "PerformanceMeasureOptions", - [ - { - key: "detail", - converter: webidl.converters.any, - }, - { - key: "start", - converter: webidl.converters["DOMString or DOMHighResTimeStamp"], - }, - { - key: "duration", - converter: webidl.converters.DOMHighResTimeStamp, - }, - { - key: "end", - converter: webidl.converters["DOMString or DOMHighResTimeStamp"], - }, - ], - ); - webidl.converters["DOMString or PerformanceMeasureOptions"] = (V, opts) => { - if (webidl.type(V) === "Object" && V !== null) { - return webidl.converters["PerformanceMeasureOptions"](V, opts); - } - return webidl.converters.DOMString(V, opts); - }; +const primordials = globalThis.__bootstrap.primordials; +const { + ArrayPrototypeFilter, + ArrayPrototypeFind, + ArrayPrototypePush, + ArrayPrototypeReverse, + ArrayPrototypeSlice, + ObjectKeys, + ObjectPrototypeIsPrototypeOf, + ReflectHas, + Symbol, + SymbolFor, + TypeError, +} = primordials; +import * as webidl from "internal:deno_webidl/00_webidl.js"; +import { structuredClone } from "internal:deno_web/02_structured_clone.js"; +import { createFilteredInspectProxy } from "internal:deno_console/02_console.js"; +import { EventTarget } from "internal:deno_web/02_event.js"; +import { opNow } from "internal:deno_web/02_timers.js"; +import DOMException from "internal:deno_web/01_dom_exception.js"; + +const illegalConstructorKey = Symbol("illegalConstructorKey"); +const customInspect = SymbolFor("Deno.customInspect"); +let performanceEntries = []; +let timeOrigin; + +webidl.converters["PerformanceMarkOptions"] = webidl + .createDictionaryConverter( + "PerformanceMarkOptions", + [ + { + key: "detail", + converter: webidl.converters.any, + }, + { + key: "startTime", + converter: webidl.converters.DOMHighResTimeStamp, + }, + ], + ); - function setTimeOrigin(origin) { - timeOrigin = origin; +webidl.converters["DOMString or DOMHighResTimeStamp"] = (V, opts) => { + if (webidl.type(V) === "Number" && V !== null) { + return webidl.converters.DOMHighResTimeStamp(V, opts); } + return webidl.converters.DOMString(V, opts); +}; + +webidl.converters["PerformanceMeasureOptions"] = webidl + .createDictionaryConverter( + "PerformanceMeasureOptions", + [ + { + key: "detail", + converter: webidl.converters.any, + }, + { + key: "start", + converter: webidl.converters["DOMString or DOMHighResTimeStamp"], + }, + { + key: "duration", + converter: webidl.converters.DOMHighResTimeStamp, + }, + { + key: "end", + converter: webidl.converters["DOMString or DOMHighResTimeStamp"], + }, + ], + ); - function findMostRecent( - name, - type, - ) { - return ArrayPrototypeFind( - ArrayPrototypeReverse(ArrayPrototypeSlice(performanceEntries)), - (entry) => entry.name === name && entry.entryType === type, - ); +webidl.converters["DOMString or PerformanceMeasureOptions"] = (V, opts) => { + if (webidl.type(V) === "Object" && V !== null) { + return webidl.converters["PerformanceMeasureOptions"](V, opts); } - - function convertMarkToTimestamp(mark) { - if (typeof mark === "string") { - const entry = findMostRecent(mark, "mark"); - if (!entry) { - throw new DOMException( - `Cannot find mark: "${mark}".`, - "SyntaxError", - ); - } - return entry.startTime; - } - if (mark < 0) { - throw new TypeError("Mark cannot be negative."); + return webidl.converters.DOMString(V, opts); +}; + +function setTimeOrigin(origin) { + timeOrigin = origin; +} + +function findMostRecent( + name, + type, +) { + return ArrayPrototypeFind( + ArrayPrototypeReverse(ArrayPrototypeSlice(performanceEntries)), + (entry) => entry.name === name && entry.entryType === type, + ); +} + +function convertMarkToTimestamp(mark) { + if (typeof mark === "string") { + const entry = findMostRecent(mark, "mark"); + if (!entry) { + throw new DOMException( + `Cannot find mark: "${mark}".`, + "SyntaxError", + ); } - return mark; + return entry.startTime; } - - function filterByNameType( - name, - type, - ) { - return ArrayPrototypeFilter( - performanceEntries, - (entry) => - (name ? entry.name === name : true) && - (type ? entry.entryType === type : true), - ); + if (mark < 0) { + throw new TypeError("Mark cannot be negative."); + } + return mark; +} + +function filterByNameType( + name, + type, +) { + return ArrayPrototypeFilter( + performanceEntries, + (entry) => + (name ? entry.name === name : true) && + (type ? entry.entryType === type : true), + ); +} + +const now = opNow; + +const _name = Symbol("[[name]]"); +const _entryType = Symbol("[[entryType]]"); +const _startTime = Symbol("[[startTime]]"); +const _duration = Symbol("[[duration]]"); +class PerformanceEntry { + [_name] = ""; + [_entryType] = ""; + [_startTime] = 0; + [_duration] = 0; + + get name() { + webidl.assertBranded(this, PerformanceEntryPrototype); + return this[_name]; } - const now = opNow; + get entryType() { + webidl.assertBranded(this, PerformanceEntryPrototype); + return this[_entryType]; + } - const _name = Symbol("[[name]]"); - const _entryType = Symbol("[[entryType]]"); - const _startTime = Symbol("[[startTime]]"); - const _duration = Symbol("[[duration]]"); - class PerformanceEntry { - [_name] = ""; - [_entryType] = ""; - [_startTime] = 0; - [_duration] = 0; + get startTime() { + webidl.assertBranded(this, PerformanceEntryPrototype); + return this[_startTime]; + } - get name() { - webidl.assertBranded(this, PerformanceEntryPrototype); - return this[_name]; - } + get duration() { + webidl.assertBranded(this, PerformanceEntryPrototype); + return this[_duration]; + } - get entryType() { - webidl.assertBranded(this, PerformanceEntryPrototype); - return this[_entryType]; + constructor( + name = null, + entryType = null, + startTime = null, + duration = null, + key = undefined, + ) { + if (key !== illegalConstructorKey) { + webidl.illegalConstructor(); } + this[webidl.brand] = webidl.brand; - get startTime() { - webidl.assertBranded(this, PerformanceEntryPrototype); - return this[_startTime]; - } + this[_name] = name; + this[_entryType] = entryType; + this[_startTime] = startTime; + this[_duration] = duration; + } - get duration() { - webidl.assertBranded(this, PerformanceEntryPrototype); - return this[_duration]; - } + toJSON() { + webidl.assertBranded(this, PerformanceEntryPrototype); + return { + name: this[_name], + entryType: this[_entryType], + startTime: this[_startTime], + duration: this[_duration], + }; + } - constructor( - name = null, - entryType = null, - startTime = null, - duration = null, - key = undefined, - ) { - if (key !== illegalConstructorKey) { - webidl.illegalConstructor(); - } - this[webidl.brand] = webidl.brand; + [customInspect](inspect) { + return inspect(createFilteredInspectProxy({ + object: this, + evaluate: ObjectPrototypeIsPrototypeOf( + PerformanceEntryPrototype, + this, + ), + keys: [ + "name", + "entryType", + "startTime", + "duration", + ], + })); + } +} +webidl.configurePrototype(PerformanceEntry); +const PerformanceEntryPrototype = PerformanceEntry.prototype; - this[_name] = name; - this[_entryType] = entryType; - this[_startTime] = startTime; - this[_duration] = duration; - } +const _detail = Symbol("[[detail]]"); +class PerformanceMark extends PerformanceEntry { + [_detail] = null; - toJSON() { - webidl.assertBranded(this, PerformanceEntryPrototype); - return { - name: this[_name], - entryType: this[_entryType], - startTime: this[_startTime], - duration: this[_duration], - }; - } + get detail() { + webidl.assertBranded(this, PerformanceMarkPrototype); + return this[_detail]; + } - [customInspect](inspect) { - return inspect(consoleInternal.createFilteredInspectProxy({ - object: this, - evaluate: ObjectPrototypeIsPrototypeOf( - PerformanceEntryPrototype, - this, - ), - keys: [ - "name", - "entryType", - "startTime", - "duration", - ], - })); - } + get entryType() { + webidl.assertBranded(this, PerformanceMarkPrototype); + return "mark"; } - webidl.configurePrototype(PerformanceEntry); - const PerformanceEntryPrototype = PerformanceEntry.prototype; - const _detail = Symbol("[[detail]]"); - class PerformanceMark extends PerformanceEntry { - [_detail] = null; + constructor( + name, + options = {}, + ) { + const prefix = "Failed to construct 'PerformanceMark'"; + webidl.requiredArguments(arguments.length, 1, { prefix }); - get detail() { - webidl.assertBranded(this, PerformanceMarkPrototype); - return this[_detail]; - } + name = webidl.converters.DOMString(name, { + prefix, + context: "Argument 1", + }); - get entryType() { - webidl.assertBranded(this, PerformanceMarkPrototype); - return "mark"; - } + options = webidl.converters.PerformanceMarkOptions(options, { + prefix, + context: "Argument 2", + }); - constructor( - name, - options = {}, - ) { - const prefix = "Failed to construct 'PerformanceMark'"; - webidl.requiredArguments(arguments.length, 1, { prefix }); + const { detail = null, startTime = now() } = options; - name = webidl.converters.DOMString(name, { - prefix, - context: "Argument 1", - }); + super(name, "mark", startTime, 0, illegalConstructorKey); + this[webidl.brand] = webidl.brand; + if (startTime < 0) { + throw new TypeError("startTime cannot be negative"); + } + this[_detail] = structuredClone(detail); + } - options = webidl.converters.PerformanceMarkOptions(options, { - prefix, - context: "Argument 2", - }); + toJSON() { + webidl.assertBranded(this, PerformanceMarkPrototype); + return { + name: this.name, + entryType: this.entryType, + startTime: this.startTime, + duration: this.duration, + detail: this.detail, + }; + } - const { detail = null, startTime = now() } = options; + [customInspect](inspect) { + return inspect(createFilteredInspectProxy({ + object: this, + evaluate: ObjectPrototypeIsPrototypeOf(PerformanceMarkPrototype, this), + keys: [ + "name", + "entryType", + "startTime", + "duration", + "detail", + ], + })); + } +} +webidl.configurePrototype(PerformanceMark); +const PerformanceMarkPrototype = PerformanceMark.prototype; +class PerformanceMeasure extends PerformanceEntry { + [_detail] = null; + + get detail() { + webidl.assertBranded(this, PerformanceMeasurePrototype); + return this[_detail]; + } - super(name, "mark", startTime, 0, illegalConstructorKey); - this[webidl.brand] = webidl.brand; - if (startTime < 0) { - throw new TypeError("startTime cannot be negative"); - } - this[_detail] = structuredClone(detail); - } + get entryType() { + webidl.assertBranded(this, PerformanceMeasurePrototype); + return "measure"; + } - toJSON() { - webidl.assertBranded(this, PerformanceMarkPrototype); - return { - name: this.name, - entryType: this.entryType, - startTime: this.startTime, - duration: this.duration, - detail: this.detail, - }; + constructor( + name = null, + startTime = null, + duration = null, + detail = null, + key = undefined, + ) { + if (key !== illegalConstructorKey) { + webidl.illegalConstructor(); } - [customInspect](inspect) { - return inspect(consoleInternal.createFilteredInspectProxy({ - object: this, - evaluate: ObjectPrototypeIsPrototypeOf(PerformanceMarkPrototype, this), - keys: [ - "name", - "entryType", - "startTime", - "duration", - "detail", - ], - })); - } + super(name, "measure", startTime, duration, key); + this[webidl.brand] = webidl.brand; + this[_detail] = structuredClone(detail); } - webidl.configurePrototype(PerformanceMark); - const PerformanceMarkPrototype = PerformanceMark.prototype; - class PerformanceMeasure extends PerformanceEntry { - [_detail] = null; - get detail() { - webidl.assertBranded(this, PerformanceMeasurePrototype); - return this[_detail]; - } + toJSON() { + webidl.assertBranded(this, PerformanceMeasurePrototype); + return { + name: this.name, + entryType: this.entryType, + startTime: this.startTime, + duration: this.duration, + detail: this.detail, + }; + } - get entryType() { - webidl.assertBranded(this, PerformanceMeasurePrototype); - return "measure"; + [customInspect](inspect) { + return inspect(createFilteredInspectProxy({ + object: this, + evaluate: ObjectPrototypeIsPrototypeOf( + PerformanceMeasurePrototype, + this, + ), + keys: [ + "name", + "entryType", + "startTime", + "duration", + "detail", + ], + })); + } +} +webidl.configurePrototype(PerformanceMeasure); +const PerformanceMeasurePrototype = PerformanceMeasure.prototype; +class Performance extends EventTarget { + constructor(key = null) { + if (key != illegalConstructorKey) { + webidl.illegalConstructor(); } - constructor( - name = null, - startTime = null, - duration = null, - detail = null, - key = undefined, - ) { - if (key !== illegalConstructorKey) { - webidl.illegalConstructor(); - } + super(); + this[webidl.brand] = webidl.brand; + } - super(name, "measure", startTime, duration, key); - this[webidl.brand] = webidl.brand; - this[_detail] = structuredClone(detail); - } + get timeOrigin() { + webidl.assertBranded(this, PerformancePrototype); + return timeOrigin; + } - toJSON() { - webidl.assertBranded(this, PerformanceMeasurePrototype); - return { - name: this.name, - entryType: this.entryType, - startTime: this.startTime, - duration: this.duration, - detail: this.detail, - }; - } + clearMarks(markName = undefined) { + webidl.assertBranded(this, PerformancePrototype); + if (markName !== undefined) { + markName = webidl.converters.DOMString(markName, { + prefix: "Failed to execute 'clearMarks' on 'Performance'", + context: "Argument 1", + }); - [customInspect](inspect) { - return inspect(consoleInternal.createFilteredInspectProxy({ - object: this, - evaluate: ObjectPrototypeIsPrototypeOf( - PerformanceMeasurePrototype, - this, - ), - keys: [ - "name", - "entryType", - "startTime", - "duration", - "detail", - ], - })); + performanceEntries = ArrayPrototypeFilter( + performanceEntries, + (entry) => !(entry.name === markName && entry.entryType === "mark"), + ); + } else { + performanceEntries = ArrayPrototypeFilter( + performanceEntries, + (entry) => entry.entryType !== "mark", + ); } } - webidl.configurePrototype(PerformanceMeasure); - const PerformanceMeasurePrototype = PerformanceMeasure.prototype; - class Performance extends EventTarget { - constructor(key = null) { - if (key != illegalConstructorKey) { - webidl.illegalConstructor(); - } - super(); - this[webidl.brand] = webidl.brand; - } - - get timeOrigin() { - webidl.assertBranded(this, PerformancePrototype); - return timeOrigin; - } + clearMeasures(measureName = undefined) { + webidl.assertBranded(this, PerformancePrototype); + if (measureName !== undefined) { + measureName = webidl.converters.DOMString(measureName, { + prefix: "Failed to execute 'clearMeasures' on 'Performance'", + context: "Argument 1", + }); - clearMarks(markName = undefined) { - webidl.assertBranded(this, PerformancePrototype); - if (markName !== undefined) { - markName = webidl.converters.DOMString(markName, { - prefix: "Failed to execute 'clearMarks' on 'Performance'", - context: "Argument 1", - }); - - performanceEntries = ArrayPrototypeFilter( - performanceEntries, - (entry) => !(entry.name === markName && entry.entryType === "mark"), - ); - } else { - performanceEntries = ArrayPrototypeFilter( - performanceEntries, - (entry) => entry.entryType !== "mark", - ); - } + performanceEntries = ArrayPrototypeFilter( + performanceEntries, + (entry) => + !(entry.name === measureName && entry.entryType === "measure"), + ); + } else { + performanceEntries = ArrayPrototypeFilter( + performanceEntries, + (entry) => entry.entryType !== "measure", + ); } + } - clearMeasures(measureName = undefined) { - webidl.assertBranded(this, PerformancePrototype); - if (measureName !== undefined) { - measureName = webidl.converters.DOMString(measureName, { - prefix: "Failed to execute 'clearMeasures' on 'Performance'", - context: "Argument 1", - }); - - performanceEntries = ArrayPrototypeFilter( - performanceEntries, - (entry) => - !(entry.name === measureName && entry.entryType === "measure"), - ); - } else { - performanceEntries = ArrayPrototypeFilter( - performanceEntries, - (entry) => entry.entryType !== "measure", - ); - } - } + getEntries() { + webidl.assertBranded(this, PerformancePrototype); + return filterByNameType(); + } - getEntries() { - webidl.assertBranded(this, PerformancePrototype); - return filterByNameType(); - } + getEntriesByName( + name, + type = undefined, + ) { + webidl.assertBranded(this, PerformancePrototype); + const prefix = "Failed to execute 'getEntriesByName' on 'Performance'"; + webidl.requiredArguments(arguments.length, 1, { prefix }); - getEntriesByName( - name, - type = undefined, - ) { - webidl.assertBranded(this, PerformancePrototype); - const prefix = "Failed to execute 'getEntriesByName' on 'Performance'"; - webidl.requiredArguments(arguments.length, 1, { prefix }); + name = webidl.converters.DOMString(name, { + prefix, + context: "Argument 1", + }); - name = webidl.converters.DOMString(name, { + if (type !== undefined) { + type = webidl.converters.DOMString(type, { prefix, - context: "Argument 1", + context: "Argument 2", }); + } - if (type !== undefined) { - type = webidl.converters.DOMString(type, { - prefix, - context: "Argument 2", - }); - } + return filterByNameType(name, type); + } - return filterByNameType(name, type); - } + getEntriesByType(type) { + webidl.assertBranded(this, PerformancePrototype); + const prefix = "Failed to execute 'getEntriesByName' on 'Performance'"; + webidl.requiredArguments(arguments.length, 1, { prefix }); - getEntriesByType(type) { - webidl.assertBranded(this, PerformancePrototype); - const prefix = "Failed to execute 'getEntriesByName' on 'Performance'"; - webidl.requiredArguments(arguments.length, 1, { prefix }); + type = webidl.converters.DOMString(type, { + prefix, + context: "Argument 1", + }); - type = webidl.converters.DOMString(type, { - prefix, - context: "Argument 1", - }); + return filterByNameType(undefined, type); + } - return filterByNameType(undefined, type); - } + mark( + markName, + markOptions = {}, + ) { + webidl.assertBranded(this, PerformancePrototype); + const prefix = "Failed to execute 'mark' on 'Performance'"; + webidl.requiredArguments(arguments.length, 1, { prefix }); + + markName = webidl.converters.DOMString(markName, { + prefix, + context: "Argument 1", + }); + + markOptions = webidl.converters.PerformanceMarkOptions(markOptions, { + prefix, + context: "Argument 2", + }); + + // 3.1.1.1 If the global object is a Window object and markName uses the + // same name as a read only attribute in the PerformanceTiming interface, + // throw a SyntaxError. - not implemented + const entry = new PerformanceMark(markName, markOptions); + // 3.1.1.7 Queue entry - not implemented + ArrayPrototypePush(performanceEntries, entry); + return entry; + } - mark( - markName, - markOptions = {}, - ) { - webidl.assertBranded(this, PerformancePrototype); - const prefix = "Failed to execute 'mark' on 'Performance'"; - webidl.requiredArguments(arguments.length, 1, { prefix }); + measure( + measureName, + startOrMeasureOptions = {}, + endMark = undefined, + ) { + webidl.assertBranded(this, PerformancePrototype); + const prefix = "Failed to execute 'measure' on 'Performance'"; + webidl.requiredArguments(arguments.length, 1, { prefix }); - markName = webidl.converters.DOMString(markName, { - prefix, - context: "Argument 1", - }); + measureName = webidl.converters.DOMString(measureName, { + prefix, + context: "Argument 1", + }); - markOptions = webidl.converters.PerformanceMarkOptions(markOptions, { + startOrMeasureOptions = webidl.converters + ["DOMString or PerformanceMeasureOptions"](startOrMeasureOptions, { prefix, context: "Argument 2", }); - // 3.1.1.1 If the global object is a Window object and markName uses the - // same name as a read only attribute in the PerformanceTiming interface, - // throw a SyntaxError. - not implemented - const entry = new PerformanceMark(markName, markOptions); - // 3.1.1.7 Queue entry - not implemented - ArrayPrototypePush(performanceEntries, entry); - return entry; - } - - measure( - measureName, - startOrMeasureOptions = {}, - endMark = undefined, - ) { - webidl.assertBranded(this, PerformancePrototype); - const prefix = "Failed to execute 'measure' on 'Performance'"; - webidl.requiredArguments(arguments.length, 1, { prefix }); - - measureName = webidl.converters.DOMString(measureName, { + if (endMark !== undefined) { + endMark = webidl.converters.DOMString(endMark, { prefix, - context: "Argument 1", + context: "Argument 3", }); + } - startOrMeasureOptions = webidl.converters - ["DOMString or PerformanceMeasureOptions"](startOrMeasureOptions, { - prefix, - context: "Argument 2", - }); - - if (endMark !== undefined) { - endMark = webidl.converters.DOMString(endMark, { - prefix, - context: "Argument 3", - }); + if ( + startOrMeasureOptions && typeof startOrMeasureOptions === "object" && + ObjectKeys(startOrMeasureOptions).length > 0 + ) { + if (endMark) { + throw new TypeError("Options cannot be passed with endMark."); } - if ( - startOrMeasureOptions && typeof startOrMeasureOptions === "object" && - ObjectKeys(startOrMeasureOptions).length > 0 - ) { - if (endMark) { - throw new TypeError("Options cannot be passed with endMark."); - } - if ( - !ReflectHas(startOrMeasureOptions, "start") && - !ReflectHas(startOrMeasureOptions, "end") - ) { - throw new TypeError( - "A start or end mark must be supplied in options.", - ); - } - if ( - ReflectHas(startOrMeasureOptions, "start") && - ReflectHas(startOrMeasureOptions, "duration") && - ReflectHas(startOrMeasureOptions, "end") - ) { - throw new TypeError( - "Cannot specify start, end, and duration together in options.", - ); - } - } - let endTime; - if (endMark) { - endTime = convertMarkToTimestamp(endMark); - } else if ( - typeof startOrMeasureOptions === "object" && - ReflectHas(startOrMeasureOptions, "end") - ) { - endTime = convertMarkToTimestamp(startOrMeasureOptions.end); - } else if ( - typeof startOrMeasureOptions === "object" && - ReflectHas(startOrMeasureOptions, "start") && - ReflectHas(startOrMeasureOptions, "duration") + !ReflectHas(startOrMeasureOptions, "start") && + !ReflectHas(startOrMeasureOptions, "end") ) { - const start = convertMarkToTimestamp(startOrMeasureOptions.start); - const duration = convertMarkToTimestamp(startOrMeasureOptions.duration); - endTime = start + duration; - } else { - endTime = now(); + throw new TypeError( + "A start or end mark must be supplied in options.", + ); } - let startTime; if ( - typeof startOrMeasureOptions === "object" && - ReflectHas(startOrMeasureOptions, "start") - ) { - startTime = convertMarkToTimestamp(startOrMeasureOptions.start); - } else if ( - typeof startOrMeasureOptions === "object" && - ReflectHas(startOrMeasureOptions, "end") && - ReflectHas(startOrMeasureOptions, "duration") + ReflectHas(startOrMeasureOptions, "start") && + ReflectHas(startOrMeasureOptions, "duration") && + ReflectHas(startOrMeasureOptions, "end") ) { - const end = convertMarkToTimestamp(startOrMeasureOptions.end); - const duration = convertMarkToTimestamp(startOrMeasureOptions.duration); - startTime = end - duration; - } else if (typeof startOrMeasureOptions === "string") { - startTime = convertMarkToTimestamp(startOrMeasureOptions); - } else { - startTime = 0; + throw new TypeError( + "Cannot specify start, end, and duration together in options.", + ); } - const entry = new PerformanceMeasure( - measureName, - startTime, - endTime - startTime, - typeof startOrMeasureOptions === "object" - ? startOrMeasureOptions.detail ?? null - : null, - illegalConstructorKey, - ); - ArrayPrototypePush(performanceEntries, entry); - return entry; - } - - now() { - webidl.assertBranded(this, PerformancePrototype); - return now(); - } - - toJSON() { - webidl.assertBranded(this, PerformancePrototype); - return { - timeOrigin: this.timeOrigin, - }; } + let endTime; + if (endMark) { + endTime = convertMarkToTimestamp(endMark); + } else if ( + typeof startOrMeasureOptions === "object" && + ReflectHas(startOrMeasureOptions, "end") + ) { + endTime = convertMarkToTimestamp(startOrMeasureOptions.end); + } else if ( + typeof startOrMeasureOptions === "object" && + ReflectHas(startOrMeasureOptions, "start") && + ReflectHas(startOrMeasureOptions, "duration") + ) { + const start = convertMarkToTimestamp(startOrMeasureOptions.start); + const duration = convertMarkToTimestamp(startOrMeasureOptions.duration); + endTime = start + duration; + } else { + endTime = now(); + } + let startTime; + if ( + typeof startOrMeasureOptions === "object" && + ReflectHas(startOrMeasureOptions, "start") + ) { + startTime = convertMarkToTimestamp(startOrMeasureOptions.start); + } else if ( + typeof startOrMeasureOptions === "object" && + ReflectHas(startOrMeasureOptions, "end") && + ReflectHas(startOrMeasureOptions, "duration") + ) { + const end = convertMarkToTimestamp(startOrMeasureOptions.end); + const duration = convertMarkToTimestamp(startOrMeasureOptions.duration); + startTime = end - duration; + } else if (typeof startOrMeasureOptions === "string") { + startTime = convertMarkToTimestamp(startOrMeasureOptions); + } else { + startTime = 0; + } + const entry = new PerformanceMeasure( + measureName, + startTime, + endTime - startTime, + typeof startOrMeasureOptions === "object" + ? startOrMeasureOptions.detail ?? null + : null, + illegalConstructorKey, + ); + ArrayPrototypePush(performanceEntries, entry); + return entry; + } - [customInspect](inspect) { - return inspect(consoleInternal.createFilteredInspectProxy({ - object: this, - evaluate: ObjectPrototypeIsPrototypeOf(PerformancePrototype, this), - keys: [], - })); - } + now() { + webidl.assertBranded(this, PerformancePrototype); + return now(); } - webidl.configurePrototype(Performance); - const PerformancePrototype = Performance.prototype; - webidl.converters["Performance"] = webidl.createInterfaceConverter( - "Performance", - PerformancePrototype, - ); + toJSON() { + webidl.assertBranded(this, PerformancePrototype); + return { + timeOrigin: this.timeOrigin, + }; + } - window.__bootstrap.performance = { - PerformanceEntry, - PerformanceMark, - PerformanceMeasure, - Performance, - performance: new Performance(illegalConstructorKey), - setTimeOrigin, - }; -})(this); + [customInspect](inspect) { + return inspect(createFilteredInspectProxy({ + object: this, + evaluate: ObjectPrototypeIsPrototypeOf(PerformancePrototype, this), + keys: [], + })); + } +} +webidl.configurePrototype(Performance); +const PerformancePrototype = Performance.prototype; + +webidl.converters["Performance"] = webidl.createInterfaceConverter( + "Performance", + PerformancePrototype, +); + +const performance = new Performance(illegalConstructorKey); + +export { + Performance, + performance, + PerformanceEntry, + PerformanceMark, + PerformanceMeasure, + setTimeOrigin, +}; diff --git a/ext/web/Cargo.toml b/ext/web/Cargo.toml index 863b3f597d893e..2e442530f7fa40 100644 --- a/ext/web/Cargo.toml +++ b/ext/web/Cargo.toml @@ -2,7 +2,7 @@ [package] name = "deno_web" -version = "0.120.0" +version = "0.122.0" authors.workspace = true edition.workspace = true license.workspace = true diff --git a/ext/web/benches/encoding.rs b/ext/web/benches/encoding.rs index 254ea4455a05da..bfae079371f404 100644 --- a/ext/web/benches/encoding.rs +++ b/ext/web/benches/encoding.rs @@ -5,6 +5,8 @@ use deno_bench_util::bench_or_profile; use deno_bench_util::bencher::benchmark_group; use deno_bench_util::bencher::Bencher; use deno_core::Extension; +use deno_core::ExtensionFileSource; +use deno_core::ExtensionFileSourceCode; use deno_web::BlobStore; struct Permissions; @@ -29,13 +31,16 @@ fn setup() -> Vec { deno_console::init(), deno_web::init::(BlobStore::default(), None), Extension::builder("bench_setup") - .js(vec![( - "setup", - r#" - const { TextDecoder } = globalThis.__bootstrap.encoding; - const hello12k = Deno.core.encode("hello world\n".repeat(1e3)); + .esm(vec![ExtensionFileSource { + specifier: "internal:setup".to_string(), + code: ExtensionFileSourceCode::IncludedInBinary( + r#" + import { TextDecoder } from "internal:deno_web/08_text_encoding.js"; + globalThis.TextDecoder = TextDecoder; + globalThis.hello12k = Deno.core.encode("hello world\n".repeat(1e3)); "#, - )]) + ), + }]) .state(|state| { state.put(Permissions {}); Ok(()) diff --git a/ext/web/benches/timers_ops.rs b/ext/web/benches/timers_ops.rs index b28b1ae1d74e01..657082df4b5741 100644 --- a/ext/web/benches/timers_ops.rs +++ b/ext/web/benches/timers_ops.rs @@ -5,6 +5,8 @@ use deno_bench_util::bench_or_profile; use deno_bench_util::bencher::benchmark_group; use deno_bench_util::bencher::Bencher; use deno_core::Extension; +use deno_core::ExtensionFileSource; +use deno_core::ExtensionFileSourceCode; use deno_web::BlobStore; struct Permissions; @@ -28,11 +30,15 @@ fn setup() -> Vec { deno_console::init(), deno_web::init::(BlobStore::default(), None), Extension::builder("bench_setup") - .js(vec![ - ("setup", r#" - const { setTimeout, handleTimerMacrotask } = globalThis.__bootstrap.timers; + .esm(vec![ + ExtensionFileSource { + specifier: "internal:setup".to_string(), + code: ExtensionFileSourceCode::IncludedInBinary(r#" + import { setTimeout, handleTimerMacrotask } from "internal:deno_web/02_timers.js"; + globalThis.setTimeout = setTimeout; Deno.core.setMacrotaskCallback(handleTimerMacrotask); - "#), + "#) + }, ]) .state(|state| { state.put(Permissions{}); diff --git a/ext/web/internal.d.ts b/ext/web/internal.d.ts index 9bb89d98e02353..9f72b0f5d823ca 100644 --- a/ext/web/internal.d.ts +++ b/ext/web/internal.d.ts @@ -1,120 +1,111 @@ // Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. -// deno-lint-ignore-file no-var - /// /// -declare namespace globalThis { - declare namespace __bootstrap { - declare var infra: { - collectSequenceOfCodepoints( - input: string, - position: number, - condition: (char: string) => boolean, - ): { - result: string; - position: number; - }; - ASCII_DIGIT: string[]; - ASCII_UPPER_ALPHA: string[]; - ASCII_LOWER_ALPHA: string[]; - ASCII_ALPHA: string[]; - ASCII_ALPHANUMERIC: string[]; - HTTP_TAB_OR_SPACE: string[]; - HTTP_WHITESPACE: string[]; - HTTP_TOKEN_CODE_POINT: string[]; - HTTP_TOKEN_CODE_POINT_RE: RegExp; - HTTP_QUOTED_STRING_TOKEN_POINT: string[]; - HTTP_QUOTED_STRING_TOKEN_POINT_RE: RegExp; - HTTP_TAB_OR_SPACE_PREFIX_RE: RegExp; - HTTP_TAB_OR_SPACE_SUFFIX_RE: RegExp; - HTTP_WHITESPACE_PREFIX_RE: RegExp; - HTTP_WHITESPACE_SUFFIX_RE: RegExp; - httpTrim(s: string): string; - regexMatcher(chars: string[]): string; - byteUpperCase(s: string): string; - byteLowerCase(s: string): string; - collectHttpQuotedString( - input: string, - position: number, - extractValue: boolean, - ): { - result: string; - position: number; - }; - forgivingBase64Encode(data: Uint8Array): string; - forgivingBase64Decode(data: string): Uint8Array; - serializeJSValueToJSONString(value: unknown): string; - }; - - declare var domException: { - DOMException: typeof DOMException; - }; +declare module "internal:deno_web/00_infra.js" { + function collectSequenceOfCodepoints( + input: string, + position: number, + condition: (char: string) => boolean, + ): { + result: string; + position: number; + }; + const ASCII_DIGIT: string[]; + const ASCII_UPPER_ALPHA: string[]; + const ASCII_LOWER_ALPHA: string[]; + const ASCII_ALPHA: string[]; + const ASCII_ALPHANUMERIC: string[]; + const HTTP_TAB_OR_SPACE: string[]; + const HTTP_WHITESPACE: string[]; + const HTTP_TOKEN_CODE_POINT: string[]; + const HTTP_TOKEN_CODE_POINT_RE: RegExp; + const HTTP_QUOTED_STRING_TOKEN_POINT: string[]; + const HTTP_QUOTED_STRING_TOKEN_POINT_RE: RegExp; + const HTTP_TAB_OR_SPACE_PREFIX_RE: RegExp; + const HTTP_TAB_OR_SPACE_SUFFIX_RE: RegExp; + const HTTP_WHITESPACE_PREFIX_RE: RegExp; + const HTTP_WHITESPACE_SUFFIX_RE: RegExp; + function httpTrim(s: string): string; + function regexMatcher(chars: string[]): string; + function byteUpperCase(s: string): string; + function byteLowerCase(s: string): string; + function collectHttpQuotedString( + input: string, + position: number, + extractValue: boolean, + ): { + result: string; + position: number; + }; + function forgivingBase64Encode(data: Uint8Array): string; + function forgivingBase64Decode(data: string): Uint8Array; + function serializeJSValueToJSONString(value: unknown): string; +} - declare namespace mimesniff { - declare interface MimeType { - type: string; - subtype: string; - parameters: Map; - } - declare function parseMimeType(input: string): MimeType | null; - declare function essence(mimeType: MimeType): string; - declare function serializeMimeType(mimeType: MimeType): string; - declare function extractMimeType( - headerValues: string[] | null, - ): MimeType | null; - } +declare module "internal:deno_web/01_dom_exception.js" { + export = DOMException; +} - declare var eventTarget: { - EventTarget: typeof EventTarget; - }; +declare module "internal:deno_web/01_mimesniff.js" { + interface MimeType { + type: string; + subtype: string; + parameters: Map; + } + function parseMimeType(input: string): MimeType | null; + function essence(mimeType: MimeType): string; + function serializeMimeType(mimeType: MimeType): string; + function extractMimeType( + headerValues: string[] | null, + ): MimeType | null; +} - declare var event: { - Event: typeof event; - ErrorEvent: typeof ErrorEvent; - CloseEvent: typeof CloseEvent; - MessageEvent: typeof MessageEvent; - CustomEvent: typeof CustomEvent; - ProgressEvent: typeof ProgressEvent; - PromiseRejectionEvent: typeof PromiseRejectionEvent; - reportError: typeof reportError; - }; +declare module "internal:deno_web/02_event.js" { + const EventTarget: typeof EventTarget; + const Event: typeof event; + const ErrorEvent: typeof ErrorEvent; + const CloseEvent: typeof CloseEvent; + const MessageEvent: typeof MessageEvent; + const CustomEvent: typeof CustomEvent; + const ProgressEvent: typeof ProgressEvent; + const PromiseRejectionEvent: typeof PromiseRejectionEvent; + const reportError: typeof reportError; +} - declare var location: { - getLocationHref(): string | undefined; - }; +declare module "internal:deno_web/12_location.js" { + function getLocationHref(): string | undefined; +} - declare var base64: { - atob(data: string): string; - btoa(data: string): string; - }; +declare module "internal:deno_web/05_base64.js" { + function atob(data: string): string; + function btoa(data: string): string; +} - declare var file: { - blobFromObjectUrl(url: string): Blob | null; - getParts(blob: Blob): string[]; - Blob: typeof Blob; - File: typeof File; - }; +declare module "internal:deno_web/09_file.js" { + function blobFromObjectUrl(url: string): Blob | null; + function getParts(blob: Blob): string[]; + const Blob: typeof Blob; + const File: typeof File; +} - declare var streams: { - ReadableStream: typeof ReadableStream; - isReadableStreamDisturbed(stream: ReadableStream): boolean; - createProxy(stream: ReadableStream): ReadableStream; - }; +declare module "internal:deno_web/06_streams.js" { + const ReadableStream: typeof ReadableStream; + function isReadableStreamDisturbed(stream: ReadableStream): boolean; + function createProxy(stream: ReadableStream): ReadableStream; +} - declare namespace messagePort { - declare type Transferable = { - kind: "messagePort"; - data: number; - } | { - kind: "arrayBuffer"; - data: number; - }; - declare interface MessageData { - data: Uint8Array; - transferables: Transferable[]; - } - } +declare module "internal:deno_web/13_message_port.js" { + type Transferable = { + kind: "messagePort"; + data: number; + } | { + kind: "arrayBuffer"; + data: number; + }; + interface MessageData { + data: Uint8Array; + transferables: Transferable[]; } } diff --git a/ext/web/lib.rs b/ext/web/lib.rs index c677bb8e911f70..c1d2c6703fbc97 100644 --- a/ext/web/lib.rs +++ b/ext/web/lib.rs @@ -64,8 +64,7 @@ pub fn init( ) -> Extension { Extension::builder(env!("CARGO_PKG_NAME")) .dependencies(vec!["deno_webidl", "deno_console", "deno_url"]) - .js(include_js_files!( - prefix "internal:ext/web", + .esm(include_js_files!( "00_infra.js", "01_dom_exception.js", "01_mimesniff.js", diff --git a/ext/webgpu/01_webgpu.js b/ext/webgpu/01_webgpu.js new file mode 100644 index 00000000000000..d859ec89ae55f9 --- /dev/null +++ b/ext/webgpu/01_webgpu.js @@ -0,0 +1,5465 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. +// Copyright 2023 Jo Bates. All rights reserved. MIT license. + +// @ts-check +/// +/// +/// +/// + +const core = globalThis.Deno.core; +const ops = core.ops; +const primordials = globalThis.__bootstrap.primordials; +import * as webidl from "internal:deno_webidl/00_webidl.js"; +import { EventTarget } from "internal:deno_web/02_event.js"; +import DOMException from "internal:deno_web/01_dom_exception.js"; +const { + ArrayBuffer, + ArrayBufferIsView, + ArrayIsArray, + ArrayPrototypeFilter, + ArrayPrototypeMap, + ArrayPrototypePop, + ArrayPrototypePush, + Error, + MathMax, + ObjectDefineProperty, + ObjectPrototypeIsPrototypeOf, + Promise, + PromisePrototypeCatch, + PromisePrototypeThen, + PromiseReject, + PromiseResolve, + SafeArrayIterator, + SafePromiseAll, + Set, + SetPrototypeHas, + Symbol, + SymbolFor, + TypeError, + Uint32Array, + Uint32ArrayPrototype, + Uint8Array, + WeakRef, +} = primordials; + +const _rid = Symbol("[[rid]]"); +const _size = Symbol("[[size]]"); +const _usage = Symbol("[[usage]]"); +const _state = Symbol("[[state]]"); +const _mappingRange = Symbol("[[mapping_range]]"); +const _mappedRanges = Symbol("[[mapped_ranges]]"); +const _mapMode = Symbol("[[map_mode]]"); +const _adapter = Symbol("[[adapter]]"); +const _cleanup = Symbol("[[cleanup]]"); +const _vendor = Symbol("[[vendor]]"); +const _architecture = Symbol("[[architecture]]"); +const _description = Symbol("[[description]]"); +const _limits = Symbol("[[limits]]"); +const _reason = Symbol("[[reason]]"); +const _message = Symbol("[[message]]"); +const _label = Symbol("[[label]]"); +const _device = Symbol("[[device]]"); +const _queue = Symbol("[[queue]]"); +const _views = Symbol("[[views]]"); +const _texture = Symbol("[[texture]]"); +const _encoders = Symbol("[[encoders]]"); +const _encoder = Symbol("[[encoder]]"); +const _descriptor = Symbol("[[descriptor]]"); +const _width = Symbol("[[width]]"); +const _height = Symbol("[[height]]"); +const _depthOrArrayLayers = Symbol("[[depthOrArrayLayers]]"); +const _mipLevelCount = Symbol("[[mipLevelCount]]"); +const _sampleCount = Symbol("[[sampleCount]]"); +const _dimension = Symbol("[[dimension]]"); +const _format = Symbol("[[format]]"); +const _type = Symbol("[[type]]"); +const _count = Symbol("[[count]]"); +const _configuration = Symbol("[[configuration]]"); +const _currentTexture = Symbol("[[currentTexture]]"); +const _surface = Symbol("[[surface]]"); +const _isSuboptimal = Symbol("[[isSuboptimal]]"); + +/** + * @param {any} self + * @param {{prefix: string, context: string}} opts + * @returns {InnerGPUDevice & {rid: number}} + */ +function assertDevice(self, { prefix, context }) { + const device = self[_device]; + const deviceRid = device?.rid; + if (deviceRid === undefined) { + throw new DOMException( + `${prefix}: ${context} references an invalid or destroyed device.`, + "OperationError", + ); + } + return device; +} + +/** + * @param {InnerGPUDevice} self + * @param {any} resource + * @param {{prefix: string, resourceContext: string, selfContext: string}} opts + * @returns {InnerGPUDevice & {rid: number}} + */ +function assertDeviceMatch( + self, + resource, + { prefix, resourceContext, selfContext }, +) { + const resourceDevice = assertDevice(resource, { + prefix, + context: resourceContext, + }); + if (resourceDevice.rid !== self.rid) { + throw new DOMException( + `${prefix}: ${resourceContext} belongs to a diffent device than ${selfContext}.`, + "OperationError", + ); + } + return { ...resourceDevice, rid: resourceDevice.rid }; +} + +/** + * @param {any} self + * @param {{prefix: string, context: string}} opts + * @returns {number} + */ +function assertResource(self, { prefix, context }) { + const rid = self[_rid]; + if (rid === undefined) { + throw new DOMException( + `${prefix}: ${context} an invalid or destroyed resource.`, + "OperationError", + ); + } + return rid; +} + +/** + * @param {number[] | GPUExtent3DDict} data + * @returns {GPUExtent3DDict} + */ +function normalizeGPUExtent3D(data) { + if (ArrayIsArray(data)) { + return { + width: data[0], + height: data[1], + depthOrArrayLayers: data[2], + }; + } else { + return data; + } +} + +/** + * @param {number[] | GPUOrigin3DDict} data + * @returns {GPUOrigin3DDict} + */ +function normalizeGPUOrigin3D(data) { + if (ArrayIsArray(data)) { + return { + x: data[0], + y: data[1], + z: data[2], + }; + } else { + return data; + } +} + +/** + * @param {number[] | GPUColor} data + * @returns {GPUColor} + */ +function normalizeGPUColor(data) { + if (ArrayIsArray(data)) { + return { + r: data[0], + g: data[1], + b: data[2], + a: data[3], + }; + } else { + return data; + } +} + +const illegalConstructorKey = Symbol("illegalConstructorKey"); +class GPUError extends Error { + constructor(key = null) { + super(); + if (key !== illegalConstructorKey) { + webidl.illegalConstructor(); + } + } + + [_message]; + get message() { + webidl.assertBranded(this, GPUErrorPrototype); + return this[_message]; + } +} +const GPUErrorPrototype = GPUError.prototype; + +class GPUValidationError extends GPUError { + name = "GPUValidationError"; + /** @param {string} message */ + constructor(message) { + const prefix = "Failed to construct 'GPUValidationError'"; + webidl.requiredArguments(arguments.length, 1, { prefix }); + message = webidl.converters.DOMString(message, { + prefix, + context: "Argument 1", + }); + super(illegalConstructorKey); + this[webidl.brand] = webidl.brand; + this[_message] = message; + } +} +const GPUValidationErrorPrototype = GPUValidationError.prototype; + +class GPUOutOfMemoryError extends GPUError { + name = "GPUOutOfMemoryError"; + constructor(message) { + const prefix = "Failed to construct 'GPUOutOfMemoryError'"; + webidl.requiredArguments(arguments.length, 1, { prefix }); + message = webidl.converters.DOMString(message, { + prefix, + context: "Argument 1", + }); + super(illegalConstructorKey); + this[webidl.brand] = webidl.brand; + this[_message] = message; + } +} +const GPUOutOfMemoryErrorPrototype = GPUOutOfMemoryError.prototype; + +class GPU { + [webidl.brand] = webidl.brand; + + constructor() { + webidl.illegalConstructor(); + } + + /** + * @param {GPURequestAdapterOptions} options + */ + async requestAdapter(options = {}) { + webidl.assertBranded(this, GPUPrototype); + const prefix = "Failed to execute 'requestAdapter' on 'GPU'"; + options = webidl.converters.GPURequestAdapterOptions(options, { + prefix, + context: "Argument 1", + }); + + let compatibleSurfaceRid = null; + if (options.compatibleSurface) { + compatibleSurfaceRid = assertResource(options.compatibleSurface, { + prefix, + context: "compatible surface", + }); + } + + const { err, ...data } = await core.opAsync( + "op_webgpu_request_adapter", + options.powerPreference, + options.forceFallbackAdapter, + compatibleSurfaceRid, + ); + + if (err) { + return null; + } else { + return createGPUAdapter(data); + } + } + + [SymbolFor("Deno.privateCustomInspect")](inspect) { + return `${this.constructor.name} ${inspect({})}`; + } +} +const GPUPrototype = GPU.prototype; + +/** + * @typedef InnerGPUAdapter + * @property {number} rid + * @property {GPUSupportedFeatures} features + * @property {GPUSupportedLimits} limits + * @property {boolean} isFallbackAdapter + */ + +/** + * @param {InnerGPUAdapter} inner + * @returns {GPUAdapter} + */ +function createGPUAdapter(inner) { + /** @type {GPUAdapter} */ + const adapter = webidl.createBranded(GPUAdapter); + adapter[_adapter] = { + ...inner, + features: createGPUSupportedFeatures(inner.features), + limits: createGPUSupportedLimits(inner.limits), + }; + return adapter; +} + +class GPUAdapter { + /** @type {InnerGPUAdapter} */ + [_adapter]; + + /** @returns {GPUSupportedFeatures} */ + get features() { + webidl.assertBranded(this, GPUAdapterPrototype); + return this[_adapter].features; + } + /** @returns {GPUSupportedLimits} */ + get limits() { + webidl.assertBranded(this, GPUAdapterPrototype); + return this[_adapter].limits; + } + /** @returns {boolean} */ + get isFallbackAdapter() { + return this[_adapter].isFallbackAdapter; + } + + constructor() { + webidl.illegalConstructor(); + } + + /** + * @param {GPUDeviceDescriptor} descriptor + * @returns {Promise} + */ + async requestDevice(descriptor = {}) { + webidl.assertBranded(this, GPUAdapterPrototype); + const prefix = "Failed to execute 'requestDevice' on 'GPUAdapter'"; + descriptor = webidl.converters.GPUDeviceDescriptor(descriptor, { + prefix, + context: "Argument 1", + }); + const requiredFeatures = descriptor.requiredFeatures ?? []; + for (let i = 0; i < requiredFeatures.length; ++i) { + const feature = requiredFeatures[i]; + if ( + !SetPrototypeHas( + this[_adapter].features[webidl.setlikeInner], + feature, + ) + ) { + throw new TypeError( + `${prefix}: requiredFeatures must be a subset of the adapter features.`, + ); + } + } + + const { rid, features, limits } = await core.opAsync( + "op_webgpu_request_device", + this[_adapter].rid, + descriptor.label, + requiredFeatures, + descriptor.requiredLimits, + ); + + const inner = new InnerGPUDevice({ + rid, + adapter: this, + features: createGPUSupportedFeatures(features), + limits: createGPUSupportedLimits(limits), + }); + return createGPUDevice( + descriptor.label, + inner, + createGPUQueue(descriptor.label, inner), + ); + } + + /** + * @param {string[]} unmaskHints + * @returns {Promise} + */ + async requestAdapterInfo(unmaskHints = []) { + webidl.assertBranded(this, GPUAdapterPrototype); + const prefix = "Failed to execute 'requestAdapterInfo' on 'GPUAdapter'"; + unmaskHints = webidl.converters["sequence"](unmaskHints, { + prefix, + context: "Argument 1", + }); + + const { + vendor, + architecture, + device, + description, + } = await core.opAsync( + "op_webgpu_request_adapter_info", + this[_adapter].rid, + ); + + const adapterInfo = webidl.createBranded(GPUAdapterInfo); + adapterInfo[_vendor] = unmaskHints.includes("vendor") ? vendor : ""; + adapterInfo[_architecture] = unmaskHints.includes("architecture") + ? architecture + : ""; + adapterInfo[_device] = unmaskHints.includes("device") ? device : ""; + adapterInfo[_description] = unmaskHints.includes("description") + ? description + : ""; + return adapterInfo; + } + + [SymbolFor("Deno.privateCustomInspect")](inspect) { + return `${this.constructor.name} ${ + inspect({ + features: this.features, + limits: this.limits, + }) + }`; + } +} +const GPUAdapterPrototype = GPUAdapter.prototype; + +class GPUAdapterInfo { + /** @type {string} */ + [_vendor]; + /** @returns {string} */ + get vendor() { + webidl.assertBranded(this, GPUAdapterInfoPrototype); + return this[_vendor]; + } + + /** @type {string} */ + [_architecture]; + /** @returns {string} */ + get architecture() { + webidl.assertBranded(this, GPUAdapterInfoPrototype); + return this[_architecture]; + } + + /** @type {string} */ + [_device]; + /** @returns {string} */ + get device() { + webidl.assertBranded(this, GPUAdapterInfoPrototype); + return this[_device]; + } + + /** @type {string} */ + [_description]; + /** @returns {string} */ + get description() { + webidl.assertBranded(this, GPUAdapterInfoPrototype); + return this[_description]; + } + + [SymbolFor("Deno.privateCustomInspect")](inspect) { + return `${this.constructor.name} ${ + inspect({ + vendor: this.vendor, + architecture: this.architecture, + device: this.device, + description: this.description, + }) + }`; + } +} +const GPUAdapterInfoPrototype = GPUAdapterInfo.prototype; + +function createGPUSupportedLimits(limits) { + /** @type {GPUSupportedLimits} */ + const adapterFeatures = webidl.createBranded(GPUSupportedLimits); + adapterFeatures[_limits] = limits; + return adapterFeatures; +} + +/** + * @typedef InnerAdapterLimits + * @property {number} maxTextureDimension1D + * @property {number} maxTextureDimension2D + * @property {number} maxTextureDimension3D + * @property {number} maxTextureArrayLayers + * @property {number} maxBindGroups + * @property {number} maxDynamicUniformBuffersPerPipelineLayout + * @property {number} maxDynamicStorageBuffersPerPipelineLayout + * @property {number} maxSampledTexturesPerShaderStage + * @property {number} maxSamplersPerShaderStage + * @property {number} maxStorageBuffersPerShaderStage + * @property {number} maxStorageTexturesPerShaderStage + * @property {number} maxUniformBuffersPerShaderStage + * @property {number} maxUniformBufferBindingSize + * @property {number} maxStorageBufferBindingSize + * @property {number} minUniformBufferOffsetAlignment + * @property {number} minStorageBufferOffsetAlignment + * @property {number} maxVertexBuffers + * @property {number} maxVertexAttributes + * @property {number} maxVertexBufferArrayStride + * @property {number} maxInterStageShaderComponents + * @property {number} maxComputeWorkgroupStorageSize + * @property {number} maxComputeInvocationsPerWorkgroup + * @property {number} maxComputeWorkgroupSizeX + * @property {number} maxComputeWorkgroupSizeY + * @property {number} maxComputeWorkgroupSizeZ + * @property {number} maxComputeWorkgroupsPerDimension + */ + +class GPUSupportedLimits { + /** @type {InnerAdapterLimits} */ + [_limits]; + constructor() { + webidl.illegalConstructor(); + } + + get maxTextureDimension1D() { + webidl.assertBranded(this, GPUSupportedLimitsPrototype); + return this[_limits].maxTextureDimension1D; + } + get maxTextureDimension2D() { + webidl.assertBranded(this, GPUSupportedLimitsPrototype); + return this[_limits].maxTextureDimension2D; + } + get maxTextureDimension3D() { + webidl.assertBranded(this, GPUSupportedLimitsPrototype); + return this[_limits].maxTextureDimension3D; + } + get maxTextureArrayLayers() { + webidl.assertBranded(this, GPUSupportedLimitsPrototype); + return this[_limits].maxTextureArrayLayers; + } + get maxBindGroups() { + webidl.assertBranded(this, GPUSupportedLimitsPrototype); + return this[_limits].maxBindGroups; + } + get maxBindingsPerBindGroup() { + webidl.assertBranded(this, GPUSupportedLimitsPrototype); + return this[_limits].maxBindingsPerBindGroup; + } + get maxBufferSize() { + webidl.assertBranded(this, GPUSupportedLimitsPrototype); + return this[_limits].maxBufferSize; + } + get maxDynamicUniformBuffersPerPipelineLayout() { + webidl.assertBranded(this, GPUSupportedLimitsPrototype); + return this[_limits].maxDynamicUniformBuffersPerPipelineLayout; + } + get maxDynamicStorageBuffersPerPipelineLayout() { + webidl.assertBranded(this, GPUSupportedLimitsPrototype); + return this[_limits].maxDynamicStorageBuffersPerPipelineLayout; + } + get maxSampledTexturesPerShaderStage() { + webidl.assertBranded(this, GPUSupportedLimitsPrototype); + return this[_limits].maxSampledTexturesPerShaderStage; + } + get maxSamplersPerShaderStage() { + webidl.assertBranded(this, GPUSupportedLimitsPrototype); + return this[_limits].maxSamplersPerShaderStage; + } + get maxStorageBuffersPerShaderStage() { + webidl.assertBranded(this, GPUSupportedLimitsPrototype); + return this[_limits].maxStorageBuffersPerShaderStage; + } + get maxStorageTexturesPerShaderStage() { + webidl.assertBranded(this, GPUSupportedLimitsPrototype); + return this[_limits].maxStorageTexturesPerShaderStage; + } + get maxUniformBuffersPerShaderStage() { + webidl.assertBranded(this, GPUSupportedLimitsPrototype); + return this[_limits].maxUniformBuffersPerShaderStage; + } + get maxUniformBufferBindingSize() { + webidl.assertBranded(this, GPUSupportedLimitsPrototype); + return this[_limits].maxUniformBufferBindingSize; + } + get maxStorageBufferBindingSize() { + webidl.assertBranded(this, GPUSupportedLimitsPrototype); + return this[_limits].maxStorageBufferBindingSize; + } + get minUniformBufferOffsetAlignment() { + webidl.assertBranded(this, GPUSupportedLimitsPrototype); + return this[_limits].minUniformBufferOffsetAlignment; + } + get minStorageBufferOffsetAlignment() { + webidl.assertBranded(this, GPUSupportedLimitsPrototype); + return this[_limits].minStorageBufferOffsetAlignment; + } + get maxVertexBuffers() { + webidl.assertBranded(this, GPUSupportedLimitsPrototype); + return this[_limits].maxVertexBuffers; + } + get maxVertexAttributes() { + webidl.assertBranded(this, GPUSupportedLimitsPrototype); + return this[_limits].maxVertexAttributes; + } + get maxVertexBufferArrayStride() { + webidl.assertBranded(this, GPUSupportedLimitsPrototype); + return this[_limits].maxVertexBufferArrayStride; + } + get maxInterStageShaderComponents() { + webidl.assertBranded(this, GPUSupportedLimitsPrototype); + return this[_limits].maxInterStageShaderComponents; + } + get maxComputeWorkgroupStorageSize() { + webidl.assertBranded(this, GPUSupportedLimitsPrototype); + return this[_limits].maxComputeWorkgroupStorageSize; + } + get maxComputeInvocationsPerWorkgroup() { + webidl.assertBranded(this, GPUSupportedLimitsPrototype); + return this[_limits].maxComputeInvocationsPerWorkgroup; + } + get maxComputeWorkgroupSizeX() { + webidl.assertBranded(this, GPUSupportedLimitsPrototype); + return this[_limits].maxComputeWorkgroupSizeX; + } + get maxComputeWorkgroupSizeY() { + webidl.assertBranded(this, GPUSupportedLimitsPrototype); + return this[_limits].maxComputeWorkgroupSizeY; + } + get maxComputeWorkgroupSizeZ() { + webidl.assertBranded(this, GPUSupportedLimitsPrototype); + return this[_limits].maxComputeWorkgroupSizeZ; + } + get maxComputeWorkgroupsPerDimension() { + webidl.assertBranded(this, GPUSupportedLimitsPrototype); + return this[_limits].maxComputeWorkgroupsPerDimension; + } + + [SymbolFor("Deno.privateCustomInspect")](inspect) { + return `${this.constructor.name} ${inspect(this[_limits])}`; + } +} +const GPUSupportedLimitsPrototype = GPUSupportedLimits.prototype; + +function createGPUSupportedFeatures(features) { + /** @type {GPUSupportedFeatures} */ + const supportedFeatures = webidl.createBranded(GPUSupportedFeatures); + supportedFeatures[webidl.setlikeInner] = new Set(features); + webidl.setlike( + supportedFeatures, + GPUSupportedFeaturesPrototype, + true, + ); + return supportedFeatures; +} + +class GPUSupportedFeatures { + constructor() { + webidl.illegalConstructor(); + } + [SymbolFor("Deno.privateCustomInspect")](inspect) { + return `${this.constructor.name} ${ + inspect([...new SafeArrayIterator(this.values())]) + }`; + } +} + +const GPUSupportedFeaturesPrototype = GPUSupportedFeatures.prototype; + +/** + * @param {string | undefined} reason + * @param {string} message + * @returns {GPUDeviceLostInfo} + */ +function createGPUDeviceLostInfo(reason, message) { + /** @type {GPUDeviceLostInfo} */ + const deviceLostInfo = webidl.createBranded(GPUDeviceLostInfo); + deviceLostInfo[_reason] = reason; + deviceLostInfo[_message] = message; + return deviceLostInfo; +} + +class GPUDeviceLostInfo { + /** @type {string | undefined} */ + [_reason]; + /** @type {string} */ + [_message]; + + constructor() { + webidl.illegalConstructor(); + } + + get reason() { + webidl.assertBranded(this, GPUDeviceLostInfoPrototype); + return this[_reason]; + } + get message() { + webidl.assertBranded(this, GPUDeviceLostInfoPrototype); + return this[_message]; + } + + [SymbolFor("Deno.privateCustomInspect")](inspect) { + return `${this.constructor.name} ${ + inspect({ reason: this[_reason], message: this[_message] }) + }`; + } +} + +const GPUDeviceLostInfoPrototype = GPUDeviceLostInfo.prototype; + +/** + * @param {string} name + * @param {any} type + */ +function GPUObjectBaseMixin(name, type) { + type.prototype[_label] = null; + ObjectDefineProperty(type.prototype, "label", { + /** + * @return {string | null} + */ + get() { + webidl.assertBranded(this, type.prototype); + return this[_label]; + }, + /** + * @param {string | null} label + */ + set(label) { + webidl.assertBranded(this, type.prototype); + label = webidl.converters["UVString?"](label, { + prefix: `Failed to set 'label' on '${name}'`, + context: "Argument 1", + }); + this[_label] = label; + }, + }); +} + +/** + * @typedef ErrorScope + * @property {string} filter + * @property {Promise[]} operations + */ + +/** + * @typedef InnerGPUDeviceOptions + * @property {GPUAdapter} adapter + * @property {number | undefined} rid + * @property {GPUSupportedFeatures} features + * @property {GPUSupportedLimits} limits + */ + +class InnerGPUDevice { + /** @type {GPUAdapter} */ + adapter; + /** @type {number | undefined} */ + rid; + /** @type {GPUSupportedFeatures} */ + features; + /** @type {GPUSupportedLimits} */ + limits; + /** @type {WeakRef[]} */ + resources; + /** @type {boolean} */ + isLost; + /** @type {Promise} */ + lost; + /** @type {(info: GPUDeviceLostInfo) => void} */ + resolveLost; + /** @type {ErrorScope[]} */ + errorScopeStack; + + /** + * @param {InnerGPUDeviceOptions} options + */ + constructor(options) { + this.adapter = options.adapter; + this.rid = options.rid; + this.features = options.features; + this.limits = options.limits; + this.resources = []; + this.isLost = false; + this.resolveLost = () => {}; + this.lost = new Promise((resolve) => { + this.resolveLost = resolve; + }); + this.errorScopeStack = []; + } + + /** @param {any} resource */ + trackResource(resource) { + ArrayPrototypePush(this.resources, new WeakRef(resource)); + } + + /** @param {{ type: string, value: string | null } | undefined} err */ + pushError(err) { + this.pushErrorPromise(PromiseResolve(err)); + } + + /** @param {Promise<{ type: string, value: string | null } | undefined>} promise */ + pushErrorPromise(promise) { + const operation = PromisePrototypeThen(promise, (err) => { + if (err) { + switch (err.type) { + case "lost": + this.isLost = true; + this.resolveLost( + createGPUDeviceLostInfo(undefined, "device was lost"), + ); + break; + case "validation": + return PromiseReject( + new GPUValidationError(err.value ?? "validation error"), + ); + case "out-of-memory": + return PromiseReject(new GPUOutOfMemoryError()); + } + } + }); + + const validationStack = ArrayPrototypeFilter( + this.errorScopeStack, + ({ filter }) => filter == "validation", + ); + const validationScope = validationStack[validationStack.length - 1]; + const validationFilteredPromise = PromisePrototypeCatch( + operation, + (err) => { + if (ObjectPrototypeIsPrototypeOf(GPUValidationErrorPrototype, err)) { + return PromiseReject(err); + } + return PromiseResolve(); + }, + ); + if (validationScope) { + ArrayPrototypePush( + validationScope.operations, + validationFilteredPromise, + ); + } else { + PromisePrototypeCatch(validationFilteredPromise, () => { + // TODO(lucacasonato): emit an UncapturedErrorEvent + }); + } + // prevent uncaptured promise rejections + PromisePrototypeCatch(validationFilteredPromise, (_err) => {}); + + const oomStack = ArrayPrototypeFilter( + this.errorScopeStack, + ({ filter }) => filter == "out-of-memory", + ); + const oomScope = oomStack[oomStack.length - 1]; + const oomFilteredPromise = PromisePrototypeCatch(operation, (err) => { + if (ObjectPrototypeIsPrototypeOf(GPUOutOfMemoryErrorPrototype, err)) { + return PromiseReject(err); + } + return PromiseResolve(); + }); + if (oomScope) { + ArrayPrototypePush(oomScope.operations, oomFilteredPromise); + } else { + PromisePrototypeCatch(oomFilteredPromise, () => { + // TODO(lucacasonato): emit an UncapturedErrorEvent + }); + } + // prevent uncaptured promise rejections + PromisePrototypeCatch(oomFilteredPromise, (_err) => {}); + } +} + +/** + * @param {string | null} label + * @param {InnerGPUDevice} inner + * @param {GPUQueue} queue + * @returns {GPUDevice} + */ +function createGPUDevice(label, inner, queue) { + /** @type {GPUDevice} */ + const device = webidl.createBranded(GPUDevice); + device[_label] = label; + device[_device] = inner; + device[_queue] = queue; + return device; +} + +class GPUDevice extends EventTarget { + /** @type {InnerGPUDevice} */ + [_device]; + + /** @type {GPUQueue} */ + [_queue]; + + [_cleanup]() { + const device = this[_device]; + const resources = device.resources; + while (resources.length > 0) { + const resource = ArrayPrototypePop(resources)?.deref(); + if (resource) { + resource[_cleanup](); + } + } + const rid = device.rid; + if (rid !== undefined) { + core.close(rid); + /** @type {number | undefined} */ + device.rid = undefined; + } + } + + get features() { + webidl.assertBranded(this, GPUDevicePrototype); + return this[_device].features; + } + get limits() { + webidl.assertBranded(this, GPUDevicePrototype); + return this[_device].limits; + } + get queue() { + webidl.assertBranded(this, GPUDevicePrototype); + return this[_queue]; + } + + constructor() { + webidl.illegalConstructor(); + super(); + } + + destroy() { + webidl.assertBranded(this, GPUDevicePrototype); + this[_cleanup](); + } + + /** + * @param {GPUBufferDescriptor} descriptor + * @returns {GPUBuffer} + */ + createBuffer(descriptor) { + webidl.assertBranded(this, GPUDevicePrototype); + const prefix = "Failed to execute 'createBuffer' on 'GPUDevice'"; + webidl.requiredArguments(arguments.length, 1, { prefix }); + descriptor = webidl.converters.GPUBufferDescriptor(descriptor, { + prefix, + context: "Argument 1", + }); + const device = assertDevice(this, { prefix, context: "this" }); + const { rid, err } = ops.op_webgpu_create_buffer( + device.rid, + descriptor.label, + descriptor.size, + descriptor.usage, + descriptor.mappedAtCreation, + ); + device.pushError(err); + /** @type {CreateGPUBufferOptions} */ + let options; + if (descriptor.mappedAtCreation) { + options = { + mapping: new ArrayBuffer(descriptor.size), + mappingRange: [0, descriptor.size], + mappedRanges: [], + state: "mapped at creation", + }; + } else { + options = { + mapping: null, + mappedRanges: null, + mappingRange: null, + state: "unmapped", + }; + } + const buffer = createGPUBuffer( + descriptor.label, + device, + rid, + descriptor.size, + descriptor.usage, + options, + ); + device.trackResource(buffer); + return buffer; + } + + /** + * @param {GPUTextureDescriptor} descriptor + * @returns {GPUTexture} + */ + createTexture(descriptor) { + webidl.assertBranded(this, GPUDevicePrototype); + const prefix = "Failed to execute 'createTexture' on 'GPUDevice'"; + webidl.requiredArguments(arguments.length, 1, { prefix }); + descriptor = webidl.converters.GPUTextureDescriptor(descriptor, { + prefix, + context: "Argument 1", + }); + const device = assertDevice(this, { prefix, context: "this" }); + const { rid, err } = ops.op_webgpu_create_texture({ + deviceRid: device.rid, + ...descriptor, + size: normalizeGPUExtent3D(descriptor.size), + }); + device.pushError(err); + + const texture = createGPUTexture( + descriptor, + device, + rid, + ); + device.trackResource(texture); + return texture; + } + + /** + * @param {GPUSamplerDescriptor} descriptor + * @returns {GPUSampler} + */ + createSampler(descriptor = {}) { + webidl.assertBranded(this, GPUDevicePrototype); + const prefix = "Failed to execute 'createSampler' on 'GPUDevice'"; + descriptor = webidl.converters.GPUSamplerDescriptor(descriptor, { + prefix, + context: "Argument 1", + }); + const device = assertDevice(this, { prefix, context: "this" }); + const { rid, err } = ops.op_webgpu_create_sampler({ + deviceRid: device.rid, + ...descriptor, + }); + device.pushError(err); + + const sampler = createGPUSampler( + descriptor.label, + device, + rid, + ); + device.trackResource(sampler); + return sampler; + } + + /** + * @param {GPUBindGroupLayoutDescriptor} descriptor + * @returns {GPUBindGroupLayout} + */ + createBindGroupLayout(descriptor) { + webidl.assertBranded(this, GPUDevicePrototype); + const prefix = "Failed to execute 'createBindGroupLayout' on 'GPUDevice'"; + webidl.requiredArguments(arguments.length, 1, { prefix }); + descriptor = webidl.converters.GPUBindGroupLayoutDescriptor(descriptor, { + prefix, + context: "Argument 1", + }); + const device = assertDevice(this, { prefix, context: "this" }); + for (let i = 0; i < descriptor.entries.length; ++i) { + const entry = descriptor.entries[i]; + + let j = 0; + if (entry.buffer) j++; + if (entry.sampler) j++; + if (entry.texture) j++; + if (entry.storageTexture) j++; + + if (j !== 1) { + throw new Error(); // TODO(@crowlKats): correct error + } + } + + const { rid, err } = ops.op_webgpu_create_bind_group_layout( + device.rid, + descriptor.label, + descriptor.entries, + ); + device.pushError(err); + + const bindGroupLayout = createGPUBindGroupLayout( + descriptor.label, + device, + rid, + ); + device.trackResource(bindGroupLayout); + return bindGroupLayout; + } + + /** + * @param {GPUPipelineLayoutDescriptor} descriptor + * @returns {GPUPipelineLayout} + */ + createPipelineLayout(descriptor) { + webidl.assertBranded(this, GPUDevicePrototype); + const prefix = "Failed to execute 'createPipelineLayout' on 'GPUDevice'"; + webidl.requiredArguments(arguments.length, 1, { prefix }); + descriptor = webidl.converters.GPUPipelineLayoutDescriptor(descriptor, { + prefix, + context: "Argument 1", + }); + const device = assertDevice(this, { prefix, context: "this" }); + const bindGroupLayouts = ArrayPrototypeMap( + descriptor.bindGroupLayouts, + (layout, i) => { + const context = `bind group layout ${i + 1}`; + const rid = assertResource(layout, { prefix, context }); + assertDeviceMatch(device, layout, { + prefix, + selfContext: "this", + resourceContext: context, + }); + return rid; + }, + ); + const { rid, err } = ops.op_webgpu_create_pipeline_layout( + device.rid, + descriptor.label, + bindGroupLayouts, + ); + device.pushError(err); + + const pipelineLayout = createGPUPipelineLayout( + descriptor.label, + device, + rid, + ); + device.trackResource(pipelineLayout); + return pipelineLayout; + } + + /** + * @param {GPUBindGroupDescriptor} descriptor + * @returns {GPUBindGroup} + */ + createBindGroup(descriptor) { + webidl.assertBranded(this, GPUDevicePrototype); + const prefix = "Failed to execute 'createBindGroup' on 'GPUDevice'"; + webidl.requiredArguments(arguments.length, 1, { prefix }); + descriptor = webidl.converters.GPUBindGroupDescriptor(descriptor, { + prefix, + context: "Argument 1", + }); + const device = assertDevice(this, { prefix, context: "this" }); + const layout = assertResource(descriptor.layout, { + prefix, + context: "layout", + }); + assertDeviceMatch(device, descriptor.layout, { + prefix, + resourceContext: "layout", + selfContext: "this", + }); + const entries = ArrayPrototypeMap(descriptor.entries, (entry, i) => { + const context = `entry ${i + 1}`; + const resource = entry.resource; + if (ObjectPrototypeIsPrototypeOf(GPUSamplerPrototype, resource)) { + const rid = assertResource(resource, { + prefix, + context, + }); + assertDeviceMatch(device, resource, { + prefix, + resourceContext: context, + selfContext: "this", + }); + return { + binding: entry.binding, + kind: "GPUSampler", + resource: rid, + }; + } else if ( + ObjectPrototypeIsPrototypeOf(GPUTextureViewPrototype, resource) + ) { + const rid = assertResource(resource, { + prefix, + context, + }); + assertResource(resource[_texture], { + prefix, + context, + }); + assertDeviceMatch(device, resource[_texture], { + prefix, + resourceContext: context, + selfContext: "this", + }); + return { + binding: entry.binding, + kind: "GPUTextureView", + resource: rid, + }; + } else { + const rid = assertResource(resource.buffer, { prefix, context }); + assertDeviceMatch(device, resource.buffer, { + prefix, + resourceContext: context, + selfContext: "this", + }); + return { + binding: entry.binding, + kind: "GPUBufferBinding", + resource: rid, + offset: entry.resource.offset, + size: entry.resource.size, + }; + } + }); + + const { rid, err } = ops.op_webgpu_create_bind_group( + device.rid, + descriptor.label, + layout, + entries, + ); + device.pushError(err); + + const bindGroup = createGPUBindGroup( + descriptor.label, + device, + rid, + ); + device.trackResource(bindGroup); + return bindGroup; + } + + /** + * @param {GPUShaderModuleDescriptor} descriptor + */ + createShaderModule(descriptor) { + webidl.assertBranded(this, GPUDevicePrototype); + const prefix = "Failed to execute 'createShaderModule' on 'GPUDevice'"; + webidl.requiredArguments(arguments.length, 1, { prefix }); + descriptor = webidl.converters.GPUShaderModuleDescriptor(descriptor, { + prefix, + context: "Argument 1", + }); + const device = assertDevice(this, { prefix, context: "this" }); + const { rid, err } = ops.op_webgpu_create_shader_module( + device.rid, + descriptor.label, + descriptor.code, + ); + device.pushError(err); + + const shaderModule = createGPUShaderModule( + descriptor.label, + device, + rid, + ); + device.trackResource(shaderModule); + return shaderModule; + } + + /** + * @param {GPUComputePipelineDescriptor} descriptor + * @returns {GPUComputePipeline} + */ + createComputePipeline(descriptor) { + webidl.assertBranded(this, GPUDevicePrototype); + const prefix = "Failed to execute 'createComputePipeline' on 'GPUDevice'"; + webidl.requiredArguments(arguments.length, 1, { prefix }); + descriptor = webidl.converters.GPUComputePipelineDescriptor(descriptor, { + prefix, + context: "Argument 1", + }); + const device = assertDevice(this, { prefix, context: "this" }); + let layout = descriptor.layout; + if (typeof descriptor.layout !== "string") { + const context = "layout"; + layout = assertResource(descriptor.layout, { prefix, context }); + assertDeviceMatch(device, descriptor.layout, { + prefix, + resourceContext: context, + selfContext: "this", + }); + } + const module = assertResource(descriptor.compute.module, { + prefix, + context: "compute shader module", + }); + assertDeviceMatch(device, descriptor.compute.module, { + prefix, + resourceContext: "compute shader module", + selfContext: "this", + }); + + const { rid, err } = ops.op_webgpu_create_compute_pipeline( + device.rid, + descriptor.label, + layout, + { + module, + entryPoint: descriptor.compute.entryPoint, + constants: descriptor.compute.constants, + }, + ); + device.pushError(err); + + const computePipeline = createGPUComputePipeline( + descriptor.label, + device, + rid, + ); + device.trackResource(computePipeline); + return computePipeline; + } + + /** + * @param {GPURenderPipelineDescriptor} descriptor + * @returns {GPURenderPipeline} + */ + createRenderPipeline(descriptor) { + webidl.assertBranded(this, GPUDevicePrototype); + const prefix = "Failed to execute 'createRenderPipeline' on 'GPUDevice'"; + webidl.requiredArguments(arguments.length, 1, { prefix }); + descriptor = webidl.converters.GPURenderPipelineDescriptor(descriptor, { + prefix, + context: "Argument 1", + }); + const device = assertDevice(this, { prefix, context: "this" }); + let layout = descriptor.layout; + if (typeof descriptor.layout !== "string") { + const context = "layout"; + layout = assertResource(descriptor.layout, { prefix, context }); + assertDeviceMatch(device, descriptor.layout, { + prefix, + resourceContext: context, + selfContext: "this", + }); + } + const module = assertResource(descriptor.vertex.module, { + prefix, + context: "vertex shader module", + }); + assertDeviceMatch(device, descriptor.vertex.module, { + prefix, + resourceContext: "vertex shader module", + selfContext: "this", + }); + let fragment = undefined; + if (descriptor.fragment) { + const module = assertResource(descriptor.fragment.module, { + prefix, + context: "fragment shader module", + }); + assertDeviceMatch(device, descriptor.fragment.module, { + prefix, + resourceContext: "fragment shader module", + selfContext: "this", + }); + fragment = { + module, + entryPoint: descriptor.fragment.entryPoint, + targets: descriptor.fragment.targets, + }; + } + + const { rid, err } = ops.op_webgpu_create_render_pipeline({ + deviceRid: device.rid, + label: descriptor.label, + layout, + vertex: { + module, + entryPoint: descriptor.vertex.entryPoint, + buffers: descriptor.vertex.buffers, + }, + primitive: descriptor.primitive, + depthStencil: descriptor.depthStencil, + multisample: descriptor.multisample, + fragment, + }); + device.pushError(err); + + const renderPipeline = createGPURenderPipeline( + descriptor.label, + device, + rid, + ); + device.trackResource(renderPipeline); + return renderPipeline; + } + + createComputePipelineAsync(descriptor) { + // TODO(lucacasonato): this should be real async + return PromiseResolve(this.createComputePipeline(descriptor)); + } + + createRenderPipelineAsync(descriptor) { + // TODO(lucacasonato): this should be real async + return PromiseResolve(this.createRenderPipeline(descriptor)); + } + + /** + * @param {GPUCommandEncoderDescriptor} descriptor + * @returns {GPUCommandEncoder} + */ + createCommandEncoder(descriptor = {}) { + webidl.assertBranded(this, GPUDevicePrototype); + const prefix = "Failed to execute 'createCommandEncoder' on 'GPUDevice'"; + descriptor = webidl.converters.GPUCommandEncoderDescriptor(descriptor, { + prefix, + context: "Argument 1", + }); + const device = assertDevice(this, { prefix, context: "this" }); + const { rid, err } = ops.op_webgpu_create_command_encoder( + device.rid, + descriptor.label, + ); + device.pushError(err); + + const commandEncoder = createGPUCommandEncoder( + descriptor.label, + device, + rid, + ); + device.trackResource(commandEncoder); + return commandEncoder; + } + + /** + * @param {GPURenderBundleEncoderDescriptor} descriptor + * @returns {GPURenderBundleEncoder} + */ + createRenderBundleEncoder(descriptor) { + webidl.assertBranded(this, GPUDevicePrototype); + const prefix = + "Failed to execute 'createRenderBundleEncoder' on 'GPUDevice'"; + webidl.requiredArguments(arguments.length, 1, { prefix }); + descriptor = webidl.converters.GPURenderBundleEncoderDescriptor( + descriptor, + { + prefix, + context: "Argument 1", + }, + ); + const device = assertDevice(this, { prefix, context: "this" }); + const { rid, err } = ops.op_webgpu_create_render_bundle_encoder({ + deviceRid: device.rid, + ...descriptor, + }); + device.pushError(err); + + const renderBundleEncoder = createGPURenderBundleEncoder( + descriptor.label, + device, + rid, + ); + device.trackResource(renderBundleEncoder); + return renderBundleEncoder; + } + + /** + * @param {GPUQuerySetDescriptor} descriptor + * @returns {GPUQuerySet} + */ + createQuerySet(descriptor) { + webidl.assertBranded(this, GPUDevicePrototype); + const prefix = "Failed to execute 'createQuerySet' on 'GPUDevice'"; + webidl.requiredArguments(arguments.length, 1, { prefix }); + descriptor = webidl.converters.GPUQuerySetDescriptor( + descriptor, + { + prefix, + context: "Argument 1", + }, + ); + const device = assertDevice(this, { prefix, context: "this" }); + const { rid, err } = ops.op_webgpu_create_query_set({ + deviceRid: device.rid, + ...descriptor, + }); + device.pushError(err); + + const querySet = createGPUQuerySet( + descriptor.label, + device, + rid, + descriptor, + ); + device.trackResource(querySet); + return querySet; + } + + get lost() { + webidl.assertBranded(this, GPUDevicePrototype); + const device = this[_device]; + if (!device) { + return PromiseResolve(true); + } + if (device.rid === undefined) { + return PromiseResolve(true); + } + return device.lost; + } + + /** + * @param {GPUErrorFilter} filter + */ + pushErrorScope(filter) { + webidl.assertBranded(this, GPUDevicePrototype); + const prefix = "Failed to execute 'pushErrorScope' on 'GPUDevice'"; + webidl.requiredArguments(arguments.length, 1, { prefix }); + filter = webidl.converters.GPUErrorFilter(filter, { + prefix, + context: "Argument 1", + }); + const device = assertDevice(this, { prefix, context: "this" }); + ArrayPrototypePush(device.errorScopeStack, { filter, operations: [] }); + } + + /** + * @returns {Promise} + */ + // deno-lint-ignore require-await + async popErrorScope() { + webidl.assertBranded(this, GPUDevicePrototype); + const prefix = "Failed to execute 'popErrorScope' on 'GPUDevice'"; + const device = assertDevice(this, { prefix, context: "this" }); + if (device.isLost) { + throw new DOMException("Device has been lost.", "OperationError"); + } + const scope = ArrayPrototypePop(device.errorScopeStack); + if (!scope) { + throw new DOMException( + "There are no error scopes on the error scope stack.", + "OperationError", + ); + } + const operations = SafePromiseAll(scope.operations); + return PromisePrototypeThen( + operations, + () => PromiseResolve(null), + (err) => PromiseResolve(err), + ); + } + + [SymbolFor("Deno.privateCustomInspect")](inspect) { + return `${this.constructor.name} ${ + inspect({ + features: this.features, + label: this.label, + limits: this.limits, + queue: this.queue, + }) + }`; + } +} +GPUObjectBaseMixin("GPUDevice", GPUDevice); +const GPUDevicePrototype = GPUDevice.prototype; + +/** + * @param {string | null} label + * @param {InnerGPUDevice} device + * @returns {GPUQueue} + */ +function createGPUQueue(label, device) { + /** @type {GPUQueue} */ + const queue = webidl.createBranded(GPUQueue); + queue[_label] = label; + queue[_device] = device; + return queue; +} + +class GPUQueue { + /** @type {InnerGPUDevice} */ + [_device]; + + constructor() { + webidl.illegalConstructor(); + } + + /** + * @param {GPUCommandBuffer[]} commandBuffers + */ + submit(commandBuffers) { + webidl.assertBranded(this, GPUQueuePrototype); + const prefix = "Failed to execute 'submit' on 'GPUQueue'"; + webidl.requiredArguments(arguments.length, 1, { + prefix, + }); + commandBuffers = webidl.converters["sequence"]( + commandBuffers, + { prefix, context: "Argument 1" }, + ); + const device = assertDevice(this, { prefix, context: "this" }); + const commandBufferRids = ArrayPrototypeMap( + commandBuffers, + (buffer, i) => { + const context = `command buffer ${i + 1}`; + const rid = assertResource(buffer, { prefix, context }); + assertDeviceMatch(device, buffer, { + prefix, + selfContext: "this", + resourceContext: context, + }); + return rid; + }, + ); + const { err } = ops.op_webgpu_queue_submit(device.rid, commandBufferRids); + for (let i = 0; i < commandBuffers.length; ++i) { + commandBuffers[i][_rid] = undefined; + } + device.pushError(err); + } + + onSubmittedWorkDone() { + webidl.assertBranded(this, GPUQueuePrototype); + return PromiseResolve(); + } + + /** + * @param {GPUBuffer} buffer + * @param {number} bufferOffset + * @param {BufferSource} data + * @param {number} [dataOffset] + * @param {number} [size] + */ + writeBuffer(buffer, bufferOffset, data, dataOffset = 0, size) { + webidl.assertBranded(this, GPUQueuePrototype); + const prefix = "Failed to execute 'writeBuffer' on 'GPUQueue'"; + webidl.requiredArguments(arguments.length, 3, { prefix }); + buffer = webidl.converters["GPUBuffer"](buffer, { + prefix, + context: "Argument 1", + }); + bufferOffset = webidl.converters["GPUSize64"](bufferOffset, { + prefix, + context: "Argument 2", + }); + data = webidl.converters.BufferSource(data, { + prefix, + context: "Argument 3", + }); + dataOffset = webidl.converters["GPUSize64"](dataOffset, { + prefix, + context: "Argument 4", + }); + size = size === undefined + ? undefined + : webidl.converters["GPUSize64"](size, { + prefix, + context: "Argument 5", + }); + const device = assertDevice(this, { prefix, context: "this" }); + const bufferRid = assertResource(buffer, { + prefix, + context: "Argument 1", + }); + assertDeviceMatch(device, buffer, { + prefix, + selfContext: "this", + resourceContext: "Argument 1", + }); + const { err } = ops.op_webgpu_write_buffer( + device.rid, + bufferRid, + bufferOffset, + dataOffset, + size, + new Uint8Array(ArrayBufferIsView(data) ? data.buffer : data), + ); + device.pushError(err); + } + + /** + * @param {GPUImageCopyTexture} destination + * @param {BufferSource} data + * @param {GPUImageDataLayout} dataLayout + * @param {GPUExtent3D} size + */ + writeTexture(destination, data, dataLayout, size) { + webidl.assertBranded(this, GPUQueuePrototype); + const prefix = "Failed to execute 'writeTexture' on 'GPUQueue'"; + webidl.requiredArguments(arguments.length, 4, { prefix }); + destination = webidl.converters.GPUImageCopyTexture(destination, { + prefix, + context: "Argument 1", + }); + data = webidl.converters.BufferSource(data, { + prefix, + context: "Argument 2", + }); + dataLayout = webidl.converters.GPUImageDataLayout(dataLayout, { + prefix, + context: "Argument 3", + }); + size = webidl.converters.GPUExtent3D(size, { + prefix, + context: "Argument 4", + }); + const device = assertDevice(this, { prefix, context: "this" }); + const textureRid = assertResource(destination.texture, { + prefix, + context: "texture", + }); + assertDeviceMatch(device, destination.texture, { + prefix, + selfContext: "this", + resourceContext: "texture", + }); + const { err } = ops.op_webgpu_write_texture( + device.rid, + { + texture: textureRid, + mipLevel: destination.mipLevel, + origin: destination.origin + ? normalizeGPUOrigin3D(destination.origin) + : undefined, + aspect: destination.aspect, + }, + dataLayout, + normalizeGPUExtent3D(size), + new Uint8Array(ArrayBufferIsView(data) ? data.buffer : data), + ); + device.pushError(err); + } + + copyImageBitmapToTexture(_source, _destination, _copySize) { + throw new Error("Not yet implemented"); + } + + [SymbolFor("Deno.privateCustomInspect")](inspect) { + return `${this.constructor.name} ${ + inspect({ + label: this.label, + }) + }`; + } +} +GPUObjectBaseMixin("GPUQueue", GPUQueue); +const GPUQueuePrototype = GPUQueue.prototype; + +/** + * @typedef CreateGPUBufferOptions + * @property {ArrayBuffer | null} mapping + * @property {number[] | null} mappingRange + * @property {[ArrayBuffer, number, number][] | null} mappedRanges + * @property {"mapped" | "mapped at creation" | "mapped pending" | "unmapped" | "destroy" } state + */ + +/** + * @param {string | null} label + * @param {InnerGPUDevice} device + * @param {number} rid + * @param {number} size + * @param {number} usage + * @param {CreateGPUBufferOptions} options + * @returns {GPUBuffer} + */ +function createGPUBuffer(label, device, rid, size, usage, options) { + /** @type {GPUBuffer} */ + const buffer = webidl.createBranded(GPUBuffer); + buffer[_label] = label; + buffer[_device] = device; + buffer[_rid] = rid; + buffer[_size] = size; + buffer[_usage] = usage; + buffer[_mappingRange] = options.mappingRange; + buffer[_mappedRanges] = options.mappedRanges; + buffer[_state] = options.state; + return buffer; +} + +class GPUBuffer { + /** @type {InnerGPUDevice} */ + [_device]; + /** @type {number} */ + [_rid]; + /** @type {number} */ + [_size]; + /** @type {number} */ + [_usage]; + /** @type {"mapped" | "mapped at creation" | "pending" | "unmapped" | "destroy"} */ + [_state]; + /** @type {[number, number] | null} */ + [_mappingRange]; + /** @type {[ArrayBuffer, number, number][] | null} */ + [_mappedRanges]; + /** @type {number} */ + [_mapMode]; + + [_cleanup]() { + const mappedRanges = this[_mappedRanges]; + if (mappedRanges) { + while (mappedRanges.length > 0) { + const mappedRange = ArrayPrototypePop(mappedRanges); + if (mappedRange !== undefined) { + core.close(mappedRange[1]); + } + } + } + const rid = this[_rid]; + if (rid !== undefined) { + core.close(rid); + /** @type {number | undefined} */ + this[_rid] = undefined; + } + this[_state] = "destroy"; + } + + constructor() { + webidl.illegalConstructor(); + } + + get size() { + webidl.assertBranded(this, GPUBufferPrototype); + return this[_size]; + } + + get usage() { + webidl.assertBranded(this, GPUBufferPrototype); + return this[_usage]; + } + + get mapState() { + webidl.assertBranded(this, GPUBufferPrototype); + const state = this[_state]; + if (state === "mapped at creation") { + return "mapped"; + } else { + return state; + } + } + + /** + * @param {number} mode + * @param {number} offset + * @param {number} [size] + */ + async mapAsync(mode, offset = 0, size) { + webidl.assertBranded(this, GPUBufferPrototype); + const prefix = "Failed to execute 'mapAsync' on 'GPUBuffer'"; + webidl.requiredArguments(arguments.length, 1, { prefix }); + mode = webidl.converters.GPUMapModeFlags(mode, { + prefix, + context: "Argument 1", + }); + offset = webidl.converters.GPUSize64(offset, { + prefix, + context: "Argument 2", + }); + size = size === undefined ? undefined : webidl.converters.GPUSize64(size, { + prefix, + context: "Argument 3", + }); + const device = assertDevice(this, { prefix, context: "this" }); + const bufferRid = assertResource(this, { prefix, context: "this" }); + /** @type {number} */ + let rangeSize; + if (size === undefined) { + rangeSize = MathMax(0, this[_size] - offset); + } else { + rangeSize = this[_size]; + } + if ((offset % 8) !== 0) { + throw new DOMException( + `${prefix}: offset must be a multiple of 8.`, + "OperationError", + ); + } + if ((rangeSize % 4) !== 0) { + throw new DOMException( + `${prefix}: rangeSize must be a multiple of 4.`, + "OperationError", + ); + } + if ((offset + rangeSize) > this[_size]) { + throw new DOMException( + `${prefix}: offset + rangeSize must be less than or equal to buffer size.`, + "OperationError", + ); + } + if (this[_state] !== "unmapped") { + throw new DOMException( + `${prefix}: GPUBuffer is not currently unmapped.`, + "OperationError", + ); + } + const readMode = (mode & 0x0001) === 0x0001; + const writeMode = (mode & 0x0002) === 0x0002; + if ((readMode && writeMode) || (!readMode && !writeMode)) { + throw new DOMException( + `${prefix}: exactly one of READ or WRITE map mode must be set.`, + "OperationError", + ); + } + if (readMode && !((this[_usage] && 0x0001) === 0x0001)) { + throw new DOMException( + `${prefix}: READ map mode not valid because buffer does not have MAP_READ usage.`, + "OperationError", + ); + } + if (writeMode && !((this[_usage] && 0x0002) === 0x0002)) { + throw new DOMException( + `${prefix}: WRITE map mode not valid because buffer does not have MAP_WRITE usage.`, + "OperationError", + ); + } + + this[_mapMode] = mode; + this[_state] = "pending"; + const promise = PromisePrototypeThen( + core.opAsync( + "op_webgpu_buffer_get_map_async", + bufferRid, + device.rid, + mode, + offset, + rangeSize, + ), + ({ err }) => err, + ); + device.pushErrorPromise(promise); + const err = await promise; + if (err) { + throw new DOMException("validation error occured", "OperationError"); + } + this[_state] = "mapped"; + this[_mappingRange] = [offset, offset + rangeSize]; + /** @type {[ArrayBuffer, number, number][] | null} */ + this[_mappedRanges] = []; + } + + /** + * @param {number} offset + * @param {number} size + */ + getMappedRange(offset = 0, size) { + webidl.assertBranded(this, GPUBufferPrototype); + const prefix = "Failed to execute 'getMappedRange' on 'GPUBuffer'"; + offset = webidl.converters.GPUSize64(offset, { + prefix, + context: "Argument 1", + }); + if (size !== undefined) { + size = webidl.converters.GPUSize64(size, { + prefix, + context: "Argument 2", + }); + } + assertDevice(this, { prefix, context: "this" }); + const bufferRid = assertResource(this, { prefix, context: "this" }); + /** @type {number} */ + let rangeSize; + if (size === undefined) { + rangeSize = MathMax(0, this[_size] - offset); + } else { + rangeSize = size; + } + + const mappedRanges = this[_mappedRanges]; + if (!mappedRanges) { + throw new DOMException(`${prefix}: invalid state.`, "OperationError"); + } + for (let i = 0; i < mappedRanges.length; ++i) { + const { 0: buffer, /* 1: rid, */ 2: start } = mappedRanges[i]; + // TODO(lucacasonato): is this logic correct? + const end = start + buffer.byteLength; + if ( + (start >= offset && start < (offset + rangeSize)) || + (end >= offset && end < (offset + rangeSize)) + ) { + throw new DOMException( + `${prefix}: requested buffer overlaps with another mapped range.`, + "OperationError", + ); + } + } + + const buffer = new ArrayBuffer(rangeSize); + const { rid } = ops.op_webgpu_buffer_get_mapped_range( + bufferRid, + offset, + size, + new Uint8Array(buffer), + ); + + ArrayPrototypePush(mappedRanges, [buffer, rid, offset]); + + return buffer; + } + + unmap() { + webidl.assertBranded(this, GPUBufferPrototype); + const prefix = "Failed to execute 'unmap' on 'GPUBuffer'"; + const device = assertDevice(this, { prefix, context: "this" }); + const bufferRid = assertResource(this, { prefix, context: "this" }); + if (this[_state] === "unmapped" || this[_state] === "destroyed") { + throw new DOMException( + `${prefix}: buffer is not ready to be unmapped.`, + "OperationError", + ); + } + if (this[_state] === "pending") { + // TODO(lucacasonato): this is not spec compliant. + throw new DOMException( + `${prefix}: can not unmap while mapping. This is a Deno limitation.`, + "OperationError", + ); + } else if ( + this[_state] === "mapped" || this[_state] === "mapped at creation" + ) { + /** @type {boolean} */ + let write = false; + if (this[_state] === "mapped at creation") { + write = true; + } else if (this[_state] === "mapped") { + const mapMode = this[_mapMode]; + if (mapMode === undefined) { + throw new DOMException( + `${prefix}: invalid state.`, + "OperationError", + ); + } + if ((mapMode & 0x0002) === 0x0002) { + write = true; + } + } + + const mappedRanges = this[_mappedRanges]; + if (!mappedRanges) { + throw new DOMException(`${prefix}: invalid state.`, "OperationError"); + } + for (let i = 0; i < mappedRanges.length; ++i) { + const { 0: buffer, 1: mappedRid } = mappedRanges[i]; + const { err } = ops.op_webgpu_buffer_unmap( + bufferRid, + mappedRid, + ...new SafeArrayIterator(write ? [new Uint8Array(buffer)] : []), + ); + device.pushError(err); + if (err) return; + } + this[_mappingRange] = null; + this[_mappedRanges] = null; + } + + this[_state] = "unmapped"; + } + + destroy() { + webidl.assertBranded(this, GPUBufferPrototype); + this[_cleanup](); + } + + [SymbolFor("Deno.privateCustomInspect")](inspect) { + return `${this.constructor.name} ${ + inspect({ + label: this.label, + }) + }`; + } +} +GPUObjectBaseMixin("GPUBuffer", GPUBuffer); +const GPUBufferPrototype = GPUBuffer.prototype; + +class GPUBufferUsage { + constructor() { + webidl.illegalConstructor(); + } + + static get MAP_READ() { + return 0x0001; + } + static get MAP_WRITE() { + return 0x0002; + } + static get COPY_SRC() { + return 0x0004; + } + static get COPY_DST() { + return 0x0008; + } + static get INDEX() { + return 0x0010; + } + static get VERTEX() { + return 0x0020; + } + static get UNIFORM() { + return 0x0040; + } + static get STORAGE() { + return 0x0080; + } + static get INDIRECT() { + return 0x0100; + } + static get QUERY_RESOLVE() { + return 0x0200; + } +} + +class GPUMapMode { + constructor() { + webidl.illegalConstructor(); + } + + static get READ() { + return 0x0001; + } + static get WRITE() { + return 0x0002; + } +} + +/** + * @param {GPUTextureDescriptor} descriptor + * @param {InnerGPUDevice} device + * @param {number} rid + * @returns {GPUTexture} + */ +function createGPUTexture(descriptor, device, rid, Type = GPUTexture) { + /** @type {GPUTexture} */ + const texture = webidl.createBranded(Type); + texture[_label] = descriptor.label; + texture[_device] = device; + texture[_rid] = rid; + texture[_views] = []; + texture[_width] = descriptor.size.width; + texture[_height] = descriptor.size.height; + texture[_depthOrArrayLayers] = descriptor.size.depthOrArrayLayers; + texture[_mipLevelCount] = descriptor.mipLevelCount; + texture[_sampleCount] = descriptor.sampleCount; + texture[_dimension] = descriptor.dimension; + texture[_format] = descriptor.format; + texture[_usage] = descriptor.usage; + return texture; +} + +class GPUTexture { + /** @type {InnerGPUDevice} */ + [_device]; + /** @type {number | undefined} */ + [_rid]; + /** @type {WeakRef[]} */ + [_views]; + + /** @type {number} */ + [_width]; + /** @type {number} */ + [_height]; + /** @type {number} */ + [_depthOrArrayLayers]; + /** @type {number} */ + [_mipLevelCount]; + /** @type {number} */ + [_sampleCount]; + /** @type {GPUTextureDimension} */ + [_dimension]; + /** @type {GPUTextureFormat} */ + [_format]; + /** @type {number} */ + [_usage]; + + [_cleanup]() { + const views = this[_views]; + while (views.length > 0) { + const view = ArrayPrototypePop(views)?.deref(); + if (view) { + view[_cleanup](); + } + } + const rid = this[_rid]; + if (rid !== undefined) { + core.close(rid); + /** @type {number | undefined} */ + this[_rid] = undefined; + } + } + + constructor() { + webidl.illegalConstructor(); + } + + /** + * @param {GPUTextureViewDescriptor} descriptor + */ + createView(descriptor = {}) { + webidl.assertBranded(this, GPUTexturePrototype); + const prefix = "Failed to execute 'createView' on 'GPUTexture'"; + webidl.requiredArguments(arguments.length, 0, { prefix }); + descriptor = webidl.converters.GPUTextureViewDescriptor(descriptor, { + prefix, + context: "Argument 1", + }); + const device = assertDevice(this, { prefix, context: "this" }); + const textureRid = assertResource(this, { prefix, context: "this" }); + const { rid, err } = ops.op_webgpu_create_texture_view({ + textureRid, + ...descriptor, + }); + device.pushError(err); + + const textureView = createGPUTextureView( + descriptor.label, + this, + rid, + ); + ArrayPrototypePush(this[_views], new WeakRef(textureView)); + return textureView; + } + + destroy() { + webidl.assertBranded(this, GPUTexturePrototype); + this[_cleanup](); + } + + get width() { + webidl.assertBranded(this, GPUTexturePrototype); + return this[_width]; + } + + get height() { + webidl.assertBranded(this, GPUTexturePrototype); + return this[_height]; + } + + get depthOrArrayLayers() { + webidl.assertBranded(this, GPUTexturePrototype); + return this[_depthOrArrayLayers]; + } + + get mipLevelCount() { + webidl.assertBranded(this, GPUTexturePrototype); + return this[_mipLevelCount]; + } + + get sampleCount() { + webidl.assertBranded(this, GPUTexturePrototype); + return this[_sampleCount]; + } + + get dimension() { + webidl.assertBranded(this, GPUTexturePrototype); + return this[_dimension]; + } + + get format() { + webidl.assertBranded(this, GPUTexturePrototype); + return this[_format]; + } + + get usage() { + webidl.assertBranded(this, GPUTexturePrototype); + return this[_usage]; + } + + [SymbolFor("Deno.privateCustomInspect")](inspect) { + return `${this.constructor.name} ${ + inspect({ + label: this.label, + }) + }`; + } +} +GPUObjectBaseMixin("GPUTexture", GPUTexture); +const GPUTexturePrototype = GPUTexture.prototype; + +class GPUTextureUsage { + constructor() { + webidl.illegalConstructor(); + } + + static get COPY_SRC() { + return 0x01; + } + static get COPY_DST() { + return 0x02; + } + static get TEXTURE_BINDING() { + return 0x04; + } + static get STORAGE_BINDING() { + return 0x08; + } + static get RENDER_ATTACHMENT() { + return 0x10; + } +} + +/** + * @param {string | null} label + * @param {GPUTexture} texture + * @param {number} rid + * @returns {GPUTextureView} + */ +function createGPUTextureView(label, texture, rid) { + /** @type {GPUTextureView} */ + const textureView = webidl.createBranded(GPUTextureView); + textureView[_label] = label; + textureView[_texture] = texture; + textureView[_rid] = rid; + return textureView; +} +class GPUTextureView { + /** @type {GPUTexture} */ + [_texture]; + /** @type {number | undefined} */ + [_rid]; + + [_cleanup]() { + const rid = this[_rid]; + if (rid !== undefined) { + core.close(rid); + /** @type {number | undefined} */ + this[_rid] = undefined; + } + } + + constructor() { + webidl.illegalConstructor(); + } + + [SymbolFor("Deno.privateCustomInspect")](inspect) { + return `${this.constructor.name} ${ + inspect({ + label: this.label, + }) + }`; + } +} +GPUObjectBaseMixin("GPUTextureView", GPUTextureView); +const GPUTextureViewPrototype = GPUTextureView.prototype; +/** + * @param {string | null} label + * @param {InnerGPUDevice} device + * @param {number} rid + * @returns {GPUSampler} + */ +function createGPUSampler(label, device, rid) { + /** @type {GPUSampler} */ + const sampler = webidl.createBranded(GPUSampler); + sampler[_label] = label; + sampler[_device] = device; + sampler[_rid] = rid; + return sampler; +} +class GPUSampler { + /** @type {InnerGPUDevice} */ + [_device]; + /** @type {number | undefined} */ + [_rid]; + + [_cleanup]() { + const rid = this[_rid]; + if (rid !== undefined) { + core.close(rid); + /** @type {number | undefined} */ + this[_rid] = undefined; + } + } + + constructor() { + webidl.illegalConstructor(); + } + + [SymbolFor("Deno.privateCustomInspect")](inspect) { + return `${this.constructor.name} ${ + inspect({ + label: this.label, + }) + }`; + } +} +GPUObjectBaseMixin("GPUSampler", GPUSampler); +const GPUSamplerPrototype = GPUSampler.prototype; +/** + * @param {string | null} label + * @param {InnerGPUDevice} device + * @param {number} rid + * @returns {GPUBindGroupLayout} + */ +function createGPUBindGroupLayout(label, device, rid) { + /** @type {GPUBindGroupLayout} */ + const bindGroupLayout = webidl.createBranded(GPUBindGroupLayout); + bindGroupLayout[_label] = label; + bindGroupLayout[_device] = device; + bindGroupLayout[_rid] = rid; + return bindGroupLayout; +} +class GPUBindGroupLayout { + /** @type {InnerGPUDevice} */ + [_device]; + /** @type {number | undefined} */ + [_rid]; + + [_cleanup]() { + const rid = this[_rid]; + if (rid !== undefined) { + core.close(rid); + /** @type {number | undefined} */ + this[_rid] = undefined; + } + } + + constructor() { + webidl.illegalConstructor(); + } + + [SymbolFor("Deno.privateCustomInspect")](inspect) { + return `${this.constructor.name} ${ + inspect({ + label: this.label, + }) + }`; + } +} +GPUObjectBaseMixin("GPUBindGroupLayout", GPUBindGroupLayout); + +/** + * @param {string | null} label + * @param {InnerGPUDevice} device + * @param {number} rid + * @returns {GPUPipelineLayout} + */ +function createGPUPipelineLayout(label, device, rid) { + /** @type {GPUPipelineLayout} */ + const pipelineLayout = webidl.createBranded(GPUPipelineLayout); + pipelineLayout[_label] = label; + pipelineLayout[_device] = device; + pipelineLayout[_rid] = rid; + return pipelineLayout; +} +class GPUPipelineLayout { + /** @type {InnerGPUDevice} */ + [_device]; + /** @type {number | undefined} */ + [_rid]; + + [_cleanup]() { + const rid = this[_rid]; + if (rid !== undefined) { + core.close(rid); + /** @type {number | undefined} */ + this[_rid] = undefined; + } + } + + constructor() { + webidl.illegalConstructor(); + } + + [SymbolFor("Deno.privateCustomInspect")](inspect) { + return `${this.constructor.name} ${ + inspect({ + label: this.label, + }) + }`; + } +} +GPUObjectBaseMixin("GPUPipelineLayout", GPUPipelineLayout); + +/** + * @param {string | null} label + * @param {InnerGPUDevice} device + * @param {number} rid + * @returns {GPUBindGroup} + */ +function createGPUBindGroup(label, device, rid) { + /** @type {GPUBindGroup} */ + const bindGroup = webidl.createBranded(GPUBindGroup); + bindGroup[_label] = label; + bindGroup[_device] = device; + bindGroup[_rid] = rid; + return bindGroup; +} +class GPUBindGroup { + /** @type {InnerGPUDevice} */ + [_device]; + /** @type {number | undefined} */ + [_rid]; + + [_cleanup]() { + const rid = this[_rid]; + if (rid !== undefined) { + core.close(rid); + /** @type {number | undefined} */ + this[_rid] = undefined; + } + } + + constructor() { + webidl.illegalConstructor(); + } + + [SymbolFor("Deno.privateCustomInspect")](inspect) { + return `${this.constructor.name} ${ + inspect({ + label: this.label, + }) + }`; + } +} +GPUObjectBaseMixin("GPUBindGroup", GPUBindGroup); + +/** + * @param {string | null} label + * @param {InnerGPUDevice} device + * @param {number} rid + * @returns {GPUShaderModule} + */ +function createGPUShaderModule(label, device, rid) { + /** @type {GPUShaderModule} */ + const bindGroup = webidl.createBranded(GPUShaderModule); + bindGroup[_label] = label; + bindGroup[_device] = device; + bindGroup[_rid] = rid; + return bindGroup; +} +class GPUShaderModule { + /** @type {InnerGPUDevice} */ + [_device]; + /** @type {number | undefined} */ + [_rid]; + + [_cleanup]() { + const rid = this[_rid]; + if (rid !== undefined) { + core.close(rid); + /** @type {number | undefined} */ + this[_rid] = undefined; + } + } + + constructor() { + webidl.illegalConstructor(); + } + + compilationInfo() { + throw new Error("Not yet implemented"); + } + + [SymbolFor("Deno.privateCustomInspect")](inspect) { + return `${this.constructor.name} ${ + inspect({ + label: this.label, + }) + }`; + } +} +GPUObjectBaseMixin("GPUShaderModule", GPUShaderModule); + +class GPUShaderStage { + constructor() { + webidl.illegalConstructor(); + } + + static get VERTEX() { + return 0x1; + } + + static get FRAGMENT() { + return 0x2; + } + + static get COMPUTE() { + return 0x4; + } +} + +/** + * @param {string | null} label + * @param {InnerGPUDevice} device + * @param {number} rid + * @returns {GPUComputePipeline} + */ +function createGPUComputePipeline(label, device, rid) { + /** @type {GPUComputePipeline} */ + const pipeline = webidl.createBranded(GPUComputePipeline); + pipeline[_label] = label; + pipeline[_device] = device; + pipeline[_rid] = rid; + return pipeline; +} +class GPUComputePipeline { + /** @type {InnerGPUDevice} */ + [_device]; + /** @type {number | undefined} */ + [_rid]; + + [_cleanup]() { + const rid = this[_rid]; + if (rid !== undefined) { + core.close(rid); + /** @type {number | undefined} */ + this[_rid] = undefined; + } + } + + constructor() { + webidl.illegalConstructor(); + } + + /** + * @param {number} index + * @returns {GPUBindGroupLayout} + */ + getBindGroupLayout(index) { + webidl.assertBranded(this, GPUComputePipelinePrototype); + const prefix = + "Failed to execute 'getBindGroupLayout' on 'GPUComputePipeline'"; + webidl.requiredArguments(arguments.length, 1, { prefix }); + index = webidl.converters["unsigned long"](index, { + prefix, + context: "Argument 1", + }); + const device = assertDevice(this, { prefix, context: "this" }); + const computePipelineRid = assertResource(this, { + prefix, + context: "this", + }); + const { rid, label, err } = ops + .op_webgpu_compute_pipeline_get_bind_group_layout( + computePipelineRid, + index, + ); + device.pushError(err); + + const bindGroupLayout = createGPUBindGroupLayout( + label, + device, + rid, + ); + device.trackResource(bindGroupLayout); + return bindGroupLayout; + } + + [SymbolFor("Deno.privateCustomInspect")](inspect) { + return `${this.constructor.name} ${ + inspect({ + label: this.label, + }) + }`; + } +} +GPUObjectBaseMixin("GPUComputePipeline", GPUComputePipeline); +const GPUComputePipelinePrototype = GPUComputePipeline.prototype; + +/** + * @param {string | null} label + * @param {InnerGPUDevice} device + * @param {number} rid + * @returns {GPURenderPipeline} + */ +function createGPURenderPipeline(label, device, rid) { + /** @type {GPURenderPipeline} */ + const pipeline = webidl.createBranded(GPURenderPipeline); + pipeline[_label] = label; + pipeline[_device] = device; + pipeline[_rid] = rid; + return pipeline; +} +class GPURenderPipeline { + /** @type {InnerGPUDevice} */ + [_device]; + /** @type {number | undefined} */ + [_rid]; + + [_cleanup]() { + const rid = this[_rid]; + if (rid !== undefined) { + core.close(rid); + /** @type {number | undefined} */ + this[_rid] = undefined; + } + } + + constructor() { + webidl.illegalConstructor(); + } + + /** + * @param {number} index + */ + getBindGroupLayout(index) { + webidl.assertBranded(this, GPURenderPipelinePrototype); + const prefix = + "Failed to execute 'getBindGroupLayout' on 'GPURenderPipeline'"; + webidl.requiredArguments(arguments.length, 1, { prefix }); + index = webidl.converters["unsigned long"](index, { + prefix, + context: "Argument 1", + }); + const device = assertDevice(this, { prefix, context: "this" }); + const renderPipelineRid = assertResource(this, { + prefix, + context: "this", + }); + const { rid, label, err } = ops + .op_webgpu_render_pipeline_get_bind_group_layout( + renderPipelineRid, + index, + ); + device.pushError(err); + + const bindGroupLayout = createGPUBindGroupLayout( + label, + device, + rid, + ); + device.trackResource(bindGroupLayout); + return bindGroupLayout; + } + + [SymbolFor("Deno.privateCustomInspect")](inspect) { + return `${this.constructor.name} ${ + inspect({ + label: this.label, + }) + }`; + } +} +GPUObjectBaseMixin("GPURenderPipeline", GPURenderPipeline); +const GPURenderPipelinePrototype = GPURenderPipeline.prototype; + +class GPUColorWrite { + constructor() { + webidl.illegalConstructor(); + } + + static get RED() { + return 0x1; + } + static get GREEN() { + return 0x2; + } + static get BLUE() { + return 0x4; + } + static get ALPHA() { + return 0x8; + } + static get ALL() { + return 0xF; + } +} + +/** + * @param {string | null} label + * @param {InnerGPUDevice} device + * @param {number} rid + * @returns {GPUCommandEncoder} + */ +function createGPUCommandEncoder(label, device, rid) { + /** @type {GPUCommandEncoder} */ + const encoder = webidl.createBranded(GPUCommandEncoder); + encoder[_label] = label; + encoder[_device] = device; + encoder[_rid] = rid; + encoder[_encoders] = []; + return encoder; +} +class GPUCommandEncoder { + /** @type {InnerGPUDevice} */ + [_device]; + /** @type {number | undefined} */ + [_rid]; + /** @type {WeakRef[]} */ + [_encoders]; + + [_cleanup]() { + const encoders = this[_encoders]; + while (encoders.length > 0) { + const encoder = ArrayPrototypePop(encoders)?.deref(); + if (encoder) { + encoder[_cleanup](); + } + } + const rid = this[_rid]; + if (rid !== undefined) { + core.close(rid); + /** @type {number | undefined} */ + this[_rid] = undefined; + } + } + + constructor() { + webidl.illegalConstructor(); + } + + /** + * @param {GPURenderPassDescriptor} descriptor + * @return {GPURenderPassEncoder} + */ + beginRenderPass(descriptor) { + webidl.assertBranded(this, GPUCommandEncoderPrototype); + const prefix = "Failed to execute 'beginRenderPass' on 'GPUCommandEncoder'"; + webidl.requiredArguments(arguments.length, 1, { prefix }); + descriptor = webidl.converters.GPURenderPassDescriptor(descriptor, { + prefix, + context: "Argument 1", + }); + const device = assertDevice(this, { prefix, context: "this" }); + const commandEncoderRid = assertResource(this, { + prefix, + context: "this", + }); + + if (this[_rid] === undefined) { + throw new DOMException( + "Failed to execute 'beginRenderPass' on 'GPUCommandEncoder': already consumed", + "OperationError", + ); + } + + let depthStencilAttachment; + if (descriptor.depthStencilAttachment) { + const view = assertResource(descriptor.depthStencilAttachment.view, { + prefix, + context: "texture view for depth stencil attachment", + }); + assertDeviceMatch( + device, + descriptor.depthStencilAttachment.view[_texture], + { + prefix, + resourceContext: "texture view for depth stencil attachment", + selfContext: "this", + }, + ); + + depthStencilAttachment = { + ...descriptor.depthStencilAttachment, + view, + }; + } + const colorAttachments = ArrayPrototypeMap( + descriptor.colorAttachments, + (colorAttachment, i) => { + const context = `color attachment ${i + 1}`; + const view = assertResource(colorAttachment.view, { + prefix, + context: `texture view for ${context}`, + }); + assertResource(colorAttachment.view[_texture], { + prefix, + context: `texture backing texture view for ${context}`, + }); + assertDeviceMatch( + device, + colorAttachment.view[_texture], + { + prefix, + resourceContext: `texture view for ${context}`, + selfContext: "this", + }, + ); + let resolveTarget; + if (colorAttachment.resolveTarget) { + resolveTarget = assertResource( + colorAttachment.resolveTarget, + { + prefix, + context: `resolve target texture view for ${context}`, + }, + ); + assertResource(colorAttachment.resolveTarget[_texture], { + prefix, + context: + `texture backing resolve target texture view for ${context}`, + }); + assertDeviceMatch( + device, + colorAttachment.resolveTarget[_texture], + { + prefix, + resourceContext: `resolve target texture view for ${context}`, + selfContext: "this", + }, + ); + } + return { + view: view, + resolveTarget, + storeOp: colorAttachment.storeOp, + loadOp: colorAttachment.loadOp, + clearValue: normalizeGPUColor(colorAttachment.clearValue), + }; + }, + ); + + const { rid } = ops.op_webgpu_command_encoder_begin_render_pass( + commandEncoderRid, + descriptor.label, + colorAttachments, + depthStencilAttachment, + ); + + const renderPassEncoder = createGPURenderPassEncoder( + descriptor.label, + this, + rid, + ); + ArrayPrototypePush(this[_encoders], new WeakRef(renderPassEncoder)); + return renderPassEncoder; + } + + /** + * @param {GPUComputePassDescriptor} descriptor + */ + beginComputePass(descriptor = {}) { + webidl.assertBranded(this, GPUCommandEncoderPrototype); + const prefix = + "Failed to execute 'beginComputePass' on 'GPUCommandEncoder'"; + descriptor = webidl.converters.GPUComputePassDescriptor(descriptor, { + prefix, + context: "Argument 1", + }); + + assertDevice(this, { prefix, context: "this" }); + const commandEncoderRid = assertResource(this, { + prefix, + context: "this", + }); + + const { rid } = ops.op_webgpu_command_encoder_begin_compute_pass( + commandEncoderRid, + descriptor.label, + ); + + const computePassEncoder = createGPUComputePassEncoder( + descriptor.label, + this, + rid, + ); + ArrayPrototypePush(this[_encoders], new WeakRef(computePassEncoder)); + return computePassEncoder; + } + + /** + * @param {GPUBuffer} source + * @param {number} sourceOffset + * @param {GPUBuffer} destination + * @param {number} destinationOffset + * @param {number} size + */ + copyBufferToBuffer( + source, + sourceOffset, + destination, + destinationOffset, + size, + ) { + webidl.assertBranded(this, GPUCommandEncoderPrototype); + const prefix = + "Failed to execute 'copyBufferToBuffer' on 'GPUCommandEncoder'"; + webidl.requiredArguments(arguments.length, 5, { prefix }); + source = webidl.converters.GPUBuffer(source, { + prefix, + context: "Argument 1", + }); + sourceOffset = webidl.converters.GPUSize64(sourceOffset, { + prefix, + context: "Argument 2", + }); + destination = webidl.converters.GPUBuffer(destination, { + prefix, + context: "Argument 3", + }); + destinationOffset = webidl.converters.GPUSize64(destinationOffset, { + prefix, + context: "Argument 4", + }); + size = webidl.converters.GPUSize64(size, { + prefix, + context: "Argument 5", + }); + const device = assertDevice(this, { prefix, context: "this" }); + const commandEncoderRid = assertResource(this, { + prefix, + context: "this", + }); + const sourceRid = assertResource(source, { + prefix, + context: "Argument 1", + }); + assertDeviceMatch(device, source, { + prefix, + resourceContext: "Argument 1", + selfContext: "this", + }); + const destinationRid = assertResource(destination, { + prefix, + context: "Argument 3", + }); + assertDeviceMatch(device, destination, { + prefix, + resourceContext: "Argument 3", + selfContext: "this", + }); + + const { err } = ops.op_webgpu_command_encoder_copy_buffer_to_buffer( + commandEncoderRid, + sourceRid, + sourceOffset, + destinationRid, + destinationOffset, + size, + ); + device.pushError(err); + } + + /** + * @param {GPUImageCopyBuffer} source + * @param {GPUImageCopyTexture} destination + * @param {GPUExtent3D} copySize + */ + copyBufferToTexture(source, destination, copySize) { + webidl.assertBranded(this, GPUCommandEncoderPrototype); + const prefix = + "Failed to execute 'copyBufferToTexture' on 'GPUCommandEncoder'"; + webidl.requiredArguments(arguments.length, 3, { prefix }); + source = webidl.converters.GPUImageCopyBuffer(source, { + prefix, + context: "Argument 1", + }); + destination = webidl.converters.GPUImageCopyTexture(destination, { + prefix, + context: "Argument 2", + }); + copySize = webidl.converters.GPUExtent3D(copySize, { + prefix, + context: "Argument 3", + }); + const device = assertDevice(this, { prefix, context: "this" }); + const commandEncoderRid = assertResource(this, { + prefix, + context: "this", + }); + const sourceBufferRid = assertResource(source.buffer, { + prefix, + context: "source in Argument 1", + }); + assertDeviceMatch(device, source.buffer, { + prefix, + resourceContext: "source in Argument 1", + selfContext: "this", + }); + const destinationTextureRid = assertResource(destination.texture, { + prefix, + context: "texture in Argument 2", + }); + assertDeviceMatch(device, destination.texture, { + prefix, + resourceContext: "texture in Argument 2", + selfContext: "this", + }); + + const { err } = ops.op_webgpu_command_encoder_copy_buffer_to_texture( + commandEncoderRid, + { + ...source, + buffer: sourceBufferRid, + }, + { + texture: destinationTextureRid, + mipLevel: destination.mipLevel, + origin: destination.origin + ? normalizeGPUOrigin3D(destination.origin) + : undefined, + aspect: destination.aspect, + }, + normalizeGPUExtent3D(copySize), + ); + device.pushError(err); + } + + /** + * @param {GPUImageCopyTexture} source + * @param {GPUImageCopyBuffer} destination + * @param {GPUExtent3D} copySize + */ + copyTextureToBuffer(source, destination, copySize) { + webidl.assertBranded(this, GPUCommandEncoderPrototype); + const prefix = + "Failed to execute 'copyTextureToBuffer' on 'GPUCommandEncoder'"; + webidl.requiredArguments(arguments.length, 3, { prefix }); + source = webidl.converters.GPUImageCopyTexture(source, { + prefix, + context: "Argument 1", + }); + destination = webidl.converters.GPUImageCopyBuffer(destination, { + prefix, + context: "Argument 2", + }); + copySize = webidl.converters.GPUExtent3D(copySize, { + prefix, + context: "Argument 3", + }); + const device = assertDevice(this, { prefix, context: "this" }); + const commandEncoderRid = assertResource(this, { + prefix, + context: "this", + }); + const sourceTextureRid = assertResource(source.texture, { + prefix, + context: "texture in Argument 1", + }); + assertDeviceMatch(device, source.texture, { + prefix, + resourceContext: "texture in Argument 1", + selfContext: "this", + }); + const destinationBufferRid = assertResource(destination.buffer, { + prefix, + context: "buffer in Argument 2", + }); + assertDeviceMatch(device, destination.buffer, { + prefix, + resourceContext: "buffer in Argument 2", + selfContext: "this", + }); + const { err } = ops.op_webgpu_command_encoder_copy_texture_to_buffer( + commandEncoderRid, + { + texture: sourceTextureRid, + mipLevel: source.mipLevel, + origin: source.origin ? normalizeGPUOrigin3D(source.origin) : undefined, + aspect: source.aspect, + }, + { + ...destination, + buffer: destinationBufferRid, + }, + normalizeGPUExtent3D(copySize), + ); + device.pushError(err); + } + + /** + * @param {GPUImageCopyTexture} source + * @param {GPUImageCopyTexture} destination + * @param {GPUExtent3D} copySize + */ + copyTextureToTexture(source, destination, copySize) { + webidl.assertBranded(this, GPUCommandEncoderPrototype); + const prefix = + "Failed to execute 'copyTextureToTexture' on 'GPUCommandEncoder'"; + webidl.requiredArguments(arguments.length, 3, { prefix }); + source = webidl.converters.GPUImageCopyTexture(source, { + prefix, + context: "Argument 1", + }); + destination = webidl.converters.GPUImageCopyTexture(destination, { + prefix, + context: "Argument 2", + }); + copySize = webidl.converters.GPUExtent3D(copySize, { + prefix, + context: "Argument 3", + }); + const device = assertDevice(this, { prefix, context: "this" }); + const commandEncoderRid = assertResource(this, { + prefix, + context: "this", + }); + const sourceTextureRid = assertResource(source.texture, { + prefix, + context: "texture in Argument 1", + }); + assertDeviceMatch(device, source.texture, { + prefix, + resourceContext: "texture in Argument 1", + selfContext: "this", + }); + const destinationTextureRid = assertResource(destination.texture, { + prefix, + context: "texture in Argument 2", + }); + assertDeviceMatch(device, destination.texture, { + prefix, + resourceContext: "texture in Argument 2", + selfContext: "this", + }); + const { err } = ops.op_webgpu_command_encoder_copy_texture_to_texture( + commandEncoderRid, + { + texture: sourceTextureRid, + mipLevel: source.mipLevel, + origin: source.origin ? normalizeGPUOrigin3D(source.origin) : undefined, + aspect: source.aspect, + }, + { + texture: destinationTextureRid, + mipLevel: destination.mipLevel, + origin: destination.origin + ? normalizeGPUOrigin3D(destination.origin) + : undefined, + aspect: source.aspect, + }, + normalizeGPUExtent3D(copySize), + ); + device.pushError(err); + } + + /** + * @param {GPUBuffer} buffer + * @param {GPUSize64} offset + * @param {GPUSize64} size + */ + clearBuffer(buffer, offset = 0, size = undefined) { + webidl.assertBranded(this, GPUCommandEncoderPrototype); + const prefix = "Failed to execute 'clearBuffer' on 'GPUCommandEncoder'"; + webidl.requiredArguments(arguments.length, 3, { prefix }); + buffer = webidl.converters.GPUBuffer(buffer, { + prefix, + context: "Argument 1", + }); + offset = webidl.converters.GPUSize64(offset, { + prefix, + context: "Argument 2", + }); + size = webidl.converters.GPUSize64(size, { + prefix, + context: "Argument 3", + }); + const device = assertDevice(this, { prefix, context: "this" }); + const commandEncoderRid = assertResource(this, { + prefix, + context: "this", + }); + const bufferRid = assertResource(buffer, { + prefix, + context: "Argument 1", + }); + const { err } = ops.op_webgpu_command_encoder_clear_buffer( + commandEncoderRid, + bufferRid, + offset, + size, + ); + device.pushError(err); + } + + /** + * @param {string} groupLabel + */ + pushDebugGroup(groupLabel) { + webidl.assertBranded(this, GPUCommandEncoderPrototype); + const prefix = "Failed to execute 'pushDebugGroup' on 'GPUCommandEncoder'"; + webidl.requiredArguments(arguments.length, 1, { prefix }); + groupLabel = webidl.converters.USVString(groupLabel, { + prefix, + context: "Argument 1", + }); + const device = assertDevice(this, { prefix, context: "this" }); + const commandEncoderRid = assertResource(this, { + prefix, + context: "this", + }); + const { err } = ops.op_webgpu_command_encoder_push_debug_group( + commandEncoderRid, + groupLabel, + ); + device.pushError(err); + } + + popDebugGroup() { + webidl.assertBranded(this, GPUCommandEncoderPrototype); + const prefix = "Failed to execute 'popDebugGroup' on 'GPUCommandEncoder'"; + const device = assertDevice(this, { prefix, context: "this" }); + const commandEncoderRid = assertResource(this, { + prefix, + context: "this", + }); + const { err } = ops.op_webgpu_command_encoder_pop_debug_group( + commandEncoderRid, + ); + device.pushError(err); + } + + /** + * @param {string} markerLabel + */ + insertDebugMarker(markerLabel) { + webidl.assertBranded(this, GPUCommandEncoderPrototype); + const prefix = + "Failed to execute 'insertDebugMarker' on 'GPUCommandEncoder'"; + webidl.requiredArguments(arguments.length, 1, { prefix }); + markerLabel = webidl.converters.USVString(markerLabel, { + prefix, + context: "Argument 1", + }); + const device = assertDevice(this, { prefix, context: "this" }); + const commandEncoderRid = assertResource(this, { + prefix, + context: "this", + }); + const { err } = ops.op_webgpu_command_encoder_insert_debug_marker( + commandEncoderRid, + markerLabel, + ); + device.pushError(err); + } + + /** + * @param {GPUQuerySet} querySet + * @param {number} queryIndex + */ + writeTimestamp(querySet, queryIndex) { + webidl.assertBranded(this, GPUCommandEncoderPrototype); + const prefix = "Failed to execute 'writeTimestamp' on 'GPUCommandEncoder'"; + webidl.requiredArguments(arguments.length, 2, { prefix }); + querySet = webidl.converters.GPUQuerySet(querySet, { + prefix, + context: "Argument 1", + }); + queryIndex = webidl.converters.GPUSize32(queryIndex, { + prefix, + context: "Argument 2", + }); + const device = assertDevice(this, { prefix, context: "this" }); + const commandEncoderRid = assertResource(this, { + prefix, + context: "this", + }); + const querySetRid = assertResource(querySet, { + prefix, + context: "Argument 1", + }); + assertDeviceMatch(device, querySet, { + prefix, + resourceContext: "Argument 1", + selfContext: "this", + }); + const { err } = ops.op_webgpu_command_encoder_write_timestamp( + commandEncoderRid, + querySetRid, + queryIndex, + ); + device.pushError(err); + } + + /** + * @param {GPUQuerySet} querySet + * @param {number} firstQuery + * @param {number} queryCount + * @param {GPUBuffer} destination + * @param {number} destinationOffset + */ + resolveQuerySet( + querySet, + firstQuery, + queryCount, + destination, + destinationOffset, + ) { + webidl.assertBranded(this, GPUCommandEncoderPrototype); + const prefix = "Failed to execute 'resolveQuerySet' on 'GPUCommandEncoder'"; + webidl.requiredArguments(arguments.length, 5, { prefix }); + querySet = webidl.converters.GPUQuerySet(querySet, { + prefix, + context: "Argument 1", + }); + firstQuery = webidl.converters.GPUSize32(firstQuery, { + prefix, + context: "Argument 2", + }); + queryCount = webidl.converters.GPUSize32(queryCount, { + prefix, + context: "Argument 3", + }); + destination = webidl.converters.GPUBuffer(destination, { + prefix, + context: "Argument 4", + }); + destinationOffset = webidl.converters.GPUSize64(destinationOffset, { + prefix, + context: "Argument 5", + }); + const device = assertDevice(this, { prefix, context: "this" }); + const commandEncoderRid = assertResource(this, { + prefix, + context: "this", + }); + const querySetRid = assertResource(querySet, { + prefix, + context: "Argument 1", + }); + assertDeviceMatch(device, querySet, { + prefix, + resourceContext: "Argument 1", + selfContext: "this", + }); + const destinationRid = assertResource(destination, { + prefix, + context: "Argument 3", + }); + assertDeviceMatch(device, destination, { + prefix, + resourceContext: "Argument 3", + selfContext: "this", + }); + const { err } = ops.op_webgpu_command_encoder_resolve_query_set( + commandEncoderRid, + querySetRid, + firstQuery, + queryCount, + destinationRid, + destinationOffset, + ); + device.pushError(err); + } + + /** + * @param {GPUCommandBufferDescriptor} descriptor + * @returns {GPUCommandBuffer} + */ + finish(descriptor = {}) { + webidl.assertBranded(this, GPUCommandEncoderPrototype); + const prefix = "Failed to execute 'finish' on 'GPUCommandEncoder'"; + descriptor = webidl.converters.GPUCommandBufferDescriptor(descriptor, { + prefix, + context: "Argument 1", + }); + const device = assertDevice(this, { prefix, context: "this" }); + const commandEncoderRid = assertResource(this, { + prefix, + context: "this", + }); + const { rid, err } = ops.op_webgpu_command_encoder_finish( + commandEncoderRid, + descriptor.label, + ); + device.pushError(err); + /** @type {number | undefined} */ + this[_rid] = undefined; + + const commandBuffer = createGPUCommandBuffer( + descriptor.label, + device, + rid, + ); + device.trackResource(commandBuffer); + return commandBuffer; + } + + [SymbolFor("Deno.privateCustomInspect")](inspect) { + return `${this.constructor.name} ${ + inspect({ + label: this.label, + }) + }`; + } +} +GPUObjectBaseMixin("GPUCommandEncoder", GPUCommandEncoder); +const GPUCommandEncoderPrototype = GPUCommandEncoder.prototype; + +/** + * @param {string | null} label + * @param {GPUCommandEncoder} encoder + * @param {number} rid + * @returns {GPURenderPassEncoder} + */ +function createGPURenderPassEncoder(label, encoder, rid) { + /** @type {GPURenderPassEncoder} */ + const passEncoder = webidl.createBranded(GPURenderPassEncoder); + passEncoder[_label] = label; + passEncoder[_encoder] = encoder; + passEncoder[_rid] = rid; + return passEncoder; +} + +class GPURenderPassEncoder { + /** @type {GPUCommandEncoder} */ + [_encoder]; + /** @type {number | undefined} */ + [_rid]; + + [_cleanup]() { + const rid = this[_rid]; + if (rid !== undefined) { + core.close(rid); + /** @type {number | undefined} */ + this[_rid] = undefined; + } + } + + constructor() { + webidl.illegalConstructor(); + } + + /** + * @param {number} x + * @param {number} y + * @param {number} width + * @param {number} height + * @param {number} minDepth + * @param {number} maxDepth + */ + setViewport(x, y, width, height, minDepth, maxDepth) { + webidl.assertBranded(this, GPURenderPassEncoderPrototype); + const prefix = "Failed to execute 'setViewport' on 'GPUComputePassEncoder'"; + webidl.requiredArguments(arguments.length, 6, { prefix }); + x = webidl.converters.float(x, { prefix, context: "Argument 1" }); + y = webidl.converters.float(y, { prefix, context: "Argument 2" }); + width = webidl.converters.float(width, { prefix, context: "Argument 3" }); + height = webidl.converters.float(height, { + prefix, + context: "Argument 4", + }); + minDepth = webidl.converters.float(minDepth, { + prefix, + context: "Argument 5", + }); + maxDepth = webidl.converters.float(maxDepth, { + prefix, + context: "Argument 6", + }); + assertDevice(this[_encoder], { + prefix, + context: "encoder referenced by this", + }); + assertResource(this[_encoder], { + prefix, + context: "encoder referenced by this", + }); + const renderPassRid = assertResource(this, { prefix, context: "this" }); + ops.op_webgpu_render_pass_set_viewport({ + renderPassRid, + x, + y, + width, + height, + minDepth, + maxDepth, + }); + } + + /** + * @param {number} x + * @param {number} y + * @param {number} width + * @param {number} height + */ + setScissorRect(x, y, width, height) { + webidl.assertBranded(this, GPURenderPassEncoderPrototype); + const prefix = + "Failed to execute 'setScissorRect' on 'GPUComputePassEncoder'"; + webidl.requiredArguments(arguments.length, 4, { prefix }); + x = webidl.converters.GPUIntegerCoordinate(x, { + prefix, + context: "Argument 1", + }); + y = webidl.converters.GPUIntegerCoordinate(y, { + prefix, + context: "Argument 2", + }); + width = webidl.converters.GPUIntegerCoordinate(width, { + prefix, + context: "Argument 3", + }); + height = webidl.converters.GPUIntegerCoordinate(height, { + prefix, + context: "Argument 4", + }); + assertDevice(this[_encoder], { + prefix, + context: "encoder referenced by this", + }); + assertResource(this[_encoder], { + prefix, + context: "encoder referenced by this", + }); + const renderPassRid = assertResource(this, { prefix, context: "this" }); + ops.op_webgpu_render_pass_set_scissor_rect( + renderPassRid, + x, + y, + width, + height, + ); + } + + /** + * @param {GPUColor} color + */ + setBlendConstant(color) { + webidl.assertBranded(this, GPURenderPassEncoderPrototype); + const prefix = + "Failed to execute 'setBlendConstant' on 'GPUComputePassEncoder'"; + webidl.requiredArguments(arguments.length, 1, { prefix }); + color = webidl.converters.GPUColor(color, { + prefix, + context: "Argument 1", + }); + assertDevice(this[_encoder], { + prefix, + context: "encoder referenced by this", + }); + assertResource(this[_encoder], { + prefix, + context: "encoder referenced by this", + }); + const renderPassRid = assertResource(this, { prefix, context: "this" }); + ops.op_webgpu_render_pass_set_blend_constant( + renderPassRid, + normalizeGPUColor(color), + ); + } + + /** + * @param {number} reference + */ + setStencilReference(reference) { + webidl.assertBranded(this, GPURenderPassEncoderPrototype); + const prefix = + "Failed to execute 'setStencilReference' on 'GPUComputePassEncoder'"; + webidl.requiredArguments(arguments.length, 1, { prefix }); + reference = webidl.converters.GPUStencilValue(reference, { + prefix, + context: "Argument 1", + }); + assertDevice(this[_encoder], { + prefix, + context: "encoder referenced by this", + }); + assertResource(this[_encoder], { + prefix, + context: "encoder referenced by this", + }); + const renderPassRid = assertResource(this, { prefix, context: "this" }); + ops.op_webgpu_render_pass_set_stencil_reference( + renderPassRid, + reference, + ); + } + + beginOcclusionQuery(_queryIndex) { + throw new Error("Not yet implemented"); + } + + endOcclusionQuery() { + throw new Error("Not yet implemented"); + } + + /** + * @param {GPUQuerySet} querySet + * @param {number} queryIndex + */ + beginPipelineStatisticsQuery(querySet, queryIndex) { + webidl.assertBranded(this, GPURenderPassEncoderPrototype); + const prefix = + "Failed to execute 'beginPipelineStatisticsQuery' on 'GPURenderPassEncoder'"; + webidl.requiredArguments(arguments.length, 2, { prefix }); + querySet = webidl.converters.GPUQuerySet(querySet, { + prefix, + context: "Argument 1", + }); + queryIndex = webidl.converters.GPUSize32(queryIndex, { + prefix, + context: "Argument 2", + }); + const device = assertDevice(this[_encoder], { + prefix, + context: "encoder referenced by this", + }); + assertResource(this[_encoder], { + prefix, + context: "encoder referenced by this", + }); + const renderPassRid = assertResource(this, { prefix, context: "this" }); + const querySetRid = assertResource(querySet, { + prefix, + context: "Argument 1", + }); + assertDeviceMatch(device, querySet, { + prefix, + resourceContext: "Argument 1", + selfContext: "this", + }); + ops.op_webgpu_render_pass_begin_pipeline_statistics_query( + renderPassRid, + querySetRid, + queryIndex, + ); + } + + endPipelineStatisticsQuery() { + webidl.assertBranded(this, GPURenderPassEncoderPrototype); + const prefix = + "Failed to execute 'endPipelineStatisticsQuery' on 'GPURenderPassEncoder'"; + assertDevice(this[_encoder], { + prefix, + context: "encoder referenced by this", + }); + assertResource(this[_encoder], { + prefix, + context: "encoder referenced by this", + }); + const renderPassRid = assertResource(this, { prefix, context: "this" }); + ops.op_webgpu_render_pass_end_pipeline_statistics_query(renderPassRid); + } + + /** + * @param {GPUQuerySet} querySet + * @param {number} queryIndex + */ + writeTimestamp(querySet, queryIndex) { + webidl.assertBranded(this, GPURenderPassEncoderPrototype); + const prefix = + "Failed to execute 'writeTimestamp' on 'GPURenderPassEncoder'"; + webidl.requiredArguments(arguments.length, 2, { prefix }); + querySet = webidl.converters.GPUQuerySet(querySet, { + prefix, + context: "Argument 1", + }); + queryIndex = webidl.converters.GPUSize32(queryIndex, { + prefix, + context: "Argument 2", + }); + const device = assertDevice(this[_encoder], { + prefix, + context: "encoder referenced by this", + }); + assertResource(this[_encoder], { + prefix, + context: "encoder referenced by this", + }); + const renderPassRid = assertResource(this, { prefix, context: "this" }); + const querySetRid = assertResource(querySet, { + prefix, + context: "Argument 1", + }); + assertDeviceMatch(device, querySet, { + prefix, + resourceContext: "Argument 1", + selfContext: "this", + }); + ops.op_webgpu_render_pass_write_timestamp( + renderPassRid, + querySetRid, + queryIndex, + ); + } + + /** + * @param {GPURenderBundle[]} bundles + */ + executeBundles(bundles) { + webidl.assertBranded(this, GPURenderPassEncoderPrototype); + const prefix = + "Failed to execute 'executeBundles' on 'GPURenderPassEncoder'"; + webidl.requiredArguments(arguments.length, 1, { prefix }); + bundles = webidl.converters["sequence"](bundles, { + prefix, + context: "Argument 1", + }); + const device = assertDevice(this[_encoder], { + prefix, + context: "encoder referenced by this", + }); + assertResource(this[_encoder], { + prefix, + context: "encoder referenced by this", + }); + const renderPassRid = assertResource(this, { prefix, context: "this" }); + const bundleRids = ArrayPrototypeMap(bundles, (bundle, i) => { + const context = `bundle ${i + 1}`; + const rid = assertResource(bundle, { prefix, context }); + assertDeviceMatch(device, bundle, { + prefix, + resourceContext: context, + selfContext: "this", + }); + return rid; + }); + ops.op_webgpu_render_pass_execute_bundles(renderPassRid, bundleRids); + } + + end() { + webidl.assertBranded(this, GPURenderPassEncoderPrototype); + const prefix = "Failed to execute 'end' on 'GPURenderPassEncoder'"; + const device = assertDevice(this[_encoder], { + prefix, + context: "encoder referenced by this", + }); + const commandEncoderRid = assertResource(this[_encoder], { + prefix, + context: "encoder referenced by this", + }); + const renderPassRid = assertResource(this, { prefix, context: "this" }); + const { err } = ops.op_webgpu_render_pass_end( + commandEncoderRid, + renderPassRid, + ); + device.pushError(err); + this[_rid] = undefined; + } + + // TODO(lucacasonato): has an overload + setBindGroup( + index, + bindGroup, + dynamicOffsetsData, + dynamicOffsetsDataStart, + dynamicOffsetsDataLength, + ) { + webidl.assertBranded(this, GPURenderPassEncoderPrototype); + const prefix = "Failed to execute 'setBindGroup' on 'GPURenderPassEncoder'"; + const device = assertDevice(this[_encoder], { + prefix, + context: "encoder referenced by this", + }); + assertResource(this[_encoder], { + prefix, + context: "encoder referenced by this", + }); + const renderPassRid = assertResource(this, { prefix, context: "this" }); + const bindGroupRid = assertResource(bindGroup, { + prefix, + context: "Argument 2", + }); + assertDeviceMatch(device, bindGroup, { + prefix, + resourceContext: "Argument 2", + selfContext: "this", + }); + if ( + !(ObjectPrototypeIsPrototypeOf( + Uint32ArrayPrototype, + dynamicOffsetsData, + )) + ) { + dynamicOffsetsData = new Uint32Array(dynamicOffsetsData ?? []); + dynamicOffsetsDataStart = 0; + dynamicOffsetsDataLength = dynamicOffsetsData.length; + } + ops.op_webgpu_render_pass_set_bind_group( + renderPassRid, + index, + bindGroupRid, + dynamicOffsetsData, + dynamicOffsetsDataStart, + dynamicOffsetsDataLength, + ); + } + + /** + * @param {string} groupLabel + */ + pushDebugGroup(groupLabel) { + webidl.assertBranded(this, GPURenderPassEncoderPrototype); + const prefix = + "Failed to execute 'pushDebugGroup' on 'GPURenderPassEncoder'"; + webidl.requiredArguments(arguments.length, 1, { prefix }); + groupLabel = webidl.converters.USVString(groupLabel, { + prefix, + context: "Argument 1", + }); + assertDevice(this[_encoder], { + prefix, + context: "encoder referenced by this", + }); + assertResource(this[_encoder], { + prefix, + context: "encoder referenced by this", + }); + const renderPassRid = assertResource(this, { prefix, context: "this" }); + ops.op_webgpu_render_pass_push_debug_group(renderPassRid, groupLabel); + } + + popDebugGroup() { + webidl.assertBranded(this, GPURenderPassEncoderPrototype); + const prefix = + "Failed to execute 'popDebugGroup' on 'GPURenderPassEncoder'"; + assertDevice(this[_encoder], { + prefix, + context: "encoder referenced by this", + }); + assertResource(this[_encoder], { + prefix, + context: "encoder referenced by this", + }); + const renderPassRid = assertResource(this, { prefix, context: "this" }); + ops.op_webgpu_render_pass_pop_debug_group(renderPassRid); + } + + /** + * @param {string} markerLabel + */ + insertDebugMarker(markerLabel) { + webidl.assertBranded(this, GPURenderPassEncoderPrototype); + const prefix = + "Failed to execute 'insertDebugMarker' on 'GPURenderPassEncoder'"; + webidl.requiredArguments(arguments.length, 1, { prefix }); + markerLabel = webidl.converters.USVString(markerLabel, { + prefix, + context: "Argument 1", + }); + assertDevice(this[_encoder], { + prefix, + context: "encoder referenced by this", + }); + assertResource(this[_encoder], { + prefix, + context: "encoder referenced by this", + }); + const renderPassRid = assertResource(this, { prefix, context: "this" }); + ops.op_webgpu_render_pass_insert_debug_marker(renderPassRid, markerLabel); + } + + /** + * @param {GPURenderPipeline} pipeline + */ + setPipeline(pipeline) { + webidl.assertBranded(this, GPURenderPassEncoderPrototype); + const prefix = "Failed to execute 'setPipeline' on 'GPURenderPassEncoder'"; + webidl.requiredArguments(arguments.length, 1, { prefix }); + pipeline = webidl.converters.GPURenderPipeline(pipeline, { + prefix, + context: "Argument 1", + }); + const device = assertDevice(this[_encoder], { + prefix, + context: "encoder referenced by this", + }); + assertResource(this[_encoder], { + prefix, + context: "encoder referenced by this", + }); + const renderPassRid = assertResource(this, { prefix, context: "this" }); + const pipelineRid = assertResource(pipeline, { + prefix, + context: "Argument 1", + }); + assertDeviceMatch(device, pipeline, { + prefix, + resourceContext: "Argument 1", + selfContext: "this", + }); + ops.op_webgpu_render_pass_set_pipeline(renderPassRid, pipelineRid); + } + + /** + * @param {GPUBuffer} buffer + * @param {GPUIndexFormat} indexFormat + * @param {number} offset + * @param {number} size + */ + setIndexBuffer(buffer, indexFormat, offset = 0, size) { + webidl.assertBranded(this, GPURenderPassEncoderPrototype); + const prefix = + "Failed to execute 'setIndexBuffer' on 'GPURenderPassEncoder'"; + webidl.requiredArguments(arguments.length, 2, { prefix }); + buffer = webidl.converters.GPUBuffer(buffer, { + prefix, + context: "Argument 1", + }); + indexFormat = webidl.converters.GPUIndexFormat(indexFormat, { + prefix, + context: "Argument 2", + }); + offset = webidl.converters.GPUSize64(offset, { + prefix, + context: "Argument 3", + }); + if (size !== undefined) { + size = webidl.converters.GPUSize64(size, { + prefix, + context: "Argument 4", + }); + } + const device = assertDevice(this[_encoder], { + prefix, + context: "encoder referenced by this", + }); + assertResource(this[_encoder], { + prefix, + context: "encoder referenced by this", + }); + const renderPassRid = assertResource(this, { prefix, context: "this" }); + const bufferRid = assertResource(buffer, { + prefix, + context: "Argument 1", + }); + assertDeviceMatch(device, buffer, { + prefix, + resourceContext: "Argument 1", + selfContext: "this", + }); + ops.op_webgpu_render_pass_set_index_buffer( + renderPassRid, + bufferRid, + indexFormat, + offset, + size, + ); + } + + /** + * @param {number} slot + * @param {GPUBuffer} buffer + * @param {number} offset + * @param {number} size + */ + setVertexBuffer(slot, buffer, offset = 0, size) { + webidl.assertBranded(this, GPURenderPassEncoderPrototype); + const prefix = + "Failed to execute 'setVertexBuffer' on 'GPURenderPassEncoder'"; + webidl.requiredArguments(arguments.length, 2, { prefix }); + slot = webidl.converters.GPUSize32(slot, { + prefix, + context: "Argument 2", + }); + buffer = webidl.converters.GPUBuffer(buffer, { + prefix, + context: "Argument 2", + }); + offset = webidl.converters.GPUSize64(offset, { + prefix, + context: "Argument 3", + }); + if (size !== undefined) { + size = webidl.converters.GPUSize64(size, { + prefix, + context: "Argument 4", + }); + } + const device = assertDevice(this[_encoder], { + prefix, + context: "encoder referenced by this", + }); + assertResource(this[_encoder], { + prefix, + context: "encoder referenced by this", + }); + const renderPassRid = assertResource(this, { prefix, context: "this" }); + const bufferRid = assertResource(buffer, { + prefix, + context: "Argument 2", + }); + assertDeviceMatch(device, buffer, { + prefix, + resourceContext: "Argument 2", + selfContext: "this", + }); + ops.op_webgpu_render_pass_set_vertex_buffer( + renderPassRid, + slot, + bufferRid, + offset, + size, + ); + } + + /** + * @param {number} vertexCount + * @param {number} instanceCount + * @param {number} firstVertex + * @param {number} firstInstance + */ + draw(vertexCount, instanceCount = 1, firstVertex = 0, firstInstance = 0) { + webidl.assertBranded(this, GPURenderPassEncoderPrototype); + const prefix = "Failed to execute 'draw' on 'GPURenderPassEncoder'"; + webidl.requiredArguments(arguments.length, 1, { prefix }); + vertexCount = webidl.converters.GPUSize32(vertexCount, { + prefix, + context: "Argument 1", + }); + instanceCount = webidl.converters.GPUSize32(instanceCount, { + prefix, + context: "Argument 2", + }); + firstVertex = webidl.converters.GPUSize32(firstVertex, { + prefix, + context: "Argument 3", + }); + firstInstance = webidl.converters.GPUSize32(firstInstance, { + prefix, + context: "Argument 4", + }); + assertDevice(this[_encoder], { + prefix, + context: "encoder referenced by this", + }); + assertResource(this[_encoder], { + prefix, + context: "encoder referenced by this", + }); + const renderPassRid = assertResource(this, { prefix, context: "this" }); + ops.op_webgpu_render_pass_draw( + renderPassRid, + vertexCount, + instanceCount, + firstVertex, + firstInstance, + ); + } + + /** + * @param {number} indexCount + * @param {number} instanceCount + * @param {number} firstIndex + * @param {number} baseVertex + * @param {number} firstInstance + */ + drawIndexed( + indexCount, + instanceCount = 1, + firstIndex = 0, + baseVertex = 0, + firstInstance = 0, + ) { + webidl.assertBranded(this, GPURenderPassEncoderPrototype); + const prefix = "Failed to execute 'drawIndexed' on 'GPURenderPassEncoder'"; + webidl.requiredArguments(arguments.length, 1, { prefix }); + indexCount = webidl.converters.GPUSize32(indexCount, { + prefix, + context: "Argument 1", + }); + instanceCount = webidl.converters.GPUSize32(instanceCount, { + prefix, + context: "Argument 2", + }); + firstIndex = webidl.converters.GPUSize32(firstIndex, { + prefix, + context: "Argument 3", + }); + baseVertex = webidl.converters.GPUSignedOffset32(baseVertex, { + prefix, + context: "Argument 4", + }); + firstInstance = webidl.converters.GPUSize32(firstInstance, { + prefix, + context: "Argument 5", + }); + assertDevice(this[_encoder], { + prefix, + context: "encoder referenced by this", + }); + assertResource(this[_encoder], { + prefix, + context: "encoder referenced by this", + }); + const renderPassRid = assertResource(this, { prefix, context: "this" }); + ops.op_webgpu_render_pass_draw_indexed( + renderPassRid, + indexCount, + instanceCount, + firstIndex, + baseVertex, + firstInstance, + ); + } + + /** + * @param {GPUBuffer} indirectBuffer + * @param {number} indirectOffset + */ + drawIndirect(indirectBuffer, indirectOffset) { + webidl.assertBranded(this, GPURenderPassEncoderPrototype); + const prefix = "Failed to execute 'drawIndirect' on 'GPURenderPassEncoder'"; + webidl.requiredArguments(arguments.length, 2, { prefix }); + indirectBuffer = webidl.converters.GPUBuffer(indirectBuffer, { + prefix, + context: "Argument 1", + }); + indirectOffset = webidl.converters.GPUSize64(indirectOffset, { + prefix, + context: "Argument 2", + }); + const device = assertDevice(this[_encoder], { + prefix, + context: "encoder referenced by this", + }); + assertResource(this[_encoder], { + prefix, + context: "encoder referenced by this", + }); + const renderPassRid = assertResource(this, { prefix, context: "this" }); + const indirectBufferRid = assertResource(indirectBuffer, { + prefix, + context: "Argument 1", + }); + assertDeviceMatch(device, indirectBuffer, { + prefix, + resourceContext: "Argument 1", + selfContext: "this", + }); + ops.op_webgpu_render_pass_draw_indirect( + renderPassRid, + indirectBufferRid, + indirectOffset, + ); + } + + /** + * @param {GPUBuffer} indirectBuffer + * @param {number} indirectOffset + */ + drawIndexedIndirect(indirectBuffer, indirectOffset) { + webidl.assertBranded(this, GPURenderPassEncoderPrototype); + const prefix = "Failed to execute 'drawIndirect' on 'GPURenderPassEncoder'"; + webidl.requiredArguments(arguments.length, 2, { prefix }); + indirectBuffer = webidl.converters.GPUBuffer(indirectBuffer, { + prefix, + context: "Argument 1", + }); + indirectOffset = webidl.converters.GPUSize64(indirectOffset, { + prefix, + context: "Argument 2", + }); + const device = assertDevice(this[_encoder], { + prefix, + context: "encoder referenced by this", + }); + assertResource(this[_encoder], { + prefix, + context: "encoder referenced by this", + }); + const renderPassRid = assertResource(this, { prefix, context: "this" }); + const indirectBufferRid = assertResource(indirectBuffer, { + prefix, + context: "Argument 1", + }); + assertDeviceMatch(device, indirectBuffer, { + prefix, + resourceContext: "Argument 1", + selfContext: "this", + }); + ops.op_webgpu_render_pass_draw_indexed_indirect( + renderPassRid, + indirectBufferRid, + indirectOffset, + ); + } + + [SymbolFor("Deno.privateCustomInspect")](inspect) { + return `${this.constructor.name} ${ + inspect({ + label: this.label, + }) + }`; + } +} +GPUObjectBaseMixin("GPURenderPassEncoder", GPURenderPassEncoder); +const GPURenderPassEncoderPrototype = GPURenderPassEncoder.prototype; + +/** + * @param {string | null} label + * @param {GPUCommandEncoder} encoder + * @param {number} rid + * @returns {GPUComputePassEncoder} + */ +function createGPUComputePassEncoder(label, encoder, rid) { + /** @type {GPUComputePassEncoder} */ + const computePassEncoder = webidl.createBranded(GPUComputePassEncoder); + computePassEncoder[_label] = label; + computePassEncoder[_encoder] = encoder; + computePassEncoder[_rid] = rid; + return computePassEncoder; +} + +class GPUComputePassEncoder { + /** @type {GPUCommandEncoder} */ + [_encoder]; + + /** @type {number | undefined} */ + [_rid]; + + [_cleanup]() { + const rid = this[_rid]; + if (rid !== undefined) { + core.close(rid); + /** @type {number | undefined} */ + this[_rid] = undefined; + } + } + + constructor() { + webidl.illegalConstructor(); + } + + /** + * @param {GPUComputePipeline} pipeline + */ + setPipeline(pipeline) { + webidl.assertBranded(this, GPUComputePassEncoderPrototype); + const prefix = "Failed to execute 'setPipeline' on 'GPUComputePassEncoder'"; + webidl.requiredArguments(arguments.length, 1, { prefix }); + pipeline = webidl.converters.GPUComputePipeline(pipeline, { + prefix, + context: "Argument 1", + }); + const device = assertDevice(this[_encoder], { + prefix, + context: "encoder referenced by this", + }); + assertResource(this[_encoder], { + prefix, + context: "encoder referenced by this", + }); + const computePassRid = assertResource(this, { prefix, context: "this" }); + const pipelineRid = assertResource(pipeline, { + prefix, + context: "Argument 1", + }); + assertDeviceMatch(device, pipeline, { + prefix, + resourceContext: "Argument 1", + selfContext: "this", + }); + ops.op_webgpu_compute_pass_set_pipeline(computePassRid, pipelineRid); + } + + /** + * @param {number} workgroupCountX + * @param {number} workgroupCountY + * @param {number} workgroupCountZ + */ + dispatchWorkgroups( + workgroupCountX, + workgroupCountY = 1, + workgroupCountZ = 1, + ) { + webidl.assertBranded(this, GPUComputePassEncoderPrototype); + const prefix = + "Failed to execute 'dispatchWorkgroups' on 'GPUComputePassEncoder'"; + webidl.requiredArguments(arguments.length, 1, { prefix }); + workgroupCountX = webidl.converters.GPUSize32(workgroupCountX, { + prefix, + context: "Argument 1", + }); + workgroupCountY = webidl.converters.GPUSize32(workgroupCountY, { + prefix, + context: "Argument 2", + }); + workgroupCountZ = webidl.converters.GPUSize32(workgroupCountZ, { + prefix, + context: "Argument 3", + }); + assertDevice(this[_encoder], { + prefix, + context: "encoder referenced by this", + }); + assertResource(this[_encoder], { + prefix, + context: "encoder referenced by this", + }); + const computePassRid = assertResource(this, { prefix, context: "this" }); + ops.op_webgpu_compute_pass_dispatch_workgroups( + computePassRid, + workgroupCountX, + workgroupCountY, + workgroupCountZ, + ); + } + + /** + * @param {GPUBuffer} indirectBuffer + * @param {number} indirectOffset + */ + dispatchWorkgroupsIndirect(indirectBuffer, indirectOffset) { + webidl.assertBranded(this, GPUComputePassEncoderPrototype); + const prefix = + "Failed to execute 'dispatchWorkgroupsIndirect' on 'GPUComputePassEncoder'"; + webidl.requiredArguments(arguments.length, 2, { prefix }); + indirectBuffer = webidl.converters.GPUBuffer(indirectBuffer, { + prefix, + context: "Argument 1", + }); + indirectOffset = webidl.converters.GPUSize64(indirectOffset, { + prefix, + context: "Argument 2", + }); + const device = assertDevice(this[_encoder], { + prefix, + context: "encoder referenced by this", + }); + assertResource(this[_encoder], { + prefix, + context: "encoder referenced by this", + }); + const computePassRid = assertResource(this, { prefix, context: "this" }); + const indirectBufferRid = assertResource(indirectBuffer, { + prefix, + context: "Argument 1", + }); + assertDeviceMatch(device, indirectBuffer, { + prefix, + resourceContext: "Argument 1", + selfContext: "this", + }); + ops.op_webgpu_compute_pass_dispatch_workgroups_indirect( + computePassRid, + indirectBufferRid, + indirectOffset, + ); + } + + /** + * @param {GPUQuerySet} querySet + * @param {number} queryIndex + */ + beginPipelineStatisticsQuery(querySet, queryIndex) { + webidl.assertBranded(this, GPUComputePassEncoderPrototype); + const prefix = + "Failed to execute 'beginPipelineStatisticsQuery' on 'GPUComputePassEncoder'"; + webidl.requiredArguments(arguments.length, 2, { prefix }); + querySet = webidl.converters.GPUQuerySet(querySet, { + prefix, + context: "Argument 1", + }); + queryIndex = webidl.converters.GPUSize32(queryIndex, { + prefix, + context: "Argument 2", + }); + const device = assertDevice(this[_encoder], { + prefix, + context: "encoder referenced by this", + }); + assertResource(this[_encoder], { + prefix, + context: "encoder referenced by this", + }); + const computePassRid = assertResource(this, { prefix, context: "this" }); + const querySetRid = assertResource(querySet, { + prefix, + context: "Argument 1", + }); + assertDeviceMatch(device, querySet, { + prefix, + resourceContext: "Argument 1", + selfContext: "this", + }); + ops.op_webgpu_compute_pass_begin_pipeline_statistics_query( + computePassRid, + querySetRid, + queryIndex, + ); + } + + endPipelineStatisticsQuery() { + webidl.assertBranded(this, GPUComputePassEncoderPrototype); + const prefix = + "Failed to execute 'endPipelineStatisticsQuery' on 'GPUComputePassEncoder'"; + assertDevice(this[_encoder], { + prefix, + context: "encoder referenced by this", + }); + assertResource(this[_encoder], { + prefix, + context: "encoder referenced by this", + }); + const computePassRid = assertResource(this, { prefix, context: "this" }); + ops.op_webgpu_compute_pass_end_pipeline_statistics_query(computePassRid); + } + + /** + * @param {GPUQuerySet} querySet + * @param {number} queryIndex + */ + writeTimestamp(querySet, queryIndex) { + webidl.assertBranded(this, GPUComputePassEncoderPrototype); + const prefix = + "Failed to execute 'writeTimestamp' on 'GPUComputePassEncoder'"; + webidl.requiredArguments(arguments.length, 2, { prefix }); + querySet = webidl.converters.GPUQuerySet(querySet, { + prefix, + context: "Argument 1", + }); + queryIndex = webidl.converters.GPUSize32(queryIndex, { + prefix, + context: "Argument 2", + }); + const device = assertDevice(this[_encoder], { + prefix, + context: "encoder referenced by this", + }); + assertResource(this[_encoder], { + prefix, + context: "encoder referenced by this", + }); + const computePassRid = assertResource(this, { prefix, context: "this" }); + const querySetRid = assertResource(querySet, { + prefix, + context: "Argument 1", + }); + assertDeviceMatch(device, querySet, { + prefix, + resourceContext: "Argument 1", + selfContext: "this", + }); + ops.op_webgpu_compute_pass_write_timestamp( + computePassRid, + querySetRid, + queryIndex, + ); + } + + end() { + webidl.assertBranded(this, GPUComputePassEncoderPrototype); + const prefix = "Failed to execute 'end' on 'GPUComputePassEncoder'"; + const device = assertDevice(this[_encoder], { + prefix, + context: "encoder referenced by this", + }); + const commandEncoderRid = assertResource(this[_encoder], { + prefix, + context: "encoder referenced by this", + }); + const computePassRid = assertResource(this, { prefix, context: "this" }); + const { err } = ops.op_webgpu_compute_pass_end( + commandEncoderRid, + computePassRid, + ); + device.pushError(err); + this[_rid] = undefined; + } + + // TODO(lucacasonato): has an overload + setBindGroup( + index, + bindGroup, + dynamicOffsetsData, + dynamicOffsetsDataStart, + dynamicOffsetsDataLength, + ) { + webidl.assertBranded(this, GPUComputePassEncoderPrototype); + const prefix = + "Failed to execute 'setBindGroup' on 'GPUComputePassEncoder'"; + const device = assertDevice(this[_encoder], { + prefix, + context: "encoder referenced by this", + }); + assertResource(this[_encoder], { + prefix, + context: "encoder referenced by this", + }); + const computePassRid = assertResource(this, { prefix, context: "this" }); + const bindGroupRid = assertResource(bindGroup, { + prefix, + context: "Argument 2", + }); + assertDeviceMatch(device, bindGroup, { + prefix, + resourceContext: "Argument 2", + selfContext: "this", + }); + if ( + !(ObjectPrototypeIsPrototypeOf( + Uint32ArrayPrototype, + dynamicOffsetsData, + )) + ) { + dynamicOffsetsData = new Uint32Array(dynamicOffsetsData ?? []); + dynamicOffsetsDataStart = 0; + dynamicOffsetsDataLength = dynamicOffsetsData.length; + } + ops.op_webgpu_compute_pass_set_bind_group( + computePassRid, + index, + bindGroupRid, + dynamicOffsetsData, + dynamicOffsetsDataStart, + dynamicOffsetsDataLength, + ); + } + + /** + * @param {string} groupLabel + */ + pushDebugGroup(groupLabel) { + webidl.assertBranded(this, GPUComputePassEncoderPrototype); + const prefix = + "Failed to execute 'pushDebugGroup' on 'GPUComputePassEncoder'"; + webidl.requiredArguments(arguments.length, 1, { prefix }); + groupLabel = webidl.converters.USVString(groupLabel, { + prefix, + context: "Argument 1", + }); + assertDevice(this[_encoder], { + prefix, + context: "encoder referenced by this", + }); + assertResource(this[_encoder], { + prefix, + context: "encoder referenced by this", + }); + const computePassRid = assertResource(this, { prefix, context: "this" }); + ops.op_webgpu_compute_pass_push_debug_group(computePassRid, groupLabel); + } + + popDebugGroup() { + webidl.assertBranded(this, GPUComputePassEncoderPrototype); + const prefix = + "Failed to execute 'popDebugGroup' on 'GPUComputePassEncoder'"; + assertDevice(this[_encoder], { + prefix, + context: "encoder referenced by this", + }); + assertResource(this[_encoder], { + prefix, + context: "encoder referenced by this", + }); + const computePassRid = assertResource(this, { prefix, context: "this" }); + ops.op_webgpu_compute_pass_pop_debug_group(computePassRid); + } + + /** + * @param {string} markerLabel + */ + insertDebugMarker(markerLabel) { + webidl.assertBranded(this, GPUComputePassEncoderPrototype); + const prefix = + "Failed to execute 'insertDebugMarker' on 'GPUComputePassEncoder'"; + webidl.requiredArguments(arguments.length, 1, { prefix }); + markerLabel = webidl.converters.USVString(markerLabel, { + prefix, + context: "Argument 1", + }); + assertDevice(this[_encoder], { + prefix, + context: "encoder referenced by this", + }); + assertResource(this[_encoder], { + prefix, + context: "encoder referenced by this", + }); + const computePassRid = assertResource(this, { prefix, context: "this" }); + ops.op_webgpu_compute_pass_insert_debug_marker( + computePassRid, + markerLabel, + ); + } + + [SymbolFor("Deno.privateCustomInspect")](inspect) { + return `${this.constructor.name} ${ + inspect({ + label: this.label, + }) + }`; + } +} +GPUObjectBaseMixin("GPUComputePassEncoder", GPUComputePassEncoder); +const GPUComputePassEncoderPrototype = GPUComputePassEncoder.prototype; + +/** + * @param {string | null} label + * @param {InnerGPUDevice} device + * @param {number} rid + * @returns {GPUCommandBuffer} + */ +function createGPUCommandBuffer(label, device, rid) { + /** @type {GPUCommandBuffer} */ + const commandBuffer = webidl.createBranded(GPUCommandBuffer); + commandBuffer[_label] = label; + commandBuffer[_device] = device; + commandBuffer[_rid] = rid; + return commandBuffer; +} + +class GPUCommandBuffer { + /** @type {InnerGPUDevice} */ + [_device]; + /** @type {number | undefined} */ + [_rid]; + + [_cleanup]() { + const rid = this[_rid]; + if (rid !== undefined) { + core.close(rid); + /** @type {number | undefined} */ + this[_rid] = undefined; + } + } + + constructor() { + webidl.illegalConstructor(); + } + + [SymbolFor("Deno.privateCustomInspect")](inspect) { + return `${this.constructor.name} ${ + inspect({ + label: this.label, + }) + }`; + } +} +GPUObjectBaseMixin("GPUCommandBuffer", GPUCommandBuffer); + +/** + * @param {string | null} label + * @param {InnerGPUDevice} device + * @param {number} rid + * @returns {GPURenderBundleEncoder} + */ +function createGPURenderBundleEncoder(label, device, rid) { + /** @type {GPURenderBundleEncoder} */ + const bundleEncoder = webidl.createBranded(GPURenderBundleEncoder); + bundleEncoder[_label] = label; + bundleEncoder[_device] = device; + bundleEncoder[_rid] = rid; + return bundleEncoder; +} + +class GPURenderBundleEncoder { + /** @type {InnerGPUDevice} */ + [_device]; + /** @type {number | undefined} */ + [_rid]; + + [_cleanup]() { + const rid = this[_rid]; + if (rid !== undefined) { + core.close(rid); + /** @type {number | undefined} */ + this[_rid] = undefined; + } + } + + constructor() { + webidl.illegalConstructor(); + } + + /** + * @param {GPURenderBundleDescriptor} descriptor + */ + finish(descriptor = {}) { + webidl.assertBranded(this, GPURenderBundleEncoderPrototype); + const prefix = "Failed to execute 'finish' on 'GPURenderBundleEncoder'"; + descriptor = webidl.converters.GPURenderBundleDescriptor(descriptor, { + prefix, + context: "Argument 1", + }); + const device = assertDevice(this, { prefix, context: "this" }); + const renderBundleEncoderRid = assertResource(this, { + prefix, + context: "this", + }); + const { rid, err } = ops.op_webgpu_render_bundle_encoder_finish( + renderBundleEncoderRid, + descriptor.label, + ); + device.pushError(err); + this[_rid] = undefined; + + const renderBundle = createGPURenderBundle( + descriptor.label, + device, + rid, + ); + device.trackResource(renderBundle); + return renderBundle; + } + + // TODO(lucacasonato): has an overload + setBindGroup( + index, + bindGroup, + dynamicOffsetsData, + dynamicOffsetsDataStart, + dynamicOffsetsDataLength, + ) { + webidl.assertBranded(this, GPURenderBundleEncoderPrototype); + const prefix = + "Failed to execute 'setBindGroup' on 'GPURenderBundleEncoder'"; + const device = assertDevice(this, { prefix, context: "this" }); + const renderBundleEncoderRid = assertResource(this, { + prefix, + context: "this", + }); + const bindGroupRid = assertResource(bindGroup, { + prefix, + context: "Argument 2", + }); + assertDeviceMatch(device, bindGroup, { + prefix, + resourceContext: "Argument 2", + selfContext: "this", + }); + if ( + !(ObjectPrototypeIsPrototypeOf( + Uint32ArrayPrototype, + dynamicOffsetsData, + )) + ) { + dynamicOffsetsData = new Uint32Array(dynamicOffsetsData ?? []); + dynamicOffsetsDataStart = 0; + dynamicOffsetsDataLength = dynamicOffsetsData.length; + } + ops.op_webgpu_render_bundle_encoder_set_bind_group( + renderBundleEncoderRid, + index, + bindGroupRid, + dynamicOffsetsData, + dynamicOffsetsDataStart, + dynamicOffsetsDataLength, + ); + } + + /** + * @param {string} groupLabel + */ + pushDebugGroup(groupLabel) { + webidl.assertBranded(this, GPURenderBundleEncoderPrototype); + const prefix = + "Failed to execute 'pushDebugGroup' on 'GPURenderBundleEncoder'"; + webidl.requiredArguments(arguments.length, 1, { prefix }); + groupLabel = webidl.converters.USVString(groupLabel, { + prefix, + context: "Argument 1", + }); + assertDevice(this, { prefix, context: "this" }); + const renderBundleEncoderRid = assertResource(this, { + prefix, + context: "this", + }); + ops.op_webgpu_render_bundle_encoder_push_debug_group( + renderBundleEncoderRid, + groupLabel, + ); + } + + popDebugGroup() { + webidl.assertBranded(this, GPURenderBundleEncoderPrototype); + const prefix = + "Failed to execute 'popDebugGroup' on 'GPURenderBundleEncoder'"; + assertDevice(this, { prefix, context: "this" }); + const renderBundleEncoderRid = assertResource(this, { + prefix, + context: "this", + }); + ops.op_webgpu_render_bundle_encoder_pop_debug_group( + renderBundleEncoderRid, + ); + } + + /** + * @param {string} markerLabel + */ + insertDebugMarker(markerLabel) { + webidl.assertBranded(this, GPURenderBundleEncoderPrototype); + const prefix = + "Failed to execute 'insertDebugMarker' on 'GPURenderBundleEncoder'"; + webidl.requiredArguments(arguments.length, 1, { prefix }); + markerLabel = webidl.converters.USVString(markerLabel, { + prefix, + context: "Argument 1", + }); + assertDevice(this, { prefix, context: "this" }); + const renderBundleEncoderRid = assertResource(this, { + prefix, + context: "this", + }); + ops.op_webgpu_render_bundle_encoder_insert_debug_marker( + renderBundleEncoderRid, + markerLabel, + ); + } + + /** + * @param {GPURenderPipeline} pipeline + */ + setPipeline(pipeline) { + webidl.assertBranded(this, GPURenderBundleEncoderPrototype); + const prefix = + "Failed to execute 'setPipeline' on 'GPURenderBundleEncoder'"; + webidl.requiredArguments(arguments.length, 1, { prefix }); + pipeline = webidl.converters.GPURenderPipeline(pipeline, { + prefix, + context: "Argument 1", + }); + const device = assertDevice(this, { prefix, context: "this" }); + const renderBundleEncoderRid = assertResource(this, { + prefix, + context: "this", + }); + const pipelineRid = assertResource(pipeline, { + prefix, + context: "Argument 1", + }); + assertDeviceMatch(device, pipeline, { + prefix, + resourceContext: "Argument 1", + selfContext: "this", + }); + ops.op_webgpu_render_bundle_encoder_set_pipeline( + renderBundleEncoderRid, + pipelineRid, + ); + } + + /** + * @param {GPUBuffer} buffer + * @param {GPUIndexFormat} indexFormat + * @param {number} offset + * @param {number} size + */ + setIndexBuffer(buffer, indexFormat, offset = 0, size = 0) { + webidl.assertBranded(this, GPURenderBundleEncoderPrototype); + const prefix = + "Failed to execute 'setIndexBuffer' on 'GPURenderBundleEncoder'"; + webidl.requiredArguments(arguments.length, 2, { prefix }); + buffer = webidl.converters.GPUBuffer(buffer, { + prefix, + context: "Argument 1", + }); + indexFormat = webidl.converters.GPUIndexFormat(indexFormat, { + prefix, + context: "Argument 2", + }); + offset = webidl.converters.GPUSize64(offset, { + prefix, + context: "Argument 3", + }); + size = webidl.converters.GPUSize64(size, { + prefix, + context: "Argument 4", + }); + const device = assertDevice(this, { prefix, context: "this" }); + const renderBundleEncoderRid = assertResource(this, { + prefix, + context: "this", + }); + const bufferRid = assertResource(buffer, { + prefix, + context: "Argument 1", + }); + assertDeviceMatch(device, buffer, { + prefix, + resourceContext: "Argument 1", + selfContext: "this", + }); + ops.op_webgpu_render_bundle_encoder_set_index_buffer( + renderBundleEncoderRid, + bufferRid, + indexFormat, + offset, + size, + ); + } + + /** + * @param {number} slot + * @param {GPUBuffer} buffer + * @param {number} offset + * @param {number} size + */ + setVertexBuffer(slot, buffer, offset = 0, size) { + webidl.assertBranded(this, GPURenderBundleEncoderPrototype); + const prefix = + "Failed to execute 'setVertexBuffer' on 'GPURenderBundleEncoder'"; + webidl.requiredArguments(arguments.length, 2, { prefix }); + slot = webidl.converters.GPUSize32(slot, { + prefix, + context: "Argument 1", + }); + buffer = webidl.converters.GPUBuffer(buffer, { + prefix, + context: "Argument 2", + }); + offset = webidl.converters.GPUSize64(offset, { + prefix, + context: "Argument 3", + }); + if (size !== undefined) { + size = webidl.converters.GPUSize64(size, { + prefix, + context: "Argument 4", + }); + } + const device = assertDevice(this, { prefix, context: "this" }); + const renderBundleEncoderRid = assertResource(this, { + prefix, + context: "this", + }); + const bufferRid = assertResource(buffer, { + prefix, + context: "Argument 2", + }); + assertDeviceMatch(device, buffer, { + prefix, + resourceContext: "Argument 2", + selfContext: "this", + }); + ops.op_webgpu_render_bundle_encoder_set_vertex_buffer( + renderBundleEncoderRid, + slot, + bufferRid, + offset, + size, + ); + } + + /** + * @param {number} vertexCount + * @param {number} instanceCount + * @param {number} firstVertex + * @param {number} firstInstance + */ + draw(vertexCount, instanceCount = 1, firstVertex = 0, firstInstance = 0) { + webidl.assertBranded(this, GPURenderBundleEncoderPrototype); + const prefix = "Failed to execute 'draw' on 'GPURenderBundleEncoder'"; + webidl.requiredArguments(arguments.length, 1, { prefix }); + vertexCount = webidl.converters.GPUSize32(vertexCount, { + prefix, + context: "Argument 1", + }); + instanceCount = webidl.converters.GPUSize32(instanceCount, { + prefix, + context: "Argument 2", + }); + firstVertex = webidl.converters.GPUSize32(firstVertex, { + prefix, + context: "Argument 3", + }); + firstInstance = webidl.converters.GPUSize32(firstInstance, { + prefix, + context: "Argument 4", + }); + assertDevice(this, { prefix, context: "this" }); + const renderBundleEncoderRid = assertResource(this, { + prefix, + context: "this", + }); + ops.op_webgpu_render_bundle_encoder_draw( + renderBundleEncoderRid, + vertexCount, + instanceCount, + firstVertex, + firstInstance, + ); + } + + /** + * @param {number} indexCount + * @param {number} instanceCount + * @param {number} firstIndex + * @param {number} baseVertex + * @param {number} firstInstance + */ + drawIndexed( + indexCount, + instanceCount = 1, + firstIndex = 0, + baseVertex = 0, + firstInstance = 0, + ) { + webidl.assertBranded(this, GPURenderBundleEncoderPrototype); + const prefix = + "Failed to execute 'drawIndexed' on 'GPURenderBundleEncoder'"; + webidl.requiredArguments(arguments.length, 1, { prefix }); + indexCount = webidl.converters.GPUSize32(indexCount, { + prefix, + context: "Argument 1", + }); + instanceCount = webidl.converters.GPUSize32(instanceCount, { + prefix, + context: "Argument 2", + }); + firstIndex = webidl.converters.GPUSize32(firstIndex, { + prefix, + context: "Argument 3", + }); + baseVertex = webidl.converters.GPUSignedOffset32(baseVertex, { + prefix, + context: "Argument 4", + }); + firstInstance = webidl.converters.GPUSize32(firstInstance, { + prefix, + context: "Argument 5", + }); + assertDevice(this, { prefix, context: "this" }); + const renderBundleEncoderRid = assertResource(this, { + prefix, + context: "this", + }); + ops.op_webgpu_render_bundle_encoder_draw_indexed( + renderBundleEncoderRid, + indexCount, + instanceCount, + firstIndex, + baseVertex, + firstInstance, + ); + } + + /** + * @param {GPUBuffer} indirectBuffer + * @param {number} indirectOffset + */ + drawIndirect(indirectBuffer, indirectOffset) { + webidl.assertBranded(this, GPURenderBundleEncoderPrototype); + const prefix = + "Failed to execute 'drawIndirect' on 'GPURenderBundleEncoder'"; + webidl.requiredArguments(arguments.length, 2, { prefix }); + indirectBuffer = webidl.converters.GPUBuffer(indirectBuffer, { + prefix, + context: "Argument 1", + }); + indirectOffset = webidl.converters.GPUSize64(indirectOffset, { + prefix, + context: "Argument 2", + }); + const device = assertDevice(this, { prefix, context: "this" }); + const renderBundleEncoderRid = assertResource(this, { + prefix, + context: "this", + }); + const indirectBufferRid = assertResource(indirectBuffer, { + prefix, + context: "Argument 1", + }); + assertDeviceMatch(device, indirectBuffer, { + prefix, + resourceContext: "Argument 1", + selfContext: "this", + }); + ops.op_webgpu_render_bundle_encoder_draw_indirect( + renderBundleEncoderRid, + indirectBufferRid, + indirectOffset, + ); + } + + drawIndexedIndirect(_indirectBuffer, _indirectOffset) { + throw new Error("Not yet implemented"); + } + + [SymbolFor("Deno.privateCustomInspect")](inspect) { + return `${this.constructor.name} ${ + inspect({ + label: this.label, + }) + }`; + } +} +GPUObjectBaseMixin("GPURenderBundleEncoder", GPURenderBundleEncoder); +const GPURenderBundleEncoderPrototype = GPURenderBundleEncoder.prototype; + +/** + * @param {string | null} label + * @param {InnerGPUDevice} device + * @param {number} rid + * @returns {GPURenderBundle} + */ +function createGPURenderBundle(label, device, rid) { + /** @type {GPURenderBundle} */ + const bundle = webidl.createBranded(GPURenderBundle); + bundle[_label] = label; + bundle[_device] = device; + bundle[_rid] = rid; + return bundle; +} + +class GPURenderBundle { + /** @type {InnerGPUDevice} */ + [_device]; + /** @type {number | undefined} */ + [_rid]; + + [_cleanup]() { + const rid = this[_rid]; + if (rid !== undefined) { + core.close(rid); + /** @type {number | undefined} */ + this[_rid] = undefined; + } + } + + constructor() { + webidl.illegalConstructor(); + } + + [SymbolFor("Deno.privateCustomInspect")](inspect) { + return `${this.constructor.name} ${ + inspect({ + label: this.label, + }) + }`; + } +} +GPUObjectBaseMixin("GPURenderBundle", GPURenderBundle); + +/** + * @param {string | null} label + * @param {InnerGPUDevice} device + * @param {number} rid + * @returns {GPUQuerySet} + */ +function createGPUQuerySet(label, device, rid, descriptor) { + /** @type {GPUQuerySet} */ + const queue = webidl.createBranded(GPUQuerySet); + queue[_label] = label; + queue[_device] = device; + queue[_rid] = rid; + queue[_descriptor] = descriptor; + return queue; +} + +class GPUQuerySet { + /** @type {InnerGPUDevice} */ + [_device]; + /** @type {number | undefined} */ + [_rid]; + /** @type {GPUQuerySetDescriptor} */ + [_descriptor]; + /** @type {GPUQueryType} */ + [_type]; + /** @type {number} */ + [_count]; + + [_cleanup]() { + const rid = this[_rid]; + if (rid !== undefined) { + core.close(rid); + /** @type {number | undefined} */ + this[_rid] = undefined; + } + } + + constructor() { + webidl.illegalConstructor(); + } + + destroy() { + webidl.assertBranded(this, GPUQuerySetPrototype); + this[_cleanup](); + } + + get type() { + webidl.assertBranded(this, GPUQuerySetPrototype); + return this[_type](); + } + + get count() { + webidl.assertBranded(this, GPUQuerySetPrototype); + return this[_count](); + } + + [SymbolFor("Deno.privateCustomInspect")](inspect) { + return `${this.constructor.name} ${ + inspect({ + label: this.label, + }) + }`; + } +} +GPUObjectBaseMixin("GPUQuerySet", GPUQuerySet); +const GPUQuerySetPrototype = GPUQuerySet.prototype; + +/** + * @param {number} rid + * @returns {GPUSurface} + */ +function createGPUSurface(rid) { + const surface = webidl.createBranded(GPUSurface); + surface[_rid] = rid; + return surface; +} + +/** + * @param {GPUSurface} surface + */ +function destroyGPUSurface(surface) { + surface[_currentTexture]?.destroy(); + surface[_configuration] = undefined; + surface[_device] = undefined; + core.close(surface[_rid]); + surface[_rid] = undefined; +} + +class GPUSurface { + /** @type {number | undefined} */ + [_rid]; + + /** @type {GPUDevice | undefined} */ + [_device]; + + /** @type {GPUSurfaceConfiguration | undefined} */ + [_configuration]; + + /** @type {GPUSurfaceTexture | undefined} */ + [_currentTexture]; + + constructor() { + webidl.illegalConstructor(); + } + + /** + * @param {GPUAdapter} adapter + * @returns {GPUSurfaceCapabilities} + */ + getCapabilities(adapter) { + webidl.assertBranded(this, GPUSurfacePrototype); + + const prefix = "Failed to execute 'getCapabilities' on 'GPUSurface'"; + const rid = assertResource(this, { prefix, context: "this" }); + webidl.requiredArguments(arguments.length, 1, { prefix }); + + adapter = webidl.converters.GPUAdapter(adapter, { + prefix, + context: "Argument 1", + }); + + return ops.op_webgpu_surface_get_capabilities( + rid, + adapter[_adapter].rid, + ); + } + + /** + * @param {GPUDevice} device + * @param {GPUSurfaceConfiguration} config + */ + configure(device, config) { + webidl.assertBranded(this, GPUSurfacePrototype); + + const prefix = "Failed to execute 'configure' on 'GPUSurface'"; + const rid = assertResource(this, { prefix, context: "this" }); + webidl.requiredArguments(arguments.length, 2, { prefix }); + + device = webidl.converters.GPUDevice(device, { + prefix, + context: "Argument 1", + }); + device = assertDevice( + device, + { prefix, context: "Argument 1" }, + ); + + config = webidl.converters.GPUSurfaceConfiguration(config, { + prefix, + context: "Argument 2", + }); + config.size = normalizeGPUExtent3D(config.size); + + ops.op_webgpu_surface_configure(rid, device.rid, config); + + this[_device] = device; + this[_configuration] = config; + } + + /** + * @returns {GPUSurfaceTexture} + */ + getCurrentTexture() { + webidl.assertBranded(this, GPUSurfacePrototype); + + if (this[_currentTexture]) { + return this[_currentTexture]; + } + + const prefix = "Failed to execute 'getCurrentTexture' on 'GPUSurface'"; + const rid = assertResource(this, { prefix, context: "this" }); + const device = assertDevice(this, { prefix, context: "this" }); + + const [textureRid, isSuboptimal] = ops + .op_webgpu_surface_get_current_texture(rid, device.rid); + + this[_currentTexture] = createGPUSurfaceTexture( + { + size: this[_configuration].size, + mipLevelCount: 1, + sampleCount: 1, + dimension: "2d", + format: this[_configuration].format, + usage: this[_configuration].usage, + viewFormats: this[_configuration].viewFormats, + }, + device, + textureRid, + this, + isSuboptimal, + ); + device.trackResource(this[_currentTexture]); + return this[_currentTexture]; + } +} +const GPUSurfacePrototype = GPUSurface.prototype; + +/** + * @param {GPUTextureDescriptor} descriptor + * @param {GPUDevice} device + * @param {number} rid + * @param {GPUSurface} surface + * @param {boolean} isSuboptimal + * @returns {GPUSurfaceTexture} + */ +function createGPUSurfaceTexture( + descriptor, + device, + rid, + surface, + isSuboptimal, +) { + const texture = createGPUTexture( + descriptor, + device, + rid, + GPUSurfaceTexture, + ); + texture[_surface] = surface; + texture[_isSuboptimal] = isSuboptimal; + return texture; +} + +class GPUSurfaceTexture extends GPUTexture { + /** @type {GPUSurface} */ + [_surface]; + + /** @type {boolean} */ + [_isSuboptimal]; + + [_cleanup]() { + if (this[_rid] !== undefined) { + ops.op_webgpu_surface_texture_discard( + this[_surface][_rid], + this[_device].rid, + ); + this[_surface][_currentTexture] = undefined; + } + super[_cleanup](); + } + + /** + * @returns {boolean} + */ + get isSuboptimal() { + webidl.assertBranded(this, GPUSurfaceTexturePrototype); + return this[_isSuboptimal]; + } + + present() { + webidl.assertBranded(this, GPUSurfaceTexturePrototype); + + const prefix = "Failed to execute 'present' on 'GPUSurfaceTexture'"; + const device = assertDevice(this, { prefix, context: "this" }); + assertResource(this, { prefix, context: "this" }); + + ops.op_webgpu_surface_texture_present(this[_surface][_rid], device.rid); + + this[_surface][_currentTexture] = undefined; + super[_cleanup](); + } +} +const GPUSurfaceTexturePrototype = GPUSurfaceTexture.prototype; + +const gpu = webidl.createBranded(GPU); +export { + _device, + assertDevice, + createGPUTexture, + createGPUSurface, + destroyGPUSurface, + GPU, + gpu, + GPUAdapter, + GPUAdapterInfo, + GPUBindGroup, + GPUBindGroupLayout, + GPUBuffer, + GPUBufferUsage, + GPUColorWrite, + GPUCommandBuffer, + GPUCommandEncoder, + GPUComputePassEncoder, + GPUComputePipeline, + GPUDevice, + GPUDeviceLostInfo, + GPUError, + GPUMapMode, + GPUOutOfMemoryError, + GPUPipelineLayout, + GPUQuerySet, + GPUQueue, + GPURenderBundle, + GPURenderBundleEncoder, + GPURenderPassEncoder, + GPURenderPipeline, + GPUSampler, + GPUShaderModule, + GPUShaderStage, + GPUSupportedFeatures, + GPUSupportedLimits, + GPUTexture, + GPUTextureUsage, + GPUTextureView, + GPUValidationError, + GPUSurface, + GPUSurfaceTexture, +}; diff --git a/ext/webgpu/02_idl_types.js b/ext/webgpu/02_idl_types.js new file mode 100644 index 00000000000000..882be07f580448 --- /dev/null +++ b/ext/webgpu/02_idl_types.js @@ -0,0 +1,2116 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. +// Copyright 2023 Jo Bates. All rights reserved. MIT license. + +// @ts-check +/// + +import * as webidl from "internal:deno_webidl/00_webidl.js"; +import { + GPU, + GPUAdapter, + GPUBindGroup, + GPUBindGroupLayout, + GPUBuffer, + GPUBufferUsage, + GPUColorWrite, + GPUCommandBuffer, + GPUCommandEncoder, + GPUComputePassEncoder, + GPUComputePipeline, + GPUDevice, + GPUMapMode, + GPUOutOfMemoryError, + GPUPipelineLayout, + GPUQuerySet, + GPUQueue, + GPURenderBundle, + GPURenderBundleEncoder, + GPURenderPassEncoder, + GPURenderPipeline, + GPUSampler, + GPUShaderModule, + GPUShaderStage, + GPUSupportedFeatures, + GPUSupportedLimits, + GPUSurface, + GPUTexture, + GPUTextureUsage, + GPUTextureView, + GPUValidationError, +} from "internal:deno_webgpu/01_webgpu.js"; +const primordials = globalThis.__bootstrap.primordials; +const { SymbolIterator, TypeError } = primordials; + +// This needs to be initialized after all of the base classes are implemented, +// otherwise their converters might not be available yet. +// DICTIONARY: GPUObjectDescriptorBase +const dictMembersGPUObjectDescriptorBase = [ + { key: "label", converter: webidl.converters["USVString"] }, +]; +webidl.converters["GPUObjectDescriptorBase"] = webidl + .createDictionaryConverter( + "GPUObjectDescriptorBase", + dictMembersGPUObjectDescriptorBase, + ); + +// INTERFACE: GPUSupportedLimits +webidl.converters.GPUSupportedLimits = webidl.createInterfaceConverter( + "GPUSupportedLimits", + GPUSupportedLimits.prototype, +); + +// INTERFACE: GPUSupportedFeatures +webidl.converters.GPUSupportedFeatures = webidl.createInterfaceConverter( + "GPUSupportedFeatures", + GPUSupportedFeatures.prototype, +); + +// INTERFACE: GPU +webidl.converters.GPU = webidl.createInterfaceConverter("GPU", GPU.prototype); + +// ENUM: GPUPowerPreference +webidl.converters["GPUPowerPreference"] = webidl.createEnumConverter( + "GPUPowerPreference", + [ + "low-power", + "high-performance", + ], +); + +// INTERFACE: GPUSurface +webidl.converters.GPUSurface = webidl.createInterfaceConverter( + "GPUSurface", + GPUSurface.prototype, +); + +// DICTIONARY: GPURequestAdapterOptions +const dictMembersGPURequestAdapterOptions = [ + { + key: "powerPreference", + converter: webidl.converters["GPUPowerPreference"], + }, + { + key: "forceFallbackAdapter", + converter: webidl.converters.boolean, + defaultValue: false, + }, + { + key: "compatibleSurface", + converter: webidl.converters["GPUSurface"], + }, +]; +webidl.converters["GPURequestAdapterOptions"] = webidl + .createDictionaryConverter( + "GPURequestAdapterOptions", + dictMembersGPURequestAdapterOptions, + ); + +// INTERFACE: GPUAdapter +webidl.converters.GPUAdapter = webidl.createInterfaceConverter( + "GPUAdapter", + GPUAdapter.prototype, +); + +// ENUM: GPUFeatureName +webidl.converters["GPUFeatureName"] = webidl.createEnumConverter( + "GPUFeatureName", + [ + "depth-clip-control", + "depth32float-stencil8", + "pipeline-statistics-query", + "texture-compression-bc", + "texture-compression-etc2", + "texture-compression-astc", + "timestamp-query", + "indirect-first-instance", + "shader-f16", + // extended from spec + "mappable-primary-buffers", + "texture-binding-array", + "buffer-binding-array", + "storage-resource-binding-array", + "sampled-texture-and-storage-buffer-array-non-uniform-indexing", + "uniform-buffer-and-storage-buffer-texture-non-uniform-indexing", + "unsized-binding-array", + "multi-draw-indirect", + "multi-draw-indirect-count", + "push-constants", + "address-mode-clamp-to-border", + "texture-adapter-specific-format-features", + "shader-float64", + "vertex-attribute-64bit", + "conservative-rasterization", + "vertex-writable-storage", + "clear-commands", + "spirv-shader-passthrough", + "shader-primitive-index", + ], +); + +// TYPEDEF: GPUSize32 +webidl.converters["GPUSize32"] = (V, opts) => + webidl.converters["unsigned long"](V, { ...opts, enforceRange: true }); + +// TYPEDEF: GPUSize64 +webidl.converters["GPUSize64"] = (V, opts) => + webidl.converters["unsigned long long"](V, { ...opts, enforceRange: true }); + +// DICTIONARY: GPUDeviceDescriptor +const dictMembersGPUDeviceDescriptor = [ + { + key: "requiredFeatures", + converter: webidl.createSequenceConverter( + webidl.converters["GPUFeatureName"], + ), + get defaultValue() { + return []; + }, + }, + { + key: "requiredLimits", + converter: webidl.createRecordConverter( + webidl.converters["DOMString"], + webidl.converters["GPUSize64"], + ), + }, +]; +webidl.converters["GPUDeviceDescriptor"] = webidl.createDictionaryConverter( + "GPUDeviceDescriptor", + dictMembersGPUObjectDescriptorBase, + dictMembersGPUDeviceDescriptor, +); + +// INTERFACE: GPUDevice +webidl.converters.GPUDevice = webidl.createInterfaceConverter( + "GPUDevice", + GPUDevice.prototype, +); + +// INTERFACE: GPUBuffer +webidl.converters.GPUBuffer = webidl.createInterfaceConverter( + "GPUBuffer", + GPUBuffer.prototype, +); + +// TYPEDEF: GPUBufferUsageFlags +webidl.converters["GPUBufferUsageFlags"] = (V, opts) => + webidl.converters["unsigned long"](V, { ...opts, enforceRange: true }); + +// DICTIONARY: GPUBufferDescriptor +const dictMembersGPUBufferDescriptor = [ + { key: "size", converter: webidl.converters["GPUSize64"], required: true }, + { + key: "usage", + converter: webidl.converters["GPUBufferUsageFlags"], + required: true, + }, + { + key: "mappedAtCreation", + converter: webidl.converters["boolean"], + defaultValue: false, + }, +]; +webidl.converters["GPUBufferDescriptor"] = webidl.createDictionaryConverter( + "GPUBufferDescriptor", + dictMembersGPUObjectDescriptorBase, + dictMembersGPUBufferDescriptor, +); + +// INTERFACE: GPUBufferUsage +webidl.converters.GPUBufferUsage = webidl.createInterfaceConverter( + "GPUBufferUsage", + GPUBufferUsage.prototype, +); + +// TYPEDEF: GPUMapModeFlags +webidl.converters["GPUMapModeFlags"] = (V, opts) => + webidl.converters["unsigned long"](V, { ...opts, enforceRange: true }); + +// INTERFACE: GPUMapMode +webidl.converters.GPUMapMode = webidl.createInterfaceConverter( + "GPUMapMode", + GPUMapMode.prototype, +); + +// INTERFACE: GPUTexture +webidl.converters.GPUTexture = webidl.createInterfaceConverter( + "GPUTexture", + GPUTexture.prototype, +); + +// TYPEDEF: GPUIntegerCoordinate +webidl.converters["GPUIntegerCoordinate"] = (V, opts) => + webidl.converters["unsigned long"](V, { ...opts, enforceRange: true }); +webidl.converters["sequence"] = webidl + .createSequenceConverter(webidl.converters["GPUIntegerCoordinate"]); + +// DICTIONARY: GPUExtent3DDict +const dictMembersGPUExtent3DDict = [ + { + key: "width", + converter: webidl.converters["GPUIntegerCoordinate"], + required: true, + }, + { + key: "height", + converter: webidl.converters["GPUIntegerCoordinate"], + defaultValue: 1, + }, + { + key: "depthOrArrayLayers", + converter: webidl.converters["GPUIntegerCoordinate"], + defaultValue: 1, + }, +]; +webidl.converters["GPUExtent3DDict"] = webidl.createDictionaryConverter( + "GPUExtent3DDict", + dictMembersGPUExtent3DDict, +); + +// TYPEDEF: GPUExtent3D +webidl.converters["GPUExtent3D"] = (V, opts) => { + // Union for (sequence or GPUExtent3DDict) + if (V === null || V === undefined) { + return webidl.converters["GPUExtent3DDict"](V, opts); + } + if (typeof V === "object") { + const method = V[SymbolIterator]; + if (method !== undefined) { + return webidl.converters["sequence"](V, opts); + } + return webidl.converters["GPUExtent3DDict"](V, opts); + } + throw webidl.makeException( + TypeError, + "can not be converted to sequence or GPUExtent3DDict.", + opts, + ); +}; + +// ENUM: GPUTextureDimension +webidl.converters["GPUTextureDimension"] = webidl.createEnumConverter( + "GPUTextureDimension", + [ + "1d", + "2d", + "3d", + ], +); + +// ENUM: GPUTextureFormat +webidl.converters["GPUTextureFormat"] = webidl.createEnumConverter( + "GPUTextureFormat", + [ + "r8unorm", + "r8snorm", + "r8uint", + "r8sint", + "r16uint", + "r16sint", + "r16float", + "rg8unorm", + "rg8snorm", + "rg8uint", + "rg8sint", + "r32uint", + "r32sint", + "r32float", + "rg16uint", + "rg16sint", + "rg16float", + "rgba8unorm", + "rgba8unorm-srgb", + "rgba8snorm", + "rgba8uint", + "rgba8sint", + "bgra8unorm", + "bgra8unorm-srgb", + "rgb9e5ufloat", + "rgb10a2unorm", + "rg11b10ufloat", + "rg32uint", + "rg32sint", + "rg32float", + "rgba16uint", + "rgba16sint", + "rgba16float", + "rgba32uint", + "rgba32sint", + "rgba32float", + "stencil8", + "depth16unorm", + "depth24plus", + "depth24plus-stencil8", + "depth32float", + "depth32float-stencil8", + "bc1-rgba-unorm", + "bc1-rgba-unorm-srgb", + "bc2-rgba-unorm", + "bc2-rgba-unorm-srgb", + "bc3-rgba-unorm", + "bc3-rgba-unorm-srgb", + "bc4-r-unorm", + "bc4-r-snorm", + "bc5-rg-unorm", + "bc5-rg-snorm", + "bc6h-rgb-ufloat", + "bc6h-rgb-float", + "bc7-rgba-unorm", + "bc7-rgba-unorm-srgb", + "etc2-rgb8unorm", + "etc2-rgb8unorm-srgb", + "etc2-rgb8a1unorm", + "etc2-rgb8a1unorm-srgb", + "etc2-rgba8unorm", + "etc2-rgba8unorm-srgb", + "eac-r11unorm", + "eac-r11snorm", + "eac-rg11unorm", + "eac-rg11snorm", + "astc-4x4-unorm", + "astc-4x4-unorm-srgb", + "astc-5x4-unorm", + "astc-5x4-unorm-srgb", + "astc-5x5-unorm", + "astc-5x5-unorm-srgb", + "astc-6x5-unorm", + "astc-6x5-unorm-srgb", + "astc-6x6-unorm", + "astc-6x6-unorm-srgb", + "astc-8x5-unorm", + "astc-8x5-unorm-srgb", + "astc-8x6-unorm", + "astc-8x6-unorm-srgb", + "astc-8x8-unorm", + "astc-8x8-unorm-srgb", + "astc-10x5-unorm", + "astc-10x5-unorm-srgb", + "astc-10x6-unorm", + "astc-10x6-unorm-srgb", + "astc-10x8-unorm", + "astc-10x8-unorm-srgb", + "astc-10x10-unorm", + "astc-10x10-unorm-srgb", + "astc-12x10-unorm", + "astc-12x10-unorm-srgb", + "astc-12x12-unorm", + "astc-12x12-unorm-srgb", + ], +); + +// TYPEDEF: GPUTextureUsageFlags +webidl.converters["GPUTextureUsageFlags"] = (V, opts) => + webidl.converters["unsigned long"](V, { ...opts, enforceRange: true }); + +// DICTIONARY: GPUTextureDescriptor +const dictMembersGPUTextureDescriptor = [ + { + key: "size", + converter: webidl.converters["GPUExtent3D"], + required: true, + }, + { + key: "mipLevelCount", + converter: webidl.converters["GPUIntegerCoordinate"], + defaultValue: 1, + }, + { + key: "sampleCount", + converter: webidl.converters["GPUSize32"], + defaultValue: 1, + }, + { + key: "dimension", + converter: webidl.converters["GPUTextureDimension"], + defaultValue: "2d", + }, + { + key: "format", + converter: webidl.converters["GPUTextureFormat"], + required: true, + }, + { + key: "usage", + converter: webidl.converters["GPUTextureUsageFlags"], + required: true, + }, + { + key: "viewFormats", + converter: webidl.createSequenceConverter( + webidl.converters["GPUTextureFormat"], + ), + get defaultValue() { + return []; + }, + }, +]; +webidl.converters["GPUTextureDescriptor"] = webidl.createDictionaryConverter( + "GPUTextureDescriptor", + dictMembersGPUObjectDescriptorBase, + dictMembersGPUTextureDescriptor, +); + +// INTERFACE: GPUTextureUsage +webidl.converters.GPUTextureUsage = webidl.createInterfaceConverter( + "GPUTextureUsage", + GPUTextureUsage.prototype, +); + +// INTERFACE: GPUTextureView +webidl.converters.GPUTextureView = webidl.createInterfaceConverter( + "GPUTextureView", + GPUTextureView.prototype, +); + +// ENUM: GPUTextureViewDimension +webidl.converters["GPUTextureViewDimension"] = webidl.createEnumConverter( + "GPUTextureViewDimension", + [ + "1d", + "2d", + "2d-array", + "cube", + "cube-array", + "3d", + ], +); + +// ENUM: GPUTextureAspect +webidl.converters["GPUTextureAspect"] = webidl.createEnumConverter( + "GPUTextureAspect", + [ + "all", + "stencil-only", + "depth-only", + ], +); + +// DICTIONARY: GPUTextureViewDescriptor +const dictMembersGPUTextureViewDescriptor = [ + { key: "format", converter: webidl.converters["GPUTextureFormat"] }, + { + key: "dimension", + converter: webidl.converters["GPUTextureViewDimension"], + }, + { + key: "aspect", + converter: webidl.converters["GPUTextureAspect"], + defaultValue: "all", + }, + { + key: "baseMipLevel", + converter: webidl.converters["GPUIntegerCoordinate"], + defaultValue: 0, + }, + { + key: "mipLevelCount", + converter: webidl.converters["GPUIntegerCoordinate"], + }, + { + key: "baseArrayLayer", + converter: webidl.converters["GPUIntegerCoordinate"], + defaultValue: 0, + }, + { + key: "arrayLayerCount", + converter: webidl.converters["GPUIntegerCoordinate"], + }, +]; +webidl.converters["GPUTextureViewDescriptor"] = webidl + .createDictionaryConverter( + "GPUTextureViewDescriptor", + dictMembersGPUObjectDescriptorBase, + dictMembersGPUTextureViewDescriptor, + ); + +// INTERFACE: GPUSampler +webidl.converters.GPUSampler = webidl.createInterfaceConverter( + "GPUSampler", + GPUSampler.prototype, +); + +// ENUM: GPUAddressMode +webidl.converters["GPUAddressMode"] = webidl.createEnumConverter( + "GPUAddressMode", + [ + "clamp-to-edge", + "repeat", + "mirror-repeat", + ], +); + +// ENUM: GPUFilterMode +webidl.converters["GPUFilterMode"] = webidl.createEnumConverter( + "GPUFilterMode", + [ + "nearest", + "linear", + ], +); + +// ENUM: GPUMipmapFilterMode +webidl.converters["GPUMipmapFilterMode"] = webidl.createEnumConverter( + "GPUMipmapFilterMode", + [ + "nearest", + "linear", + ], +); + +// ENUM: GPUCompareFunction +webidl.converters["GPUCompareFunction"] = webidl.createEnumConverter( + "GPUCompareFunction", + [ + "never", + "less", + "equal", + "less-equal", + "greater", + "not-equal", + "greater-equal", + "always", + ], +); + +// DICTIONARY: GPUSamplerDescriptor +const dictMembersGPUSamplerDescriptor = [ + { + key: "addressModeU", + converter: webidl.converters["GPUAddressMode"], + defaultValue: "clamp-to-edge", + }, + { + key: "addressModeV", + converter: webidl.converters["GPUAddressMode"], + defaultValue: "clamp-to-edge", + }, + { + key: "addressModeW", + converter: webidl.converters["GPUAddressMode"], + defaultValue: "clamp-to-edge", + }, + { + key: "magFilter", + converter: webidl.converters["GPUFilterMode"], + defaultValue: "nearest", + }, + { + key: "minFilter", + converter: webidl.converters["GPUFilterMode"], + defaultValue: "nearest", + }, + { + key: "mipmapFilter", + converter: webidl.converters["GPUMipmapFilterMode"], + defaultValue: "nearest", + }, + { + key: "lodMinClamp", + converter: webidl.converters["float"], + defaultValue: 0, + }, + { + key: "lodMaxClamp", + converter: webidl.converters["float"], + defaultValue: 0xffffffff, + }, + { key: "compare", converter: webidl.converters["GPUCompareFunction"] }, + { + key: "maxAnisotropy", + converter: (V, opts) => + webidl.converters["unsigned short"](V, { ...opts, clamp: true }), + defaultValue: 1, + }, +]; +webidl.converters["GPUSamplerDescriptor"] = webidl.createDictionaryConverter( + "GPUSamplerDescriptor", + dictMembersGPUObjectDescriptorBase, + dictMembersGPUSamplerDescriptor, +); + +// INTERFACE: GPUBindGroupLayout +webidl.converters.GPUBindGroupLayout = webidl.createInterfaceConverter( + "GPUBindGroupLayout", + GPUBindGroupLayout.prototype, +); + +// TYPEDEF: GPUIndex32 +webidl.converters["GPUIndex32"] = (V, opts) => + webidl.converters["unsigned long"](V, { ...opts, enforceRange: true }); + +// TYPEDEF: GPUShaderStageFlags +webidl.converters["GPUShaderStageFlags"] = (V, opts) => + webidl.converters["unsigned long"](V, { ...opts, enforceRange: true }); + +// ENUM: GPUBufferBindingType +webidl.converters["GPUBufferBindingType"] = webidl.createEnumConverter( + "GPUBufferBindingType", + [ + "uniform", + "storage", + "read-only-storage", + ], +); + +// DICTIONARY: GPUBufferBindingLayout +const dictMembersGPUBufferBindingLayout = [ + { + key: "type", + converter: webidl.converters["GPUBufferBindingType"], + defaultValue: "uniform", + }, + { + key: "hasDynamicOffset", + converter: webidl.converters["boolean"], + defaultValue: false, + }, + { + key: "minBindingSize", + converter: webidl.converters["GPUSize64"], + defaultValue: 0, + }, +]; +webidl.converters["GPUBufferBindingLayout"] = webidl + .createDictionaryConverter( + "GPUBufferBindingLayout", + dictMembersGPUBufferBindingLayout, + ); + +// ENUM: GPUSamplerBindingType +webidl.converters["GPUSamplerBindingType"] = webidl.createEnumConverter( + "GPUSamplerBindingType", + [ + "filtering", + "non-filtering", + "comparison", + ], +); + +// DICTIONARY: GPUSamplerBindingLayout +const dictMembersGPUSamplerBindingLayout = [ + { + key: "type", + converter: webidl.converters["GPUSamplerBindingType"], + defaultValue: "filtering", + }, +]; +webidl.converters["GPUSamplerBindingLayout"] = webidl + .createDictionaryConverter( + "GPUSamplerBindingLayout", + dictMembersGPUSamplerBindingLayout, + ); + +// ENUM: GPUTextureSampleType +webidl.converters["GPUTextureSampleType"] = webidl.createEnumConverter( + "GPUTextureSampleType", + [ + "float", + "unfilterable-float", + "depth", + "sint", + "uint", + ], +); + +// DICTIONARY: GPUTextureBindingLayout +const dictMembersGPUTextureBindingLayout = [ + { + key: "sampleType", + converter: webidl.converters["GPUTextureSampleType"], + defaultValue: "float", + }, + { + key: "viewDimension", + converter: webidl.converters["GPUTextureViewDimension"], + defaultValue: "2d", + }, + { + key: "multisampled", + converter: webidl.converters["boolean"], + defaultValue: false, + }, +]; +webidl.converters["GPUTextureBindingLayout"] = webidl + .createDictionaryConverter( + "GPUTextureBindingLayout", + dictMembersGPUTextureBindingLayout, + ); + +// ENUM: GPUStorageTextureAccess +webidl.converters["GPUStorageTextureAccess"] = webidl.createEnumConverter( + "GPUStorageTextureAccess", + [ + "write-only", + ], +); + +// DICTIONARY: GPUStorageTextureBindingLayout +const dictMembersGPUStorageTextureBindingLayout = [ + { + key: "access", + converter: webidl.converters["GPUStorageTextureAccess"], + defaultValue: "write-only", + }, + { + key: "format", + converter: webidl.converters["GPUTextureFormat"], + required: true, + }, + { + key: "viewDimension", + converter: webidl.converters["GPUTextureViewDimension"], + defaultValue: "2d", + }, +]; +webidl.converters["GPUStorageTextureBindingLayout"] = webidl + .createDictionaryConverter( + "GPUStorageTextureBindingLayout", + dictMembersGPUStorageTextureBindingLayout, + ); + +// DICTIONARY: GPUBindGroupLayoutEntry +const dictMembersGPUBindGroupLayoutEntry = [ + { + key: "binding", + converter: webidl.converters["GPUIndex32"], + required: true, + }, + { + key: "visibility", + converter: webidl.converters["GPUShaderStageFlags"], + required: true, + }, + { key: "buffer", converter: webidl.converters["GPUBufferBindingLayout"] }, + { key: "sampler", converter: webidl.converters["GPUSamplerBindingLayout"] }, + { key: "texture", converter: webidl.converters["GPUTextureBindingLayout"] }, + { + key: "storageTexture", + converter: webidl.converters["GPUStorageTextureBindingLayout"], + }, +]; +webidl.converters["GPUBindGroupLayoutEntry"] = webidl + .createDictionaryConverter( + "GPUBindGroupLayoutEntry", + dictMembersGPUBindGroupLayoutEntry, + ); + +// DICTIONARY: GPUBindGroupLayoutDescriptor +const dictMembersGPUBindGroupLayoutDescriptor = [ + { + key: "entries", + converter: webidl.createSequenceConverter( + webidl.converters["GPUBindGroupLayoutEntry"], + ), + required: true, + }, +]; +webidl.converters["GPUBindGroupLayoutDescriptor"] = webidl + .createDictionaryConverter( + "GPUBindGroupLayoutDescriptor", + dictMembersGPUObjectDescriptorBase, + dictMembersGPUBindGroupLayoutDescriptor, + ); + +// INTERFACE: GPUShaderStage +webidl.converters.GPUShaderStage = webidl.createInterfaceConverter( + "GPUShaderStage", + GPUShaderStage.prototype, +); + +// INTERFACE: GPUBindGroup +webidl.converters.GPUBindGroup = webidl.createInterfaceConverter( + "GPUBindGroup", + GPUBindGroup.prototype, +); + +// DICTIONARY: GPUBufferBinding +const dictMembersGPUBufferBinding = [ + { + key: "buffer", + converter: webidl.converters["GPUBuffer"], + required: true, + }, + { + key: "offset", + converter: webidl.converters["GPUSize64"], + defaultValue: 0, + }, + { key: "size", converter: webidl.converters["GPUSize64"] }, +]; +webidl.converters["GPUBufferBinding"] = webidl.createDictionaryConverter( + "GPUBufferBinding", + dictMembersGPUBufferBinding, +); + +// TYPEDEF: GPUBindingResource +webidl.converters["GPUBindingResource"] = + webidl.converters.any /** put union here! **/; + +// DICTIONARY: GPUBindGroupEntry +const dictMembersGPUBindGroupEntry = [ + { + key: "binding", + converter: webidl.converters["GPUIndex32"], + required: true, + }, + { + key: "resource", + converter: webidl.converters["GPUBindingResource"], + required: true, + }, +]; +webidl.converters["GPUBindGroupEntry"] = webidl.createDictionaryConverter( + "GPUBindGroupEntry", + dictMembersGPUBindGroupEntry, +); + +// DICTIONARY: GPUBindGroupDescriptor +const dictMembersGPUBindGroupDescriptor = [ + { + key: "layout", + converter: webidl.converters["GPUBindGroupLayout"], + required: true, + }, + { + key: "entries", + converter: webidl.createSequenceConverter( + webidl.converters["GPUBindGroupEntry"], + ), + required: true, + }, +]; +webidl.converters["GPUBindGroupDescriptor"] = webidl + .createDictionaryConverter( + "GPUBindGroupDescriptor", + dictMembersGPUObjectDescriptorBase, + dictMembersGPUBindGroupDescriptor, + ); + +// INTERFACE: GPUPipelineLayout +webidl.converters.GPUPipelineLayout = webidl.createInterfaceConverter( + "GPUPipelineLayout", + GPUPipelineLayout.prototype, +); + +// DICTIONARY: GPUPipelineLayoutDescriptor +const dictMembersGPUPipelineLayoutDescriptor = [ + { + key: "bindGroupLayouts", + converter: webidl.createSequenceConverter( + webidl.converters["GPUBindGroupLayout"], + ), + required: true, + }, +]; +webidl.converters["GPUPipelineLayoutDescriptor"] = webidl + .createDictionaryConverter( + "GPUPipelineLayoutDescriptor", + dictMembersGPUObjectDescriptorBase, + dictMembersGPUPipelineLayoutDescriptor, + ); + +// INTERFACE: GPUShaderModule +webidl.converters.GPUShaderModule = webidl.createInterfaceConverter( + "GPUShaderModule", + GPUShaderModule.prototype, +); + +// DICTIONARY: GPUShaderModuleDescriptor +const dictMembersGPUShaderModuleDescriptor = [ + { + key: "code", + converter: webidl.converters["DOMString"], + required: true, + }, +]; +webidl.converters["GPUShaderModuleDescriptor"] = webidl + .createDictionaryConverter( + "GPUShaderModuleDescriptor", + dictMembersGPUObjectDescriptorBase, + dictMembersGPUShaderModuleDescriptor, + ); + +// // ENUM: GPUCompilationMessageType +// webidl.converters["GPUCompilationMessageType"] = webidl.createEnumConverter( +// "GPUCompilationMessageType", +// [ +// "error", +// "warning", +// "info", +// ], +// ); + +// // INTERFACE: GPUCompilationMessage +// webidl.converters.GPUCompilationMessage = webidl.createInterfaceConverter( +// "GPUCompilationMessage", +// GPUCompilationMessage.prototype, +// ); + +// // INTERFACE: GPUCompilationInfo +// webidl.converters.GPUCompilationInfo = webidl.createInterfaceConverter( +// "GPUCompilationInfo", +// GPUCompilationInfo.prototype, +// ); + +webidl.converters["GPUAutoLayoutMode"] = webidl.createEnumConverter( + "GPUAutoLayoutMode", + [ + "auto", + ], +); + +webidl.converters["GPUPipelineLayout or GPUAutoLayoutMode"] = (V, opts) => { + if (typeof V === "object") { + return webidl.converters["GPUPipelineLayout"](V, opts); + } + return webidl.converters["GPUAutoLayoutMode"](V, opts); +}; + +// DICTIONARY: GPUPipelineDescriptorBase +const dictMembersGPUPipelineDescriptorBase = [ + { + key: "layout", + converter: webidl.converters["GPUPipelineLayout or GPUAutoLayoutMode"], + }, +]; +webidl.converters["GPUPipelineDescriptorBase"] = webidl + .createDictionaryConverter( + "GPUPipelineDescriptorBase", + dictMembersGPUObjectDescriptorBase, + dictMembersGPUPipelineDescriptorBase, + ); + +// TYPEDEF: GPUPipelineConstantValue +webidl.converters.GPUPipelineConstantValue = webidl.converters.double; + +webidl.converters["record"] = webidl + .createRecordConverter( + webidl.converters.USVString, + webidl.converters.GPUPipelineConstantValue, + ); + +// DICTIONARY: GPUProgrammableStage +const dictMembersGPUProgrammableStage = [ + { + key: "module", + converter: webidl.converters["GPUShaderModule"], + required: true, + }, + { + key: "entryPoint", + converter: webidl.converters["USVString"], + required: true, + }, + { + key: "constants", + converter: webidl.converters["record"], + }, +]; +webidl.converters["GPUProgrammableStage"] = webidl.createDictionaryConverter( + "GPUProgrammableStage", + dictMembersGPUProgrammableStage, +); + +// INTERFACE: GPUComputePipeline +webidl.converters.GPUComputePipeline = webidl.createInterfaceConverter( + "GPUComputePipeline", + GPUComputePipeline.prototype, +); + +// DICTIONARY: GPUComputePipelineDescriptor +const dictMembersGPUComputePipelineDescriptor = [ + { + key: "compute", + converter: webidl.converters["GPUProgrammableStage"], + required: true, + }, +]; +webidl.converters["GPUComputePipelineDescriptor"] = webidl + .createDictionaryConverter( + "GPUComputePipelineDescriptor", + dictMembersGPUObjectDescriptorBase, + dictMembersGPUPipelineDescriptorBase, + dictMembersGPUComputePipelineDescriptor, + ); + +// INTERFACE: GPURenderPipeline +webidl.converters.GPURenderPipeline = webidl.createInterfaceConverter( + "GPURenderPipeline", + GPURenderPipeline.prototype, +); + +// ENUM: GPUVertexStepMode +webidl.converters["GPUVertexStepMode"] = webidl.createEnumConverter( + "GPUVertexStepMode", + [ + "vertex", + "instance", + ], +); + +// ENUM: GPUVertexFormat +webidl.converters["GPUVertexFormat"] = webidl.createEnumConverter( + "GPUVertexFormat", + [ + "uint8x2", + "uint8x4", + "sint8x2", + "sint8x4", + "unorm8x2", + "unorm8x4", + "snorm8x2", + "snorm8x4", + "uint16x2", + "uint16x4", + "sint16x2", + "sint16x4", + "unorm16x2", + "unorm16x4", + "snorm16x2", + "snorm16x4", + "float16x2", + "float16x4", + "float32", + "float32x2", + "float32x3", + "float32x4", + "uint32", + "uint32x2", + "uint32x3", + "uint32x4", + "sint32", + "sint32x2", + "sint32x3", + "sint32x4", + ], +); + +// DICTIONARY: GPUVertexAttribute +const dictMembersGPUVertexAttribute = [ + { + key: "format", + converter: webidl.converters["GPUVertexFormat"], + required: true, + }, + { + key: "offset", + converter: webidl.converters["GPUSize64"], + required: true, + }, + { + key: "shaderLocation", + converter: webidl.converters["GPUIndex32"], + required: true, + }, +]; +webidl.converters["GPUVertexAttribute"] = webidl.createDictionaryConverter( + "GPUVertexAttribute", + dictMembersGPUVertexAttribute, +); + +// DICTIONARY: GPUVertexBufferLayout +const dictMembersGPUVertexBufferLayout = [ + { + key: "arrayStride", + converter: webidl.converters["GPUSize64"], + required: true, + }, + { + key: "stepMode", + converter: webidl.converters["GPUVertexStepMode"], + defaultValue: "vertex", + }, + { + key: "attributes", + converter: webidl.createSequenceConverter( + webidl.converters["GPUVertexAttribute"], + ), + required: true, + }, +]; +webidl.converters["GPUVertexBufferLayout"] = webidl.createDictionaryConverter( + "GPUVertexBufferLayout", + dictMembersGPUVertexBufferLayout, +); + +// DICTIONARY: GPUVertexState +const dictMembersGPUVertexState = [ + { + key: "buffers", + converter: webidl.createSequenceConverter( + webidl.createNullableConverter( + webidl.converters["GPUVertexBufferLayout"], + ), + ), + get defaultValue() { + return []; + }, + }, +]; +webidl.converters["GPUVertexState"] = webidl.createDictionaryConverter( + "GPUVertexState", + dictMembersGPUProgrammableStage, + dictMembersGPUVertexState, +); + +// ENUM: GPUPrimitiveTopology +webidl.converters["GPUPrimitiveTopology"] = webidl.createEnumConverter( + "GPUPrimitiveTopology", + [ + "point-list", + "line-list", + "line-strip", + "triangle-list", + "triangle-strip", + ], +); + +// ENUM: GPUIndexFormat +webidl.converters["GPUIndexFormat"] = webidl.createEnumConverter( + "GPUIndexFormat", + [ + "uint16", + "uint32", + ], +); + +// ENUM: GPUFrontFace +webidl.converters["GPUFrontFace"] = webidl.createEnumConverter( + "GPUFrontFace", + [ + "ccw", + "cw", + ], +); + +// ENUM: GPUCullMode +webidl.converters["GPUCullMode"] = webidl.createEnumConverter("GPUCullMode", [ + "none", + "front", + "back", +]); + +// DICTIONARY: GPUPrimitiveState +const dictMembersGPUPrimitiveState = [ + { + key: "topology", + converter: webidl.converters["GPUPrimitiveTopology"], + defaultValue: "triangle-list", + }, + { key: "stripIndexFormat", converter: webidl.converters["GPUIndexFormat"] }, + { + key: "frontFace", + converter: webidl.converters["GPUFrontFace"], + defaultValue: "ccw", + }, + { + key: "cullMode", + converter: webidl.converters["GPUCullMode"], + defaultValue: "none", + }, + { + key: "unclippedDepth", + converter: webidl.converters["boolean"], + defaultValue: false, + }, +]; +webidl.converters["GPUPrimitiveState"] = webidl.createDictionaryConverter( + "GPUPrimitiveState", + dictMembersGPUPrimitiveState, +); + +// ENUM: GPUStencilOperation +webidl.converters["GPUStencilOperation"] = webidl.createEnumConverter( + "GPUStencilOperation", + [ + "keep", + "zero", + "replace", + "invert", + "increment-clamp", + "decrement-clamp", + "increment-wrap", + "decrement-wrap", + ], +); + +// DICTIONARY: GPUStencilFaceState +const dictMembersGPUStencilFaceState = [ + { + key: "compare", + converter: webidl.converters["GPUCompareFunction"], + defaultValue: "always", + }, + { + key: "failOp", + converter: webidl.converters["GPUStencilOperation"], + defaultValue: "keep", + }, + { + key: "depthFailOp", + converter: webidl.converters["GPUStencilOperation"], + defaultValue: "keep", + }, + { + key: "passOp", + converter: webidl.converters["GPUStencilOperation"], + defaultValue: "keep", + }, +]; +webidl.converters["GPUStencilFaceState"] = webidl.createDictionaryConverter( + "GPUStencilFaceState", + dictMembersGPUStencilFaceState, +); + +// TYPEDEF: GPUStencilValue +webidl.converters["GPUStencilValue"] = (V, opts) => + webidl.converters["unsigned long"](V, { ...opts, enforceRange: true }); + +// TYPEDEF: GPUDepthBias +webidl.converters["GPUDepthBias"] = (V, opts) => + webidl.converters["long"](V, { ...opts, enforceRange: true }); + +// DICTIONARY: GPUDepthStencilState +const dictMembersGPUDepthStencilState = [ + { + key: "format", + converter: webidl.converters["GPUTextureFormat"], + required: true, + }, + { + key: "depthWriteEnabled", + converter: webidl.converters["boolean"], + defaultValue: false, + }, + { + key: "depthCompare", + converter: webidl.converters["GPUCompareFunction"], + defaultValue: "always", + }, + { + key: "stencilFront", + converter: webidl.converters["GPUStencilFaceState"], + get defaultValue() { + return {}; + }, + }, + { + key: "stencilBack", + converter: webidl.converters["GPUStencilFaceState"], + get defaultValue() { + return {}; + }, + }, + { + key: "stencilReadMask", + converter: webidl.converters["GPUStencilValue"], + defaultValue: 0xFFFFFFFF, + }, + { + key: "stencilWriteMask", + converter: webidl.converters["GPUStencilValue"], + defaultValue: 0xFFFFFFFF, + }, + { + key: "depthBias", + converter: webidl.converters["GPUDepthBias"], + defaultValue: 0, + }, + { + key: "depthBiasSlopeScale", + converter: webidl.converters["float"], + defaultValue: 0, + }, + { + key: "depthBiasClamp", + converter: webidl.converters["float"], + defaultValue: 0, + }, +]; +webidl.converters["GPUDepthStencilState"] = webidl.createDictionaryConverter( + "GPUDepthStencilState", + dictMembersGPUDepthStencilState, +); + +// TYPEDEF: GPUSampleMask +webidl.converters["GPUSampleMask"] = (V, opts) => + webidl.converters["unsigned long"](V, { ...opts, enforceRange: true }); + +// DICTIONARY: GPUMultisampleState +const dictMembersGPUMultisampleState = [ + { + key: "count", + converter: webidl.converters["GPUSize32"], + defaultValue: 1, + }, + { + key: "mask", + converter: webidl.converters["GPUSampleMask"], + defaultValue: 0xFFFFFFFF, + }, + { + key: "alphaToCoverageEnabled", + converter: webidl.converters["boolean"], + defaultValue: false, + }, +]; +webidl.converters["GPUMultisampleState"] = webidl.createDictionaryConverter( + "GPUMultisampleState", + dictMembersGPUMultisampleState, +); + +// ENUM: GPUBlendFactor +webidl.converters["GPUBlendFactor"] = webidl.createEnumConverter( + "GPUBlendFactor", + [ + "zero", + "one", + "src", + "one-minus-src", + "src-alpha", + "one-minus-src-alpha", + "dst", + "one-minus-dst", + "dst-alpha", + "one-minus-dst-alpha", + "src-alpha-saturated", + "constant", + "one-minus-constant", + ], +); + +// ENUM: GPUBlendOperation +webidl.converters["GPUBlendOperation"] = webidl.createEnumConverter( + "GPUBlendOperation", + [ + "add", + "subtract", + "reverse-subtract", + "min", + "max", + ], +); + +// DICTIONARY: GPUBlendComponent +const dictMembersGPUBlendComponent = [ + { + key: "srcFactor", + converter: webidl.converters["GPUBlendFactor"], + defaultValue: "one", + }, + { + key: "dstFactor", + converter: webidl.converters["GPUBlendFactor"], + defaultValue: "zero", + }, + { + key: "operation", + converter: webidl.converters["GPUBlendOperation"], + defaultValue: "add", + }, +]; +webidl.converters["GPUBlendComponent"] = webidl.createDictionaryConverter( + "GPUBlendComponent", + dictMembersGPUBlendComponent, +); + +// DICTIONARY: GPUBlendState +const dictMembersGPUBlendState = [ + { + key: "color", + converter: webidl.converters["GPUBlendComponent"], + required: true, + }, + { + key: "alpha", + converter: webidl.converters["GPUBlendComponent"], + required: true, + }, +]; +webidl.converters["GPUBlendState"] = webidl.createDictionaryConverter( + "GPUBlendState", + dictMembersGPUBlendState, +); + +// TYPEDEF: GPUColorWriteFlags +webidl.converters["GPUColorWriteFlags"] = (V, opts) => + webidl.converters["unsigned long"](V, { ...opts, enforceRange: true }); + +// DICTIONARY: GPUColorTargetState +const dictMembersGPUColorTargetState = [ + { + key: "format", + converter: webidl.converters["GPUTextureFormat"], + required: true, + }, + { key: "blend", converter: webidl.converters["GPUBlendState"] }, + { + key: "writeMask", + converter: webidl.converters["GPUColorWriteFlags"], + defaultValue: 0xF, + }, +]; +webidl.converters["GPUColorTargetState"] = webidl.createDictionaryConverter( + "GPUColorTargetState", + dictMembersGPUColorTargetState, +); + +// DICTIONARY: GPUFragmentState +const dictMembersGPUFragmentState = [ + { + key: "targets", + converter: webidl.createSequenceConverter( + webidl.createNullableConverter( + webidl.converters["GPUColorTargetState"], + ), + ), + required: true, + }, +]; +webidl.converters["GPUFragmentState"] = webidl.createDictionaryConverter( + "GPUFragmentState", + dictMembersGPUProgrammableStage, + dictMembersGPUFragmentState, +); + +// DICTIONARY: GPURenderPipelineDescriptor +const dictMembersGPURenderPipelineDescriptor = [ + { + key: "vertex", + converter: webidl.converters["GPUVertexState"], + required: true, + }, + { + key: "primitive", + converter: webidl.converters["GPUPrimitiveState"], + get defaultValue() { + return {}; + }, + }, + { + key: "depthStencil", + converter: webidl.converters["GPUDepthStencilState"], + }, + { + key: "multisample", + converter: webidl.converters["GPUMultisampleState"], + get defaultValue() { + return {}; + }, + }, + { key: "fragment", converter: webidl.converters["GPUFragmentState"] }, +]; +webidl.converters["GPURenderPipelineDescriptor"] = webidl + .createDictionaryConverter( + "GPURenderPipelineDescriptor", + dictMembersGPUObjectDescriptorBase, + dictMembersGPUPipelineDescriptorBase, + dictMembersGPURenderPipelineDescriptor, + ); + +// INTERFACE: GPUColorWrite +webidl.converters.GPUColorWrite = webidl.createInterfaceConverter( + "GPUColorWrite", + GPUColorWrite.prototype, +); + +// INTERFACE: GPUCommandBuffer +webidl.converters.GPUCommandBuffer = webidl.createInterfaceConverter( + "GPUCommandBuffer", + GPUCommandBuffer.prototype, +); +webidl.converters["sequence"] = webidl + .createSequenceConverter(webidl.converters["GPUCommandBuffer"]); + +// DICTIONARY: GPUCommandBufferDescriptor +const dictMembersGPUCommandBufferDescriptor = []; +webidl.converters["GPUCommandBufferDescriptor"] = webidl + .createDictionaryConverter( + "GPUCommandBufferDescriptor", + dictMembersGPUObjectDescriptorBase, + dictMembersGPUCommandBufferDescriptor, + ); + +// INTERFACE: GPUCommandEncoder +webidl.converters.GPUCommandEncoder = webidl.createInterfaceConverter( + "GPUCommandEncoder", + GPUCommandEncoder.prototype, +); + +// DICTIONARY: GPUCommandEncoderDescriptor +const dictMembersGPUCommandEncoderDescriptor = []; +webidl.converters["GPUCommandEncoderDescriptor"] = webidl + .createDictionaryConverter( + "GPUCommandEncoderDescriptor", + dictMembersGPUObjectDescriptorBase, + dictMembersGPUCommandEncoderDescriptor, + ); + +// DICTIONARY: GPUImageDataLayout +const dictMembersGPUImageDataLayout = [ + { + key: "offset", + converter: webidl.converters["GPUSize64"], + defaultValue: 0, + }, + { key: "bytesPerRow", converter: webidl.converters["GPUSize32"] }, + { key: "rowsPerImage", converter: webidl.converters["GPUSize32"] }, +]; +webidl.converters["GPUImageDataLayout"] = webidl.createDictionaryConverter( + "GPUImageDataLayout", + dictMembersGPUImageDataLayout, +); + +// DICTIONARY: GPUImageCopyBuffer +const dictMembersGPUImageCopyBuffer = [ + { + key: "buffer", + converter: webidl.converters["GPUBuffer"], + required: true, + }, +]; +webidl.converters["GPUImageCopyBuffer"] = webidl.createDictionaryConverter( + "GPUImageCopyBuffer", + dictMembersGPUImageDataLayout, + dictMembersGPUImageCopyBuffer, +); + +// DICTIONARY: GPUOrigin3DDict +const dictMembersGPUOrigin3DDict = [ + { + key: "x", + converter: webidl.converters["GPUIntegerCoordinate"], + defaultValue: 0, + }, + { + key: "y", + converter: webidl.converters["GPUIntegerCoordinate"], + defaultValue: 0, + }, + { + key: "z", + converter: webidl.converters["GPUIntegerCoordinate"], + defaultValue: 0, + }, +]; +webidl.converters["GPUOrigin3DDict"] = webidl.createDictionaryConverter( + "GPUOrigin3DDict", + dictMembersGPUOrigin3DDict, +); + +// TYPEDEF: GPUOrigin3D +webidl.converters["GPUOrigin3D"] = (V, opts) => { + // Union for (sequence or GPUOrigin3DDict) + if (V === null || V === undefined) { + return webidl.converters["GPUOrigin3DDict"](V, opts); + } + if (typeof V === "object") { + const method = V[SymbolIterator]; + if (method !== undefined) { + return webidl.converters["sequence"](V, opts); + } + return webidl.converters["GPUOrigin3DDict"](V, opts); + } + throw webidl.makeException( + TypeError, + "can not be converted to sequence or GPUOrigin3DDict.", + opts, + ); +}; + +// DICTIONARY: GPUImageCopyTexture +const dictMembersGPUImageCopyTexture = [ + { + key: "texture", + converter: webidl.converters["GPUTexture"], + required: true, + }, + { + key: "mipLevel", + converter: webidl.converters["GPUIntegerCoordinate"], + defaultValue: 0, + }, + { + key: "origin", + converter: webidl.converters["GPUOrigin3D"], + get defaultValue() { + return {}; + }, + }, + { + key: "aspect", + converter: webidl.converters["GPUTextureAspect"], + defaultValue: "all", + }, +]; +webidl.converters["GPUImageCopyTexture"] = webidl.createDictionaryConverter( + "GPUImageCopyTexture", + dictMembersGPUImageCopyTexture, +); + +// DICTIONARY: GPUOrigin2DDict +const dictMembersGPUOrigin2DDict = [ + { + key: "x", + converter: webidl.converters["GPUIntegerCoordinate"], + defaultValue: 0, + }, + { + key: "y", + converter: webidl.converters["GPUIntegerCoordinate"], + defaultValue: 0, + }, +]; +webidl.converters["GPUOrigin2DDict"] = webidl.createDictionaryConverter( + "GPUOrigin2DDict", + dictMembersGPUOrigin2DDict, +); + +// TYPEDEF: GPUOrigin2D +webidl.converters["GPUOrigin2D"] = (V, opts) => { + // Union for (sequence or GPUOrigin2DDict) + if (V === null || V === undefined) { + return webidl.converters["GPUOrigin2DDict"](V, opts); + } + if (typeof V === "object") { + const method = V[SymbolIterator]; + if (method !== undefined) { + return webidl.converters["sequence"](V, opts); + } + return webidl.converters["GPUOrigin2DDict"](V, opts); + } + throw webidl.makeException( + TypeError, + "can not be converted to sequence or GPUOrigin2DDict.", + opts, + ); +}; + +// INTERFACE: GPUComputePassEncoder +webidl.converters.GPUComputePassEncoder = webidl.createInterfaceConverter( + "GPUComputePassEncoder", + GPUComputePassEncoder.prototype, +); + +// DICTIONARY: GPUComputePassDescriptor +const dictMembersGPUComputePassDescriptor = []; +webidl.converters["GPUComputePassDescriptor"] = webidl + .createDictionaryConverter( + "GPUComputePassDescriptor", + dictMembersGPUObjectDescriptorBase, + dictMembersGPUComputePassDescriptor, + ); + +// INTERFACE: GPURenderPassEncoder +webidl.converters.GPURenderPassEncoder = webidl.createInterfaceConverter( + "GPURenderPassEncoder", + GPURenderPassEncoder.prototype, +); + +// ENUM: GPULoadOp +webidl.converters["GPULoadOp"] = webidl.createEnumConverter("GPULoadOp", [ + "load", + "clear", +]); + +// DICTIONARY: GPUColorDict +const dictMembersGPUColorDict = [ + { key: "r", converter: webidl.converters["double"], required: true }, + { key: "g", converter: webidl.converters["double"], required: true }, + { key: "b", converter: webidl.converters["double"], required: true }, + { key: "a", converter: webidl.converters["double"], required: true }, +]; +webidl.converters["GPUColorDict"] = webidl.createDictionaryConverter( + "GPUColorDict", + dictMembersGPUColorDict, +); + +// TYPEDEF: GPUColor +webidl.converters["GPUColor"] = (V, opts) => { + // Union for (sequence or GPUColorDict) + if (V === null || V === undefined) { + return webidl.converters["GPUColorDict"](V, opts); + } + if (typeof V === "object") { + const method = V[SymbolIterator]; + if (method !== undefined) { + return webidl.converters["sequence"](V, opts); + } + return webidl.converters["GPUColorDict"](V, opts); + } + throw webidl.makeException( + TypeError, + "can not be converted to sequence or GPUColorDict.", + opts, + ); +}; + +// ENUM: GPUStoreOp +webidl.converters["GPUStoreOp"] = webidl.createEnumConverter("GPUStoreOp", [ + "store", + "discard", +]); + +// DICTIONARY: GPURenderPassColorAttachment +const dictMembersGPURenderPassColorAttachment = [ + { + key: "view", + converter: webidl.converters["GPUTextureView"], + required: true, + }, + { key: "resolveTarget", converter: webidl.converters["GPUTextureView"] }, + { + key: "clearValue", + converter: webidl.converters["GPUColor"], + }, + { + key: "loadOp", + converter: webidl.converters["GPULoadOp"], + required: true, + }, + { + key: "storeOp", + converter: webidl.converters["GPUStoreOp"], + required: true, + }, +]; +webidl.converters["GPURenderPassColorAttachment"] = webidl + .createDictionaryConverter( + "GPURenderPassColorAttachment", + dictMembersGPURenderPassColorAttachment, + ); + +// DICTIONARY: GPURenderPassDepthStencilAttachment +const dictMembersGPURenderPassDepthStencilAttachment = [ + { + key: "view", + converter: webidl.converters["GPUTextureView"], + required: true, + }, + { + key: "depthClearValue", + converter: webidl.converters["float"], + defaultValue: 0, + }, + { + key: "depthLoadOp", + converter: webidl.converters["GPULoadOp"], + }, + { + key: "depthStoreOp", + converter: webidl.converters["GPUStoreOp"], + }, + { + key: "depthReadOnly", + converter: webidl.converters["boolean"], + defaultValue: false, + }, + { + key: "stencilClearValue", + converter: webidl.converters["GPUStencilValue"], + defaultValue: 0, + }, + { + key: "stencilLoadOp", + converter: webidl.converters["GPULoadOp"], + }, + { + key: "stencilStoreOp", + converter: webidl.converters["GPUStoreOp"], + }, + { + key: "stencilReadOnly", + converter: webidl.converters["boolean"], + defaultValue: false, + }, +]; +webidl.converters["GPURenderPassDepthStencilAttachment"] = webidl + .createDictionaryConverter( + "GPURenderPassDepthStencilAttachment", + dictMembersGPURenderPassDepthStencilAttachment, + ); + +// INTERFACE: GPUQuerySet +webidl.converters.GPUQuerySet = webidl.createInterfaceConverter( + "GPUQuerySet", + GPUQuerySet.prototype, +); + +// DICTIONARY: GPURenderPassDescriptor +const dictMembersGPURenderPassDescriptor = [ + { + key: "colorAttachments", + converter: webidl.createSequenceConverter( + webidl.createNullableConverter( + webidl.converters["GPURenderPassColorAttachment"], + ), + ), + required: true, + }, + { + key: "depthStencilAttachment", + converter: webidl.converters["GPURenderPassDepthStencilAttachment"], + }, +]; +webidl.converters["GPURenderPassDescriptor"] = webidl + .createDictionaryConverter( + "GPURenderPassDescriptor", + dictMembersGPUObjectDescriptorBase, + dictMembersGPURenderPassDescriptor, + ); + +// INTERFACE: GPURenderBundle +webidl.converters.GPURenderBundle = webidl.createInterfaceConverter( + "GPURenderBundle", + GPURenderBundle.prototype, +); +webidl.converters["sequence"] = webidl + .createSequenceConverter(webidl.converters["GPURenderBundle"]); + +// DICTIONARY: GPURenderBundleDescriptor +const dictMembersGPURenderBundleDescriptor = []; +webidl.converters["GPURenderBundleDescriptor"] = webidl + .createDictionaryConverter( + "GPURenderBundleDescriptor", + dictMembersGPUObjectDescriptorBase, + dictMembersGPURenderBundleDescriptor, + ); + +// INTERFACE: GPURenderBundleEncoder +webidl.converters.GPURenderBundleEncoder = webidl.createInterfaceConverter( + "GPURenderBundleEncoder", + GPURenderBundleEncoder.prototype, +); + +// DICTIONARY: GPURenderPassLayout +const dictMembersGPURenderPassLayout = [ + { + key: "colorFormats", + converter: webidl.createSequenceConverter( + webidl.createNullableConverter(webidl.converters["GPUTextureFormat"]), + ), + required: true, + }, + { + key: "depthStencilFormat", + converter: webidl.converters["GPUTextureFormat"], + }, + { + key: "sampleCount", + converter: webidl.converters["GPUSize32"], + defaultValue: 1, + }, +]; +webidl.converters["GPURenderPassLayout"] = webidl + .createDictionaryConverter( + "GPURenderPassLayout", + dictMembersGPUObjectDescriptorBase, + dictMembersGPURenderPassLayout, + ); + +// DICTIONARY: GPURenderBundleEncoderDescriptor +const dictMembersGPURenderBundleEncoderDescriptor = [ + { + key: "depthReadOnly", + converter: webidl.converters.boolean, + defaultValue: false, + }, + { + key: "stencilReadOnly", + converter: webidl.converters.boolean, + defaultValue: false, + }, +]; +webidl.converters["GPURenderBundleEncoderDescriptor"] = webidl + .createDictionaryConverter( + "GPURenderBundleEncoderDescriptor", + dictMembersGPUObjectDescriptorBase, + dictMembersGPURenderPassLayout, + dictMembersGPURenderBundleEncoderDescriptor, + ); + +// INTERFACE: GPUQueue +webidl.converters.GPUQueue = webidl.createInterfaceConverter( + "GPUQueue", + GPUQueue.prototype, +); + +// ENUM: GPUQueryType +webidl.converters["GPUQueryType"] = webidl.createEnumConverter( + "GPUQueryType", + [ + "occlusion", + "pipeline-statistics", + "timestamp", + ], +); + +// ENUM: GPUPipelineStatisticName +webidl.converters["GPUPipelineStatisticName"] = webidl.createEnumConverter( + "GPUPipelineStatisticName", + [ + "vertex-shader-invocations", + "clipper-invocations", + "clipper-primitives-out", + "fragment-shader-invocations", + "compute-shader-invocations", + ], +); + +// DICTIONARY: GPUQuerySetDescriptor +const dictMembersGPUQuerySetDescriptor = [ + { + key: "type", + converter: webidl.converters["GPUQueryType"], + required: true, + }, + { key: "count", converter: webidl.converters["GPUSize32"], required: true }, + { + key: "pipelineStatistics", + converter: webidl.createSequenceConverter( + webidl.converters["GPUPipelineStatisticName"], + ), + get defaultValue() { + return []; + }, + }, +]; +webidl.converters["GPUQuerySetDescriptor"] = webidl.createDictionaryConverter( + "GPUQuerySetDescriptor", + dictMembersGPUObjectDescriptorBase, + dictMembersGPUQuerySetDescriptor, +); + +// ENUM: GPUDeviceLostReason +webidl.converters["GPUDeviceLostReason"] = webidl.createEnumConverter( + "GPUDeviceLostReason", + [ + "destroyed", + ], +); + +// // INTERFACE: GPUDeviceLostInfo +// webidl.converters.GPUDeviceLostInfo = webidl.createInterfaceConverter( +// "GPUDeviceLostInfo", +// GPUDeviceLostInfo.prototype, +// ); + +// ENUM: GPUErrorFilter +webidl.converters["GPUErrorFilter"] = webidl.createEnumConverter( + "GPUErrorFilter", + [ + "out-of-memory", + "validation", + ], +); + +// INTERFACE: GPUOutOfMemoryError +webidl.converters.GPUOutOfMemoryError = webidl.createInterfaceConverter( + "GPUOutOfMemoryError", + GPUOutOfMemoryError.prototype, +); + +// INTERFACE: GPUValidationError +webidl.converters.GPUValidationError = webidl.createInterfaceConverter( + "GPUValidationError", + GPUValidationError.prototype, +); + +// TYPEDEF: GPUError +webidl.converters["GPUError"] = webidl.converters.any /** put union here! **/; + +// // INTERFACE: GPUUncapturedErrorEvent +// webidl.converters.GPUUncapturedErrorEvent = webidl.createInterfaceConverter( +// "GPUUncapturedErrorEvent", +// GPUUncapturedErrorEvent.prototype, +// ); + +// DICTIONARY: GPUUncapturedErrorEventInit +const dictMembersGPUUncapturedErrorEventInit = [ + { key: "error", converter: webidl.converters["GPUError"], required: true }, +]; +webidl.converters["GPUUncapturedErrorEventInit"] = webidl + .createDictionaryConverter( + "GPUUncapturedErrorEventInit", + // dictMembersEventInit, + dictMembersGPUUncapturedErrorEventInit, + ); + +// TYPEDEF: GPUBufferDynamicOffset +webidl.converters["GPUBufferDynamicOffset"] = (V, opts) => + webidl.converters["unsigned long"](V, { ...opts, enforceRange: true }); + +// TYPEDEF: GPUSignedOffset32 +webidl.converters["GPUSignedOffset32"] = (V, opts) => + webidl.converters["long"](V, { ...opts, enforceRange: true }); + +// TYPEDEF: GPUFlagsConstant +webidl.converters["GPUFlagsConstant"] = webidl.converters["unsigned long"]; + +// ENUM: GPUSurfacePresentMode +webidl.converters["GPUSurfacePresentMode"] = webidl.createEnumConverter( + "GPUSurfacePresentMode", + [ + "auto-vsync", + "auto-no-vsync", + "fifo", + "fifo-relaxed", + "immediate", + "mailbox", + ], +); + +// ENUM: GPUSurfaceAlphaMode +webidl.converters["GPUSurfaceAlphaMode"] = webidl.createEnumConverter( + "GPUSurfaceAlphaMode", + [ + "auto", + "opaque", + "pre-multiplied", + "post-multiplied", + "inherit", + ], +); + +// DICTIONARY: GPUSurfaceConfiguration +const dictMembersGPUSurfaceConfiguration = [ + { + key: "usage", + converter: webidl.converters["GPUTextureUsageFlags"], + defaultValue: GPUTextureUsage.RENDER_ATTACHMENT, + }, + { + key: "format", + converter: webidl.converters["GPUTextureFormat"], + required: true, + }, + { + key: "size", + converter: webidl.converters["GPUExtent3D"], + required: true, + }, + { + key: "presentMode", + converter: webidl.converters["GPUSurfacePresentMode"], + defaultValue: "fifo", + }, + { + key: "alphaMode", + converter: webidl.converters["GPUSurfaceAlphaMode"], + defaultValue: "opaque", + }, + { + key: "viewFormats", + converter: webidl.createSequenceConverter( + webidl.converters["GPUTextureFormat"], + ), + get defaultValue() { + return []; + }, + }, +]; +webidl.converters["GPUSurfaceConfiguration"] = webidl + .createDictionaryConverter( + "GPUSurfaceConfiguration", + dictMembersGPUSurfaceConfiguration, + ); diff --git a/ext/webgpu/Cargo.toml b/ext/webgpu/Cargo.toml index 182733a24e6a36..4e55f6edc892ae 100644 --- a/ext/webgpu/Cargo.toml +++ b/ext/webgpu/Cargo.toml @@ -3,7 +3,7 @@ [package] name = "denog_webgpu" -version = "0.6.0" +version = "0.7.0" authors = ["the Deno authors", "Jo Bates"] edition.workspace = true license = "MIT" @@ -11,6 +11,9 @@ readme = "README.md" repository.workspace = true description = "WebGPU implementation for Denog" +[lib] +path = "lib.rs" + [dependencies] deno_core.workspace = true raw-window-handle.workspace = true diff --git a/ext/webgpu/src/binding.rs b/ext/webgpu/binding.rs similarity index 100% rename from ext/webgpu/src/binding.rs rename to ext/webgpu/binding.rs diff --git a/ext/webgpu/src/buffer.rs b/ext/webgpu/buffer.rs similarity index 100% rename from ext/webgpu/src/buffer.rs rename to ext/webgpu/buffer.rs diff --git a/ext/webgpu/src/bundle.rs b/ext/webgpu/bundle.rs similarity index 98% rename from ext/webgpu/src/bundle.rs rename to ext/webgpu/bundle.rs index 3d0f11d89634fb..65897d80b207d0 100644 --- a/ext/webgpu/src/bundle.rs +++ b/ext/webgpu/bundle.rs @@ -289,7 +289,7 @@ pub fn op_webgpu_render_bundle_encoder_set_vertex_buffer( slot: u32, buffer: ResourceId, offset: u64, - size: u64, + size: Option, ) -> Result { let buffer_resource = state .resource_table @@ -298,10 +298,14 @@ pub fn op_webgpu_render_bundle_encoder_set_vertex_buffer( state .resource_table .get::(render_bundle_encoder_rid)?; - let size = Some( - std::num::NonZeroU64::new(size) - .ok_or_else(|| type_error("size must be larger than 0"))?, - ); + let size = if let Some(size) = size { + Some( + std::num::NonZeroU64::new(size) + .ok_or_else(|| type_error("size must be larger than 0"))?, + ) + } else { + None + }; wgpu_core::command::bundle_ffi::wgpu_render_bundle_set_vertex_buffer( &mut render_bundle_encoder_resource.0.borrow_mut(), diff --git a/ext/webgpu/src/command_encoder.rs b/ext/webgpu/command_encoder.rs similarity index 100% rename from ext/webgpu/src/command_encoder.rs rename to ext/webgpu/command_encoder.rs diff --git a/ext/webgpu/src/compute_pass.rs b/ext/webgpu/compute_pass.rs similarity index 100% rename from ext/webgpu/src/compute_pass.rs rename to ext/webgpu/compute_pass.rs diff --git a/ext/webgpu/src/error.rs b/ext/webgpu/error.rs similarity index 100% rename from ext/webgpu/src/error.rs rename to ext/webgpu/error.rs diff --git a/ext/webgpu/src/lib.rs b/ext/webgpu/lib.rs similarity index 99% rename from ext/webgpu/src/lib.rs rename to ext/webgpu/lib.rs index 20154807bbddb9..4d0a947b43e6cd 100644 --- a/ext/webgpu/src/lib.rs +++ b/ext/webgpu/lib.rs @@ -141,11 +141,7 @@ impl Resource for WebGpuQuerySet { pub fn init(unstable: bool) -> Extension { Extension::builder("deno_webgpu") .dependencies(vec!["deno_webidl", "deno_web"]) - .js(include_js_files!( - prefix "internal:ext/webgpu", - "01_webgpu.js", - "02_idl_types.js", - )) + .esm(include_js_files!("01_webgpu.js", "02_idl_types.js",)) .ops(declare_webgpu_ops()) .state(move |state| { // TODO: check & possibly streamline this diff --git a/ext/webgpu/src/pipeline.rs b/ext/webgpu/pipeline.rs similarity index 100% rename from ext/webgpu/src/pipeline.rs rename to ext/webgpu/pipeline.rs diff --git a/ext/webgpu/src/queue.rs b/ext/webgpu/queue.rs similarity index 100% rename from ext/webgpu/src/queue.rs rename to ext/webgpu/queue.rs diff --git a/ext/webgpu/src/render_pass.rs b/ext/webgpu/render_pass.rs similarity index 100% rename from ext/webgpu/src/render_pass.rs rename to ext/webgpu/render_pass.rs diff --git a/ext/webgpu/src/sampler.rs b/ext/webgpu/sampler.rs similarity index 100% rename from ext/webgpu/src/sampler.rs rename to ext/webgpu/sampler.rs diff --git a/ext/webgpu/src/shader.rs b/ext/webgpu/shader.rs similarity index 100% rename from ext/webgpu/src/shader.rs rename to ext/webgpu/shader.rs diff --git a/ext/webgpu/src/01_webgpu.js b/ext/webgpu/src/01_webgpu.js deleted file mode 100644 index 7a87ffc245d252..00000000000000 --- a/ext/webgpu/src/01_webgpu.js +++ /dev/null @@ -1,5481 +0,0 @@ -// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. -// Copyright 2023 Jo Bates. All rights reserved. MIT license. - -// @ts-check -/// -/// -/// -/// - -"use strict"; - -((window) => { - const core = window.Deno.core; - const ops = core.ops; - const webidl = window.__bootstrap.webidl; - const eventTarget = window.__bootstrap.eventTarget; - const { DOMException } = window.__bootstrap.domException; - const { - ArrayBuffer, - ArrayBufferIsView, - ArrayIsArray, - ArrayPrototypeFilter, - ArrayPrototypeMap, - ArrayPrototypePop, - ArrayPrototypePush, - Error, - MathMax, - ObjectDefineProperty, - ObjectPrototypeIsPrototypeOf, - Promise, - PromisePrototypeCatch, - PromisePrototypeThen, - PromiseReject, - PromiseResolve, - SafeArrayIterator, - SafePromiseAll, - Set, - SetPrototypeHas, - Symbol, - SymbolFor, - TypeError, - Uint32Array, - Uint32ArrayPrototype, - Uint8Array, - WeakRef, - } = window.__bootstrap.primordials; - - const _rid = Symbol("[[rid]]"); - const _size = Symbol("[[size]]"); - const _usage = Symbol("[[usage]]"); - const _state = Symbol("[[state]]"); - const _mappingRange = Symbol("[[mapping_range]]"); - const _mappedRanges = Symbol("[[mapped_ranges]]"); - const _mapMode = Symbol("[[map_mode]]"); - const _adapter = Symbol("[[adapter]]"); - const _cleanup = Symbol("[[cleanup]]"); - const _vendor = Symbol("[[vendor]]"); - const _architecture = Symbol("[[architecture]]"); - const _description = Symbol("[[description]]"); - const _limits = Symbol("[[limits]]"); - const _reason = Symbol("[[reason]]"); - const _message = Symbol("[[message]]"); - const _label = Symbol("[[label]]"); - const _device = Symbol("[[device]]"); - const _queue = Symbol("[[queue]]"); - const _views = Symbol("[[views]]"); - const _texture = Symbol("[[texture]]"); - const _encoders = Symbol("[[encoders]]"); - const _encoder = Symbol("[[encoder]]"); - const _descriptor = Symbol("[[descriptor]]"); - const _width = Symbol("[[width]]"); - const _height = Symbol("[[height]]"); - const _depthOrArrayLayers = Symbol("[[depthOrArrayLayers]]"); - const _mipLevelCount = Symbol("[[mipLevelCount]]"); - const _sampleCount = Symbol("[[sampleCount]]"); - const _dimension = Symbol("[[dimension]]"); - const _format = Symbol("[[format]]"); - const _type = Symbol("[[type]]"); - const _count = Symbol("[[count]]"); - const _configuration = Symbol("[[configuration]]"); - const _currentTexture = Symbol("[[currentTexture]]"); - const _surface = Symbol("[[surface]]"); - const _isSuboptimal = Symbol("[[isSuboptimal]]"); - - /** - * @param {any} self - * @param {{prefix: string, context: string}} opts - * @returns {InnerGPUDevice & {rid: number}} - */ - function assertDevice(self, { prefix, context }) { - const device = self[_device]; - const deviceRid = device?.rid; - if (deviceRid === undefined) { - throw new DOMException( - `${prefix}: ${context} references an invalid or destroyed device.`, - "OperationError", - ); - } - return device; - } - - /** - * @param {InnerGPUDevice} self - * @param {any} resource - * @param {{prefix: string, resourceContext: string, selfContext: string}} opts - * @returns {InnerGPUDevice & {rid: number}} - */ - function assertDeviceMatch( - self, - resource, - { prefix, resourceContext, selfContext }, - ) { - const resourceDevice = assertDevice(resource, { - prefix, - context: resourceContext, - }); - if (resourceDevice.rid !== self.rid) { - throw new DOMException( - `${prefix}: ${resourceContext} belongs to a diffent device than ${selfContext}.`, - "OperationError", - ); - } - return { ...resourceDevice, rid: resourceDevice.rid }; - } - - /** - * @param {any} self - * @param {{prefix: string, context: string}} opts - * @returns {number} - */ - function assertResource(self, { prefix, context }) { - const rid = self[_rid]; - if (rid === undefined) { - throw new DOMException( - `${prefix}: ${context} an invalid or destroyed resource.`, - "OperationError", - ); - } - return rid; - } - - /** - * @param {number[] | GPUExtent3DDict} data - * @returns {GPUExtent3DDict} - */ - function normalizeGPUExtent3D(data) { - if (ArrayIsArray(data)) { - return { - width: data[0], - height: data[1], - depthOrArrayLayers: data[2], - }; - } else { - return data; - } - } - - /** - * @param {number[] | GPUOrigin3DDict} data - * @returns {GPUOrigin3DDict} - */ - function normalizeGPUOrigin3D(data) { - if (ArrayIsArray(data)) { - return { - x: data[0], - y: data[1], - z: data[2], - }; - } else { - return data; - } - } - - /** - * @param {number[] | GPUColor} data - * @returns {GPUColor} - */ - function normalizeGPUColor(data) { - if (ArrayIsArray(data)) { - return { - r: data[0], - g: data[1], - b: data[2], - a: data[3], - }; - } else { - return data; - } - } - - const illegalConstructorKey = Symbol("illegalConstructorKey"); - class GPUError extends Error { - constructor(key = null) { - super(); - if (key !== illegalConstructorKey) { - webidl.illegalConstructor(); - } - } - - [_message]; - get message() { - webidl.assertBranded(this, GPUErrorPrototype); - return this[_message]; - } - } - const GPUErrorPrototype = GPUError.prototype; - - class GPUValidationError extends GPUError { - name = "GPUValidationError"; - /** @param {string} message */ - constructor(message) { - const prefix = "Failed to construct 'GPUValidationError'"; - webidl.requiredArguments(arguments.length, 1, { prefix }); - message = webidl.converters.DOMString(message, { - prefix, - context: "Argument 1", - }); - super(illegalConstructorKey); - this[webidl.brand] = webidl.brand; - this[_message] = message; - } - } - const GPUValidationErrorPrototype = GPUValidationError.prototype; - - class GPUOutOfMemoryError extends GPUError { - name = "GPUOutOfMemoryError"; - constructor(message) { - const prefix = "Failed to construct 'GPUOutOfMemoryError'"; - webidl.requiredArguments(arguments.length, 1, { prefix }); - message = webidl.converters.DOMString(message, { - prefix, - context: "Argument 1", - }); - super(illegalConstructorKey); - this[webidl.brand] = webidl.brand; - this[_message] = message; - } - } - const GPUOutOfMemoryErrorPrototype = GPUOutOfMemoryError.prototype; - - class GPU { - [webidl.brand] = webidl.brand; - - constructor() { - webidl.illegalConstructor(); - } - - /** - * @param {GPURequestAdapterOptions} options - */ - async requestAdapter(options = {}) { - webidl.assertBranded(this, GPUPrototype); - const prefix = "Failed to execute 'requestAdapter' on 'GPU'"; - options = webidl.converters.GPURequestAdapterOptions(options, { - prefix, - context: "Argument 1", - }); - - let compatibleSurfaceRid = null; - if (options.compatibleSurface) { - compatibleSurfaceRid = assertResource(options.compatibleSurface, { - prefix, - context: "compatible surface", - }); - } - - const { err, ...data } = await core.opAsync( - "op_webgpu_request_adapter", - options.powerPreference, - options.forceFallbackAdapter, - compatibleSurfaceRid, - ); - - if (err) { - return null; - } else { - return createGPUAdapter(data); - } - } - - [SymbolFor("Deno.privateCustomInspect")](inspect) { - return `${this.constructor.name} ${inspect({})}`; - } - } - const GPUPrototype = GPU.prototype; - - /** - * @typedef InnerGPUAdapter - * @property {number} rid - * @property {GPUSupportedFeatures} features - * @property {GPUSupportedLimits} limits - * @property {boolean} isFallbackAdapter - */ - - /** - * @param {InnerGPUAdapter} inner - * @returns {GPUAdapter} - */ - function createGPUAdapter(inner) { - /** @type {GPUAdapter} */ - const adapter = webidl.createBranded(GPUAdapter); - adapter[_adapter] = { - ...inner, - features: createGPUSupportedFeatures(inner.features), - limits: createGPUSupportedLimits(inner.limits), - }; - return adapter; - } - - class GPUAdapter { - /** @type {InnerGPUAdapter} */ - [_adapter]; - - /** @returns {GPUSupportedFeatures} */ - get features() { - webidl.assertBranded(this, GPUAdapterPrototype); - return this[_adapter].features; - } - /** @returns {GPUSupportedLimits} */ - get limits() { - webidl.assertBranded(this, GPUAdapterPrototype); - return this[_adapter].limits; - } - /** @returns {boolean} */ - get isFallbackAdapter() { - return this[_adapter].isFallbackAdapter; - } - - constructor() { - webidl.illegalConstructor(); - } - - /** - * @param {GPUDeviceDescriptor} descriptor - * @returns {Promise} - */ - async requestDevice(descriptor = {}) { - webidl.assertBranded(this, GPUAdapterPrototype); - const prefix = "Failed to execute 'requestDevice' on 'GPUAdapter'"; - descriptor = webidl.converters.GPUDeviceDescriptor(descriptor, { - prefix, - context: "Argument 1", - }); - const requiredFeatures = descriptor.requiredFeatures ?? []; - for (let i = 0; i < requiredFeatures.length; ++i) { - const feature = requiredFeatures[i]; - if ( - !SetPrototypeHas( - this[_adapter].features[webidl.setlikeInner], - feature, - ) - ) { - throw new TypeError( - `${prefix}: requiredFeatures must be a subset of the adapter features.`, - ); - } - } - - const { rid, features, limits } = await core.opAsync( - "op_webgpu_request_device", - this[_adapter].rid, - descriptor.label, - requiredFeatures, - descriptor.requiredLimits, - ); - - const inner = new InnerGPUDevice({ - rid, - adapter: this, - features: createGPUSupportedFeatures(features), - limits: createGPUSupportedLimits(limits), - }); - return createGPUDevice( - descriptor.label, - inner, - createGPUQueue(descriptor.label, inner), - ); - } - - /** - * @param {string[]} unmaskHints - * @returns {Promise} - */ - async requestAdapterInfo(unmaskHints = []) { - webidl.assertBranded(this, GPUAdapterPrototype); - const prefix = "Failed to execute 'requestAdapterInfo' on 'GPUAdapter'"; - unmaskHints = webidl.converters["sequence"](unmaskHints, { - prefix, - context: "Argument 1", - }); - - const { - vendor, - architecture, - device, - description, - } = await core.opAsync( - "op_webgpu_request_adapter_info", - this[_adapter].rid, - ); - - const adapterInfo = webidl.createBranded(GPUAdapterInfo); - adapterInfo[_vendor] = unmaskHints.includes("vendor") ? vendor : ""; - adapterInfo[_architecture] = unmaskHints.includes("architecture") - ? architecture - : ""; - adapterInfo[_device] = unmaskHints.includes("device") ? device : ""; - adapterInfo[_description] = unmaskHints.includes("description") - ? description - : ""; - return adapterInfo; - } - - [SymbolFor("Deno.privateCustomInspect")](inspect) { - return `${this.constructor.name} ${ - inspect({ - features: this.features, - limits: this.limits, - }) - }`; - } - } - const GPUAdapterPrototype = GPUAdapter.prototype; - - class GPUAdapterInfo { - /** @type {string} */ - [_vendor]; - /** @returns {string} */ - get vendor() { - webidl.assertBranded(this, GPUAdapterInfoPrototype); - return this[_vendor]; - } - - /** @type {string} */ - [_architecture]; - /** @returns {string} */ - get architecture() { - webidl.assertBranded(this, GPUAdapterInfoPrototype); - return this[_architecture]; - } - - /** @type {string} */ - [_device]; - /** @returns {string} */ - get device() { - webidl.assertBranded(this, GPUAdapterInfoPrototype); - return this[_device]; - } - - /** @type {string} */ - [_description]; - /** @returns {string} */ - get description() { - webidl.assertBranded(this, GPUAdapterInfoPrototype); - return this[_description]; - } - - [SymbolFor("Deno.privateCustomInspect")](inspect) { - return `${this.constructor.name} ${ - inspect({ - vendor: this.vendor, - architecture: this.architecture, - device: this.device, - description: this.description, - }) - }`; - } - } - const GPUAdapterInfoPrototype = GPUAdapterInfo.prototype; - - function createGPUSupportedLimits(limits) { - /** @type {GPUSupportedLimits} */ - const adapterFeatures = webidl.createBranded(GPUSupportedLimits); - adapterFeatures[_limits] = limits; - return adapterFeatures; - } - - /** - * @typedef InnerAdapterLimits - * @property {number} maxTextureDimension1D - * @property {number} maxTextureDimension2D - * @property {number} maxTextureDimension3D - * @property {number} maxTextureArrayLayers - * @property {number} maxBindGroups - * @property {number} maxDynamicUniformBuffersPerPipelineLayout - * @property {number} maxDynamicStorageBuffersPerPipelineLayout - * @property {number} maxSampledTexturesPerShaderStage - * @property {number} maxSamplersPerShaderStage - * @property {number} maxStorageBuffersPerShaderStage - * @property {number} maxStorageTexturesPerShaderStage - * @property {number} maxUniformBuffersPerShaderStage - * @property {number} maxUniformBufferBindingSize - * @property {number} maxStorageBufferBindingSize - * @property {number} minUniformBufferOffsetAlignment - * @property {number} minStorageBufferOffsetAlignment - * @property {number} maxVertexBuffers - * @property {number} maxVertexAttributes - * @property {number} maxVertexBufferArrayStride - * @property {number} maxInterStageShaderComponents - * @property {number} maxComputeWorkgroupStorageSize - * @property {number} maxComputeInvocationsPerWorkgroup - * @property {number} maxComputeWorkgroupSizeX - * @property {number} maxComputeWorkgroupSizeY - * @property {number} maxComputeWorkgroupSizeZ - * @property {number} maxComputeWorkgroupsPerDimension - */ - - class GPUSupportedLimits { - /** @type {InnerAdapterLimits} */ - [_limits]; - constructor() { - webidl.illegalConstructor(); - } - - get maxTextureDimension1D() { - webidl.assertBranded(this, GPUSupportedLimitsPrototype); - return this[_limits].maxTextureDimension1D; - } - get maxTextureDimension2D() { - webidl.assertBranded(this, GPUSupportedLimitsPrototype); - return this[_limits].maxTextureDimension2D; - } - get maxTextureDimension3D() { - webidl.assertBranded(this, GPUSupportedLimitsPrototype); - return this[_limits].maxTextureDimension3D; - } - get maxTextureArrayLayers() { - webidl.assertBranded(this, GPUSupportedLimitsPrototype); - return this[_limits].maxTextureArrayLayers; - } - get maxBindGroups() { - webidl.assertBranded(this, GPUSupportedLimitsPrototype); - return this[_limits].maxBindGroups; - } - get maxBindingsPerBindGroup() { - webidl.assertBranded(this, GPUSupportedLimitsPrototype); - return this[_limits].maxBindingsPerBindGroup; - } - get maxBufferSize() { - webidl.assertBranded(this, GPUSupportedLimitsPrototype); - return this[_limits].maxBufferSize; - } - get maxDynamicUniformBuffersPerPipelineLayout() { - webidl.assertBranded(this, GPUSupportedLimitsPrototype); - return this[_limits].maxDynamicUniformBuffersPerPipelineLayout; - } - get maxDynamicStorageBuffersPerPipelineLayout() { - webidl.assertBranded(this, GPUSupportedLimitsPrototype); - return this[_limits].maxDynamicStorageBuffersPerPipelineLayout; - } - get maxSampledTexturesPerShaderStage() { - webidl.assertBranded(this, GPUSupportedLimitsPrototype); - return this[_limits].maxSampledTexturesPerShaderStage; - } - get maxSamplersPerShaderStage() { - webidl.assertBranded(this, GPUSupportedLimitsPrototype); - return this[_limits].maxSamplersPerShaderStage; - } - get maxStorageBuffersPerShaderStage() { - webidl.assertBranded(this, GPUSupportedLimitsPrototype); - return this[_limits].maxStorageBuffersPerShaderStage; - } - get maxStorageTexturesPerShaderStage() { - webidl.assertBranded(this, GPUSupportedLimitsPrototype); - return this[_limits].maxStorageTexturesPerShaderStage; - } - get maxUniformBuffersPerShaderStage() { - webidl.assertBranded(this, GPUSupportedLimitsPrototype); - return this[_limits].maxUniformBuffersPerShaderStage; - } - get maxUniformBufferBindingSize() { - webidl.assertBranded(this, GPUSupportedLimitsPrototype); - return this[_limits].maxUniformBufferBindingSize; - } - get maxStorageBufferBindingSize() { - webidl.assertBranded(this, GPUSupportedLimitsPrototype); - return this[_limits].maxStorageBufferBindingSize; - } - get minUniformBufferOffsetAlignment() { - webidl.assertBranded(this, GPUSupportedLimitsPrototype); - return this[_limits].minUniformBufferOffsetAlignment; - } - get minStorageBufferOffsetAlignment() { - webidl.assertBranded(this, GPUSupportedLimitsPrototype); - return this[_limits].minStorageBufferOffsetAlignment; - } - get maxVertexBuffers() { - webidl.assertBranded(this, GPUSupportedLimitsPrototype); - return this[_limits].maxVertexBuffers; - } - get maxVertexAttributes() { - webidl.assertBranded(this, GPUSupportedLimitsPrototype); - return this[_limits].maxVertexAttributes; - } - get maxVertexBufferArrayStride() { - webidl.assertBranded(this, GPUSupportedLimitsPrototype); - return this[_limits].maxVertexBufferArrayStride; - } - get maxInterStageShaderComponents() { - webidl.assertBranded(this, GPUSupportedLimitsPrototype); - return this[_limits].maxInterStageShaderComponents; - } - get maxComputeWorkgroupStorageSize() { - webidl.assertBranded(this, GPUSupportedLimitsPrototype); - return this[_limits].maxComputeWorkgroupStorageSize; - } - get maxComputeInvocationsPerWorkgroup() { - webidl.assertBranded(this, GPUSupportedLimitsPrototype); - return this[_limits].maxComputeInvocationsPerWorkgroup; - } - get maxComputeWorkgroupSizeX() { - webidl.assertBranded(this, GPUSupportedLimitsPrototype); - return this[_limits].maxComputeWorkgroupSizeX; - } - get maxComputeWorkgroupSizeY() { - webidl.assertBranded(this, GPUSupportedLimitsPrototype); - return this[_limits].maxComputeWorkgroupSizeY; - } - get maxComputeWorkgroupSizeZ() { - webidl.assertBranded(this, GPUSupportedLimitsPrototype); - return this[_limits].maxComputeWorkgroupSizeZ; - } - get maxComputeWorkgroupsPerDimension() { - webidl.assertBranded(this, GPUSupportedLimitsPrototype); - return this[_limits].maxComputeWorkgroupsPerDimension; - } - - [SymbolFor("Deno.privateCustomInspect")](inspect) { - return `${this.constructor.name} ${inspect(this[_limits])}`; - } - } - const GPUSupportedLimitsPrototype = GPUSupportedLimits.prototype; - - function createGPUSupportedFeatures(features) { - /** @type {GPUSupportedFeatures} */ - const supportedFeatures = webidl.createBranded(GPUSupportedFeatures); - supportedFeatures[webidl.setlikeInner] = new Set(features); - return webidl.setlike( - supportedFeatures, - GPUSupportedFeaturesPrototype, - true, - ); - } - - class GPUSupportedFeatures { - constructor() { - webidl.illegalConstructor(); - } - [SymbolFor("Deno.privateCustomInspect")](inspect) { - return `${this.constructor.name} ${ - inspect([...new SafeArrayIterator(this.values())]) - }`; - } - } - - const GPUSupportedFeaturesPrototype = GPUSupportedFeatures.prototype; - - /** - * @param {string | undefined} reason - * @param {string} message - * @returns {GPUDeviceLostInfo} - */ - function createGPUDeviceLostInfo(reason, message) { - /** @type {GPUDeviceLostInfo} */ - const deviceLostInfo = webidl.createBranded(GPUDeviceLostInfo); - deviceLostInfo[_reason] = reason; - deviceLostInfo[_message] = message; - return deviceLostInfo; - } - - class GPUDeviceLostInfo { - /** @type {string | undefined} */ - [_reason]; - /** @type {string} */ - [_message]; - - constructor() { - webidl.illegalConstructor(); - } - - get reason() { - webidl.assertBranded(this, GPUDeviceLostInfoPrototype); - return this[_reason]; - } - get message() { - webidl.assertBranded(this, GPUDeviceLostInfoPrototype); - return this[_message]; - } - - [SymbolFor("Deno.privateCustomInspect")](inspect) { - return `${this.constructor.name} ${ - inspect({ reason: this[_reason], message: this[_message] }) - }`; - } - } - - const GPUDeviceLostInfoPrototype = GPUDeviceLostInfo.prototype; - - /** - * @param {string} name - * @param {any} type - */ - function GPUObjectBaseMixin(name, type) { - type.prototype[_label] = null; - ObjectDefineProperty(type.prototype, "label", { - /** - * @return {string | null} - */ - get() { - webidl.assertBranded(this, type.prototype); - return this[_label]; - }, - /** - * @param {string | null} label - */ - set(label) { - webidl.assertBranded(this, type.prototype); - label = webidl.converters["UVString?"](label, { - prefix: `Failed to set 'label' on '${name}'`, - context: "Argument 1", - }); - this[_label] = label; - }, - }); - } - - /** - * @typedef ErrorScope - * @property {string} filter - * @property {Promise[]} operations - */ - - /** - * @typedef InnerGPUDeviceOptions - * @property {GPUAdapter} adapter - * @property {number | undefined} rid - * @property {GPUSupportedFeatures} features - * @property {GPUSupportedLimits} limits - */ - - class InnerGPUDevice { - /** @type {GPUAdapter} */ - adapter; - /** @type {number | undefined} */ - rid; - /** @type {GPUSupportedFeatures} */ - features; - /** @type {GPUSupportedLimits} */ - limits; - /** @type {WeakRef[]} */ - resources; - /** @type {boolean} */ - isLost; - /** @type {Promise} */ - lost; - /** @type {(info: GPUDeviceLostInfo) => void} */ - resolveLost; - /** @type {ErrorScope[]} */ - errorScopeStack; - - /** - * @param {InnerGPUDeviceOptions} options - */ - constructor(options) { - this.adapter = options.adapter; - this.rid = options.rid; - this.features = options.features; - this.limits = options.limits; - this.resources = []; - this.isLost = false; - this.resolveLost = () => {}; - this.lost = new Promise((resolve) => { - this.resolveLost = resolve; - }); - this.errorScopeStack = []; - } - - /** @param {any} resource */ - trackResource(resource) { - ArrayPrototypePush(this.resources, new WeakRef(resource)); - } - - /** @param {{ type: string, value: string | null } | undefined} err */ - pushError(err) { - this.pushErrorPromise(PromiseResolve(err)); - } - - /** @param {Promise<{ type: string, value: string | null } | undefined>} promise */ - pushErrorPromise(promise) { - const operation = PromisePrototypeThen(promise, (err) => { - if (err) { - switch (err.type) { - case "lost": - this.isLost = true; - this.resolveLost( - createGPUDeviceLostInfo(undefined, "device was lost"), - ); - break; - case "validation": - return PromiseReject( - new GPUValidationError(err.value ?? "validation error"), - ); - case "out-of-memory": - return PromiseReject(new GPUOutOfMemoryError()); - } - } - }); - - const validationStack = ArrayPrototypeFilter( - this.errorScopeStack, - ({ filter }) => filter == "validation", - ); - const validationScope = validationStack[validationStack.length - 1]; - const validationFilteredPromise = PromisePrototypeCatch( - operation, - (err) => { - if (ObjectPrototypeIsPrototypeOf(GPUValidationErrorPrototype, err)) { - return PromiseReject(err); - } - return PromiseResolve(); - }, - ); - if (validationScope) { - ArrayPrototypePush( - validationScope.operations, - validationFilteredPromise, - ); - } else { - PromisePrototypeCatch(validationFilteredPromise, () => { - // TODO(lucacasonato): emit an UncapturedErrorEvent - }); - } - // prevent uncaptured promise rejections - PromisePrototypeCatch(validationFilteredPromise, (_err) => {}); - - const oomStack = ArrayPrototypeFilter( - this.errorScopeStack, - ({ filter }) => filter == "out-of-memory", - ); - const oomScope = oomStack[oomStack.length - 1]; - const oomFilteredPromise = PromisePrototypeCatch(operation, (err) => { - if (ObjectPrototypeIsPrototypeOf(GPUOutOfMemoryErrorPrototype, err)) { - return PromiseReject(err); - } - return PromiseResolve(); - }); - if (oomScope) { - ArrayPrototypePush(oomScope.operations, oomFilteredPromise); - } else { - PromisePrototypeCatch(oomFilteredPromise, () => { - // TODO(lucacasonato): emit an UncapturedErrorEvent - }); - } - // prevent uncaptured promise rejections - PromisePrototypeCatch(oomFilteredPromise, (_err) => {}); - } - } - - /** - * @param {string | null} label - * @param {InnerGPUDevice} inner - * @param {GPUQueue} queue - * @returns {GPUDevice} - */ - function createGPUDevice(label, inner, queue) { - /** @type {GPUDevice} */ - const device = webidl.createBranded(GPUDevice); - device[_label] = label; - device[_device] = inner; - device[_queue] = queue; - return device; - } - - class GPUDevice extends eventTarget.EventTarget { - /** @type {InnerGPUDevice} */ - [_device]; - - /** @type {GPUQueue} */ - [_queue]; - - [_cleanup]() { - const device = this[_device]; - const resources = device.resources; - while (resources.length > 0) { - const resource = ArrayPrototypePop(resources)?.deref(); - if (resource) { - resource[_cleanup](); - } - } - const rid = device.rid; - if (rid !== undefined) { - core.close(rid); - /** @type {number | undefined} */ - device.rid = undefined; - } - } - - get features() { - webidl.assertBranded(this, GPUDevicePrototype); - return this[_device].features; - } - get limits() { - webidl.assertBranded(this, GPUDevicePrototype); - return this[_device].limits; - } - get queue() { - webidl.assertBranded(this, GPUDevicePrototype); - return this[_queue]; - } - - constructor() { - webidl.illegalConstructor(); - super(); - } - - destroy() { - webidl.assertBranded(this, GPUDevicePrototype); - this[_cleanup](); - } - - /** - * @param {GPUBufferDescriptor} descriptor - * @returns {GPUBuffer} - */ - createBuffer(descriptor) { - webidl.assertBranded(this, GPUDevicePrototype); - const prefix = "Failed to execute 'createBuffer' on 'GPUDevice'"; - webidl.requiredArguments(arguments.length, 1, { prefix }); - descriptor = webidl.converters.GPUBufferDescriptor(descriptor, { - prefix, - context: "Argument 1", - }); - const device = assertDevice(this, { prefix, context: "this" }); - const { rid, err } = ops.op_webgpu_create_buffer( - device.rid, - descriptor.label, - descriptor.size, - descriptor.usage, - descriptor.mappedAtCreation, - ); - device.pushError(err); - /** @type {CreateGPUBufferOptions} */ - let options; - if (descriptor.mappedAtCreation) { - options = { - mapping: new ArrayBuffer(descriptor.size), - mappingRange: [0, descriptor.size], - mappedRanges: [], - state: "mapped at creation", - }; - } else { - options = { - mapping: null, - mappedRanges: null, - mappingRange: null, - state: "unmapped", - }; - } - const buffer = createGPUBuffer( - descriptor.label, - device, - rid, - descriptor.size, - descriptor.usage, - options, - ); - device.trackResource(buffer); - return buffer; - } - - /** - * @param {GPUTextureDescriptor} descriptor - * @returns {GPUTexture} - */ - createTexture(descriptor) { - webidl.assertBranded(this, GPUDevicePrototype); - const prefix = "Failed to execute 'createTexture' on 'GPUDevice'"; - webidl.requiredArguments(arguments.length, 1, { prefix }); - descriptor = webidl.converters.GPUTextureDescriptor(descriptor, { - prefix, - context: "Argument 1", - }); - const device = assertDevice(this, { prefix, context: "this" }); - const { rid, err } = ops.op_webgpu_create_texture({ - deviceRid: device.rid, - ...descriptor, - size: normalizeGPUExtent3D(descriptor.size), - }); - device.pushError(err); - - const texture = createGPUTexture( - descriptor, - device, - rid, - ); - device.trackResource(texture); - return texture; - } - - /** - * @param {GPUSamplerDescriptor} descriptor - * @returns {GPUSampler} - */ - createSampler(descriptor = {}) { - webidl.assertBranded(this, GPUDevicePrototype); - const prefix = "Failed to execute 'createSampler' on 'GPUDevice'"; - descriptor = webidl.converters.GPUSamplerDescriptor(descriptor, { - prefix, - context: "Argument 1", - }); - const device = assertDevice(this, { prefix, context: "this" }); - const { rid, err } = ops.op_webgpu_create_texture({ - deviceRid: device.rid, - ...descriptor, - }); - device.pushError(err); - - const sampler = createGPUSampler( - descriptor.label, - device, - rid, - ); - device.trackResource(sampler); - return sampler; - } - - /** - * @param {GPUBindGroupLayoutDescriptor} descriptor - * @returns {GPUBindGroupLayout} - */ - createBindGroupLayout(descriptor) { - webidl.assertBranded(this, GPUDevicePrototype); - const prefix = "Failed to execute 'createBindGroupLayout' on 'GPUDevice'"; - webidl.requiredArguments(arguments.length, 1, { prefix }); - descriptor = webidl.converters.GPUBindGroupLayoutDescriptor(descriptor, { - prefix, - context: "Argument 1", - }); - const device = assertDevice(this, { prefix, context: "this" }); - for (let i = 0; i < descriptor.entries.length; ++i) { - const entry = descriptor.entries[i]; - - let j = 0; - if (entry.buffer) j++; - if (entry.sampler) j++; - if (entry.texture) j++; - if (entry.storageTexture) j++; - - if (j !== 1) { - throw new Error(); // TODO(@crowlKats): correct error - } - } - - const { rid, err } = ops.op_webgpu_create_bind_group_layout( - device.rid, - descriptor.label, - descriptor.entries, - ); - device.pushError(err); - - const bindGroupLayout = createGPUBindGroupLayout( - descriptor.label, - device, - rid, - ); - device.trackResource(bindGroupLayout); - return bindGroupLayout; - } - - /** - * @param {GPUPipelineLayoutDescriptor} descriptor - * @returns {GPUPipelineLayout} - */ - createPipelineLayout(descriptor) { - webidl.assertBranded(this, GPUDevicePrototype); - const prefix = "Failed to execute 'createPipelineLayout' on 'GPUDevice'"; - webidl.requiredArguments(arguments.length, 1, { prefix }); - descriptor = webidl.converters.GPUPipelineLayoutDescriptor(descriptor, { - prefix, - context: "Argument 1", - }); - const device = assertDevice(this, { prefix, context: "this" }); - const bindGroupLayouts = ArrayPrototypeMap( - descriptor.bindGroupLayouts, - (layout, i) => { - const context = `bind group layout ${i + 1}`; - const rid = assertResource(layout, { prefix, context }); - assertDeviceMatch(device, layout, { - prefix, - selfContext: "this", - resourceContext: context, - }); - return rid; - }, - ); - const { rid, err } = ops.op_webgpu_create_pipeline_layout( - device.rid, - descriptor.label, - bindGroupLayouts, - ); - device.pushError(err); - - const pipelineLayout = createGPUPipelineLayout( - descriptor.label, - device, - rid, - ); - device.trackResource(pipelineLayout); - return pipelineLayout; - } - - /** - * @param {GPUBindGroupDescriptor} descriptor - * @returns {GPUBindGroup} - */ - createBindGroup(descriptor) { - webidl.assertBranded(this, GPUDevicePrototype); - const prefix = "Failed to execute 'createBindGroup' on 'GPUDevice'"; - webidl.requiredArguments(arguments.length, 1, { prefix }); - descriptor = webidl.converters.GPUBindGroupDescriptor(descriptor, { - prefix, - context: "Argument 1", - }); - const device = assertDevice(this, { prefix, context: "this" }); - const layout = assertResource(descriptor.layout, { - prefix, - context: "layout", - }); - assertDeviceMatch(device, descriptor.layout, { - prefix, - resourceContext: "layout", - selfContext: "this", - }); - const entries = ArrayPrototypeMap(descriptor.entries, (entry, i) => { - const context = `entry ${i + 1}`; - const resource = entry.resource; - if (ObjectPrototypeIsPrototypeOf(GPUSamplerPrototype, resource)) { - const rid = assertResource(resource, { - prefix, - context, - }); - assertDeviceMatch(device, resource, { - prefix, - resourceContext: context, - selfContext: "this", - }); - return { - binding: entry.binding, - kind: "GPUSampler", - resource: rid, - }; - } else if ( - ObjectPrototypeIsPrototypeOf(GPUTextureViewPrototype, resource) - ) { - const rid = assertResource(resource, { - prefix, - context, - }); - assertResource(resource[_texture], { - prefix, - context, - }); - assertDeviceMatch(device, resource[_texture], { - prefix, - resourceContext: context, - selfContext: "this", - }); - return { - binding: entry.binding, - kind: "GPUTextureView", - resource: rid, - }; - } else { - const rid = assertResource(resource.buffer, { prefix, context }); - assertDeviceMatch(device, resource.buffer, { - prefix, - resourceContext: context, - selfContext: "this", - }); - return { - binding: entry.binding, - kind: "GPUBufferBinding", - resource: rid, - offset: entry.resource.offset, - size: entry.resource.size, - }; - } - }); - - const { rid, err } = ops.op_webgpu_create_bind_group( - device.rid, - descriptor.label, - layout, - entries, - ); - device.pushError(err); - - const bindGroup = createGPUBindGroup( - descriptor.label, - device, - rid, - ); - device.trackResource(bindGroup); - return bindGroup; - } - - /** - * @param {GPUShaderModuleDescriptor} descriptor - */ - createShaderModule(descriptor) { - webidl.assertBranded(this, GPUDevicePrototype); - const prefix = "Failed to execute 'createShaderModule' on 'GPUDevice'"; - webidl.requiredArguments(arguments.length, 1, { prefix }); - descriptor = webidl.converters.GPUShaderModuleDescriptor(descriptor, { - prefix, - context: "Argument 1", - }); - const device = assertDevice(this, { prefix, context: "this" }); - const { rid, err } = ops.op_webgpu_create_shader_module( - device.rid, - descriptor.label, - descriptor.code, - ); - device.pushError(err); - - const shaderModule = createGPUShaderModule( - descriptor.label, - device, - rid, - ); - device.trackResource(shaderModule); - return shaderModule; - } - - /** - * @param {GPUComputePipelineDescriptor} descriptor - * @returns {GPUComputePipeline} - */ - createComputePipeline(descriptor) { - webidl.assertBranded(this, GPUDevicePrototype); - const prefix = "Failed to execute 'createComputePipeline' on 'GPUDevice'"; - webidl.requiredArguments(arguments.length, 1, { prefix }); - descriptor = webidl.converters.GPUComputePipelineDescriptor(descriptor, { - prefix, - context: "Argument 1", - }); - const device = assertDevice(this, { prefix, context: "this" }); - let layout = descriptor.layout; - if (typeof descriptor.layout !== "string") { - const context = "layout"; - layout = assertResource(descriptor.layout, { prefix, context }); - assertDeviceMatch(device, descriptor.layout, { - prefix, - resourceContext: context, - selfContext: "this", - }); - } - const module = assertResource(descriptor.compute.module, { - prefix, - context: "compute shader module", - }); - assertDeviceMatch(device, descriptor.compute.module, { - prefix, - resourceContext: "compute shader module", - selfContext: "this", - }); - - const { rid, err } = ops.op_webgpu_create_compute_pipeline( - device.rid, - descriptor.label, - layout, - { - module, - entryPoint: descriptor.compute.entryPoint, - constants: descriptor.compute.constants, - }, - ); - device.pushError(err); - - const computePipeline = createGPUComputePipeline( - descriptor.label, - device, - rid, - ); - device.trackResource(computePipeline); - return computePipeline; - } - - /** - * @param {GPURenderPipelineDescriptor} descriptor - * @returns {GPURenderPipeline} - */ - createRenderPipeline(descriptor) { - webidl.assertBranded(this, GPUDevicePrototype); - const prefix = "Failed to execute 'createRenderPipeline' on 'GPUDevice'"; - webidl.requiredArguments(arguments.length, 1, { prefix }); - descriptor = webidl.converters.GPURenderPipelineDescriptor(descriptor, { - prefix, - context: "Argument 1", - }); - const device = assertDevice(this, { prefix, context: "this" }); - let layout = descriptor.layout; - if (typeof descriptor.layout !== "string") { - const context = "layout"; - layout = assertResource(descriptor.layout, { prefix, context }); - assertDeviceMatch(device, descriptor.layout, { - prefix, - resourceContext: context, - selfContext: "this", - }); - } - const module = assertResource(descriptor.vertex.module, { - prefix, - context: "vertex shader module", - }); - assertDeviceMatch(device, descriptor.vertex.module, { - prefix, - resourceContext: "vertex shader module", - selfContext: "this", - }); - let fragment = undefined; - if (descriptor.fragment) { - const module = assertResource(descriptor.fragment.module, { - prefix, - context: "fragment shader module", - }); - assertDeviceMatch(device, descriptor.fragment.module, { - prefix, - resourceContext: "fragment shader module", - selfContext: "this", - }); - fragment = { - module, - entryPoint: descriptor.fragment.entryPoint, - targets: descriptor.fragment.targets, - }; - } - - const { rid, err } = ops.op_webgpu_create_render_pipeline({ - deviceRid: device.rid, - label: descriptor.label, - layout, - vertex: { - module, - entryPoint: descriptor.vertex.entryPoint, - buffers: descriptor.vertex.buffers, - }, - primitive: descriptor.primitive, - depthStencil: descriptor.depthStencil, - multisample: descriptor.multisample, - fragment, - }); - device.pushError(err); - - const renderPipeline = createGPURenderPipeline( - descriptor.label, - device, - rid, - ); - device.trackResource(renderPipeline); - return renderPipeline; - } - - createComputePipelineAsync(descriptor) { - // TODO(lucacasonato): this should be real async - return PromiseResolve(this.createComputePipeline(descriptor)); - } - - createRenderPipelineAsync(descriptor) { - // TODO(lucacasonato): this should be real async - return PromiseResolve(this.createRenderPipeline(descriptor)); - } - - /** - * @param {GPUCommandEncoderDescriptor} descriptor - * @returns {GPUCommandEncoder} - */ - createCommandEncoder(descriptor = {}) { - webidl.assertBranded(this, GPUDevicePrototype); - const prefix = "Failed to execute 'createCommandEncoder' on 'GPUDevice'"; - descriptor = webidl.converters.GPUCommandEncoderDescriptor(descriptor, { - prefix, - context: "Argument 1", - }); - const device = assertDevice(this, { prefix, context: "this" }); - const { rid, err } = ops.op_webgpu_create_command_encoder( - device.rid, - descriptor.label, - ); - device.pushError(err); - - const commandEncoder = createGPUCommandEncoder( - descriptor.label, - device, - rid, - ); - device.trackResource(commandEncoder); - return commandEncoder; - } - - /** - * @param {GPURenderBundleEncoderDescriptor} descriptor - * @returns {GPURenderBundleEncoder} - */ - createRenderBundleEncoder(descriptor) { - webidl.assertBranded(this, GPUDevicePrototype); - const prefix = - "Failed to execute 'createRenderBundleEncoder' on 'GPUDevice'"; - webidl.requiredArguments(arguments.length, 1, { prefix }); - descriptor = webidl.converters.GPURenderBundleEncoderDescriptor( - descriptor, - { - prefix, - context: "Argument 1", - }, - ); - const device = assertDevice(this, { prefix, context: "this" }); - const { rid, err } = ops.op_webgpu_create_render_bundle_encoder({ - deviceRid: device.rid, - ...descriptor, - }); - device.pushError(err); - - const renderBundleEncoder = createGPURenderBundleEncoder( - descriptor.label, - device, - rid, - ); - device.trackResource(renderBundleEncoder); - return renderBundleEncoder; - } - - /** - * @param {GPUQuerySetDescriptor} descriptor - * @returns {GPUQuerySet} - */ - createQuerySet(descriptor) { - webidl.assertBranded(this, GPUDevicePrototype); - const prefix = "Failed to execute 'createQuerySet' on 'GPUDevice'"; - webidl.requiredArguments(arguments.length, 1, { prefix }); - descriptor = webidl.converters.GPUQuerySetDescriptor( - descriptor, - { - prefix, - context: "Argument 1", - }, - ); - const device = assertDevice(this, { prefix, context: "this" }); - const { rid, err } = ops.op_webgpu_create_query_set({ - deviceRid: device.rid, - ...descriptor, - }); - device.pushError(err); - - const querySet = createGPUQuerySet( - descriptor.label, - device, - rid, - descriptor, - ); - device.trackResource(querySet); - return querySet; - } - - get lost() { - webidl.assertBranded(this, GPUDevicePrototype); - const device = this[_device]; - if (!device) { - return PromiseResolve(true); - } - if (device.rid === undefined) { - return PromiseResolve(true); - } - return device.lost; - } - - /** - * @param {GPUErrorFilter} filter - */ - pushErrorScope(filter) { - webidl.assertBranded(this, GPUDevicePrototype); - const prefix = "Failed to execute 'pushErrorScope' on 'GPUDevice'"; - webidl.requiredArguments(arguments.length, 1, { prefix }); - filter = webidl.converters.GPUErrorFilter(filter, { - prefix, - context: "Argument 1", - }); - const device = assertDevice(this, { prefix, context: "this" }); - ArrayPrototypePush(device.errorScopeStack, { filter, operations: [] }); - } - - /** - * @returns {Promise} - */ - // deno-lint-ignore require-await - async popErrorScope() { - webidl.assertBranded(this, GPUDevicePrototype); - const prefix = "Failed to execute 'popErrorScope' on 'GPUDevice'"; - const device = assertDevice(this, { prefix, context: "this" }); - if (device.isLost) { - throw new DOMException("Device has been lost.", "OperationError"); - } - const scope = ArrayPrototypePop(device.errorScopeStack); - if (!scope) { - throw new DOMException( - "There are no error scopes on the error scope stack.", - "OperationError", - ); - } - const operations = SafePromiseAll(scope.operations); - return PromisePrototypeThen( - operations, - () => PromiseResolve(null), - (err) => PromiseResolve(err), - ); - } - - [SymbolFor("Deno.privateCustomInspect")](inspect) { - return `${this.constructor.name} ${ - inspect({ - features: this.features, - label: this.label, - limits: this.limits, - queue: this.queue, - }) - }`; - } - } - GPUObjectBaseMixin("GPUDevice", GPUDevice); - const GPUDevicePrototype = GPUDevice.prototype; - - /** - * @param {string | null} label - * @param {InnerGPUDevice} device - * @returns {GPUQueue} - */ - function createGPUQueue(label, device) { - /** @type {GPUQueue} */ - const queue = webidl.createBranded(GPUQueue); - queue[_label] = label; - queue[_device] = device; - return queue; - } - - class GPUQueue { - /** @type {InnerGPUDevice} */ - [_device]; - - constructor() { - webidl.illegalConstructor(); - } - - /** - * @param {GPUCommandBuffer[]} commandBuffers - */ - submit(commandBuffers) { - webidl.assertBranded(this, GPUQueuePrototype); - const prefix = "Failed to execute 'submit' on 'GPUQueue'"; - webidl.requiredArguments(arguments.length, 1, { - prefix, - }); - commandBuffers = webidl.converters["sequence"]( - commandBuffers, - { prefix, context: "Argument 1" }, - ); - const device = assertDevice(this, { prefix, context: "this" }); - const commandBufferRids = ArrayPrototypeMap( - commandBuffers, - (buffer, i) => { - const context = `command buffer ${i + 1}`; - const rid = assertResource(buffer, { prefix, context }); - assertDeviceMatch(device, buffer, { - prefix, - selfContext: "this", - resourceContext: context, - }); - return rid; - }, - ); - const { err } = ops.op_webgpu_queue_submit(device.rid, commandBufferRids); - for (let i = 0; i < commandBuffers.length; ++i) { - commandBuffers[i][_rid] = undefined; - } - device.pushError(err); - } - - onSubmittedWorkDone() { - webidl.assertBranded(this, GPUQueuePrototype); - return PromiseResolve(); - } - - /** - * @param {GPUBuffer} buffer - * @param {number} bufferOffset - * @param {BufferSource} data - * @param {number} [dataOffset] - * @param {number} [size] - */ - writeBuffer(buffer, bufferOffset, data, dataOffset = 0, size) { - webidl.assertBranded(this, GPUQueuePrototype); - const prefix = "Failed to execute 'writeBuffer' on 'GPUQueue'"; - webidl.requiredArguments(arguments.length, 3, { prefix }); - buffer = webidl.converters["GPUBuffer"](buffer, { - prefix, - context: "Argument 1", - }); - bufferOffset = webidl.converters["GPUSize64"](bufferOffset, { - prefix, - context: "Argument 2", - }); - data = webidl.converters.BufferSource(data, { - prefix, - context: "Argument 3", - }); - dataOffset = webidl.converters["GPUSize64"](dataOffset, { - prefix, - context: "Argument 4", - }); - size = size === undefined - ? undefined - : webidl.converters["GPUSize64"](size, { - prefix, - context: "Argument 5", - }); - const device = assertDevice(this, { prefix, context: "this" }); - const bufferRid = assertResource(buffer, { - prefix, - context: "Argument 1", - }); - assertDeviceMatch(device, buffer, { - prefix, - selfContext: "this", - resourceContext: "Argument 1", - }); - const { err } = ops.op_webgpu_write_buffer( - device.rid, - bufferRid, - bufferOffset, - dataOffset, - size, - new Uint8Array(ArrayBufferIsView(data) ? data.buffer : data), - ); - device.pushError(err); - } - - /** - * @param {GPUImageCopyTexture} destination - * @param {BufferSource} data - * @param {GPUImageDataLayout} dataLayout - * @param {GPUExtent3D} size - */ - writeTexture(destination, data, dataLayout, size) { - webidl.assertBranded(this, GPUQueuePrototype); - const prefix = "Failed to execute 'writeTexture' on 'GPUQueue'"; - webidl.requiredArguments(arguments.length, 4, { prefix }); - destination = webidl.converters.GPUImageCopyTexture(destination, { - prefix, - context: "Argument 1", - }); - data = webidl.converters.BufferSource(data, { - prefix, - context: "Argument 2", - }); - dataLayout = webidl.converters.GPUImageDataLayout(dataLayout, { - prefix, - context: "Argument 3", - }); - size = webidl.converters.GPUExtent3D(size, { - prefix, - context: "Argument 4", - }); - const device = assertDevice(this, { prefix, context: "this" }); - const textureRid = assertResource(destination.texture, { - prefix, - context: "texture", - }); - assertDeviceMatch(device, destination.texture, { - prefix, - selfContext: "this", - resourceContext: "texture", - }); - const { err } = ops.op_webgpu_write_texture( - device.rid, - { - texture: textureRid, - mipLevel: destination.mipLevel, - origin: destination.origin - ? normalizeGPUOrigin3D(destination.origin) - : undefined, - aspect: destination.aspect, - }, - dataLayout, - normalizeGPUExtent3D(size), - new Uint8Array(ArrayBufferIsView(data) ? data.buffer : data), - ); - device.pushError(err); - } - - copyImageBitmapToTexture(_source, _destination, _copySize) { - throw new Error("Not yet implemented"); - } - - [SymbolFor("Deno.privateCustomInspect")](inspect) { - return `${this.constructor.name} ${ - inspect({ - label: this.label, - }) - }`; - } - } - GPUObjectBaseMixin("GPUQueue", GPUQueue); - const GPUQueuePrototype = GPUQueue.prototype; - - /** - * @typedef CreateGPUBufferOptions - * @property {ArrayBuffer | null} mapping - * @property {number[] | null} mappingRange - * @property {[ArrayBuffer, number, number][] | null} mappedRanges - * @property {"mapped" | "mapped at creation" | "mapped pending" | "unmapped" | "destroy" } state - */ - - /** - * @param {string | null} label - * @param {InnerGPUDevice} device - * @param {number} rid - * @param {number} size - * @param {number} usage - * @param {CreateGPUBufferOptions} options - * @returns {GPUBuffer} - */ - function createGPUBuffer(label, device, rid, size, usage, options) { - /** @type {GPUBuffer} */ - const buffer = webidl.createBranded(GPUBuffer); - buffer[_label] = label; - buffer[_device] = device; - buffer[_rid] = rid; - buffer[_size] = size; - buffer[_usage] = usage; - buffer[_mappingRange] = options.mappingRange; - buffer[_mappedRanges] = options.mappedRanges; - buffer[_state] = options.state; - return buffer; - } - - class GPUBuffer { - /** @type {InnerGPUDevice} */ - [_device]; - /** @type {number} */ - [_rid]; - /** @type {number} */ - [_size]; - /** @type {number} */ - [_usage]; - /** @type {"mapped" | "mapped at creation" | "pending" | "unmapped" | "destroy"} */ - [_state]; - /** @type {[number, number] | null} */ - [_mappingRange]; - /** @type {[ArrayBuffer, number, number][] | null} */ - [_mappedRanges]; - /** @type {number} */ - [_mapMode]; - - [_cleanup]() { - const mappedRanges = this[_mappedRanges]; - if (mappedRanges) { - while (mappedRanges.length > 0) { - const mappedRange = ArrayPrototypePop(mappedRanges); - if (mappedRange !== undefined) { - core.close(mappedRange[1]); - } - } - } - const rid = this[_rid]; - if (rid !== undefined) { - core.close(rid); - /** @type {number | undefined} */ - this[_rid] = undefined; - } - this[_state] = "destroy"; - } - - constructor() { - webidl.illegalConstructor(); - } - - get size() { - webidl.assertBranded(this, GPUBufferPrototype); - return this[_size]; - } - - get usage() { - webidl.assertBranded(this, GPUBufferPrototype); - return this[_usage]; - } - - get mapState() { - webidl.assertBranded(this, GPUBufferPrototype); - const state = this[_state]; - if (state === "mapped at creation") { - return "mapped"; - } else { - return state; - } - } - - /** - * @param {number} mode - * @param {number} offset - * @param {number} [size] - */ - async mapAsync(mode, offset = 0, size) { - webidl.assertBranded(this, GPUBufferPrototype); - const prefix = "Failed to execute 'mapAsync' on 'GPUBuffer'"; - webidl.requiredArguments(arguments.length, 1, { prefix }); - mode = webidl.converters.GPUMapModeFlags(mode, { - prefix, - context: "Argument 1", - }); - offset = webidl.converters.GPUSize64(offset, { - prefix, - context: "Argument 2", - }); - size = size === undefined - ? undefined - : webidl.converters.GPUSize64(size, { - prefix, - context: "Argument 3", - }); - const device = assertDevice(this, { prefix, context: "this" }); - const bufferRid = assertResource(this, { prefix, context: "this" }); - /** @type {number} */ - let rangeSize; - if (size === undefined) { - rangeSize = MathMax(0, this[_size] - offset); - } else { - rangeSize = this[_size]; - } - if ((offset % 8) !== 0) { - throw new DOMException( - `${prefix}: offset must be a multiple of 8.`, - "OperationError", - ); - } - if ((rangeSize % 4) !== 0) { - throw new DOMException( - `${prefix}: rangeSize must be a multiple of 4.`, - "OperationError", - ); - } - if ((offset + rangeSize) > this[_size]) { - throw new DOMException( - `${prefix}: offset + rangeSize must be less than or equal to buffer size.`, - "OperationError", - ); - } - if (this[_state] !== "unmapped") { - throw new DOMException( - `${prefix}: GPUBuffer is not currently unmapped.`, - "OperationError", - ); - } - const readMode = (mode & 0x0001) === 0x0001; - const writeMode = (mode & 0x0002) === 0x0002; - if ((readMode && writeMode) || (!readMode && !writeMode)) { - throw new DOMException( - `${prefix}: exactly one of READ or WRITE map mode must be set.`, - "OperationError", - ); - } - if (readMode && !((this[_usage] && 0x0001) === 0x0001)) { - throw new DOMException( - `${prefix}: READ map mode not valid because buffer does not have MAP_READ usage.`, - "OperationError", - ); - } - if (writeMode && !((this[_usage] && 0x0002) === 0x0002)) { - throw new DOMException( - `${prefix}: WRITE map mode not valid because buffer does not have MAP_WRITE usage.`, - "OperationError", - ); - } - - this[_mapMode] = mode; - this[_state] = "pending"; - const promise = PromisePrototypeThen( - core.opAsync( - "op_webgpu_buffer_get_map_async", - bufferRid, - device.rid, - mode, - offset, - rangeSize, - ), - ({ err }) => err, - ); - device.pushErrorPromise(promise); - const err = await promise; - if (err) { - throw new DOMException("validation error occured", "OperationError"); - } - this[_state] = "mapped"; - this[_mappingRange] = [offset, offset + rangeSize]; - /** @type {[ArrayBuffer, number, number][] | null} */ - this[_mappedRanges] = []; - } - - /** - * @param {number} offset - * @param {number} size - */ - getMappedRange(offset = 0, size) { - webidl.assertBranded(this, GPUBufferPrototype); - const prefix = "Failed to execute 'getMappedRange' on 'GPUBuffer'"; - offset = webidl.converters.GPUSize64(offset, { - prefix, - context: "Argument 1", - }); - if (size !== undefined) { - size = webidl.converters.GPUSize64(size, { - prefix, - context: "Argument 2", - }); - } - assertDevice(this, { prefix, context: "this" }); - const bufferRid = assertResource(this, { prefix, context: "this" }); - /** @type {number} */ - let rangeSize; - if (size === undefined) { - rangeSize = MathMax(0, this[_size] - offset); - } else { - rangeSize = size; - } - - const mappedRanges = this[_mappedRanges]; - if (!mappedRanges) { - throw new DOMException(`${prefix}: invalid state.`, "OperationError"); - } - for (let i = 0; i < mappedRanges.length; ++i) { - const { 0: buffer, /* 1: rid, */ 2: start } = mappedRanges[i]; - // TODO(lucacasonato): is this logic correct? - const end = start + buffer.byteLength; - if ( - (start >= offset && start < (offset + rangeSize)) || - (end >= offset && end < (offset + rangeSize)) - ) { - throw new DOMException( - `${prefix}: requested buffer overlaps with another mapped range.`, - "OperationError", - ); - } - } - - const buffer = new ArrayBuffer(rangeSize); - const { rid } = ops.op_webgpu_buffer_get_mapped_range( - bufferRid, - offset, - size, - new Uint8Array(buffer), - ); - - ArrayPrototypePush(mappedRanges, [buffer, rid, offset]); - - return buffer; - } - - unmap() { - webidl.assertBranded(this, GPUBufferPrototype); - const prefix = "Failed to execute 'unmap' on 'GPUBuffer'"; - const device = assertDevice(this, { prefix, context: "this" }); - const bufferRid = assertResource(this, { prefix, context: "this" }); - if (this[_state] === "unmapped" || this[_state] === "destroyed") { - throw new DOMException( - `${prefix}: buffer is not ready to be unmapped.`, - "OperationError", - ); - } - if (this[_state] === "pending") { - // TODO(lucacasonato): this is not spec compliant. - throw new DOMException( - `${prefix}: can not unmap while mapping. This is a Deno limitation.`, - "OperationError", - ); - } else if ( - this[_state] === "mapped" || this[_state] === "mapped at creation" - ) { - /** @type {boolean} */ - let write = false; - if (this[_state] === "mapped at creation") { - write = true; - } else if (this[_state] === "mapped") { - const mapMode = this[_mapMode]; - if (mapMode === undefined) { - throw new DOMException( - `${prefix}: invalid state.`, - "OperationError", - ); - } - if ((mapMode & 0x0002) === 0x0002) { - write = true; - } - } - - const mappedRanges = this[_mappedRanges]; - if (!mappedRanges) { - throw new DOMException(`${prefix}: invalid state.`, "OperationError"); - } - for (let i = 0; i < mappedRanges.length; ++i) { - const { 0: buffer, 1: mappedRid } = mappedRanges[i]; - const { err } = ops.op_webgpu_buffer_unmap( - bufferRid, - mappedRid, - ...new SafeArrayIterator(write ? [new Uint8Array(buffer)] : []), - ); - device.pushError(err); - if (err) return; - } - this[_mappingRange] = null; - this[_mappedRanges] = null; - } - - this[_state] = "unmapped"; - } - - destroy() { - webidl.assertBranded(this, GPUBufferPrototype); - this[_cleanup](); - } - - [SymbolFor("Deno.privateCustomInspect")](inspect) { - return `${this.constructor.name} ${ - inspect({ - label: this.label, - }) - }`; - } - } - GPUObjectBaseMixin("GPUBuffer", GPUBuffer); - const GPUBufferPrototype = GPUBuffer.prototype; - - class GPUBufferUsage { - constructor() { - webidl.illegalConstructor(); - } - - static get MAP_READ() { - return 0x0001; - } - static get MAP_WRITE() { - return 0x0002; - } - static get COPY_SRC() { - return 0x0004; - } - static get COPY_DST() { - return 0x0008; - } - static get INDEX() { - return 0x0010; - } - static get VERTEX() { - return 0x0020; - } - static get UNIFORM() { - return 0x0040; - } - static get STORAGE() { - return 0x0080; - } - static get INDIRECT() { - return 0x0100; - } - static get QUERY_RESOLVE() { - return 0x0200; - } - } - - class GPUMapMode { - constructor() { - webidl.illegalConstructor(); - } - - static get READ() { - return 0x0001; - } - static get WRITE() { - return 0x0002; - } - } - - /** - * @param {GPUTextureDescriptor} descriptor - * @param {InnerGPUDevice} device - * @param {number} rid - * @returns {GPUTexture} - */ - function createGPUTexture(descriptor, device, rid, Type = GPUTexture) { - /** @type {GPUTexture} */ - const texture = webidl.createBranded(Type); - texture[_label] = descriptor.label; - texture[_device] = device; - texture[_rid] = rid; - texture[_views] = []; - texture[_width] = descriptor.size.width; - texture[_height] = descriptor.size.height; - texture[_depthOrArrayLayers] = descriptor.size.depthOrArrayLayers; - texture[_mipLevelCount] = descriptor.mipLevelCount; - texture[_sampleCount] = descriptor.sampleCount; - texture[_dimension] = descriptor.dimension; - texture[_format] = descriptor.format; - texture[_usage] = descriptor.usage; - return texture; - } - - class GPUTexture { - /** @type {InnerGPUDevice} */ - [_device]; - /** @type {number | undefined} */ - [_rid]; - /** @type {WeakRef[]} */ - [_views]; - - /** @type {number} */ - [_width]; - /** @type {number} */ - [_height]; - /** @type {number} */ - [_depthOrArrayLayers]; - /** @type {number} */ - [_mipLevelCount]; - /** @type {number} */ - [_sampleCount]; - /** @type {GPUTextureDimension} */ - [_dimension]; - /** @type {GPUTextureFormat} */ - [_format]; - /** @type {number} */ - [_usage]; - - [_cleanup]() { - const views = this[_views]; - while (views.length > 0) { - const view = ArrayPrototypePop(views)?.deref(); - if (view) { - view[_cleanup](); - } - } - const rid = this[_rid]; - if (rid !== undefined) { - core.close(rid); - /** @type {number | undefined} */ - this[_rid] = undefined; - } - } - - constructor() { - webidl.illegalConstructor(); - } - - /** - * @param {GPUTextureViewDescriptor} descriptor - */ - createView(descriptor = {}) { - webidl.assertBranded(this, GPUTexturePrototype); - const prefix = "Failed to execute 'createView' on 'GPUTexture'"; - webidl.requiredArguments(arguments.length, 0, { prefix }); - descriptor = webidl.converters.GPUTextureViewDescriptor(descriptor, { - prefix, - context: "Argument 1", - }); - const device = assertDevice(this, { prefix, context: "this" }); - const textureRid = assertResource(this, { prefix, context: "this" }); - const { rid, err } = ops.op_webgpu_create_texture_view({ - textureRid, - ...descriptor, - }); - device.pushError(err); - - const textureView = createGPUTextureView( - descriptor.label, - this, - rid, - ); - ArrayPrototypePush(this[_views], new WeakRef(textureView)); - return textureView; - } - - destroy() { - webidl.assertBranded(this, GPUTexturePrototype); - this[_cleanup](); - } - - get width() { - webidl.assertBranded(this, GPUTexturePrototype); - return this[_width]; - } - - get height() { - webidl.assertBranded(this, GPUTexturePrototype); - return this[_height]; - } - - get depthOrArrayLayers() { - webidl.assertBranded(this, GPUTexturePrototype); - return this[_depthOrArrayLayers]; - } - - get mipLevelCount() { - webidl.assertBranded(this, GPUTexturePrototype); - return this[_mipLevelCount]; - } - - get sampleCount() { - webidl.assertBranded(this, GPUTexturePrototype); - return this[_sampleCount]; - } - - get dimension() { - webidl.assertBranded(this, GPUTexturePrototype); - return this[_dimension]; - } - - get format() { - webidl.assertBranded(this, GPUTexturePrototype); - return this[_format]; - } - - get usage() { - webidl.assertBranded(this, GPUTexturePrototype); - return this[_usage]; - } - - [SymbolFor("Deno.privateCustomInspect")](inspect) { - return `${this.constructor.name} ${ - inspect({ - label: this.label, - }) - }`; - } - } - GPUObjectBaseMixin("GPUTexture", GPUTexture); - const GPUTexturePrototype = GPUTexture.prototype; - - class GPUTextureUsage { - constructor() { - webidl.illegalConstructor(); - } - - static get COPY_SRC() { - return 0x01; - } - static get COPY_DST() { - return 0x02; - } - static get TEXTURE_BINDING() { - return 0x04; - } - static get STORAGE_BINDING() { - return 0x08; - } - static get RENDER_ATTACHMENT() { - return 0x10; - } - } - - /** - * @param {string | null} label - * @param {GPUTexture} texture - * @param {number} rid - * @returns {GPUTextureView} - */ - function createGPUTextureView(label, texture, rid) { - /** @type {GPUTextureView} */ - const textureView = webidl.createBranded(GPUTextureView); - textureView[_label] = label; - textureView[_texture] = texture; - textureView[_rid] = rid; - return textureView; - } - class GPUTextureView { - /** @type {GPUTexture} */ - [_texture]; - /** @type {number | undefined} */ - [_rid]; - - [_cleanup]() { - const rid = this[_rid]; - if (rid !== undefined) { - core.close(rid); - /** @type {number | undefined} */ - this[_rid] = undefined; - } - } - - constructor() { - webidl.illegalConstructor(); - } - - [SymbolFor("Deno.privateCustomInspect")](inspect) { - return `${this.constructor.name} ${ - inspect({ - label: this.label, - }) - }`; - } - } - GPUObjectBaseMixin("GPUTextureView", GPUTextureView); - const GPUTextureViewPrototype = GPUTextureView.prototype; - /** - * @param {string | null} label - * @param {InnerGPUDevice} device - * @param {number} rid - * @returns {GPUSampler} - */ - function createGPUSampler(label, device, rid) { - /** @type {GPUSampler} */ - const sampler = webidl.createBranded(GPUSampler); - sampler[_label] = label; - sampler[_device] = device; - sampler[_rid] = rid; - return sampler; - } - class GPUSampler { - /** @type {InnerGPUDevice} */ - [_device]; - /** @type {number | undefined} */ - [_rid]; - - [_cleanup]() { - const rid = this[_rid]; - if (rid !== undefined) { - core.close(rid); - /** @type {number | undefined} */ - this[_rid] = undefined; - } - } - - constructor() { - webidl.illegalConstructor(); - } - - [SymbolFor("Deno.privateCustomInspect")](inspect) { - return `${this.constructor.name} ${ - inspect({ - label: this.label, - }) - }`; - } - } - GPUObjectBaseMixin("GPUSampler", GPUSampler); - const GPUSamplerPrototype = GPUSampler.prototype; - /** - * @param {string | null} label - * @param {InnerGPUDevice} device - * @param {number} rid - * @returns {GPUBindGroupLayout} - */ - function createGPUBindGroupLayout(label, device, rid) { - /** @type {GPUBindGroupLayout} */ - const bindGroupLayout = webidl.createBranded(GPUBindGroupLayout); - bindGroupLayout[_label] = label; - bindGroupLayout[_device] = device; - bindGroupLayout[_rid] = rid; - return bindGroupLayout; - } - class GPUBindGroupLayout { - /** @type {InnerGPUDevice} */ - [_device]; - /** @type {number | undefined} */ - [_rid]; - - [_cleanup]() { - const rid = this[_rid]; - if (rid !== undefined) { - core.close(rid); - /** @type {number | undefined} */ - this[_rid] = undefined; - } - } - - constructor() { - webidl.illegalConstructor(); - } - - [SymbolFor("Deno.privateCustomInspect")](inspect) { - return `${this.constructor.name} ${ - inspect({ - label: this.label, - }) - }`; - } - } - GPUObjectBaseMixin("GPUBindGroupLayout", GPUBindGroupLayout); - - /** - * @param {string | null} label - * @param {InnerGPUDevice} device - * @param {number} rid - * @returns {GPUPipelineLayout} - */ - function createGPUPipelineLayout(label, device, rid) { - /** @type {GPUPipelineLayout} */ - const pipelineLayout = webidl.createBranded(GPUPipelineLayout); - pipelineLayout[_label] = label; - pipelineLayout[_device] = device; - pipelineLayout[_rid] = rid; - return pipelineLayout; - } - class GPUPipelineLayout { - /** @type {InnerGPUDevice} */ - [_device]; - /** @type {number | undefined} */ - [_rid]; - - [_cleanup]() { - const rid = this[_rid]; - if (rid !== undefined) { - core.close(rid); - /** @type {number | undefined} */ - this[_rid] = undefined; - } - } - - constructor() { - webidl.illegalConstructor(); - } - - [SymbolFor("Deno.privateCustomInspect")](inspect) { - return `${this.constructor.name} ${ - inspect({ - label: this.label, - }) - }`; - } - } - GPUObjectBaseMixin("GPUPipelineLayout", GPUPipelineLayout); - - /** - * @param {string | null} label - * @param {InnerGPUDevice} device - * @param {number} rid - * @returns {GPUBindGroup} - */ - function createGPUBindGroup(label, device, rid) { - /** @type {GPUBindGroup} */ - const bindGroup = webidl.createBranded(GPUBindGroup); - bindGroup[_label] = label; - bindGroup[_device] = device; - bindGroup[_rid] = rid; - return bindGroup; - } - class GPUBindGroup { - /** @type {InnerGPUDevice} */ - [_device]; - /** @type {number | undefined} */ - [_rid]; - - [_cleanup]() { - const rid = this[_rid]; - if (rid !== undefined) { - core.close(rid); - /** @type {number | undefined} */ - this[_rid] = undefined; - } - } - - constructor() { - webidl.illegalConstructor(); - } - - [SymbolFor("Deno.privateCustomInspect")](inspect) { - return `${this.constructor.name} ${ - inspect({ - label: this.label, - }) - }`; - } - } - GPUObjectBaseMixin("GPUBindGroup", GPUBindGroup); - - /** - * @param {string | null} label - * @param {InnerGPUDevice} device - * @param {number} rid - * @returns {GPUShaderModule} - */ - function createGPUShaderModule(label, device, rid) { - /** @type {GPUShaderModule} */ - const bindGroup = webidl.createBranded(GPUShaderModule); - bindGroup[_label] = label; - bindGroup[_device] = device; - bindGroup[_rid] = rid; - return bindGroup; - } - class GPUShaderModule { - /** @type {InnerGPUDevice} */ - [_device]; - /** @type {number | undefined} */ - [_rid]; - - [_cleanup]() { - const rid = this[_rid]; - if (rid !== undefined) { - core.close(rid); - /** @type {number | undefined} */ - this[_rid] = undefined; - } - } - - constructor() { - webidl.illegalConstructor(); - } - - compilationInfo() { - throw new Error("Not yet implemented"); - } - - [SymbolFor("Deno.privateCustomInspect")](inspect) { - return `${this.constructor.name} ${ - inspect({ - label: this.label, - }) - }`; - } - } - GPUObjectBaseMixin("GPUShaderModule", GPUShaderModule); - - class GPUShaderStage { - constructor() { - webidl.illegalConstructor(); - } - - static get VERTEX() { - return 0x1; - } - - static get FRAGMENT() { - return 0x2; - } - - static get COMPUTE() { - return 0x4; - } - } - - /** - * @param {string | null} label - * @param {InnerGPUDevice} device - * @param {number} rid - * @returns {GPUComputePipeline} - */ - function createGPUComputePipeline(label, device, rid) { - /** @type {GPUComputePipeline} */ - const pipeline = webidl.createBranded(GPUComputePipeline); - pipeline[_label] = label; - pipeline[_device] = device; - pipeline[_rid] = rid; - return pipeline; - } - class GPUComputePipeline { - /** @type {InnerGPUDevice} */ - [_device]; - /** @type {number | undefined} */ - [_rid]; - - [_cleanup]() { - const rid = this[_rid]; - if (rid !== undefined) { - core.close(rid); - /** @type {number | undefined} */ - this[_rid] = undefined; - } - } - - constructor() { - webidl.illegalConstructor(); - } - - /** - * @param {number} index - * @returns {GPUBindGroupLayout} - */ - getBindGroupLayout(index) { - webidl.assertBranded(this, GPUComputePipelinePrototype); - const prefix = - "Failed to execute 'getBindGroupLayout' on 'GPUComputePipeline'"; - webidl.requiredArguments(arguments.length, 1, { prefix }); - index = webidl.converters["unsigned long"](index, { - prefix, - context: "Argument 1", - }); - const device = assertDevice(this, { prefix, context: "this" }); - const computePipelineRid = assertResource(this, { - prefix, - context: "this", - }); - const { rid, label, err } = ops - .op_webgpu_compute_pipeline_get_bind_group_layout( - computePipelineRid, - index, - ); - device.pushError(err); - - const bindGroupLayout = createGPUBindGroupLayout( - label, - device, - rid, - ); - device.trackResource(bindGroupLayout); - return bindGroupLayout; - } - - [SymbolFor("Deno.privateCustomInspect")](inspect) { - return `${this.constructor.name} ${ - inspect({ - label: this.label, - }) - }`; - } - } - GPUObjectBaseMixin("GPUComputePipeline", GPUComputePipeline); - const GPUComputePipelinePrototype = GPUComputePipeline.prototype; - - /** - * @param {string | null} label - * @param {InnerGPUDevice} device - * @param {number} rid - * @returns {GPURenderPipeline} - */ - function createGPURenderPipeline(label, device, rid) { - /** @type {GPURenderPipeline} */ - const pipeline = webidl.createBranded(GPURenderPipeline); - pipeline[_label] = label; - pipeline[_device] = device; - pipeline[_rid] = rid; - return pipeline; - } - class GPURenderPipeline { - /** @type {InnerGPUDevice} */ - [_device]; - /** @type {number | undefined} */ - [_rid]; - - [_cleanup]() { - const rid = this[_rid]; - if (rid !== undefined) { - core.close(rid); - /** @type {number | undefined} */ - this[_rid] = undefined; - } - } - - constructor() { - webidl.illegalConstructor(); - } - - /** - * @param {number} index - */ - getBindGroupLayout(index) { - webidl.assertBranded(this, GPURenderPipelinePrototype); - const prefix = - "Failed to execute 'getBindGroupLayout' on 'GPURenderPipeline'"; - webidl.requiredArguments(arguments.length, 1, { prefix }); - index = webidl.converters["unsigned long"](index, { - prefix, - context: "Argument 1", - }); - const device = assertDevice(this, { prefix, context: "this" }); - const renderPipelineRid = assertResource(this, { - prefix, - context: "this", - }); - const { rid, label, err } = ops - .op_webgpu_render_pipeline_get_bind_group_layout( - renderPipelineRid, - index, - ); - device.pushError(err); - - const bindGroupLayout = createGPUBindGroupLayout( - label, - device, - rid, - ); - device.trackResource(bindGroupLayout); - return bindGroupLayout; - } - - [SymbolFor("Deno.privateCustomInspect")](inspect) { - return `${this.constructor.name} ${ - inspect({ - label: this.label, - }) - }`; - } - } - GPUObjectBaseMixin("GPURenderPipeline", GPURenderPipeline); - const GPURenderPipelinePrototype = GPURenderPipeline.prototype; - - class GPUColorWrite { - constructor() { - webidl.illegalConstructor(); - } - - static get RED() { - return 0x1; - } - static get GREEN() { - return 0x2; - } - static get BLUE() { - return 0x4; - } - static get ALPHA() { - return 0x8; - } - static get ALL() { - return 0xF; - } - } - - /** - * @param {string | null} label - * @param {InnerGPUDevice} device - * @param {number} rid - * @returns {GPUCommandEncoder} - */ - function createGPUCommandEncoder(label, device, rid) { - /** @type {GPUCommandEncoder} */ - const encoder = webidl.createBranded(GPUCommandEncoder); - encoder[_label] = label; - encoder[_device] = device; - encoder[_rid] = rid; - encoder[_encoders] = []; - return encoder; - } - class GPUCommandEncoder { - /** @type {InnerGPUDevice} */ - [_device]; - /** @type {number | undefined} */ - [_rid]; - /** @type {WeakRef[]} */ - [_encoders]; - - [_cleanup]() { - const encoders = this[_encoders]; - while (encoders.length > 0) { - const encoder = ArrayPrototypePop(encoders)?.deref(); - if (encoder) { - encoder[_cleanup](); - } - } - const rid = this[_rid]; - if (rid !== undefined) { - core.close(rid); - /** @type {number | undefined} */ - this[_rid] = undefined; - } - } - - constructor() { - webidl.illegalConstructor(); - } - - /** - * @param {GPURenderPassDescriptor} descriptor - * @return {GPURenderPassEncoder} - */ - beginRenderPass(descriptor) { - webidl.assertBranded(this, GPUCommandEncoderPrototype); - const prefix = - "Failed to execute 'beginRenderPass' on 'GPUCommandEncoder'"; - webidl.requiredArguments(arguments.length, 1, { prefix }); - descriptor = webidl.converters.GPURenderPassDescriptor(descriptor, { - prefix, - context: "Argument 1", - }); - const device = assertDevice(this, { prefix, context: "this" }); - const commandEncoderRid = assertResource(this, { - prefix, - context: "this", - }); - - if (this[_rid] === undefined) { - throw new DOMException( - "Failed to execute 'beginRenderPass' on 'GPUCommandEncoder': already consumed", - "OperationError", - ); - } - - let depthStencilAttachment; - if (descriptor.depthStencilAttachment) { - const view = assertResource(descriptor.depthStencilAttachment.view, { - prefix, - context: "texture view for depth stencil attachment", - }); - assertDeviceMatch( - device, - descriptor.depthStencilAttachment.view[_texture], - { - prefix, - resourceContext: "texture view for depth stencil attachment", - selfContext: "this", - }, - ); - - depthStencilAttachment = { - ...descriptor.depthStencilAttachment, - view, - }; - } - const colorAttachments = ArrayPrototypeMap( - descriptor.colorAttachments, - (colorAttachment, i) => { - const context = `color attachment ${i + 1}`; - const view = assertResource(colorAttachment.view, { - prefix, - context: `texture view for ${context}`, - }); - assertResource(colorAttachment.view[_texture], { - prefix, - context: `texture backing texture view for ${context}`, - }); - assertDeviceMatch( - device, - colorAttachment.view[_texture], - { - prefix, - resourceContext: `texture view for ${context}`, - selfContext: "this", - }, - ); - let resolveTarget; - if (colorAttachment.resolveTarget) { - resolveTarget = assertResource( - colorAttachment.resolveTarget, - { - prefix, - context: `resolve target texture view for ${context}`, - }, - ); - assertResource(colorAttachment.resolveTarget[_texture], { - prefix, - context: - `texture backing resolve target texture view for ${context}`, - }); - assertDeviceMatch( - device, - colorAttachment.resolveTarget[_texture], - { - prefix, - resourceContext: `resolve target texture view for ${context}`, - selfContext: "this", - }, - ); - } - return { - view: view, - resolveTarget, - storeOp: colorAttachment.storeOp, - loadOp: colorAttachment.loadOp, - clearValue: normalizeGPUColor(colorAttachment.clearValue), - }; - }, - ); - - const { rid } = ops.op_webgpu_command_encoder_begin_render_pass( - commandEncoderRid, - descriptor.label, - colorAttachments, - depthStencilAttachment, - ); - - const renderPassEncoder = createGPURenderPassEncoder( - descriptor.label, - this, - rid, - ); - ArrayPrototypePush(this[_encoders], new WeakRef(renderPassEncoder)); - return renderPassEncoder; - } - - /** - * @param {GPUComputePassDescriptor} descriptor - */ - beginComputePass(descriptor = {}) { - webidl.assertBranded(this, GPUCommandEncoderPrototype); - const prefix = - "Failed to execute 'beginComputePass' on 'GPUCommandEncoder'"; - descriptor = webidl.converters.GPUComputePassDescriptor(descriptor, { - prefix, - context: "Argument 1", - }); - - assertDevice(this, { prefix, context: "this" }); - const commandEncoderRid = assertResource(this, { - prefix, - context: "this", - }); - - const { rid } = ops.op_webgpu_command_encoder_begin_compute_pass( - commandEncoderRid, - descriptor.label, - ); - - const computePassEncoder = createGPUComputePassEncoder( - descriptor.label, - this, - rid, - ); - ArrayPrototypePush(this[_encoders], new WeakRef(computePassEncoder)); - return computePassEncoder; - } - - /** - * @param {GPUBuffer} source - * @param {number} sourceOffset - * @param {GPUBuffer} destination - * @param {number} destinationOffset - * @param {number} size - */ - copyBufferToBuffer( - source, - sourceOffset, - destination, - destinationOffset, - size, - ) { - webidl.assertBranded(this, GPUCommandEncoderPrototype); - const prefix = - "Failed to execute 'copyBufferToBuffer' on 'GPUCommandEncoder'"; - webidl.requiredArguments(arguments.length, 5, { prefix }); - source = webidl.converters.GPUBuffer(source, { - prefix, - context: "Argument 1", - }); - sourceOffset = webidl.converters.GPUSize64(sourceOffset, { - prefix, - context: "Argument 2", - }); - destination = webidl.converters.GPUBuffer(destination, { - prefix, - context: "Argument 3", - }); - destinationOffset = webidl.converters.GPUSize64(destinationOffset, { - prefix, - context: "Argument 4", - }); - size = webidl.converters.GPUSize64(size, { - prefix, - context: "Argument 5", - }); - const device = assertDevice(this, { prefix, context: "this" }); - const commandEncoderRid = assertResource(this, { - prefix, - context: "this", - }); - const sourceRid = assertResource(source, { - prefix, - context: "Argument 1", - }); - assertDeviceMatch(device, source, { - prefix, - resourceContext: "Argument 1", - selfContext: "this", - }); - const destinationRid = assertResource(destination, { - prefix, - context: "Argument 3", - }); - assertDeviceMatch(device, destination, { - prefix, - resourceContext: "Argument 3", - selfContext: "this", - }); - - const { err } = ops.op_webgpu_command_encoder_copy_buffer_to_buffer( - commandEncoderRid, - sourceRid, - sourceOffset, - destinationRid, - destinationOffset, - size, - ); - device.pushError(err); - } - - /** - * @param {GPUImageCopyBuffer} source - * @param {GPUImageCopyTexture} destination - * @param {GPUExtent3D} copySize - */ - copyBufferToTexture(source, destination, copySize) { - webidl.assertBranded(this, GPUCommandEncoderPrototype); - const prefix = - "Failed to execute 'copyBufferToTexture' on 'GPUCommandEncoder'"; - webidl.requiredArguments(arguments.length, 3, { prefix }); - source = webidl.converters.GPUImageCopyBuffer(source, { - prefix, - context: "Argument 1", - }); - destination = webidl.converters.GPUImageCopyTexture(destination, { - prefix, - context: "Argument 2", - }); - copySize = webidl.converters.GPUExtent3D(copySize, { - prefix, - context: "Argument 3", - }); - const device = assertDevice(this, { prefix, context: "this" }); - const commandEncoderRid = assertResource(this, { - prefix, - context: "this", - }); - const sourceBufferRid = assertResource(source.buffer, { - prefix, - context: "source in Argument 1", - }); - assertDeviceMatch(device, source.buffer, { - prefix, - resourceContext: "source in Argument 1", - selfContext: "this", - }); - const destinationTextureRid = assertResource(destination.texture, { - prefix, - context: "texture in Argument 2", - }); - assertDeviceMatch(device, destination.texture, { - prefix, - resourceContext: "texture in Argument 2", - selfContext: "this", - }); - - const { err } = ops.op_webgpu_command_encoder_copy_buffer_to_texture( - commandEncoderRid, - { - ...source, - buffer: sourceBufferRid, - }, - { - texture: destinationTextureRid, - mipLevel: destination.mipLevel, - origin: destination.origin - ? normalizeGPUOrigin3D(destination.origin) - : undefined, - aspect: destination.aspect, - }, - normalizeGPUExtent3D(copySize), - ); - device.pushError(err); - } - - /** - * @param {GPUImageCopyTexture} source - * @param {GPUImageCopyBuffer} destination - * @param {GPUExtent3D} copySize - */ - copyTextureToBuffer(source, destination, copySize) { - webidl.assertBranded(this, GPUCommandEncoderPrototype); - const prefix = - "Failed to execute 'copyTextureToBuffer' on 'GPUCommandEncoder'"; - webidl.requiredArguments(arguments.length, 3, { prefix }); - source = webidl.converters.GPUImageCopyTexture(source, { - prefix, - context: "Argument 1", - }); - destination = webidl.converters.GPUImageCopyBuffer(destination, { - prefix, - context: "Argument 2", - }); - copySize = webidl.converters.GPUExtent3D(copySize, { - prefix, - context: "Argument 3", - }); - const device = assertDevice(this, { prefix, context: "this" }); - const commandEncoderRid = assertResource(this, { - prefix, - context: "this", - }); - const sourceTextureRid = assertResource(source.texture, { - prefix, - context: "texture in Argument 1", - }); - assertDeviceMatch(device, source.texture, { - prefix, - resourceContext: "texture in Argument 1", - selfContext: "this", - }); - const destinationBufferRid = assertResource(destination.buffer, { - prefix, - context: "buffer in Argument 2", - }); - assertDeviceMatch(device, destination.buffer, { - prefix, - resourceContext: "buffer in Argument 2", - selfContext: "this", - }); - const { err } = ops.op_webgpu_command_encoder_copy_texture_to_buffer( - commandEncoderRid, - { - texture: sourceTextureRid, - mipLevel: source.mipLevel, - origin: source.origin - ? normalizeGPUOrigin3D(source.origin) - : undefined, - aspect: source.aspect, - }, - { - ...destination, - buffer: destinationBufferRid, - }, - normalizeGPUExtent3D(copySize), - ); - device.pushError(err); - } - - /** - * @param {GPUImageCopyTexture} source - * @param {GPUImageCopyTexture} destination - * @param {GPUExtent3D} copySize - */ - copyTextureToTexture(source, destination, copySize) { - webidl.assertBranded(this, GPUCommandEncoderPrototype); - const prefix = - "Failed to execute 'copyTextureToTexture' on 'GPUCommandEncoder'"; - webidl.requiredArguments(arguments.length, 3, { prefix }); - source = webidl.converters.GPUImageCopyTexture(source, { - prefix, - context: "Argument 1", - }); - destination = webidl.converters.GPUImageCopyTexture(destination, { - prefix, - context: "Argument 2", - }); - copySize = webidl.converters.GPUExtent3D(copySize, { - prefix, - context: "Argument 3", - }); - const device = assertDevice(this, { prefix, context: "this" }); - const commandEncoderRid = assertResource(this, { - prefix, - context: "this", - }); - const sourceTextureRid = assertResource(source.texture, { - prefix, - context: "texture in Argument 1", - }); - assertDeviceMatch(device, source.texture, { - prefix, - resourceContext: "texture in Argument 1", - selfContext: "this", - }); - const destinationTextureRid = assertResource(destination.texture, { - prefix, - context: "texture in Argument 2", - }); - assertDeviceMatch(device, destination.texture, { - prefix, - resourceContext: "texture in Argument 2", - selfContext: "this", - }); - const { err } = ops.op_webgpu_command_encoder_copy_texture_to_texture( - commandEncoderRid, - { - texture: sourceTextureRid, - mipLevel: source.mipLevel, - origin: source.origin - ? normalizeGPUOrigin3D(source.origin) - : undefined, - aspect: source.aspect, - }, - { - texture: destinationTextureRid, - mipLevel: destination.mipLevel, - origin: destination.origin - ? normalizeGPUOrigin3D(destination.origin) - : undefined, - aspect: source.aspect, - }, - normalizeGPUExtent3D(copySize), - ); - device.pushError(err); - } - - /** - * @param {GPUBuffer} buffer - * @param {GPUSize64} offset - * @param {GPUSize64} size - */ - clearBuffer(buffer, offset = 0, size = undefined) { - webidl.assertBranded(this, GPUCommandEncoderPrototype); - const prefix = "Failed to execute 'clearBuffer' on 'GPUCommandEncoder'"; - webidl.requiredArguments(arguments.length, 3, { prefix }); - buffer = webidl.converters.GPUBuffer(buffer, { - prefix, - context: "Argument 1", - }); - offset = webidl.converters.GPUSize64(offset, { - prefix, - context: "Argument 2", - }); - size = webidl.converters.GPUSize64(size, { - prefix, - context: "Argument 3", - }); - const device = assertDevice(this, { prefix, context: "this" }); - const commandEncoderRid = assertResource(this, { - prefix, - context: "this", - }); - const bufferRid = assertResource(buffer, { - prefix, - context: "Argument 1", - }); - const { err } = ops.op_webgpu_command_encoder_clear_buffer( - commandEncoderRid, - bufferRid, - offset, - size, - ); - device.pushError(err); - } - - /** - * @param {string} groupLabel - */ - pushDebugGroup(groupLabel) { - webidl.assertBranded(this, GPUCommandEncoderPrototype); - const prefix = - "Failed to execute 'pushDebugGroup' on 'GPUCommandEncoder'"; - webidl.requiredArguments(arguments.length, 1, { prefix }); - groupLabel = webidl.converters.USVString(groupLabel, { - prefix, - context: "Argument 1", - }); - const device = assertDevice(this, { prefix, context: "this" }); - const commandEncoderRid = assertResource(this, { - prefix, - context: "this", - }); - const { err } = ops.op_webgpu_command_encoder_push_debug_group( - commandEncoderRid, - groupLabel, - ); - device.pushError(err); - } - - popDebugGroup() { - webidl.assertBranded(this, GPUCommandEncoderPrototype); - const prefix = "Failed to execute 'popDebugGroup' on 'GPUCommandEncoder'"; - const device = assertDevice(this, { prefix, context: "this" }); - const commandEncoderRid = assertResource(this, { - prefix, - context: "this", - }); - const { err } = ops.op_webgpu_command_encoder_pop_debug_group( - commandEncoderRid, - ); - device.pushError(err); - } - - /** - * @param {string} markerLabel - */ - insertDebugMarker(markerLabel) { - webidl.assertBranded(this, GPUCommandEncoderPrototype); - const prefix = - "Failed to execute 'insertDebugMarker' on 'GPUCommandEncoder'"; - webidl.requiredArguments(arguments.length, 1, { prefix }); - markerLabel = webidl.converters.USVString(markerLabel, { - prefix, - context: "Argument 1", - }); - const device = assertDevice(this, { prefix, context: "this" }); - const commandEncoderRid = assertResource(this, { - prefix, - context: "this", - }); - const { err } = ops.op_webgpu_command_encoder_insert_debug_marker( - commandEncoderRid, - markerLabel, - ); - device.pushError(err); - } - - /** - * @param {GPUQuerySet} querySet - * @param {number} queryIndex - */ - writeTimestamp(querySet, queryIndex) { - webidl.assertBranded(this, GPUCommandEncoderPrototype); - const prefix = - "Failed to execute 'writeTimestamp' on 'GPUCommandEncoder'"; - webidl.requiredArguments(arguments.length, 2, { prefix }); - querySet = webidl.converters.GPUQuerySet(querySet, { - prefix, - context: "Argument 1", - }); - queryIndex = webidl.converters.GPUSize32(queryIndex, { - prefix, - context: "Argument 2", - }); - const device = assertDevice(this, { prefix, context: "this" }); - const commandEncoderRid = assertResource(this, { - prefix, - context: "this", - }); - const querySetRid = assertResource(querySet, { - prefix, - context: "Argument 1", - }); - assertDeviceMatch(device, querySet, { - prefix, - resourceContext: "Argument 1", - selfContext: "this", - }); - const { err } = ops.op_webgpu_command_encoder_write_timestamp( - commandEncoderRid, - querySetRid, - queryIndex, - ); - device.pushError(err); - } - - /** - * @param {GPUQuerySet} querySet - * @param {number} firstQuery - * @param {number} queryCount - * @param {GPUBuffer} destination - * @param {number} destinationOffset - */ - resolveQuerySet( - querySet, - firstQuery, - queryCount, - destination, - destinationOffset, - ) { - webidl.assertBranded(this, GPUCommandEncoderPrototype); - const prefix = - "Failed to execute 'resolveQuerySet' on 'GPUCommandEncoder'"; - webidl.requiredArguments(arguments.length, 5, { prefix }); - querySet = webidl.converters.GPUQuerySet(querySet, { - prefix, - context: "Argument 1", - }); - firstQuery = webidl.converters.GPUSize32(firstQuery, { - prefix, - context: "Argument 2", - }); - queryCount = webidl.converters.GPUSize32(queryCount, { - prefix, - context: "Argument 3", - }); - destination = webidl.converters.GPUBuffer(destination, { - prefix, - context: "Argument 4", - }); - destinationOffset = webidl.converters.GPUSize64(destinationOffset, { - prefix, - context: "Argument 5", - }); - const device = assertDevice(this, { prefix, context: "this" }); - const commandEncoderRid = assertResource(this, { - prefix, - context: "this", - }); - const querySetRid = assertResource(querySet, { - prefix, - context: "Argument 1", - }); - assertDeviceMatch(device, querySet, { - prefix, - resourceContext: "Argument 1", - selfContext: "this", - }); - const destinationRid = assertResource(destination, { - prefix, - context: "Argument 3", - }); - assertDeviceMatch(device, destination, { - prefix, - resourceContext: "Argument 3", - selfContext: "this", - }); - const { err } = ops.op_webgpu_command_encoder_resolve_query_set( - commandEncoderRid, - querySetRid, - firstQuery, - queryCount, - destinationRid, - destinationOffset, - ); - device.pushError(err); - } - - /** - * @param {GPUCommandBufferDescriptor} descriptor - * @returns {GPUCommandBuffer} - */ - finish(descriptor = {}) { - webidl.assertBranded(this, GPUCommandEncoderPrototype); - const prefix = "Failed to execute 'finish' on 'GPUCommandEncoder'"; - descriptor = webidl.converters.GPUCommandBufferDescriptor(descriptor, { - prefix, - context: "Argument 1", - }); - const device = assertDevice(this, { prefix, context: "this" }); - const commandEncoderRid = assertResource(this, { - prefix, - context: "this", - }); - const { rid, err } = ops.op_webgpu_command_encoder_finish( - commandEncoderRid, - descriptor.label, - ); - device.pushError(err); - /** @type {number | undefined} */ - this[_rid] = undefined; - - const commandBuffer = createGPUCommandBuffer( - descriptor.label, - device, - rid, - ); - device.trackResource(commandBuffer); - return commandBuffer; - } - - [SymbolFor("Deno.privateCustomInspect")](inspect) { - return `${this.constructor.name} ${ - inspect({ - label: this.label, - }) - }`; - } - } - GPUObjectBaseMixin("GPUCommandEncoder", GPUCommandEncoder); - const GPUCommandEncoderPrototype = GPUCommandEncoder.prototype; - - /** - * @param {string | null} label - * @param {GPUCommandEncoder} encoder - * @param {number} rid - * @returns {GPURenderPassEncoder} - */ - function createGPURenderPassEncoder(label, encoder, rid) { - /** @type {GPURenderPassEncoder} */ - const passEncoder = webidl.createBranded(GPURenderPassEncoder); - passEncoder[_label] = label; - passEncoder[_encoder] = encoder; - passEncoder[_rid] = rid; - return passEncoder; - } - - class GPURenderPassEncoder { - /** @type {GPUCommandEncoder} */ - [_encoder]; - /** @type {number | undefined} */ - [_rid]; - - [_cleanup]() { - const rid = this[_rid]; - if (rid !== undefined) { - core.close(rid); - /** @type {number | undefined} */ - this[_rid] = undefined; - } - } - - constructor() { - webidl.illegalConstructor(); - } - - /** - * @param {number} x - * @param {number} y - * @param {number} width - * @param {number} height - * @param {number} minDepth - * @param {number} maxDepth - */ - setViewport(x, y, width, height, minDepth, maxDepth) { - webidl.assertBranded(this, GPURenderPassEncoderPrototype); - const prefix = - "Failed to execute 'setViewport' on 'GPUComputePassEncoder'"; - webidl.requiredArguments(arguments.length, 6, { prefix }); - x = webidl.converters.float(x, { prefix, context: "Argument 1" }); - y = webidl.converters.float(y, { prefix, context: "Argument 2" }); - width = webidl.converters.float(width, { prefix, context: "Argument 3" }); - height = webidl.converters.float(height, { - prefix, - context: "Argument 4", - }); - minDepth = webidl.converters.float(minDepth, { - prefix, - context: "Argument 5", - }); - maxDepth = webidl.converters.float(maxDepth, { - prefix, - context: "Argument 6", - }); - assertDevice(this[_encoder], { - prefix, - context: "encoder referenced by this", - }); - assertResource(this[_encoder], { - prefix, - context: "encoder referenced by this", - }); - const renderPassRid = assertResource(this, { prefix, context: "this" }); - ops.op_webgpu_render_pass_set_viewport({ - renderPassRid, - x, - y, - width, - height, - minDepth, - maxDepth, - }); - } - - /** - * @param {number} x - * @param {number} y - * @param {number} width - * @param {number} height - */ - setScissorRect(x, y, width, height) { - webidl.assertBranded(this, GPURenderPassEncoderPrototype); - const prefix = - "Failed to execute 'setScissorRect' on 'GPUComputePassEncoder'"; - webidl.requiredArguments(arguments.length, 4, { prefix }); - x = webidl.converters.GPUIntegerCoordinate(x, { - prefix, - context: "Argument 1", - }); - y = webidl.converters.GPUIntegerCoordinate(y, { - prefix, - context: "Argument 2", - }); - width = webidl.converters.GPUIntegerCoordinate(width, { - prefix, - context: "Argument 3", - }); - height = webidl.converters.GPUIntegerCoordinate(height, { - prefix, - context: "Argument 4", - }); - assertDevice(this[_encoder], { - prefix, - context: "encoder referenced by this", - }); - assertResource(this[_encoder], { - prefix, - context: "encoder referenced by this", - }); - const renderPassRid = assertResource(this, { prefix, context: "this" }); - ops.op_webgpu_render_pass_set_scissor_rect( - renderPassRid, - x, - y, - width, - height, - ); - } - - /** - * @param {GPUColor} color - */ - setBlendConstant(color) { - webidl.assertBranded(this, GPURenderPassEncoderPrototype); - const prefix = - "Failed to execute 'setBlendConstant' on 'GPUComputePassEncoder'"; - webidl.requiredArguments(arguments.length, 1, { prefix }); - color = webidl.converters.GPUColor(color, { - prefix, - context: "Argument 1", - }); - assertDevice(this[_encoder], { - prefix, - context: "encoder referenced by this", - }); - assertResource(this[_encoder], { - prefix, - context: "encoder referenced by this", - }); - const renderPassRid = assertResource(this, { prefix, context: "this" }); - ops.op_webgpu_render_pass_set_blend_constant( - renderPassRid, - normalizeGPUColor(color), - ); - } - - /** - * @param {number} reference - */ - setStencilReference(reference) { - webidl.assertBranded(this, GPURenderPassEncoderPrototype); - const prefix = - "Failed to execute 'setStencilReference' on 'GPUComputePassEncoder'"; - webidl.requiredArguments(arguments.length, 1, { prefix }); - reference = webidl.converters.GPUStencilValue(reference, { - prefix, - context: "Argument 1", - }); - assertDevice(this[_encoder], { - prefix, - context: "encoder referenced by this", - }); - assertResource(this[_encoder], { - prefix, - context: "encoder referenced by this", - }); - const renderPassRid = assertResource(this, { prefix, context: "this" }); - ops.op_webgpu_render_pass_set_stencil_reference( - renderPassRid, - reference, - ); - } - - beginOcclusionQuery(_queryIndex) { - throw new Error("Not yet implemented"); - } - - endOcclusionQuery() { - throw new Error("Not yet implemented"); - } - - /** - * @param {GPUQuerySet} querySet - * @param {number} queryIndex - */ - beginPipelineStatisticsQuery(querySet, queryIndex) { - webidl.assertBranded(this, GPURenderPassEncoderPrototype); - const prefix = - "Failed to execute 'beginPipelineStatisticsQuery' on 'GPURenderPassEncoder'"; - webidl.requiredArguments(arguments.length, 2, { prefix }); - querySet = webidl.converters.GPUQuerySet(querySet, { - prefix, - context: "Argument 1", - }); - queryIndex = webidl.converters.GPUSize32(queryIndex, { - prefix, - context: "Argument 2", - }); - const device = assertDevice(this[_encoder], { - prefix, - context: "encoder referenced by this", - }); - assertResource(this[_encoder], { - prefix, - context: "encoder referenced by this", - }); - const renderPassRid = assertResource(this, { prefix, context: "this" }); - const querySetRid = assertResource(querySet, { - prefix, - context: "Argument 1", - }); - assertDeviceMatch(device, querySet, { - prefix, - resourceContext: "Argument 1", - selfContext: "this", - }); - ops.op_webgpu_render_pass_begin_pipeline_statistics_query( - renderPassRid, - querySetRid, - queryIndex, - ); - } - - endPipelineStatisticsQuery() { - webidl.assertBranded(this, GPURenderPassEncoderPrototype); - const prefix = - "Failed to execute 'endPipelineStatisticsQuery' on 'GPURenderPassEncoder'"; - assertDevice(this[_encoder], { - prefix, - context: "encoder referenced by this", - }); - assertResource(this[_encoder], { - prefix, - context: "encoder referenced by this", - }); - const renderPassRid = assertResource(this, { prefix, context: "this" }); - ops.op_webgpu_render_pass_end_pipeline_statistics_query(renderPassRid); - } - - /** - * @param {GPUQuerySet} querySet - * @param {number} queryIndex - */ - writeTimestamp(querySet, queryIndex) { - webidl.assertBranded(this, GPURenderPassEncoderPrototype); - const prefix = - "Failed to execute 'writeTimestamp' on 'GPURenderPassEncoder'"; - webidl.requiredArguments(arguments.length, 2, { prefix }); - querySet = webidl.converters.GPUQuerySet(querySet, { - prefix, - context: "Argument 1", - }); - queryIndex = webidl.converters.GPUSize32(queryIndex, { - prefix, - context: "Argument 2", - }); - const device = assertDevice(this[_encoder], { - prefix, - context: "encoder referenced by this", - }); - assertResource(this[_encoder], { - prefix, - context: "encoder referenced by this", - }); - const renderPassRid = assertResource(this, { prefix, context: "this" }); - const querySetRid = assertResource(querySet, { - prefix, - context: "Argument 1", - }); - assertDeviceMatch(device, querySet, { - prefix, - resourceContext: "Argument 1", - selfContext: "this", - }); - ops.op_webgpu_render_pass_write_timestamp( - renderPassRid, - querySetRid, - queryIndex, - ); - } - - /** - * @param {GPURenderBundle[]} bundles - */ - executeBundles(bundles) { - webidl.assertBranded(this, GPURenderPassEncoderPrototype); - const prefix = - "Failed to execute 'executeBundles' on 'GPURenderPassEncoder'"; - webidl.requiredArguments(arguments.length, 1, { prefix }); - bundles = webidl.converters["sequence"](bundles, { - prefix, - context: "Argument 1", - }); - const device = assertDevice(this[_encoder], { - prefix, - context: "encoder referenced by this", - }); - assertResource(this[_encoder], { - prefix, - context: "encoder referenced by this", - }); - const renderPassRid = assertResource(this, { prefix, context: "this" }); - const bundleRids = ArrayPrototypeMap(bundles, (bundle, i) => { - const context = `bundle ${i + 1}`; - const rid = assertResource(bundle, { prefix, context }); - assertDeviceMatch(device, bundle, { - prefix, - resourceContext: context, - selfContext: "this", - }); - return rid; - }); - ops.op_webgpu_render_pass_execute_bundles(renderPassRid, bundleRids); - } - - end() { - webidl.assertBranded(this, GPURenderPassEncoderPrototype); - const prefix = "Failed to execute 'end' on 'GPURenderPassEncoder'"; - const device = assertDevice(this[_encoder], { - prefix, - context: "encoder referenced by this", - }); - const commandEncoderRid = assertResource(this[_encoder], { - prefix, - context: "encoder referenced by this", - }); - const renderPassRid = assertResource(this, { prefix, context: "this" }); - const { err } = ops.op_webgpu_render_pass_end( - commandEncoderRid, - renderPassRid, - ); - device.pushError(err); - this[_rid] = undefined; - } - - // TODO(lucacasonato): has an overload - setBindGroup( - index, - bindGroup, - dynamicOffsetsData, - dynamicOffsetsDataStart, - dynamicOffsetsDataLength, - ) { - webidl.assertBranded(this, GPURenderPassEncoderPrototype); - const prefix = - "Failed to execute 'setBindGroup' on 'GPURenderPassEncoder'"; - const device = assertDevice(this[_encoder], { - prefix, - context: "encoder referenced by this", - }); - assertResource(this[_encoder], { - prefix, - context: "encoder referenced by this", - }); - const renderPassRid = assertResource(this, { prefix, context: "this" }); - const bindGroupRid = assertResource(bindGroup, { - prefix, - context: "Argument 2", - }); - assertDeviceMatch(device, bindGroup, { - prefix, - resourceContext: "Argument 2", - selfContext: "this", - }); - if ( - !(ObjectPrototypeIsPrototypeOf( - Uint32ArrayPrototype, - dynamicOffsetsData, - )) - ) { - dynamicOffsetsData = new Uint32Array(dynamicOffsetsData ?? []); - dynamicOffsetsDataStart = 0; - dynamicOffsetsDataLength = dynamicOffsetsData.length; - } - ops.op_webgpu_render_pass_set_bind_group( - renderPassRid, - index, - bindGroupRid, - dynamicOffsetsData, - dynamicOffsetsDataStart, - dynamicOffsetsDataLength, - ); - } - - /** - * @param {string} groupLabel - */ - pushDebugGroup(groupLabel) { - webidl.assertBranded(this, GPURenderPassEncoderPrototype); - const prefix = - "Failed to execute 'pushDebugGroup' on 'GPURenderPassEncoder'"; - webidl.requiredArguments(arguments.length, 1, { prefix }); - groupLabel = webidl.converters.USVString(groupLabel, { - prefix, - context: "Argument 1", - }); - assertDevice(this[_encoder], { - prefix, - context: "encoder referenced by this", - }); - assertResource(this[_encoder], { - prefix, - context: "encoder referenced by this", - }); - const renderPassRid = assertResource(this, { prefix, context: "this" }); - ops.op_webgpu_render_pass_push_debug_group(renderPassRid, groupLabel); - } - - popDebugGroup() { - webidl.assertBranded(this, GPURenderPassEncoderPrototype); - const prefix = - "Failed to execute 'popDebugGroup' on 'GPURenderPassEncoder'"; - assertDevice(this[_encoder], { - prefix, - context: "encoder referenced by this", - }); - assertResource(this[_encoder], { - prefix, - context: "encoder referenced by this", - }); - const renderPassRid = assertResource(this, { prefix, context: "this" }); - ops.op_webgpu_render_pass_pop_debug_group(renderPassRid); - } - - /** - * @param {string} markerLabel - */ - insertDebugMarker(markerLabel) { - webidl.assertBranded(this, GPURenderPassEncoderPrototype); - const prefix = - "Failed to execute 'insertDebugMarker' on 'GPURenderPassEncoder'"; - webidl.requiredArguments(arguments.length, 1, { prefix }); - markerLabel = webidl.converters.USVString(markerLabel, { - prefix, - context: "Argument 1", - }); - assertDevice(this[_encoder], { - prefix, - context: "encoder referenced by this", - }); - assertResource(this[_encoder], { - prefix, - context: "encoder referenced by this", - }); - const renderPassRid = assertResource(this, { prefix, context: "this" }); - ops.op_webgpu_render_pass_insert_debug_marker(renderPassRid, markerLabel); - } - - /** - * @param {GPURenderPipeline} pipeline - */ - setPipeline(pipeline) { - webidl.assertBranded(this, GPURenderPassEncoderPrototype); - const prefix = - "Failed to execute 'setPipeline' on 'GPURenderPassEncoder'"; - webidl.requiredArguments(arguments.length, 1, { prefix }); - pipeline = webidl.converters.GPURenderPipeline(pipeline, { - prefix, - context: "Argument 1", - }); - const device = assertDevice(this[_encoder], { - prefix, - context: "encoder referenced by this", - }); - assertResource(this[_encoder], { - prefix, - context: "encoder referenced by this", - }); - const renderPassRid = assertResource(this, { prefix, context: "this" }); - const pipelineRid = assertResource(pipeline, { - prefix, - context: "Argument 1", - }); - assertDeviceMatch(device, pipeline, { - prefix, - resourceContext: "Argument 1", - selfContext: "this", - }); - ops.op_webgpu_render_pass_set_pipeline(renderPassRid, pipelineRid); - } - - /** - * @param {GPUBuffer} buffer - * @param {GPUIndexFormat} indexFormat - * @param {number} offset - * @param {number} size - */ - setIndexBuffer(buffer, indexFormat, offset = 0, size) { - webidl.assertBranded(this, GPURenderPassEncoderPrototype); - const prefix = - "Failed to execute 'setIndexBuffer' on 'GPURenderPassEncoder'"; - webidl.requiredArguments(arguments.length, 2, { prefix }); - buffer = webidl.converters.GPUBuffer(buffer, { - prefix, - context: "Argument 1", - }); - indexFormat = webidl.converters.GPUIndexFormat(indexFormat, { - prefix, - context: "Argument 2", - }); - offset = webidl.converters.GPUSize64(offset, { - prefix, - context: "Argument 3", - }); - if (size !== undefined) { - size = webidl.converters.GPUSize64(size, { - prefix, - context: "Argument 4", - }); - } - const device = assertDevice(this[_encoder], { - prefix, - context: "encoder referenced by this", - }); - assertResource(this[_encoder], { - prefix, - context: "encoder referenced by this", - }); - const renderPassRid = assertResource(this, { prefix, context: "this" }); - const bufferRid = assertResource(buffer, { - prefix, - context: "Argument 1", - }); - assertDeviceMatch(device, buffer, { - prefix, - resourceContext: "Argument 1", - selfContext: "this", - }); - ops.op_webgpu_render_pass_set_index_buffer( - renderPassRid, - bufferRid, - indexFormat, - offset, - size, - ); - } - - /** - * @param {number} slot - * @param {GPUBuffer} buffer - * @param {number} offset - * @param {number} size - */ - setVertexBuffer(slot, buffer, offset = 0, size) { - webidl.assertBranded(this, GPURenderPassEncoderPrototype); - const prefix = - "Failed to execute 'setVertexBuffer' on 'GPURenderPassEncoder'"; - webidl.requiredArguments(arguments.length, 2, { prefix }); - slot = webidl.converters.GPUSize32(slot, { - prefix, - context: "Argument 2", - }); - buffer = webidl.converters.GPUBuffer(buffer, { - prefix, - context: "Argument 2", - }); - offset = webidl.converters.GPUSize64(offset, { - prefix, - context: "Argument 3", - }); - if (size !== undefined) { - size = webidl.converters.GPUSize64(size, { - prefix, - context: "Argument 4", - }); - } - const device = assertDevice(this[_encoder], { - prefix, - context: "encoder referenced by this", - }); - assertResource(this[_encoder], { - prefix, - context: "encoder referenced by this", - }); - const renderPassRid = assertResource(this, { prefix, context: "this" }); - const bufferRid = assertResource(buffer, { - prefix, - context: "Argument 2", - }); - assertDeviceMatch(device, buffer, { - prefix, - resourceContext: "Argument 2", - selfContext: "this", - }); - ops.op_webgpu_render_pass_set_vertex_buffer( - renderPassRid, - slot, - bufferRid, - offset, - size, - ); - } - - /** - * @param {number} vertexCount - * @param {number} instanceCount - * @param {number} firstVertex - * @param {number} firstInstance - */ - draw(vertexCount, instanceCount = 1, firstVertex = 0, firstInstance = 0) { - webidl.assertBranded(this, GPURenderPassEncoderPrototype); - const prefix = "Failed to execute 'draw' on 'GPURenderPassEncoder'"; - webidl.requiredArguments(arguments.length, 1, { prefix }); - vertexCount = webidl.converters.GPUSize32(vertexCount, { - prefix, - context: "Argument 1", - }); - instanceCount = webidl.converters.GPUSize32(instanceCount, { - prefix, - context: "Argument 2", - }); - firstVertex = webidl.converters.GPUSize32(firstVertex, { - prefix, - context: "Argument 3", - }); - firstInstance = webidl.converters.GPUSize32(firstInstance, { - prefix, - context: "Argument 4", - }); - assertDevice(this[_encoder], { - prefix, - context: "encoder referenced by this", - }); - assertResource(this[_encoder], { - prefix, - context: "encoder referenced by this", - }); - const renderPassRid = assertResource(this, { prefix, context: "this" }); - ops.op_webgpu_render_pass_draw( - renderPassRid, - vertexCount, - instanceCount, - firstVertex, - firstInstance, - ); - } - - /** - * @param {number} indexCount - * @param {number} instanceCount - * @param {number} firstIndex - * @param {number} baseVertex - * @param {number} firstInstance - */ - drawIndexed( - indexCount, - instanceCount = 1, - firstIndex = 0, - baseVertex = 0, - firstInstance = 0, - ) { - webidl.assertBranded(this, GPURenderPassEncoderPrototype); - const prefix = - "Failed to execute 'drawIndexed' on 'GPURenderPassEncoder'"; - webidl.requiredArguments(arguments.length, 1, { prefix }); - indexCount = webidl.converters.GPUSize32(indexCount, { - prefix, - context: "Argument 1", - }); - instanceCount = webidl.converters.GPUSize32(instanceCount, { - prefix, - context: "Argument 2", - }); - firstIndex = webidl.converters.GPUSize32(firstIndex, { - prefix, - context: "Argument 3", - }); - baseVertex = webidl.converters.GPUSignedOffset32(baseVertex, { - prefix, - context: "Argument 4", - }); - firstInstance = webidl.converters.GPUSize32(firstInstance, { - prefix, - context: "Argument 5", - }); - assertDevice(this[_encoder], { - prefix, - context: "encoder referenced by this", - }); - assertResource(this[_encoder], { - prefix, - context: "encoder referenced by this", - }); - const renderPassRid = assertResource(this, { prefix, context: "this" }); - ops.op_webgpu_render_pass_draw_indexed( - renderPassRid, - indexCount, - instanceCount, - firstIndex, - baseVertex, - firstInstance, - ); - } - - /** - * @param {GPUBuffer} indirectBuffer - * @param {number} indirectOffset - */ - drawIndirect(indirectBuffer, indirectOffset) { - webidl.assertBranded(this, GPURenderPassEncoderPrototype); - const prefix = - "Failed to execute 'drawIndirect' on 'GPURenderPassEncoder'"; - webidl.requiredArguments(arguments.length, 2, { prefix }); - indirectBuffer = webidl.converters.GPUBuffer(indirectBuffer, { - prefix, - context: "Argument 1", - }); - indirectOffset = webidl.converters.GPUSize64(indirectOffset, { - prefix, - context: "Argument 2", - }); - const device = assertDevice(this[_encoder], { - prefix, - context: "encoder referenced by this", - }); - assertResource(this[_encoder], { - prefix, - context: "encoder referenced by this", - }); - const renderPassRid = assertResource(this, { prefix, context: "this" }); - const indirectBufferRid = assertResource(indirectBuffer, { - prefix, - context: "Argument 1", - }); - assertDeviceMatch(device, indirectBuffer, { - prefix, - resourceContext: "Argument 1", - selfContext: "this", - }); - ops.op_webgpu_render_pass_draw_indirect( - renderPassRid, - indirectBufferRid, - indirectOffset, - ); - } - - /** - * @param {GPUBuffer} indirectBuffer - * @param {number} indirectOffset - */ - drawIndexedIndirect(indirectBuffer, indirectOffset) { - webidl.assertBranded(this, GPURenderPassEncoderPrototype); - const prefix = - "Failed to execute 'drawIndirect' on 'GPURenderPassEncoder'"; - webidl.requiredArguments(arguments.length, 2, { prefix }); - indirectBuffer = webidl.converters.GPUBuffer(indirectBuffer, { - prefix, - context: "Argument 1", - }); - indirectOffset = webidl.converters.GPUSize64(indirectOffset, { - prefix, - context: "Argument 2", - }); - const device = assertDevice(this[_encoder], { - prefix, - context: "encoder referenced by this", - }); - assertResource(this[_encoder], { - prefix, - context: "encoder referenced by this", - }); - const renderPassRid = assertResource(this, { prefix, context: "this" }); - const indirectBufferRid = assertResource(indirectBuffer, { - prefix, - context: "Argument 1", - }); - assertDeviceMatch(device, indirectBuffer, { - prefix, - resourceContext: "Argument 1", - selfContext: "this", - }); - ops.op_webgpu_render_pass_draw_indexed_indirect( - renderPassRid, - indirectBufferRid, - indirectOffset, - ); - } - - [SymbolFor("Deno.privateCustomInspect")](inspect) { - return `${this.constructor.name} ${ - inspect({ - label: this.label, - }) - }`; - } - } - GPUObjectBaseMixin("GPURenderPassEncoder", GPURenderPassEncoder); - const GPURenderPassEncoderPrototype = GPURenderPassEncoder.prototype; - - /** - * @param {string | null} label - * @param {GPUCommandEncoder} encoder - * @param {number} rid - * @returns {GPUComputePassEncoder} - */ - function createGPUComputePassEncoder(label, encoder, rid) { - /** @type {GPUComputePassEncoder} */ - const computePassEncoder = webidl.createBranded(GPUComputePassEncoder); - computePassEncoder[_label] = label; - computePassEncoder[_encoder] = encoder; - computePassEncoder[_rid] = rid; - return computePassEncoder; - } - - class GPUComputePassEncoder { - /** @type {GPUCommandEncoder} */ - [_encoder]; - - /** @type {number | undefined} */ - [_rid]; - - [_cleanup]() { - const rid = this[_rid]; - if (rid !== undefined) { - core.close(rid); - /** @type {number | undefined} */ - this[_rid] = undefined; - } - } - - constructor() { - webidl.illegalConstructor(); - } - - /** - * @param {GPUComputePipeline} pipeline - */ - setPipeline(pipeline) { - webidl.assertBranded(this, GPUComputePassEncoderPrototype); - const prefix = - "Failed to execute 'setPipeline' on 'GPUComputePassEncoder'"; - webidl.requiredArguments(arguments.length, 1, { prefix }); - pipeline = webidl.converters.GPUComputePipeline(pipeline, { - prefix, - context: "Argument 1", - }); - const device = assertDevice(this[_encoder], { - prefix, - context: "encoder referenced by this", - }); - assertResource(this[_encoder], { - prefix, - context: "encoder referenced by this", - }); - const computePassRid = assertResource(this, { prefix, context: "this" }); - const pipelineRid = assertResource(pipeline, { - prefix, - context: "Argument 1", - }); - assertDeviceMatch(device, pipeline, { - prefix, - resourceContext: "Argument 1", - selfContext: "this", - }); - ops.op_webgpu_compute_pass_set_pipeline(computePassRid, pipelineRid); - } - - /** - * @param {number} workgroupCountX - * @param {number} workgroupCountY - * @param {number} workgroupCountZ - */ - dispatchWorkgroups( - workgroupCountX, - workgroupCountY = 1, - workgroupCountZ = 1, - ) { - webidl.assertBranded(this, GPUComputePassEncoderPrototype); - const prefix = - "Failed to execute 'dispatchWorkgroups' on 'GPUComputePassEncoder'"; - webidl.requiredArguments(arguments.length, 1, { prefix }); - workgroupCountX = webidl.converters.GPUSize32(workgroupCountX, { - prefix, - context: "Argument 1", - }); - workgroupCountY = webidl.converters.GPUSize32(workgroupCountY, { - prefix, - context: "Argument 2", - }); - workgroupCountZ = webidl.converters.GPUSize32(workgroupCountZ, { - prefix, - context: "Argument 3", - }); - assertDevice(this[_encoder], { - prefix, - context: "encoder referenced by this", - }); - assertResource(this[_encoder], { - prefix, - context: "encoder referenced by this", - }); - const computePassRid = assertResource(this, { prefix, context: "this" }); - ops.op_webgpu_compute_pass_dispatch_workgroups( - computePassRid, - workgroupCountX, - workgroupCountY, - workgroupCountZ, - ); - } - - /** - * @param {GPUBuffer} indirectBuffer - * @param {number} indirectOffset - */ - dispatchWorkgroupsIndirect(indirectBuffer, indirectOffset) { - webidl.assertBranded(this, GPUComputePassEncoderPrototype); - const prefix = - "Failed to execute 'dispatchWorkgroupsIndirect' on 'GPUComputePassEncoder'"; - webidl.requiredArguments(arguments.length, 2, { prefix }); - indirectBuffer = webidl.converters.GPUBuffer(indirectBuffer, { - prefix, - context: "Argument 1", - }); - indirectOffset = webidl.converters.GPUSize64(indirectOffset, { - prefix, - context: "Argument 2", - }); - const device = assertDevice(this[_encoder], { - prefix, - context: "encoder referenced by this", - }); - assertResource(this[_encoder], { - prefix, - context: "encoder referenced by this", - }); - const computePassRid = assertResource(this, { prefix, context: "this" }); - const indirectBufferRid = assertResource(indirectBuffer, { - prefix, - context: "Argument 1", - }); - assertDeviceMatch(device, indirectBuffer, { - prefix, - resourceContext: "Argument 1", - selfContext: "this", - }); - ops.op_webgpu_compute_pass_dispatch_workgroups_indirect( - computePassRid, - indirectBufferRid, - indirectOffset, - ); - } - - /** - * @param {GPUQuerySet} querySet - * @param {number} queryIndex - */ - beginPipelineStatisticsQuery(querySet, queryIndex) { - webidl.assertBranded(this, GPUComputePassEncoderPrototype); - const prefix = - "Failed to execute 'beginPipelineStatisticsQuery' on 'GPUComputePassEncoder'"; - webidl.requiredArguments(arguments.length, 2, { prefix }); - querySet = webidl.converters.GPUQuerySet(querySet, { - prefix, - context: "Argument 1", - }); - queryIndex = webidl.converters.GPUSize32(queryIndex, { - prefix, - context: "Argument 2", - }); - const device = assertDevice(this[_encoder], { - prefix, - context: "encoder referenced by this", - }); - assertResource(this[_encoder], { - prefix, - context: "encoder referenced by this", - }); - const computePassRid = assertResource(this, { prefix, context: "this" }); - const querySetRid = assertResource(querySet, { - prefix, - context: "Argument 1", - }); - assertDeviceMatch(device, querySet, { - prefix, - resourceContext: "Argument 1", - selfContext: "this", - }); - ops.op_webgpu_compute_pass_begin_pipeline_statistics_query( - computePassRid, - querySetRid, - queryIndex, - ); - } - - endPipelineStatisticsQuery() { - webidl.assertBranded(this, GPUComputePassEncoderPrototype); - const prefix = - "Failed to execute 'endPipelineStatisticsQuery' on 'GPUComputePassEncoder'"; - assertDevice(this[_encoder], { - prefix, - context: "encoder referenced by this", - }); - assertResource(this[_encoder], { - prefix, - context: "encoder referenced by this", - }); - const computePassRid = assertResource(this, { prefix, context: "this" }); - ops.op_webgpu_compute_pass_end_pipeline_statistics_query(computePassRid); - } - - /** - * @param {GPUQuerySet} querySet - * @param {number} queryIndex - */ - writeTimestamp(querySet, queryIndex) { - webidl.assertBranded(this, GPUComputePassEncoderPrototype); - const prefix = - "Failed to execute 'writeTimestamp' on 'GPUComputePassEncoder'"; - webidl.requiredArguments(arguments.length, 2, { prefix }); - querySet = webidl.converters.GPUQuerySet(querySet, { - prefix, - context: "Argument 1", - }); - queryIndex = webidl.converters.GPUSize32(queryIndex, { - prefix, - context: "Argument 2", - }); - const device = assertDevice(this[_encoder], { - prefix, - context: "encoder referenced by this", - }); - assertResource(this[_encoder], { - prefix, - context: "encoder referenced by this", - }); - const computePassRid = assertResource(this, { prefix, context: "this" }); - const querySetRid = assertResource(querySet, { - prefix, - context: "Argument 1", - }); - assertDeviceMatch(device, querySet, { - prefix, - resourceContext: "Argument 1", - selfContext: "this", - }); - ops.op_webgpu_compute_pass_write_timestamp( - computePassRid, - querySetRid, - queryIndex, - ); - } - - end() { - webidl.assertBranded(this, GPUComputePassEncoderPrototype); - const prefix = "Failed to execute 'end' on 'GPUComputePassEncoder'"; - const device = assertDevice(this[_encoder], { - prefix, - context: "encoder referenced by this", - }); - const commandEncoderRid = assertResource(this[_encoder], { - prefix, - context: "encoder referenced by this", - }); - const computePassRid = assertResource(this, { prefix, context: "this" }); - const { err } = ops.op_webgpu_compute_pass_end( - commandEncoderRid, - computePassRid, - ); - device.pushError(err); - this[_rid] = undefined; - } - - // TODO(lucacasonato): has an overload - setBindGroup( - index, - bindGroup, - dynamicOffsetsData, - dynamicOffsetsDataStart, - dynamicOffsetsDataLength, - ) { - webidl.assertBranded(this, GPUComputePassEncoderPrototype); - const prefix = - "Failed to execute 'setBindGroup' on 'GPUComputePassEncoder'"; - const device = assertDevice(this[_encoder], { - prefix, - context: "encoder referenced by this", - }); - assertResource(this[_encoder], { - prefix, - context: "encoder referenced by this", - }); - const computePassRid = assertResource(this, { prefix, context: "this" }); - const bindGroupRid = assertResource(bindGroup, { - prefix, - context: "Argument 2", - }); - assertDeviceMatch(device, bindGroup, { - prefix, - resourceContext: "Argument 2", - selfContext: "this", - }); - if ( - !(ObjectPrototypeIsPrototypeOf( - Uint32ArrayPrototype, - dynamicOffsetsData, - )) - ) { - dynamicOffsetsData = new Uint32Array(dynamicOffsetsData ?? []); - dynamicOffsetsDataStart = 0; - dynamicOffsetsDataLength = dynamicOffsetsData.length; - } - ops.op_webgpu_compute_pass_set_bind_group( - computePassRid, - index, - bindGroupRid, - dynamicOffsetsData, - dynamicOffsetsDataStart, - dynamicOffsetsDataLength, - ); - } - - /** - * @param {string} groupLabel - */ - pushDebugGroup(groupLabel) { - webidl.assertBranded(this, GPUComputePassEncoderPrototype); - const prefix = - "Failed to execute 'pushDebugGroup' on 'GPUComputePassEncoder'"; - webidl.requiredArguments(arguments.length, 1, { prefix }); - groupLabel = webidl.converters.USVString(groupLabel, { - prefix, - context: "Argument 1", - }); - assertDevice(this[_encoder], { - prefix, - context: "encoder referenced by this", - }); - assertResource(this[_encoder], { - prefix, - context: "encoder referenced by this", - }); - const computePassRid = assertResource(this, { prefix, context: "this" }); - ops.op_webgpu_compute_pass_push_debug_group(computePassRid, groupLabel); - } - - popDebugGroup() { - webidl.assertBranded(this, GPUComputePassEncoderPrototype); - const prefix = - "Failed to execute 'popDebugGroup' on 'GPUComputePassEncoder'"; - assertDevice(this[_encoder], { - prefix, - context: "encoder referenced by this", - }); - assertResource(this[_encoder], { - prefix, - context: "encoder referenced by this", - }); - const computePassRid = assertResource(this, { prefix, context: "this" }); - ops.op_webgpu_compute_pass_pop_debug_group(computePassRid); - } - - /** - * @param {string} markerLabel - */ - insertDebugMarker(markerLabel) { - webidl.assertBranded(this, GPUComputePassEncoderPrototype); - const prefix = - "Failed to execute 'insertDebugMarker' on 'GPUComputePassEncoder'"; - webidl.requiredArguments(arguments.length, 1, { prefix }); - markerLabel = webidl.converters.USVString(markerLabel, { - prefix, - context: "Argument 1", - }); - assertDevice(this[_encoder], { - prefix, - context: "encoder referenced by this", - }); - assertResource(this[_encoder], { - prefix, - context: "encoder referenced by this", - }); - const computePassRid = assertResource(this, { prefix, context: "this" }); - ops.op_webgpu_compute_pass_insert_debug_marker( - computePassRid, - markerLabel, - ); - } - - [SymbolFor("Deno.privateCustomInspect")](inspect) { - return `${this.constructor.name} ${ - inspect({ - label: this.label, - }) - }`; - } - } - GPUObjectBaseMixin("GPUComputePassEncoder", GPUComputePassEncoder); - const GPUComputePassEncoderPrototype = GPUComputePassEncoder.prototype; - - /** - * @param {string | null} label - * @param {InnerGPUDevice} device - * @param {number} rid - * @returns {GPUCommandBuffer} - */ - function createGPUCommandBuffer(label, device, rid) { - /** @type {GPUCommandBuffer} */ - const commandBuffer = webidl.createBranded(GPUCommandBuffer); - commandBuffer[_label] = label; - commandBuffer[_device] = device; - commandBuffer[_rid] = rid; - return commandBuffer; - } - - class GPUCommandBuffer { - /** @type {InnerGPUDevice} */ - [_device]; - /** @type {number | undefined} */ - [_rid]; - - [_cleanup]() { - const rid = this[_rid]; - if (rid !== undefined) { - core.close(rid); - /** @type {number | undefined} */ - this[_rid] = undefined; - } - } - - constructor() { - webidl.illegalConstructor(); - } - - [SymbolFor("Deno.privateCustomInspect")](inspect) { - return `${this.constructor.name} ${ - inspect({ - label: this.label, - }) - }`; - } - } - GPUObjectBaseMixin("GPUCommandBuffer", GPUCommandBuffer); - - /** - * @param {string | null} label - * @param {InnerGPUDevice} device - * @param {number} rid - * @returns {GPURenderBundleEncoder} - */ - function createGPURenderBundleEncoder(label, device, rid) { - /** @type {GPURenderBundleEncoder} */ - const bundleEncoder = webidl.createBranded(GPURenderBundleEncoder); - bundleEncoder[_label] = label; - bundleEncoder[_device] = device; - bundleEncoder[_rid] = rid; - return bundleEncoder; - } - - class GPURenderBundleEncoder { - /** @type {InnerGPUDevice} */ - [_device]; - /** @type {number | undefined} */ - [_rid]; - - [_cleanup]() { - const rid = this[_rid]; - if (rid !== undefined) { - core.close(rid); - /** @type {number | undefined} */ - this[_rid] = undefined; - } - } - - constructor() { - webidl.illegalConstructor(); - } - - /** - * @param {GPURenderBundleDescriptor} descriptor - */ - finish(descriptor = {}) { - webidl.assertBranded(this, GPURenderBundleEncoderPrototype); - const prefix = "Failed to execute 'finish' on 'GPURenderBundleEncoder'"; - descriptor = webidl.converters.GPURenderBundleDescriptor(descriptor, { - prefix, - context: "Argument 1", - }); - const device = assertDevice(this, { prefix, context: "this" }); - const renderBundleEncoderRid = assertResource(this, { - prefix, - context: "this", - }); - const { rid, err } = ops.op_webgpu_render_bundle_encoder_finish( - renderBundleEncoderRid, - descriptor.label, - ); - device.pushError(err); - this[_rid] = undefined; - - const renderBundle = createGPURenderBundle( - descriptor.label, - device, - rid, - ); - device.trackResource(renderBundle); - return renderBundle; - } - - // TODO(lucacasonato): has an overload - setBindGroup( - index, - bindGroup, - dynamicOffsetsData, - dynamicOffsetsDataStart, - dynamicOffsetsDataLength, - ) { - webidl.assertBranded(this, GPURenderBundleEncoderPrototype); - const prefix = - "Failed to execute 'setBindGroup' on 'GPURenderBundleEncoder'"; - const device = assertDevice(this, { prefix, context: "this" }); - const renderBundleEncoderRid = assertResource(this, { - prefix, - context: "this", - }); - const bindGroupRid = assertResource(bindGroup, { - prefix, - context: "Argument 2", - }); - assertDeviceMatch(device, bindGroup, { - prefix, - resourceContext: "Argument 2", - selfContext: "this", - }); - if ( - !(ObjectPrototypeIsPrototypeOf( - Uint32ArrayPrototype, - dynamicOffsetsData, - )) - ) { - dynamicOffsetsData = new Uint32Array(dynamicOffsetsData ?? []); - dynamicOffsetsDataStart = 0; - dynamicOffsetsDataLength = dynamicOffsetsData.length; - } - ops.op_webgpu_render_bundle_encoder_set_bind_group( - renderBundleEncoderRid, - index, - bindGroupRid, - dynamicOffsetsData, - dynamicOffsetsDataStart, - dynamicOffsetsDataLength, - ); - } - - /** - * @param {string} groupLabel - */ - pushDebugGroup(groupLabel) { - webidl.assertBranded(this, GPURenderBundleEncoderPrototype); - const prefix = - "Failed to execute 'pushDebugGroup' on 'GPURenderBundleEncoder'"; - webidl.requiredArguments(arguments.length, 1, { prefix }); - groupLabel = webidl.converters.USVString(groupLabel, { - prefix, - context: "Argument 1", - }); - assertDevice(this, { prefix, context: "this" }); - const renderBundleEncoderRid = assertResource(this, { - prefix, - context: "this", - }); - ops.op_webgpu_render_bundle_encoder_push_debug_group( - renderBundleEncoderRid, - groupLabel, - ); - } - - popDebugGroup() { - webidl.assertBranded(this, GPURenderBundleEncoderPrototype); - const prefix = - "Failed to execute 'popDebugGroup' on 'GPURenderBundleEncoder'"; - assertDevice(this, { prefix, context: "this" }); - const renderBundleEncoderRid = assertResource(this, { - prefix, - context: "this", - }); - ops.op_webgpu_render_bundle_encoder_pop_debug_group( - renderBundleEncoderRid, - ); - } - - /** - * @param {string} markerLabel - */ - insertDebugMarker(markerLabel) { - webidl.assertBranded(this, GPURenderBundleEncoderPrototype); - const prefix = - "Failed to execute 'insertDebugMarker' on 'GPURenderBundleEncoder'"; - webidl.requiredArguments(arguments.length, 1, { prefix }); - markerLabel = webidl.converters.USVString(markerLabel, { - prefix, - context: "Argument 1", - }); - assertDevice(this, { prefix, context: "this" }); - const renderBundleEncoderRid = assertResource(this, { - prefix, - context: "this", - }); - ops.op_webgpu_render_bundle_encoder_insert_debug_marker( - renderBundleEncoderRid, - markerLabel, - ); - } - - /** - * @param {GPURenderPipeline} pipeline - */ - setPipeline(pipeline) { - webidl.assertBranded(this, GPURenderBundleEncoderPrototype); - const prefix = - "Failed to execute 'setPipeline' on 'GPURenderBundleEncoder'"; - webidl.requiredArguments(arguments.length, 1, { prefix }); - pipeline = webidl.converters.GPURenderPipeline(pipeline, { - prefix, - context: "Argument 1", - }); - const device = assertDevice(this, { prefix, context: "this" }); - const renderBundleEncoderRid = assertResource(this, { - prefix, - context: "this", - }); - const pipelineRid = assertResource(pipeline, { - prefix, - context: "Argument 1", - }); - assertDeviceMatch(device, pipeline, { - prefix, - resourceContext: "Argument 1", - selfContext: "this", - }); - ops.op_webgpu_render_bundle_encoder_set_pipeline( - renderBundleEncoderRid, - pipelineRid, - ); - } - - /** - * @param {GPUBuffer} buffer - * @param {GPUIndexFormat} indexFormat - * @param {number} offset - * @param {number} size - */ - setIndexBuffer(buffer, indexFormat, offset = 0, size = 0) { - webidl.assertBranded(this, GPURenderBundleEncoderPrototype); - const prefix = - "Failed to execute 'setIndexBuffer' on 'GPURenderBundleEncoder'"; - webidl.requiredArguments(arguments.length, 2, { prefix }); - buffer = webidl.converters.GPUBuffer(buffer, { - prefix, - context: "Argument 1", - }); - indexFormat = webidl.converters.GPUIndexFormat(indexFormat, { - prefix, - context: "Argument 2", - }); - offset = webidl.converters.GPUSize64(offset, { - prefix, - context: "Argument 3", - }); - size = webidl.converters.GPUSize64(size, { - prefix, - context: "Argument 4", - }); - const device = assertDevice(this, { prefix, context: "this" }); - const renderBundleEncoderRid = assertResource(this, { - prefix, - context: "this", - }); - const bufferRid = assertResource(buffer, { - prefix, - context: "Argument 1", - }); - assertDeviceMatch(device, buffer, { - prefix, - resourceContext: "Argument 1", - selfContext: "this", - }); - ops.op_webgpu_render_bundle_encoder_set_index_buffer( - renderBundleEncoderRid, - bufferRid, - indexFormat, - offset, - size, - ); - } - - /** - * @param {number} slot - * @param {GPUBuffer} buffer - * @param {number} offset - * @param {number} size - */ - setVertexBuffer(slot, buffer, offset = 0, size = 0) { - webidl.assertBranded(this, GPURenderBundleEncoderPrototype); - const prefix = - "Failed to execute 'setVertexBuffer' on 'GPURenderBundleEncoder'"; - webidl.requiredArguments(arguments.length, 2, { prefix }); - slot = webidl.converters.GPUSize32(slot, { - prefix, - context: "Argument 2", - }); - buffer = webidl.converters.GPUBuffer(buffer, { - prefix, - context: "Argument 2", - }); - offset = webidl.converters.GPUSize64(offset, { - prefix, - context: "Argument 3", - }); - size = webidl.converters.GPUSize64(size, { - prefix, - context: "Argument 4", - }); - const device = assertDevice(this, { prefix, context: "this" }); - const renderBundleEncoderRid = assertResource(this, { - prefix, - context: "this", - }); - const bufferRid = assertResource(buffer, { - prefix, - context: "Argument 2", - }); - assertDeviceMatch(device, buffer, { - prefix, - resourceContext: "Argument 2", - selfContext: "this", - }); - ops.op_webgpu_render_bundle_encoder_set_vertex_buffer( - renderBundleEncoderRid, - slot, - bufferRid, - offset, - size, - ); - } - - /** - * @param {number} vertexCount - * @param {number} instanceCount - * @param {number} firstVertex - * @param {number} firstInstance - */ - draw(vertexCount, instanceCount = 1, firstVertex = 0, firstInstance = 0) { - webidl.assertBranded(this, GPURenderBundleEncoderPrototype); - const prefix = "Failed to execute 'draw' on 'GPURenderBundleEncoder'"; - webidl.requiredArguments(arguments.length, 1, { prefix }); - vertexCount = webidl.converters.GPUSize32(vertexCount, { - prefix, - context: "Argument 1", - }); - instanceCount = webidl.converters.GPUSize32(instanceCount, { - prefix, - context: "Argument 2", - }); - firstVertex = webidl.converters.GPUSize32(firstVertex, { - prefix, - context: "Argument 3", - }); - firstInstance = webidl.converters.GPUSize32(firstInstance, { - prefix, - context: "Argument 4", - }); - assertDevice(this, { prefix, context: "this" }); - const renderBundleEncoderRid = assertResource(this, { - prefix, - context: "this", - }); - ops.op_webgpu_render_bundle_encoder_draw( - renderBundleEncoderRid, - vertexCount, - instanceCount, - firstVertex, - firstInstance, - ); - } - - /** - * @param {number} indexCount - * @param {number} instanceCount - * @param {number} firstIndex - * @param {number} baseVertex - * @param {number} firstInstance - */ - drawIndexed( - indexCount, - instanceCount = 1, - firstIndex = 0, - baseVertex = 0, - firstInstance = 0, - ) { - webidl.assertBranded(this, GPURenderBundleEncoderPrototype); - const prefix = - "Failed to execute 'drawIndexed' on 'GPURenderBundleEncoder'"; - webidl.requiredArguments(arguments.length, 1, { prefix }); - indexCount = webidl.converters.GPUSize32(indexCount, { - prefix, - context: "Argument 1", - }); - instanceCount = webidl.converters.GPUSize32(instanceCount, { - prefix, - context: "Argument 2", - }); - firstIndex = webidl.converters.GPUSize32(firstIndex, { - prefix, - context: "Argument 3", - }); - baseVertex = webidl.converters.GPUSignedOffset32(baseVertex, { - prefix, - context: "Argument 4", - }); - firstInstance = webidl.converters.GPUSize32(firstInstance, { - prefix, - context: "Argument 5", - }); - assertDevice(this, { prefix, context: "this" }); - const renderBundleEncoderRid = assertResource(this, { - prefix, - context: "this", - }); - ops.op_webgpu_render_bundle_encoder_draw_indexed( - renderBundleEncoderRid, - indexCount, - instanceCount, - firstIndex, - baseVertex, - firstInstance, - ); - } - - /** - * @param {GPUBuffer} indirectBuffer - * @param {number} indirectOffset - */ - drawIndirect(indirectBuffer, indirectOffset) { - webidl.assertBranded(this, GPURenderBundleEncoderPrototype); - const prefix = - "Failed to execute 'drawIndirect' on 'GPURenderBundleEncoder'"; - webidl.requiredArguments(arguments.length, 2, { prefix }); - indirectBuffer = webidl.converters.GPUBuffer(indirectBuffer, { - prefix, - context: "Argument 1", - }); - indirectOffset = webidl.converters.GPUSize64(indirectOffset, { - prefix, - context: "Argument 2", - }); - const device = assertDevice(this, { prefix, context: "this" }); - const renderBundleEncoderRid = assertResource(this, { - prefix, - context: "this", - }); - const indirectBufferRid = assertResource(indirectBuffer, { - prefix, - context: "Argument 1", - }); - assertDeviceMatch(device, indirectBuffer, { - prefix, - resourceContext: "Argument 1", - selfContext: "this", - }); - ops.op_webgpu_render_bundle_encoder_draw_indirect( - renderBundleEncoderRid, - indirectBufferRid, - indirectOffset, - ); - } - - drawIndexedIndirect(_indirectBuffer, _indirectOffset) { - throw new Error("Not yet implemented"); - } - - [SymbolFor("Deno.privateCustomInspect")](inspect) { - return `${this.constructor.name} ${ - inspect({ - label: this.label, - }) - }`; - } - } - GPUObjectBaseMixin("GPURenderBundleEncoder", GPURenderBundleEncoder); - const GPURenderBundleEncoderPrototype = GPURenderBundleEncoder.prototype; - - /** - * @param {string | null} label - * @param {InnerGPUDevice} device - * @param {number} rid - * @returns {GPURenderBundle} - */ - function createGPURenderBundle(label, device, rid) { - /** @type {GPURenderBundle} */ - const bundle = webidl.createBranded(GPURenderBundle); - bundle[_label] = label; - bundle[_device] = device; - bundle[_rid] = rid; - return bundle; - } - - class GPURenderBundle { - /** @type {InnerGPUDevice} */ - [_device]; - /** @type {number | undefined} */ - [_rid]; - - [_cleanup]() { - const rid = this[_rid]; - if (rid !== undefined) { - core.close(rid); - /** @type {number | undefined} */ - this[_rid] = undefined; - } - } - - constructor() { - webidl.illegalConstructor(); - } - - [SymbolFor("Deno.privateCustomInspect")](inspect) { - return `${this.constructor.name} ${ - inspect({ - label: this.label, - }) - }`; - } - } - GPUObjectBaseMixin("GPURenderBundle", GPURenderBundle); - - /** - * @param {string | null} label - * @param {InnerGPUDevice} device - * @param {number} rid - * @returns {GPUQuerySet} - */ - function createGPUQuerySet(label, device, rid, descriptor) { - /** @type {GPUQuerySet} */ - const queue = webidl.createBranded(GPUQuerySet); - queue[_label] = label; - queue[_device] = device; - queue[_rid] = rid; - queue[_descriptor] = descriptor; - return queue; - } - - class GPUQuerySet { - /** @type {InnerGPUDevice} */ - [_device]; - /** @type {number | undefined} */ - [_rid]; - /** @type {GPUQuerySetDescriptor} */ - [_descriptor]; - /** @type {GPUQueryType} */ - [_type]; - /** @type {number} */ - [_count]; - - [_cleanup]() { - const rid = this[_rid]; - if (rid !== undefined) { - core.close(rid); - /** @type {number | undefined} */ - this[_rid] = undefined; - } - } - - constructor() { - webidl.illegalConstructor(); - } - - destroy() { - webidl.assertBranded(this, GPUQuerySetPrototype); - this[_cleanup](); - } - - get type() { - webidl.assertBranded(this, GPUQuerySetPrototype); - return this[_type](); - } - - get count() { - webidl.assertBranded(this, GPUQuerySetPrototype); - return this[_count](); - } - - [SymbolFor("Deno.privateCustomInspect")](inspect) { - return `${this.constructor.name} ${ - inspect({ - label: this.label, - }) - }`; - } - } - GPUObjectBaseMixin("GPUQuerySet", GPUQuerySet); - const GPUQuerySetPrototype = GPUQuerySet.prototype; - - /** - * @param {number} rid - * @returns {GPUSurface} - */ - function createGPUSurface(rid) { - const surface = webidl.createBranded(GPUSurface); - surface[_rid] = rid; - return surface; - } - - /** - * @param {GPUSurface} surface - */ - function destroyGPUSurface(surface) { - surface[_currentTexture]?.destroy(); - surface[_configuration] = undefined; - surface[_device] = undefined; - core.close(surface[_rid]); - surface[_rid] = undefined; - } - - class GPUSurface { - /** @type {number | undefined} */ - [_rid]; - - /** @type {GPUDevice | undefined} */ - [_device]; - - /** @type {GPUSurfaceConfiguration | undefined} */ - [_configuration]; - - /** @type {GPUSurfaceTexture | undefined} */ - [_currentTexture]; - - constructor() { - webidl.illegalConstructor(); - } - - /** - * @param {GPUAdapter} adapter - * @returns {GPUSurfaceCapabilities} - */ - getCapabilities(adapter) { - webidl.assertBranded(this, GPUSurfacePrototype); - - const prefix = "Failed to execute 'getCapabilities' on 'GPUSurface'"; - const rid = assertResource(this, { prefix, context: "this" }); - webidl.requiredArguments(arguments.length, 1, { prefix }); - - adapter = webidl.converters.GPUAdapter(adapter, { - prefix, - context: "Argument 1", - }); - - return ops.op_webgpu_surface_get_capabilities( - rid, - adapter[_adapter].rid, - ); - } - - /** - * @param {GPUDevice} device - * @param {GPUSurfaceConfiguration} config - */ - configure(device, config) { - webidl.assertBranded(this, GPUSurfacePrototype); - - const prefix = "Failed to execute 'configure' on 'GPUSurface'"; - const rid = assertResource(this, { prefix, context: "this" }); - webidl.requiredArguments(arguments.length, 2, { prefix }); - - device = webidl.converters.GPUDevice(device, { - prefix, - context: "Argument 1", - }); - device = assertDevice( - device, - { prefix, context: "Argument 1" }, - ); - - config = webidl.converters.GPUSurfaceConfiguration(config, { - prefix, - context: "Argument 2", - }); - config.size = normalizeGPUExtent3D(config.size); - - ops.op_webgpu_surface_configure(rid, device.rid, config); - - this[_device] = device; - this[_configuration] = config; - } - - /** - * @returns {GPUSurfaceTexture} - */ - getCurrentTexture() { - webidl.assertBranded(this, GPUSurfacePrototype); - - if (this[_currentTexture]) { - return this[_currentTexture]; - } - - const prefix = "Failed to execute 'getCurrentTexture' on 'GPUSurface'"; - const rid = assertResource(this, { prefix, context: "this" }); - const device = assertDevice(this, { prefix, context: "this" }); - - const [textureRid, isSuboptimal] = ops - .op_webgpu_surface_get_current_texture(rid, device.rid); - - this[_currentTexture] = createGPUSurfaceTexture( - { - size: this[_configuration].size, - mipLevelCount: 1, - sampleCount: 1, - dimension: "2d", - format: this[_configuration].format, - usage: this[_configuration].usage, - viewFormats: this[_configuration].viewFormats, - }, - device, - textureRid, - this, - isSuboptimal, - ); - device.trackResource(this[_currentTexture]); - return this[_currentTexture]; - } - } - const GPUSurfacePrototype = GPUSurface.prototype; - - /** - * @param {GPUTextureDescriptor} descriptor - * @param {GPUDevice} device - * @param {number} rid - * @param {GPUSurface} surface - * @param {boolean} isSuboptimal - * @returns {GPUSurfaceTexture} - */ - function createGPUSurfaceTexture( - descriptor, - device, - rid, - surface, - isSuboptimal, - ) { - const texture = createGPUTexture( - descriptor, - device, - rid, - GPUSurfaceTexture, - ); - texture[_surface] = surface; - texture[_isSuboptimal] = isSuboptimal; - return texture; - } - - class GPUSurfaceTexture extends GPUTexture { - /** @type {GPUSurface} */ - [_surface]; - - /** @type {boolean} */ - [_isSuboptimal]; - - [_cleanup]() { - if (this[_rid] !== undefined) { - ops.op_webgpu_surface_texture_discard( - this[_surface][_rid], - this[_device].rid, - ); - this[_surface][_currentTexture] = undefined; - } - super[_cleanup](); - } - - /** - * @returns {boolean} - */ - get isSuboptimal() { - webidl.assertBranded(this, GPUSurfaceTexturePrototype); - return this[_isSuboptimal]; - } - - present() { - webidl.assertBranded(this, GPUSurfaceTexturePrototype); - - const prefix = "Failed to execute 'present' on 'GPUSurfaceTexture'"; - const device = assertDevice(this, { prefix, context: "this" }); - assertResource(this, { prefix, context: "this" }); - - ops.op_webgpu_surface_texture_present(this[_surface][_rid], device.rid); - - this[_surface][_currentTexture] = undefined; - super[_cleanup](); - } - } - const GPUSurfaceTexturePrototype = GPUSurfaceTexture.prototype; - - window.__bootstrap.webgpu = { - _device, - assertDevice, - createGPUTexture, - createGPUSurface, - destroyGPUSurface, - gpu: webidl.createBranded(GPU), - GPU, - GPUAdapter, - GPUAdapterInfo, - GPUSupportedLimits, - GPUSupportedFeatures, - GPUDevice, - GPUDeviceLostInfo, - GPUQueue, - GPUBuffer, - GPUBufferUsage, - GPUMapMode, - GPUTextureUsage, - GPUTexture, - GPUTextureView, - GPUSampler, - GPUBindGroupLayout, - GPUPipelineLayout, - GPUBindGroup, - GPUShaderModule, - GPUShaderStage, - GPUComputePipeline, - GPURenderPipeline, - GPUColorWrite, - GPUCommandEncoder, - GPURenderPassEncoder, - GPUComputePassEncoder, - GPUCommandBuffer, - GPURenderBundleEncoder, - GPURenderBundle, - GPUQuerySet, - GPUError, - GPUValidationError, - GPUOutOfMemoryError, - GPUSurface, - GPUSurfaceTexture, - }; -})(this); diff --git a/ext/webgpu/src/02_idl_types.js b/ext/webgpu/src/02_idl_types.js deleted file mode 100644 index 9a314de83c2e73..00000000000000 --- a/ext/webgpu/src/02_idl_types.js +++ /dev/null @@ -1,2120 +0,0 @@ -// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. -// Copyright 2023 Jo Bates. All rights reserved. MIT license. - -// @ts-check -/// - -"use strict"; - -((window) => { - const webidl = window.__bootstrap.webidl; - const { - GPU, - GPUAdapter, - GPUSupportedLimits, - GPUSupportedFeatures, - GPUDevice, - GPUQueue, - GPUBuffer, - GPUBufferUsage, - GPUMapMode, - GPUTextureUsage, - GPUTexture, - GPUTextureView, - GPUSampler, - GPUBindGroupLayout, - GPUPipelineLayout, - GPUBindGroup, - GPUShaderModule, - GPUShaderStage, - GPUComputePipeline, - GPURenderPipeline, - GPUColorWrite, - GPUCommandEncoder, - GPURenderPassEncoder, - GPUComputePassEncoder, - GPUCommandBuffer, - GPURenderBundleEncoder, - GPURenderBundle, - GPUQuerySet, - GPUOutOfMemoryError, - GPUValidationError, - GPUSurface, - } = window.__bootstrap.webgpu; - const { SymbolIterator, TypeError } = window.__bootstrap.primordials; - - // This needs to be initialized after all of the base classes are implemented, - // otherwise their converters might not be available yet. - // DICTIONARY: GPUObjectDescriptorBase - const dictMembersGPUObjectDescriptorBase = [ - { key: "label", converter: webidl.converters["USVString"] }, - ]; - webidl.converters["GPUObjectDescriptorBase"] = webidl - .createDictionaryConverter( - "GPUObjectDescriptorBase", - dictMembersGPUObjectDescriptorBase, - ); - - // INTERFACE: GPUSupportedLimits - webidl.converters.GPUSupportedLimits = webidl.createInterfaceConverter( - "GPUSupportedLimits", - GPUSupportedLimits.prototype, - ); - - // INTERFACE: GPUSupportedFeatures - webidl.converters.GPUSupportedFeatures = webidl.createInterfaceConverter( - "GPUSupportedFeatures", - GPUSupportedFeatures.prototype, - ); - - // INTERFACE: GPU - webidl.converters.GPU = webidl.createInterfaceConverter("GPU", GPU.prototype); - - // ENUM: GPUPowerPreference - webidl.converters["GPUPowerPreference"] = webidl.createEnumConverter( - "GPUPowerPreference", - [ - "low-power", - "high-performance", - ], - ); - - // INTERFACE: GPUSurface - webidl.converters.GPUSurface = webidl.createInterfaceConverter( - "GPUSurface", - GPUSurface.prototype, - ); - - // DICTIONARY: GPURequestAdapterOptions - const dictMembersGPURequestAdapterOptions = [ - { - key: "powerPreference", - converter: webidl.converters["GPUPowerPreference"], - }, - { - key: "forceFallbackAdapter", - converter: webidl.converters.boolean, - defaultValue: false, - }, - { - key: "compatibleSurface", - converter: webidl.converters["GPUSurface"], - }, - ]; - webidl.converters["GPURequestAdapterOptions"] = webidl - .createDictionaryConverter( - "GPURequestAdapterOptions", - dictMembersGPURequestAdapterOptions, - ); - - // INTERFACE: GPUAdapter - webidl.converters.GPUAdapter = webidl.createInterfaceConverter( - "GPUAdapter", - GPUAdapter.prototype, - ); - - // ENUM: GPUFeatureName - webidl.converters["GPUFeatureName"] = webidl.createEnumConverter( - "GPUFeatureName", - [ - "depth-clip-control", - "depth32float-stencil8", - "pipeline-statistics-query", - "texture-compression-bc", - "texture-compression-etc2", - "texture-compression-astc", - "timestamp-query", - "indirect-first-instance", - "shader-f16", - // extended from spec - "mappable-primary-buffers", - "texture-binding-array", - "buffer-binding-array", - "storage-resource-binding-array", - "sampled-texture-and-storage-buffer-array-non-uniform-indexing", - "uniform-buffer-and-storage-buffer-texture-non-uniform-indexing", - "unsized-binding-array", - "multi-draw-indirect", - "multi-draw-indirect-count", - "push-constants", - "address-mode-clamp-to-border", - "texture-adapter-specific-format-features", - "shader-float64", - "vertex-attribute-64bit", - "conservative-rasterization", - "vertex-writable-storage", - "clear-commands", - "spirv-shader-passthrough", - "shader-primitive-index", - ], - ); - - // TYPEDEF: GPUSize32 - webidl.converters["GPUSize32"] = (V, opts) => - webidl.converters["unsigned long"](V, { ...opts, enforceRange: true }); - - // TYPEDEF: GPUSize64 - webidl.converters["GPUSize64"] = (V, opts) => - webidl.converters["unsigned long long"](V, { ...opts, enforceRange: true }); - - // DICTIONARY: GPUDeviceDescriptor - const dictMembersGPUDeviceDescriptor = [ - { - key: "requiredFeatures", - converter: webidl.createSequenceConverter( - webidl.converters["GPUFeatureName"], - ), - get defaultValue() { - return []; - }, - }, - { - key: "requiredLimits", - converter: webidl.createRecordConverter( - webidl.converters["DOMString"], - webidl.converters["GPUSize64"], - ), - }, - ]; - webidl.converters["GPUDeviceDescriptor"] = webidl.createDictionaryConverter( - "GPUDeviceDescriptor", - dictMembersGPUObjectDescriptorBase, - dictMembersGPUDeviceDescriptor, - ); - - // INTERFACE: GPUDevice - webidl.converters.GPUDevice = webidl.createInterfaceConverter( - "GPUDevice", - GPUDevice.prototype, - ); - - // INTERFACE: GPUBuffer - webidl.converters.GPUBuffer = webidl.createInterfaceConverter( - "GPUBuffer", - GPUBuffer.prototype, - ); - - // TYPEDEF: GPUBufferUsageFlags - webidl.converters["GPUBufferUsageFlags"] = (V, opts) => - webidl.converters["unsigned long"](V, { ...opts, enforceRange: true }); - - // DICTIONARY: GPUBufferDescriptor - const dictMembersGPUBufferDescriptor = [ - { key: "size", converter: webidl.converters["GPUSize64"], required: true }, - { - key: "usage", - converter: webidl.converters["GPUBufferUsageFlags"], - required: true, - }, - { - key: "mappedAtCreation", - converter: webidl.converters["boolean"], - defaultValue: false, - }, - ]; - webidl.converters["GPUBufferDescriptor"] = webidl.createDictionaryConverter( - "GPUBufferDescriptor", - dictMembersGPUObjectDescriptorBase, - dictMembersGPUBufferDescriptor, - ); - - // INTERFACE: GPUBufferUsage - webidl.converters.GPUBufferUsage = webidl.createInterfaceConverter( - "GPUBufferUsage", - GPUBufferUsage.prototype, - ); - - // TYPEDEF: GPUMapModeFlags - webidl.converters["GPUMapModeFlags"] = (V, opts) => - webidl.converters["unsigned long"](V, { ...opts, enforceRange: true }); - - // INTERFACE: GPUMapMode - webidl.converters.GPUMapMode = webidl.createInterfaceConverter( - "GPUMapMode", - GPUMapMode.prototype, - ); - - // INTERFACE: GPUTexture - webidl.converters.GPUTexture = webidl.createInterfaceConverter( - "GPUTexture", - GPUTexture.prototype, - ); - - // TYPEDEF: GPUIntegerCoordinate - webidl.converters["GPUIntegerCoordinate"] = (V, opts) => - webidl.converters["unsigned long"](V, { ...opts, enforceRange: true }); - webidl.converters["sequence"] = webidl - .createSequenceConverter(webidl.converters["GPUIntegerCoordinate"]); - - // DICTIONARY: GPUExtent3DDict - const dictMembersGPUExtent3DDict = [ - { - key: "width", - converter: webidl.converters["GPUIntegerCoordinate"], - required: true, - }, - { - key: "height", - converter: webidl.converters["GPUIntegerCoordinate"], - defaultValue: 1, - }, - { - key: "depthOrArrayLayers", - converter: webidl.converters["GPUIntegerCoordinate"], - defaultValue: 1, - }, - ]; - webidl.converters["GPUExtent3DDict"] = webidl.createDictionaryConverter( - "GPUExtent3DDict", - dictMembersGPUExtent3DDict, - ); - - // TYPEDEF: GPUExtent3D - webidl.converters["GPUExtent3D"] = (V, opts) => { - // Union for (sequence or GPUExtent3DDict) - if (V === null || V === undefined) { - return webidl.converters["GPUExtent3DDict"](V, opts); - } - if (typeof V === "object") { - const method = V[SymbolIterator]; - if (method !== undefined) { - return webidl.converters["sequence"](V, opts); - } - return webidl.converters["GPUExtent3DDict"](V, opts); - } - throw webidl.makeException( - TypeError, - "can not be converted to sequence or GPUExtent3DDict.", - opts, - ); - }; - - // ENUM: GPUTextureDimension - webidl.converters["GPUTextureDimension"] = webidl.createEnumConverter( - "GPUTextureDimension", - [ - "1d", - "2d", - "3d", - ], - ); - - // ENUM: GPUTextureFormat - webidl.converters["GPUTextureFormat"] = webidl.createEnumConverter( - "GPUTextureFormat", - [ - "r8unorm", - "r8snorm", - "r8uint", - "r8sint", - "r16uint", - "r16sint", - "r16float", - "rg8unorm", - "rg8snorm", - "rg8uint", - "rg8sint", - "r32uint", - "r32sint", - "r32float", - "rg16uint", - "rg16sint", - "rg16float", - "rgba8unorm", - "rgba8unorm-srgb", - "rgba8snorm", - "rgba8uint", - "rgba8sint", - "bgra8unorm", - "bgra8unorm-srgb", - "rgb9e5ufloat", - "rgb10a2unorm", - "rg11b10ufloat", - "rg32uint", - "rg32sint", - "rg32float", - "rgba16uint", - "rgba16sint", - "rgba16float", - "rgba32uint", - "rgba32sint", - "rgba32float", - "stencil8", - "depth16unorm", - "depth24plus", - "depth24plus-stencil8", - "depth32float", - "depth32float-stencil8", - "bc1-rgba-unorm", - "bc1-rgba-unorm-srgb", - "bc2-rgba-unorm", - "bc2-rgba-unorm-srgb", - "bc3-rgba-unorm", - "bc3-rgba-unorm-srgb", - "bc4-r-unorm", - "bc4-r-snorm", - "bc5-rg-unorm", - "bc5-rg-snorm", - "bc6h-rgb-ufloat", - "bc6h-rgb-float", - "bc7-rgba-unorm", - "bc7-rgba-unorm-srgb", - "etc2-rgb8unorm", - "etc2-rgb8unorm-srgb", - "etc2-rgb8a1unorm", - "etc2-rgb8a1unorm-srgb", - "etc2-rgba8unorm", - "etc2-rgba8unorm-srgb", - "eac-r11unorm", - "eac-r11snorm", - "eac-rg11unorm", - "eac-rg11snorm", - "astc-4x4-unorm", - "astc-4x4-unorm-srgb", - "astc-5x4-unorm", - "astc-5x4-unorm-srgb", - "astc-5x5-unorm", - "astc-5x5-unorm-srgb", - "astc-6x5-unorm", - "astc-6x5-unorm-srgb", - "astc-6x6-unorm", - "astc-6x6-unorm-srgb", - "astc-8x5-unorm", - "astc-8x5-unorm-srgb", - "astc-8x6-unorm", - "astc-8x6-unorm-srgb", - "astc-8x8-unorm", - "astc-8x8-unorm-srgb", - "astc-10x5-unorm", - "astc-10x5-unorm-srgb", - "astc-10x6-unorm", - "astc-10x6-unorm-srgb", - "astc-10x8-unorm", - "astc-10x8-unorm-srgb", - "astc-10x10-unorm", - "astc-10x10-unorm-srgb", - "astc-12x10-unorm", - "astc-12x10-unorm-srgb", - "astc-12x12-unorm", - "astc-12x12-unorm-srgb", - ], - ); - - // TYPEDEF: GPUTextureUsageFlags - webidl.converters["GPUTextureUsageFlags"] = (V, opts) => - webidl.converters["unsigned long"](V, { ...opts, enforceRange: true }); - - // DICTIONARY: GPUTextureDescriptor - const dictMembersGPUTextureDescriptor = [ - { - key: "size", - converter: webidl.converters["GPUExtent3D"], - required: true, - }, - { - key: "mipLevelCount", - converter: webidl.converters["GPUIntegerCoordinate"], - defaultValue: 1, - }, - { - key: "sampleCount", - converter: webidl.converters["GPUSize32"], - defaultValue: 1, - }, - { - key: "dimension", - converter: webidl.converters["GPUTextureDimension"], - defaultValue: "2d", - }, - { - key: "format", - converter: webidl.converters["GPUTextureFormat"], - required: true, - }, - { - key: "usage", - converter: webidl.converters["GPUTextureUsageFlags"], - required: true, - }, - { - key: "viewFormats", - converter: webidl.createSequenceConverter( - webidl.converters["GPUTextureFormat"], - ), - get defaultValue() { - return []; - }, - }, - ]; - webidl.converters["GPUTextureDescriptor"] = webidl.createDictionaryConverter( - "GPUTextureDescriptor", - dictMembersGPUObjectDescriptorBase, - dictMembersGPUTextureDescriptor, - ); - - // INTERFACE: GPUTextureUsage - webidl.converters.GPUTextureUsage = webidl.createInterfaceConverter( - "GPUTextureUsage", - GPUTextureUsage.prototype, - ); - - // INTERFACE: GPUTextureView - webidl.converters.GPUTextureView = webidl.createInterfaceConverter( - "GPUTextureView", - GPUTextureView.prototype, - ); - - // ENUM: GPUTextureViewDimension - webidl.converters["GPUTextureViewDimension"] = webidl.createEnumConverter( - "GPUTextureViewDimension", - [ - "1d", - "2d", - "2d-array", - "cube", - "cube-array", - "3d", - ], - ); - - // ENUM: GPUTextureAspect - webidl.converters["GPUTextureAspect"] = webidl.createEnumConverter( - "GPUTextureAspect", - [ - "all", - "stencil-only", - "depth-only", - ], - ); - - // DICTIONARY: GPUTextureViewDescriptor - const dictMembersGPUTextureViewDescriptor = [ - { key: "format", converter: webidl.converters["GPUTextureFormat"] }, - { - key: "dimension", - converter: webidl.converters["GPUTextureViewDimension"], - }, - { - key: "aspect", - converter: webidl.converters["GPUTextureAspect"], - defaultValue: "all", - }, - { - key: "baseMipLevel", - converter: webidl.converters["GPUIntegerCoordinate"], - defaultValue: 0, - }, - { - key: "mipLevelCount", - converter: webidl.converters["GPUIntegerCoordinate"], - }, - { - key: "baseArrayLayer", - converter: webidl.converters["GPUIntegerCoordinate"], - defaultValue: 0, - }, - { - key: "arrayLayerCount", - converter: webidl.converters["GPUIntegerCoordinate"], - }, - ]; - webidl.converters["GPUTextureViewDescriptor"] = webidl - .createDictionaryConverter( - "GPUTextureViewDescriptor", - dictMembersGPUObjectDescriptorBase, - dictMembersGPUTextureViewDescriptor, - ); - - // INTERFACE: GPUSampler - webidl.converters.GPUSampler = webidl.createInterfaceConverter( - "GPUSampler", - GPUSampler.prototype, - ); - - // ENUM: GPUAddressMode - webidl.converters["GPUAddressMode"] = webidl.createEnumConverter( - "GPUAddressMode", - [ - "clamp-to-edge", - "repeat", - "mirror-repeat", - ], - ); - - // ENUM: GPUFilterMode - webidl.converters["GPUFilterMode"] = webidl.createEnumConverter( - "GPUFilterMode", - [ - "nearest", - "linear", - ], - ); - - // ENUM: GPUMipmapFilterMode - webidl.converters["GPUMipmapFilterMode"] = webidl.createEnumConverter( - "GPUMipmapFilterMode", - [ - "nearest", - "linear", - ], - ); - - // ENUM: GPUCompareFunction - webidl.converters["GPUCompareFunction"] = webidl.createEnumConverter( - "GPUCompareFunction", - [ - "never", - "less", - "equal", - "less-equal", - "greater", - "not-equal", - "greater-equal", - "always", - ], - ); - - // DICTIONARY: GPUSamplerDescriptor - const dictMembersGPUSamplerDescriptor = [ - { - key: "addressModeU", - converter: webidl.converters["GPUAddressMode"], - defaultValue: "clamp-to-edge", - }, - { - key: "addressModeV", - converter: webidl.converters["GPUAddressMode"], - defaultValue: "clamp-to-edge", - }, - { - key: "addressModeW", - converter: webidl.converters["GPUAddressMode"], - defaultValue: "clamp-to-edge", - }, - { - key: "magFilter", - converter: webidl.converters["GPUFilterMode"], - defaultValue: "nearest", - }, - { - key: "minFilter", - converter: webidl.converters["GPUFilterMode"], - defaultValue: "nearest", - }, - { - key: "mipmapFilter", - converter: webidl.converters["GPUMipmapFilterMode"], - defaultValue: "nearest", - }, - { - key: "lodMinClamp", - converter: webidl.converters["float"], - defaultValue: 0, - }, - { - key: "lodMaxClamp", - converter: webidl.converters["float"], - defaultValue: 0xffffffff, - }, - { key: "compare", converter: webidl.converters["GPUCompareFunction"] }, - { - key: "maxAnisotropy", - converter: (V, opts) => - webidl.converters["unsigned short"](V, { ...opts, clamp: true }), - defaultValue: 1, - }, - ]; - webidl.converters["GPUSamplerDescriptor"] = webidl.createDictionaryConverter( - "GPUSamplerDescriptor", - dictMembersGPUObjectDescriptorBase, - dictMembersGPUSamplerDescriptor, - ); - - // INTERFACE: GPUBindGroupLayout - webidl.converters.GPUBindGroupLayout = webidl.createInterfaceConverter( - "GPUBindGroupLayout", - GPUBindGroupLayout.prototype, - ); - - // TYPEDEF: GPUIndex32 - webidl.converters["GPUIndex32"] = (V, opts) => - webidl.converters["unsigned long"](V, { ...opts, enforceRange: true }); - - // TYPEDEF: GPUShaderStageFlags - webidl.converters["GPUShaderStageFlags"] = (V, opts) => - webidl.converters["unsigned long"](V, { ...opts, enforceRange: true }); - - // ENUM: GPUBufferBindingType - webidl.converters["GPUBufferBindingType"] = webidl.createEnumConverter( - "GPUBufferBindingType", - [ - "uniform", - "storage", - "read-only-storage", - ], - ); - - // DICTIONARY: GPUBufferBindingLayout - const dictMembersGPUBufferBindingLayout = [ - { - key: "type", - converter: webidl.converters["GPUBufferBindingType"], - defaultValue: "uniform", - }, - { - key: "hasDynamicOffset", - converter: webidl.converters["boolean"], - defaultValue: false, - }, - { - key: "minBindingSize", - converter: webidl.converters["GPUSize64"], - defaultValue: 0, - }, - ]; - webidl.converters["GPUBufferBindingLayout"] = webidl - .createDictionaryConverter( - "GPUBufferBindingLayout", - dictMembersGPUBufferBindingLayout, - ); - - // ENUM: GPUSamplerBindingType - webidl.converters["GPUSamplerBindingType"] = webidl.createEnumConverter( - "GPUSamplerBindingType", - [ - "filtering", - "non-filtering", - "comparison", - ], - ); - - // DICTIONARY: GPUSamplerBindingLayout - const dictMembersGPUSamplerBindingLayout = [ - { - key: "type", - converter: webidl.converters["GPUSamplerBindingType"], - defaultValue: "filtering", - }, - ]; - webidl.converters["GPUSamplerBindingLayout"] = webidl - .createDictionaryConverter( - "GPUSamplerBindingLayout", - dictMembersGPUSamplerBindingLayout, - ); - - // ENUM: GPUTextureSampleType - webidl.converters["GPUTextureSampleType"] = webidl.createEnumConverter( - "GPUTextureSampleType", - [ - "float", - "unfilterable-float", - "depth", - "sint", - "uint", - ], - ); - - // DICTIONARY: GPUTextureBindingLayout - const dictMembersGPUTextureBindingLayout = [ - { - key: "sampleType", - converter: webidl.converters["GPUTextureSampleType"], - defaultValue: "float", - }, - { - key: "viewDimension", - converter: webidl.converters["GPUTextureViewDimension"], - defaultValue: "2d", - }, - { - key: "multisampled", - converter: webidl.converters["boolean"], - defaultValue: false, - }, - ]; - webidl.converters["GPUTextureBindingLayout"] = webidl - .createDictionaryConverter( - "GPUTextureBindingLayout", - dictMembersGPUTextureBindingLayout, - ); - - // ENUM: GPUStorageTextureAccess - webidl.converters["GPUStorageTextureAccess"] = webidl.createEnumConverter( - "GPUStorageTextureAccess", - [ - "write-only", - ], - ); - - // DICTIONARY: GPUStorageTextureBindingLayout - const dictMembersGPUStorageTextureBindingLayout = [ - { - key: "access", - converter: webidl.converters["GPUStorageTextureAccess"], - defaultValue: "write-only", - }, - { - key: "format", - converter: webidl.converters["GPUTextureFormat"], - required: true, - }, - { - key: "viewDimension", - converter: webidl.converters["GPUTextureViewDimension"], - defaultValue: "2d", - }, - ]; - webidl.converters["GPUStorageTextureBindingLayout"] = webidl - .createDictionaryConverter( - "GPUStorageTextureBindingLayout", - dictMembersGPUStorageTextureBindingLayout, - ); - - // DICTIONARY: GPUBindGroupLayoutEntry - const dictMembersGPUBindGroupLayoutEntry = [ - { - key: "binding", - converter: webidl.converters["GPUIndex32"], - required: true, - }, - { - key: "visibility", - converter: webidl.converters["GPUShaderStageFlags"], - required: true, - }, - { key: "buffer", converter: webidl.converters["GPUBufferBindingLayout"] }, - { key: "sampler", converter: webidl.converters["GPUSamplerBindingLayout"] }, - { key: "texture", converter: webidl.converters["GPUTextureBindingLayout"] }, - { - key: "storageTexture", - converter: webidl.converters["GPUStorageTextureBindingLayout"], - }, - ]; - webidl.converters["GPUBindGroupLayoutEntry"] = webidl - .createDictionaryConverter( - "GPUBindGroupLayoutEntry", - dictMembersGPUBindGroupLayoutEntry, - ); - - // DICTIONARY: GPUBindGroupLayoutDescriptor - const dictMembersGPUBindGroupLayoutDescriptor = [ - { - key: "entries", - converter: webidl.createSequenceConverter( - webidl.converters["GPUBindGroupLayoutEntry"], - ), - required: true, - }, - ]; - webidl.converters["GPUBindGroupLayoutDescriptor"] = webidl - .createDictionaryConverter( - "GPUBindGroupLayoutDescriptor", - dictMembersGPUObjectDescriptorBase, - dictMembersGPUBindGroupLayoutDescriptor, - ); - - // INTERFACE: GPUShaderStage - webidl.converters.GPUShaderStage = webidl.createInterfaceConverter( - "GPUShaderStage", - GPUShaderStage.prototype, - ); - - // INTERFACE: GPUBindGroup - webidl.converters.GPUBindGroup = webidl.createInterfaceConverter( - "GPUBindGroup", - GPUBindGroup.prototype, - ); - - // DICTIONARY: GPUBufferBinding - const dictMembersGPUBufferBinding = [ - { - key: "buffer", - converter: webidl.converters["GPUBuffer"], - required: true, - }, - { - key: "offset", - converter: webidl.converters["GPUSize64"], - defaultValue: 0, - }, - { key: "size", converter: webidl.converters["GPUSize64"] }, - ]; - webidl.converters["GPUBufferBinding"] = webidl.createDictionaryConverter( - "GPUBufferBinding", - dictMembersGPUBufferBinding, - ); - - // TYPEDEF: GPUBindingResource - webidl.converters["GPUBindingResource"] = - webidl.converters.any /** put union here! **/; - - // DICTIONARY: GPUBindGroupEntry - const dictMembersGPUBindGroupEntry = [ - { - key: "binding", - converter: webidl.converters["GPUIndex32"], - required: true, - }, - { - key: "resource", - converter: webidl.converters["GPUBindingResource"], - required: true, - }, - ]; - webidl.converters["GPUBindGroupEntry"] = webidl.createDictionaryConverter( - "GPUBindGroupEntry", - dictMembersGPUBindGroupEntry, - ); - - // DICTIONARY: GPUBindGroupDescriptor - const dictMembersGPUBindGroupDescriptor = [ - { - key: "layout", - converter: webidl.converters["GPUBindGroupLayout"], - required: true, - }, - { - key: "entries", - converter: webidl.createSequenceConverter( - webidl.converters["GPUBindGroupEntry"], - ), - required: true, - }, - ]; - webidl.converters["GPUBindGroupDescriptor"] = webidl - .createDictionaryConverter( - "GPUBindGroupDescriptor", - dictMembersGPUObjectDescriptorBase, - dictMembersGPUBindGroupDescriptor, - ); - - // INTERFACE: GPUPipelineLayout - webidl.converters.GPUPipelineLayout = webidl.createInterfaceConverter( - "GPUPipelineLayout", - GPUPipelineLayout.prototype, - ); - - // DICTIONARY: GPUPipelineLayoutDescriptor - const dictMembersGPUPipelineLayoutDescriptor = [ - { - key: "bindGroupLayouts", - converter: webidl.createSequenceConverter( - webidl.converters["GPUBindGroupLayout"], - ), - required: true, - }, - ]; - webidl.converters["GPUPipelineLayoutDescriptor"] = webidl - .createDictionaryConverter( - "GPUPipelineLayoutDescriptor", - dictMembersGPUObjectDescriptorBase, - dictMembersGPUPipelineLayoutDescriptor, - ); - - // INTERFACE: GPUShaderModule - webidl.converters.GPUShaderModule = webidl.createInterfaceConverter( - "GPUShaderModule", - GPUShaderModule.prototype, - ); - - // DICTIONARY: GPUShaderModuleDescriptor - const dictMembersGPUShaderModuleDescriptor = [ - { - key: "code", - converter: webidl.converters["DOMString"], - required: true, - }, - ]; - webidl.converters["GPUShaderModuleDescriptor"] = webidl - .createDictionaryConverter( - "GPUShaderModuleDescriptor", - dictMembersGPUObjectDescriptorBase, - dictMembersGPUShaderModuleDescriptor, - ); - - // // ENUM: GPUCompilationMessageType - // webidl.converters["GPUCompilationMessageType"] = webidl.createEnumConverter( - // "GPUCompilationMessageType", - // [ - // "error", - // "warning", - // "info", - // ], - // ); - - // // INTERFACE: GPUCompilationMessage - // webidl.converters.GPUCompilationMessage = webidl.createInterfaceConverter( - // "GPUCompilationMessage", - // GPUCompilationMessage.prototype, - // ); - - // // INTERFACE: GPUCompilationInfo - // webidl.converters.GPUCompilationInfo = webidl.createInterfaceConverter( - // "GPUCompilationInfo", - // GPUCompilationInfo.prototype, - // ); - - webidl.converters["GPUAutoLayoutMode"] = webidl.createEnumConverter( - "GPUAutoLayoutMode", - [ - "auto", - ], - ); - - webidl.converters["GPUPipelineLayout or GPUAutoLayoutMode"] = (V, opts) => { - if (typeof V === "object") { - return webidl.converters["GPUPipelineLayout"](V, opts); - } - return webidl.converters["GPUAutoLayoutMode"](V, opts); - }; - - // DICTIONARY: GPUPipelineDescriptorBase - const dictMembersGPUPipelineDescriptorBase = [ - { - key: "layout", - converter: webidl.converters["GPUPipelineLayout or GPUAutoLayoutMode"], - }, - ]; - webidl.converters["GPUPipelineDescriptorBase"] = webidl - .createDictionaryConverter( - "GPUPipelineDescriptorBase", - dictMembersGPUObjectDescriptorBase, - dictMembersGPUPipelineDescriptorBase, - ); - - // TYPEDEF: GPUPipelineConstantValue - webidl.converters.GPUPipelineConstantValue = webidl.converters.double; - - webidl.converters["record"] = webidl - .createRecordConverter( - webidl.converters.USVString, - webidl.converters.GPUPipelineConstantValue, - ); - - // DICTIONARY: GPUProgrammableStage - const dictMembersGPUProgrammableStage = [ - { - key: "module", - converter: webidl.converters["GPUShaderModule"], - required: true, - }, - { - key: "entryPoint", - converter: webidl.converters["USVString"], - required: true, - }, - { - key: "constants", - converter: - webidl.converters["record"], - }, - ]; - webidl.converters["GPUProgrammableStage"] = webidl.createDictionaryConverter( - "GPUProgrammableStage", - dictMembersGPUProgrammableStage, - ); - - // INTERFACE: GPUComputePipeline - webidl.converters.GPUComputePipeline = webidl.createInterfaceConverter( - "GPUComputePipeline", - GPUComputePipeline.prototype, - ); - - // DICTIONARY: GPUComputePipelineDescriptor - const dictMembersGPUComputePipelineDescriptor = [ - { - key: "compute", - converter: webidl.converters["GPUProgrammableStage"], - required: true, - }, - ]; - webidl.converters["GPUComputePipelineDescriptor"] = webidl - .createDictionaryConverter( - "GPUComputePipelineDescriptor", - dictMembersGPUObjectDescriptorBase, - dictMembersGPUPipelineDescriptorBase, - dictMembersGPUComputePipelineDescriptor, - ); - - // INTERFACE: GPURenderPipeline - webidl.converters.GPURenderPipeline = webidl.createInterfaceConverter( - "GPURenderPipeline", - GPURenderPipeline.prototype, - ); - - // ENUM: GPUVertexStepMode - webidl.converters["GPUVertexStepMode"] = webidl.createEnumConverter( - "GPUVertexStepMode", - [ - "vertex", - "instance", - ], - ); - - // ENUM: GPUVertexFormat - webidl.converters["GPUVertexFormat"] = webidl.createEnumConverter( - "GPUVertexFormat", - [ - "uint8x2", - "uint8x4", - "sint8x2", - "sint8x4", - "unorm8x2", - "unorm8x4", - "snorm8x2", - "snorm8x4", - "uint16x2", - "uint16x4", - "sint16x2", - "sint16x4", - "unorm16x2", - "unorm16x4", - "snorm16x2", - "snorm16x4", - "float16x2", - "float16x4", - "float32", - "float32x2", - "float32x3", - "float32x4", - "uint32", - "uint32x2", - "uint32x3", - "uint32x4", - "sint32", - "sint32x2", - "sint32x3", - "sint32x4", - ], - ); - - // DICTIONARY: GPUVertexAttribute - const dictMembersGPUVertexAttribute = [ - { - key: "format", - converter: webidl.converters["GPUVertexFormat"], - required: true, - }, - { - key: "offset", - converter: webidl.converters["GPUSize64"], - required: true, - }, - { - key: "shaderLocation", - converter: webidl.converters["GPUIndex32"], - required: true, - }, - ]; - webidl.converters["GPUVertexAttribute"] = webidl.createDictionaryConverter( - "GPUVertexAttribute", - dictMembersGPUVertexAttribute, - ); - - // DICTIONARY: GPUVertexBufferLayout - const dictMembersGPUVertexBufferLayout = [ - { - key: "arrayStride", - converter: webidl.converters["GPUSize64"], - required: true, - }, - { - key: "stepMode", - converter: webidl.converters["GPUVertexStepMode"], - defaultValue: "vertex", - }, - { - key: "attributes", - converter: webidl.createSequenceConverter( - webidl.converters["GPUVertexAttribute"], - ), - required: true, - }, - ]; - webidl.converters["GPUVertexBufferLayout"] = webidl.createDictionaryConverter( - "GPUVertexBufferLayout", - dictMembersGPUVertexBufferLayout, - ); - - // DICTIONARY: GPUVertexState - const dictMembersGPUVertexState = [ - { - key: "buffers", - converter: webidl.createSequenceConverter( - webidl.createNullableConverter( - webidl.converters["GPUVertexBufferLayout"], - ), - ), - get defaultValue() { - return []; - }, - }, - ]; - webidl.converters["GPUVertexState"] = webidl.createDictionaryConverter( - "GPUVertexState", - dictMembersGPUProgrammableStage, - dictMembersGPUVertexState, - ); - - // ENUM: GPUPrimitiveTopology - webidl.converters["GPUPrimitiveTopology"] = webidl.createEnumConverter( - "GPUPrimitiveTopology", - [ - "point-list", - "line-list", - "line-strip", - "triangle-list", - "triangle-strip", - ], - ); - - // ENUM: GPUIndexFormat - webidl.converters["GPUIndexFormat"] = webidl.createEnumConverter( - "GPUIndexFormat", - [ - "uint16", - "uint32", - ], - ); - - // ENUM: GPUFrontFace - webidl.converters["GPUFrontFace"] = webidl.createEnumConverter( - "GPUFrontFace", - [ - "ccw", - "cw", - ], - ); - - // ENUM: GPUCullMode - webidl.converters["GPUCullMode"] = webidl.createEnumConverter("GPUCullMode", [ - "none", - "front", - "back", - ]); - - // DICTIONARY: GPUPrimitiveState - const dictMembersGPUPrimitiveState = [ - { - key: "topology", - converter: webidl.converters["GPUPrimitiveTopology"], - defaultValue: "triangle-list", - }, - { key: "stripIndexFormat", converter: webidl.converters["GPUIndexFormat"] }, - { - key: "frontFace", - converter: webidl.converters["GPUFrontFace"], - defaultValue: "ccw", - }, - { - key: "cullMode", - converter: webidl.converters["GPUCullMode"], - defaultValue: "none", - }, - { - key: "unclippedDepth", - converter: webidl.converters["boolean"], - defaultValue: false, - }, - ]; - webidl.converters["GPUPrimitiveState"] = webidl.createDictionaryConverter( - "GPUPrimitiveState", - dictMembersGPUPrimitiveState, - ); - - // ENUM: GPUStencilOperation - webidl.converters["GPUStencilOperation"] = webidl.createEnumConverter( - "GPUStencilOperation", - [ - "keep", - "zero", - "replace", - "invert", - "increment-clamp", - "decrement-clamp", - "increment-wrap", - "decrement-wrap", - ], - ); - - // DICTIONARY: GPUStencilFaceState - const dictMembersGPUStencilFaceState = [ - { - key: "compare", - converter: webidl.converters["GPUCompareFunction"], - defaultValue: "always", - }, - { - key: "failOp", - converter: webidl.converters["GPUStencilOperation"], - defaultValue: "keep", - }, - { - key: "depthFailOp", - converter: webidl.converters["GPUStencilOperation"], - defaultValue: "keep", - }, - { - key: "passOp", - converter: webidl.converters["GPUStencilOperation"], - defaultValue: "keep", - }, - ]; - webidl.converters["GPUStencilFaceState"] = webidl.createDictionaryConverter( - "GPUStencilFaceState", - dictMembersGPUStencilFaceState, - ); - - // TYPEDEF: GPUStencilValue - webidl.converters["GPUStencilValue"] = (V, opts) => - webidl.converters["unsigned long"](V, { ...opts, enforceRange: true }); - - // TYPEDEF: GPUDepthBias - webidl.converters["GPUDepthBias"] = (V, opts) => - webidl.converters["long"](V, { ...opts, enforceRange: true }); - - // DICTIONARY: GPUDepthStencilState - const dictMembersGPUDepthStencilState = [ - { - key: "format", - converter: webidl.converters["GPUTextureFormat"], - required: true, - }, - { - key: "depthWriteEnabled", - converter: webidl.converters["boolean"], - defaultValue: false, - }, - { - key: "depthCompare", - converter: webidl.converters["GPUCompareFunction"], - defaultValue: "always", - }, - { - key: "stencilFront", - converter: webidl.converters["GPUStencilFaceState"], - get defaultValue() { - return {}; - }, - }, - { - key: "stencilBack", - converter: webidl.converters["GPUStencilFaceState"], - get defaultValue() { - return {}; - }, - }, - { - key: "stencilReadMask", - converter: webidl.converters["GPUStencilValue"], - defaultValue: 0xFFFFFFFF, - }, - { - key: "stencilWriteMask", - converter: webidl.converters["GPUStencilValue"], - defaultValue: 0xFFFFFFFF, - }, - { - key: "depthBias", - converter: webidl.converters["GPUDepthBias"], - defaultValue: 0, - }, - { - key: "depthBiasSlopeScale", - converter: webidl.converters["float"], - defaultValue: 0, - }, - { - key: "depthBiasClamp", - converter: webidl.converters["float"], - defaultValue: 0, - }, - ]; - webidl.converters["GPUDepthStencilState"] = webidl.createDictionaryConverter( - "GPUDepthStencilState", - dictMembersGPUDepthStencilState, - ); - - // TYPEDEF: GPUSampleMask - webidl.converters["GPUSampleMask"] = (V, opts) => - webidl.converters["unsigned long"](V, { ...opts, enforceRange: true }); - - // DICTIONARY: GPUMultisampleState - const dictMembersGPUMultisampleState = [ - { - key: "count", - converter: webidl.converters["GPUSize32"], - defaultValue: 1, - }, - { - key: "mask", - converter: webidl.converters["GPUSampleMask"], - defaultValue: 0xFFFFFFFF, - }, - { - key: "alphaToCoverageEnabled", - converter: webidl.converters["boolean"], - defaultValue: false, - }, - ]; - webidl.converters["GPUMultisampleState"] = webidl.createDictionaryConverter( - "GPUMultisampleState", - dictMembersGPUMultisampleState, - ); - - // ENUM: GPUBlendFactor - webidl.converters["GPUBlendFactor"] = webidl.createEnumConverter( - "GPUBlendFactor", - [ - "zero", - "one", - "src", - "one-minus-src", - "src-alpha", - "one-minus-src-alpha", - "dst", - "one-minus-dst", - "dst-alpha", - "one-minus-dst-alpha", - "src-alpha-saturated", - "constant", - "one-minus-constant", - ], - ); - - // ENUM: GPUBlendOperation - webidl.converters["GPUBlendOperation"] = webidl.createEnumConverter( - "GPUBlendOperation", - [ - "add", - "subtract", - "reverse-subtract", - "min", - "max", - ], - ); - - // DICTIONARY: GPUBlendComponent - const dictMembersGPUBlendComponent = [ - { - key: "srcFactor", - converter: webidl.converters["GPUBlendFactor"], - defaultValue: "one", - }, - { - key: "dstFactor", - converter: webidl.converters["GPUBlendFactor"], - defaultValue: "zero", - }, - { - key: "operation", - converter: webidl.converters["GPUBlendOperation"], - defaultValue: "add", - }, - ]; - webidl.converters["GPUBlendComponent"] = webidl.createDictionaryConverter( - "GPUBlendComponent", - dictMembersGPUBlendComponent, - ); - - // DICTIONARY: GPUBlendState - const dictMembersGPUBlendState = [ - { - key: "color", - converter: webidl.converters["GPUBlendComponent"], - required: true, - }, - { - key: "alpha", - converter: webidl.converters["GPUBlendComponent"], - required: true, - }, - ]; - webidl.converters["GPUBlendState"] = webidl.createDictionaryConverter( - "GPUBlendState", - dictMembersGPUBlendState, - ); - - // TYPEDEF: GPUColorWriteFlags - webidl.converters["GPUColorWriteFlags"] = (V, opts) => - webidl.converters["unsigned long"](V, { ...opts, enforceRange: true }); - - // DICTIONARY: GPUColorTargetState - const dictMembersGPUColorTargetState = [ - { - key: "format", - converter: webidl.converters["GPUTextureFormat"], - required: true, - }, - { key: "blend", converter: webidl.converters["GPUBlendState"] }, - { - key: "writeMask", - converter: webidl.converters["GPUColorWriteFlags"], - defaultValue: 0xF, - }, - ]; - webidl.converters["GPUColorTargetState"] = webidl.createDictionaryConverter( - "GPUColorTargetState", - dictMembersGPUColorTargetState, - ); - - // DICTIONARY: GPUFragmentState - const dictMembersGPUFragmentState = [ - { - key: "targets", - converter: webidl.createSequenceConverter( - webidl.createNullableConverter( - webidl.converters["GPUColorTargetState"], - ), - ), - required: true, - }, - ]; - webidl.converters["GPUFragmentState"] = webidl.createDictionaryConverter( - "GPUFragmentState", - dictMembersGPUProgrammableStage, - dictMembersGPUFragmentState, - ); - - // DICTIONARY: GPURenderPipelineDescriptor - const dictMembersGPURenderPipelineDescriptor = [ - { - key: "vertex", - converter: webidl.converters["GPUVertexState"], - required: true, - }, - { - key: "primitive", - converter: webidl.converters["GPUPrimitiveState"], - get defaultValue() { - return {}; - }, - }, - { - key: "depthStencil", - converter: webidl.converters["GPUDepthStencilState"], - }, - { - key: "multisample", - converter: webidl.converters["GPUMultisampleState"], - get defaultValue() { - return {}; - }, - }, - { key: "fragment", converter: webidl.converters["GPUFragmentState"] }, - ]; - webidl.converters["GPURenderPipelineDescriptor"] = webidl - .createDictionaryConverter( - "GPURenderPipelineDescriptor", - dictMembersGPUObjectDescriptorBase, - dictMembersGPUPipelineDescriptorBase, - dictMembersGPURenderPipelineDescriptor, - ); - - // INTERFACE: GPUColorWrite - webidl.converters.GPUColorWrite = webidl.createInterfaceConverter( - "GPUColorWrite", - GPUColorWrite.prototype, - ); - - // INTERFACE: GPUCommandBuffer - webidl.converters.GPUCommandBuffer = webidl.createInterfaceConverter( - "GPUCommandBuffer", - GPUCommandBuffer.prototype, - ); - webidl.converters["sequence"] = webidl - .createSequenceConverter(webidl.converters["GPUCommandBuffer"]); - - // DICTIONARY: GPUCommandBufferDescriptor - const dictMembersGPUCommandBufferDescriptor = []; - webidl.converters["GPUCommandBufferDescriptor"] = webidl - .createDictionaryConverter( - "GPUCommandBufferDescriptor", - dictMembersGPUObjectDescriptorBase, - dictMembersGPUCommandBufferDescriptor, - ); - - // INTERFACE: GPUCommandEncoder - webidl.converters.GPUCommandEncoder = webidl.createInterfaceConverter( - "GPUCommandEncoder", - GPUCommandEncoder.prototype, - ); - - // DICTIONARY: GPUCommandEncoderDescriptor - const dictMembersGPUCommandEncoderDescriptor = []; - webidl.converters["GPUCommandEncoderDescriptor"] = webidl - .createDictionaryConverter( - "GPUCommandEncoderDescriptor", - dictMembersGPUObjectDescriptorBase, - dictMembersGPUCommandEncoderDescriptor, - ); - - // DICTIONARY: GPUImageDataLayout - const dictMembersGPUImageDataLayout = [ - { - key: "offset", - converter: webidl.converters["GPUSize64"], - defaultValue: 0, - }, - { key: "bytesPerRow", converter: webidl.converters["GPUSize32"] }, - { key: "rowsPerImage", converter: webidl.converters["GPUSize32"] }, - ]; - webidl.converters["GPUImageDataLayout"] = webidl.createDictionaryConverter( - "GPUImageDataLayout", - dictMembersGPUImageDataLayout, - ); - - // DICTIONARY: GPUImageCopyBuffer - const dictMembersGPUImageCopyBuffer = [ - { - key: "buffer", - converter: webidl.converters["GPUBuffer"], - required: true, - }, - ]; - webidl.converters["GPUImageCopyBuffer"] = webidl.createDictionaryConverter( - "GPUImageCopyBuffer", - dictMembersGPUImageDataLayout, - dictMembersGPUImageCopyBuffer, - ); - - // DICTIONARY: GPUOrigin3DDict - const dictMembersGPUOrigin3DDict = [ - { - key: "x", - converter: webidl.converters["GPUIntegerCoordinate"], - defaultValue: 0, - }, - { - key: "y", - converter: webidl.converters["GPUIntegerCoordinate"], - defaultValue: 0, - }, - { - key: "z", - converter: webidl.converters["GPUIntegerCoordinate"], - defaultValue: 0, - }, - ]; - webidl.converters["GPUOrigin3DDict"] = webidl.createDictionaryConverter( - "GPUOrigin3DDict", - dictMembersGPUOrigin3DDict, - ); - - // TYPEDEF: GPUOrigin3D - webidl.converters["GPUOrigin3D"] = (V, opts) => { - // Union for (sequence or GPUOrigin3DDict) - if (V === null || V === undefined) { - return webidl.converters["GPUOrigin3DDict"](V, opts); - } - if (typeof V === "object") { - const method = V[SymbolIterator]; - if (method !== undefined) { - return webidl.converters["sequence"](V, opts); - } - return webidl.converters["GPUOrigin3DDict"](V, opts); - } - throw webidl.makeException( - TypeError, - "can not be converted to sequence or GPUOrigin3DDict.", - opts, - ); - }; - - // DICTIONARY: GPUImageCopyTexture - const dictMembersGPUImageCopyTexture = [ - { - key: "texture", - converter: webidl.converters["GPUTexture"], - required: true, - }, - { - key: "mipLevel", - converter: webidl.converters["GPUIntegerCoordinate"], - defaultValue: 0, - }, - { - key: "origin", - converter: webidl.converters["GPUOrigin3D"], - get defaultValue() { - return {}; - }, - }, - { - key: "aspect", - converter: webidl.converters["GPUTextureAspect"], - defaultValue: "all", - }, - ]; - webidl.converters["GPUImageCopyTexture"] = webidl.createDictionaryConverter( - "GPUImageCopyTexture", - dictMembersGPUImageCopyTexture, - ); - - // DICTIONARY: GPUOrigin2DDict - const dictMembersGPUOrigin2DDict = [ - { - key: "x", - converter: webidl.converters["GPUIntegerCoordinate"], - defaultValue: 0, - }, - { - key: "y", - converter: webidl.converters["GPUIntegerCoordinate"], - defaultValue: 0, - }, - ]; - webidl.converters["GPUOrigin2DDict"] = webidl.createDictionaryConverter( - "GPUOrigin2DDict", - dictMembersGPUOrigin2DDict, - ); - - // TYPEDEF: GPUOrigin2D - webidl.converters["GPUOrigin2D"] = (V, opts) => { - // Union for (sequence or GPUOrigin2DDict) - if (V === null || V === undefined) { - return webidl.converters["GPUOrigin2DDict"](V, opts); - } - if (typeof V === "object") { - const method = V[SymbolIterator]; - if (method !== undefined) { - return webidl.converters["sequence"](V, opts); - } - return webidl.converters["GPUOrigin2DDict"](V, opts); - } - throw webidl.makeException( - TypeError, - "can not be converted to sequence or GPUOrigin2DDict.", - opts, - ); - }; - - // INTERFACE: GPUComputePassEncoder - webidl.converters.GPUComputePassEncoder = webidl.createInterfaceConverter( - "GPUComputePassEncoder", - GPUComputePassEncoder.prototype, - ); - - // DICTIONARY: GPUComputePassDescriptor - const dictMembersGPUComputePassDescriptor = []; - webidl.converters["GPUComputePassDescriptor"] = webidl - .createDictionaryConverter( - "GPUComputePassDescriptor", - dictMembersGPUObjectDescriptorBase, - dictMembersGPUComputePassDescriptor, - ); - - // INTERFACE: GPURenderPassEncoder - webidl.converters.GPURenderPassEncoder = webidl.createInterfaceConverter( - "GPURenderPassEncoder", - GPURenderPassEncoder.prototype, - ); - - // ENUM: GPULoadOp - webidl.converters["GPULoadOp"] = webidl.createEnumConverter("GPULoadOp", [ - "load", - "clear", - ]); - - // DICTIONARY: GPUColorDict - const dictMembersGPUColorDict = [ - { key: "r", converter: webidl.converters["double"], required: true }, - { key: "g", converter: webidl.converters["double"], required: true }, - { key: "b", converter: webidl.converters["double"], required: true }, - { key: "a", converter: webidl.converters["double"], required: true }, - ]; - webidl.converters["GPUColorDict"] = webidl.createDictionaryConverter( - "GPUColorDict", - dictMembersGPUColorDict, - ); - - // TYPEDEF: GPUColor - webidl.converters["GPUColor"] = (V, opts) => { - // Union for (sequence or GPUColorDict) - if (V === null || V === undefined) { - return webidl.converters["GPUColorDict"](V, opts); - } - if (typeof V === "object") { - const method = V[SymbolIterator]; - if (method !== undefined) { - return webidl.converters["sequence"](V, opts); - } - return webidl.converters["GPUColorDict"](V, opts); - } - throw webidl.makeException( - TypeError, - "can not be converted to sequence or GPUColorDict.", - opts, - ); - }; - - // ENUM: GPUStoreOp - webidl.converters["GPUStoreOp"] = webidl.createEnumConverter("GPUStoreOp", [ - "store", - "discard", - ]); - - // DICTIONARY: GPURenderPassColorAttachment - const dictMembersGPURenderPassColorAttachment = [ - { - key: "view", - converter: webidl.converters["GPUTextureView"], - required: true, - }, - { key: "resolveTarget", converter: webidl.converters["GPUTextureView"] }, - { - key: "clearValue", - converter: webidl.converters["GPUColor"], - }, - { - key: "loadOp", - converter: webidl.converters["GPULoadOp"], - required: true, - }, - { - key: "storeOp", - converter: webidl.converters["GPUStoreOp"], - required: true, - }, - ]; - webidl.converters["GPURenderPassColorAttachment"] = webidl - .createDictionaryConverter( - "GPURenderPassColorAttachment", - dictMembersGPURenderPassColorAttachment, - ); - - // DICTIONARY: GPURenderPassDepthStencilAttachment - const dictMembersGPURenderPassDepthStencilAttachment = [ - { - key: "view", - converter: webidl.converters["GPUTextureView"], - required: true, - }, - { - key: "depthClearValue", - converter: webidl.converters["float"], - defaultValue: 0, - }, - { - key: "depthLoadOp", - converter: webidl.converters["GPULoadOp"], - }, - { - key: "depthStoreOp", - converter: webidl.converters["GPUStoreOp"], - }, - { - key: "depthReadOnly", - converter: webidl.converters["boolean"], - defaultValue: false, - }, - { - key: "stencilClearValue", - converter: webidl.converters["GPUStencilValue"], - defaultValue: 0, - }, - { - key: "stencilLoadOp", - converter: webidl.converters["GPULoadOp"], - }, - { - key: "stencilStoreOp", - converter: webidl.converters["GPUStoreOp"], - }, - { - key: "stencilReadOnly", - converter: webidl.converters["boolean"], - defaultValue: false, - }, - ]; - webidl.converters["GPURenderPassDepthStencilAttachment"] = webidl - .createDictionaryConverter( - "GPURenderPassDepthStencilAttachment", - dictMembersGPURenderPassDepthStencilAttachment, - ); - - // INTERFACE: GPUQuerySet - webidl.converters.GPUQuerySet = webidl.createInterfaceConverter( - "GPUQuerySet", - GPUQuerySet.prototype, - ); - - // DICTIONARY: GPURenderPassDescriptor - const dictMembersGPURenderPassDescriptor = [ - { - key: "colorAttachments", - converter: webidl.createSequenceConverter( - webidl.createNullableConverter( - webidl.converters["GPURenderPassColorAttachment"], - ), - ), - required: true, - }, - { - key: "depthStencilAttachment", - converter: webidl.converters["GPURenderPassDepthStencilAttachment"], - }, - ]; - webidl.converters["GPURenderPassDescriptor"] = webidl - .createDictionaryConverter( - "GPURenderPassDescriptor", - dictMembersGPUObjectDescriptorBase, - dictMembersGPURenderPassDescriptor, - ); - - // INTERFACE: GPURenderBundle - webidl.converters.GPURenderBundle = webidl.createInterfaceConverter( - "GPURenderBundle", - GPURenderBundle.prototype, - ); - webidl.converters["sequence"] = webidl - .createSequenceConverter(webidl.converters["GPURenderBundle"]); - - // DICTIONARY: GPURenderBundleDescriptor - const dictMembersGPURenderBundleDescriptor = []; - webidl.converters["GPURenderBundleDescriptor"] = webidl - .createDictionaryConverter( - "GPURenderBundleDescriptor", - dictMembersGPUObjectDescriptorBase, - dictMembersGPURenderBundleDescriptor, - ); - - // INTERFACE: GPURenderBundleEncoder - webidl.converters.GPURenderBundleEncoder = webidl.createInterfaceConverter( - "GPURenderBundleEncoder", - GPURenderBundleEncoder.prototype, - ); - - // DICTIONARY: GPURenderPassLayout - const dictMembersGPURenderPassLayout = [ - { - key: "colorFormats", - converter: webidl.createSequenceConverter( - webidl.createNullableConverter(webidl.converters["GPUTextureFormat"]), - ), - required: true, - }, - { - key: "depthStencilFormat", - converter: webidl.converters["GPUTextureFormat"], - }, - { - key: "sampleCount", - converter: webidl.converters["GPUSize32"], - defaultValue: 1, - }, - ]; - webidl.converters["GPURenderPassLayout"] = webidl - .createDictionaryConverter( - "GPURenderPassLayout", - dictMembersGPUObjectDescriptorBase, - dictMembersGPURenderPassLayout, - ); - - // DICTIONARY: GPURenderBundleEncoderDescriptor - const dictMembersGPURenderBundleEncoderDescriptor = [ - { - key: "depthReadOnly", - converter: webidl.converters.boolean, - defaultValue: false, - }, - { - key: "stencilReadOnly", - converter: webidl.converters.boolean, - defaultValue: false, - }, - ]; - webidl.converters["GPURenderBundleEncoderDescriptor"] = webidl - .createDictionaryConverter( - "GPURenderBundleEncoderDescriptor", - dictMembersGPUObjectDescriptorBase, - dictMembersGPURenderPassLayout, - dictMembersGPURenderBundleEncoderDescriptor, - ); - - // INTERFACE: GPUQueue - webidl.converters.GPUQueue = webidl.createInterfaceConverter( - "GPUQueue", - GPUQueue.prototype, - ); - - // ENUM: GPUQueryType - webidl.converters["GPUQueryType"] = webidl.createEnumConverter( - "GPUQueryType", - [ - "occlusion", - "pipeline-statistics", - "timestamp", - ], - ); - - // ENUM: GPUPipelineStatisticName - webidl.converters["GPUPipelineStatisticName"] = webidl.createEnumConverter( - "GPUPipelineStatisticName", - [ - "vertex-shader-invocations", - "clipper-invocations", - "clipper-primitives-out", - "fragment-shader-invocations", - "compute-shader-invocations", - ], - ); - - // DICTIONARY: GPUQuerySetDescriptor - const dictMembersGPUQuerySetDescriptor = [ - { - key: "type", - converter: webidl.converters["GPUQueryType"], - required: true, - }, - { key: "count", converter: webidl.converters["GPUSize32"], required: true }, - { - key: "pipelineStatistics", - converter: webidl.createSequenceConverter( - webidl.converters["GPUPipelineStatisticName"], - ), - get defaultValue() { - return []; - }, - }, - ]; - webidl.converters["GPUQuerySetDescriptor"] = webidl.createDictionaryConverter( - "GPUQuerySetDescriptor", - dictMembersGPUObjectDescriptorBase, - dictMembersGPUQuerySetDescriptor, - ); - - // ENUM: GPUDeviceLostReason - webidl.converters["GPUDeviceLostReason"] = webidl.createEnumConverter( - "GPUDeviceLostReason", - [ - "destroyed", - ], - ); - - // // INTERFACE: GPUDeviceLostInfo - // webidl.converters.GPUDeviceLostInfo = webidl.createInterfaceConverter( - // "GPUDeviceLostInfo", - // GPUDeviceLostInfo.prototype, - // ); - - // ENUM: GPUErrorFilter - webidl.converters["GPUErrorFilter"] = webidl.createEnumConverter( - "GPUErrorFilter", - [ - "out-of-memory", - "validation", - ], - ); - - // INTERFACE: GPUOutOfMemoryError - webidl.converters.GPUOutOfMemoryError = webidl.createInterfaceConverter( - "GPUOutOfMemoryError", - GPUOutOfMemoryError.prototype, - ); - - // INTERFACE: GPUValidationError - webidl.converters.GPUValidationError = webidl.createInterfaceConverter( - "GPUValidationError", - GPUValidationError.prototype, - ); - - // TYPEDEF: GPUError - webidl.converters["GPUError"] = webidl.converters.any /** put union here! **/; - - // // INTERFACE: GPUUncapturedErrorEvent - // webidl.converters.GPUUncapturedErrorEvent = webidl.createInterfaceConverter( - // "GPUUncapturedErrorEvent", - // GPUUncapturedErrorEvent.prototype, - // ); - - // DICTIONARY: GPUUncapturedErrorEventInit - const dictMembersGPUUncapturedErrorEventInit = [ - { key: "error", converter: webidl.converters["GPUError"], required: true }, - ]; - webidl.converters["GPUUncapturedErrorEventInit"] = webidl - .createDictionaryConverter( - "GPUUncapturedErrorEventInit", - // dictMembersEventInit, - dictMembersGPUUncapturedErrorEventInit, - ); - - // TYPEDEF: GPUBufferDynamicOffset - webidl.converters["GPUBufferDynamicOffset"] = (V, opts) => - webidl.converters["unsigned long"](V, { ...opts, enforceRange: true }); - - // TYPEDEF: GPUSignedOffset32 - webidl.converters["GPUSignedOffset32"] = (V, opts) => - webidl.converters["long"](V, { ...opts, enforceRange: true }); - - // TYPEDEF: GPUFlagsConstant - webidl.converters["GPUFlagsConstant"] = webidl.converters["unsigned long"]; - - // ENUM: GPUSurfacePresentMode - webidl.converters["GPUSurfacePresentMode"] = webidl.createEnumConverter( - "GPUSurfacePresentMode", - [ - "auto-vsync", - "auto-no-vsync", - "fifo", - "fifo-relaxed", - "immediate", - "mailbox", - ], - ); - - // ENUM: GPUSurfaceAlphaMode - webidl.converters["GPUSurfaceAlphaMode"] = webidl.createEnumConverter( - "GPUSurfaceAlphaMode", - [ - "auto", - "opaque", - "pre-multiplied", - "post-multiplied", - "inherit", - ], - ); - - // DICTIONARY: GPUSurfaceConfiguration - const dictMembersGPUSurfaceConfiguration = [ - { - key: "usage", - converter: webidl.converters["GPUTextureUsageFlags"], - defaultValue: GPUTextureUsage.RENDER_ATTACHMENT, - }, - { - key: "format", - converter: webidl.converters["GPUTextureFormat"], - required: true, - }, - { - key: "size", - converter: webidl.converters["GPUExtent3D"], - required: true, - }, - { - key: "presentMode", - converter: webidl.converters["GPUSurfacePresentMode"], - defaultValue: "fifo", - }, - { - key: "alphaMode", - converter: webidl.converters["GPUSurfaceAlphaMode"], - defaultValue: "opaque", - }, - { - key: "viewFormats", - converter: webidl.createSequenceConverter( - webidl.converters["GPUTextureFormat"], - ), - get defaultValue() { - return []; - }, - }, - ]; - webidl.converters["GPUSurfaceConfiguration"] = webidl - .createDictionaryConverter( - "GPUSurfaceConfiguration", - dictMembersGPUSurfaceConfiguration, - ); -})(this); diff --git a/ext/webgpu/src/surface.rs b/ext/webgpu/surface.rs similarity index 100% rename from ext/webgpu/src/surface.rs rename to ext/webgpu/surface.rs diff --git a/ext/webgpu/src/texture.rs b/ext/webgpu/texture.rs similarity index 100% rename from ext/webgpu/src/texture.rs rename to ext/webgpu/texture.rs diff --git a/ext/webidl/00_webidl.js b/ext/webidl/00_webidl.js index bb90d973ec0eca..46d0a2c1d0a8bf 100644 --- a/ext/webidl/00_webidl.js +++ b/ext/webidl/00_webidl.js @@ -6,1186 +6,1174 @@ /// -"use strict"; - -((window) => { - const core = window.Deno.core; - const { - ArrayBufferPrototype, - ArrayBufferIsView, - ArrayPrototypeForEach, - ArrayPrototypePush, - ArrayPrototypeSort, - ArrayIteratorPrototype, - BigInt, - BigIntAsIntN, - BigIntAsUintN, - Float32Array, - Float64Array, - FunctionPrototypeBind, - Int16Array, - Int32Array, - Int8Array, - isNaN, - MathFloor, - MathFround, - MathMax, - MathMin, - MathPow, - MathRound, - MathTrunc, - Number, - NumberIsFinite, - NumberIsNaN, - // deno-lint-ignore camelcase - NumberMAX_SAFE_INTEGER, - // deno-lint-ignore camelcase - NumberMIN_SAFE_INTEGER, - ObjectAssign, - ObjectCreate, - ObjectDefineProperties, - ObjectDefineProperty, - ObjectGetOwnPropertyDescriptor, - ObjectGetOwnPropertyDescriptors, - ObjectGetPrototypeOf, - ObjectPrototypeHasOwnProperty, - ObjectPrototypeIsPrototypeOf, - ObjectIs, - PromisePrototypeThen, - PromiseReject, - PromiseResolve, - ReflectApply, - ReflectDefineProperty, - ReflectGetOwnPropertyDescriptor, - ReflectHas, - ReflectOwnKeys, - RegExpPrototypeTest, - Set, - SetPrototypeEntries, - SetPrototypeForEach, - SetPrototypeKeys, - SetPrototypeValues, - SetPrototypeHas, - SetPrototypeClear, - SetPrototypeDelete, - SetPrototypeAdd, - // TODO(lucacasonato): add SharedArrayBuffer to primordials - // SharedArrayBufferPrototype - String, - StringFromCodePoint, - StringPrototypeCharCodeAt, - Symbol, - SymbolIterator, - SymbolToStringTag, - TypedArrayPrototypeGetSymbolToStringTag, - TypeError, - Uint16Array, - Uint32Array, - Uint8Array, - Uint8ClampedArray, - } = window.__bootstrap.primordials; +const core = globalThis.Deno.core; +const primordials = globalThis.__bootstrap.primordials; +const { + ArrayBufferPrototype, + ArrayBufferIsView, + ArrayPrototypeForEach, + ArrayPrototypePush, + ArrayPrototypeSort, + ArrayIteratorPrototype, + BigInt, + BigIntAsIntN, + BigIntAsUintN, + Float32Array, + Float64Array, + FunctionPrototypeBind, + Int16Array, + Int32Array, + Int8Array, + isNaN, + MathFloor, + MathFround, + MathMax, + MathMin, + MathPow, + MathRound, + MathTrunc, + Number, + NumberIsFinite, + NumberIsNaN, + // deno-lint-ignore camelcase + NumberMAX_SAFE_INTEGER, + // deno-lint-ignore camelcase + NumberMIN_SAFE_INTEGER, + ObjectAssign, + ObjectCreate, + ObjectDefineProperties, + ObjectDefineProperty, + ObjectGetOwnPropertyDescriptor, + ObjectGetOwnPropertyDescriptors, + ObjectGetPrototypeOf, + ObjectPrototypeHasOwnProperty, + ObjectPrototypeIsPrototypeOf, + ObjectIs, + PromisePrototypeThen, + PromiseReject, + PromiseResolve, + ReflectApply, + ReflectDefineProperty, + ReflectGetOwnPropertyDescriptor, + ReflectHas, + ReflectOwnKeys, + RegExpPrototypeTest, + Set, + SetPrototypeEntries, + SetPrototypeForEach, + SetPrototypeKeys, + SetPrototypeValues, + SetPrototypeHas, + SetPrototypeClear, + SetPrototypeDelete, + SetPrototypeAdd, + // TODO(lucacasonato): add SharedArrayBuffer to primordials + // SharedArrayBufferPrototype + String, + StringFromCodePoint, + StringPrototypeCharCodeAt, + Symbol, + SymbolIterator, + SymbolToStringTag, + TypedArrayPrototypeGetSymbolToStringTag, + TypeError, + Uint16Array, + Uint32Array, + Uint8Array, + Uint8ClampedArray, +} = primordials; + +function makeException(ErrorType, message, opts = {}) { + return new ErrorType( + `${opts.prefix ? opts.prefix + ": " : ""}${ + opts.context ? opts.context : "Value" + } ${message}`, + ); +} - function makeException(ErrorType, message, opts = {}) { - return new ErrorType( - `${opts.prefix ? opts.prefix + ": " : ""}${ - opts.context ? opts.context : "Value" - } ${message}`, - ); +function toNumber(value) { + if (typeof value === "bigint") { + throw TypeError("Cannot convert a BigInt value to a number"); } + return Number(value); +} - function toNumber(value) { - if (typeof value === "bigint") { - throw TypeError("Cannot convert a BigInt value to a number"); - } - return Number(value); +function type(V) { + if (V === null) { + return "Null"; } + switch (typeof V) { + case "undefined": + return "Undefined"; + case "boolean": + return "Boolean"; + case "number": + return "Number"; + case "string": + return "String"; + case "symbol": + return "Symbol"; + case "bigint": + return "BigInt"; + case "object": + // Falls through + case "function": + // Falls through + default: + // Per ES spec, typeof returns an implemention-defined value that is not any of the existing ones for + // uncallable non-standard exotic objects. Yet Type() which the Web IDL spec depends on returns Object for + // such cases. So treat the default case as an object. + return "Object"; + } +} - function type(V) { - if (V === null) { - return "Null"; - } - switch (typeof V) { - case "undefined": - return "Undefined"; - case "boolean": - return "Boolean"; - case "number": - return "Number"; - case "string": - return "String"; - case "symbol": - return "Symbol"; - case "bigint": - return "BigInt"; - case "object": - // Falls through - case "function": - // Falls through - default: - // Per ES spec, typeof returns an implemention-defined value that is not any of the existing ones for - // uncallable non-standard exotic objects. Yet Type() which the Web IDL spec depends on returns Object for - // such cases. So treat the default case as an object. - return "Object"; - } +// Round x to the nearest integer, choosing the even integer if it lies halfway between two. +function evenRound(x) { + // There are four cases for numbers with fractional part being .5: + // + // case | x | floor(x) | round(x) | expected | x <> 0 | x % 1 | x & 1 | example + // 1 | 2n + 0.5 | 2n | 2n + 1 | 2n | > | 0.5 | 0 | 0.5 -> 0 + // 2 | 2n + 1.5 | 2n + 1 | 2n + 2 | 2n + 2 | > | 0.5 | 1 | 1.5 -> 2 + // 3 | -2n - 0.5 | -2n - 1 | -2n | -2n | < | -0.5 | 0 | -0.5 -> 0 + // 4 | -2n - 1.5 | -2n - 2 | -2n - 1 | -2n - 2 | < | -0.5 | 1 | -1.5 -> -2 + // (where n is a non-negative integer) + // + // Branch here for cases 1 and 4 + if ( + (x > 0 && x % 1 === +0.5 && (x & 1) === 0) || + (x < 0 && x % 1 === -0.5 && (x & 1) === 1) + ) { + return censorNegativeZero(MathFloor(x)); } - // Round x to the nearest integer, choosing the even integer if it lies halfway between two. - function evenRound(x) { - // There are four cases for numbers with fractional part being .5: - // - // case | x | floor(x) | round(x) | expected | x <> 0 | x % 1 | x & 1 | example - // 1 | 2n + 0.5 | 2n | 2n + 1 | 2n | > | 0.5 | 0 | 0.5 -> 0 - // 2 | 2n + 1.5 | 2n + 1 | 2n + 2 | 2n + 2 | > | 0.5 | 1 | 1.5 -> 2 - // 3 | -2n - 0.5 | -2n - 1 | -2n | -2n | < | -0.5 | 0 | -0.5 -> 0 - // 4 | -2n - 1.5 | -2n - 2 | -2n - 1 | -2n - 2 | < | -0.5 | 1 | -1.5 -> -2 - // (where n is a non-negative integer) - // - // Branch here for cases 1 and 4 - if ( - (x > 0 && x % 1 === +0.5 && (x & 1) === 0) || - (x < 0 && x % 1 === -0.5 && (x & 1) === 1) - ) { - return censorNegativeZero(MathFloor(x)); - } + return censorNegativeZero(MathRound(x)); +} - return censorNegativeZero(MathRound(x)); - } +function integerPart(n) { + return censorNegativeZero(MathTrunc(n)); +} - function integerPart(n) { - return censorNegativeZero(MathTrunc(n)); - } +function sign(x) { + return x < 0 ? -1 : 1; +} - function sign(x) { - return x < 0 ? -1 : 1; +function modulo(x, y) { + // https://tc39.github.io/ecma262/#eqn-modulo + // Note that http://stackoverflow.com/a/4467559/3191 does NOT work for large modulos + const signMightNotMatch = x % y; + if (sign(y) !== sign(signMightNotMatch)) { + return signMightNotMatch + y; } - - function modulo(x, y) { - // https://tc39.github.io/ecma262/#eqn-modulo - // Note that http://stackoverflow.com/a/4467559/3191 does NOT work for large modulos - const signMightNotMatch = x % y; - if (sign(y) !== sign(signMightNotMatch)) { - return signMightNotMatch + y; - } - return signMightNotMatch; + return signMightNotMatch; +} + +function censorNegativeZero(x) { + return x === 0 ? 0 : x; +} + +function createIntegerConversion(bitLength, typeOpts) { + const isSigned = !typeOpts.unsigned; + + let lowerBound; + let upperBound; + if (bitLength === 64) { + upperBound = NumberMAX_SAFE_INTEGER; + lowerBound = !isSigned ? 0 : NumberMIN_SAFE_INTEGER; + } else if (!isSigned) { + lowerBound = 0; + upperBound = MathPow(2, bitLength) - 1; + } else { + lowerBound = -MathPow(2, bitLength - 1); + upperBound = MathPow(2, bitLength - 1) - 1; } - function censorNegativeZero(x) { - return x === 0 ? 0 : x; - } + const twoToTheBitLength = MathPow(2, bitLength); + const twoToOneLessThanTheBitLength = MathPow(2, bitLength - 1); - function createIntegerConversion(bitLength, typeOpts) { - const isSigned = !typeOpts.unsigned; - - let lowerBound; - let upperBound; - if (bitLength === 64) { - upperBound = NumberMAX_SAFE_INTEGER; - lowerBound = !isSigned ? 0 : NumberMIN_SAFE_INTEGER; - } else if (!isSigned) { - lowerBound = 0; - upperBound = MathPow(2, bitLength) - 1; - } else { - lowerBound = -MathPow(2, bitLength - 1); - upperBound = MathPow(2, bitLength - 1) - 1; + return (V, opts = {}) => { + let x = toNumber(V); + x = censorNegativeZero(x); + + if (opts.enforceRange) { + if (!NumberIsFinite(x)) { + throw makeException(TypeError, "is not a finite number", opts); + } + + x = integerPart(x); + + if (x < lowerBound || x > upperBound) { + throw makeException( + TypeError, + `is outside the accepted range of ${lowerBound} to ${upperBound}, inclusive`, + opts, + ); + } + + return x; } - const twoToTheBitLength = MathPow(2, bitLength); - const twoToOneLessThanTheBitLength = MathPow(2, bitLength - 1); + if (!NumberIsNaN(x) && opts.clamp) { + x = MathMin(MathMax(x, lowerBound), upperBound); + x = evenRound(x); + return x; + } - return (V, opts = {}) => { - let x = toNumber(V); - x = censorNegativeZero(x); + if (!NumberIsFinite(x) || x === 0) { + return 0; + } + x = integerPart(x); - if (opts.enforceRange) { - if (!NumberIsFinite(x)) { - throw makeException(TypeError, "is not a finite number", opts); - } + // Math.pow(2, 64) is not accurately representable in JavaScript, so try to avoid these per-spec operations if + // possible. Hopefully it's an optimization for the non-64-bitLength cases too. + if (x >= lowerBound && x <= upperBound) { + return x; + } - x = integerPart(x); + // These will not work great for bitLength of 64, but oh well. See the README for more details. + x = modulo(x, twoToTheBitLength); + if (isSigned && x >= twoToOneLessThanTheBitLength) { + return x - twoToTheBitLength; + } + return x; + }; +} - if (x < lowerBound || x > upperBound) { - throw makeException( - TypeError, - `is outside the accepted range of ${lowerBound} to ${upperBound}, inclusive`, - opts, - ); - } +function createLongLongConversion(bitLength, { unsigned }) { + const upperBound = NumberMAX_SAFE_INTEGER; + const lowerBound = unsigned ? 0 : NumberMIN_SAFE_INTEGER; + const asBigIntN = unsigned ? BigIntAsUintN : BigIntAsIntN; - return x; - } + return (V, opts = {}) => { + let x = toNumber(V); + x = censorNegativeZero(x); - if (!NumberIsNaN(x) && opts.clamp) { - x = MathMin(MathMax(x, lowerBound), upperBound); - x = evenRound(x); - return x; + if (opts.enforceRange) { + if (!NumberIsFinite(x)) { + throw makeException(TypeError, "is not a finite number", opts); } - if (!NumberIsFinite(x) || x === 0) { - return 0; - } x = integerPart(x); - // Math.pow(2, 64) is not accurately representable in JavaScript, so try to avoid these per-spec operations if - // possible. Hopefully it's an optimization for the non-64-bitLength cases too. - if (x >= lowerBound && x <= upperBound) { - return x; + if (x < lowerBound || x > upperBound) { + throw makeException( + TypeError, + `is outside the accepted range of ${lowerBound} to ${upperBound}, inclusive`, + opts, + ); } - // These will not work great for bitLength of 64, but oh well. See the README for more details. - x = modulo(x, twoToTheBitLength); - if (isSigned && x >= twoToOneLessThanTheBitLength) { - return x - twoToTheBitLength; - } return x; - }; - } + } - function createLongLongConversion(bitLength, { unsigned }) { - const upperBound = NumberMAX_SAFE_INTEGER; - const lowerBound = unsigned ? 0 : NumberMIN_SAFE_INTEGER; - const asBigIntN = unsigned ? BigIntAsUintN : BigIntAsIntN; + if (!NumberIsNaN(x) && opts.clamp) { + x = MathMin(MathMax(x, lowerBound), upperBound); + x = evenRound(x); + return x; + } - return (V, opts = {}) => { - let x = toNumber(V); - x = censorNegativeZero(x); + if (!NumberIsFinite(x) || x === 0) { + return 0; + } - if (opts.enforceRange) { - if (!NumberIsFinite(x)) { - throw makeException(TypeError, "is not a finite number", opts); - } + let xBigInt = BigInt(integerPart(x)); + xBigInt = asBigIntN(bitLength, xBigInt); + return Number(xBigInt); + }; +} - x = integerPart(x); +const converters = []; - if (x < lowerBound || x > upperBound) { - throw makeException( - TypeError, - `is outside the accepted range of ${lowerBound} to ${upperBound}, inclusive`, - opts, - ); - } +converters.any = (V) => { + return V; +}; - return x; - } +converters.boolean = function (val) { + return !!val; +}; - if (!NumberIsNaN(x) && opts.clamp) { - x = MathMin(MathMax(x, lowerBound), upperBound); - x = evenRound(x); - return x; - } +converters.byte = createIntegerConversion(8, { unsigned: false }); +converters.octet = createIntegerConversion(8, { unsigned: true }); - if (!NumberIsFinite(x) || x === 0) { - return 0; - } +converters.short = createIntegerConversion(16, { unsigned: false }); +converters["unsigned short"] = createIntegerConversion(16, { + unsigned: true, +}); - let xBigInt = BigInt(integerPart(x)); - xBigInt = asBigIntN(bitLength, xBigInt); - return Number(xBigInt); - }; +converters.long = createIntegerConversion(32, { unsigned: false }); +converters["unsigned long"] = createIntegerConversion(32, { unsigned: true }); + +converters["long long"] = createLongLongConversion(64, { unsigned: false }); +converters["unsigned long long"] = createLongLongConversion(64, { + unsigned: true, +}); + +converters.float = (V, opts) => { + const x = toNumber(V); + + if (!NumberIsFinite(x)) { + throw makeException( + TypeError, + "is not a finite floating-point value", + opts, + ); } - const converters = []; + if (ObjectIs(x, -0)) { + return x; + } - converters.any = (V) => { - return V; - }; + const y = MathFround(x); - converters.boolean = function (val) { - return !!val; - }; + if (!NumberIsFinite(y)) { + throw makeException( + TypeError, + "is outside the range of a single-precision floating-point value", + opts, + ); + } - converters.byte = createIntegerConversion(8, { unsigned: false }); - converters.octet = createIntegerConversion(8, { unsigned: true }); + return y; +}; - converters.short = createIntegerConversion(16, { unsigned: false }); - converters["unsigned short"] = createIntegerConversion(16, { - unsigned: true, - }); +converters["unrestricted float"] = (V, _opts) => { + const x = toNumber(V); - converters.long = createIntegerConversion(32, { unsigned: false }); - converters["unsigned long"] = createIntegerConversion(32, { unsigned: true }); + if (isNaN(x)) { + return x; + } - converters["long long"] = createLongLongConversion(64, { unsigned: false }); - converters["unsigned long long"] = createLongLongConversion(64, { - unsigned: true, - }); + if (ObjectIs(x, -0)) { + return x; + } - converters.float = (V, opts) => { - const x = toNumber(V); + return MathFround(x); +}; - if (!NumberIsFinite(x)) { - throw makeException( - TypeError, - "is not a finite floating-point value", - opts, - ); - } +converters.double = (V, opts) => { + const x = toNumber(V); - if (ObjectIs(x, -0)) { - return x; - } + if (!NumberIsFinite(x)) { + throw makeException( + TypeError, + "is not a finite floating-point value", + opts, + ); + } - const y = MathFround(x); + return x; +}; - if (!NumberIsFinite(y)) { - throw makeException( - TypeError, - "is outside the range of a single-precision floating-point value", - opts, - ); - } +converters["unrestricted double"] = (V, _opts) => { + const x = toNumber(V); - return y; - }; + return x; +}; - converters["unrestricted float"] = (V, _opts) => { - const x = toNumber(V); +converters.DOMString = function (V, opts = {}) { + if (typeof V === "string") { + return V; + } else if (V === null && opts.treatNullAsEmptyString) { + return ""; + } else if (typeof V === "symbol") { + throw makeException( + TypeError, + "is a symbol, which cannot be converted to a string", + opts, + ); + } - if (isNaN(x)) { - return x; - } + return String(V); +}; - if (ObjectIs(x, -0)) { - return x; +// deno-lint-ignore no-control-regex +const IS_BYTE_STRING = /^[\x00-\xFF]*$/; +converters.ByteString = (V, opts) => { + const x = converters.DOMString(V, opts); + if (!RegExpPrototypeTest(IS_BYTE_STRING, x)) { + throw makeException(TypeError, "is not a valid ByteString", opts); + } + return x; +}; + +converters.USVString = (V, opts) => { + const S = converters.DOMString(V, opts); + const n = S.length; + let U = ""; + for (let i = 0; i < n; ++i) { + const c = StringPrototypeCharCodeAt(S, i); + if (c < 0xd800 || c > 0xdfff) { + U += StringFromCodePoint(c); + } else if (0xdc00 <= c && c <= 0xdfff) { + U += StringFromCodePoint(0xfffd); + } else if (i === n - 1) { + U += StringFromCodePoint(0xfffd); + } else { + const d = StringPrototypeCharCodeAt(S, i + 1); + if (0xdc00 <= d && d <= 0xdfff) { + const a = c & 0x3ff; + const b = d & 0x3ff; + U += StringFromCodePoint((2 << 15) + (2 << 9) * a + b); + ++i; + } else { + U += StringFromCodePoint(0xfffd); + } } + } + return U; +}; - return MathFround(x); - }; +converters.object = (V, opts) => { + if (type(V) !== "Object") { + throw makeException(TypeError, "is not an object", opts); + } - converters.double = (V, opts) => { - const x = toNumber(V); + return V; +}; - if (!NumberIsFinite(x)) { +// Not exported, but used in Function and VoidFunction. + +// Neither Function nor VoidFunction is defined with [TreatNonObjectAsNull], so +// handling for that is omitted. +function convertCallbackFunction(V, opts) { + if (typeof V !== "function") { + throw makeException(TypeError, "is not a function", opts); + } + return V; +} + +function isDataView(V) { + return ArrayBufferIsView(V) && + TypedArrayPrototypeGetSymbolToStringTag(V) === undefined; +} + +function isNonSharedArrayBuffer(V) { + return ObjectPrototypeIsPrototypeOf(ArrayBufferPrototype, V); +} + +function isSharedArrayBuffer(V) { + // deno-lint-ignore prefer-primordials + return ObjectPrototypeIsPrototypeOf(SharedArrayBuffer.prototype, V); +} + +converters.ArrayBuffer = (V, opts = {}) => { + if (!isNonSharedArrayBuffer(V)) { + if (opts.allowShared && !isSharedArrayBuffer(V)) { throw makeException( TypeError, - "is not a finite floating-point value", + "is not an ArrayBuffer or SharedArrayBuffer", opts, ); } + throw makeException(TypeError, "is not an ArrayBuffer", opts); + } - return x; - }; + return V; +}; - converters["unrestricted double"] = (V, _opts) => { - const x = toNumber(V); +converters.DataView = (V, opts = {}) => { + if (!isDataView(V)) { + throw makeException(TypeError, "is not a DataView", opts); + } - return x; - }; + if (!opts.allowShared && isSharedArrayBuffer(V.buffer)) { + throw makeException( + TypeError, + "is backed by a SharedArrayBuffer, which is not allowed", + opts, + ); + } + + return V; +}; + +ArrayPrototypeForEach( + [ + Int8Array, + Int16Array, + Int32Array, + Uint8Array, + Uint16Array, + Uint32Array, + Uint8ClampedArray, + Float32Array, + Float64Array, + ], + (func) => { + const name = func.name; + const article = RegExpPrototypeTest(/^[AEIOU]/, name) ? "an" : "a"; + converters[name] = (V, opts = {}) => { + if (TypedArrayPrototypeGetSymbolToStringTag(V) !== name) { + throw makeException( + TypeError, + `is not ${article} ${name} object`, + opts, + ); + } + if (!opts.allowShared && isSharedArrayBuffer(V.buffer)) { + throw makeException( + TypeError, + "is a view on a SharedArrayBuffer, which is not allowed", + opts, + ); + } - converters.DOMString = function (V, opts = {}) { - if (typeof V === "string") { return V; - } else if (V === null && opts.treatNullAsEmptyString) { - return ""; - } else if (typeof V === "symbol") { + }; + }, +); + +// Common definitions + +converters.ArrayBufferView = (V, opts = {}) => { + if (!ArrayBufferIsView(V)) { + throw makeException( + TypeError, + "is not a view on an ArrayBuffer or SharedArrayBuffer", + opts, + ); + } + + if (!opts.allowShared && isSharedArrayBuffer(V.buffer)) { + throw makeException( + TypeError, + "is a view on a SharedArrayBuffer, which is not allowed", + opts, + ); + } + + return V; +}; + +converters.BufferSource = (V, opts = {}) => { + if (ArrayBufferIsView(V)) { + if (!opts.allowShared && isSharedArrayBuffer(V.buffer)) { throw makeException( TypeError, - "is a symbol, which cannot be converted to a string", + "is a view on a SharedArrayBuffer, which is not allowed", opts, ); } - return String(V); - }; + return V; + } + + if (!opts.allowShared && !isNonSharedArrayBuffer(V)) { + throw makeException( + TypeError, + "is not an ArrayBuffer or a view on one", + opts, + ); + } + if ( + opts.allowShared && + !isSharedArrayBuffer(V) && + !isNonSharedArrayBuffer(V) + ) { + throw makeException( + TypeError, + "is not an ArrayBuffer, SharedArrayBuffer, or a view on one", + opts, + ); + } - // deno-lint-ignore no-control-regex - const IS_BYTE_STRING = /^[\x00-\xFF]*$/; - converters.ByteString = (V, opts) => { - const x = converters.DOMString(V, opts); - if (!RegExpPrototypeTest(IS_BYTE_STRING, x)) { - throw makeException(TypeError, "is not a valid ByteString", opts); + return V; +}; + +converters.DOMTimeStamp = converters["unsigned long long"]; +converters.DOMHighResTimeStamp = converters["double"]; + +converters.Function = convertCallbackFunction; + +converters.VoidFunction = convertCallbackFunction; + +converters["UVString?"] = createNullableConverter( + converters.USVString, +); +converters["sequence"] = createSequenceConverter( + converters.double, +); +converters["sequence"] = createSequenceConverter( + converters.object, +); +converters["Promise"] = createPromiseConverter(() => undefined); + +converters["sequence"] = createSequenceConverter( + converters.ByteString, +); +converters["sequence>"] = createSequenceConverter( + converters["sequence"], +); +converters["record"] = createRecordConverter( + converters.ByteString, + converters.ByteString, +); + +converters["sequence"] = createSequenceConverter( + converters.USVString, +); +converters["sequence>"] = createSequenceConverter( + converters["sequence"], +); +converters["record"] = createRecordConverter( + converters.USVString, + converters.USVString, +); + +converters["sequence"] = createSequenceConverter( + converters.DOMString, +); + +function requiredArguments(length, required, opts = {}) { + if (length < required) { + const errMsg = `${ + opts.prefix ? opts.prefix + ": " : "" + }${required} argument${ + required === 1 ? "" : "s" + } required, but only ${length} present.`; + throw new TypeError(errMsg); + } +} + +function createDictionaryConverter(name, ...dictionaries) { + let hasRequiredKey = false; + const allMembers = []; + for (let i = 0; i < dictionaries.length; ++i) { + const members = dictionaries[i]; + for (let j = 0; j < members.length; ++j) { + const member = members[j]; + if (member.required) { + hasRequiredKey = true; + } + ArrayPrototypePush(allMembers, member); } - return x; - }; + } + ArrayPrototypeSort(allMembers, (a, b) => { + if (a.key == b.key) { + return 0; + } + return a.key < b.key ? -1 : 1; + }); - converters.USVString = (V, opts) => { - const S = converters.DOMString(V, opts); - const n = S.length; - let U = ""; - for (let i = 0; i < n; ++i) { - const c = StringPrototypeCharCodeAt(S, i); - if (c < 0xd800 || c > 0xdfff) { - U += StringFromCodePoint(c); - } else if (0xdc00 <= c && c <= 0xdfff) { - U += StringFromCodePoint(0xfffd); - } else if (i === n - 1) { - U += StringFromCodePoint(0xfffd); + const defaultValues = {}; + for (let i = 0; i < allMembers.length; ++i) { + const member = allMembers[i]; + if (ReflectHas(member, "defaultValue")) { + const idlMemberValue = member.defaultValue; + const imvType = typeof idlMemberValue; + // Copy by value types can be directly assigned, copy by reference types + // need to be re-created for each allocation. + if ( + imvType === "number" || imvType === "boolean" || + imvType === "string" || imvType === "bigint" || + imvType === "undefined" + ) { + defaultValues[member.key] = member.converter(idlMemberValue, {}); } else { - const d = StringPrototypeCharCodeAt(S, i + 1); - if (0xdc00 <= d && d <= 0xdfff) { - const a = c & 0x3ff; - const b = d & 0x3ff; - U += StringFromCodePoint((2 << 15) + (2 << 9) * a + b); - ++i; - } else { - U += StringFromCodePoint(0xfffd); - } + ObjectDefineProperty(defaultValues, member.key, { + get() { + return member.converter(idlMemberValue, member.defaultValue); + }, + enumerable: true, + }); } } - return U; - }; + } - converters.object = (V, opts) => { - if (type(V) !== "Object") { - throw makeException(TypeError, "is not an object", opts); + return function (V, opts = {}) { + const typeV = type(V); + switch (typeV) { + case "Undefined": + case "Null": + case "Object": + break; + default: + throw makeException( + TypeError, + "can not be converted to a dictionary", + opts, + ); } + const esDict = V; - return V; - }; + const idlDict = ObjectAssign({}, defaultValues); - // Not exported, but used in Function and VoidFunction. - - // Neither Function nor VoidFunction is defined with [TreatNonObjectAsNull], so - // handling for that is omitted. - function convertCallbackFunction(V, opts) { - if (typeof V !== "function") { - throw makeException(TypeError, "is not a function", opts); + // NOTE: fast path Null and Undefined. + if ((V === undefined || V === null) && !hasRequiredKey) { + return idlDict; } - return V; - } - - function isDataView(V) { - return ArrayBufferIsView(V) && - TypedArrayPrototypeGetSymbolToStringTag(V) === undefined; - } - function isNonSharedArrayBuffer(V) { - return ObjectPrototypeIsPrototypeOf(ArrayBufferPrototype, V); - } + for (let i = 0; i < allMembers.length; ++i) { + const member = allMembers[i]; + const key = member.key; - function isSharedArrayBuffer(V) { - // deno-lint-ignore prefer-primordials - return ObjectPrototypeIsPrototypeOf(SharedArrayBuffer.prototype, V); - } + let esMemberValue; + if (typeV === "Undefined" || typeV === "Null") { + esMemberValue = undefined; + } else { + esMemberValue = esDict[key]; + } - converters.ArrayBuffer = (V, opts = {}) => { - if (!isNonSharedArrayBuffer(V)) { - if (opts.allowShared && !isSharedArrayBuffer(V)) { + if (esMemberValue !== undefined) { + const context = `'${key}' of '${name}'${ + opts.context ? ` (${opts.context})` : "" + }`; + const converter = member.converter; + const idlMemberValue = converter(esMemberValue, { ...opts, context }); + idlDict[key] = idlMemberValue; + } else if (member.required) { throw makeException( TypeError, - "is not an ArrayBuffer or SharedArrayBuffer", + `can not be converted to '${name}' because '${key}' is required in '${name}'.`, opts, ); } - throw makeException(TypeError, "is not an ArrayBuffer", opts); } - return V; + return idlDict; }; +} - converters.DataView = (V, opts = {}) => { - if (!isDataView(V)) { - throw makeException(TypeError, "is not a DataView", opts); - } +// https://heycam.github.io/webidl/#es-enumeration +function createEnumConverter(name, values) { + const E = new Set(values); - if (!opts.allowShared && isSharedArrayBuffer(V.buffer)) { - throw makeException( - TypeError, - "is backed by a SharedArrayBuffer, which is not allowed", - opts, + return function (V, opts = {}) { + const S = String(V); + + if (!E.has(S)) { + throw new TypeError( + `${ + opts.prefix ? opts.prefix + ": " : "" + }The provided value '${S}' is not a valid enum value of type ${name}.`, ); } - return V; + return S; }; +} + +function createNullableConverter(converter) { + return (V, opts = {}) => { + // FIXME: If Type(V) is not Object, and the conversion to an IDL value is + // being performed due to V being assigned to an attribute whose type is a + // nullable callback function that is annotated with + // [LegacyTreatNonObjectAsNull], then return the IDL nullable type T? + // value null. + + if (V === null || V === undefined) return null; + return converter(V, opts); + }; +} - // Returns the unforgeable `TypedArray` constructor name or `undefined`, - // if the `this` value isn't a valid `TypedArray` object. - // - // https://tc39.es/ecma262/#sec-get-%typedarray%.prototype-@@tostringtag - const typedArrayNameGetter = ObjectGetOwnPropertyDescriptor( - ObjectGetPrototypeOf(Uint8Array).prototype, - SymbolToStringTag, - ).get; - ArrayPrototypeForEach( - [ - Int8Array, - Int16Array, - Int32Array, - Uint8Array, - Uint16Array, - Uint32Array, - Uint8ClampedArray, - Float32Array, - Float64Array, - ], - (func) => { - const name = func.name; - const article = RegExpPrototypeTest(/^[AEIOU]/, name) ? "an" : "a"; - converters[name] = (V, opts = {}) => { - if (!ArrayBufferIsView(V) || typedArrayNameGetter.call(V) !== name) { - throw makeException( - TypeError, - `is not ${article} ${name} object`, - opts, - ); - } - if (!opts.allowShared && isSharedArrayBuffer(V.buffer)) { - throw makeException( - TypeError, - "is a view on a SharedArrayBuffer, which is not allowed", - opts, - ); - } - - return V; - }; - }, - ); - - // Common definitions - - converters.ArrayBufferView = (V, opts = {}) => { - if (!ArrayBufferIsView(V)) { +// https://heycam.github.io/webidl/#es-sequence +function createSequenceConverter(converter) { + return function (V, opts = {}) { + if (type(V) !== "Object") { throw makeException( TypeError, - "is not a view on an ArrayBuffer or SharedArrayBuffer", + "can not be converted to sequence.", opts, ); } - - if (!opts.allowShared && isSharedArrayBuffer(V.buffer)) { + const iter = V?.[SymbolIterator]?.(); + if (iter === undefined) { throw makeException( TypeError, - "is a view on a SharedArrayBuffer, which is not allowed", + "can not be converted to sequence.", opts, ); } - - return V; - }; - - converters.BufferSource = (V, opts = {}) => { - if (ArrayBufferIsView(V)) { - if (!opts.allowShared && isSharedArrayBuffer(V.buffer)) { + const array = []; + while (true) { + const res = iter?.next?.(); + if (res === undefined) { throw makeException( TypeError, - "is a view on a SharedArrayBuffer, which is not allowed", + "can not be converted to sequence.", opts, ); } - - return V; + if (res.done === true) break; + const val = converter(res.value, { + ...opts, + context: `${opts.context}, index ${array.length}`, + }); + ArrayPrototypePush(array, val); } + return array; + }; +} - if (!opts.allowShared && !isNonSharedArrayBuffer(V)) { - throw makeException( - TypeError, - "is not an ArrayBuffer or a view on one", - opts, - ); - } - if ( - opts.allowShared && - !isSharedArrayBuffer(V) && - !isNonSharedArrayBuffer(V) - ) { +function createRecordConverter(keyConverter, valueConverter) { + return (V, opts) => { + if (type(V) !== "Object") { throw makeException( TypeError, - "is not an ArrayBuffer, SharedArrayBuffer, or a view on one", + "can not be converted to dictionary.", opts, ); } - - return V; - }; - - converters.DOMTimeStamp = converters["unsigned long long"]; - converters.DOMHighResTimeStamp = converters["double"]; - - converters.Function = convertCallbackFunction; - - converters.VoidFunction = convertCallbackFunction; - - converters["UVString?"] = createNullableConverter( - converters.USVString, - ); - converters["sequence"] = createSequenceConverter( - converters.double, - ); - converters["sequence"] = createSequenceConverter( - converters.object, - ); - converters["Promise"] = createPromiseConverter(() => undefined); - - converters["sequence"] = createSequenceConverter( - converters.ByteString, - ); - converters["sequence>"] = createSequenceConverter( - converters["sequence"], - ); - converters["record"] = createRecordConverter( - converters.ByteString, - converters.ByteString, - ); - - converters["sequence"] = createSequenceConverter( - converters.USVString, - ); - converters["sequence>"] = createSequenceConverter( - converters["sequence"], - ); - converters["record"] = createRecordConverter( - converters.USVString, - converters.USVString, - ); - - converters["sequence"] = createSequenceConverter( - converters.DOMString, - ); - - function requiredArguments(length, required, opts = {}) { - if (length < required) { - const errMsg = `${ - opts.prefix ? opts.prefix + ": " : "" - }${required} argument${ - required === 1 ? "" : "s" - } required, but only ${length} present.`; - throw new TypeError(errMsg); - } - } - - function createDictionaryConverter(name, ...dictionaries) { - let hasRequiredKey = false; - const allMembers = []; - for (let i = 0; i < dictionaries.length; ++i) { - const members = dictionaries[i]; - for (let j = 0; j < members.length; ++j) { - const member = members[j]; - if (member.required) { - hasRequiredKey = true; + const result = {}; + // Fast path for common case (not a Proxy) + if (!core.isProxy(V)) { + for (const key in V) { + if (!ObjectPrototypeHasOwnProperty(V, key)) { + continue; } - ArrayPrototypePush(allMembers, member); + const typedKey = keyConverter(key, opts); + const value = V[key]; + const typedValue = valueConverter(value, opts); + result[typedKey] = typedValue; } + return result; } - ArrayPrototypeSort(allMembers, (a, b) => { - if (a.key == b.key) { - return 0; + // Slow path if Proxy (e.g: in WPT tests) + const keys = ReflectOwnKeys(V); + for (let i = 0; i < keys.length; ++i) { + const key = keys[i]; + const desc = ObjectGetOwnPropertyDescriptor(V, key); + if (desc !== undefined && desc.enumerable === true) { + const typedKey = keyConverter(key, opts); + const value = V[key]; + const typedValue = valueConverter(value, opts); + result[typedKey] = typedValue; } - return a.key < b.key ? -1 : 1; + } + return result; + }; +} + +function createPromiseConverter(converter) { + return (V, opts) => + PromisePrototypeThen(PromiseResolve(V), (V) => converter(V, opts)); +} + +function invokeCallbackFunction( + callable, + args, + thisArg, + returnValueConverter, + opts, +) { + try { + const rv = ReflectApply(callable, thisArg, args); + return returnValueConverter(rv, { + prefix: opts.prefix, + context: "return value", }); - - const defaultValues = {}; - for (let i = 0; i < allMembers.length; ++i) { - const member = allMembers[i]; - if (ReflectHas(member, "defaultValue")) { - const idlMemberValue = member.defaultValue; - const imvType = typeof idlMemberValue; - // Copy by value types can be directly assigned, copy by reference types - // need to be re-created for each allocation. - if ( - imvType === "number" || imvType === "boolean" || - imvType === "string" || imvType === "bigint" || - imvType === "undefined" - ) { - defaultValues[member.key] = member.converter(idlMemberValue, {}); - } else { - ObjectDefineProperty(defaultValues, member.key, { - get() { - return member.converter(idlMemberValue, member.defaultValue); - }, - enumerable: true, - }); - } - } + } catch (err) { + if (opts.returnsPromise === true) { + return PromiseReject(err); } + throw err; + } +} - return function (V, opts = {}) { - const typeV = type(V); - switch (typeV) { - case "Undefined": - case "Null": - case "Object": - break; - default: - throw makeException( - TypeError, - "can not be converted to a dictionary", - opts, - ); - } - const esDict = V; - - const idlDict = ObjectAssign({}, defaultValues); - - // NOTE: fast path Null and Undefined. - if ((V === undefined || V === null) && !hasRequiredKey) { - return idlDict; - } - - for (let i = 0; i < allMembers.length; ++i) { - const member = allMembers[i]; - const key = member.key; - - let esMemberValue; - if (typeV === "Undefined" || typeV === "Null") { - esMemberValue = undefined; - } else { - esMemberValue = esDict[key]; - } - - if (esMemberValue !== undefined) { - const context = `'${key}' of '${name}'${ - opts.context ? ` (${opts.context})` : "" - }`; - const converter = member.converter; - const idlMemberValue = converter(esMemberValue, { ...opts, context }); - idlDict[key] = idlMemberValue; - } else if (member.required) { - throw makeException( - TypeError, - `can not be converted to '${name}' because '${key}' is required in '${name}'.`, - opts, - ); - } - } +const brand = Symbol("[[webidl.brand]]"); - return idlDict; - }; +function createInterfaceConverter(name, prototype) { + return (V, opts) => { + if (!ObjectPrototypeIsPrototypeOf(prototype, V) || V[brand] !== brand) { + throw makeException(TypeError, `is not of type ${name}.`, opts); + } + return V; + }; +} + +// TODO(lucacasonato): have the user pass in the prototype, and not the type. +function createBranded(Type) { + const t = ObjectCreate(Type.prototype); + t[brand] = brand; + return t; +} + +function assertBranded(self, prototype) { + if ( + !ObjectPrototypeIsPrototypeOf(prototype, self) || self[brand] !== brand + ) { + throw new TypeError("Illegal invocation"); + } +} + +function illegalConstructor() { + throw new TypeError("Illegal constructor"); +} + +function define(target, source) { + const keys = ReflectOwnKeys(source); + for (let i = 0; i < keys.length; ++i) { + const key = keys[i]; + const descriptor = ReflectGetOwnPropertyDescriptor(source, key); + if (descriptor && !ReflectDefineProperty(target, key, descriptor)) { + throw new TypeError(`Cannot redefine property: ${String(key)}`); + } } +} - // https://heycam.github.io/webidl/#es-enumeration - function createEnumConverter(name, values) { - const E = new Set(values); +const _iteratorInternal = Symbol("iterator internal"); - return function (V, opts = {}) { - const S = String(V); +const globalIteratorPrototype = ObjectGetPrototypeOf(ArrayIteratorPrototype); - if (!E.has(S)) { +function mixinPairIterable(name, prototype, dataSymbol, keyKey, valueKey) { + const iteratorPrototype = ObjectCreate(globalIteratorPrototype, { + [SymbolToStringTag]: { configurable: true, value: `${name} Iterator` }, + }); + define(iteratorPrototype, { + next() { + const internal = this && this[_iteratorInternal]; + if (!internal) { throw new TypeError( - `${ - opts.prefix ? opts.prefix + ": " : "" - }The provided value '${S}' is not a valid enum value of type ${name}.`, + `next() called on a value that is not a ${name} iterator object`, ); } - - return S; - }; + const { target, kind, index } = internal; + const values = target[dataSymbol]; + const len = values.length; + if (index >= len) { + return { value: undefined, done: true }; + } + const pair = values[index]; + internal.index = index + 1; + let result; + switch (kind) { + case "key": + result = pair[keyKey]; + break; + case "value": + result = pair[valueKey]; + break; + case "key+value": + result = [pair[keyKey], pair[valueKey]]; + break; + } + return { value: result, done: false }; + }, + }); + function createDefaultIterator(target, kind) { + const iterator = ObjectCreate(iteratorPrototype); + ObjectDefineProperty(iterator, _iteratorInternal, { + value: { target, kind, index: 0 }, + configurable: true, + }); + return iterator; } - function createNullableConverter(converter) { - return (V, opts = {}) => { - // FIXME: If Type(V) is not Object, and the conversion to an IDL value is - // being performed due to V being assigned to an attribute whose type is a - // nullable callback function that is annotated with - // [LegacyTreatNonObjectAsNull], then return the IDL nullable type T? - // value null. - - if (V === null || V === undefined) return null; - return converter(V, opts); - }; + function entries() { + assertBranded(this, prototype.prototype); + return createDefaultIterator(this, "key+value"); } - // https://heycam.github.io/webidl/#es-sequence - function createSequenceConverter(converter) { - return function (V, opts = {}) { - if (type(V) !== "Object") { - throw makeException( - TypeError, - "can not be converted to sequence.", - opts, - ); - } - const iter = V?.[SymbolIterator]?.(); - if (iter === undefined) { - throw makeException( - TypeError, - "can not be converted to sequence.", - opts, - ); - } - const array = []; - while (true) { - const res = iter?.next?.(); - if (res === undefined) { - throw makeException( - TypeError, - "can not be converted to sequence.", - opts, - ); - } - if (res.done === true) break; - const val = converter(res.value, { - ...opts, - context: `${opts.context}, index ${array.length}`, + const properties = { + entries: { + value: entries, + writable: true, + enumerable: true, + configurable: true, + }, + [SymbolIterator]: { + value: entries, + writable: true, + enumerable: false, + configurable: true, + }, + keys: { + value: function keys() { + assertBranded(this, prototype.prototype); + return createDefaultIterator(this, "key"); + }, + writable: true, + enumerable: true, + configurable: true, + }, + values: { + value: function values() { + assertBranded(this, prototype.prototype); + return createDefaultIterator(this, "value"); + }, + writable: true, + enumerable: true, + configurable: true, + }, + forEach: { + value: function forEach(idlCallback, thisArg = undefined) { + assertBranded(this, prototype.prototype); + const prefix = `Failed to execute 'forEach' on '${name}'`; + requiredArguments(arguments.length, 1, { prefix }); + idlCallback = converters["Function"](idlCallback, { + prefix, + context: "Argument 1", }); - ArrayPrototypePush(array, val); - } - return array; - }; - } - - function createRecordConverter(keyConverter, valueConverter) { - return (V, opts) => { - if (type(V) !== "Object") { - throw makeException( - TypeError, - "can not be converted to dictionary.", - opts, + idlCallback = FunctionPrototypeBind( + idlCallback, + thisArg ?? globalThis, ); - } - const result = {}; - // Fast path for common case (not a Proxy) - if (!core.isProxy(V)) { - for (const key in V) { - if (!ObjectPrototypeHasOwnProperty(V, key)) { - continue; - } - const typedKey = keyConverter(key, opts); - const value = V[key]; - const typedValue = valueConverter(value, opts); - result[typedKey] = typedValue; + const pairs = this[dataSymbol]; + for (let i = 0; i < pairs.length; i++) { + const entry = pairs[i]; + idlCallback(entry[valueKey], entry[keyKey], this); } - return result; - } - // Slow path if Proxy (e.g: in WPT tests) - const keys = ReflectOwnKeys(V); - for (let i = 0; i < keys.length; ++i) { - const key = keys[i]; - const desc = ObjectGetOwnPropertyDescriptor(V, key); - if (desc !== undefined && desc.enumerable === true) { - const typedKey = keyConverter(key, opts); - const value = V[key]; - const typedValue = valueConverter(value, opts); - result[typedKey] = typedValue; - } - } - return result; - }; - } - - function createPromiseConverter(converter) { - return (V, opts) => - PromisePrototypeThen(PromiseResolve(V), (V) => converter(V, opts)); - } - - function invokeCallbackFunction( - callable, - args, - thisArg, - returnValueConverter, - opts, - ) { - try { - const rv = ReflectApply(callable, thisArg, args); - return returnValueConverter(rv, { - prefix: opts.prefix, - context: "return value", - }); - } catch (err) { - if (opts.returnsPromise === true) { - return PromiseReject(err); - } - throw err; + }, + writable: true, + enumerable: true, + configurable: true, + }, + }; + return ObjectDefineProperties(prototype.prototype, properties); +} + +function configurePrototype(prototype) { + const descriptors = ObjectGetOwnPropertyDescriptors(prototype.prototype); + for (const key in descriptors) { + if (!ObjectPrototypeHasOwnProperty(descriptors, key)) { + continue; } - } - - const brand = Symbol("[[webidl.brand]]"); - - function createInterfaceConverter(name, prototype) { - return (V, opts) => { - if (!ObjectPrototypeIsPrototypeOf(prototype, V) || V[brand] !== brand) { - throw makeException(TypeError, `is not of type ${name}.`, opts); - } - return V; - }; - } - - // TODO(lucacasonato): have the user pass in the prototype, and not the type. - function createBranded(Type) { - const t = ObjectCreate(Type.prototype); - t[brand] = brand; - return t; - } - - function assertBranded(self, prototype) { + if (key === "constructor") continue; + const descriptor = descriptors[key]; if ( - !ObjectPrototypeIsPrototypeOf(prototype, self) || self[brand] !== brand + ReflectHas(descriptor, "value") && + typeof descriptor.value === "function" ) { - throw new TypeError("Illegal invocation"); - } - } - - function illegalConstructor() { - throw new TypeError("Illegal constructor"); - } - - function define(target, source) { - const keys = ReflectOwnKeys(source); - for (let i = 0; i < keys.length; ++i) { - const key = keys[i]; - const descriptor = ReflectGetOwnPropertyDescriptor(source, key); - if (descriptor && !ReflectDefineProperty(target, key, descriptor)) { - throw new TypeError(`Cannot redefine property: ${String(key)}`); - } - } - } - - const _iteratorInternal = Symbol("iterator internal"); - - const globalIteratorPrototype = ObjectGetPrototypeOf(ArrayIteratorPrototype); - - function mixinPairIterable(name, prototype, dataSymbol, keyKey, valueKey) { - const iteratorPrototype = ObjectCreate(globalIteratorPrototype, { - [SymbolToStringTag]: { configurable: true, value: `${name} Iterator` }, - }); - define(iteratorPrototype, { - next() { - const internal = this && this[_iteratorInternal]; - if (!internal) { - throw new TypeError( - `next() called on a value that is not a ${name} iterator object`, - ); - } - const { target, kind, index } = internal; - const values = target[dataSymbol]; - const len = values.length; - if (index >= len) { - return { value: undefined, done: true }; - } - const pair = values[index]; - internal.index = index + 1; - let result; - switch (kind) { - case "key": - result = pair[keyKey]; - break; - case "value": - result = pair[valueKey]; - break; - case "key+value": - result = [pair[keyKey], pair[valueKey]]; - break; - } - return { value: result, done: false }; - }, - }); - function createDefaultIterator(target, kind) { - const iterator = ObjectCreate(iteratorPrototype); - ObjectDefineProperty(iterator, _iteratorInternal, { - value: { target, kind, index: 0 }, + ObjectDefineProperty(prototype.prototype, key, { + enumerable: true, + writable: true, + configurable: true, + }); + } else if (ReflectHas(descriptor, "get")) { + ObjectDefineProperty(prototype.prototype, key, { + enumerable: true, configurable: true, }); - return iterator; } + } + ObjectDefineProperty(prototype.prototype, SymbolToStringTag, { + value: prototype.name, + enumerable: false, + configurable: true, + writable: false, + }); +} - function entries() { - assertBranded(this, prototype.prototype); - return createDefaultIterator(this, "key+value"); - } +const setlikeInner = Symbol("[[set]]"); - const properties = { - entries: { - value: entries, - writable: true, - enumerable: true, - configurable: true, +// Ref: https://webidl.spec.whatwg.org/#es-setlike +function setlike(obj, objPrototype, readonly) { + ObjectDefineProperties(obj, { + size: { + configurable: true, + enumerable: true, + get() { + assertBranded(this, objPrototype); + return obj[setlikeInner].size; }, - [SymbolIterator]: { - value: entries, - writable: true, - enumerable: false, - configurable: true, + }, + [SymbolIterator]: { + configurable: true, + enumerable: false, + writable: true, + value() { + assertBranded(this, objPrototype); + return obj[setlikeInner][SymbolIterator](); }, - keys: { - value: function keys() { - assertBranded(this, prototype.prototype); - return createDefaultIterator(this, "key"); - }, - writable: true, - enumerable: true, - configurable: true, + }, + entries: { + configurable: true, + enumerable: true, + writable: true, + value() { + assertBranded(this, objPrototype); + return SetPrototypeEntries(obj[setlikeInner]); }, - values: { - value: function values() { - assertBranded(this, prototype.prototype); - return createDefaultIterator(this, "value"); - }, - writable: true, - enumerable: true, - configurable: true, + }, + keys: { + configurable: true, + enumerable: true, + writable: true, + value() { + assertBranded(this, objPrototype); + return SetPrototypeKeys(obj[setlikeInner]); }, - forEach: { - value: function forEach(idlCallback, thisArg = undefined) { - assertBranded(this, prototype.prototype); - const prefix = `Failed to execute 'forEach' on '${name}'`; - requiredArguments(arguments.length, 1, { prefix }); - idlCallback = converters["Function"](idlCallback, { - prefix, - context: "Argument 1", - }); - idlCallback = FunctionPrototypeBind( - idlCallback, - thisArg ?? globalThis, - ); - const pairs = this[dataSymbol]; - for (let i = 0; i < pairs.length; i++) { - const entry = pairs[i]; - idlCallback(entry[valueKey], entry[keyKey], this); - } - }, - writable: true, - enumerable: true, - configurable: true, + }, + values: { + configurable: true, + enumerable: true, + writable: true, + value() { + assertBranded(this, objPrototype); + return SetPrototypeValues(obj[setlikeInner]); }, - }; - return ObjectDefineProperties(prototype.prototype, properties); - } - - function configurePrototype(prototype) { - const descriptors = ObjectGetOwnPropertyDescriptors(prototype.prototype); - for (const key in descriptors) { - if (!ObjectPrototypeHasOwnProperty(descriptors, key)) { - continue; - } - if (key === "constructor") continue; - const descriptor = descriptors[key]; - if ( - ReflectHas(descriptor, "value") && - typeof descriptor.value === "function" - ) { - ObjectDefineProperty(prototype.prototype, key, { - enumerable: true, - writable: true, - configurable: true, - }); - } else if (ReflectHas(descriptor, "get")) { - ObjectDefineProperty(prototype.prototype, key, { - enumerable: true, - configurable: true, - }); - } - } - ObjectDefineProperty(prototype.prototype, SymbolToStringTag, { - value: prototype.name, - enumerable: false, + }, + forEach: { configurable: true, - writable: false, - }); - } - - const setlikeInner = Symbol("[[set]]"); - - // Ref: https://webidl.spec.whatwg.org/#es-setlike - function setlike(obj, objPrototype, readonly) { - ObjectDefineProperties(obj, { - size: { - configurable: true, - enumerable: true, - get() { - assertBranded(this, objPrototype); - return obj[setlikeInner].size; - }, + enumerable: true, + writable: true, + value(callbackfn, thisArg) { + assertBranded(this, objPrototype); + return SetPrototypeForEach(obj[setlikeInner], callbackfn, thisArg); }, - [SymbolIterator]: { - configurable: true, - enumerable: false, - writable: true, - value() { - assertBranded(this, objPrototype); - return obj[setlikeInner][SymbolIterator](); - }, + }, + has: { + configurable: true, + enumerable: true, + writable: true, + value(value) { + assertBranded(this, objPrototype); + return SetPrototypeHas(obj[setlikeInner], value); }, - entries: { + }, + }); + + if (!readonly) { + ObjectDefineProperties(obj, { + add: { configurable: true, enumerable: true, writable: true, - value() { + value(value) { assertBranded(this, objPrototype); - return SetPrototypeEntries(obj[setlikeInner]); + return SetPrototypeAdd(obj[setlikeInner], value); }, }, - keys: { + delete: { configurable: true, enumerable: true, writable: true, - value() { + value(value) { assertBranded(this, objPrototype); - return SetPrototypeKeys(obj[setlikeInner]); + return SetPrototypeDelete(obj[setlikeInner], value); }, }, - values: { + clear: { configurable: true, enumerable: true, writable: true, value() { assertBranded(this, objPrototype); - return SetPrototypeValues(obj[setlikeInner]); - }, - }, - forEach: { - configurable: true, - enumerable: true, - writable: true, - value(callbackfn, thisArg) { - assertBranded(this, objPrototype); - return SetPrototypeForEach(obj[setlikeInner], callbackfn, thisArg); - }, - }, - has: { - configurable: true, - enumerable: true, - writable: true, - value(value) { - assertBranded(this, objPrototype); - return SetPrototypeHas(obj[setlikeInner], value); + return SetPrototypeClear(obj[setlikeInner]); }, }, }); - - if (!readonly) { - ObjectDefineProperties(obj, { - add: { - configurable: true, - enumerable: true, - writable: true, - value(value) { - assertBranded(this, objPrototype); - return SetPrototypeAdd(obj[setlikeInner], value); - }, - }, - delete: { - configurable: true, - enumerable: true, - writable: true, - value(value) { - assertBranded(this, objPrototype); - return SetPrototypeDelete(obj[setlikeInner], value); - }, - }, - clear: { - configurable: true, - enumerable: true, - writable: true, - value() { - assertBranded(this, objPrototype); - return SetPrototypeClear(obj[setlikeInner]); - }, - }, - }); - } } - - window.__bootstrap ??= {}; - window.__bootstrap.webidl = { - type, - makeException, - converters, - requiredArguments, - createDictionaryConverter, - createEnumConverter, - createNullableConverter, - createSequenceConverter, - createRecordConverter, - createPromiseConverter, - invokeCallbackFunction, - createInterfaceConverter, - brand, - createBranded, - assertBranded, - illegalConstructor, - mixinPairIterable, - configurePrototype, - setlike, - setlikeInner, - }; -})(this); +} + +export { + assertBranded, + brand, + configurePrototype, + converters, + createBranded, + createDictionaryConverter, + createEnumConverter, + createInterfaceConverter, + createNullableConverter, + createPromiseConverter, + createRecordConverter, + createSequenceConverter, + illegalConstructor, + invokeCallbackFunction, + makeException, + mixinPairIterable, + requiredArguments, + setlike, + setlikeInner, + type, +}; diff --git a/ext/webidl/Cargo.toml b/ext/webidl/Cargo.toml index 7a101259ec46dd..a64a290536d4d0 100644 --- a/ext/webidl/Cargo.toml +++ b/ext/webidl/Cargo.toml @@ -2,7 +2,7 @@ [package] name = "deno_webidl" -version = "0.89.0" +version = "0.91.0" authors.workspace = true edition.workspace = true license.workspace = true diff --git a/ext/webidl/benches/dict.js b/ext/webidl/benches/dict.js index 353a630eb9b49e..b009a3c9470c9a 100644 --- a/ext/webidl/benches/dict.js +++ b/ext/webidl/benches/dict.js @@ -2,7 +2,10 @@ // deno-lint-ignore-file -const { createDictionaryConverter, converters } = globalThis.__bootstrap.webidl; +import { + converters, + createDictionaryConverter, +} from "internal:deno_webidl/00_webidl.js"; const TextDecodeOptions = createDictionaryConverter( "TextDecodeOptions", @@ -14,6 +17,7 @@ const TextDecodeOptions = createDictionaryConverter( }, ], ); +globalThis.TextDecodeOptions = TextDecodeOptions; // Sanity check { @@ -33,3 +37,4 @@ function handwrittenConverter(V) { } return defaultValue; } +globalThis.handwrittenConverter = handwrittenConverter; diff --git a/ext/webidl/benches/dict.rs b/ext/webidl/benches/dict.rs index e7df8af62dc79e..df0844fce2b104 100644 --- a/ext/webidl/benches/dict.rs +++ b/ext/webidl/benches/dict.rs @@ -6,12 +6,19 @@ use deno_bench_util::bencher::benchmark_group; use deno_bench_util::bencher::Bencher; use deno_core::Extension; +use deno_core::ExtensionFileSource; +use deno_core::ExtensionFileSourceCode; fn setup() -> Vec { vec![ deno_webidl::init(), Extension::builder("deno_webidl_bench") - .js(vec![("setup", include_str!("dict.js"))]) + .esm(vec![ExtensionFileSource { + specifier: "internal:setup".to_string(), + code: ExtensionFileSourceCode::IncludedInBinary(include_str!( + "dict.js" + )), + }]) .build(), ] } diff --git a/ext/webidl/internal.d.ts b/ext/webidl/internal.d.ts index 4ab9e33a92754c..22a9a693da907f 100644 --- a/ext/webidl/internal.d.ts +++ b/ext/webidl/internal.d.ts @@ -4,338 +4,334 @@ /// /// -declare namespace globalThis { - declare namespace __bootstrap { - declare namespace webidl { - declare interface ConverterOpts { - /** - * The prefix for error messages created by this converter. - * Examples: - * - `Failed to construct 'Event'` - * - `Failed to execute 'removeEventListener' on 'EventTarget'` - */ - prefix: string; - } - declare interface ValueConverterOpts extends ConverterOpts { - /** - * The context of this value error messages created by this converter. - * Examples: - * - `Argument 1` - * - `Argument 3` - */ - context: string; - } - declare function makeException( - ErrorType: any, - message: string, - opts: ValueConverterOpts, - ): any; - declare interface IntConverterOpts extends ValueConverterOpts { - /** - * Wether to throw if the number is outside of the acceptable values for - * this type. - */ - enforceRange?: boolean; - /** - * Wether to clamp this number to the acceptable values for this type. - */ - clamp?: boolean; - } - declare interface StringConverterOpts extends ValueConverterOpts { - /** - * Wether to treat `null` value as an empty string. - */ - treatNullAsEmptyString?: boolean; - } - declare interface BufferConverterOpts extends ValueConverterOpts { - /** - * Wether to allow `SharedArrayBuffer` (not just `ArrayBuffer`). - */ - allowShared?: boolean; - } - declare const converters: { - any(v: any): any; - /** - * Convert a value into a `boolean` (bool). - */ - boolean(v: any, opts?: IntConverterOpts): boolean; - /** - * Convert a value into a `byte` (int8). - */ - byte(v: any, opts?: IntConverterOpts): number; - /** - * Convert a value into a `octet` (uint8). - */ - octet(v: any, opts?: IntConverterOpts): number; - /** - * Convert a value into a `short` (int16). - */ - short(v: any, opts?: IntConverterOpts): number; - /** - * Convert a value into a `unsigned short` (uint16). - */ - ["unsigned short"](v: any, opts?: IntConverterOpts): number; - /** - * Convert a value into a `long` (int32). - */ - long(v: any, opts?: IntConverterOpts): number; - /** - * Convert a value into a `unsigned long` (uint32). - */ - ["unsigned long"](v: any, opts?: IntConverterOpts): number; - /** - * Convert a value into a `long long` (int64). - * **Note this is truncated to a JS number (53 bit precision).** - */ - ["long long"](v: any, opts?: IntConverterOpts): number; - /** - * Convert a value into a `unsigned long long` (uint64). - * **Note this is truncated to a JS number (53 bit precision).** - */ - ["unsigned long long"](v: any, opts?: IntConverterOpts): number; - /** - * Convert a value into a `float` (f32). - */ - float(v: any, opts?: ValueConverterOpts): number; - /** - * Convert a value into a `unrestricted float` (f32, infinity, or NaN). - */ - ["unrestricted float"](v: any, opts?: ValueConverterOpts): number; - /** - * Convert a value into a `double` (f64). - */ - double(v: any, opts?: ValueConverterOpts): number; - /** - * Convert a value into a `unrestricted double` (f64, infinity, or NaN). - */ - ["unrestricted double"](v: any, opts?: ValueConverterOpts): number; - /** - * Convert a value into a `DOMString` (string). - */ - DOMString(v: any, opts?: StringConverterOpts): string; - /** - * Convert a value into a `ByteString` (string with only u8 codepoints). - */ - ByteString(v: any, opts?: StringConverterOpts): string; - /** - * Convert a value into a `USVString` (string with only valid non - * surrogate Unicode code points). - */ - USVString(v: any, opts?: StringConverterOpts): string; - /** - * Convert a value into an `object` (object). - */ - object(v: any, opts?: ValueConverterOpts): object; - /** - * Convert a value into an `ArrayBuffer` (ArrayBuffer). - */ - ArrayBuffer(v: any, opts?: BufferConverterOpts): ArrayBuffer; - /** - * Convert a value into a `DataView` (ArrayBuffer). - */ - DataView(v: any, opts?: BufferConverterOpts): DataView; - /** - * Convert a value into a `Int8Array` (Int8Array). - */ - Int8Array(v: any, opts?: BufferConverterOpts): Int8Array; - /** - * Convert a value into a `Int16Array` (Int16Array). - */ - Int16Array(v: any, opts?: BufferConverterOpts): Int16Array; - /** - * Convert a value into a `Int32Array` (Int32Array). - */ - Int32Array(v: any, opts?: BufferConverterOpts): Int32Array; - /** - * Convert a value into a `Uint8Array` (Uint8Array). - */ - Uint8Array(v: any, opts?: BufferConverterOpts): Uint8Array; - /** - * Convert a value into a `Uint16Array` (Uint16Array). - */ - Uint16Array(v: any, opts?: BufferConverterOpts): Uint16Array; - /** - * Convert a value into a `Uint32Array` (Uint32Array). - */ - Uint32Array(v: any, opts?: BufferConverterOpts): Uint32Array; - /** - * Convert a value into a `Uint8ClampedArray` (Uint8ClampedArray). - */ - Uint8ClampedArray( - v: any, - opts?: BufferConverterOpts, - ): Uint8ClampedArray; - /** - * Convert a value into a `Float32Array` (Float32Array). - */ - Float32Array(v: any, opts?: BufferConverterOpts): Float32Array; - /** - * Convert a value into a `Float64Array` (Float64Array). - */ - Float64Array(v: any, opts?: BufferConverterOpts): Float64Array; - /** - * Convert a value into an `ArrayBufferView` (ArrayBufferView). - */ - ArrayBufferView(v: any, opts?: BufferConverterOpts): ArrayBufferView; - /** - * Convert a value into a `BufferSource` (ArrayBuffer or ArrayBufferView). - */ - BufferSource( - v: any, - opts?: BufferConverterOpts, - ): ArrayBuffer | ArrayBufferView; - /** - * Convert a value into a `DOMTimeStamp` (u64). Alias for unsigned long long - */ - DOMTimeStamp(v: any, opts?: IntConverterOpts): number; - /** - * Convert a value into a `Function` ((...args: any[]) => any). - */ - Function(v: any, opts?: ValueConverterOpts): (...args: any) => any; - /** - * Convert a value into a `VoidFunction` (() => void). - */ - VoidFunction(v: any, opts?: ValueConverterOpts): () => void; - ["UVString?"](v: any, opts?: ValueConverterOpts): string | null; - ["sequence"](v: any, opts?: ValueConverterOpts): number[]; +declare module "internal:deno_webidl/00_webidl.js" { + interface ConverterOpts { + /** + * The prefix for error messages created by this converter. + * Examples: + * - `Failed to construct 'Event'` + * - `Failed to execute 'removeEventListener' on 'EventTarget'` + */ + prefix: string; + } + interface ValueConverterOpts extends ConverterOpts { + /** + * The context of this value error messages created by this converter. + * Examples: + * - `Argument 1` + * - `Argument 3` + */ + context: string; + } + function makeException( + ErrorType: any, + message: string, + opts: ValueConverterOpts, + ): any; + interface IntConverterOpts extends ValueConverterOpts { + /** + * Wether to throw if the number is outside of the acceptable values for + * this type. + */ + enforceRange?: boolean; + /** + * Wether to clamp this number to the acceptable values for this type. + */ + clamp?: boolean; + } + interface StringConverterOpts extends ValueConverterOpts { + /** + * Wether to treat `null` value as an empty string. + */ + treatNullAsEmptyString?: boolean; + } + interface BufferConverterOpts extends ValueConverterOpts { + /** + * Wether to allow `SharedArrayBuffer` (not just `ArrayBuffer`). + */ + allowShared?: boolean; + } + const converters: { + any(v: any): any; + /** + * Convert a value into a `boolean` (bool). + */ + boolean(v: any, opts?: IntConverterOpts): boolean; + /** + * Convert a value into a `byte` (int8). + */ + byte(v: any, opts?: IntConverterOpts): number; + /** + * Convert a value into a `octet` (uint8). + */ + octet(v: any, opts?: IntConverterOpts): number; + /** + * Convert a value into a `short` (int16). + */ + short(v: any, opts?: IntConverterOpts): number; + /** + * Convert a value into a `unsigned short` (uint16). + */ + ["unsigned short"](v: any, opts?: IntConverterOpts): number; + /** + * Convert a value into a `long` (int32). + */ + long(v: any, opts?: IntConverterOpts): number; + /** + * Convert a value into a `unsigned long` (uint32). + */ + ["unsigned long"](v: any, opts?: IntConverterOpts): number; + /** + * Convert a value into a `long long` (int64). + * **Note this is truncated to a JS number (53 bit precision).** + */ + ["long long"](v: any, opts?: IntConverterOpts): number; + /** + * Convert a value into a `unsigned long long` (uint64). + * **Note this is truncated to a JS number (53 bit precision).** + */ + ["unsigned long long"](v: any, opts?: IntConverterOpts): number; + /** + * Convert a value into a `float` (f32). + */ + float(v: any, opts?: ValueConverterOpts): number; + /** + * Convert a value into a `unrestricted float` (f32, infinity, or NaN). + */ + ["unrestricted float"](v: any, opts?: ValueConverterOpts): number; + /** + * Convert a value into a `double` (f64). + */ + double(v: any, opts?: ValueConverterOpts): number; + /** + * Convert a value into a `unrestricted double` (f64, infinity, or NaN). + */ + ["unrestricted double"](v: any, opts?: ValueConverterOpts): number; + /** + * Convert a value into a `DOMString` (string). + */ + DOMString(v: any, opts?: StringConverterOpts): string; + /** + * Convert a value into a `ByteString` (string with only u8 codepoints). + */ + ByteString(v: any, opts?: StringConverterOpts): string; + /** + * Convert a value into a `USVString` (string with only valid non + * surrogate Unicode code points). + */ + USVString(v: any, opts?: StringConverterOpts): string; + /** + * Convert a value into an `object` (object). + */ + object(v: any, opts?: ValueConverterOpts): object; + /** + * Convert a value into an `ArrayBuffer` (ArrayBuffer). + */ + ArrayBuffer(v: any, opts?: BufferConverterOpts): ArrayBuffer; + /** + * Convert a value into a `DataView` (ArrayBuffer). + */ + DataView(v: any, opts?: BufferConverterOpts): DataView; + /** + * Convert a value into a `Int8Array` (Int8Array). + */ + Int8Array(v: any, opts?: BufferConverterOpts): Int8Array; + /** + * Convert a value into a `Int16Array` (Int16Array). + */ + Int16Array(v: any, opts?: BufferConverterOpts): Int16Array; + /** + * Convert a value into a `Int32Array` (Int32Array). + */ + Int32Array(v: any, opts?: BufferConverterOpts): Int32Array; + /** + * Convert a value into a `Uint8Array` (Uint8Array). + */ + Uint8Array(v: any, opts?: BufferConverterOpts): Uint8Array; + /** + * Convert a value into a `Uint16Array` (Uint16Array). + */ + Uint16Array(v: any, opts?: BufferConverterOpts): Uint16Array; + /** + * Convert a value into a `Uint32Array` (Uint32Array). + */ + Uint32Array(v: any, opts?: BufferConverterOpts): Uint32Array; + /** + * Convert a value into a `Uint8ClampedArray` (Uint8ClampedArray). + */ + Uint8ClampedArray( + v: any, + opts?: BufferConverterOpts, + ): Uint8ClampedArray; + /** + * Convert a value into a `Float32Array` (Float32Array). + */ + Float32Array(v: any, opts?: BufferConverterOpts): Float32Array; + /** + * Convert a value into a `Float64Array` (Float64Array). + */ + Float64Array(v: any, opts?: BufferConverterOpts): Float64Array; + /** + * Convert a value into an `ArrayBufferView` (ArrayBufferView). + */ + ArrayBufferView(v: any, opts?: BufferConverterOpts): ArrayBufferView; + /** + * Convert a value into a `BufferSource` (ArrayBuffer or ArrayBufferView). + */ + BufferSource( + v: any, + opts?: BufferConverterOpts, + ): ArrayBuffer | ArrayBufferView; + /** + * Convert a value into a `DOMTimeStamp` (u64). Alias for unsigned long long + */ + DOMTimeStamp(v: any, opts?: IntConverterOpts): number; + /** + * Convert a value into a `Function` ((...args: any[]) => any). + */ + Function(v: any, opts?: ValueConverterOpts): (...args: any) => any; + /** + * Convert a value into a `VoidFunction` (() => void). + */ + VoidFunction(v: any, opts?: ValueConverterOpts): () => void; + ["UVString?"](v: any, opts?: ValueConverterOpts): string | null; + ["sequence"](v: any, opts?: ValueConverterOpts): number[]; - [type: string]: (v: any, opts: ValueConverterOpts) => any; - }; + [type: string]: (v: any, opts: ValueConverterOpts) => any; + }; - /** - * Assert that the a function has at least a required amount of arguments. - */ - declare function requiredArguments( - length: number, - required: number, - opts: ConverterOpts, - ): void; - declare type Dictionary = DictionaryMember[]; - declare interface DictionaryMember { - key: string; - converter: (v: any, opts: ValueConverterOpts) => any; - defaultValue?: any; - required?: boolean; - } + /** + * Assert that the a function has at least a required amount of arguments. + */ + function requiredArguments( + length: number, + required: number, + opts: ConverterOpts, + ): void; + type Dictionary = DictionaryMember[]; + interface DictionaryMember { + key: string; + converter: (v: any, opts: ValueConverterOpts) => any; + defaultValue?: any; + required?: boolean; + } - /** - * Create a converter for dictionaries. - */ - declare function createDictionaryConverter( - name: string, - ...dictionaries: Dictionary[] - ): (v: any, opts: ValueConverterOpts) => T; + /** + * Create a converter for dictionaries. + */ + function createDictionaryConverter( + name: string, + ...dictionaries: Dictionary[] + ): (v: any, opts: ValueConverterOpts) => T; - /** - * Create a converter for enums. - */ - declare function createEnumConverter( - name: string, - values: string[], - ): (v: any, opts: ValueConverterOpts) => string; + /** + * Create a converter for enums. + */ + function createEnumConverter( + name: string, + values: string[], + ): (v: any, opts: ValueConverterOpts) => string; - /** - * Create a converter that makes the contained type nullable. - */ - declare function createNullableConverter( - converter: (v: any, opts: ValueConverterOpts) => T, - ): (v: any, opts: ValueConverterOpts) => T | null; + /** + * Create a converter that makes the contained type nullable. + */ + function createNullableConverter( + converter: (v: any, opts: ValueConverterOpts) => T, + ): (v: any, opts: ValueConverterOpts) => T | null; - /** - * Create a converter that converts a sequence of the inner type. - */ - declare function createSequenceConverter( - converter: (v: any, opts: ValueConverterOpts) => T, - ): (v: any, opts: ValueConverterOpts) => T[]; + /** + * Create a converter that converts a sequence of the inner type. + */ + function createSequenceConverter( + converter: (v: any, opts: ValueConverterOpts) => T, + ): (v: any, opts: ValueConverterOpts) => T[]; - /** - * Create a converter that converts a Promise of the inner type. - */ - declare function createPromiseConverter( - converter: (v: any, opts: ValueConverterOpts) => T, - ): (v: any, opts: ValueConverterOpts) => Promise; + /** + * Create a converter that converts a Promise of the inner type. + */ + function createPromiseConverter( + converter: (v: any, opts: ValueConverterOpts) => T, + ): (v: any, opts: ValueConverterOpts) => Promise; - /** - * Invoke a callback function. - */ - declare function invokeCallbackFunction( - callable: (...args: any) => any, - args: any[], - thisArg: any, - returnValueConverter: (v: any, opts: ValueConverterOpts) => T, - opts: ConverterOpts & { returnsPromise?: boolean }, - ): T; + /** + * Invoke a callback function. + */ + function invokeCallbackFunction( + callable: (...args: any) => any, + args: any[], + thisArg: any, + returnValueConverter: (v: any, opts: ValueConverterOpts) => T, + opts: ConverterOpts & { returnsPromise?: boolean }, + ): T; - /** - * Throw an illegal constructor error. - */ - declare function illegalConstructor(): never; + /** + * Throw an illegal constructor error. + */ + function illegalConstructor(): never; - /** - * The branding symbol. - */ - declare const brand: unique symbol; + /** + * The branding symbol. + */ + const brand: unique symbol; - /** - * Create a branded instance of an interface. - */ - declare function createBranded(self: any): any; + /** + * Create a branded instance of an interface. + */ + function createBranded(self: any): any; - /** - * Assert that self is branded. - */ - declare function assertBranded(self: any, type: any): void; + /** + * Assert that self is branded. + */ + function assertBranded(self: any, type: any): void; - /** - * Create a converter for interfaces. - */ - declare function createInterfaceConverter( - name: string, - prototype: any, - ): (v: any, opts: ValueConverterOpts) => any; + /** + * Create a converter for interfaces. + */ + function createInterfaceConverter( + name: string, + prototype: any, + ): (v: any, opts: ValueConverterOpts) => any; - declare function createRecordConverter< - K extends string | number | symbol, - V, - >( - keyConverter: (v: any, opts: ValueConverterOpts) => K, - valueConverter: (v: any, opts: ValueConverterOpts) => V, - ): ( - v: Record, - opts: ValueConverterOpts, - ) => any; + function createRecordConverter< + K extends string | number | symbol, + V, + >( + keyConverter: (v: any, opts: ValueConverterOpts) => K, + valueConverter: (v: any, opts: ValueConverterOpts) => V, + ): ( + v: Record, + opts: ValueConverterOpts, + ) => any; - /** - * Mix in the iterable declarations defined in WebIDL. - * https://heycam.github.io/webidl/#es-iterable - */ - declare function mixinPairIterable( - name: string, - prototype: any, - dataSymbol: symbol, - keyKey: string | number | symbol, - valueKey: string | number | symbol, - ): void; + /** + * Mix in the iterable declarations defined in WebIDL. + * https://heycam.github.io/webidl/#es-iterable + */ + function mixinPairIterable( + name: string, + prototype: any, + dataSymbol: symbol, + keyKey: string | number | symbol, + valueKey: string | number | symbol, + ): void; - /** - * Configure prototype properties enumerability / writability / configurability. - */ - declare function configurePrototype(prototype: any); + /** + * Configure prototype properties enumerability / writability / configurability. + */ + function configurePrototype(prototype: any); - /** - * Get the WebIDL / ES type of a value. - */ - declare function type( - v: any, - ): - | "Null" - | "Undefined" - | "Boolean" - | "Number" - | "String" - | "Symbol" - | "BigInt" - | "Object"; - } - } + /** + * Get the WebIDL / ES type of a value. + */ + function type( + v: any, + ): + | "Null" + | "Undefined" + | "Boolean" + | "Number" + | "String" + | "Symbol" + | "BigInt" + | "Object"; } diff --git a/ext/webidl/lib.rs b/ext/webidl/lib.rs index ae25f04c7571a3..1c3d760d37ccd4 100644 --- a/ext/webidl/lib.rs +++ b/ext/webidl/lib.rs @@ -6,9 +6,6 @@ use deno_core::Extension; /// Load and execute the javascript code. pub fn init() -> Extension { Extension::builder(env!("CARGO_PKG_NAME")) - .js(include_js_files!( - prefix "internal:ext/webidl", - "00_webidl.js", - )) + .esm(include_js_files!("00_webidl.js",)) .build() } diff --git a/ext/websocket/01_websocket.js b/ext/websocket/01_websocket.js index 9b7c45e708568b..00b3e5775280bf 100644 --- a/ext/websocket/01_websocket.js +++ b/ext/websocket/01_websocket.js @@ -1,579 +1,570 @@ // Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. -"use strict"; /// -((window) => { - const core = window.Deno.core; - const ops = core.ops; - const { URL } = window.__bootstrap.url; - const webidl = window.__bootstrap.webidl; - const { HTTP_TOKEN_CODE_POINT_RE } = window.__bootstrap.infra; - const { DOMException } = window.__bootstrap.domException; - const { - Event, - ErrorEvent, - CloseEvent, - MessageEvent, - defineEventHandler, - _skipInternalInit, - } = window.__bootstrap.event; - const { EventTarget } = window.__bootstrap.eventTarget; - const { Blob, BlobPrototype } = globalThis.__bootstrap.file; - const { - ArrayBufferPrototype, - ArrayBufferIsView, - ArrayPrototypeJoin, - ArrayPrototypeMap, - ArrayPrototypeSome, - DataView, - ErrorPrototypeToString, - ObjectDefineProperties, - ObjectPrototypeIsPrototypeOf, - PromisePrototypeThen, - RegExpPrototypeTest, - Set, - // TODO(lucacasonato): add SharedArrayBuffer to primordials - // SharedArrayBufferPrototype - String, - StringPrototypeEndsWith, - StringPrototypeToLowerCase, - Symbol, - SymbolIterator, - PromisePrototypeCatch, - SymbolFor, - } = window.__bootstrap.primordials; - - webidl.converters["sequence or DOMString"] = (V, opts) => { - // Union for (sequence or DOMString) - if (webidl.type(V) === "Object" && V !== null) { - if (V[SymbolIterator] !== undefined) { - return webidl.converters["sequence"](V, opts); - } +const core = globalThis.Deno.core; +const ops = core.ops; +import { URL } from "internal:deno_url/00_url.js"; +import * as webidl from "internal:deno_webidl/00_webidl.js"; +import { HTTP_TOKEN_CODE_POINT_RE } from "internal:deno_web/00_infra.js"; +import DOMException from "internal:deno_web/01_dom_exception.js"; +import { + _skipInternalInit, + CloseEvent, + defineEventHandler, + ErrorEvent, + Event, + EventTarget, + MessageEvent, +} from "internal:deno_web/02_event.js"; +import { Blob, BlobPrototype } from "internal:deno_web/09_file.js"; +const primordials = globalThis.__bootstrap.primordials; +const { + ArrayBufferPrototype, + ArrayBufferIsView, + ArrayPrototypeJoin, + ArrayPrototypeMap, + ArrayPrototypeSome, + DataView, + ErrorPrototypeToString, + ObjectDefineProperties, + ObjectPrototypeIsPrototypeOf, + PromisePrototypeThen, + RegExpPrototypeTest, + Set, + // TODO(lucacasonato): add SharedArrayBuffer to primordials + // SharedArrayBufferPrototype + String, + StringPrototypeEndsWith, + StringPrototypeToLowerCase, + Symbol, + SymbolIterator, + PromisePrototypeCatch, + SymbolFor, +} = primordials; + +webidl.converters["sequence or DOMString"] = (V, opts) => { + // Union for (sequence or DOMString) + if (webidl.type(V) === "Object" && V !== null) { + if (V[SymbolIterator] !== undefined) { + return webidl.converters["sequence"](V, opts); } - return webidl.converters.DOMString(V, opts); - }; + } + return webidl.converters.DOMString(V, opts); +}; - webidl.converters["WebSocketSend"] = (V, opts) => { - // Union for (Blob or ArrayBufferView or ArrayBuffer or USVString) - if (ObjectPrototypeIsPrototypeOf(BlobPrototype, V)) { - return webidl.converters["Blob"](V, opts); - } - if (typeof V === "object") { - if ( - ObjectPrototypeIsPrototypeOf(ArrayBufferPrototype, V) || - // deno-lint-ignore prefer-primordials - ObjectPrototypeIsPrototypeOf(SharedArrayBuffer.prototype, V) - ) { - return webidl.converters["ArrayBuffer"](V, opts); - } - if (ArrayBufferIsView(V)) { - return webidl.converters["ArrayBufferView"](V, opts); - } +webidl.converters["WebSocketSend"] = (V, opts) => { + // Union for (Blob or ArrayBufferView or ArrayBuffer or USVString) + if (ObjectPrototypeIsPrototypeOf(BlobPrototype, V)) { + return webidl.converters["Blob"](V, opts); + } + if (typeof V === "object") { + if ( + ObjectPrototypeIsPrototypeOf(ArrayBufferPrototype, V) || + // deno-lint-ignore prefer-primordials + ObjectPrototypeIsPrototypeOf(SharedArrayBuffer.prototype, V) + ) { + return webidl.converters["ArrayBuffer"](V, opts); } - return webidl.converters["USVString"](V, opts); - }; - - const CONNECTING = 0; - const OPEN = 1; - const CLOSING = 2; - const CLOSED = 3; - - const _readyState = Symbol("[[readyState]]"); - const _url = Symbol("[[url]]"); - const _rid = Symbol("[[rid]]"); - const _extensions = Symbol("[[extensions]]"); - const _protocol = Symbol("[[protocol]]"); - const _binaryType = Symbol("[[binaryType]]"); - const _bufferedAmount = Symbol("[[bufferedAmount]]"); - const _eventLoop = Symbol("[[eventLoop]]"); - - const _server = Symbol("[[server]]"); - const _idleTimeoutDuration = Symbol("[[idleTimeout]]"); - const _idleTimeoutTimeout = Symbol("[[idleTimeoutTimeout]]"); - const _serverHandleIdleTimeout = Symbol("[[serverHandleIdleTimeout]]"); - class WebSocket extends EventTarget { - [_rid]; - - [_readyState] = CONNECTING; - get readyState() { - webidl.assertBranded(this, WebSocketPrototype); - return this[_readyState]; + if (ArrayBufferIsView(V)) { + return webidl.converters["ArrayBufferView"](V, opts); } + } + return webidl.converters["USVString"](V, opts); +}; + +const CONNECTING = 0; +const OPEN = 1; +const CLOSING = 2; +const CLOSED = 3; + +const _readyState = Symbol("[[readyState]]"); +const _url = Symbol("[[url]]"); +const _rid = Symbol("[[rid]]"); +const _extensions = Symbol("[[extensions]]"); +const _protocol = Symbol("[[protocol]]"); +const _binaryType = Symbol("[[binaryType]]"); +const _bufferedAmount = Symbol("[[bufferedAmount]]"); +const _eventLoop = Symbol("[[eventLoop]]"); + +const _server = Symbol("[[server]]"); +const _idleTimeoutDuration = Symbol("[[idleTimeout]]"); +const _idleTimeoutTimeout = Symbol("[[idleTimeoutTimeout]]"); +const _serverHandleIdleTimeout = Symbol("[[serverHandleIdleTimeout]]"); +class WebSocket extends EventTarget { + [_rid]; + + [_readyState] = CONNECTING; + get readyState() { + webidl.assertBranded(this, WebSocketPrototype); + return this[_readyState]; + } - get CONNECTING() { - webidl.assertBranded(this, WebSocketPrototype); - return CONNECTING; - } - get OPEN() { - webidl.assertBranded(this, WebSocketPrototype); - return OPEN; - } - get CLOSING() { - webidl.assertBranded(this, WebSocketPrototype); - return CLOSING; - } - get CLOSED() { - webidl.assertBranded(this, WebSocketPrototype); - return CLOSED; - } + get CONNECTING() { + webidl.assertBranded(this, WebSocketPrototype); + return CONNECTING; + } + get OPEN() { + webidl.assertBranded(this, WebSocketPrototype); + return OPEN; + } + get CLOSING() { + webidl.assertBranded(this, WebSocketPrototype); + return CLOSING; + } + get CLOSED() { + webidl.assertBranded(this, WebSocketPrototype); + return CLOSED; + } + + [_extensions] = ""; + get extensions() { + webidl.assertBranded(this, WebSocketPrototype); + return this[_extensions]; + } + + [_protocol] = ""; + get protocol() { + webidl.assertBranded(this, WebSocketPrototype); + return this[_protocol]; + } + + [_url] = ""; + get url() { + webidl.assertBranded(this, WebSocketPrototype); + return this[_url]; + } - [_extensions] = ""; - get extensions() { - webidl.assertBranded(this, WebSocketPrototype); - return this[_extensions]; + [_binaryType] = "blob"; + get binaryType() { + webidl.assertBranded(this, WebSocketPrototype); + return this[_binaryType]; + } + set binaryType(value) { + webidl.assertBranded(this, WebSocketPrototype); + value = webidl.converters.DOMString(value, { + prefix: "Failed to set 'binaryType' on 'WebSocket'", + }); + if (value === "blob" || value === "arraybuffer") { + this[_binaryType] = value; } + } - [_protocol] = ""; - get protocol() { - webidl.assertBranded(this, WebSocketPrototype); - return this[_protocol]; + [_bufferedAmount] = 0; + get bufferedAmount() { + webidl.assertBranded(this, WebSocketPrototype); + return this[_bufferedAmount]; + } + + constructor(url, protocols = []) { + super(); + this[webidl.brand] = webidl.brand; + const prefix = "Failed to construct 'WebSocket'"; + webidl.requiredArguments(arguments.length, 1, { + prefix, + }); + url = webidl.converters.USVString(url, { + prefix, + context: "Argument 1", + }); + protocols = webidl.converters["sequence or DOMString"]( + protocols, + { + prefix, + context: "Argument 2", + }, + ); + + let wsURL; + + try { + wsURL = new URL(url); + } catch (e) { + throw new DOMException(e.message, "SyntaxError"); } - [_url] = ""; - get url() { - webidl.assertBranded(this, WebSocketPrototype); - return this[_url]; + if (wsURL.protocol !== "ws:" && wsURL.protocol !== "wss:") { + throw new DOMException( + "Only ws & wss schemes are allowed in a WebSocket URL.", + "SyntaxError", + ); } - [_binaryType] = "blob"; - get binaryType() { - webidl.assertBranded(this, WebSocketPrototype); - return this[_binaryType]; + if (wsURL.hash !== "" || StringPrototypeEndsWith(wsURL.href, "#")) { + throw new DOMException( + "Fragments are not allowed in a WebSocket URL.", + "SyntaxError", + ); } - set binaryType(value) { - webidl.assertBranded(this, WebSocketPrototype); - value = webidl.converters.DOMString(value, { - prefix: "Failed to set 'binaryType' on 'WebSocket'", - }); - if (value === "blob" || value === "arraybuffer") { - this[_binaryType] = value; - } + + this[_url] = wsURL.href; + + ops.op_ws_check_permission_and_cancel_handle( + "WebSocket.abort()", + this[_url], + false, + ); + + if (typeof protocols === "string") { + protocols = [protocols]; } - [_bufferedAmount] = 0; - get bufferedAmount() { - webidl.assertBranded(this, WebSocketPrototype); - return this[_bufferedAmount]; + if ( + protocols.length !== + new Set( + ArrayPrototypeMap(protocols, (p) => StringPrototypeToLowerCase(p)), + ).size + ) { + throw new DOMException( + "Can't supply multiple times the same protocol.", + "SyntaxError", + ); } - constructor(url, protocols = []) { - super(); - this[webidl.brand] = webidl.brand; - const prefix = "Failed to construct 'WebSocket'"; - webidl.requiredArguments(arguments.length, 1, { - prefix, - }); - url = webidl.converters.USVString(url, { - prefix, - context: "Argument 1", - }); - protocols = webidl.converters["sequence or DOMString"]( + if ( + ArrayPrototypeSome( protocols, - { - prefix, - context: "Argument 2", - }, + (protocol) => !RegExpPrototypeTest(HTTP_TOKEN_CODE_POINT_RE, protocol), + ) + ) { + throw new DOMException( + "Invalid protocol value.", + "SyntaxError", ); + } - let wsURL; - - try { - wsURL = new URL(url); - } catch (e) { - throw new DOMException(e.message, "SyntaxError"); - } + PromisePrototypeThen( + core.opAsync( + "op_ws_create", + "new WebSocket()", + wsURL.href, + ArrayPrototypeJoin(protocols, ", "), + ), + (create) => { + this[_rid] = create.rid; + this[_extensions] = create.extensions; + this[_protocol] = create.protocol; + + if (this[_readyState] === CLOSING) { + PromisePrototypeThen( + core.opAsync("op_ws_close", this[_rid]), + () => { + this[_readyState] = CLOSED; + + const errEvent = new ErrorEvent("error"); + this.dispatchEvent(errEvent); + + const event = new CloseEvent("close"); + this.dispatchEvent(event); + core.tryClose(this[_rid]); + }, + ); + } else { + this[_readyState] = OPEN; + const event = new Event("open"); + this.dispatchEvent(event); - if (wsURL.protocol !== "ws:" && wsURL.protocol !== "wss:") { - throw new DOMException( - "Only ws & wss schemes are allowed in a WebSocket URL.", - "SyntaxError", - ); - } + this[_eventLoop](); + } + }, + (err) => { + this[_readyState] = CLOSED; - if (wsURL.hash !== "" || StringPrototypeEndsWith(wsURL.href, "#")) { - throw new DOMException( - "Fragments are not allowed in a WebSocket URL.", - "SyntaxError", + const errorEv = new ErrorEvent( + "error", + { error: err, message: ErrorPrototypeToString(err) }, ); - } + this.dispatchEvent(errorEv); - this[_url] = wsURL.href; - - ops.op_ws_check_permission_and_cancel_handle( - "WebSocket.abort()", - this[_url], - false, - ); + const closeEv = new CloseEvent("close"); + this.dispatchEvent(closeEv); + }, + ); + } - if (typeof protocols === "string") { - protocols = [protocols]; - } + send(data) { + webidl.assertBranded(this, WebSocketPrototype); + const prefix = "Failed to execute 'send' on 'WebSocket'"; - if ( - protocols.length !== - new Set( - ArrayPrototypeMap(protocols, (p) => StringPrototypeToLowerCase(p)), - ).size - ) { - throw new DOMException( - "Can't supply multiple times the same protocol.", - "SyntaxError", - ); - } + webidl.requiredArguments(arguments.length, 1, { + prefix, + }); + data = webidl.converters.WebSocketSend(data, { + prefix, + context: "Argument 1", + }); - if ( - ArrayPrototypeSome( - protocols, - (protocol) => - !RegExpPrototypeTest(HTTP_TOKEN_CODE_POINT_RE, protocol), - ) - ) { - throw new DOMException( - "Invalid protocol value.", - "SyntaxError", - ); - } + if (this[_readyState] !== OPEN) { + throw new DOMException("readyState not OPEN", "InvalidStateError"); + } + const sendTypedArray = (ta) => { + this[_bufferedAmount] += ta.byteLength; PromisePrototypeThen( - core.opAsync( - "op_ws_create", - "new WebSocket()", - wsURL.href, - ArrayPrototypeJoin(protocols, ", "), - ), - (create) => { - this[_rid] = create.rid; - this[_extensions] = create.extensions; - this[_protocol] = create.protocol; - - if (this[_readyState] === CLOSING) { - PromisePrototypeThen( - core.opAsync("op_ws_close", this[_rid]), - () => { - this[_readyState] = CLOSED; - - const errEvent = new ErrorEvent("error"); - this.dispatchEvent(errEvent); - - const event = new CloseEvent("close"); - this.dispatchEvent(event); - core.tryClose(this[_rid]); - }, - ); - } else { - this[_readyState] = OPEN; - const event = new Event("open"); - this.dispatchEvent(event); - - this[_eventLoop](); - } + core.opAsync("op_ws_send", this[_rid], { + kind: "binary", + value: ta, + }), + () => { + this[_bufferedAmount] -= ta.byteLength; }, - (err) => { - this[_readyState] = CLOSED; - - const errorEv = new ErrorEvent( - "error", - { error: err, message: ErrorPrototypeToString(err) }, - ); - this.dispatchEvent(errorEv); + ); + }; - const closeEv = new CloseEvent("close"); - this.dispatchEvent(closeEv); + if (ObjectPrototypeIsPrototypeOf(BlobPrototype, data)) { + PromisePrototypeThen( + data.slice().arrayBuffer(), + (ab) => sendTypedArray(new DataView(ab)), + ); + } else if (ArrayBufferIsView(data)) { + sendTypedArray(data); + } else if (ObjectPrototypeIsPrototypeOf(ArrayBufferPrototype, data)) { + sendTypedArray(new DataView(data)); + } else { + const string = String(data); + const d = core.encode(string); + this[_bufferedAmount] += d.byteLength; + PromisePrototypeThen( + core.opAsync("op_ws_send", this[_rid], { + kind: "text", + value: string, + }), + () => { + this[_bufferedAmount] -= d.byteLength; }, ); } + } - send(data) { - webidl.assertBranded(this, WebSocketPrototype); - const prefix = "Failed to execute 'send' on 'WebSocket'"; + close(code = undefined, reason = undefined) { + webidl.assertBranded(this, WebSocketPrototype); + const prefix = "Failed to execute 'close' on 'WebSocket'"; - webidl.requiredArguments(arguments.length, 1, { - prefix, - }); - data = webidl.converters.WebSocketSend(data, { + if (code !== undefined) { + code = webidl.converters["unsigned short"](code, { prefix, + clamp: true, context: "Argument 1", }); + } - if (this[_readyState] !== OPEN) { - throw new DOMException("readyState not OPEN", "InvalidStateError"); - } - - const sendTypedArray = (ta) => { - this[_bufferedAmount] += ta.byteLength; - PromisePrototypeThen( - core.opAsync("op_ws_send", this[_rid], { - kind: "binary", - value: ta, - }), - () => { - this[_bufferedAmount] -= ta.byteLength; - }, - ); - }; + if (reason !== undefined) { + reason = webidl.converters.USVString(reason, { + prefix, + context: "Argument 2", + }); + } - if (ObjectPrototypeIsPrototypeOf(BlobPrototype, data)) { - PromisePrototypeThen( - data.slice().arrayBuffer(), - (ab) => sendTypedArray(new DataView(ab)), - ); - } else if (ArrayBufferIsView(data)) { - sendTypedArray(data); - } else if (ObjectPrototypeIsPrototypeOf(ArrayBufferPrototype, data)) { - sendTypedArray(new DataView(data)); - } else { - const string = String(data); - const d = core.encode(string); - this[_bufferedAmount] += d.byteLength; - PromisePrototypeThen( - core.opAsync("op_ws_send", this[_rid], { - kind: "text", - value: string, - }), - () => { - this[_bufferedAmount] -= d.byteLength; - }, + if (!this[_server]) { + if ( + code !== undefined && + !(code === 1000 || (3000 <= code && code < 5000)) + ) { + throw new DOMException( + "The close code must be either 1000 or in the range of 3000 to 4999.", + "InvalidAccessError", ); } } - close(code = undefined, reason = undefined) { - webidl.assertBranded(this, WebSocketPrototype); - const prefix = "Failed to execute 'close' on 'WebSocket'"; - - if (code !== undefined) { - code = webidl.converters["unsigned short"](code, { - prefix, - clamp: true, - context: "Argument 1", - }); - } + if (reason !== undefined && core.encode(reason).byteLength > 123) { + throw new DOMException( + "The close reason may not be longer than 123 bytes.", + "SyntaxError", + ); + } - if (reason !== undefined) { - reason = webidl.converters.USVString(reason, { - prefix, - context: "Argument 2", - }); - } + if (this[_readyState] === CONNECTING) { + this[_readyState] = CLOSING; + } else if (this[_readyState] === OPEN) { + this[_readyState] = CLOSING; - if (!this[_server]) { - if ( - code !== undefined && - !(code === 1000 || (3000 <= code && code < 5000)) - ) { - throw new DOMException( - "The close code must be either 1000 or in the range of 3000 to 4999.", - "InvalidAccessError", - ); - } - } + PromisePrototypeCatch( + core.opAsync("op_ws_close", this[_rid], code, reason), + (err) => { + this[_readyState] = CLOSED; - if (reason !== undefined && core.encode(reason).byteLength > 123) { - throw new DOMException( - "The close reason may not be longer than 123 bytes.", - "SyntaxError", - ); - } + const errorEv = new ErrorEvent("error", { + error: err, + message: ErrorPrototypeToString(err), + }); + this.dispatchEvent(errorEv); - if (this[_readyState] === CONNECTING) { - this[_readyState] = CLOSING; - } else if (this[_readyState] === OPEN) { - this[_readyState] = CLOSING; - - PromisePrototypeCatch( - core.opAsync("op_ws_close", this[_rid], code, reason), - (err) => { - this[_readyState] = CLOSED; - - const errorEv = new ErrorEvent("error", { - error: err, - message: ErrorPrototypeToString(err), - }); - this.dispatchEvent(errorEv); - - const closeEv = new CloseEvent("close"); - this.dispatchEvent(closeEv); - core.tryClose(this[_rid]); - }, - ); - } + const closeEv = new CloseEvent("close"); + this.dispatchEvent(closeEv); + core.tryClose(this[_rid]); + }, + ); } + } - async [_eventLoop]() { - while (this[_readyState] !== CLOSED) { - const { kind, value } = await core.opAsync( - "op_ws_next_event", - this[_rid], - ); + async [_eventLoop]() { + while (this[_readyState] !== CLOSED) { + const { kind, value } = await core.opAsync( + "op_ws_next_event", + this[_rid], + ); - switch (kind) { - case "string": { - this[_serverHandleIdleTimeout](); - const event = new MessageEvent("message", { - data: value, - origin: this[_url], - }); - this.dispatchEvent(event); - break; + switch (kind) { + case "string": { + this[_serverHandleIdleTimeout](); + const event = new MessageEvent("message", { + data: value, + origin: this[_url], + }); + this.dispatchEvent(event); + break; + } + case "binary": { + this[_serverHandleIdleTimeout](); + let data; + + if (this.binaryType === "blob") { + data = new Blob([value]); + } else { + data = value.buffer; } - case "binary": { - this[_serverHandleIdleTimeout](); - let data; - if (this.binaryType === "blob") { - data = new Blob([value]); - } else { - data = value.buffer; + const event = new MessageEvent("message", { + data, + origin: this[_url], + [_skipInternalInit]: true, + }); + this.dispatchEvent(event); + break; + } + case "pong": { + this[_serverHandleIdleTimeout](); + break; + } + case "closed": + case "close": { + const prevState = this[_readyState]; + this[_readyState] = CLOSED; + clearTimeout(this[_idleTimeoutTimeout]); + + if (prevState === OPEN) { + try { + await core.opAsync( + "op_ws_close", + this[_rid], + value.code, + value.reason, + ); + } catch { + // ignore failures } - - const event = new MessageEvent("message", { - data, - origin: this[_url], - [_skipInternalInit]: true, - }); - this.dispatchEvent(event); - break; - } - case "ping": { - core.opAsync("op_ws_send", this[_rid], { - kind: "pong", - }); - break; - } - case "pong": { - this[_serverHandleIdleTimeout](); - break; } - case "closed": - case "close": { - const prevState = this[_readyState]; - this[_readyState] = CLOSED; - clearTimeout(this[_idleTimeoutTimeout]); - - if (prevState === OPEN) { - try { - await core.opAsync( - "op_ws_close", - this[_rid], - value.code, - value.reason, - ); - } catch { - // ignore failures - } - } - const event = new CloseEvent("close", { - wasClean: true, - code: value.code, - reason: value.reason, - }); - this.dispatchEvent(event); - core.tryClose(this[_rid]); - break; - } - case "error": { - this[_readyState] = CLOSED; - - const errorEv = new ErrorEvent("error", { - message: value, - }); - this.dispatchEvent(errorEv); - - const closeEv = new CloseEvent("close"); - this.dispatchEvent(closeEv); - core.tryClose(this[_rid]); - break; - } + const event = new CloseEvent("close", { + wasClean: true, + code: value.code, + reason: value.reason, + }); + this.dispatchEvent(event); + core.tryClose(this[_rid]); + break; } - } - } + case "error": { + this[_readyState] = CLOSED; - [_serverHandleIdleTimeout]() { - if (this[_idleTimeoutDuration]) { - clearTimeout(this[_idleTimeoutTimeout]); - this[_idleTimeoutTimeout] = setTimeout(async () => { - if (this[_readyState] === OPEN) { - await core.opAsync("op_ws_send", this[_rid], { - kind: "ping", - }); - this[_idleTimeoutTimeout] = setTimeout(async () => { - if (this[_readyState] === OPEN) { - this[_readyState] = CLOSING; - const reason = "No response from ping frame."; - await core.opAsync("op_ws_close", this[_rid], 1001, reason); - this[_readyState] = CLOSED; - - const errEvent = new ErrorEvent("error", { - message: reason, - }); - this.dispatchEvent(errEvent); - - const event = new CloseEvent("close", { - wasClean: false, - code: 1001, - reason, - }); - this.dispatchEvent(event); - core.tryClose(this[_rid]); - } else { - clearTimeout(this[_idleTimeoutTimeout]); - } - }, (this[_idleTimeoutDuration] / 2) * 1000); - } else { - clearTimeout(this[_idleTimeoutTimeout]); - } - }, (this[_idleTimeoutDuration] / 2) * 1000); + const errorEv = new ErrorEvent("error", { + message: value, + }); + this.dispatchEvent(errorEv); + + const closeEv = new CloseEvent("close"); + this.dispatchEvent(closeEv); + core.tryClose(this[_rid]); + break; + } } } + } - [SymbolFor("Deno.customInspect")](inspect) { - return `${this.constructor.name} ${ - inspect({ - url: this.url, - readyState: this.readyState, - extensions: this.extensions, - protocol: this.protocol, - binaryType: this.binaryType, - bufferedAmount: this.bufferedAmount, - }) - }`; + [_serverHandleIdleTimeout]() { + if (this[_idleTimeoutDuration]) { + clearTimeout(this[_idleTimeoutTimeout]); + this[_idleTimeoutTimeout] = setTimeout(async () => { + if (this[_readyState] === OPEN) { + await core.opAsync("op_ws_send", this[_rid], { + kind: "ping", + }); + this[_idleTimeoutTimeout] = setTimeout(async () => { + if (this[_readyState] === OPEN) { + this[_readyState] = CLOSING; + const reason = "No response from ping frame."; + await core.opAsync("op_ws_close", this[_rid], 1001, reason); + this[_readyState] = CLOSED; + + const errEvent = new ErrorEvent("error", { + message: reason, + }); + this.dispatchEvent(errEvent); + + const event = new CloseEvent("close", { + wasClean: false, + code: 1001, + reason, + }); + this.dispatchEvent(event); + core.tryClose(this[_rid]); + } else { + clearTimeout(this[_idleTimeoutTimeout]); + } + }, (this[_idleTimeoutDuration] / 2) * 1000); + } else { + clearTimeout(this[_idleTimeoutTimeout]); + } + }, (this[_idleTimeoutDuration] / 2) * 1000); } } - ObjectDefineProperties(WebSocket, { - CONNECTING: { - value: 0, - }, - OPEN: { - value: 1, - }, - CLOSING: { - value: 2, - }, - CLOSED: { - value: 3, - }, - }); - - defineEventHandler(WebSocket.prototype, "message"); - defineEventHandler(WebSocket.prototype, "error"); - defineEventHandler(WebSocket.prototype, "close"); - defineEventHandler(WebSocket.prototype, "open"); - - webidl.configurePrototype(WebSocket); - const WebSocketPrototype = WebSocket.prototype; - - window.__bootstrap.webSocket = { - WebSocket, - _rid, - _readyState, - _eventLoop, - _protocol, - _server, - _idleTimeoutDuration, - _idleTimeoutTimeout, - _serverHandleIdleTimeout, - }; -})(this); + [SymbolFor("Deno.customInspect")](inspect) { + return `${this.constructor.name} ${ + inspect({ + url: this.url, + readyState: this.readyState, + extensions: this.extensions, + protocol: this.protocol, + binaryType: this.binaryType, + bufferedAmount: this.bufferedAmount, + }) + }`; + } +} + +ObjectDefineProperties(WebSocket, { + CONNECTING: { + value: 0, + }, + OPEN: { + value: 1, + }, + CLOSING: { + value: 2, + }, + CLOSED: { + value: 3, + }, +}); + +defineEventHandler(WebSocket.prototype, "message"); +defineEventHandler(WebSocket.prototype, "error"); +defineEventHandler(WebSocket.prototype, "close"); +defineEventHandler(WebSocket.prototype, "open"); + +webidl.configurePrototype(WebSocket); +const WebSocketPrototype = WebSocket.prototype; + +export { + _eventLoop, + _idleTimeoutDuration, + _idleTimeoutTimeout, + _protocol, + _readyState, + _rid, + _server, + _serverHandleIdleTimeout, + WebSocket, +}; diff --git a/ext/websocket/02_websocketstream.js b/ext/websocket/02_websocketstream.js index 5d7e47cc430252..1418da1ffb8054 100644 --- a/ext/websocket/02_websocketstream.js +++ b/ext/websocket/02_websocketstream.js @@ -1,426 +1,424 @@ // Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. -"use strict"; /// -((window) => { - const core = window.Deno.core; - const ops = core.ops; - const webidl = window.__bootstrap.webidl; - const { writableStreamClose, Deferred } = window.__bootstrap.streams; - const { DOMException } = window.__bootstrap.domException; - const { add, remove } = window.__bootstrap.abortSignal; - const { headersFromHeaderList, headerListFromHeaders, fillHeaders } = - window.__bootstrap.headers; - - const { - ArrayPrototypeJoin, - ArrayPrototypeMap, - Error, - ObjectPrototypeIsPrototypeOf, - PromisePrototypeCatch, - PromisePrototypeThen, - Set, - StringPrototypeEndsWith, - StringPrototypeToLowerCase, - Symbol, - SymbolFor, - TypeError, - Uint8ArrayPrototype, - } = window.__bootstrap.primordials; - - webidl.converters.WebSocketStreamOptions = webidl.createDictionaryConverter( - "WebSocketStreamOptions", - [ - { - key: "protocols", - converter: webidl.converters["sequence"], - get defaultValue() { - return []; - }, - }, - { - key: "signal", - converter: webidl.converters.AbortSignal, - }, - { - key: "headers", - converter: webidl.converters.HeadersInit, +const core = globalThis.Deno.core; +const ops = core.ops; +import * as webidl from "internal:deno_webidl/00_webidl.js"; +import { Deferred, writableStreamClose } from "internal:deno_web/06_streams.js"; +import DOMException from "internal:deno_web/01_dom_exception.js"; +import { add, remove } from "internal:deno_web/03_abort_signal.js"; +import { + fillHeaders, + headerListFromHeaders, + headersFromHeaderList, +} from "internal:deno_fetch/20_headers.js"; +const primordials = globalThis.__bootstrap.primordials; +const { + ArrayPrototypeJoin, + ArrayPrototypeMap, + Error, + ObjectPrototypeIsPrototypeOf, + PromisePrototypeCatch, + PromisePrototypeThen, + Set, + StringPrototypeEndsWith, + StringPrototypeToLowerCase, + Symbol, + SymbolFor, + TypeError, + Uint8ArrayPrototype, +} = primordials; + +webidl.converters.WebSocketStreamOptions = webidl.createDictionaryConverter( + "WebSocketStreamOptions", + [ + { + key: "protocols", + converter: webidl.converters["sequence"], + get defaultValue() { + return []; }, - ], - ); - webidl.converters.WebSocketCloseInfo = webidl.createDictionaryConverter( - "WebSocketCloseInfo", - [ - { - key: "code", - converter: webidl.converters["unsigned short"], - }, - { - key: "reason", - converter: webidl.converters.USVString, - defaultValue: "", - }, - ], - ); - - const CLOSE_RESPONSE_TIMEOUT = 5000; - - const _rid = Symbol("[[rid]]"); - const _url = Symbol("[[url]]"); - const _connection = Symbol("[[connection]]"); - const _closed = Symbol("[[closed]]"); - const _earlyClose = Symbol("[[earlyClose]]"); - const _closeSent = Symbol("[[closeSent]]"); - class WebSocketStream { - [_rid]; - - [_url]; - get url() { - webidl.assertBranded(this, WebSocketStreamPrototype); - return this[_url]; + }, + { + key: "signal", + converter: webidl.converters.AbortSignal, + }, + { + key: "headers", + converter: webidl.converters.HeadersInit, + }, + ], +); +webidl.converters.WebSocketCloseInfo = webidl.createDictionaryConverter( + "WebSocketCloseInfo", + [ + { + key: "code", + converter: webidl.converters["unsigned short"], + }, + { + key: "reason", + converter: webidl.converters.USVString, + defaultValue: "", + }, + ], +); + +const CLOSE_RESPONSE_TIMEOUT = 5000; + +const _rid = Symbol("[[rid]]"); +const _url = Symbol("[[url]]"); +const _connection = Symbol("[[connection]]"); +const _closed = Symbol("[[closed]]"); +const _earlyClose = Symbol("[[earlyClose]]"); +const _closeSent = Symbol("[[closeSent]]"); +class WebSocketStream { + [_rid]; + + [_url]; + get url() { + webidl.assertBranded(this, WebSocketStreamPrototype); + return this[_url]; + } + + constructor(url, options) { + this[webidl.brand] = webidl.brand; + const prefix = "Failed to construct 'WebSocketStream'"; + webidl.requiredArguments(arguments.length, 1, { prefix }); + url = webidl.converters.USVString(url, { + prefix, + context: "Argument 1", + }); + options = webidl.converters.WebSocketStreamOptions(options, { + prefix, + context: "Argument 2", + }); + + const wsURL = new URL(url); + + if (wsURL.protocol !== "ws:" && wsURL.protocol !== "wss:") { + throw new DOMException( + "Only ws & wss schemes are allowed in a WebSocket URL.", + "SyntaxError", + ); } - constructor(url, options) { - this[webidl.brand] = webidl.brand; - const prefix = "Failed to construct 'WebSocketStream'"; - webidl.requiredArguments(arguments.length, 1, { prefix }); - url = webidl.converters.USVString(url, { - prefix, - context: "Argument 1", - }); - options = webidl.converters.WebSocketStreamOptions(options, { - prefix, - context: "Argument 2", - }); - - const wsURL = new URL(url); - - if (wsURL.protocol !== "ws:" && wsURL.protocol !== "wss:") { - throw new DOMException( - "Only ws & wss schemes are allowed in a WebSocket URL.", - "SyntaxError", - ); - } - - if (wsURL.hash !== "" || StringPrototypeEndsWith(wsURL.href, "#")) { - throw new DOMException( - "Fragments are not allowed in a WebSocket URL.", - "SyntaxError", - ); - } - - this[_url] = wsURL.href; - - if ( - options.protocols.length !== - new Set( - ArrayPrototypeMap( - options.protocols, - (p) => StringPrototypeToLowerCase(p), - ), - ).size - ) { - throw new DOMException( - "Can't supply multiple times the same protocol.", - "SyntaxError", - ); - } - - const headers = headersFromHeaderList([], "request"); - if (options.headers !== undefined) { - fillHeaders(headers, options.headers); - } - - const cancelRid = ops.op_ws_check_permission_and_cancel_handle( - "WebSocketStream.abort()", - this[_url], - true, + if (wsURL.hash !== "" || StringPrototypeEndsWith(wsURL.href, "#")) { + throw new DOMException( + "Fragments are not allowed in a WebSocket URL.", + "SyntaxError", ); + } - if (options.signal?.aborted) { - core.close(cancelRid); - const err = options.signal.reason; - this[_connection].reject(err); - this[_closed].reject(err); - } else { - const abort = () => { - core.close(cancelRid); - }; - options.signal?.[add](abort); - PromisePrototypeThen( - core.opAsync( - "op_ws_create", - "new WebSocketStream()", - this[_url], - options.protocols - ? ArrayPrototypeJoin(options.protocols, ", ") - : "", - cancelRid, - headerListFromHeaders(headers), + this[_url] = wsURL.href; + + if ( + options.protocols.length !== + new Set( + ArrayPrototypeMap( + options.protocols, + (p) => StringPrototypeToLowerCase(p), ), - (create) => { - options.signal?.[remove](abort); - if (this[_earlyClose]) { - PromisePrototypeThen( - core.opAsync("op_ws_close", create.rid), - () => { - PromisePrototypeThen( - (async () => { - while (true) { - const { kind } = await core.opAsync( - "op_ws_next_event", - create.rid, - ); - - if (kind === "close") { - break; - } - } - })(), - () => { - const err = new DOMException( - "Closed while connecting", - "NetworkError", + ).size + ) { + throw new DOMException( + "Can't supply multiple times the same protocol.", + "SyntaxError", + ); + } + + const headers = headersFromHeaderList([], "request"); + if (options.headers !== undefined) { + fillHeaders(headers, options.headers); + } + + const cancelRid = ops.op_ws_check_permission_and_cancel_handle( + "WebSocketStream.abort()", + this[_url], + true, + ); + + if (options.signal?.aborted) { + core.close(cancelRid); + const err = options.signal.reason; + this[_connection].reject(err); + this[_closed].reject(err); + } else { + const abort = () => { + core.close(cancelRid); + }; + options.signal?.[add](abort); + PromisePrototypeThen( + core.opAsync( + "op_ws_create", + "new WebSocketStream()", + this[_url], + options.protocols ? ArrayPrototypeJoin(options.protocols, ", ") : "", + cancelRid, + headerListFromHeaders(headers), + ), + (create) => { + options.signal?.[remove](abort); + if (this[_earlyClose]) { + PromisePrototypeThen( + core.opAsync("op_ws_close", create.rid), + () => { + PromisePrototypeThen( + (async () => { + while (true) { + const { kind } = await core.opAsync( + "op_ws_next_event", + create.rid, ); - this[_connection].reject(err); - this[_closed].reject(err); - }, - ); - }, - () => { - const err = new DOMException( - "Closed while connecting", - "NetworkError", + + if (kind === "close") { + break; + } + } + })(), + () => { + const err = new DOMException( + "Closed while connecting", + "NetworkError", + ); + this[_connection].reject(err); + this[_closed].reject(err); + }, + ); + }, + () => { + const err = new DOMException( + "Closed while connecting", + "NetworkError", + ); + this[_connection].reject(err); + this[_closed].reject(err); + }, + ); + } else { + this[_rid] = create.rid; + + const writable = new WritableStream({ + write: async (chunk) => { + if (typeof chunk === "string") { + await core.opAsync("op_ws_send", this[_rid], { + kind: "text", + value: chunk, + }); + } else if ( + ObjectPrototypeIsPrototypeOf(Uint8ArrayPrototype, chunk) + ) { + await core.opAsync("op_ws_send", this[_rid], { + kind: "binary", + value: chunk, + }, chunk); + } else { + throw new TypeError( + "A chunk may only be either a string or an Uint8Array", ); - this[_connection].reject(err); - this[_closed].reject(err); - }, + } + }, + close: async (reason) => { + try { + this.close(reason?.code !== undefined ? reason : {}); + } catch (_) { + this.close(); + } + await this.closed; + }, + abort: async (reason) => { + try { + this.close(reason?.code !== undefined ? reason : {}); + } catch (_) { + this.close(); + } + await this.closed; + }, + }); + const pull = async (controller) => { + const { kind, value } = await core.opAsync( + "op_ws_next_event", + this[_rid], ); - } else { - this[_rid] = create.rid; - - const writable = new WritableStream({ - write: async (chunk) => { - if (typeof chunk === "string") { - await core.opAsync("op_ws_send", this[_rid], { - kind: "text", - value: chunk, - }); - } else if ( - ObjectPrototypeIsPrototypeOf(Uint8ArrayPrototype, chunk) - ) { - await core.opAsync("op_ws_send", this[_rid], { - kind: "binary", - value: chunk, - }, chunk); - } else { - throw new TypeError( - "A chunk may only be either a string or an Uint8Array", - ); - } - }, - close: async (reason) => { + + switch (kind) { + case "string": { + controller.enqueue(value); + break; + } + case "binary": { + controller.enqueue(value); + break; + } + case "ping": { + await core.opAsync("op_ws_send", this[_rid], { + kind: "pong", + }); + await pull(controller); + break; + } + case "closed": + case "close": { + this[_closed].resolve(value); + core.tryClose(this[_rid]); + break; + } + case "error": { + const err = new Error(value); + this[_closed].reject(err); + controller.error(err); + core.tryClose(this[_rid]); + break; + } + } + + if ( + this[_closeSent].state === "fulfilled" && + this[_closed].state === "pending" + ) { + if ( + new Date().getTime() - await this[_closeSent].promise <= + CLOSE_RESPONSE_TIMEOUT + ) { + return pull(controller); + } + + this[_closed].resolve(value); + core.tryClose(this[_rid]); + } + }; + const readable = new ReadableStream({ + start: (controller) => { + PromisePrototypeThen(this.closed, () => { try { - this.close(reason?.code !== undefined ? reason : {}); + controller.close(); } catch (_) { - this.close(); + // needed to ignore warnings & assertions } - await this.closed; - }, - abort: async (reason) => { try { - this.close(reason?.code !== undefined ? reason : {}); + PromisePrototypeCatch( + writableStreamClose(writable), + () => {}, + ); } catch (_) { - this.close(); + // needed to ignore warnings & assertions } - await this.closed; - }, - }); - const pull = async (controller) => { - const { kind, value } = await core.opAsync( - "op_ws_next_event", - this[_rid], - ); + }); - switch (kind) { - case "string": { - controller.enqueue(value); - break; - } - case "binary": { - controller.enqueue(value); - break; - } - case "ping": { - await core.opAsync("op_ws_send", this[_rid], { - kind: "pong", - }); - await pull(controller); - break; - } - case "closed": - case "close": { - this[_closed].resolve(value); - core.tryClose(this[_rid]); - break; - } - case "error": { - const err = new Error(value); - this[_closed].reject(err); - controller.error(err); - core.tryClose(this[_rid]); - break; + PromisePrototypeThen(this[_closeSent].promise, () => { + if (this[_closed].state === "pending") { + return pull(controller); } + }); + }, + pull, + cancel: async (reason) => { + try { + this.close(reason?.code !== undefined ? reason : {}); + } catch (_) { + this.close(); } + await this.closed; + }, + }); + + this[_connection].resolve({ + readable, + writable, + extensions: create.extensions ?? "", + protocol: create.protocol ?? "", + }); + } + }, + (err) => { + if (ObjectPrototypeIsPrototypeOf(core.InterruptedPrototype, err)) { + // The signal was aborted. + err = options.signal.reason; + } else { + core.tryClose(cancelRid); + } + this[_connection].reject(err); + this[_closed].reject(err); + }, + ); + } + } - if ( - this[_closeSent].state === "fulfilled" && - this[_closed].state === "pending" - ) { - if ( - new Date().getTime() - await this[_closeSent].promise <= - CLOSE_RESPONSE_TIMEOUT - ) { - return pull(controller); - } + [_connection] = new Deferred(); + get connection() { + webidl.assertBranded(this, WebSocketStreamPrototype); + return this[_connection].promise; + } - this[_closed].resolve(value); - core.tryClose(this[_rid]); - } - }; - const readable = new ReadableStream({ - start: (controller) => { - PromisePrototypeThen(this.closed, () => { - try { - controller.close(); - } catch (_) { - // needed to ignore warnings & assertions - } - try { - PromisePrototypeCatch( - writableStreamClose(writable), - () => {}, - ); - } catch (_) { - // needed to ignore warnings & assertions - } - }); + [_earlyClose] = false; + [_closed] = new Deferred(); + [_closeSent] = new Deferred(); + get closed() { + webidl.assertBranded(this, WebSocketStreamPrototype); + return this[_closed].promise; + } - PromisePrototypeThen(this[_closeSent].promise, () => { - if (this[_closed].state === "pending") { - return pull(controller); - } - }); - }, - pull, - cancel: async (reason) => { - try { - this.close(reason?.code !== undefined ? reason : {}); - } catch (_) { - this.close(); - } - await this.closed; - }, - }); - - this[_connection].resolve({ - readable, - writable, - extensions: create.extensions ?? "", - protocol: create.protocol ?? "", - }); - } - }, - (err) => { - if (ObjectPrototypeIsPrototypeOf(core.InterruptedPrototype, err)) { - // The signal was aborted. - err = options.signal.reason; - } else { - core.tryClose(cancelRid); - } - this[_connection].reject(err); - this[_closed].reject(err); - }, - ); - } + close(closeInfo) { + webidl.assertBranded(this, WebSocketStreamPrototype); + closeInfo = webidl.converters.WebSocketCloseInfo(closeInfo, { + prefix: "Failed to execute 'close' on 'WebSocketStream'", + context: "Argument 1", + }); + + if ( + closeInfo.code && + !(closeInfo.code === 1000 || + (3000 <= closeInfo.code && closeInfo.code < 5000)) + ) { + throw new DOMException( + "The close code must be either 1000 or in the range of 3000 to 4999.", + "InvalidAccessError", + ); } - [_connection] = new Deferred(); - get connection() { - webidl.assertBranded(this, WebSocketStreamPrototype); - return this[_connection].promise; + const encoder = new TextEncoder(); + if ( + closeInfo.reason && encoder.encode(closeInfo.reason).byteLength > 123 + ) { + throw new DOMException( + "The close reason may not be longer than 123 bytes.", + "SyntaxError", + ); } - [_earlyClose] = false; - [_closed] = new Deferred(); - [_closeSent] = new Deferred(); - get closed() { - webidl.assertBranded(this, WebSocketStreamPrototype); - return this[_closed].promise; + let code = closeInfo.code; + if (closeInfo.reason && code === undefined) { + code = 1000; } - close(closeInfo) { - webidl.assertBranded(this, WebSocketStreamPrototype); - closeInfo = webidl.converters.WebSocketCloseInfo(closeInfo, { - prefix: "Failed to execute 'close' on 'WebSocketStream'", - context: "Argument 1", - }); - - if ( - closeInfo.code && - !(closeInfo.code === 1000 || - (3000 <= closeInfo.code && closeInfo.code < 5000)) - ) { - throw new DOMException( - "The close code must be either 1000 or in the range of 3000 to 4999.", - "InvalidAccessError", - ); - } - - const encoder = new TextEncoder(); - if ( - closeInfo.reason && encoder.encode(closeInfo.reason).byteLength > 123 - ) { - throw new DOMException( - "The close reason may not be longer than 123 bytes.", - "SyntaxError", - ); - } - - let code = closeInfo.code; - if (closeInfo.reason && code === undefined) { - code = 1000; - } - - if (this[_connection].state === "pending") { - this[_earlyClose] = true; - } else if (this[_closed].state === "pending") { - PromisePrototypeThen( - core.opAsync("op_ws_close", this[_rid], code, closeInfo.reason), - () => { - setTimeout(() => { - this[_closeSent].resolve(new Date().getTime()); - }, 0); - }, - (err) => { - this[_rid] && core.tryClose(this[_rid]); - this[_closed].reject(err); - }, - ); - } + if (this[_connection].state === "pending") { + this[_earlyClose] = true; + } else if (this[_closed].state === "pending") { + PromisePrototypeThen( + core.opAsync("op_ws_close", this[_rid], code, closeInfo.reason), + () => { + setTimeout(() => { + this[_closeSent].resolve(new Date().getTime()); + }, 0); + }, + (err) => { + this[_rid] && core.tryClose(this[_rid]); + this[_closed].reject(err); + }, + ); } + } - [SymbolFor("Deno.customInspect")](inspect) { - return `${this.constructor.name} ${ - inspect({ - url: this.url, - }) - }`; - } + [SymbolFor("Deno.customInspect")](inspect) { + return `${this.constructor.name} ${ + inspect({ + url: this.url, + }) + }`; } +} - const WebSocketStreamPrototype = WebSocketStream.prototype; +const WebSocketStreamPrototype = WebSocketStream.prototype; - window.__bootstrap.webSocket.WebSocketStream = WebSocketStream; -})(this); +export { WebSocketStream }; diff --git a/ext/websocket/Cargo.toml b/ext/websocket/Cargo.toml index 8b93a9852bd312..d864c48eea9259 100644 --- a/ext/websocket/Cargo.toml +++ b/ext/websocket/Cargo.toml @@ -2,7 +2,7 @@ [package] name = "deno_websocket" -version = "0.94.0" +version = "0.96.0" authors.workspace = true edition.workspace = true license.workspace = true diff --git a/ext/websocket/lib.rs b/ext/websocket/lib.rs index 82a2c5918a77a3..0ae2ada63f64b5 100644 --- a/ext/websocket/lib.rs +++ b/ext/websocket/lib.rs @@ -504,8 +504,7 @@ pub fn init( ) -> Extension { Extension::builder(env!("CARGO_PKG_NAME")) .dependencies(vec!["deno_url", "deno_webidl"]) - .js(include_js_files!( - prefix "internal:ext/websocket", + .esm(include_js_files!( "01_websocket.js", "02_websocketstream.js", )) diff --git a/ext/webstorage/01_webstorage.js b/ext/webstorage/01_webstorage.js index 27d7aac09f33c7..d0e75faf7ae195 100644 --- a/ext/webstorage/01_webstorage.js +++ b/ext/webstorage/01_webstorage.js @@ -2,191 +2,189 @@ /// -((window) => { - const core = window.Deno.core; - const ops = core.ops; - const webidl = window.__bootstrap.webidl; - const { - SafeArrayIterator, - Symbol, - SymbolFor, - ObjectDefineProperty, - ObjectFromEntries, - ObjectEntries, - ReflectGet, - ReflectHas, - Proxy, - } = window.__bootstrap.primordials; - - const _persistent = Symbol("[[persistent]]"); - - class Storage { - [_persistent]; - - constructor() { - webidl.illegalConstructor(); - } - - get length() { - webidl.assertBranded(this, StoragePrototype); - return ops.op_webstorage_length(this[_persistent]); - } - - key(index) { - webidl.assertBranded(this, StoragePrototype); - const prefix = "Failed to execute 'key' on 'Storage'"; - webidl.requiredArguments(arguments.length, 1, { prefix }); - index = webidl.converters["unsigned long"](index, { - prefix, - context: "Argument 1", - }); - - return ops.op_webstorage_key(index, this[_persistent]); - } - - setItem(key, value) { - webidl.assertBranded(this, StoragePrototype); - const prefix = "Failed to execute 'setItem' on 'Storage'"; - webidl.requiredArguments(arguments.length, 2, { prefix }); - key = webidl.converters.DOMString(key, { - prefix, - context: "Argument 1", - }); - value = webidl.converters.DOMString(value, { - prefix, - context: "Argument 2", - }); - - ops.op_webstorage_set(key, value, this[_persistent]); - } - - getItem(key) { - webidl.assertBranded(this, StoragePrototype); - const prefix = "Failed to execute 'getItem' on 'Storage'"; - webidl.requiredArguments(arguments.length, 1, { prefix }); - key = webidl.converters.DOMString(key, { - prefix, - context: "Argument 1", - }); - - return ops.op_webstorage_get(key, this[_persistent]); - } - - removeItem(key) { - webidl.assertBranded(this, StoragePrototype); - const prefix = "Failed to execute 'removeItem' on 'Storage'"; - webidl.requiredArguments(arguments.length, 1, { prefix }); - key = webidl.converters.DOMString(key, { - prefix, - context: "Argument 1", - }); - - ops.op_webstorage_remove(key, this[_persistent]); - } - - clear() { - webidl.assertBranded(this, StoragePrototype); - ops.op_webstorage_clear(this[_persistent]); - } +const core = globalThis.Deno.core; +const ops = core.ops; +import * as webidl from "internal:deno_webidl/00_webidl.js"; +const primordials = globalThis.__bootstrap.primordials; +const { + SafeArrayIterator, + Symbol, + SymbolFor, + ObjectDefineProperty, + ObjectFromEntries, + ObjectEntries, + ReflectGet, + ReflectHas, + Proxy, +} = primordials; + +const _persistent = Symbol("[[persistent]]"); + +class Storage { + [_persistent]; + + constructor() { + webidl.illegalConstructor(); } - const StoragePrototype = Storage.prototype; - - function createStorage(persistent) { - const storage = webidl.createBranded(Storage); - storage[_persistent] = persistent; - - const proxy = new Proxy(storage, { - deleteProperty(target, key) { - if (typeof key == "symbol") { - delete target[key]; - } else { - target.removeItem(key); - } - return true; - }, - defineProperty(target, key, descriptor) { - if (typeof key == "symbol") { - ObjectDefineProperty(target, key, descriptor); - } else { - target.setItem(key, descriptor.value); - } - return true; - }, - get(target, key) { - if (typeof key == "symbol") return target[key]; - if (ReflectHas(target, key)) { - return ReflectGet(...new SafeArrayIterator(arguments)); - } else { - return target.getItem(key) ?? undefined; - } - }, - set(target, key, value) { - if (typeof key == "symbol") { - ObjectDefineProperty(target, key, { - value, - configurable: true, - }); - } else { - target.setItem(key, value); - } - return true; - }, - has(target, p) { - return p === SymbolFor("Deno.customInspect") || - (typeof target.getItem(p)) === "string"; - }, - ownKeys() { - return ops.op_webstorage_iterate_keys(persistent); - }, - getOwnPropertyDescriptor(target, key) { - if (arguments.length === 1) { - return undefined; - } - if (ReflectHas(target, key)) { - return undefined; - } - const value = target.getItem(key); - if (value === null) { - return undefined; - } - return { - value, - enumerable: true, - configurable: true, - writable: true, - }; - }, + get length() { + webidl.assertBranded(this, StoragePrototype); + return ops.op_webstorage_length(this[_persistent]); + } + + key(index) { + webidl.assertBranded(this, StoragePrototype); + const prefix = "Failed to execute 'key' on 'Storage'"; + webidl.requiredArguments(arguments.length, 1, { prefix }); + index = webidl.converters["unsigned long"](index, { + prefix, + context: "Argument 1", }); - proxy[SymbolFor("Deno.customInspect")] = function (inspect) { - return `${this.constructor.name} ${ - inspect({ - length: this.length, - ...ObjectFromEntries(ObjectEntries(proxy)), - }) - }`; - }; + return ops.op_webstorage_key(index, this[_persistent]); + } + + setItem(key, value) { + webidl.assertBranded(this, StoragePrototype); + const prefix = "Failed to execute 'setItem' on 'Storage'"; + webidl.requiredArguments(arguments.length, 2, { prefix }); + key = webidl.converters.DOMString(key, { + prefix, + context: "Argument 1", + }); + value = webidl.converters.DOMString(value, { + prefix, + context: "Argument 2", + }); - return proxy; + ops.op_webstorage_set(key, value, this[_persistent]); } - let localStorage; - let sessionStorage; + getItem(key) { + webidl.assertBranded(this, StoragePrototype); + const prefix = "Failed to execute 'getItem' on 'Storage'"; + webidl.requiredArguments(arguments.length, 1, { prefix }); + key = webidl.converters.DOMString(key, { + prefix, + context: "Argument 1", + }); + + return ops.op_webstorage_get(key, this[_persistent]); + } + + removeItem(key) { + webidl.assertBranded(this, StoragePrototype); + const prefix = "Failed to execute 'removeItem' on 'Storage'"; + webidl.requiredArguments(arguments.length, 1, { prefix }); + key = webidl.converters.DOMString(key, { + prefix, + context: "Argument 1", + }); + + ops.op_webstorage_remove(key, this[_persistent]); + } + + clear() { + webidl.assertBranded(this, StoragePrototype); + ops.op_webstorage_clear(this[_persistent]); + } +} + +const StoragePrototype = Storage.prototype; + +function createStorage(persistent) { + const storage = webidl.createBranded(Storage); + storage[_persistent] = persistent; - window.__bootstrap.webStorage = { - localStorage() { - if (!localStorage) { - localStorage = createStorage(true); + const proxy = new Proxy(storage, { + deleteProperty(target, key) { + if (typeof key == "symbol") { + delete target[key]; + } else { + target.removeItem(key); } - return localStorage; + return true; }, - sessionStorage() { - if (!sessionStorage) { - sessionStorage = createStorage(false); + defineProperty(target, key, descriptor) { + if (typeof key == "symbol") { + ObjectDefineProperty(target, key, descriptor); + } else { + target.setItem(key, descriptor.value); } - return sessionStorage; + return true; }, - Storage, + get(target, key) { + if (typeof key == "symbol") return target[key]; + if (ReflectHas(target, key)) { + return ReflectGet(...new SafeArrayIterator(arguments)); + } else { + return target.getItem(key) ?? undefined; + } + }, + set(target, key, value) { + if (typeof key == "symbol") { + ObjectDefineProperty(target, key, { + value, + configurable: true, + }); + } else { + target.setItem(key, value); + } + return true; + }, + has(target, p) { + return p === SymbolFor("Deno.customInspect") || + (typeof target.getItem(p)) === "string"; + }, + ownKeys() { + return ops.op_webstorage_iterate_keys(persistent); + }, + getOwnPropertyDescriptor(target, key) { + if (arguments.length === 1) { + return undefined; + } + if (ReflectHas(target, key)) { + return undefined; + } + const value = target.getItem(key); + if (value === null) { + return undefined; + } + return { + value, + enumerable: true, + configurable: true, + writable: true, + }; + }, + }); + + proxy[SymbolFor("Deno.customInspect")] = function (inspect) { + return `${this.constructor.name} ${ + inspect({ + length: this.length, + ...ObjectFromEntries(ObjectEntries(proxy)), + }) + }`; }; -})(this); + + return proxy; +} + +let localStorageStorage; +function localStorage() { + if (!localStorageStorage) { + localStorageStorage = createStorage(true); + } + return localStorageStorage; +} + +let sessionStorageStorage; +function sessionStorage() { + if (!sessionStorageStorage) { + sessionStorageStorage = createStorage(false); + } + return sessionStorageStorage; +} + +export { localStorage, sessionStorage, Storage }; diff --git a/ext/webstorage/Cargo.toml b/ext/webstorage/Cargo.toml index cc5dd1f17d1a2d..f45132b05df5d6 100644 --- a/ext/webstorage/Cargo.toml +++ b/ext/webstorage/Cargo.toml @@ -2,7 +2,7 @@ [package] name = "deno_webstorage" -version = "0.84.0" +version = "0.86.0" authors.workspace = true edition.workspace = true license.workspace = true diff --git a/ext/webstorage/lib.rs b/ext/webstorage/lib.rs index 29deaee84426cb..852ce229ef4dd5 100644 --- a/ext/webstorage/lib.rs +++ b/ext/webstorage/lib.rs @@ -24,10 +24,7 @@ const MAX_STORAGE_BYTES: u32 = 10 * 1024 * 1024; pub fn init(origin_storage_dir: Option) -> Extension { Extension::builder(env!("CARGO_PKG_NAME")) .dependencies(vec!["deno_webidl"]) - .js(include_js_files!( - prefix "internal:ext/webstorage", - "01_webstorage.js", - )) + .esm(include_js_files!("01_webstorage.js",)) .ops(vec![ op_webstorage_length::decl(), op_webstorage_key::decl(), diff --git a/ext/wsi/01_wsi.js b/ext/wsi/01_wsi.js index 6b08abf1afbc5c..373870c211a1e1 100644 --- a/ext/wsi/01_wsi.js +++ b/ext/wsi/01_wsi.js @@ -1,735 +1,727 @@ // Copyright 2023 Jo Bates. All rights reserved. MIT license. -"use strict"; - -((globalThis) => { - const ops = globalThis.Deno.core.ops; - const webgpu = globalThis.__bootstrap.webgpu; - const webidl = globalThis.__bootstrap.webidl; - - const _wid = Symbol("wid"); - const _gpuSurface = Symbol("gpuSurface"); - - const windows = new Map(); - - function convertPosition(prefix, args) { - if (args.length >= 2) { - const x = webidl.converters["long"](args[0], { - prefix, - context: "Argument 1", - }); - const y = webidl.converters["long"](args[1], { - prefix, - context: "Argument 2", - }); - return [x, y]; +const core = globalThis.Deno.core; +const ops = core.ops; +import * as webgpu from "internal:deno_webgpu/01_webgpu.js"; +import * as webidl from "internal:deno_webidl/00_webidl.js"; + +const _wid = Symbol("wid"); +const _gpuSurface = Symbol("gpuSurface"); + +const windows = new Map(); + +function convertPosition(prefix, args) { + if (args.length >= 2) { + const x = webidl.converters["long"](args[0], { + prefix, + context: "Argument 1", + }); + const y = webidl.converters["long"](args[1], { + prefix, + context: "Argument 2", + }); + return [x, y]; + } else { + webidl.requiredArguments(args.length, 1, { prefix }); + const position = webidl.converters["WSIPosition"](args[0], { + prefix, + context: "Argument 1", + }); + checkPosition(prefix, position); + return position; + } +} + +function checkPosition(prefix, position) { + if (position.length != 2) { + throw new DOMException( + `${prefix}: position.length must equal 2.`, + "OperationError", + ); + } +} + +function convertSize(prefix, args, nullable = false) { + if (args.length >= 2) { + const width = webidl.converters["unsigned long"](args[0], { + prefix, + context: "Argument 1", + }); + const height = webidl.converters["unsigned long"](args[1], { + prefix, + context: "Argument 2", + }); + return [width, height]; + } else { + webidl.requiredArguments(args.length, 1, { prefix }); + if (nullable && args[0] === null) { + return null; } else { - webidl.requiredArguments(args.length, 1, { prefix }); - const position = webidl.converters["WSIPosition"](args[0], { + const size = webidl.converters["WSISize"](args[0], { prefix, context: "Argument 1", }); - checkPosition(prefix, position); - return position; + checkSize(prefix, size); + return size; } } - - function checkPosition(prefix, position) { - if (position.length != 2) { - throw new DOMException( - `${prefix}: position.length must equal 2.`, - "OperationError", - ); - } +} + +function checkSize(prefix, size) { + if (size.length != 2) { + throw new DOMException( + `${prefix}: size.length must equal 2.`, + "OperationError", + ); } +} - function convertSize(prefix, args, nullable = false) { - if (args.length >= 2) { - const width = webidl.converters["unsigned long"](args[0], { - prefix, - context: "Argument 1", - }); - const height = webidl.converters["unsigned long"](args[1], { - prefix, - context: "Argument 2", - }); - return [width, height]; - } else { - webidl.requiredArguments(args.length, 1, { prefix }); - if (nullable && args[0] === null) { - return null; - } else { - const size = webidl.converters["WSISize"](args[0], { - prefix, - context: "Argument 1", - }); - checkSize(prefix, size); - return size; - } - } - } +class WSI { + [webidl.brand] = webidl.brand; - function checkSize(prefix, size) { - if (size.length != 2) { - throw new DOMException( - `${prefix}: size.length must equal 2.`, - "OperationError", - ); - } + constructor() { + webidl.illegalConstructor(); } - class WSI { - [webidl.brand] = webidl.brand; + async nextEvent() { + webidl.assertBranded(this, WSIPrototype); - constructor() { - webidl.illegalConstructor(); + const event = await core.opAsync("op_wsi_next_event"); + if (event.window != null) { + event.window = windows.get(event.window); } + return event; + } - async nextEvent() { - webidl.assertBranded(this, WSIPrototype); + setDeviceEventFilter(filter) { + webidl.assertBranded(this, WSIPrototype); + const prefix = "Failed to execute 'setDeviceEventFilter' on 'WSI'"; - const event = await ops.op_wsi_next_event(); - if (event.window != null) { - event.window = windows.get(event.window); - } - return event; - } + webidl.requiredArguments(arguments.length, 1, { prefix }); + filter = webidl.converters["WSIDeviceEventFilter"](filter, { + prefix, + context: "Argument 1", + }); + + return ops.op_wsi_set_device_event_filter(filter); + } - setDeviceEventFilter(filter) { - webidl.assertBranded(this, WSIPrototype); - const prefix = "Failed to execute 'setDeviceEventFilter' on 'WSI'"; + createWindow(options) { + webidl.assertBranded(this, WSIPrototype); + const prefix = "Failed to execute 'createWindow' on 'WSI'"; - webidl.requiredArguments(arguments.length, 1, { prefix }); - filter = webidl.converters["WSIDeviceEventFilter"](filter, { + if (options !== undefined) { + options = webidl.converters["WSICreateWindowOptions"](options, { prefix, context: "Argument 1", }); - - return ops.op_wsi_set_device_event_filter(filter); + if (options.position != null) { + checkPosition(prefix, options.position); + } + if (options.innerSize != null) { + checkSize(prefix, options.innerSize); + } + if (options.minInnerSize != null) { + checkSize(prefix, options.minInnerSize); + } + if (options.maxInnerSize != null) { + checkSize(prefix, options.maxInnerSize); + } + if (options.resizeIncrements != null) { + checkSize(prefix, options.resizeIncrements); + } } - createWindow(options) { - webidl.assertBranded(this, WSIPrototype); - const prefix = "Failed to execute 'createWindow' on 'WSI'"; - - if (options !== undefined) { - options = webidl.converters["WSICreateWindowOptions"](options, { - prefix, - context: "Argument 1", - }); - if (options.position != null) { - checkPosition(prefix, options.position); - } - if (options.innerSize != null) { - checkSize(prefix, options.innerSize); - } - if (options.minInnerSize != null) { - checkSize(prefix, options.minInnerSize); - } - if (options.maxInnerSize != null) { - checkSize(prefix, options.maxInnerSize); - } - if (options.resizeIncrements != null) { - checkSize(prefix, options.resizeIncrements); - } - } + const wid = ops.op_wsi_create_window(options); + const window = webidl.createBranded(WSIWindow); + windows.set(wid, window); + window[_wid] = wid; + return window; + } +} +const WSIPrototype = WSI.prototype; - const wid = ops.op_wsi_create_window(options); - const window = webidl.createBranded(WSIWindow); - windows.set(wid, window); - window[_wid] = wid; - return window; - } +class WSIModifierKey { + constructor() { + webidl.illegalConstructor(); } - const WSIPrototype = WSI.prototype; - class WSIModifierKey { - constructor() { - webidl.illegalConstructor(); - } + static get SHIFT() { + return 0o0004; + } + static get CTRL() { + return 0o0040; + } + static get ALT() { + return 0o0400; + } + static get GUI() { + return 0o4000; + } +} + +function assertWindow(window, { prefix, context }) { + const wid = window[_wid]; + if (wid === undefined) { + throw new DOMException( + `${prefix}: ${context} references an invalid or destroyed window.`, + "OperationError", + ); + } + return wid; +} - static get SHIFT() { - return 0o0004; - } - static get CTRL() { - return 0o0040; - } - static get ALT() { - return 0o0400; - } - static get GUI() { - return 0o4000; - } +class WSIWindow { + [_wid]; + [_gpuSurface]; + + constructor() { + webidl.illegalConstructor(); } - function assertWindow(window, { prefix, context }) { - const wid = window[_wid]; - if (wid === undefined) { - throw new DOMException( - `${prefix}: ${context} references an invalid or destroyed window.`, - "OperationError", - ); - } - return wid; + setContentProtected(contentProtected = true) { + webidl.assertBranded(this, WSIWindowPrototype); + const prefix = "Failed to execute 'setContentProtected' on 'WSIWindow'"; + const wid = assertWindow(this, { prefix, context: "this" }); + + contentProtected = webidl.converters["boolean"](contentProtected, { + prefix, + context: "Argument 1", + }); + + return ops.op_wsi_window_set_content_protected(wid, contentProtected); } - class WSIWindow { - [_wid]; - [_gpuSurface]; + setCursorGrabMode(mode) { + webidl.assertBranded(this, WSIWindowPrototype); + const prefix = "Failed to execute 'setCursorGrabMode' on 'WSIWindow'"; + const wid = assertWindow(this, { prefix, context: "this" }); - constructor() { - webidl.illegalConstructor(); - } + webidl.requiredArguments(arguments.length, 1, { prefix }); + mode = webidl.converters["WSICursorGrabMode"](mode, { + prefix, + context: "Argument 1", + }); - setContentProtected(contentProtected = true) { - webidl.assertBranded(this, WSIWindowPrototype); - const prefix = "Failed to execute 'setContentProtected' on 'WSIWindow'"; - const wid = assertWindow(this, { prefix, context: "this" }); + return ops.op_wsi_window_set_cursor_grab_mode(wid, mode); + } - contentProtected = webidl.converters["boolean"](contentProtected, { - prefix, - context: "Argument 1", - }); + setCursorHitTestEnabled(enabled = true) { + webidl.assertBranded(this, WSIWindowPrototype); + const prefix = "Failed to execute 'setCursorHitTestEnabled' on 'WSIWindow'"; + const wid = assertWindow(this, { prefix, context: "this" }); - return ops.op_wsi_window_set_content_protected(wid, contentProtected); - } + enabled = webidl.converters["boolean"](enabled, { + prefix, + context: "Argument 1", + }); - setCursorGrabMode(mode) { - webidl.assertBranded(this, WSIWindowPrototype); - const prefix = "Failed to execute 'setCursorGrabMode' on 'WSIWindow'"; - const wid = assertWindow(this, { prefix, context: "this" }); + return ops.op_wsi_window_set_cursor_hit_test_enabled(wid, enabled); + } - webidl.requiredArguments(arguments.length, 1, { prefix }); - mode = webidl.converters["WSICursorGrabMode"](mode, { - prefix, - context: "Argument 1", - }); + setCursorIcon(icon) { + webidl.assertBranded(this, WSIWindowPrototype); + const prefix = "Failed to execute 'setCursorIcon' on 'WSIWindow'"; + const wid = assertWindow(this, { prefix, context: "this" }); - return ops.op_wsi_window_set_cursor_grab_mode(wid, mode); - } + webidl.requiredArguments(arguments.length, 1, { prefix }); + icon = webidl.converters["WSICursorIcon"](icon, { + prefix, + context: "Argument 1", + }); - setCursorHitTestEnabled(enabled = true) { - webidl.assertBranded(this, WSIWindowPrototype); - const prefix = "Failed to execute 'setCursorHitTestEnabled' on 'WSIWindow'"; - const wid = assertWindow(this, { prefix, context: "this" }); + return ops.op_wsi_window_set_cursor_icon(wid, icon); + } - enabled = webidl.converters["boolean"](enabled, { - prefix, - context: "Argument 1", - }); + setCursorPosition() { + webidl.assertBranded(this, WSIWindowPrototype); + const prefix = "Failed to execute 'setCursorPosition' on 'WSIWindow'"; + const wid = assertWindow(this, { prefix, context: "this" }); - return ops.op_wsi_window_set_cursor_hit_test_enabled(wid, enabled); - } + const position = convertPosition(prefix, arguments); - setCursorIcon(icon) { - webidl.assertBranded(this, WSIWindowPrototype); - const prefix = "Failed to execute 'setCursorIcon' on 'WSIWindow'"; - const wid = assertWindow(this, { prefix, context: "this" }); + return ops.op_wsi_window_set_cursor_position(wid, position); + } - webidl.requiredArguments(arguments.length, 1, { prefix }); - icon = webidl.converters["WSICursorIcon"](icon, { - prefix, - context: "Argument 1", - }); + setCursorVisible(visible = true) { + webidl.assertBranded(this, WSIWindowPrototype); + const prefix = "Failed to execute 'setCursorVisible' on 'WSIWindow'"; + const wid = assertWindow(this, { prefix, context: "this" }); - return ops.op_wsi_window_set_cursor_icon(wid, icon); - } + visible = webidl.converters["boolean"](visible, { + prefix, + context: "Argument 1", + }); - setCursorPosition() { - webidl.assertBranded(this, WSIWindowPrototype); - const prefix = "Failed to execute 'setCursorPosition' on 'WSIWindow'"; - const wid = assertWindow(this, { prefix, context: "this" }); + return ops.op_wsi_window_set_cursor_visible(wid, visible); + } - const position = convertPosition(prefix, arguments); + isDecorated() { + webidl.assertBranded(this, WSIWindowPrototype); + const prefix = "Failed to execute 'isDecorated' on 'WSIWindow'"; + const wid = assertWindow(this, { prefix, context: "this" }); - return ops.op_wsi_window_set_cursor_position(wid, position); - } + return ops.op_wsi_window_is_decorated(wid); + } - setCursorVisible(visible = true) { - webidl.assertBranded(this, WSIWindowPrototype); - const prefix = "Failed to execute 'setCursorVisible' on 'WSIWindow'"; - const wid = assertWindow(this, { prefix, context: "this" }); + setDecorated(decorated = true) { + webidl.assertBranded(this, WSIWindowPrototype); + const prefix = "Failed to execute 'setDecorated' on 'WSIWindow'"; + const wid = assertWindow(this, { prefix, context: "this" }); - visible = webidl.converters["boolean"](visible, { - prefix, - context: "Argument 1", - }); + decorated = webidl.converters["boolean"](decorated, { + prefix, + context: "Argument 1", + }); - return ops.op_wsi_window_set_cursor_visible(wid, visible); - } + return ops.op_wsi_window_set_decorated(wid, decorated); + } - isDecorated() { - webidl.assertBranded(this, WSIWindowPrototype); - const prefix = "Failed to execute 'isDecorated' on 'WSIWindow'"; - const wid = assertWindow(this, { prefix, context: "this" }); + getEnabledButtons() { + webidl.assertBranded(this, WSIWindowPrototype); + const prefix = "Failed to execute 'getEnabledButtons' on 'WSIWindow'"; + const wid = assertWindow(this, { prefix, context: "this" }); - return ops.op_wsi_window_is_decorated(wid); - } + return ops.op_wsi_window_get_enabled_buttons(wid); + } - setDecorated(decorated = true) { - webidl.assertBranded(this, WSIWindowPrototype); - const prefix = "Failed to execute 'setDecorated' on 'WSIWindow'"; - const wid = assertWindow(this, { prefix, context: "this" }); + setEnabledButtons(buttons) { + webidl.assertBranded(this, WSIWindowPrototype); + const prefix = "Failed to execute 'setEnabledButtons' on 'WSIWindow'"; + const wid = assertWindow(this, { prefix, context: "this" }); - decorated = webidl.converters["boolean"](decorated, { - prefix, - context: "Argument 1", - }); + webidl.requiredArguments(arguments.length, 1, { prefix }); + buttons = webidl.converters["unsigned long"](buttons, { + prefix, + context: "Argument 1", + }); - return ops.op_wsi_window_set_decorated(wid, decorated); - } + return ops.op_wsi_window_set_enabled_buttons(wid, buttons); + } - getEnabledButtons() { - webidl.assertBranded(this, WSIWindowPrototype); - const prefix = "Failed to execute 'getEnabledButtons' on 'WSIWindow'"; - const wid = assertWindow(this, { prefix, context: "this" }); + hasFocus() { + webidl.assertBranded(this, WSIWindowPrototype); + const prefix = "Failed to execute 'hasFocus' on 'WSIWindow'"; + const wid = assertWindow(this, { prefix, context: "this" }); - return ops.op_wsi_window_get_enabled_buttons(wid); - } + return ops.op_wsi_window_has_focus(wid); + } - setEnabledButtons(buttons) { - webidl.assertBranded(this, WSIWindowPrototype); - const prefix = "Failed to execute 'setEnabledButtons' on 'WSIWindow'"; - const wid = assertWindow(this, { prefix, context: "this" }); + takeFocus() { + webidl.assertBranded(this, WSIWindowPrototype); + const prefix = "Failed to execute 'takeFocus' on 'WSIWindow'"; + const wid = assertWindow(this, { prefix, context: "this" }); - webidl.requiredArguments(arguments.length, 1, { prefix }); - buttons = webidl.converters["unsigned long"](buttons, { - prefix, - context: "Argument 1", - }); + return ops.op_wsi_window_take_focus(wid); + } - return ops.op_wsi_window_set_enabled_buttons(wid, buttons); - } + isFullscreen() { + webidl.assertBranded(this, WSIWindowPrototype); + const prefix = "Failed to execute 'isFullscreen' on 'WSIWindow'"; + const wid = assertWindow(this, { prefix, context: "this" }); - hasFocus() { - webidl.assertBranded(this, WSIWindowPrototype); - const prefix = "Failed to execute 'hasFocus' on 'WSIWindow'"; - const wid = assertWindow(this, { prefix, context: "this" }); + return ops.op_wsi_window_is_fullscreen(wid); + } - return ops.op_wsi_window_has_focus(wid); - } + setFullscreen(fullscreen = true) { + webidl.assertBranded(this, WSIWindowPrototype); + const prefix = "Failed to execute 'setFullscreen' on 'WSIWindow'"; + const wid = assertWindow(this, { prefix, context: "this" }); - takeFocus() { - webidl.assertBranded(this, WSIWindowPrototype); - const prefix = "Failed to execute 'takeFocus' on 'WSIWindow'"; - const wid = assertWindow(this, { prefix, context: "this" }); + fullscreen = webidl.converters["boolean"](fullscreen, { + prefix, + context: "Argument 1", + }); - return ops.op_wsi_window_take_focus(wid); - } + return ops.op_wsi_window_set_fullscreen(wid, fullscreen); + } - isFullscreen() { - webidl.assertBranded(this, WSIWindowPrototype); - const prefix = "Failed to execute 'isFullscreen' on 'WSIWindow'"; - const wid = assertWindow(this, { prefix, context: "this" }); + getGPUSurface() { + webidl.assertBranded(this, WSIWindowPrototype); + const prefix = "Failed to execute 'getGPUSurface' on 'WSIWindow'"; + const wid = assertWindow(this, { prefix, context: "this" }); - return ops.op_wsi_window_is_fullscreen(wid); + if (this[_gpuSurface] != null) { + return this[_gpuSurface]; + } else { + const rid = ops.op_wsi_window_create_gpu_surface(wid); + return this[_gpuSurface] = webgpu.createGPUSurface(rid); } + } - setFullscreen(fullscreen = true) { - webidl.assertBranded(this, WSIWindowPrototype); - const prefix = "Failed to execute 'setFullscreen' on 'WSIWindow'"; - const wid = assertWindow(this, { prefix, context: "this" }); + setIMEAllowed(allowed = true) { + webidl.assertBranded(this, WSIWindowPrototype); + const prefix = "Failed to execute 'setIMEAllowed' on 'WSIWindow'"; + const wid = assertWindow(this, { prefix, context: "this" }); - fullscreen = webidl.converters["boolean"](fullscreen, { - prefix, - context: "Argument 1", - }); + allowed = webidl.converters["boolean"](allowed, { + prefix, + context: "Argument 1", + }); - return ops.op_wsi_window_set_fullscreen(wid, fullscreen); - } + return ops.op_wsi_window_set_ime_allowed(wid, allowed); + } - getGPUSurface() { - webidl.assertBranded(this, WSIWindowPrototype); - const prefix = "Failed to execute 'getGPUSurface' on 'WSIWindow'"; - const wid = assertWindow(this, { prefix, context: "this" }); + setIMEPosition() { + webidl.assertBranded(this, WSIWindowPrototype); + const prefix = "Failed to execute 'setIMEPosition' on 'WSIWindow'"; + const wid = assertWindow(this, { prefix, context: "this" }); - if (this[_gpuSurface] != null) { - return this[_gpuSurface]; - } else { - const rid = ops.op_wsi_window_create_gpu_surface(wid); - return this[_gpuSurface] = webgpu.createGPUSurface(rid); - } - } + const position = convertPosition(prefix, arguments); - setIMEAllowed(allowed = true) { - webidl.assertBranded(this, WSIWindowPrototype); - const prefix = "Failed to execute 'setIMEAllowed' on 'WSIWindow'"; - const wid = assertWindow(this, { prefix, context: "this" }); + return ops.op_wsi_window_set_ime_position(wid, position); + } - allowed = webidl.converters["boolean"](allowed, { - prefix, - context: "Argument 1", - }); + setIMEPurpose(purpose) { + webidl.assertBranded(this, WSIWindowPrototype); + const prefix = "Failed to execute 'setIMEPurpose' on 'WSIWindow'"; + const wid = assertWindow(this, { prefix, context: "this" }); - return ops.op_wsi_window_set_ime_allowed(wid, allowed); - } + webidl.requiredArguments(arguments.length, 1, { prefix }); + purpose = webidl.converters["WSIIMEPurpose"](purpose, { + prefix, + context: "Argument 1", + }); - setIMEPosition() { - webidl.assertBranded(this, WSIWindowPrototype); - const prefix = "Failed to execute 'setIMEPosition' on 'WSIWindow'"; - const wid = assertWindow(this, { prefix, context: "this" }); + return ops.op_wsi_window_set_ime_purpose(wid, purpose); + } - const position = convertPosition(prefix, arguments); + getInnerPosition() { + webidl.assertBranded(this, WSIWindowPrototype); + const prefix = "Failed to execute 'getInnerPosition' on 'WSIWindow'"; + const wid = assertWindow(this, { prefix, context: "this" }); - return ops.op_wsi_window_set_ime_position(wid, position); - } + return ops.op_wsi_window_get_inner_position(wid); + } - setIMEPurpose(purpose) { - webidl.assertBranded(this, WSIWindowPrototype); - const prefix = "Failed to execute 'setIMEPurpose' on 'WSIWindow'"; - const wid = assertWindow(this, { prefix, context: "this" }); + getOuterPosition() { + webidl.assertBranded(this, WSIWindowPrototype); + const prefix = "Failed to execute 'getOuterPosition' on 'WSIWindow'"; + const wid = assertWindow(this, { prefix, context: "this" }); - webidl.requiredArguments(arguments.length, 1, { prefix }); - purpose = webidl.converters["WSIIMEPurpose"](purpose, { - prefix, - context: "Argument 1", - }); + return ops.op_wsi_window_get_outer_position(wid); + } - return ops.op_wsi_window_set_ime_purpose(wid, purpose); - } + setOuterPosition() { + webidl.assertBranded(this, WSIWindowPrototype); + const prefix = "Failed to execute 'setOuterPosition' on 'WSIWindow'"; + const wid = assertWindow(this, { prefix, context: "this" }); - getInnerPosition() { - webidl.assertBranded(this, WSIWindowPrototype); - const prefix = "Failed to execute 'getInnerPosition' on 'WSIWindow'"; - const wid = assertWindow(this, { prefix, context: "this" }); + const position = convertPosition(prefix, arguments); - return ops.op_wsi_window_get_inner_position(wid); - } + return ops.op_wsi_window_set_outer_position(wid, position); + } - getOuterPosition() { - webidl.assertBranded(this, WSIWindowPrototype); - const prefix = "Failed to execute 'getOuterPosition' on 'WSIWindow'"; - const wid = assertWindow(this, { prefix, context: "this" }); + getInnerSize() { + webidl.assertBranded(this, WSIWindowPrototype); + const prefix = "Failed to execute 'getInnerSize' on 'WSIWindow'"; + const wid = assertWindow(this, { prefix, context: "this" }); - return ops.op_wsi_window_get_outer_position(wid); - } + return ops.op_wsi_window_get_inner_size(wid); + } - setOuterPosition() { - webidl.assertBranded(this, WSIWindowPrototype); - const prefix = "Failed to execute 'setOuterPosition' on 'WSIWindow'"; - const wid = assertWindow(this, { prefix, context: "this" }); + getOuterSize() { + webidl.assertBranded(this, WSIWindowPrototype); + const prefix = "Failed to execute 'getOuterSize' on 'WSIWindow'"; + const wid = assertWindow(this, { prefix, context: "this" }); - const position = convertPosition(prefix, arguments); + return ops.op_wsi_window_get_outer_size(wid); + } - return ops.op_wsi_window_set_outer_position(wid, position); - } + setInnerSize() { + webidl.assertBranded(this, WSIWindowPrototype); + const prefix = "Failed to execute 'setInnerSize' on 'WSIWindow'"; + const wid = assertWindow(this, { prefix, context: "this" }); - getInnerSize() { - webidl.assertBranded(this, WSIWindowPrototype); - const prefix = "Failed to execute 'getInnerSize' on 'WSIWindow'"; - const wid = assertWindow(this, { prefix, context: "this" }); + const size = convertSize(prefix, arguments); - return ops.op_wsi_window_get_inner_size(wid); - } + return ops.op_wsi_window_set_inner_size(wid, size); + } - getOuterSize() { - webidl.assertBranded(this, WSIWindowPrototype); - const prefix = "Failed to execute 'getOuterSize' on 'WSIWindow'"; - const wid = assertWindow(this, { prefix, context: "this" }); + setMinInnerSize() { + webidl.assertBranded(this, WSIWindowPrototype); + const prefix = "Failed to execute 'setMinInnerSize' on 'WSIWindow'"; + const wid = assertWindow(this, { prefix, context: "this" }); - return ops.op_wsi_window_get_outer_size(wid); - } + const nullable = true; + const size = convertSize(prefix, arguments, nullable); - setInnerSize() { - webidl.assertBranded(this, WSIWindowPrototype); - const prefix = "Failed to execute 'setInnerSize' on 'WSIWindow'"; - const wid = assertWindow(this, { prefix, context: "this" }); + return ops.op_wsi_window_set_min_inner_size(wid, size); + } - const size = convertSize(prefix, arguments); + setMaxInnerSize() { + webidl.assertBranded(this, WSIWindowPrototype); + const prefix = "Failed to execute 'setMaxInnerSize' on 'WSIWindow'"; + const wid = assertWindow(this, { prefix, context: "this" }); - return ops.op_wsi_window_set_inner_size(wid, size); - } + const nullable = true; + const size = convertSize(prefix, arguments, nullable); - setMinInnerSize() { - webidl.assertBranded(this, WSIWindowPrototype); - const prefix = "Failed to execute 'setMinInnerSize' on 'WSIWindow'"; - const wid = assertWindow(this, { prefix, context: "this" }); + return ops.op_wsi_window_set_max_inner_size(wid, size); + } - const nullable = true; - const size = convertSize(prefix, arguments, nullable); + setLevel(level) { + webidl.assertBranded(this, WSIWindowPrototype); + const prefix = "Failed to execute 'setLevel' on 'WSIWindow'"; + const wid = assertWindow(this, { prefix, context: "this" }); - return ops.op_wsi_window_set_min_inner_size(wid, size); - } + webidl.requiredArguments(arguments.length, 1, { prefix }); + level = webidl.converters["WSIWindowLevel"](level, { + prefix, + context: "Argument 1", + }); - setMaxInnerSize() { - webidl.assertBranded(this, WSIWindowPrototype); - const prefix = "Failed to execute 'setMaxInnerSize' on 'WSIWindow'"; - const wid = assertWindow(this, { prefix, context: "this" }); + return ops.op_wsi_window_set_level(wid, level); + } - const nullable = true; - const size = convertSize(prefix, arguments, nullable); + isMinimized() { + webidl.assertBranded(this, WSIWindowPrototype); + const prefix = "Failed to execute 'isMinimized' on 'WSIWindow'"; + const wid = assertWindow(this, { prefix, context: "this" }); - return ops.op_wsi_window_set_max_inner_size(wid, size); - } + return ops.op_wsi_window_is_minimized(wid); + } - setLevel(level) { - webidl.assertBranded(this, WSIWindowPrototype); - const prefix = "Failed to execute 'setLevel' on 'WSIWindow'"; - const wid = assertWindow(this, { prefix, context: "this" }); + setMinimized(minimized = true) { + webidl.assertBranded(this, WSIWindowPrototype); + const prefix = "Failed to execute 'setMinimized' on 'WSIWindow'"; + const wid = assertWindow(this, { prefix, context: "this" }); - webidl.requiredArguments(arguments.length, 1, { prefix }); - level = webidl.converters["WSIWindowLevel"](level, { - prefix, - context: "Argument 1", - }); + minimized = webidl.converters["boolean"](minimized, { + prefix, + context: "Argument 1", + }); - return ops.op_wsi_window_set_level(wid, level); - } + return ops.op_wsi_window_set_minimized(wid, minimized); + } - isMinimized() { - webidl.assertBranded(this, WSIWindowPrototype); - const prefix = "Failed to execute 'isMinimized' on 'WSIWindow'"; - const wid = assertWindow(this, { prefix, context: "this" }); + isMaximized() { + webidl.assertBranded(this, WSIWindowPrototype); + const prefix = "Failed to execute 'isMaximized' on 'WSIWindow'"; + const wid = assertWindow(this, { prefix, context: "this" }); - return ops.op_wsi_window_is_minimized(wid); - } + return ops.op_wsi_window_is_maximized(wid); + } - setMinimized(minimized = true) { - webidl.assertBranded(this, WSIWindowPrototype); - const prefix = "Failed to execute 'setMinimized' on 'WSIWindow'"; - const wid = assertWindow(this, { prefix, context: "this" }); + setMaximized(maximized = true) { + webidl.assertBranded(this, WSIWindowPrototype); + const prefix = "Failed to execute 'setMaximized' on 'WSIWindow'"; + const wid = assertWindow(this, { prefix, context: "this" }); - minimized = webidl.converters["boolean"](minimized, { - prefix, - context: "Argument 1", - }); + maximized = webidl.converters["boolean"](maximized, { + prefix, + context: "Argument 1", + }); - return ops.op_wsi_window_set_minimized(wid, minimized); - } + return ops.op_wsi_window_set_maximized(wid, maximized); + } - isMaximized() { - webidl.assertBranded(this, WSIWindowPrototype); - const prefix = "Failed to execute 'isMaximized' on 'WSIWindow'"; - const wid = assertWindow(this, { prefix, context: "this" }); + isResizable() { + webidl.assertBranded(this, WSIWindowPrototype); + const prefix = "Failed to execute 'isResizable' on 'WSIWindow'"; + const wid = assertWindow(this, { prefix, context: "this" }); - return ops.op_wsi_window_is_maximized(wid); - } + return ops.op_wsi_window_is_resizable(wid); + } - setMaximized(maximized = true) { - webidl.assertBranded(this, WSIWindowPrototype); - const prefix = "Failed to execute 'setMaximized' on 'WSIWindow'"; - const wid = assertWindow(this, { prefix, context: "this" }); + setResizable(resizable = true) { + webidl.assertBranded(this, WSIWindowPrototype); + const prefix = "Failed to execute 'setResizable' on 'WSIWindow'"; + const wid = assertWindow(this, { prefix, context: "this" }); - maximized = webidl.converters["boolean"](maximized, { - prefix, - context: "Argument 1", - }); + resizable = webidl.converters["boolean"](resizable, { + prefix, + context: "Argument 1", + }); - return ops.op_wsi_window_set_maximized(wid, maximized); - } + return ops.op_wsi_window_set_resizable(wid, resizable); + } - isResizable() { - webidl.assertBranded(this, WSIWindowPrototype); - const prefix = "Failed to execute 'isResizable' on 'WSIWindow'"; - const wid = assertWindow(this, { prefix, context: "this" }); + getResizeIncrements() { + webidl.assertBranded(this, WSIWindowPrototype); + const prefix = "Failed to execute 'getResizeIncrements' on 'WSIWindow'"; + const wid = assertWindow(this, { prefix, context: "this" }); - return ops.op_wsi_window_is_resizable(wid); - } + return ops.op_wsi_window_get_resize_increments(wid); + } - setResizable(resizable = true) { - webidl.assertBranded(this, WSIWindowPrototype); - const prefix = "Failed to execute 'setResizable' on 'WSIWindow'"; - const wid = assertWindow(this, { prefix, context: "this" }); + setResizeIncrements() { + webidl.assertBranded(this, WSIWindowPrototype); + const prefix = "Failed to execute 'setResizeIncrements' on 'WSIWindow'"; + const wid = assertWindow(this, { prefix, context: "this" }); - resizable = webidl.converters["boolean"](resizable, { - prefix, - context: "Argument 1", - }); + const nullable = true; + const size = convertSize(prefix, arguments, nullable); - return ops.op_wsi_window_set_resizable(wid, resizable); - } + return ops.op_wsi_window_set_resize_increments(wid, size); + } - getResizeIncrements() { - webidl.assertBranded(this, WSIWindowPrototype); - const prefix = "Failed to execute 'getResizeIncrements' on 'WSIWindow'"; - const wid = assertWindow(this, { prefix, context: "this" }); + getScaleFactor() { + webidl.assertBranded(this, WSIWindowPrototype); + const prefix = "Failed to execute 'getScaleFactor' on 'WSIWindow'"; + const wid = assertWindow(this, { prefix, context: "this" }); - return ops.op_wsi_window_get_resize_increments(wid); - } + return ops.op_wsi_window_get_scale_factor(wid); + } - setResizeIncrements() { - webidl.assertBranded(this, WSIWindowPrototype); - const prefix = "Failed to execute 'setResizeIncrements' on 'WSIWindow'"; - const wid = assertWindow(this, { prefix, context: "this" }); + getTheme() { + webidl.assertBranded(this, WSIWindowPrototype); + const prefix = "Failed to execute 'getTheme' on 'WSIWindow'"; + const wid = assertWindow(this, { prefix, context: "this" }); - const nullable = true; - const size = convertSize(prefix, arguments, nullable); + return ops.op_wsi_window_get_theme(wid); + } + + setTheme(theme) { + webidl.assertBranded(this, WSIWindowPrototype); + const prefix = "Failed to execute 'setTheme' on 'WSIWindow'"; + const wid = assertWindow(this, { prefix, context: "this" }); - return ops.op_wsi_window_set_resize_increments(wid, size); + webidl.requiredArguments(arguments.length, 1, { prefix }); + if (theme !== null) { + theme = webidl.converters["WSIWindowTheme"](theme, { + prefix, + context: "Argument 1", + }); } - getScaleFactor() { - webidl.assertBranded(this, WSIWindowPrototype); - const prefix = "Failed to execute 'getScaleFactor' on 'WSIWindow'"; - const wid = assertWindow(this, { prefix, context: "this" }); + return ops.op_wsi_window_set_theme(wid, theme); + } - return ops.op_wsi_window_get_scale_factor(wid); - } + getTitle() { + webidl.assertBranded(this, WSIWindowPrototype); + const prefix = "Failed to execute 'getTitle' on 'WSIWindow'"; + const wid = assertWindow(this, { prefix, context: "this" }); - getTheme() { - webidl.assertBranded(this, WSIWindowPrototype); - const prefix = "Failed to execute 'getTheme' on 'WSIWindow'"; - const wid = assertWindow(this, { prefix, context: "this" }); + return ops.op_wsi_window_get_title(wid); + } - return ops.op_wsi_window_get_theme(wid); - } + setTitle(title) { + webidl.assertBranded(this, WSIWindowPrototype); + const prefix = "Failed to execute 'setTitle' on 'WSIWindow'"; + const wid = assertWindow(this, { prefix, context: "this" }); - setTheme(theme) { - webidl.assertBranded(this, WSIWindowPrototype); - const prefix = "Failed to execute 'setTheme' on 'WSIWindow'"; - const wid = assertWindow(this, { prefix, context: "this" }); - - webidl.requiredArguments(arguments.length, 1, { prefix }); - if (theme !== null) { - theme = webidl.converters["WSIWindowTheme"](theme, { - prefix, - context: "Argument 1", - }); - } + webidl.requiredArguments(arguments.length, 1, { prefix }); + title = webidl.converters["DOMString"](title, { + prefix, + context: "Argument 1", + }); - return ops.op_wsi_window_set_theme(wid, theme); - } + return ops.op_wsi_window_set_title(wid, title); + } - getTitle() { - webidl.assertBranded(this, WSIWindowPrototype); - const prefix = "Failed to execute 'getTitle' on 'WSIWindow'"; - const wid = assertWindow(this, { prefix, context: "this" }); + setTransparent(transparent = true) { + webidl.assertBranded(this, WSIWindowPrototype); + const prefix = "Failed to execute 'setTransparent' on 'WSIWindow'"; + const wid = assertWindow(this, { prefix, context: "this" }); - return ops.op_wsi_window_get_title(wid); - } + transparent = webidl.converters["boolean"](transparent, { + prefix, + context: "Argument 1", + }); - setTitle(title) { - webidl.assertBranded(this, WSIWindowPrototype); - const prefix = "Failed to execute 'setTitle' on 'WSIWindow'"; - const wid = assertWindow(this, { prefix, context: "this" }); + return ops.op_wsi_window_set_transparent(wid, transparent); + } - webidl.requiredArguments(arguments.length, 1, { prefix }); - title = webidl.converters["DOMString"](title, { - prefix, - context: "Argument 1", - }); + isVisible() { + webidl.assertBranded(this, WSIWindowPrototype); + const prefix = "Failed to execute 'isVisible' on 'WSIWindow'"; + const wid = assertWindow(this, { prefix, context: "this" }); - return ops.op_wsi_window_set_title(wid, title); - } + return ops.op_wsi_window_is_visible(wid); + } - setTransparent(transparent = true) { - webidl.assertBranded(this, WSIWindowPrototype); - const prefix = "Failed to execute 'setTransparent' on 'WSIWindow'"; - const wid = assertWindow(this, { prefix, context: "this" }); + setVisible(visible = true) { + webidl.assertBranded(this, WSIWindowPrototype); + const prefix = "Failed to execute 'setVisible' on 'WSIWindow'"; + const wid = assertWindow(this, { prefix, context: "this" }); - transparent = webidl.converters["boolean"](transparent, { - prefix, - context: "Argument 1", - }); + visible = webidl.converters["boolean"](visible, { + prefix, + context: "Argument 1", + }); - return ops.op_wsi_window_set_transparent(wid, transparent); - } + return ops.op_wsi_window_set_visible(wid, visible); + } - isVisible() { - webidl.assertBranded(this, WSIWindowPrototype); - const prefix = "Failed to execute 'isVisible' on 'WSIWindow'"; - const wid = assertWindow(this, { prefix, context: "this" }); + beginDragMove() { + webidl.assertBranded(this, WSIWindowPrototype); + const prefix = "Failed to execute 'beginDragMove' on 'WSIWindow'"; + const wid = assertWindow(this, { prefix, context: "this" }); - return ops.op_wsi_window_is_visible(wid); - } + return ops.op_wsi_window_begin_drag_move(wid); + } - setVisible(visible = true) { - webidl.assertBranded(this, WSIWindowPrototype); - const prefix = "Failed to execute 'setVisible' on 'WSIWindow'"; - const wid = assertWindow(this, { prefix, context: "this" }); + beginDragResize(direction) { + webidl.assertBranded(this, WSIWindowPrototype); + const prefix = "Failed to execute 'beginDragResize' on 'WSIWindow'"; + const wid = assertWindow(this, { prefix, context: "this" }); - visible = webidl.converters["boolean"](visible, { - prefix, - context: "Argument 1", - }); + webidl.requiredArguments(arguments.length, 1, { prefix }); + direction = webidl.converters["WSIResizeDirection"](direction, { + prefix, + context: "Argument 1", + }); - return ops.op_wsi_window_set_visible(wid, visible); - } + return ops.op_wsi_window_begin_drag_resize(wid, direction); + } - beginDragMove() { - webidl.assertBranded(this, WSIWindowPrototype); - const prefix = "Failed to execute 'beginDragMove' on 'WSIWindow'"; - const wid = assertWindow(this, { prefix, context: "this" }); + requestRedraw() { + webidl.assertBranded(this, WSIWindowPrototype); + const prefix = "Failed to execute 'requestRedraw' on 'WSIWindow'"; + const wid = assertWindow(this, { prefix, context: "this" }); - return ops.op_wsi_window_begin_drag_move(wid); - } + return ops.op_wsi_window_request_redraw(wid); + } - beginDragResize(direction) { - webidl.assertBranded(this, WSIWindowPrototype); - const prefix = "Failed to execute 'beginDragResize' on 'WSIWindow'"; - const wid = assertWindow(this, { prefix, context: "this" }); + requestUserAttention(type) { + webidl.assertBranded(this, WSIWindowPrototype); + const prefix = "Failed to execute 'requestUserAttention' on 'WSIWindow'"; + const wid = assertWindow(this, { prefix, context: "this" }); - webidl.requiredArguments(arguments.length, 1, { prefix }); - direction = webidl.converters["WSIResizeDirection"](direction, { + webidl.requiredArguments(arguments.length, 1, { prefix }); + if (type !== null) { + type = webidl.converters["WSIUserAttentionType"](type, { prefix, context: "Argument 1", }); - - return ops.op_wsi_window_begin_drag_resize(wid, direction); } - requestRedraw() { - webidl.assertBranded(this, WSIWindowPrototype); - const prefix = "Failed to execute 'requestRedraw' on 'WSIWindow'"; - const wid = assertWindow(this, { prefix, context: "this" }); - - return ops.op_wsi_window_request_redraw(wid); - } + return ops.op_wsi_window_request_user_attention(wid, type); + } - requestUserAttention(type) { - webidl.assertBranded(this, WSIWindowPrototype); - const prefix = "Failed to execute 'requestUserAttention' on 'WSIWindow'"; - const wid = assertWindow(this, { prefix, context: "this" }); - - webidl.requiredArguments(arguments.length, 1, { prefix }); - if (type !== null) { - type = webidl.converters["WSIUserAttentionType"](type, { - prefix, - context: "Argument 1", - }); - } + destroy() { + webidl.assertBranded(this, WSIWindowPrototype); + const prefix = "Failed to execute 'destroy' on 'WSIWindow'"; + const wid = assertWindow(this, { prefix, context: "this" }); - return ops.op_wsi_window_request_user_attention(wid, type); + if (this[_gpuSurface] != null) { + webgpu.destroyGPUSurface(this[_gpuSurface]); + this[_gpuSurface] = undefined; } - destroy() { - webidl.assertBranded(this, WSIWindowPrototype); - const prefix = "Failed to execute 'destroy' on 'WSIWindow'"; - const wid = assertWindow(this, { prefix, context: "this" }); - - if (this[_gpuSurface] != null) { - webgpu.destroyGPUSurface(this[_gpuSurface]); - this[_gpuSurface] = undefined; - } - - ops.op_wsi_window_destroy(wid); - windows.delete(wid); - this[_wid] = undefined; - } + ops.op_wsi_window_destroy(wid); + windows.delete(wid); + this[_wid] = undefined; } - const WSIWindowPrototype = WSIWindow.prototype; +} +const WSIWindowPrototype = WSIWindow.prototype; - class WSIWindowButton { - constructor() { - webidl.illegalConstructor(); - } +class WSIWindowButton { + constructor() { + webidl.illegalConstructor(); + } - static get CLOSE() { - return 0b001; - } - static get MINIMIZE() { - return 0b010; - } - static get MAXIMIZE() { - return 0b100; - } + static get CLOSE() { + return 0b001; + } + static get MINIMIZE() { + return 0b010; + } + static get MAXIMIZE() { + return 0b100; } +} - globalThis.__bootstrap.wsi = { - wsi: webidl.createBranded(WSI), - WSI, - WSIModifierKey, - WSIWindow, - WSIWindowButton, - }; -})(this); +const wsi = webidl.createBranded(WSI); +export { WSI, wsi, WSIModifierKey, WSIWindow, WSIWindowButton }; diff --git a/ext/wsi/02_idl_types.js b/ext/wsi/02_idl_types.js index a92b924872620f..3f8a87fdfcd3ae 100644 --- a/ext/wsi/02_idl_types.js +++ b/ext/wsi/02_idl_types.js @@ -1,209 +1,205 @@ // Copyright 2023 Jo Bates. All rights reserved. MIT license. -"use strict"; +import * as webidl from "internal:deno_webidl/00_webidl.js"; -((globalThis) => { - const webidl = globalThis.__bootstrap.webidl; +// ENUM: WSICursorGrabMode +webidl.converters["WSICursorGrabMode"] = webidl.createEnumConverter( + "WSICursorGrabMode", + [ + "none", + "confined", + "locked", + ], +); - // ENUM: WSICursorGrabMode - webidl.converters["WSICursorGrabMode"] = webidl.createEnumConverter( - "WSICursorGrabMode", - [ - "none", - "confined", - "locked", - ], - ); +// ENUM: WSICursorIcon +webidl.converters["WSICursorIcon"] = webidl.createEnumConverter( + "WSICursorIcon", + [ + "default", + "crosshair", + "hand", + "arrow", + "move", + "text", + "wait", + "help", + "progress", + "not-allowed", + "context-menu", + "cell", + "vertical-text", + "alias", + "copy", + "no-drop", + "grab", + "grabbing", + "all-scroll", + "zoom-in", + "zoom-out", + "e-resize", + "n-resize", + "ne-resize", + "nw-resize", + "s-resize", + "se-resize", + "sw-resize", + "w-resize", + "ew-resize", + "ns-resize", + "nesw-resize", + "nwse-resize", + "col-resize", + "row-resize", + ], +); - // ENUM: WSICursorIcon - webidl.converters["WSICursorIcon"] = webidl.createEnumConverter( - "WSICursorIcon", - [ - "default", - "crosshair", - "hand", - "arrow", - "move", - "text", - "wait", - "help", - "progress", - "not-allowed", - "context-menu", - "cell", - "vertical-text", - "alias", - "copy", - "no-drop", - "grab", - "grabbing", - "all-scroll", - "zoom-in", - "zoom-out", - "e-resize", - "n-resize", - "ne-resize", - "nw-resize", - "s-resize", - "se-resize", - "sw-resize", - "w-resize", - "ew-resize", - "ns-resize", - "nesw-resize", - "nwse-resize", - "col-resize", - "row-resize", - ], - ); +// ENUM: WSIDeviceEventFilter +webidl.converters["WSIDeviceEventFilter"] = webidl.createEnumConverter( + "WSIDeviceEventFilter", + [ + "always", + "unfocused", + "never", + ], +); - // ENUM: WSIDeviceEventFilter - webidl.converters["WSIDeviceEventFilter"] = webidl.createEnumConverter( - "WSIDeviceEventFilter", - [ - "always", - "unfocused", - "never", - ], - ); +// ENUM: WSIIMEPurpose +webidl.converters["WSIIMEPurpose"] = webidl.createEnumConverter( + "WSIIMEPurpose", + [ + "normal", + "password", + "terminal", + ], +); - // ENUM: WSIIMEPurpose - webidl.converters["WSIIMEPurpose"] = webidl.createEnumConverter( - "WSIIMEPurpose", - [ - "normal", - "password", - "terminal", - ], - ); +// TYPEDEF: WSIPosition +webidl.converters["WSIPosition"] = webidl.createSequenceConverter( + webidl.converters["long"], +); - // TYPEDEF: WSIPosition - webidl.converters["WSIPosition"] = webidl.createSequenceConverter( - webidl.converters["long"], - ); +// ENUM: WSIResizeDirection +webidl.converters["WSIResizeDirection"] = webidl.createEnumConverter( + "WSIResizeDirection", + [ + "east", + "north", + "northeast", + "northwest", + "south", + "southeast", + "southwest", + "west", + ], +); - // ENUM: WSIResizeDirection - webidl.converters["WSIResizeDirection"] = webidl.createEnumConverter( - "WSIResizeDirection", - [ - "east", - "north", - "northeast", - "northwest", - "south", - "southeast", - "southwest", - "west", - ], - ); +// TYPEDEF: WSISize +webidl.converters["WSISize"] = webidl.createSequenceConverter( + webidl.converters["unsigned long"], +); - // TYPEDEF: WSISize - webidl.converters["WSISize"] = webidl.createSequenceConverter( - webidl.converters["unsigned long"], - ); +// ENUM: WSIUserAttentionType +webidl.converters["WSIUserAttentionType"] = webidl.createEnumConverter( + "WSIUserAttentionType", + [ + "critical", + "informational", + ], +); - // ENUM: WSIUserAttentionType - webidl.converters["WSIUserAttentionType"] = webidl.createEnumConverter( - "WSIUserAttentionType", - [ - "critical", - "informational", - ], - ); +// ENUM: WSIWindowLevel +webidl.converters["WSIWindowLevel"] = webidl.createEnumConverter( + "WSIWindowLevel", + [ + "always-on-bottom", + "normal", + "always-on-top", + ], +); - // ENUM: WSIWindowLevel - webidl.converters["WSIWindowLevel"] = webidl.createEnumConverter( - "WSIWindowLevel", - [ - "always-on-bottom", - "normal", - "always-on-top", - ], - ); +// ENUM: WSIWindowTheme +webidl.converters["WSIWindowTheme"] = webidl.createEnumConverter( + "WSIWindowTheme", + [ + "light", + "dark", + ], +); - // ENUM: WSIWindowTheme - webidl.converters["WSIWindowTheme"] = webidl.createEnumConverter( - "WSIWindowTheme", - [ - "light", - "dark", - ], +// DICTIONARY: WSICreateWindowOptions +const dictMembersWSICreateWindowOptions = [ + { + key: "active", + converter: webidl.converters["boolean"], + }, + { + key: "contentProtected", + converter: webidl.converters["boolean"], + }, + { + key: "decorated", + converter: webidl.converters["boolean"], + }, + { + key: "enabledButtons", + converter: webidl.converters["unsigned long"], + }, + { + key: "fullscreen", + converter: webidl.converters["boolean"], + }, + { + key: "position", + converter: webidl.converters["WSIPosition"], + }, + { + key: "innerSize", + converter: webidl.converters["WSISize"], + }, + { + key: "minInnerSize", + converter: webidl.converters["WSISize"], + }, + { + key: "maxInnerSize", + converter: webidl.converters["WSISize"], + }, + { + key: "level", + converter: webidl.converters["WSIWindowLevel"], + }, + { + key: "maximized", + converter: webidl.converters["boolean"], + }, + { + key: "resizable", + converter: webidl.converters["boolean"], + }, + { + key: "resizeIncrements", + converter: webidl.converters["WSISize"], + }, + { + key: "theme", + converter: webidl.converters["WSIWindowTheme"], + }, + { + key: "title", + converter: webidl.converters["DOMString"], + }, + { + key: "transparent", + converter: webidl.converters["boolean"], + }, + { + key: "visible", + converter: webidl.converters["boolean"], + }, +]; +webidl.converters["WSICreateWindowOptions"] = webidl + .createDictionaryConverter( + "WSICreateWindowOptions", + dictMembersWSICreateWindowOptions, ); - - // DICTIONARY: WSICreateWindowOptions - const dictMembersWSICreateWindowOptions = [ - { - key: "active", - converter: webidl.converters["boolean"], - }, - { - key: "contentProtected", - converter: webidl.converters["boolean"], - }, - { - key: "decorated", - converter: webidl.converters["boolean"], - }, - { - key: "enabledButtons", - converter: webidl.converters["unsigned long"], - }, - { - key: "fullscreen", - converter: webidl.converters["boolean"], - }, - { - key: "position", - converter: webidl.converters["WSIPosition"], - }, - { - key: "innerSize", - converter: webidl.converters["WSISize"], - }, - { - key: "minInnerSize", - converter: webidl.converters["WSISize"], - }, - { - key: "maxInnerSize", - converter: webidl.converters["WSISize"], - }, - { - key: "level", - converter: webidl.converters["WSIWindowLevel"], - }, - { - key: "maximized", - converter: webidl.converters["boolean"], - }, - { - key: "resizable", - converter: webidl.converters["boolean"], - }, - { - key: "resizeIncrements", - converter: webidl.converters["WSISize"], - }, - { - key: "theme", - converter: webidl.converters["WSIWindowTheme"], - }, - { - key: "title", - converter: webidl.converters["DOMString"], - }, - { - key: "transparent", - converter: webidl.converters["boolean"], - }, - { - key: "visible", - converter: webidl.converters["boolean"], - }, - ]; - webidl.converters["WSICreateWindowOptions"] = webidl - .createDictionaryConverter( - "WSICreateWindowOptions", - dictMembersWSICreateWindowOptions, - ); -})(this); diff --git a/ext/wsi/Cargo.toml b/ext/wsi/Cargo.toml index ba408ca8b4a3e5..7c6f103c2dc846 100644 --- a/ext/wsi/Cargo.toml +++ b/ext/wsi/Cargo.toml @@ -2,7 +2,7 @@ [package] name = "denog_wsi" -version = "0.6.0" +version = "0.7.0" authors = ["Jo Bates"] edition.workspace = true license.workspace = true diff --git a/ext/wsi/lib.rs b/ext/wsi/lib.rs index cac72e0d5825f1..ede965d4f72ad5 100644 --- a/ext/wsi/lib.rs +++ b/ext/wsi/lib.rs @@ -30,11 +30,7 @@ use winit::{ pub fn init(event_loop_proxy: Option>) -> Extension { Extension::builder("deno_wsi") .dependencies(vec!["deno_webgpu", "deno_webidl"]) - .js(include_js_files!( - prefix "internal:ext/wsi", - "01_wsi.js", - "02_idl_types.js", - )) + .esm(include_js_files!("01_wsi.js", "02_idl_types.js",)) .ops(vec![ op_wsi_next_event::decl(), op_wsi_set_device_event_filter::decl(), diff --git a/lockfile/Cargo.toml b/lockfile/Cargo.toml index 775a755647247d..a73cea20be4bf3 100644 --- a/lockfile/Cargo.toml +++ b/lockfile/Cargo.toml @@ -2,7 +2,7 @@ [package] name = "deno_lockfile" -version = "0.5.0" +version = "0.7.0" edition = "2021" license = "MIT" description = "An implementation of a lockfile used in Deno" diff --git a/ops/Cargo.toml b/ops/Cargo.toml index ca037e2a3f0711..baeb8c220e0d9f 100644 --- a/ops/Cargo.toml +++ b/ops/Cargo.toml @@ -2,7 +2,7 @@ [package] name = "deno_ops" -version = "0.49.0" +version = "0.51.0" authors.workspace = true edition.workspace = true license.workspace = true diff --git a/ops/fast_call.rs b/ops/fast_call.rs index 9093190b287ec8..a7ca51d4fd14e0 100644 --- a/ops/fast_call.rs +++ b/ops/fast_call.rs @@ -418,15 +418,18 @@ pub(crate) fn generate( fn q_fast_ty(v: &FastValue) -> Quote { match v { FastValue::Void => q!({ () }), + FastValue::Bool => q!({ bool }), FastValue::U32 => q!({ u32 }), FastValue::I32 => q!({ i32 }), FastValue::U64 => q!({ u64 }), FastValue::I64 => q!({ i64 }), FastValue::F32 => q!({ f32 }), FastValue::F64 => q!({ f64 }), - FastValue::Bool => q!({ bool }), + FastValue::Pointer => q!({ *mut ::std::ffi::c_void }), FastValue::V8Value => q!({ v8::Local }), - FastValue::Uint8Array | FastValue::Uint32Array => unreachable!(), + FastValue::Uint8Array + | FastValue::Uint32Array + | FastValue::Float64Array => unreachable!(), } } @@ -434,16 +437,18 @@ fn q_fast_ty(v: &FastValue) -> Quote { fn q_fast_ty_variant(v: &FastValue) -> Quote { match v { FastValue::Void => q!({ Void }), + FastValue::Bool => q!({ Bool }), FastValue::U32 => q!({ Uint32 }), FastValue::I32 => q!({ Int32 }), FastValue::U64 => q!({ Uint64 }), FastValue::I64 => q!({ Int64 }), FastValue::F32 => q!({ Float32 }), FastValue::F64 => q!({ Float64 }), - FastValue::Bool => q!({ Bool }), + FastValue::Pointer => q!({ Pointer }), FastValue::V8Value => q!({ V8Value }), FastValue::Uint8Array => q!({ TypedArray(CType::Uint8) }), FastValue::Uint32Array => q!({ TypedArray(CType::Uint32) }), + FastValue::Float64Array => q!({ TypedArray(CType::Float64) }), } } diff --git a/ops/lib.rs b/ops/lib.rs index 35796d41a5d520..28cb3c320a8914 100644 --- a/ops/lib.rs +++ b/ops/lib.rs @@ -452,6 +452,13 @@ fn codegen_arg( let #ident = #blck; }; } + Some(SliceType::F64Mut) => { + assert!(!asyncness, "Memory slices are not allowed in async ops"); + let blck = codegen_f64_mut_slice(core, idx); + return quote! { + let #ident = #blck; + }; + } Some(_) => { assert!(!asyncness, "Memory slices are not allowed in async ops"); let blck = codegen_u8_slice(core, idx); @@ -467,6 +474,13 @@ fn codegen_arg( let #ident = #blk; }; } + // Fast path for `*const c_void` and `*mut c_void` + if is_ptr_cvoid(&**ty) { + let blk = codegen_cvoid_ptr(core, idx); + return quote! { + let #ident = #blk; + }; + } // Otherwise deserialize it via serde_v8 quote! { let #ident = args.get(#idx as i32); @@ -553,6 +567,19 @@ fn codegen_u8_ptr(core: &TokenStream2, idx: usize) -> TokenStream2 { }} } +fn codegen_cvoid_ptr(core: &TokenStream2, idx: usize) -> TokenStream2 { + quote! {{ + let value = args.get(#idx as i32); + if value.is_null() { + std::ptr::null_mut() + } else if let Ok(b) = #core::v8::Local::<#core::v8::External>::try_from(value) { + b.value() + } else { + return #core::_ops::throw_type_error(scope, format!("Expected External at position {}", #idx)); + } + }} +} + fn codegen_u32_mut_slice(core: &TokenStream2, idx: usize) -> TokenStream2 { quote! { if let Ok(view) = #core::v8::Local::<#core::v8::Uint32Array>::try_from(args.get(#idx as i32)) { @@ -576,6 +603,28 @@ fn codegen_u32_mut_slice(core: &TokenStream2, idx: usize) -> TokenStream2 { } } +fn codegen_f64_mut_slice(core: &TokenStream2, idx: usize) -> TokenStream2 { + quote! { + if let Ok(view) = #core::v8::Local::<#core::v8::Float64Array>::try_from(args.get(#idx as i32)) { + let (offset, len) = (view.byte_offset(), view.byte_length()); + let buffer = match view.buffer(scope) { + Some(v) => v, + None => { + return #core::_ops::throw_type_error(scope, format!("Expected Float64Array at position {}", #idx)); + } + }; + if let Some(data) = buffer.data() { + let store = data.cast::().as_ptr(); + unsafe { ::std::slice::from_raw_parts_mut(store.add(offset) as *mut f64, len / 8) } + } else { + &mut [] + } + } else { + return #core::_ops::throw_type_error(scope, format!("Expected Float64Array at position {}", #idx)); + } + } +} + fn codegen_sync_ret( core: &TokenStream2, output: &syn::ReturnType, @@ -597,6 +646,15 @@ fn codegen_sync_ret( quote! { rv.set_uint32(result as u32); } + } else if is_ptr_cvoid(output) || is_ptr_cvoid_rv(output) { + quote! { + if result.is_null() { + // External canot contain a null pointer, null pointers are instead represented as null. + rv.set_null(); + } else { + rv.set(v8::External::new(scope, result as *mut ::std::ffi::c_void).into()); + } + } } else { quote! { match #core::serde_v8::to_v8(scope, result) { @@ -655,6 +713,7 @@ enum SliceType { U8, U8Mut, U32Mut, + F64Mut, } fn is_ref_slice(ty: impl ToTokens) -> Option { @@ -667,6 +726,9 @@ fn is_ref_slice(ty: impl ToTokens) -> Option { if is_u32_slice_mut(&ty) { return Some(SliceType::U32Mut); } + if is_f64_slice_mut(&ty) { + return Some(SliceType::F64Mut); + } None } @@ -682,10 +744,23 @@ fn is_u32_slice_mut(ty: impl ToTokens) -> bool { tokens(ty) == "& mut [u32]" } +fn is_f64_slice_mut(ty: impl ToTokens) -> bool { + tokens(ty) == "& mut [f64]" +} + fn is_ptr_u8(ty: impl ToTokens) -> bool { tokens(ty) == "* const u8" } +fn is_ptr_cvoid(ty: impl ToTokens) -> bool { + tokens(&ty) == "* const c_void" || tokens(&ty) == "* mut c_void" +} + +fn is_ptr_cvoid_rv(ty: impl ToTokens) -> bool { + tokens(&ty).contains("Result < * const c_void") + || tokens(&ty).contains("Result < * mut c_void") +} + fn is_optional_fast_callback_option(ty: impl ToTokens) -> bool { tokens(&ty).contains("Option < & mut FastApiCallbackOptions") } diff --git a/ops/optimizer.rs b/ops/optimizer.rs index 7c19a7cfda4301..a3ccd51b3c73b8 100644 --- a/ops/optimizer.rs +++ b/ops/optimizer.rs @@ -43,7 +43,9 @@ enum TransformKind { V8Value, SliceU32(bool), SliceU8(bool), + SliceF64(bool), PtrU8, + PtrVoid, WasmMemory, } @@ -69,6 +71,13 @@ impl Transform { } } + fn slice_f64(index: usize, is_mut: bool) -> Self { + Transform { + kind: TransformKind::SliceF64(is_mut), + index, + } + } + fn wasm_memory(index: usize) -> Self { Transform { kind: TransformKind::WasmMemory, @@ -82,6 +91,13 @@ impl Transform { index, } } + + fn void_ptr(index: usize) -> Self { + Transform { + kind: TransformKind::PtrVoid, + index, + } + } } #[derive(Debug, PartialEq)] @@ -150,6 +166,20 @@ impl Transform { unsafe { (&*var).get_storage_if_aligned().unwrap_unchecked() }; }) } + TransformKind::SliceF64(_) => { + *ty = + parse_quote! { *const #core::v8::fast_api::FastApiTypedArray }; + + q!(Vars { var: &ident }, { + let var = match unsafe { &*var }.get_storage_if_aligned() { + Some(v) => v, + None => { + unsafe { &mut *fast_api_callback_options }.fallback = true; + return Default::default(); + } + }; + }) + } TransformKind::WasmMemory => { // Note: `ty` is correctly set to __opts by the fast call tier. // U8 slice is always byte-aligned. @@ -173,19 +203,25 @@ impl Transform { .as_ptr(); }) } + TransformKind::PtrVoid => { + *ty = parse_quote! { *mut ::std::ffi::c_void }; + + q!(Vars {}, {}) + } } } } fn get_fast_scalar(s: &str) -> Option { match s { + "bool" => Some(FastValue::Bool), "u32" => Some(FastValue::U32), "i32" => Some(FastValue::I32), "u64" => Some(FastValue::U64), "i64" => Some(FastValue::I64), "f32" => Some(FastValue::F32), "f64" => Some(FastValue::F64), - "bool" => Some(FastValue::Bool), + "* const c_void" | "* mut c_void" => Some(FastValue::Pointer), "ResourceId" => Some(FastValue::U32), _ => None, } @@ -204,16 +240,18 @@ fn can_return_fast(v: &FastValue) -> bool { #[derive(Debug, PartialEq, Clone)] pub(crate) enum FastValue { Void, + Bool, U32, I32, U64, I64, F32, F64, - Bool, + Pointer, V8Value, Uint8Array, Uint32Array, + Float64Array, } impl Default for FastValue { @@ -391,6 +429,31 @@ impl Optimizer { { self.fast_result = Some(FastValue::Void); } + Some(GenericArgument::Type(Type::Ptr(TypePtr { + mutability: Some(_), + elem, + .. + }))) => { + match &**elem { + Type::Path(TypePath { + path: Path { segments, .. }, + .. + }) => { + // Is `T` a c_void? + let segment = single_segment(segments)?; + match segment { + PathSegment { ident, .. } if ident == "c_void" => { + self.fast_result = Some(FastValue::Pointer); + return Ok(()); + } + _ => { + return Err(BailoutReason::FastUnsupportedParamType) + } + } + } + _ => return Err(BailoutReason::FastUnsupportedParamType), + } + } _ => return Err(BailoutReason::FastUnsupportedParamType), } } @@ -407,6 +470,29 @@ impl Optimizer { } }; } + Type::Ptr(TypePtr { + mutability: Some(_), + elem, + .. + }) => { + match &**elem { + Type::Path(TypePath { + path: Path { segments, .. }, + .. + }) => { + // Is `T` a c_void? + let segment = single_segment(segments)?; + match segment { + PathSegment { ident, .. } if ident == "c_void" => { + self.fast_result = Some(FastValue::Pointer); + return Ok(()); + } + _ => return Err(BailoutReason::FastUnsupportedParamType), + } + } + _ => return Err(BailoutReason::FastUnsupportedParamType), + } + } _ => return Err(BailoutReason::FastUnsupportedParamType), }; @@ -620,6 +706,15 @@ impl Optimizer { .insert(index, Transform::slice_u32(index, is_mut_ref)) .is_none()); } + // Is `T` a f64? + PathSegment { ident, .. } if ident == "f64" => { + self.needs_fast_callback_option = true; + self.fast_parameters.push(FastValue::Float64Array); + assert!(self + .transforms + .insert(index, Transform::slice_f64(index, is_mut_ref)) + .is_none()); + } _ => return Err(BailoutReason::FastUnsupportedParamType), } } @@ -652,6 +747,31 @@ impl Optimizer { } _ => return Err(BailoutReason::FastUnsupportedParamType), }, + // *const T + Type::Ptr(TypePtr { + elem, + mutability: Some(_), + .. + }) => match &**elem { + Type::Path(TypePath { + path: Path { segments, .. }, + .. + }) => { + let segment = single_segment(segments)?; + match segment { + // Is `T` a c_void? + PathSegment { ident, .. } if ident == "c_void" => { + self.fast_parameters.push(FastValue::Pointer); + assert!(self + .transforms + .insert(index, Transform::void_ptr(index)) + .is_none()); + } + _ => return Err(BailoutReason::FastUnsupportedParamType), + } + } + _ => return Err(BailoutReason::FastUnsupportedParamType), + }, _ => return Err(BailoutReason::FastUnsupportedParamType), }, _ => return Err(BailoutReason::FastUnsupportedParamType), diff --git a/ops/optimizer_tests/f64_slice.expected b/ops/optimizer_tests/f64_slice.expected new file mode 100644 index 00000000000000..32182b004a50b4 --- /dev/null +++ b/ops/optimizer_tests/f64_slice.expected @@ -0,0 +1,11 @@ +=== Optimizer Dump === +returns_result: false +has_ref_opstate: false +has_rc_opstate: false +has_fast_callback_option: false +needs_fast_callback_option: true +fast_result: Some(Void) +fast_parameters: [V8Value, Float64Array] +transforms: {0: Transform { kind: SliceF64(true), index: 0 }} +is_async: false +fast_compatible: true diff --git a/ops/optimizer_tests/f64_slice.out b/ops/optimizer_tests/f64_slice.out new file mode 100644 index 00000000000000..88ccd232ad8933 --- /dev/null +++ b/ops/optimizer_tests/f64_slice.out @@ -0,0 +1,112 @@ +#[allow(non_camel_case_types)] +///Auto-generated by `deno_ops`, i.e: `#[op]` +/// +///Use `op_f64_buf::decl()` to get an op-declaration +///you can include in a `deno_core::Extension`. +pub struct op_f64_buf; +#[doc(hidden)] +impl op_f64_buf { + pub fn name() -> &'static str { + stringify!(op_f64_buf) + } + pub fn v8_fn_ptr<'scope>() -> deno_core::v8::FunctionCallback { + use deno_core::v8::MapFnTo; + Self::v8_func.map_fn_to() + } + pub fn decl<'scope>() -> deno_core::OpDecl { + deno_core::OpDecl { + name: Self::name(), + v8_fn_ptr: Self::v8_fn_ptr(), + enabled: true, + fast_fn: Some( + Box::new(op_f64_buf_fast { + _phantom: ::std::marker::PhantomData, + }), + ), + is_async: false, + is_unstable: false, + is_v8: false, + argc: 1usize, + } + } + #[inline] + #[allow(clippy::too_many_arguments)] + fn call(buffer: &mut [f64]) {} + pub fn v8_func<'scope>( + scope: &mut deno_core::v8::HandleScope<'scope>, + args: deno_core::v8::FunctionCallbackArguments, + mut rv: deno_core::v8::ReturnValue, + ) { + let ctx = unsafe { + &*(deno_core::v8::Local::::cast(args.data()).value() + as *const deno_core::_ops::OpCtx) + }; + let arg_0 = if let Ok(view) + = deno_core::v8::Local::< + deno_core::v8::Float64Array, + >::try_from(args.get(0usize as i32)) { + let (offset, len) = (view.byte_offset(), view.byte_length()); + let buffer = match view.buffer(scope) { + Some(v) => v, + None => { + return deno_core::_ops::throw_type_error( + scope, + format!("Expected Float64Array at position {}", 0usize), + ); + } + }; + if let Some(data) = buffer.data() { + let store = data.cast::().as_ptr(); + unsafe { + ::std::slice::from_raw_parts_mut( + store.add(offset) as *mut f64, + len / 8, + ) + } + } else { + &mut [] + } + } else { + return deno_core::_ops::throw_type_error( + scope, + format!("Expected Float64Array at position {}", 0usize), + ); + }; + let result = Self::call(arg_0); + let op_state = ::std::cell::RefCell::borrow(&*ctx.state); + op_state.tracker.track_sync(ctx.id); + } +} +struct op_f64_buf_fast { + _phantom: ::std::marker::PhantomData<()>, +} +impl<'scope> deno_core::v8::fast_api::FastFunction for op_f64_buf_fast { + fn function(&self) -> *const ::std::ffi::c_void { + op_f64_buf_fast_fn as *const ::std::ffi::c_void + } + fn args(&self) -> &'static [deno_core::v8::fast_api::Type] { + use deno_core::v8::fast_api::Type::*; + use deno_core::v8::fast_api::CType; + &[V8Value, TypedArray(CType::Float64), CallbackOptions] + } + fn return_type(&self) -> deno_core::v8::fast_api::CType { + deno_core::v8::fast_api::CType::Void + } +} +fn op_f64_buf_fast_fn<'scope>( + _: deno_core::v8::Local, + buffer: *const deno_core::v8::fast_api::FastApiTypedArray, + fast_api_callback_options: *mut deno_core::v8::fast_api::FastApiCallbackOptions, +) -> () { + use deno_core::v8; + use deno_core::_ops; + let buffer = match unsafe { &*buffer }.get_storage_if_aligned() { + Some(v) => v, + None => { + unsafe { &mut *fast_api_callback_options }.fallback = true; + return Default::default(); + } + }; + let result = op_f64_buf::call(buffer); + result +} diff --git a/ops/optimizer_tests/f64_slice.rs b/ops/optimizer_tests/f64_slice.rs new file mode 100644 index 00000000000000..fa2778531e26d9 --- /dev/null +++ b/ops/optimizer_tests/f64_slice.rs @@ -0,0 +1,3 @@ +fn op_f64_buf(buffer: &mut [f64]) { + // @test-attr:fast +} diff --git a/ops/optimizer_tests/op_ffi_ptr_value.expected b/ops/optimizer_tests/op_ffi_ptr_value.expected new file mode 100644 index 00000000000000..00a28591c0b46f --- /dev/null +++ b/ops/optimizer_tests/op_ffi_ptr_value.expected @@ -0,0 +1,11 @@ +=== Optimizer Dump === +returns_result: false +has_ref_opstate: false +has_rc_opstate: false +has_fast_callback_option: false +needs_fast_callback_option: true +fast_result: Some(Void) +fast_parameters: [V8Value, Pointer, Uint32Array] +transforms: {0: Transform { kind: PtrVoid, index: 0 }, 1: Transform { kind: SliceU32(true), index: 1 }} +is_async: false +fast_compatible: true diff --git a/ops/optimizer_tests/op_ffi_ptr_value.out b/ops/optimizer_tests/op_ffi_ptr_value.out new file mode 100644 index 00000000000000..bfbbd5ae756c2a --- /dev/null +++ b/ops/optimizer_tests/op_ffi_ptr_value.out @@ -0,0 +1,127 @@ +#[allow(non_camel_case_types)] +///Auto-generated by `deno_ops`, i.e: `#[op]` +/// +///Use `op_ffi_ptr_value::decl()` to get an op-declaration +///you can include in a `deno_core::Extension`. +pub struct op_ffi_ptr_value; +#[doc(hidden)] +impl op_ffi_ptr_value { + pub fn name() -> &'static str { + stringify!(op_ffi_ptr_value) + } + pub fn v8_fn_ptr<'scope>() -> deno_core::v8::FunctionCallback { + use deno_core::v8::MapFnTo; + Self::v8_func.map_fn_to() + } + pub fn decl<'scope>() -> deno_core::OpDecl { + deno_core::OpDecl { + name: Self::name(), + v8_fn_ptr: Self::v8_fn_ptr(), + enabled: true, + fast_fn: Some( + Box::new(op_ffi_ptr_value_fast { + _phantom: ::std::marker::PhantomData, + }), + ), + is_async: false, + is_unstable: false, + is_v8: false, + argc: 2usize, + } + } + #[inline] + #[allow(clippy::too_many_arguments)] + pub fn call(ptr: *mut c_void, out: &mut [u32]) {} + pub fn v8_func<'scope>( + scope: &mut deno_core::v8::HandleScope<'scope>, + args: deno_core::v8::FunctionCallbackArguments, + mut rv: deno_core::v8::ReturnValue, + ) { + let ctx = unsafe { + &*(deno_core::v8::Local::::cast(args.data()).value() + as *const deno_core::_ops::OpCtx) + }; + let arg_0 = { + let value = args.get(0usize as i32); + if value.is_null() { + std::ptr::null_mut() + } else if let Ok(b) + = deno_core::v8::Local::::try_from(value) { + b.value() + } else { + return deno_core::_ops::throw_type_error( + scope, + format!("Expected External at position {}", 0usize), + ); + } + }; + let arg_1 = if let Ok(view) + = deno_core::v8::Local::< + deno_core::v8::Uint32Array, + >::try_from(args.get(1usize as i32)) { + let (offset, len) = (view.byte_offset(), view.byte_length()); + let buffer = match view.buffer(scope) { + Some(v) => v, + None => { + return deno_core::_ops::throw_type_error( + scope, + format!("Expected Uint32Array at position {}", 1usize), + ); + } + }; + if let Some(data) = buffer.data() { + let store = data.cast::().as_ptr(); + unsafe { + ::std::slice::from_raw_parts_mut( + store.add(offset) as *mut u32, + len / 4, + ) + } + } else { + &mut [] + } + } else { + return deno_core::_ops::throw_type_error( + scope, + format!("Expected Uint32Array at position {}", 1usize), + ); + }; + let result = Self::call(arg_0, arg_1); + let op_state = ::std::cell::RefCell::borrow(&*ctx.state); + op_state.tracker.track_sync(ctx.id); + } +} +struct op_ffi_ptr_value_fast { + _phantom: ::std::marker::PhantomData<()>, +} +impl<'scope> deno_core::v8::fast_api::FastFunction for op_ffi_ptr_value_fast { + fn function(&self) -> *const ::std::ffi::c_void { + op_ffi_ptr_value_fast_fn as *const ::std::ffi::c_void + } + fn args(&self) -> &'static [deno_core::v8::fast_api::Type] { + use deno_core::v8::fast_api::Type::*; + use deno_core::v8::fast_api::CType; + &[V8Value, Pointer, TypedArray(CType::Uint32), CallbackOptions] + } + fn return_type(&self) -> deno_core::v8::fast_api::CType { + deno_core::v8::fast_api::CType::Void + } +} +fn op_ffi_ptr_value_fast_fn<'scope>( + _: deno_core::v8::Local, + ptr: *mut ::std::ffi::c_void, + out: *const deno_core::v8::fast_api::FastApiTypedArray, + fast_api_callback_options: *mut deno_core::v8::fast_api::FastApiCallbackOptions, +) -> () { + use deno_core::v8; + use deno_core::_ops; + let out = match unsafe { &*out }.get_storage_if_aligned() { + Some(v) => v, + None => { + unsafe { &mut *fast_api_callback_options }.fallback = true; + return Default::default(); + } + }; + let result = op_ffi_ptr_value::call(ptr, out); + result +} diff --git a/ops/optimizer_tests/op_ffi_ptr_value.rs b/ops/optimizer_tests/op_ffi_ptr_value.rs new file mode 100644 index 00000000000000..4c3364507b5bef --- /dev/null +++ b/ops/optimizer_tests/op_ffi_ptr_value.rs @@ -0,0 +1,3 @@ +pub fn op_ffi_ptr_value(ptr: *mut c_void, out: &mut [u32]) { + // ... +} diff --git a/runtime/Cargo.toml b/runtime/Cargo.toml index 8c3a374e58d98e..879b6bf72a0844 100644 --- a/runtime/Cargo.toml +++ b/runtime/Cargo.toml @@ -3,7 +3,7 @@ [package] name = "denog_runtime" -version = "0.6.0" +version = "0.7.0" authors.workspace = true edition.workspace = true license.workspace = true @@ -13,8 +13,17 @@ description = "Provides the denog runtime library" [features] # "fake" feature that allows to generate docs on docs.rs docsrs = [] -# feature that modifies the snapshot to allow extending it +# A feature that disables creation of startup snapshot during in the build script. +dont_create_runtime_snapshot = [] +# A feature that changes how startup snapshot is generated, that allows +# extending it in embedder crates. snapshot_from_snapshot = [] +# A feature that disables embedding of the JavaScript source files in the binary. +# With this feature enabled, the sources must be consumed during build time, +# by creating a startup snapshot. +include_js_files_for_snapshotting = [ + "deno_core/include_js_files_for_snapshotting", +] [lib] name = "denog_runtime" @@ -25,6 +34,7 @@ name = "hello_runtime" path = "examples/hello_runtime.rs" [build-dependencies] +deno_ast.workspace = true deno_broadcast_channel.workspace = true deno_cache.workspace = true deno_console.workspace = true diff --git a/runtime/build.rs b/runtime/build.rs index ffdf8d795197bb..37a48f45a43e08 100644 --- a/runtime/build.rs +++ b/runtime/build.rs @@ -2,18 +2,62 @@ // Copyright 2023 Jo Bates. All rights reserved. MIT license. use std::env; - use std::path::PathBuf; -// This is a shim that allows to generate documentation on docs.rs -#[cfg(not(feature = "docsrs"))] -mod not_docs { +#[cfg(all( + not(feature = "docsrs"), + not(feature = "dont_create_runtime_snapshot") +))] +mod startup_snapshot { use std::path::Path; use super::*; + use deno_ast::MediaType; + use deno_ast::ParseParams; + use deno_ast::SourceTextInfo; use deno_cache::SqliteBackedCache; + use deno_core::error::AnyError; + use deno_core::include_js_files; use deno_core::snapshot_util::*; use deno_core::Extension; + use deno_core::ExtensionFileSource; + + fn transpile_ts_for_snapshotting( + file_source: &ExtensionFileSource, + ) -> Result { + let media_type = MediaType::from(Path::new(&file_source.specifier)); + + let should_transpile = match media_type { + MediaType::JavaScript => false, + MediaType::Mjs => false, + MediaType::TypeScript => true, + _ => panic!( + "Unsupported media type for snapshotting {media_type:?} for file {}", + file_source.specifier + ), + }; + let code = file_source.code.load()?; + + if !should_transpile { + return Ok(code); + } + + let parsed = deno_ast::parse_module(ParseParams { + specifier: file_source.specifier.to_string(), + text_info: SourceTextInfo::from_string(code), + media_type, + capture_tokens: false, + scope_analysis: false, + maybe_syntax: None, + })?; + let transpiled_source = parsed.transpile(&deno_ast::EmitOptions { + imports_not_used_as_values: deno_ast::ImportsNotUsedAsValues::Remove, + inline_source_map: false, + ..Default::default() + })?; + + Ok(transpiled_source.text) + } struct Permissions; @@ -122,8 +166,62 @@ mod not_docs { } } - fn create_runtime_snapshot(snapshot_path: PathBuf, files: Vec) { - let extensions_with_js: Vec = vec![ + fn create_runtime_snapshot( + snapshot_path: PathBuf, + maybe_additional_extension: Option, + ) { + let runtime_extension = Extension::builder("runtime") + .dependencies(vec![ + "deno_webidl", + "deno_console", + "deno_url", + "deno_tls", + "deno_web", + "deno_fetch", + "deno_cache", + "deno_websocket", + "deno_webstorage", + "deno_crypto", + "deno_webgpu", + "deno_broadcast_channel", + // FIXME(bartlomieju): this should be reenabled + // "deno_node", + "deno_ffi", + "deno_net", + "deno_napi", + "deno_http", + "deno_flash", + "deno_wsi", + ]) + .esm(include_js_files!( + dir "js", + "01_build.js", + "01_errors.js", + "01_version.ts", + "06_util.js", + "10_permissions.js", + "11_workers.js", + "12_io.js", + "13_buffer.js", + "30_fs.js", + "30_os.js", + "40_diagnostics.js", + "40_files.js", + "40_fs_events.js", + "40_http.js", + "40_process.js", + "40_read_file.js", + "40_signals.js", + "40_spawn.js", + "40_tty.js", + "40_write_file.js", + "41_prompt.js", + "90_deno_ns.js", + "98_global_scope.js", + )) + .build(); + + let mut extensions_with_js: Vec = vec![ deno_webidl::init(), deno_console::init(), deno_url::init(), @@ -142,25 +240,32 @@ mod not_docs { deno_broadcast_channel::InMemoryBroadcastChannel::default(), false, // No --unstable. ), - deno_node::init::(None), deno_ffi::init::(false), deno_net::init::( None, false, // No --unstable. None, ), - deno_napi::init::(false), + deno_napi::init::(), deno_http::init(), deno_flash::init::(false), // No --unstable deno_wsi::init(None), + runtime_extension, + // FIXME(bartlomieju): these extensions are specified last, because they + // depend on `runtime`, even though it should be other way around + deno_node::init::(None), + deno_node::init_polyfill(), ]; + if let Some(additional_extension) = maybe_additional_extension { + extensions_with_js.push(additional_extension); + } + create_snapshot(CreateSnapshotOptions { cargo_manifest_dir: env!("CARGO_MANIFEST_DIR"), snapshot_path, startup_snapshot: None, extensions: vec![], extensions_with_js, - additional_files: files, compression_cb: Some(Box::new(|vec, snapshot_slice| { lzzzz::lz4_hc::compress_to_vec( snapshot_slice, @@ -169,19 +274,31 @@ mod not_docs { ) .expect("snapshot compression failed"); })), + snapshot_module_load_cb: Some(Box::new(transpile_ts_for_snapshotting)), }); } pub fn build_snapshot(runtime_snapshot_path: PathBuf) { - #[allow(unused_mut)] - let mut js_files = get_js_files(env!("CARGO_MANIFEST_DIR"), "js"); + #[allow(unused_mut, unused_assignments)] + let mut maybe_additional_extension = None; + #[cfg(not(feature = "snapshot_from_snapshot"))] { - let manifest = env!("CARGO_MANIFEST_DIR"); - let path = PathBuf::from(manifest); - js_files.push(path.join("js").join("99_main.js")); + use deno_core::ExtensionFileSourceCode; + maybe_additional_extension = Some( + Extension::builder("runtime_main") + .dependencies(vec!["runtime"]) + .esm(vec![ExtensionFileSource { + specifier: "js/99_main.js".to_string(), + code: ExtensionFileSourceCode::IncludedInBinary(include_str!( + "js/99_main.js" + )), + }]) + .build(), + ); } - create_runtime_snapshot(runtime_snapshot_path, js_files); + + create_runtime_snapshot(runtime_snapshot_path, maybe_additional_extension); } } @@ -201,9 +318,13 @@ fn main() { // doesn't actually compile on docs.rs if env::var_os("DOCS_RS").is_some() { let snapshot_slice = &[]; + #[allow(clippy::needless_borrow)] std::fs::write(&runtime_snapshot_path, snapshot_slice).unwrap(); } - #[cfg(not(feature = "docsrs"))] - not_docs::build_snapshot(runtime_snapshot_path) + #[cfg(all( + not(feature = "docsrs"), + not(feature = "dont_create_runtime_snapshot") + ))] + startup_snapshot::build_snapshot(runtime_snapshot_path) } diff --git a/runtime/errors.rs b/runtime/errors.rs index 68fae9387ca01c..b26fd9fe307aca 100644 --- a/runtime/errors.rs +++ b/runtime/errors.rs @@ -58,7 +58,7 @@ fn get_io_error_class(error: &io::Error) -> &'static str { WriteZero => "WriteZero", UnexpectedEof => "UnexpectedEof", Other => "Error", - WouldBlock => unreachable!(), + WouldBlock => "WouldBlock", // Non-exhaustive enum - might add new variants // in the future _ => "Error", diff --git a/runtime/js.rs b/runtime/js.rs index 3d14c744cb0be7..57b1e7be472d9b 100644 --- a/runtime/js.rs +++ b/runtime/js.rs @@ -1,17 +1,21 @@ // Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. +#[cfg(not(feature = "dont_create_runtime_snapshot"))] use deno_core::Snapshot; +#[cfg(not(feature = "dont_create_runtime_snapshot"))] use log::debug; +#[cfg(not(feature = "dont_create_runtime_snapshot"))] use once_cell::sync::Lazy; -use std::path::PathBuf; +#[cfg(not(feature = "dont_create_runtime_snapshot"))] +static COMPRESSED_RUNTIME_SNAPSHOT: &[u8] = + include_bytes!(concat!(env!("OUT_DIR"), "/RUNTIME_SNAPSHOT.bin")); + +#[cfg(not(feature = "dont_create_runtime_snapshot"))] pub static RUNTIME_SNAPSHOT: Lazy> = Lazy::new( #[allow(clippy::uninit_vec)] #[cold] #[inline(never)] || { - static COMPRESSED_RUNTIME_SNAPSHOT: &[u8] = - include_bytes!(concat!(env!("OUT_DIR"), "/RUNTIME_SNAPSHOT.bin")); - let size = u32::from_le_bytes(COMPRESSED_RUNTIME_SNAPSHOT[0..4].try_into().unwrap()) as usize; @@ -30,13 +34,15 @@ pub static RUNTIME_SNAPSHOT: Lazy> = Lazy::new( }, ); +#[cfg(not(feature = "dont_create_runtime_snapshot"))] pub fn deno_isolate_init() -> Snapshot { debug!("Deno isolate init with snapshots."); Snapshot::Static(&RUNTIME_SNAPSHOT) } -pub fn get_99_main() -> PathBuf { - let manifest = env!("CARGO_MANIFEST_DIR"); - let path = PathBuf::from(manifest); - path.join("js").join("99_main.js") -} +#[cfg(not(feature = "include_js_files_for_snapshotting"))] +pub static SOURCE_CODE_FOR_99_MAIN_JS: &str = include_str!("js/99_main.js"); + +#[cfg(feature = "include_js_files_for_snapshotting")] +pub static PATH_FOR_99_MAIN_JS: &str = + concat!(env!("CARGO_MANIFEST_DIR"), "/js/99_main.js"); diff --git a/runtime/js/01_build.js b/runtime/js/01_build.js index 778331cdd6d72b..a9515c5b84aa5e 100644 --- a/runtime/js/01_build.js +++ b/runtime/js/01_build.js @@ -1,33 +1,28 @@ // Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. -"use strict"; -((window) => { - const { ObjectFreeze, StringPrototypeSplit } = window.__bootstrap.primordials; +const primordials = globalThis.__bootstrap.primordials; +const { ObjectFreeze, StringPrototypeSplit } = primordials; - const build = { - target: "unknown", - arch: "unknown", - os: "unknown", - vendor: "unknown", - env: undefined, - }; +const build = { + target: "unknown", + arch: "unknown", + os: "unknown", + vendor: "unknown", + env: undefined, +}; - function setBuildInfo(target) { - const { 0: arch, 1: vendor, 2: os, 3: env } = StringPrototypeSplit( - target, - "-", - 4, - ); - build.target = target; - build.arch = arch; - build.vendor = vendor; - build.os = os; - build.env = env; - ObjectFreeze(build); - } +function setBuildInfo(target) { + const { 0: arch, 1: vendor, 2: os, 3: env } = StringPrototypeSplit( + target, + "-", + 4, + ); + build.target = target; + build.arch = arch; + build.vendor = vendor; + build.os = os; + build.env = env; + ObjectFreeze(build); +} - window.__bootstrap.build = { - build, - setBuildInfo, - }; -})(this); +export { build, setBuildInfo }; diff --git a/runtime/js/01_errors.js b/runtime/js/01_errors.js index 620c64c3370b7a..8288e3ce9a5a92 100644 --- a/runtime/js/01_errors.js +++ b/runtime/js/01_errors.js @@ -1,153 +1,157 @@ // Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. -"use strict"; -((window) => { - const core = window.Deno.core; - const { Error } = window.__bootstrap.primordials; - const { BadResource, Interrupted } = core; +const core = globalThis.Deno.core; +const { BadResource, Interrupted } = core; +const primordials = globalThis.__bootstrap.primordials; +const { Error } = primordials; - class NotFound extends Error { - constructor(msg) { - super(msg); - this.name = "NotFound"; - } +class NotFound extends Error { + constructor(msg) { + super(msg); + this.name = "NotFound"; } +} - class PermissionDenied extends Error { - constructor(msg) { - super(msg); - this.name = "PermissionDenied"; - } +class PermissionDenied extends Error { + constructor(msg) { + super(msg); + this.name = "PermissionDenied"; } +} - class ConnectionRefused extends Error { - constructor(msg) { - super(msg); - this.name = "ConnectionRefused"; - } +class ConnectionRefused extends Error { + constructor(msg) { + super(msg); + this.name = "ConnectionRefused"; } +} - class ConnectionReset extends Error { - constructor(msg) { - super(msg); - this.name = "ConnectionReset"; - } +class ConnectionReset extends Error { + constructor(msg) { + super(msg); + this.name = "ConnectionReset"; } +} - class ConnectionAborted extends Error { - constructor(msg) { - super(msg); - this.name = "ConnectionAborted"; - } +class ConnectionAborted extends Error { + constructor(msg) { + super(msg); + this.name = "ConnectionAborted"; } +} - class NotConnected extends Error { - constructor(msg) { - super(msg); - this.name = "NotConnected"; - } +class NotConnected extends Error { + constructor(msg) { + super(msg); + this.name = "NotConnected"; } +} - class AddrInUse extends Error { - constructor(msg) { - super(msg); - this.name = "AddrInUse"; - } - } - - class AddrNotAvailable extends Error { - constructor(msg) { - super(msg); - this.name = "AddrNotAvailable"; - } - } - - class BrokenPipe extends Error { - constructor(msg) { - super(msg); - this.name = "BrokenPipe"; - } - } - - class AlreadyExists extends Error { - constructor(msg) { - super(msg); - this.name = "AlreadyExists"; - } - } - - class InvalidData extends Error { - constructor(msg) { - super(msg); - this.name = "InvalidData"; - } - } - - class TimedOut extends Error { - constructor(msg) { - super(msg); - this.name = "TimedOut"; - } - } - - class WriteZero extends Error { - constructor(msg) { - super(msg); - this.name = "WriteZero"; - } - } - - class UnexpectedEof extends Error { - constructor(msg) { - super(msg); - this.name = "UnexpectedEof"; - } - } - - class Http extends Error { - constructor(msg) { - super(msg); - this.name = "Http"; - } +class AddrInUse extends Error { + constructor(msg) { + super(msg); + this.name = "AddrInUse"; } +} - class Busy extends Error { - constructor(msg) { - super(msg); - this.name = "Busy"; - } +class AddrNotAvailable extends Error { + constructor(msg) { + super(msg); + this.name = "AddrNotAvailable"; } +} - class NotSupported extends Error { - constructor(msg) { - super(msg); - this.name = "NotSupported"; - } - } +class BrokenPipe extends Error { + constructor(msg) { + super(msg); + this.name = "BrokenPipe"; + } +} + +class AlreadyExists extends Error { + constructor(msg) { + super(msg); + this.name = "AlreadyExists"; + } +} + +class InvalidData extends Error { + constructor(msg) { + super(msg); + this.name = "InvalidData"; + } +} + +class TimedOut extends Error { + constructor(msg) { + super(msg); + this.name = "TimedOut"; + } +} + +class WriteZero extends Error { + constructor(msg) { + super(msg); + this.name = "WriteZero"; + } +} + +class WouldBlock extends Error { + constructor(msg) { + super(msg); + this.name = "WouldBlock"; + } +} + +class UnexpectedEof extends Error { + constructor(msg) { + super(msg); + this.name = "UnexpectedEof"; + } +} + +class Http extends Error { + constructor(msg) { + super(msg); + this.name = "Http"; + } +} + +class Busy extends Error { + constructor(msg) { + super(msg); + this.name = "Busy"; + } +} + +class NotSupported extends Error { + constructor(msg) { + super(msg); + this.name = "NotSupported"; + } +} - const errors = { - NotFound, - PermissionDenied, - ConnectionRefused, - ConnectionReset, - ConnectionAborted, - NotConnected, - AddrInUse, - AddrNotAvailable, - BrokenPipe, - AlreadyExists, - InvalidData, - TimedOut, - Interrupted, - WriteZero, - UnexpectedEof, - BadResource, - Http, - Busy, - NotSupported, - }; - - window.__bootstrap.errors = { - errors, - }; -})(this); +const errors = { + NotFound, + PermissionDenied, + ConnectionRefused, + ConnectionReset, + ConnectionAborted, + NotConnected, + AddrInUse, + AddrNotAvailable, + BrokenPipe, + AlreadyExists, + InvalidData, + TimedOut, + Interrupted, + WriteZero, + WouldBlock, + UnexpectedEof, + BadResource, + Http, + Busy, + NotSupported, +}; + +export { errors }; diff --git a/runtime/js/01_version.js b/runtime/js/01_version.js deleted file mode 100644 index b1b778a428ec47..00000000000000 --- a/runtime/js/01_version.js +++ /dev/null @@ -1,29 +0,0 @@ -// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. -"use strict"; - -((window) => { - const { ObjectFreeze } = window.__bootstrap.primordials; - - const version = { - deno: "", - v8: "", - typescript: "", - }; - - function setVersions( - denoVersion, - v8Version, - tsVersion, - ) { - version.deno = denoVersion; - version.v8 = v8Version; - version.typescript = tsVersion; - - ObjectFreeze(version); - } - - window.__bootstrap.version = { - version, - setVersions, - }; -})(this); diff --git a/runtime/js/01_version.ts b/runtime/js/01_version.ts new file mode 100644 index 00000000000000..cbbbd8d03e20ba --- /dev/null +++ b/runtime/js/01_version.ts @@ -0,0 +1,30 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. + +const primordials = globalThis.__bootstrap.primordials; +const { ObjectFreeze } = primordials; + +interface Version { + deno: string; + v8: string; + typescript: string; +} + +const version: Version = { + deno: "", + v8: "", + typescript: "", +}; + +function setVersions( + denoVersion, + v8Version, + tsVersion, +) { + version.deno = denoVersion; + version.v8 = v8Version; + version.typescript = tsVersion; + + ObjectFreeze(version); +} + +export { setVersions, version }; diff --git a/runtime/js/01_web_util.js b/runtime/js/01_web_util.js deleted file mode 100644 index 9e0bedbd8075e0..00000000000000 --- a/runtime/js/01_web_util.js +++ /dev/null @@ -1,25 +0,0 @@ -// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. -"use strict"; - -((window) => { - const { TypeError, Symbol } = window.__bootstrap.primordials; - const illegalConstructorKey = Symbol("illegalConstructorKey"); - - function requiredArguments( - name, - length, - required, - ) { - if (length < required) { - const errMsg = `${name} requires at least ${required} argument${ - required === 1 ? "" : "s" - }, but only ${length} present`; - throw new TypeError(errMsg); - } - } - - window.__bootstrap.webUtil = { - illegalConstructorKey, - requiredArguments, - }; -})(this); diff --git a/runtime/js/06_util.js b/runtime/js/06_util.js index 391497ba802c7a..435a55a61f4497 100644 --- a/runtime/js/06_util.js +++ b/runtime/js/06_util.js @@ -1,150 +1,147 @@ // Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. -"use strict"; -((window) => { - const { - decodeURIComponent, - ObjectPrototypeIsPrototypeOf, - Promise, - SafeArrayIterator, - StringPrototypeReplace, - TypeError, - } = window.__bootstrap.primordials; - const { build } = window.__bootstrap.build; - const { URLPrototype } = window.__bootstrap.url; - let logDebug = false; - let logSource = "JS"; +const internals = globalThis.__bootstrap.internals; +const primordials = globalThis.__bootstrap.primordials; +const { + decodeURIComponent, + ObjectPrototypeIsPrototypeOf, + Promise, + SafeArrayIterator, + StringPrototypeReplace, + TypeError, +} = primordials; +import { build } from "internal:runtime/js/01_build.js"; +import { URLPrototype } from "internal:deno_url/00_url.js"; +let logDebug = false; +let logSource = "JS"; - function setLogDebug(debug, source) { - logDebug = debug; - if (source) { - logSource = source; - } +function setLogDebug(debug, source) { + logDebug = debug; + if (source) { + logSource = source; } +} - function log(...args) { - if (logDebug) { - // if we destructure `console` off `globalThis` too early, we don't bind to - // the right console, therefore we don't log anything out. - globalThis.console.log( - `DEBUG ${logSource} -`, - ...new SafeArrayIterator(args), - ); - } +function log(...args) { + if (logDebug) { + // if we destructure `console` off `globalThis` too early, we don't bind to + // the right console, therefore we don't log anything out. + globalThis.console.log( + `DEBUG ${logSource} -`, + ...new SafeArrayIterator(args), + ); } +} - function createResolvable() { - let resolve; - let reject; - const promise = new Promise((res, rej) => { - resolve = res; - reject = rej; - }); - promise.resolve = resolve; - promise.reject = reject; - return promise; - } +function createResolvable() { + let resolve; + let reject; + const promise = new Promise((res, rej) => { + resolve = res; + reject = rej; + }); + promise.resolve = resolve; + promise.reject = reject; + return promise; +} - // Keep in sync with `fromFileUrl()` in `std/path/win32.ts`. - function pathFromURLWin32(url) { - let p = StringPrototypeReplace( - url.pathname, - /^\/*([A-Za-z]:)(\/|$)/, - "$1/", - ); - p = StringPrototypeReplace( - p, - /\//g, - "\\", - ); - p = StringPrototypeReplace( - p, - /%(?![0-9A-Fa-f]{2})/g, - "%25", - ); - let path = decodeURIComponent(p); - if (url.hostname != "") { - // Note: The `URL` implementation guarantees that the drive letter and - // hostname are mutually exclusive. Otherwise it would not have been valid - // to append the hostname and path like this. - path = `\\\\${url.hostname}${path}`; - } - return path; +// Keep in sync with `fromFileUrl()` in `std/path/win32.ts`. +function pathFromURLWin32(url) { + let p = StringPrototypeReplace( + url.pathname, + /^\/*([A-Za-z]:)(\/|$)/, + "$1/", + ); + p = StringPrototypeReplace( + p, + /\//g, + "\\", + ); + p = StringPrototypeReplace( + p, + /%(?![0-9A-Fa-f]{2})/g, + "%25", + ); + let path = decodeURIComponent(p); + if (url.hostname != "") { + // Note: The `URL` implementation guarantees that the drive letter and + // hostname are mutually exclusive. Otherwise it would not have been valid + // to append the hostname and path like this. + path = `\\\\${url.hostname}${path}`; } + return path; +} - // Keep in sync with `fromFileUrl()` in `std/path/posix.ts`. - function pathFromURLPosix(url) { - if (url.hostname !== "") { - throw new TypeError(`Host must be empty.`); - } - - return decodeURIComponent( - StringPrototypeReplace(url.pathname, /%(?![0-9A-Fa-f]{2})/g, "%25"), - ); +// Keep in sync with `fromFileUrl()` in `std/path/posix.ts`. +function pathFromURLPosix(url) { + if (url.hostname !== "") { + throw new TypeError(`Host must be empty.`); } - function pathFromURL(pathOrUrl) { - if (ObjectPrototypeIsPrototypeOf(URLPrototype, pathOrUrl)) { - if (pathOrUrl.protocol != "file:") { - throw new TypeError("Must be a file URL."); - } + return decodeURIComponent( + StringPrototypeReplace(url.pathname, /%(?![0-9A-Fa-f]{2})/g, "%25"), + ); +} - return build.os == "windows" - ? pathFromURLWin32(pathOrUrl) - : pathFromURLPosix(pathOrUrl); +function pathFromURL(pathOrUrl) { + if (ObjectPrototypeIsPrototypeOf(URLPrototype, pathOrUrl)) { + if (pathOrUrl.protocol != "file:") { + throw new TypeError("Must be a file URL."); } - return pathOrUrl; - } - - window.__bootstrap.internals = { - ...window.__bootstrap.internals ?? {}, - pathFromURL, - }; - function writable(value) { - return { - value, - writable: true, - enumerable: true, - configurable: true, - }; + return build.os == "windows" + ? pathFromURLWin32(pathOrUrl) + : pathFromURLPosix(pathOrUrl); } + return pathOrUrl; +} - function nonEnumerable(value) { - return { - value, - writable: true, - enumerable: false, - configurable: true, - }; - } +// TODO(bartlomieju): remove +internals.pathFromURL = pathFromURL; - function readOnly(value) { - return { - value, - enumerable: true, - writable: false, - configurable: true, - }; - } +function writable(value) { + return { + value, + writable: true, + enumerable: true, + configurable: true, + }; +} - function getterOnly(getter) { - return { - get: getter, - set() {}, - enumerable: true, - configurable: true, - }; - } +function nonEnumerable(value) { + return { + value, + writable: true, + enumerable: false, + configurable: true, + }; +} - window.__bootstrap.util = { - log, - setLogDebug, - createResolvable, - pathFromURL, - writable, - nonEnumerable, - readOnly, - getterOnly, +function readOnly(value) { + return { + value, + enumerable: true, + writable: false, + configurable: true, }; -})(this); +} + +function getterOnly(getter) { + return { + get: getter, + set() {}, + enumerable: true, + configurable: true, + }; +} + +export { + createResolvable, + getterOnly, + log, + nonEnumerable, + pathFromURL, + readOnly, + setLogDebug, + writable, +}; diff --git a/runtime/js/10_permissions.js b/runtime/js/10_permissions.js index 7c4af8ed0c8691..e8243153d745c0 100644 --- a/runtime/js/10_permissions.js +++ b/runtime/js/10_permissions.js @@ -1,287 +1,282 @@ // Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. -"use strict"; - -((window) => { - const { ops } = Deno.core; - const { Event } = window.__bootstrap.event; - const { EventTarget } = window.__bootstrap.eventTarget; - const { pathFromURL } = window.__bootstrap.util; - const { illegalConstructorKey } = window.__bootstrap.webUtil; - const { - ArrayIsArray, - ArrayPrototypeIncludes, - ArrayPrototypeMap, - ArrayPrototypeSlice, - Map, - MapPrototypeGet, - MapPrototypeHas, - MapPrototypeSet, - FunctionPrototypeCall, - PromiseResolve, - PromiseReject, - ReflectHas, - SafeArrayIterator, - SymbolFor, - TypeError, - } = window.__bootstrap.primordials; - /** - * @typedef StatusCacheValue - * @property {PermissionState} state - * @property {PermissionStatus} status - */ - - /** @type {ReadonlyArray<"read" | "write" | "net" | "env" | "sys" | "run" | "ffi" | "hrtime">} */ - const permissionNames = [ - "read", - "write", - "net", - "env", - "sys", - "run", - "ffi", - "hrtime", - ]; - - /** - * @param {Deno.PermissionDescriptor} desc - * @returns {Deno.PermissionState} - */ - function opQuery(desc) { - return ops.op_query_permission(desc); +const core = globalThis.Deno.core; +const ops = core.ops; +import { Event, EventTarget } from "internal:deno_web/02_event.js"; +import { pathFromURL } from "internal:runtime/js/06_util.js"; +const primordials = globalThis.__bootstrap.primordials; +const { + ArrayIsArray, + ArrayPrototypeIncludes, + ArrayPrototypeMap, + ArrayPrototypeSlice, + Map, + MapPrototypeGet, + MapPrototypeHas, + MapPrototypeSet, + FunctionPrototypeCall, + PromiseResolve, + PromiseReject, + ReflectHas, + SafeArrayIterator, + Symbol, + SymbolFor, + TypeError, +} = primordials; + +const illegalConstructorKey = Symbol("illegalConstructorKey"); + +/** + * @typedef StatusCacheValue + * @property {PermissionState} state + * @property {PermissionStatus} status + */ + +/** @type {ReadonlyArray<"read" | "write" | "net" | "env" | "sys" | "run" | "ffi" | "hrtime">} */ +const permissionNames = [ + "read", + "write", + "net", + "env", + "sys", + "run", + "ffi", + "hrtime", +]; + +/** + * @param {Deno.PermissionDescriptor} desc + * @returns {Deno.PermissionState} + */ +function opQuery(desc) { + return ops.op_query_permission(desc); +} + +/** + * @param {Deno.PermissionDescriptor} desc + * @returns {Deno.PermissionState} + */ +function opRevoke(desc) { + return ops.op_revoke_permission(desc); +} + +/** + * @param {Deno.PermissionDescriptor} desc + * @returns {Deno.PermissionState} + */ +function opRequest(desc) { + return ops.op_request_permission(desc); +} + +class PermissionStatus extends EventTarget { + /** @type {{ state: Deno.PermissionState }} */ + #state; + + /** @type {((this: PermissionStatus, event: Event) => any) | null} */ + onchange = null; + + /** @returns {Deno.PermissionState} */ + get state() { + return this.#state.state; } /** - * @param {Deno.PermissionDescriptor} desc - * @returns {Deno.PermissionState} + * @param {{ state: Deno.PermissionState }} state + * @param {unknown} key */ - function opRevoke(desc) { - return ops.op_revoke_permission(desc); + constructor(state = null, key = null) { + if (key != illegalConstructorKey) { + throw new TypeError("Illegal constructor."); + } + super(); + this.#state = state; } /** - * @param {Deno.PermissionDescriptor} desc - * @returns {Deno.PermissionState} + * @param {Event} event + * @returns {boolean} */ - function opRequest(desc) { - return ops.op_request_permission(desc); - } - - class PermissionStatus extends EventTarget { - /** @type {{ state: Deno.PermissionState }} */ - #state; - - /** @type {((this: PermissionStatus, event: Event) => any) | null} */ - onchange = null; - - /** @returns {Deno.PermissionState} */ - get state() { - return this.#state.state; - } - - /** - * @param {{ state: Deno.PermissionState }} state - * @param {unknown} key - */ - constructor(state = null, key = null) { - if (key != illegalConstructorKey) { - throw new TypeError("Illegal constructor."); - } - super(); - this.#state = state; - } - - /** - * @param {Event} event - * @returns {boolean} - */ - dispatchEvent(event) { - let dispatched = super.dispatchEvent(event); - if (dispatched && this.onchange) { - FunctionPrototypeCall(this.onchange, this, event); - dispatched = !event.defaultPrevented; - } - return dispatched; - } - - [SymbolFor("Deno.privateCustomInspect")](inspect) { - return `${this.constructor.name} ${ - inspect({ state: this.state, onchange: this.onchange }) - }`; + dispatchEvent(event) { + let dispatched = super.dispatchEvent(event); + if (dispatched && this.onchange) { + FunctionPrototypeCall(this.onchange, this, event); + dispatched = !event.defaultPrevented; } + return dispatched; } - /** @type {Map} */ - const statusCache = new Map(); - - /** - * @param {Deno.PermissionDescriptor} desc - * @param {Deno.PermissionState} state - * @returns {PermissionStatus} - */ - function cache(desc, state) { - let { name: key } = desc; - if ( - (desc.name === "read" || desc.name === "write" || desc.name === "ffi") && - ReflectHas(desc, "path") - ) { - key += `-${desc.path}&`; - } else if (desc.name === "net" && desc.host) { - key += `-${desc.host}&`; - } else if (desc.name === "run" && desc.command) { - key += `-${desc.command}&`; - } else if (desc.name === "env" && desc.variable) { - key += `-${desc.variable}&`; - } else if (desc.name === "sys" && desc.kind) { - key += `-${desc.kind}&`; - } else { - key += "$"; - } - if (MapPrototypeHas(statusCache, key)) { - const status = MapPrototypeGet(statusCache, key); - if (status.state !== state) { - status.state = state; - status.status.dispatchEvent(new Event("change", { cancelable: false })); - } - return status.status; + [SymbolFor("Deno.privateCustomInspect")](inspect) { + return `${this.constructor.name} ${ + inspect({ state: this.state, onchange: this.onchange }) + }`; + } +} + +/** @type {Map} */ +const statusCache = new Map(); + +/** + * @param {Deno.PermissionDescriptor} desc + * @param {Deno.PermissionState} state + * @returns {PermissionStatus} + */ +function cache(desc, state) { + let { name: key } = desc; + if ( + (desc.name === "read" || desc.name === "write" || desc.name === "ffi") && + ReflectHas(desc, "path") + ) { + key += `-${desc.path}&`; + } else if (desc.name === "net" && desc.host) { + key += `-${desc.host}&`; + } else if (desc.name === "run" && desc.command) { + key += `-${desc.command}&`; + } else if (desc.name === "env" && desc.variable) { + key += `-${desc.variable}&`; + } else if (desc.name === "sys" && desc.kind) { + key += `-${desc.kind}&`; + } else { + key += "$"; + } + if (MapPrototypeHas(statusCache, key)) { + const status = MapPrototypeGet(statusCache, key); + if (status.state !== state) { + status.state = state; + status.status.dispatchEvent(new Event("change", { cancelable: false })); } - /** @type {{ state: Deno.PermissionState; status?: PermissionStatus }} */ - const status = { state }; - status.status = new PermissionStatus(status, illegalConstructorKey); - MapPrototypeSet(statusCache, key, status); return status.status; } - - /** - * @param {unknown} desc - * @returns {desc is Deno.PermissionDescriptor} - */ - function isValidDescriptor(desc) { - return typeof desc === "object" && desc !== null && - ArrayPrototypeIncludes(permissionNames, desc.name); + /** @type {{ state: Deno.PermissionState; status?: PermissionStatus }} */ + const status = { state }; + status.status = new PermissionStatus(status, illegalConstructorKey); + MapPrototypeSet(statusCache, key, status); + return status.status; +} + +/** + * @param {unknown} desc + * @returns {desc is Deno.PermissionDescriptor} + */ +function isValidDescriptor(desc) { + return typeof desc === "object" && desc !== null && + ArrayPrototypeIncludes(permissionNames, desc.name); +} + +/** + * @param {Deno.PermissionDescriptor} desc + * @returns {desc is Deno.PermissionDescriptor} + */ +function formDescriptor(desc) { + if ( + desc.name === "read" || desc.name === "write" || desc.name === "ffi" + ) { + desc.path = pathFromURL(desc.path); + } else if (desc.name === "run") { + desc.command = pathFromURL(desc.command); } +} - /** - * @param {Deno.PermissionDescriptor} desc - * @returns {desc is Deno.PermissionDescriptor} - */ - function formDescriptor(desc) { - if ( - desc.name === "read" || desc.name === "write" || desc.name === "ffi" - ) { - desc.path = pathFromURL(desc.path); - } else if (desc.name === "run") { - desc.command = pathFromURL(desc.command); +class Permissions { + constructor(key = null) { + if (key != illegalConstructorKey) { + throw new TypeError("Illegal constructor."); } } - class Permissions { - constructor(key = null) { - if (key != illegalConstructorKey) { - throw new TypeError("Illegal constructor."); - } + query(desc) { + try { + return PromiseResolve(this.querySync(desc)); + } catch (error) { + return PromiseReject(error); } + } - query(desc) { - try { - return PromiseResolve(this.querySync(desc)); - } catch (error) { - return PromiseReject(error); - } + querySync(desc) { + if (!isValidDescriptor(desc)) { + throw new TypeError( + `The provided value "${desc?.name}" is not a valid permission name.`, + ); } - querySync(desc) { - if (!isValidDescriptor(desc)) { - throw new TypeError( - `The provided value "${desc?.name}" is not a valid permission name.`, - ); - } + formDescriptor(desc); - formDescriptor(desc); + const state = opQuery(desc); + return cache(desc, state); + } - const state = opQuery(desc); - return cache(desc, state); + revoke(desc) { + try { + return PromiseResolve(this.revokeSync(desc)); + } catch (error) { + return PromiseReject(error); } + } - revoke(desc) { - try { - return PromiseResolve(this.revokeSync(desc)); - } catch (error) { - return PromiseReject(error); - } + revokeSync(desc) { + if (!isValidDescriptor(desc)) { + throw new TypeError( + `The provided value "${desc?.name}" is not a valid permission name.`, + ); } - revokeSync(desc) { - if (!isValidDescriptor(desc)) { - throw new TypeError( - `The provided value "${desc?.name}" is not a valid permission name.`, - ); - } + formDescriptor(desc); - formDescriptor(desc); + const state = opRevoke(desc); + return cache(desc, state); + } - const state = opRevoke(desc); - return cache(desc, state); + request(desc) { + try { + return PromiseResolve(this.requestSync(desc)); + } catch (error) { + return PromiseReject(error); } + } - request(desc) { - try { - return PromiseResolve(this.requestSync(desc)); - } catch (error) { - return PromiseReject(error); - } + requestSync(desc) { + if (!isValidDescriptor(desc)) { + throw new TypeError( + `The provided value "${desc?.name}" is not a valid permission name.`, + ); } - requestSync(desc) { - if (!isValidDescriptor(desc)) { - throw new TypeError( - `The provided value "${desc?.name}" is not a valid permission name.`, - ); - } - - formDescriptor(desc); + formDescriptor(desc); - const state = opRequest(desc); - return cache(desc, state); - } + const state = opRequest(desc); + return cache(desc, state); } +} + +const permissions = new Permissions(illegalConstructorKey); - const permissions = new Permissions(illegalConstructorKey); - - /** Converts all file URLs in FS allowlists to paths. */ - function serializePermissions(permissions) { - if (typeof permissions == "object" && permissions != null) { - const serializedPermissions = {}; - for ( - const key of new SafeArrayIterator(["read", "write", "run", "ffi"]) - ) { - if (ArrayIsArray(permissions[key])) { - serializedPermissions[key] = ArrayPrototypeMap( - permissions[key], - (path) => pathFromURL(path), - ); - } else { - serializedPermissions[key] = permissions[key]; - } +/** Converts all file URLs in FS allowlists to paths. */ +function serializePermissions(permissions) { + if (typeof permissions == "object" && permissions != null) { + const serializedPermissions = {}; + for ( + const key of new SafeArrayIterator(["read", "write", "run", "ffi"]) + ) { + if (ArrayIsArray(permissions[key])) { + serializedPermissions[key] = ArrayPrototypeMap( + permissions[key], + (path) => pathFromURL(path), + ); + } else { + serializedPermissions[key] = permissions[key]; } - for ( - const key of new SafeArrayIterator(["env", "hrtime", "net", "sys"]) - ) { - if (ArrayIsArray(permissions[key])) { - serializedPermissions[key] = ArrayPrototypeSlice(permissions[key]); - } else { - serializedPermissions[key] = permissions[key]; - } + } + for ( + const key of new SafeArrayIterator(["env", "hrtime", "net", "sys"]) + ) { + if (ArrayIsArray(permissions[key])) { + serializedPermissions[key] = ArrayPrototypeSlice(permissions[key]); + } else { + serializedPermissions[key] = permissions[key]; } - return serializedPermissions; } - return permissions; + return serializedPermissions; } + return permissions; +} - window.__bootstrap.permissions = { - serializePermissions, - permissions, - Permissions, - PermissionStatus, - }; -})(this); +export { Permissions, permissions, PermissionStatus, serializePermissions }; diff --git a/runtime/js/11_workers.js b/runtime/js/11_workers.js index 85c01e1a92200f..7b5090dc54ba3e 100644 --- a/runtime/js/11_workers.js +++ b/runtime/js/11_workers.js @@ -1,254 +1,253 @@ // Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. -"use strict"; - -((window) => { - const core = window.Deno.core; - const ops = core.ops; - const { - Error, - ObjectPrototypeIsPrototypeOf, - StringPrototypeStartsWith, - String, - SymbolIterator, - SymbolToStringTag, - } = window.__bootstrap.primordials; - const webidl = window.__bootstrap.webidl; - const { URL } = window.__bootstrap.url; - const { getLocationHref } = window.__bootstrap.location; - const { serializePermissions } = window.__bootstrap.permissions; - const { log } = window.__bootstrap.util; - const { ErrorEvent, MessageEvent, defineEventHandler } = - window.__bootstrap.event; - const { EventTarget } = window.__bootstrap.eventTarget; - const { - deserializeJsMessageData, - serializeJsMessageData, - MessagePortPrototype, - } = window.__bootstrap.messagePort; - - function createWorker( - specifier, + +const core = globalThis.Deno.core; +const ops = core.ops; +const primordials = globalThis.__bootstrap.primordials; +const { + Error, + ObjectPrototypeIsPrototypeOf, + StringPrototypeStartsWith, + String, + SymbolIterator, + SymbolToStringTag, +} = primordials; +import * as webidl from "internal:deno_webidl/00_webidl.js"; +import { URL } from "internal:deno_url/00_url.js"; +import { getLocationHref } from "internal:deno_web/12_location.js"; +import { serializePermissions } from "internal:runtime/js/10_permissions.js"; +import { log } from "internal:runtime/js/06_util.js"; +import { + defineEventHandler, + ErrorEvent, + EventTarget, + MessageEvent, +} from "internal:deno_web/02_event.js"; +import { + deserializeJsMessageData, + MessagePortPrototype, + serializeJsMessageData, +} from "internal:deno_web/13_message_port.js"; + +function createWorker( + specifier, + hasSourceCode, + sourceCode, + permissions, + name, + workerType, +) { + return ops.op_create_worker({ hasSourceCode, - sourceCode, - permissions, name, + permissions: serializePermissions(permissions), + sourceCode, + specifier, workerType, - ) { - return ops.op_create_worker({ - hasSourceCode, + }); +} + +function hostTerminateWorker(id) { + ops.op_host_terminate_worker(id); +} + +function hostPostMessage(id, data) { + ops.op_host_post_message(id, data); +} + +function hostRecvCtrl(id) { + return core.opAsync("op_host_recv_ctrl", id); +} + +function hostRecvMessage(id) { + return core.opAsync("op_host_recv_message", id); +} + +class Worker extends EventTarget { + #id = 0; + #name = ""; + + // "RUNNING" | "CLOSED" | "TERMINATED" + // "TERMINATED" means that any controls or messages received will be + // discarded. "CLOSED" means that we have received a control + // indicating that the worker is no longer running, but there might + // still be messages left to receive. + #status = "RUNNING"; + + constructor(specifier, options = {}) { + super(); + specifier = String(specifier); + const { + deno, name, - permissions: serializePermissions(permissions), - sourceCode, + type = "classic", + } = options; + + const workerType = webidl.converters["WorkerType"](type); + + if ( + StringPrototypeStartsWith(specifier, "./") || + StringPrototypeStartsWith(specifier, "../") || + StringPrototypeStartsWith(specifier, "/") || workerType === "classic" + ) { + const baseUrl = getLocationHref(); + if (baseUrl != null) { + specifier = new URL(specifier, baseUrl).href; + } + } + + this.#name = name; + let hasSourceCode, sourceCode; + if (workerType === "classic") { + hasSourceCode = true; + sourceCode = `importScripts("#");`; + } else { + hasSourceCode = false; + sourceCode = ""; + } + + const id = createWorker( specifier, + hasSourceCode, + sourceCode, + deno?.permissions, + name, workerType, - }); + ); + this.#id = id; + this.#pollControl(); + this.#pollMessages(); } - function hostTerminateWorker(id) { - ops.op_host_terminate_worker(id); - } - - function hostPostMessage(id, data) { - ops.op_host_post_message(id, data); - } + #handleError(e) { + const event = new ErrorEvent("error", { + cancelable: true, + message: e.message, + lineno: e.lineNumber ? e.lineNumber : undefined, + colno: e.columnNumber ? e.columnNumber : undefined, + filename: e.fileName, + error: null, + }); - function hostRecvCtrl(id) { - return core.opAsync("op_host_recv_ctrl", id); - } + this.dispatchEvent(event); + // Don't bubble error event to window for loader errors (`!e.fileName`). + // TODO(nayeemrmn): It's not correct to use `e.fileName` to detect user + // errors. It won't be there for non-awaited async ops for example. + if (e.fileName && !event.defaultPrevented) { + globalThis.dispatchEvent(event); + } - function hostRecvMessage(id) { - return core.opAsync("op_host_recv_message", id); + return event.defaultPrevented; } - class Worker extends EventTarget { - #id = 0; - #name = ""; - - // "RUNNING" | "CLOSED" | "TERMINATED" - // "TERMINATED" means that any controls or messages received will be - // discarded. "CLOSED" means that we have received a control - // indicating that the worker is no longer running, but there might - // still be messages left to receive. - #status = "RUNNING"; - - constructor(specifier, options = {}) { - super(); - specifier = String(specifier); - const { - deno, - name, - type = "classic", - } = options; - - const workerType = webidl.converters["WorkerType"](type); - - if ( - StringPrototypeStartsWith(specifier, "./") || - StringPrototypeStartsWith(specifier, "../") || - StringPrototypeStartsWith(specifier, "/") || workerType === "classic" - ) { - const baseUrl = getLocationHref(); - if (baseUrl != null) { - specifier = new URL(specifier, baseUrl).href; - } - } + #pollControl = async () => { + while (this.#status === "RUNNING") { + const { 0: type, 1: data } = await hostRecvCtrl(this.#id); - this.#name = name; - let hasSourceCode, sourceCode; - if (workerType === "classic") { - hasSourceCode = true; - sourceCode = `importScripts("#");`; - } else { - hasSourceCode = false; - sourceCode = ""; + // If terminate was called then we ignore all messages + if (this.#status === "TERMINATED") { + return; } - const id = createWorker( - specifier, - hasSourceCode, - sourceCode, - deno?.permissions, - name, - workerType, - ); - this.#id = id; - this.#pollControl(); - this.#pollMessages(); - } - - #handleError(e) { - const event = new ErrorEvent("error", { - cancelable: true, - message: e.message, - lineno: e.lineNumber ? e.lineNumber : undefined, - colno: e.columnNumber ? e.columnNumber : undefined, - filename: e.fileName, - error: null, - }); - - this.dispatchEvent(event); - // Don't bubble error event to window for loader errors (`!e.fileName`). - // TODO(nayeemrmn): It's not correct to use `e.fileName` to detect user - // errors. It won't be there for non-awaited async ops for example. - if (e.fileName && !event.defaultPrevented) { - window.dispatchEvent(event); - } - - return event.defaultPrevented; - } - - #pollControl = async () => { - while (this.#status === "RUNNING") { - const { 0: type, 1: data } = await hostRecvCtrl(this.#id); - - // If terminate was called then we ignore all messages - if (this.#status === "TERMINATED") { - return; - } - - switch (type) { - case 1: { // TerminalError - this.#status = "CLOSED"; - } /* falls through */ - case 2: { // Error - if (!this.#handleError(data)) { - throw new Error("Unhandled error in child worker."); - } - break; - } - case 3: { // Close - log(`Host got "close" message from worker: ${this.#name}`); - this.#status = "CLOSED"; - return; - } - default: { - throw new Error(`Unknown worker event: "${type}"`); + switch (type) { + case 1: { // TerminalError + this.#status = "CLOSED"; + } /* falls through */ + case 2: { // Error + if (!this.#handleError(data)) { + throw new Error("Unhandled error in child worker."); } + break; } - } - }; - - #pollMessages = async () => { - while (this.#status !== "TERMINATED") { - const data = await hostRecvMessage(this.#id); - if (this.#status === "TERMINATED" || data === null) { + case 3: { // Close + log(`Host got "close" message from worker: ${this.#name}`); + this.#status = "CLOSED"; return; } - let message, transferables; - try { - const v = deserializeJsMessageData(data); - message = v[0]; - transferables = v[1]; - } catch (err) { - const event = new MessageEvent("messageerror", { - cancelable: false, - data: err, - }); - this.dispatchEvent(event); - return; + default: { + throw new Error(`Unknown worker event: "${type}"`); } - const event = new MessageEvent("message", { + } + } + }; + + #pollMessages = async () => { + while (this.#status !== "TERMINATED") { + const data = await hostRecvMessage(this.#id); + if (this.#status === "TERMINATED" || data === null) { + return; + } + let message, transferables; + try { + const v = deserializeJsMessageData(data); + message = v[0]; + transferables = v[1]; + } catch (err) { + const event = new MessageEvent("messageerror", { cancelable: false, - data: message, - ports: transferables.filter((t) => - ObjectPrototypeIsPrototypeOf(MessagePortPrototype, t) - ), + data: err, }); this.dispatchEvent(event); + return; } - }; - - postMessage(message, transferOrOptions = {}) { - const prefix = "Failed to execute 'postMessage' on 'MessagePort'"; - webidl.requiredArguments(arguments.length, 1, { prefix }); - message = webidl.converters.any(message); - let options; - if ( - webidl.type(transferOrOptions) === "Object" && - transferOrOptions !== undefined && - transferOrOptions[SymbolIterator] !== undefined - ) { - const transfer = webidl.converters["sequence"]( - transferOrOptions, - { prefix, context: "Argument 2" }, - ); - options = { transfer }; - } else { - options = webidl.converters.StructuredSerializeOptions( - transferOrOptions, - { - prefix, - context: "Argument 2", - }, - ); - } - const { transfer } = options; - const data = serializeJsMessageData(message, transfer); - if (this.#status === "RUNNING") { - hostPostMessage(this.#id, data); - } + const event = new MessageEvent("message", { + cancelable: false, + data: message, + ports: transferables.filter((t) => + ObjectPrototypeIsPrototypeOf(MessagePortPrototype, t) + ), + }); + this.dispatchEvent(event); } + }; - terminate() { - if (this.#status !== "TERMINATED") { - this.#status = "TERMINATED"; - hostTerminateWorker(this.#id); - } + postMessage(message, transferOrOptions = {}) { + const prefix = "Failed to execute 'postMessage' on 'MessagePort'"; + webidl.requiredArguments(arguments.length, 1, { prefix }); + message = webidl.converters.any(message); + let options; + if ( + webidl.type(transferOrOptions) === "Object" && + transferOrOptions !== undefined && + transferOrOptions[SymbolIterator] !== undefined + ) { + const transfer = webidl.converters["sequence"]( + transferOrOptions, + { prefix, context: "Argument 2" }, + ); + options = { transfer }; + } else { + options = webidl.converters.StructuredSerializeOptions( + transferOrOptions, + { + prefix, + context: "Argument 2", + }, + ); } + const { transfer } = options; + const data = serializeJsMessageData(message, transfer); + if (this.#status === "RUNNING") { + hostPostMessage(this.#id, data); + } + } - [SymbolToStringTag] = "Worker"; + terminate() { + if (this.#status !== "TERMINATED") { + this.#status = "TERMINATED"; + hostTerminateWorker(this.#id); + } } - defineEventHandler(Worker.prototype, "error"); - defineEventHandler(Worker.prototype, "message"); - defineEventHandler(Worker.prototype, "messageerror"); + [SymbolToStringTag] = "Worker"; +} - webidl.converters["WorkerType"] = webidl.createEnumConverter("WorkerType", [ - "classic", - "module", - ]); +defineEventHandler(Worker.prototype, "error"); +defineEventHandler(Worker.prototype, "message"); +defineEventHandler(Worker.prototype, "messageerror"); - window.__bootstrap.worker = { - Worker, - }; -})(this); +webidl.converters["WorkerType"] = webidl.createEnumConverter("WorkerType", [ + "classic", + "module", +]); + +export { Worker }; diff --git a/runtime/js/12_io.js b/runtime/js/12_io.js index f6b2d6b8774b03..b9ff1190b18bfa 100644 --- a/runtime/js/12_io.js +++ b/runtime/js/12_io.js @@ -3,238 +3,236 @@ // Interfaces 100% copied from Go. // Documentation liberally lifted from them too. // Thank you! We love Go! <3 -"use strict"; - -((window) => { - const core = window.Deno.core; - const ops = core.ops; - const { - Uint8Array, - ArrayPrototypePush, - MathMin, - TypedArrayPrototypeSubarray, - TypedArrayPrototypeSet, - } = window.__bootstrap.primordials; - - const DEFAULT_BUFFER_SIZE = 32 * 1024; - // Seek whence values. - // https://golang.org/pkg/io/#pkg-constants - const SeekMode = { - 0: "Start", - 1: "Current", - 2: "End", - - Start: 0, - Current: 1, - End: 2, - }; - - async function copy( - src, - dst, - options, - ) { - let n = 0; - const bufSize = options?.bufSize ?? DEFAULT_BUFFER_SIZE; - const b = new Uint8Array(bufSize); - let gotEOF = false; - while (gotEOF === false) { - const result = await src.read(b); - if (result === null) { - gotEOF = true; - } else { - let nwritten = 0; - while (nwritten < result) { - nwritten += await dst.write( - TypedArrayPrototypeSubarray(b, nwritten, result), - ); - } - n += nwritten; - } - } - return n; - } - async function* iter( - r, - options, - ) { - const bufSize = options?.bufSize ?? DEFAULT_BUFFER_SIZE; - const b = new Uint8Array(bufSize); - while (true) { - const result = await r.read(b); - if (result === null) { - break; +const core = globalThis.Deno.core; +const ops = core.ops; +const primordials = globalThis.__bootstrap.primordials; +const { + Uint8Array, + ArrayPrototypePush, + MathMin, + TypedArrayPrototypeSubarray, + TypedArrayPrototypeSet, +} = primordials; + +const DEFAULT_BUFFER_SIZE = 32 * 1024; +// Seek whence values. +// https://golang.org/pkg/io/#pkg-constants +const SeekMode = { + 0: "Start", + 1: "Current", + 2: "End", + + Start: 0, + Current: 1, + End: 2, +}; + +async function copy( + src, + dst, + options, +) { + let n = 0; + const bufSize = options?.bufSize ?? DEFAULT_BUFFER_SIZE; + const b = new Uint8Array(bufSize); + let gotEOF = false; + while (gotEOF === false) { + const result = await src.read(b); + if (result === null) { + gotEOF = true; + } else { + let nwritten = 0; + while (nwritten < result) { + nwritten += await dst.write( + TypedArrayPrototypeSubarray(b, nwritten, result), + ); } - - yield TypedArrayPrototypeSubarray(b, 0, result); + n += nwritten; } } - - function* iterSync( - r, - options, - ) { - const bufSize = options?.bufSize ?? DEFAULT_BUFFER_SIZE; - const b = new Uint8Array(bufSize); - while (true) { - const result = r.readSync(b); - if (result === null) { - break; - } - - yield TypedArrayPrototypeSubarray(b, 0, result); + return n; +} + +async function* iter( + r, + options, +) { + const bufSize = options?.bufSize ?? DEFAULT_BUFFER_SIZE; + const b = new Uint8Array(bufSize); + while (true) { + const result = await r.read(b); + if (result === null) { + break; } - } - function readSync(rid, buffer) { - if (buffer.length === 0) { - return 0; + yield TypedArrayPrototypeSubarray(b, 0, result); + } +} + +function* iterSync( + r, + options, +) { + const bufSize = options?.bufSize ?? DEFAULT_BUFFER_SIZE; + const b = new Uint8Array(bufSize); + while (true) { + const result = r.readSync(b); + if (result === null) { + break; } - const nread = ops.op_read_sync(rid, buffer); + yield TypedArrayPrototypeSubarray(b, 0, result); + } +} - return nread === 0 ? null : nread; +function readSync(rid, buffer) { + if (buffer.length === 0) { + return 0; } - async function read(rid, buffer) { - if (buffer.length === 0) { - return 0; - } + const nread = ops.op_read_sync(rid, buffer); - const nread = await core.read(rid, buffer); + return nread === 0 ? null : nread; +} - return nread === 0 ? null : nread; +async function read(rid, buffer) { + if (buffer.length === 0) { + return 0; } - function writeSync(rid, data) { - return ops.op_write_sync(rid, data); - } + const nread = await core.read(rid, buffer); - function write(rid, data) { - return core.write(rid, data); - } + return nread === 0 ? null : nread; +} - const READ_PER_ITER = 64 * 1024; // 64kb +function writeSync(rid, data) { + return ops.op_write_sync(rid, data); +} - function readAll(r) { - return readAllInner(r); - } - async function readAllInner(r, options) { - const buffers = []; - const signal = options?.signal ?? null; - while (true) { - signal?.throwIfAborted(); - const buf = new Uint8Array(READ_PER_ITER); - const read = await r.read(buf); - if (typeof read == "number") { - ArrayPrototypePush(buffers, new Uint8Array(buf.buffer, 0, read)); - } else { - break; - } - } - signal?.throwIfAborted(); +function write(rid, data) { + return core.write(rid, data); +} - return concatBuffers(buffers); +const READ_PER_ITER = 64 * 1024; // 64kb + +function readAll(r) { + return readAllInner(r); +} +async function readAllInner(r, options) { + const buffers = []; + const signal = options?.signal ?? null; + while (true) { + signal?.throwIfAborted(); + const buf = new Uint8Array(READ_PER_ITER); + const read = await r.read(buf); + if (typeof read == "number") { + ArrayPrototypePush(buffers, new Uint8Array(buf.buffer, 0, read)); + } else { + break; + } } + signal?.throwIfAborted(); - function readAllSync(r) { - const buffers = []; + return concatBuffers(buffers); +} - while (true) { - const buf = new Uint8Array(READ_PER_ITER); - const read = r.readSync(buf); - if (typeof read == "number") { - ArrayPrototypePush(buffers, TypedArrayPrototypeSubarray(buf, 0, read)); - } else { - break; - } - } +function readAllSync(r) { + const buffers = []; - return concatBuffers(buffers); + while (true) { + const buf = new Uint8Array(READ_PER_ITER); + const read = r.readSync(buf); + if (typeof read == "number") { + ArrayPrototypePush(buffers, TypedArrayPrototypeSubarray(buf, 0, read)); + } else { + break; + } } - function concatBuffers(buffers) { - let totalLen = 0; - for (let i = 0; i < buffers.length; ++i) { - totalLen += buffers[i].byteLength; - } + return concatBuffers(buffers); +} - const contents = new Uint8Array(totalLen); +function concatBuffers(buffers) { + let totalLen = 0; + for (let i = 0; i < buffers.length; ++i) { + totalLen += buffers[i].byteLength; + } - let n = 0; - for (let i = 0; i < buffers.length; ++i) { - const buf = buffers[i]; - TypedArrayPrototypeSet(contents, buf, n); - n += buf.byteLength; - } + const contents = new Uint8Array(totalLen); - return contents; + let n = 0; + for (let i = 0; i < buffers.length; ++i) { + const buf = buffers[i]; + TypedArrayPrototypeSet(contents, buf, n); + n += buf.byteLength; } - function readAllSyncSized(r, size) { - const buf = new Uint8Array(size + 1); // 1B to detect extended files - let cursor = 0; - - while (cursor < size) { - const sliceEnd = MathMin(size + 1, cursor + READ_PER_ITER); - const slice = TypedArrayPrototypeSubarray(buf, cursor, sliceEnd); - const read = r.readSync(slice); - if (typeof read == "number") { - cursor += read; - } else { - break; - } - } + return contents; +} + +function readAllSyncSized(r, size) { + const buf = new Uint8Array(size + 1); // 1B to detect extended files + let cursor = 0; - // Handle truncated or extended files during read - if (cursor > size) { - // Read remaining and concat - return concatBuffers([buf, readAllSync(r)]); - } else { // cursor == size - return TypedArrayPrototypeSubarray(buf, 0, cursor); + while (cursor < size) { + const sliceEnd = MathMin(size + 1, cursor + READ_PER_ITER); + const slice = TypedArrayPrototypeSubarray(buf, cursor, sliceEnd); + const read = r.readSync(slice); + if (typeof read == "number") { + cursor += read; + } else { + break; } } - async function readAllInnerSized(r, size, options) { - const buf = new Uint8Array(size + 1); // 1B to detect extended files - let cursor = 0; - const signal = options?.signal ?? null; - while (cursor < size) { - signal?.throwIfAborted(); - const sliceEnd = MathMin(size + 1, cursor + READ_PER_ITER); - const slice = TypedArrayPrototypeSubarray(buf, cursor, sliceEnd); - const read = await r.read(slice); - if (typeof read == "number") { - cursor += read; - } else { - break; - } - } - signal?.throwIfAborted(); + // Handle truncated or extended files during read + if (cursor > size) { + // Read remaining and concat + return concatBuffers([buf, readAllSync(r)]); + } else { // cursor == size + return TypedArrayPrototypeSubarray(buf, 0, cursor); + } +} - // Handle truncated or extended files during read - if (cursor > size) { - // Read remaining and concat - return concatBuffers([buf, await readAllInner(r, options)]); +async function readAllInnerSized(r, size, options) { + const buf = new Uint8Array(size + 1); // 1B to detect extended files + let cursor = 0; + const signal = options?.signal ?? null; + while (cursor < size) { + signal?.throwIfAborted(); + const sliceEnd = MathMin(size + 1, cursor + READ_PER_ITER); + const slice = TypedArrayPrototypeSubarray(buf, cursor, sliceEnd); + const read = await r.read(slice); + if (typeof read == "number") { + cursor += read; } else { - return TypedArrayPrototypeSubarray(buf, 0, cursor); + break; } } - - window.__bootstrap.io = { - iterSync, - iter, - copy, - SeekMode, - read, - readSync, - write, - writeSync, - readAll, - readAllInner, - readAllSync, - readAllSyncSized, - readAllInnerSized, - }; -})(this); + signal?.throwIfAborted(); + + // Handle truncated or extended files during read + if (cursor > size) { + // Read remaining and concat + return concatBuffers([buf, await readAllInner(r, options)]); + } else { + return TypedArrayPrototypeSubarray(buf, 0, cursor); + } +} + +export { + copy, + iter, + iterSync, + read, + readAll, + readAllInner, + readAllInnerSized, + readAllSync, + readAllSyncSized, + readSync, + SeekMode, + write, + writeSync, +}; diff --git a/runtime/js/13_buffer.js b/runtime/js/13_buffer.js index 180e9d26dcd530..be3aed390e45f0 100644 --- a/runtime/js/13_buffer.js +++ b/runtime/js/13_buffer.js @@ -3,257 +3,249 @@ // This code has been ported almost directly from Go's src/bytes/buffer.go // Copyright 2009 The Go Authors. All rights reserved. BSD license. // https://github.com/golang/go/blob/master/LICENSE -"use strict"; - -((window) => { - const { assert } = window.__bootstrap.infra; - const { - TypedArrayPrototypeSubarray, - TypedArrayPrototypeSlice, - TypedArrayPrototypeSet, - MathFloor, - MathMin, - PromiseResolve, - Uint8Array, - Error, - } = window.__bootstrap.primordials; - - // MIN_READ is the minimum ArrayBuffer size passed to a read call by - // buffer.ReadFrom. As long as the Buffer has at least MIN_READ bytes beyond - // what is required to hold the contents of r, readFrom() will not grow the - // underlying buffer. - const MIN_READ = 32 * 1024; - const MAX_SIZE = 2 ** 32 - 2; - - // `off` is the offset into `dst` where it will at which to begin writing values - // from `src`. - // Returns the number of bytes copied. - function copyBytes(src, dst, off = 0) { - const r = dst.byteLength - off; - if (src.byteLength > r) { - src = TypedArrayPrototypeSubarray(src, 0, r); - } - TypedArrayPrototypeSet(dst, src, off); - return src.byteLength; - } - class Buffer { - #buf = null; // contents are the bytes buf[off : len(buf)] - #off = 0; // read at buf[off], write at buf[buf.byteLength] +import { assert } from "internal:deno_web/00_infra.js"; +const primordials = globalThis.__bootstrap.primordials; +const { + TypedArrayPrototypeSubarray, + TypedArrayPrototypeSlice, + TypedArrayPrototypeSet, + MathFloor, + MathMin, + PromiseResolve, + Uint8Array, + Error, +} = primordials; + +// MIN_READ is the minimum ArrayBuffer size passed to a read call by +// buffer.ReadFrom. As long as the Buffer has at least MIN_READ bytes beyond +// what is required to hold the contents of r, readFrom() will not grow the +// underlying buffer. +const MIN_READ = 32 * 1024; +const MAX_SIZE = 2 ** 32 - 2; + +// `off` is the offset into `dst` where it will at which to begin writing values +// from `src`. +// Returns the number of bytes copied. +function copyBytes(src, dst, off = 0) { + const r = dst.byteLength - off; + if (src.byteLength > r) { + src = TypedArrayPrototypeSubarray(src, 0, r); + } + TypedArrayPrototypeSet(dst, src, off); + return src.byteLength; +} - constructor(ab) { - if (ab == null) { - this.#buf = new Uint8Array(0); - return; - } +class Buffer { + #buf = null; // contents are the bytes buf[off : len(buf)] + #off = 0; // read at buf[off], write at buf[buf.byteLength] - this.#buf = new Uint8Array(ab); + constructor(ab) { + if (ab == null) { + this.#buf = new Uint8Array(0); + return; } - bytes(options = { copy: true }) { - if (options.copy === false) { - return TypedArrayPrototypeSubarray(this.#buf, this.#off); - } - return TypedArrayPrototypeSlice(this.#buf, this.#off); - } + this.#buf = new Uint8Array(ab); + } - empty() { - return this.#buf.byteLength <= this.#off; + bytes(options = { copy: true }) { + if (options.copy === false) { + return TypedArrayPrototypeSubarray(this.#buf, this.#off); } + return TypedArrayPrototypeSlice(this.#buf, this.#off); + } - get length() { - return this.#buf.byteLength - this.#off; - } + empty() { + return this.#buf.byteLength <= this.#off; + } - get capacity() { - return this.#buf.buffer.byteLength; - } + get length() { + return this.#buf.byteLength - this.#off; + } - truncate(n) { - if (n === 0) { - this.reset(); - return; - } - if (n < 0 || n > this.length) { - throw Error("bytes.Buffer: truncation out of range"); - } - this.#reslice(this.#off + n); - } + get capacity() { + return this.#buf.buffer.byteLength; + } - reset() { - this.#reslice(0); - this.#off = 0; + truncate(n) { + if (n === 0) { + this.reset(); + return; } - - #tryGrowByReslice(n) { - const l = this.#buf.byteLength; - if (n <= this.capacity - l) { - this.#reslice(l + n); - return l; - } - return -1; + if (n < 0 || n > this.length) { + throw Error("bytes.Buffer: truncation out of range"); } + this.#reslice(this.#off + n); + } + + reset() { + this.#reslice(0); + this.#off = 0; + } - #reslice(len) { - assert(len <= this.#buf.buffer.byteLength); - this.#buf = new Uint8Array(this.#buf.buffer, 0, len); + #tryGrowByReslice(n) { + const l = this.#buf.byteLength; + if (n <= this.capacity - l) { + this.#reslice(l + n); + return l; } + return -1; + } + + #reslice(len) { + assert(len <= this.#buf.buffer.byteLength); + this.#buf = new Uint8Array(this.#buf.buffer, 0, len); + } - readSync(p) { - if (this.empty()) { - // Buffer is empty, reset to recover space. - this.reset(); - if (p.byteLength === 0) { - // this edge case is tested in 'bufferReadEmptyAtEOF' test - return 0; - } - return null; + readSync(p) { + if (this.empty()) { + // Buffer is empty, reset to recover space. + this.reset(); + if (p.byteLength === 0) { + // this edge case is tested in 'bufferReadEmptyAtEOF' test + return 0; } - const nread = copyBytes( - TypedArrayPrototypeSubarray(this.#buf, this.#off), - p, - ); - this.#off += nread; - return nread; - } + return null; + } + const nread = copyBytes( + TypedArrayPrototypeSubarray(this.#buf, this.#off), + p, + ); + this.#off += nread; + return nread; + } - read(p) { - const rr = this.readSync(p); - return PromiseResolve(rr); - } + read(p) { + const rr = this.readSync(p); + return PromiseResolve(rr); + } - writeSync(p) { - const m = this.#grow(p.byteLength); - return copyBytes(p, this.#buf, m); - } + writeSync(p) { + const m = this.#grow(p.byteLength); + return copyBytes(p, this.#buf, m); + } - write(p) { - const n = this.writeSync(p); - return PromiseResolve(n); - } + write(p) { + const n = this.writeSync(p); + return PromiseResolve(n); + } - #grow(n) { - const m = this.length; - // If buffer is empty, reset to recover space. - if (m === 0 && this.#off !== 0) { - this.reset(); - } - // Fast: Try to grow by means of a reslice. - const i = this.#tryGrowByReslice(n); - if (i >= 0) { - return i; - } - const c = this.capacity; - if (n <= MathFloor(c / 2) - m) { - // We can slide things down instead of allocating a new - // ArrayBuffer. We only need m+n <= c to slide, but - // we instead let capacity get twice as large so we - // don't spend all our time copying. - copyBytes(TypedArrayPrototypeSubarray(this.#buf, this.#off), this.#buf); - } else if (c + n > MAX_SIZE) { - throw new Error("The buffer cannot be grown beyond the maximum size."); - } else { - // Not enough space anywhere, we need to allocate. - const buf = new Uint8Array(MathMin(2 * c + n, MAX_SIZE)); - copyBytes(TypedArrayPrototypeSubarray(this.#buf, this.#off), buf); - this.#buf = buf; - } - // Restore this.#off and len(this.#buf). - this.#off = 0; - this.#reslice(MathMin(m + n, MAX_SIZE)); - return m; - } + #grow(n) { + const m = this.length; + // If buffer is empty, reset to recover space. + if (m === 0 && this.#off !== 0) { + this.reset(); + } + // Fast: Try to grow by means of a reslice. + const i = this.#tryGrowByReslice(n); + if (i >= 0) { + return i; + } + const c = this.capacity; + if (n <= MathFloor(c / 2) - m) { + // We can slide things down instead of allocating a new + // ArrayBuffer. We only need m+n <= c to slide, but + // we instead let capacity get twice as large so we + // don't spend all our time copying. + copyBytes(TypedArrayPrototypeSubarray(this.#buf, this.#off), this.#buf); + } else if (c + n > MAX_SIZE) { + throw new Error("The buffer cannot be grown beyond the maximum size."); + } else { + // Not enough space anywhere, we need to allocate. + const buf = new Uint8Array(MathMin(2 * c + n, MAX_SIZE)); + copyBytes(TypedArrayPrototypeSubarray(this.#buf, this.#off), buf); + this.#buf = buf; + } + // Restore this.#off and len(this.#buf). + this.#off = 0; + this.#reslice(MathMin(m + n, MAX_SIZE)); + return m; + } - grow(n) { - if (n < 0) { - throw Error("Buffer.grow: negative count"); - } - const m = this.#grow(n); - this.#reslice(m); + grow(n) { + if (n < 0) { + throw Error("Buffer.grow: negative count"); } + const m = this.#grow(n); + this.#reslice(m); + } - async readFrom(r) { - let n = 0; - const tmp = new Uint8Array(MIN_READ); - while (true) { - const shouldGrow = this.capacity - this.length < MIN_READ; - // read into tmp buffer if there's not enough room - // otherwise read directly into the internal buffer - const buf = shouldGrow - ? tmp - : new Uint8Array(this.#buf.buffer, this.length); - - const nread = await r.read(buf); - if (nread === null) { - return n; - } - - // write will grow if needed - if (shouldGrow) { - this.writeSync(TypedArrayPrototypeSubarray(buf, 0, nread)); - } else this.#reslice(this.length + nread); - - n += nread; + async readFrom(r) { + let n = 0; + const tmp = new Uint8Array(MIN_READ); + while (true) { + const shouldGrow = this.capacity - this.length < MIN_READ; + // read into tmp buffer if there's not enough room + // otherwise read directly into the internal buffer + const buf = shouldGrow + ? tmp + : new Uint8Array(this.#buf.buffer, this.length); + + const nread = await r.read(buf); + if (nread === null) { + return n; } - } - readFromSync(r) { - let n = 0; - const tmp = new Uint8Array(MIN_READ); - while (true) { - const shouldGrow = this.capacity - this.length < MIN_READ; - // read into tmp buffer if there's not enough room - // otherwise read directly into the internal buffer - const buf = shouldGrow - ? tmp - : new Uint8Array(this.#buf.buffer, this.length); - - const nread = r.readSync(buf); - if (nread === null) { - return n; - } - - // write will grow if needed - if (shouldGrow) { - this.writeSync(TypedArrayPrototypeSubarray(buf, 0, nread)); - } else this.#reslice(this.length + nread); - - n += nread; - } + // write will grow if needed + if (shouldGrow) { + this.writeSync(TypedArrayPrototypeSubarray(buf, 0, nread)); + } else this.#reslice(this.length + nread); + + n += nread; } } - async function readAll(r) { - const buf = new Buffer(); - await buf.readFrom(r); - return buf.bytes(); - } + readFromSync(r) { + let n = 0; + const tmp = new Uint8Array(MIN_READ); + while (true) { + const shouldGrow = this.capacity - this.length < MIN_READ; + // read into tmp buffer if there's not enough room + // otherwise read directly into the internal buffer + const buf = shouldGrow + ? tmp + : new Uint8Array(this.#buf.buffer, this.length); + + const nread = r.readSync(buf); + if (nread === null) { + return n; + } - function readAllSync(r) { - const buf = new Buffer(); - buf.readFromSync(r); - return buf.bytes(); - } + // write will grow if needed + if (shouldGrow) { + this.writeSync(TypedArrayPrototypeSubarray(buf, 0, nread)); + } else this.#reslice(this.length + nread); - async function writeAll(w, arr) { - let nwritten = 0; - while (nwritten < arr.length) { - nwritten += await w.write(TypedArrayPrototypeSubarray(arr, nwritten)); + n += nread; } } +} + +async function readAll(r) { + const buf = new Buffer(); + await buf.readFrom(r); + return buf.bytes(); +} + +function readAllSync(r) { + const buf = new Buffer(); + buf.readFromSync(r); + return buf.bytes(); +} + +async function writeAll(w, arr) { + let nwritten = 0; + while (nwritten < arr.length) { + nwritten += await w.write(TypedArrayPrototypeSubarray(arr, nwritten)); + } +} - function writeAllSync(w, arr) { - let nwritten = 0; - while (nwritten < arr.length) { - nwritten += w.writeSync(TypedArrayPrototypeSubarray(arr, nwritten)); - } +function writeAllSync(w, arr) { + let nwritten = 0; + while (nwritten < arr.length) { + nwritten += w.writeSync(TypedArrayPrototypeSubarray(arr, nwritten)); } +} - window.__bootstrap.buffer = { - writeAll, - writeAllSync, - readAll, - readAllSync, - Buffer, - }; -})(this); +export { Buffer, readAll, readAllSync, writeAll, writeAllSync }; diff --git a/runtime/js/30_fs.js b/runtime/js/30_fs.js index 87b2015fb92d7a..17b0b41ac675dd 100644 --- a/runtime/js/30_fs.js +++ b/runtime/js/30_fs.js @@ -1,385 +1,375 @@ // Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. -"use strict"; - -((window) => { - const core = window.Deno.core; - const ops = core.ops; - const { - Date, - DatePrototype, - MathTrunc, - ObjectPrototypeIsPrototypeOf, - SymbolAsyncIterator, - SymbolIterator, - Function, - ObjectEntries, - Uint32Array, - } = window.__bootstrap.primordials; - const { pathFromURL } = window.__bootstrap.util; - const build = window.__bootstrap.build.build; - - function chmodSync(path, mode) { - ops.op_chmod_sync(pathFromURL(path), mode); - } - - async function chmod(path, mode) { - await core.opAsync("op_chmod_async", pathFromURL(path), mode); - } - - function chownSync( - path, - uid, - gid, - ) { - ops.op_chown_sync(pathFromURL(path), uid, gid); - } - async function chown( - path, +const core = globalThis.Deno.core; +const ops = core.ops; +const primordials = globalThis.__bootstrap.primordials; +const { + Date, + DatePrototype, + MathTrunc, + ObjectPrototypeIsPrototypeOf, + SymbolAsyncIterator, + SymbolIterator, + Function, + ObjectEntries, + Uint32Array, +} = primordials; +import { pathFromURL } from "internal:runtime/js/06_util.js"; +import { build } from "internal:runtime/js/01_build.js"; + +function chmodSync(path, mode) { + ops.op_chmod_sync(pathFromURL(path), mode); +} + +async function chmod(path, mode) { + await core.opAsync("op_chmod_async", pathFromURL(path), mode); +} + +function chownSync( + path, + uid, + gid, +) { + ops.op_chown_sync(pathFromURL(path), uid, gid); +} + +async function chown( + path, + uid, + gid, +) { + await core.opAsync( + "op_chown_async", + pathFromURL(path), uid, gid, - ) { - await core.opAsync( - "op_chown_async", - pathFromURL(path), - uid, - gid, - ); - } - - function copyFileSync( - fromPath, - toPath, - ) { - ops.op_copy_file_sync( - pathFromURL(fromPath), - pathFromURL(toPath), - ); - } - - async function copyFile( - fromPath, - toPath, - ) { - await core.opAsync( - "op_copy_file_async", - pathFromURL(fromPath), - pathFromURL(toPath), - ); - } - - function cwd() { - return ops.op_cwd(); - } - - function chdir(directory) { - ops.op_chdir(pathFromURL(directory)); - } - - function makeTempDirSync(options = {}) { - return ops.op_make_temp_dir_sync(options); - } - - function makeTempDir(options = {}) { - return core.opAsync("op_make_temp_dir_async", options); - } - - function makeTempFileSync(options = {}) { - return ops.op_make_temp_file_sync(options); - } - - function makeTempFile(options = {}) { - return core.opAsync("op_make_temp_file_async", options); - } - - function mkdirArgs(path, options) { - const args = { path: pathFromURL(path), recursive: false }; - if (options != null) { - if (typeof options.recursive == "boolean") { - args.recursive = options.recursive; - } - if (options.mode) { - args.mode = options.mode; - } + ); +} + +function copyFileSync( + fromPath, + toPath, +) { + ops.op_copy_file_sync( + pathFromURL(fromPath), + pathFromURL(toPath), + ); +} + +async function copyFile( + fromPath, + toPath, +) { + await core.opAsync( + "op_copy_file_async", + pathFromURL(fromPath), + pathFromURL(toPath), + ); +} + +function cwd() { + return ops.op_cwd(); +} + +function chdir(directory) { + ops.op_chdir(pathFromURL(directory)); +} + +function makeTempDirSync(options = {}) { + return ops.op_make_temp_dir_sync(options); +} + +function makeTempDir(options = {}) { + return core.opAsync("op_make_temp_dir_async", options); +} + +function makeTempFileSync(options = {}) { + return ops.op_make_temp_file_sync(options); +} + +function makeTempFile(options = {}) { + return core.opAsync("op_make_temp_file_async", options); +} + +function mkdirArgs(path, options) { + const args = { path: pathFromURL(path), recursive: false }; + if (options != null) { + if (typeof options.recursive == "boolean") { + args.recursive = options.recursive; + } + if (options.mode) { + args.mode = options.mode; } - return args; - } - - function mkdirSync(path, options) { - ops.op_mkdir_sync(mkdirArgs(path, options)); - } - - async function mkdir( - path, - options, - ) { - await core.opAsync("op_mkdir_async", mkdirArgs(path, options)); - } - - function readDirSync(path) { - return ops.op_read_dir_sync(pathFromURL(path))[ - SymbolIterator - ](); - } - - function readDir(path) { - const array = core.opAsync( - "op_read_dir_async", - pathFromURL(path), - ); - return { - async *[SymbolAsyncIterator]() { - yield* await array; - }, - }; - } - - function readLinkSync(path) { - return ops.op_read_link_sync(pathFromURL(path)); - } - - function readLink(path) { - return core.opAsync("op_read_link_async", pathFromURL(path)); - } - - function realPathSync(path) { - return ops.op_realpath_sync(pathFromURL(path)); - } - - function realPath(path) { - return core.opAsync("op_realpath_async", pathFromURL(path)); - } - - function removeSync( - path, - options = {}, - ) { - ops.op_remove_sync( - pathFromURL(path), - !!options.recursive, - ); - } - - async function remove( - path, - options = {}, - ) { - await core.opAsync( - "op_remove_async", - pathFromURL(path), - !!options.recursive, - ); - } - - function renameSync(oldpath, newpath) { - ops.op_rename_sync( - pathFromURL(oldpath), - pathFromURL(newpath), - ); - } - - async function rename(oldpath, newpath) { - await core.opAsync( - "op_rename_async", - pathFromURL(oldpath), - pathFromURL(newpath), - ); } - - // Extract the FsStat object from the encoded buffer. - // See `runtime/ops/fs.rs` for the encoder. - // - // This is not a general purpose decoder. There are 4 types: - // - // 1. date - // offset += 4 - // 1/0 | extra padding | high u32 | low u32 - // if date[0] == 1, new Date(u64) else null - // - // 2. bool - // offset += 2 - // 1/0 | extra padding - // - // 3. u64 - // offset += 2 - // high u32 | low u32 - // - // 4. ?u64 converts a zero u64 value to JS null on Windows. - function createByteStruct(types) { - // types can be "date", "bool" or "u64". - // `?` prefix means optional on windows. - let offset = 0; - let str = - 'const unix = Deno.build.os === "darwin" || Deno.build.os === "linux"; return {'; - const typeEntries = ObjectEntries(types); - for (let i = 0; i < typeEntries.length; ++i) { - let { 0: name, 1: type } = typeEntries[i]; - - const optional = type.startsWith("?"); - if (optional) type = type.slice(1); - - if (type == "u64") { - if (!optional) { - str += `${name}: view[${offset}] + view[${offset + 1}] * 2**32,`; - } else { - str += `${name}: (unix ? (view[${offset}] + view[${ - offset + 1 - }] * 2**32) : (view[${offset}] + view[${ - offset + 1 - }] * 2**32) || null),`; - } - } else if (type == "date") { - str += `${name}: view[${offset}] === 0 ? null : new Date(view[${ - offset + 2 - }] + view[${offset + 3}] * 2**32),`; - offset += 2; + return args; +} + +function mkdirSync(path, options) { + ops.op_mkdir_sync(mkdirArgs(path, options)); +} + +async function mkdir( + path, + options, +) { + await core.opAsync("op_mkdir_async", mkdirArgs(path, options)); +} + +function readDirSync(path) { + return ops.op_read_dir_sync(pathFromURL(path))[ + SymbolIterator + ](); +} + +function readDir(path) { + const array = core.opAsync( + "op_read_dir_async", + pathFromURL(path), + ); + return { + async *[SymbolAsyncIterator]() { + yield* await array; + }, + }; +} + +function readLinkSync(path) { + return ops.op_read_link_sync(pathFromURL(path)); +} + +function readLink(path) { + return core.opAsync("op_read_link_async", pathFromURL(path)); +} + +function realPathSync(path) { + return ops.op_realpath_sync(pathFromURL(path)); +} + +function realPath(path) { + return core.opAsync("op_realpath_async", pathFromURL(path)); +} + +function removeSync( + path, + options = {}, +) { + ops.op_remove_sync( + pathFromURL(path), + !!options.recursive, + ); +} + +async function remove( + path, + options = {}, +) { + await core.opAsync( + "op_remove_async", + pathFromURL(path), + !!options.recursive, + ); +} + +function renameSync(oldpath, newpath) { + ops.op_rename_sync( + pathFromURL(oldpath), + pathFromURL(newpath), + ); +} + +async function rename(oldpath, newpath) { + await core.opAsync( + "op_rename_async", + pathFromURL(oldpath), + pathFromURL(newpath), + ); +} + +// Extract the FsStat object from the encoded buffer. +// See `runtime/ops/fs.rs` for the encoder. +// +// This is not a general purpose decoder. There are 4 types: +// +// 1. date +// offset += 4 +// 1/0 | extra padding | high u32 | low u32 +// if date[0] == 1, new Date(u64) else null +// +// 2. bool +// offset += 2 +// 1/0 | extra padding +// +// 3. u64 +// offset += 2 +// high u32 | low u32 +// +// 4. ?u64 converts a zero u64 value to JS null on Windows. +function createByteStruct(types) { + // types can be "date", "bool" or "u64". + // `?` prefix means optional on windows. + let offset = 0; + let str = + 'const unix = Deno.build.os === "darwin" || Deno.build.os === "linux"; return {'; + const typeEntries = ObjectEntries(types); + for (let i = 0; i < typeEntries.length; ++i) { + let { 0: name, 1: type } = typeEntries[i]; + + const optional = type.startsWith("?"); + if (optional) type = type.slice(1); + + if (type == "u64") { + if (!optional) { + str += `${name}: view[${offset}] + view[${offset + 1}] * 2**32,`; } else { - str += `${name}: !!(view[${offset}] + view[${offset + 1}] * 2**32),`; + str += `${name}: (unix ? (view[${offset}] + view[${ + offset + 1 + }] * 2**32) : (view[${offset}] + view[${ + offset + 1 + }] * 2**32) || null),`; } + } else if (type == "date") { + str += `${name}: view[${offset}] === 0 ? null : new Date(view[${ + offset + 2 + }] + view[${offset + 3}] * 2**32),`; offset += 2; + } else { + str += `${name}: !!(view[${offset}] + view[${offset + 1}] * 2**32),`; } - str += "};"; - // ...so you don't like eval huh? don't worry, it only executes during snapshot :) - return [new Function("view", str), new Uint32Array(offset)]; - } - - const { 0: statStruct, 1: statBuf } = createByteStruct({ - isFile: "bool", - isDirectory: "bool", - isSymlink: "bool", - size: "u64", - mtime: "date", - atime: "date", - birthtime: "date", - dev: "?u64", - ino: "?u64", - mode: "?u64", - nlink: "?u64", - uid: "?u64", - gid: "?u64", - rdev: "?u64", - blksize: "?u64", - blocks: "?u64", - }); - - function parseFileInfo(response) { - const unix = build.os === "darwin" || build.os === "linux"; - return { - isFile: response.isFile, - isDirectory: response.isDirectory, - isSymlink: response.isSymlink, - size: response.size, - mtime: response.mtimeSet !== null ? new Date(response.mtime) : null, - atime: response.atimeSet !== null ? new Date(response.atime) : null, - birthtime: response.birthtimeSet !== null - ? new Date(response.birthtime) - : null, - // Only non-null if on Unix - dev: unix ? response.dev : null, - ino: unix ? response.ino : null, - mode: unix ? response.mode : null, - nlink: unix ? response.nlink : null, - uid: unix ? response.uid : null, - gid: unix ? response.gid : null, - rdev: unix ? response.rdev : null, - blksize: unix ? response.blksize : null, - blocks: unix ? response.blocks : null, - }; - } - - function fstatSync(rid) { - ops.op_fstat_sync(rid, statBuf); - return statStruct(statBuf); - } - - async function fstat(rid) { - return parseFileInfo(await core.opAsync("op_fstat_async", rid)); - } - - async function lstat(path) { - const res = await core.opAsync("op_stat_async", { - path: pathFromURL(path), - lstat: true, - }); - return parseFileInfo(res); - } - - function lstatSync(path) { - ops.op_stat_sync( - pathFromURL(path), - true, - statBuf, - ); - return statStruct(statBuf); - } + offset += 2; + } + str += "};"; + // ...so you don't like eval huh? don't worry, it only executes during snapshot :) + return [new Function("view", str), new Uint32Array(offset)]; +} + +const { 0: statStruct, 1: statBuf } = createByteStruct({ + isFile: "bool", + isDirectory: "bool", + isSymlink: "bool", + size: "u64", + mtime: "date", + atime: "date", + birthtime: "date", + dev: "?u64", + ino: "?u64", + mode: "?u64", + nlink: "?u64", + uid: "?u64", + gid: "?u64", + rdev: "?u64", + blksize: "?u64", + blocks: "?u64", +}); + +function parseFileInfo(response) { + const unix = build.os === "darwin" || build.os === "linux"; + return { + isFile: response.isFile, + isDirectory: response.isDirectory, + isSymlink: response.isSymlink, + size: response.size, + mtime: response.mtimeSet !== null ? new Date(response.mtime) : null, + atime: response.atimeSet !== null ? new Date(response.atime) : null, + birthtime: response.birthtimeSet !== null + ? new Date(response.birthtime) + : null, + // Only non-null if on Unix + dev: unix ? response.dev : null, + ino: unix ? response.ino : null, + mode: unix ? response.mode : null, + nlink: unix ? response.nlink : null, + uid: unix ? response.uid : null, + gid: unix ? response.gid : null, + rdev: unix ? response.rdev : null, + blksize: unix ? response.blksize : null, + blocks: unix ? response.blocks : null, + }; +} - async function stat(path) { - const res = await core.opAsync("op_stat_async", { - path: pathFromURL(path), - lstat: false, - }); - return parseFileInfo(res); - } +function fstatSync(rid) { + ops.op_fstat_sync(rid, statBuf); + return statStruct(statBuf); +} - function statSync(path) { - ops.op_stat_sync( - pathFromURL(path), - false, - statBuf, - ); - return statStruct(statBuf); - } +async function fstat(rid) { + return parseFileInfo(await core.opAsync("op_fstat_async", rid)); +} - function coerceLen(len) { - if (len == null || len < 0) { - return 0; - } +async function lstat(path) { + const res = await core.opAsync("op_stat_async", { + path: pathFromURL(path), + lstat: true, + }); + return parseFileInfo(res); +} + +function lstatSync(path) { + ops.op_stat_sync( + pathFromURL(path), + true, + statBuf, + ); + return statStruct(statBuf); +} + +async function stat(path) { + const res = await core.opAsync("op_stat_async", { + path: pathFromURL(path), + lstat: false, + }); + return parseFileInfo(res); +} - return len; - } +function statSync(path) { + ops.op_stat_sync( + pathFromURL(path), + false, + statBuf, + ); + return statStruct(statBuf); +} - function ftruncateSync(rid, len) { - ops.op_ftruncate_sync(rid, coerceLen(len)); +function coerceLen(len) { + if (len == null || len < 0) { + return 0; } - async function ftruncate(rid, len) { - await core.opAsync("op_ftruncate_async", rid, coerceLen(len)); - } + return len; +} - function truncateSync(path, len) { - ops.op_truncate_sync(path, coerceLen(len)); - } +function ftruncateSync(rid, len) { + ops.op_ftruncate_sync(rid, coerceLen(len)); +} - async function truncate(path, len) { - await core.opAsync("op_truncate_async", path, coerceLen(len)); - } +async function ftruncate(rid, len) { + await core.opAsync("op_ftruncate_async", rid, coerceLen(len)); +} - function umask(mask) { - return ops.op_umask(mask); - } +function truncateSync(path, len) { + ops.op_truncate_sync(path, coerceLen(len)); +} - function linkSync(oldpath, newpath) { - ops.op_link_sync(oldpath, newpath); - } +async function truncate(path, len) { + await core.opAsync("op_truncate_async", path, coerceLen(len)); +} - async function link(oldpath, newpath) { - await core.opAsync("op_link_async", oldpath, newpath); - } +function umask(mask) { + return ops.op_umask(mask); +} - function toUnixTimeFromEpoch(value) { - if (ObjectPrototypeIsPrototypeOf(DatePrototype, value)) { - const time = value.valueOf(); - const seconds = MathTrunc(time / 1e3); - const nanoseconds = MathTrunc(time - (seconds * 1e3)) * 1e6; +function linkSync(oldpath, newpath) { + ops.op_link_sync(oldpath, newpath); +} - return [ - seconds, - nanoseconds, - ]; - } +async function link(oldpath, newpath) { + await core.opAsync("op_link_async", oldpath, newpath); +} - const seconds = value; - const nanoseconds = 0; +function toUnixTimeFromEpoch(value) { + if (ObjectPrototypeIsPrototypeOf(DatePrototype, value)) { + const time = value.valueOf(); + const seconds = MathTrunc(time / 1e3); + const nanoseconds = MathTrunc(time - (seconds * 1e3)) * 1e6; return [ seconds, @@ -387,174 +377,182 @@ ]; } - function futimeSync( - rid, - atime, - mtime, - ) { - const { 0: atimeSec, 1: atimeNsec } = toUnixTimeFromEpoch(atime); - const { 0: mtimeSec, 1: mtimeNsec } = toUnixTimeFromEpoch(mtime); - ops.op_futime_sync(rid, atimeSec, atimeNsec, mtimeSec, mtimeNsec); - } - - async function futime( + const seconds = value; + const nanoseconds = 0; + + return [ + seconds, + nanoseconds, + ]; +} + +function futimeSync( + rid, + atime, + mtime, +) { + const { 0: atimeSec, 1: atimeNsec } = toUnixTimeFromEpoch(atime); + const { 0: mtimeSec, 1: mtimeNsec } = toUnixTimeFromEpoch(mtime); + ops.op_futime_sync(rid, atimeSec, atimeNsec, mtimeSec, mtimeNsec); +} + +async function futime( + rid, + atime, + mtime, +) { + const { 0: atimeSec, 1: atimeNsec } = toUnixTimeFromEpoch(atime); + const { 0: mtimeSec, 1: mtimeNsec } = toUnixTimeFromEpoch(mtime); + await core.opAsync( + "op_futime_async", rid, - atime, - mtime, - ) { - const { 0: atimeSec, 1: atimeNsec } = toUnixTimeFromEpoch(atime); - const { 0: mtimeSec, 1: mtimeNsec } = toUnixTimeFromEpoch(mtime); - await core.opAsync( - "op_futime_async", - rid, - atimeSec, - atimeNsec, - mtimeSec, - mtimeNsec, - ); - } - - function utimeSync( - path, - atime, - mtime, - ) { - const { 0: atimeSec, 1: atimeNsec } = toUnixTimeFromEpoch(atime); - const { 0: mtimeSec, 1: mtimeNsec } = toUnixTimeFromEpoch(mtime); - ops.op_utime_sync( - pathFromURL(path), - atimeSec, - atimeNsec, - mtimeSec, - mtimeNsec, - ); - } - - async function utime( - path, - atime, - mtime, - ) { - const { 0: atimeSec, 1: atimeNsec } = toUnixTimeFromEpoch(atime); - const { 0: mtimeSec, 1: mtimeNsec } = toUnixTimeFromEpoch(mtime); - await core.opAsync( - "op_utime_async", - pathFromURL(path), - atimeSec, - atimeNsec, - mtimeSec, - mtimeNsec, - ); - } - - function symlinkSync( - oldpath, - newpath, - options, - ) { - ops.op_symlink_sync( - pathFromURL(oldpath), - pathFromURL(newpath), - options?.type, - ); - } - - async function symlink( - oldpath, - newpath, - options, - ) { - await core.opAsync( - "op_symlink_async", - pathFromURL(oldpath), - pathFromURL(newpath), - options?.type, - ); - } - - function fdatasyncSync(rid) { - ops.op_fdatasync_sync(rid); - } - - async function fdatasync(rid) { - await core.opAsync("op_fdatasync_async", rid); - } - - function fsyncSync(rid) { - ops.op_fsync_sync(rid); - } - - async function fsync(rid) { - await core.opAsync("op_fsync_async", rid); - } - - function flockSync(rid, exclusive) { - ops.op_flock_sync(rid, exclusive === true); - } - - async function flock(rid, exclusive) { - await core.opAsync("op_flock_async", rid, exclusive === true); - } - - function funlockSync(rid) { - ops.op_funlock_sync(rid); - } - - async function funlock(rid) { - await core.opAsync("op_funlock_async", rid); - } - - window.__bootstrap.fs = { - cwd, - chdir, - chmodSync, - chmod, - chown, - chownSync, - copyFile, - copyFileSync, - makeTempFile, - makeTempDir, - makeTempFileSync, - makeTempDirSync, - mkdir, - mkdirSync, - readDir, - readDirSync, - readLinkSync, - readLink, - realPathSync, - realPath, - remove, - removeSync, - renameSync, - rename, - lstat, - lstatSync, - stat, - statSync, - ftruncate, - ftruncateSync, - truncate, - truncateSync, - umask, - link, - linkSync, - fstatSync, - fstat, - futime, - futimeSync, - utime, - utimeSync, - symlink, - symlinkSync, - fdatasync, - fdatasyncSync, - fsync, - fsyncSync, - flock, - flockSync, - funlock, - funlockSync, - }; -})(this); + atimeSec, + atimeNsec, + mtimeSec, + mtimeNsec, + ); +} + +function utimeSync( + path, + atime, + mtime, +) { + const { 0: atimeSec, 1: atimeNsec } = toUnixTimeFromEpoch(atime); + const { 0: mtimeSec, 1: mtimeNsec } = toUnixTimeFromEpoch(mtime); + ops.op_utime_sync( + pathFromURL(path), + atimeSec, + atimeNsec, + mtimeSec, + mtimeNsec, + ); +} + +async function utime( + path, + atime, + mtime, +) { + const { 0: atimeSec, 1: atimeNsec } = toUnixTimeFromEpoch(atime); + const { 0: mtimeSec, 1: mtimeNsec } = toUnixTimeFromEpoch(mtime); + await core.opAsync( + "op_utime_async", + pathFromURL(path), + atimeSec, + atimeNsec, + mtimeSec, + mtimeNsec, + ); +} + +function symlinkSync( + oldpath, + newpath, + options, +) { + ops.op_symlink_sync( + pathFromURL(oldpath), + pathFromURL(newpath), + options?.type, + ); +} + +async function symlink( + oldpath, + newpath, + options, +) { + await core.opAsync( + "op_symlink_async", + pathFromURL(oldpath), + pathFromURL(newpath), + options?.type, + ); +} + +function fdatasyncSync(rid) { + ops.op_fdatasync_sync(rid); +} + +async function fdatasync(rid) { + await core.opAsync("op_fdatasync_async", rid); +} + +function fsyncSync(rid) { + ops.op_fsync_sync(rid); +} + +async function fsync(rid) { + await core.opAsync("op_fsync_async", rid); +} + +function flockSync(rid, exclusive) { + ops.op_flock_sync(rid, exclusive === true); +} + +async function flock(rid, exclusive) { + await core.opAsync("op_flock_async", rid, exclusive === true); +} + +function funlockSync(rid) { + ops.op_funlock_sync(rid); +} + +async function funlock(rid) { + await core.opAsync("op_funlock_async", rid); +} + +export { + chdir, + chmod, + chmodSync, + chown, + chownSync, + copyFile, + copyFileSync, + cwd, + fdatasync, + fdatasyncSync, + flock, + flockSync, + fstat, + fstatSync, + fsync, + fsyncSync, + ftruncate, + ftruncateSync, + funlock, + funlockSync, + futime, + futimeSync, + link, + linkSync, + lstat, + lstatSync, + makeTempDir, + makeTempDirSync, + makeTempFile, + makeTempFileSync, + mkdir, + mkdirSync, + readDir, + readDirSync, + readLink, + readLinkSync, + realPath, + realPathSync, + remove, + removeSync, + rename, + renameSync, + stat, + statSync, + symlink, + symlinkSync, + truncate, + truncateSync, + umask, + utime, + utimeSync, +}; diff --git a/runtime/js/30_os.js b/runtime/js/30_os.js index 8069c3a34fa0a7..63e3748d35f435 100644 --- a/runtime/js/30_os.js +++ b/runtime/js/30_os.js @@ -1,123 +1,120 @@ // Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. -"use strict"; - -((window) => { - const core = window.Deno.core; - const ops = core.ops; - const { Event } = window.__bootstrap.event; - const { EventTarget } = window.__bootstrap.eventTarget; - const { - Error, - SymbolFor, - } = window.__bootstrap.primordials; - - const windowDispatchEvent = EventTarget.prototype.dispatchEvent.bind(window); - - function loadavg() { - return ops.op_loadavg(); - } - - function hostname() { - return ops.op_hostname(); - } - - function osRelease() { - return ops.op_os_release(); - } - - function createOsUptime(opFn) { - return function osUptime() { - return opFn(); - }; - } - - function systemMemoryInfo() { - return ops.op_system_memory_info(); - } - - function networkInterfaces() { - return ops.op_network_interfaces(); - } - - function gid() { - return ops.op_gid(); - } - - function uid() { - return ops.op_uid(); - } - - // This is an internal only method used by the test harness to override the - // behavior of exit when the exit sanitizer is enabled. - let exitHandler = null; - function setExitHandler(fn) { - exitHandler = fn; - } - - function exit(code) { - // Set exit code first so unload event listeners can override it. - if (typeof code === "number") { - ops.op_set_exit_code(code); - } else { - code = 0; - } - - // Dispatches `unload` only when it's not dispatched yet. - if (!window[SymbolFor("isUnloadDispatched")]) { - // Invokes the `unload` hooks before exiting - // ref: https://github.com/denoland/deno/issues/3603 - windowDispatchEvent(new Event("unload")); - } - - if (exitHandler) { - exitHandler(code); - return; - } - - ops.op_exit(); - throw new Error("Code not reachable"); - } - - function setEnv(key, value) { - ops.op_set_env(key, value); - } - function getEnv(key) { - return ops.op_get_env(key) ?? undefined; +const core = globalThis.Deno.core; +const ops = core.ops; +import { Event, EventTarget } from "internal:deno_web/02_event.js"; +const primordials = globalThis.__bootstrap.primordials; +const { + Error, + SymbolFor, +} = primordials; + +const windowDispatchEvent = EventTarget.prototype.dispatchEvent.bind( + globalThis, +); + +function loadavg() { + return ops.op_loadavg(); +} + +function hostname() { + return ops.op_hostname(); +} + +function osRelease() { + return ops.op_os_release(); +} + +function osUptime() { + return ops.op_os_uptime(); +} + +function systemMemoryInfo() { + return ops.op_system_memory_info(); +} + +function networkInterfaces() { + return ops.op_network_interfaces(); +} + +function gid() { + return ops.op_gid(); +} + +function uid() { + return ops.op_uid(); +} + +// This is an internal only method used by the test harness to override the +// behavior of exit when the exit sanitizer is enabled. +let exitHandler = null; +function setExitHandler(fn) { + exitHandler = fn; +} + +function exit(code) { + // Set exit code first so unload event listeners can override it. + if (typeof code === "number") { + ops.op_set_exit_code(code); + } else { + code = 0; } - function deleteEnv(key) { - ops.op_delete_env(key); + // Dispatches `unload` only when it's not dispatched yet. + if (!globalThis[SymbolFor("isUnloadDispatched")]) { + // Invokes the `unload` hooks before exiting + // ref: https://github.com/denoland/deno/issues/3603 + windowDispatchEvent(new Event("unload")); } - const env = { - get: getEnv, - toObject() { - return ops.op_env(); - }, - set: setEnv, - has(key) { - return getEnv(key) !== undefined; - }, - delete: deleteEnv, - }; - - function execPath() { - return ops.op_exec_path(); + if (exitHandler) { + exitHandler(code); + return; } - window.__bootstrap.os = { - env, - execPath, - exit, - gid, - hostname, - loadavg, - networkInterfaces, - osRelease, - createOsUptime, - setExitHandler, - systemMemoryInfo, - uid, - }; -})(this); + ops.op_exit(); + throw new Error("Code not reachable"); +} + +function setEnv(key, value) { + ops.op_set_env(key, value); +} + +function getEnv(key) { + return ops.op_get_env(key) ?? undefined; +} + +function deleteEnv(key) { + ops.op_delete_env(key); +} + +const env = { + get: getEnv, + toObject() { + return ops.op_env(); + }, + set: setEnv, + has(key) { + return getEnv(key) !== undefined; + }, + delete: deleteEnv, +}; + +function execPath() { + return ops.op_exec_path(); +} + +export { + env, + execPath, + exit, + gid, + hostname, + loadavg, + networkInterfaces, + osRelease, + osUptime, + setExitHandler, + systemMemoryInfo, + uid, +}; diff --git a/runtime/js/40_diagnostics.js b/runtime/js/40_diagnostics.js index 6939b5204247a8..669a54263c8b61 100644 --- a/runtime/js/40_diagnostics.js +++ b/runtime/js/40_diagnostics.js @@ -3,22 +3,17 @@ // Diagnostic provides an abstraction for advice/errors received from a // compiler, which is strongly influenced by the format of TypeScript // diagnostics. -"use strict"; -((window) => { - const DiagnosticCategory = { - 0: "Warning", - 1: "Error", - 2: "Suggestion", - 3: "Message", +const DiagnosticCategory = { + 0: "Warning", + 1: "Error", + 2: "Suggestion", + 3: "Message", - Warning: 0, - Error: 1, - Suggestion: 2, - Message: 3, - }; + Warning: 0, + Error: 1, + Suggestion: 2, + Message: 3, +}; - window.__bootstrap.diagnostics = { - DiagnosticCategory, - }; -})(this); +export { DiagnosticCategory }; diff --git a/runtime/js/40_files.js b/runtime/js/40_files.js index 4471610ccdae0c..f380ca7ebe2385 100644 --- a/runtime/js/40_files.js +++ b/runtime/js/40_files.js @@ -1,293 +1,299 @@ // Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. -"use strict"; - -((window) => { - const core = window.Deno.core; - const ops = core.ops; - const { read, readSync, write, writeSync } = window.__bootstrap.io; - const { ftruncate, ftruncateSync, fstat, fstatSync } = window.__bootstrap.fs; - const { pathFromURL } = window.__bootstrap.util; - const { readableStreamForRid, writableStreamForRid } = - window.__bootstrap.streams; - const { - ArrayPrototypeFilter, - Error, - ObjectValues, - } = window.__bootstrap.primordials; - - function seekSync( - rid, - offset, - whence, - ) { - return ops.op_seek_sync({ rid, offset, whence }); - } - - function seek( - rid, - offset, - whence, - ) { - return core.opAsync("op_seek_async", { rid, offset, whence }); - } - function openSync( - path, +const core = globalThis.Deno.core; +const ops = core.ops; +import { read, readSync, write, writeSync } from "internal:runtime/js/12_io.js"; +import { + fstat, + fstatSync, + ftruncate, + ftruncateSync, +} from "internal:runtime/js/30_fs.js"; +import { pathFromURL } from "internal:runtime/js/06_util.js"; +import { + readableStreamForRid, + writableStreamForRid, +} from "internal:deno_web/06_streams.js"; +const primordials = globalThis.__bootstrap.primordials; +const { + ArrayPrototypeFilter, + Error, + ObjectValues, +} = primordials; + +function seekSync( + rid, + offset, + whence, +) { + return ops.op_seek_sync({ rid, offset, whence }); +} + +function seek( + rid, + offset, + whence, +) { + return core.opAsync("op_seek_async", { rid, offset, whence }); +} + +function openSync( + path, + options, +) { + if (options) checkOpenOptions(options); + const mode = options?.mode; + const rid = ops.op_open_sync( + pathFromURL(path), options, - ) { - if (options) checkOpenOptions(options); - const mode = options?.mode; - const rid = ops.op_open_sync( - pathFromURL(path), - options, - mode, - ); - - return new FsFile(rid); - } - - async function open( - path, + mode, + ); + + return new FsFile(rid); +} + +async function open( + path, + options, +) { + if (options) checkOpenOptions(options); + const mode = options?.mode; + const rid = await core.opAsync( + "op_open_async", + pathFromURL(path), options, - ) { - if (options) checkOpenOptions(options); - const mode = options?.mode; - const rid = await core.opAsync( - "op_open_async", - pathFromURL(path), - options, - mode, - ); - - return new FsFile(rid); + mode, + ); + + return new FsFile(rid); +} + +function createSync(path) { + return openSync(path, { + read: true, + write: true, + truncate: true, + create: true, + }); +} + +function create(path) { + return open(path, { + read: true, + write: true, + truncate: true, + create: true, + }); +} + +class FsFile { + #rid = 0; + + #readable; + #writable; + + constructor(rid) { + this.#rid = rid; } - function createSync(path) { - return openSync(path, { - read: true, - write: true, - truncate: true, - create: true, - }); + get rid() { + return this.#rid; } - function create(path) { - return open(path, { - read: true, - write: true, - truncate: true, - create: true, - }); + write(p) { + return write(this.rid, p); } - class FsFile { - #rid = 0; - - #readable; - #writable; - - constructor(rid) { - this.#rid = rid; - } - - get rid() { - return this.#rid; - } - - write(p) { - return write(this.rid, p); - } - - writeSync(p) { - return writeSync(this.rid, p); - } + writeSync(p) { + return writeSync(this.rid, p); + } - truncate(len) { - return ftruncate(this.rid, len); - } + truncate(len) { + return ftruncate(this.rid, len); + } - truncateSync(len) { - return ftruncateSync(this.rid, len); - } + truncateSync(len) { + return ftruncateSync(this.rid, len); + } - read(p) { - return read(this.rid, p); - } + read(p) { + return read(this.rid, p); + } - readSync(p) { - return readSync(this.rid, p); - } + readSync(p) { + return readSync(this.rid, p); + } - seek(offset, whence) { - return seek(this.rid, offset, whence); - } + seek(offset, whence) { + return seek(this.rid, offset, whence); + } - seekSync(offset, whence) { - return seekSync(this.rid, offset, whence); - } + seekSync(offset, whence) { + return seekSync(this.rid, offset, whence); + } - stat() { - return fstat(this.rid); - } + stat() { + return fstat(this.rid); + } - statSync() { - return fstatSync(this.rid); - } + statSync() { + return fstatSync(this.rid); + } - close() { - core.close(this.rid); - } + close() { + core.close(this.rid); + } - get readable() { - if (this.#readable === undefined) { - this.#readable = readableStreamForRid(this.rid); - } - return this.#readable; + get readable() { + if (this.#readable === undefined) { + this.#readable = readableStreamForRid(this.rid); } + return this.#readable; + } - get writable() { - if (this.#writable === undefined) { - this.#writable = writableStreamForRid(this.rid); - } - return this.#writable; + get writable() { + if (this.#writable === undefined) { + this.#writable = writableStreamForRid(this.rid); } + return this.#writable; } +} - class Stdin { - #readable; +class Stdin { + #readable; - constructor() { - } - - get rid() { - return 0; - } + constructor() { + } - read(p) { - return read(this.rid, p); - } + get rid() { + return 0; + } - readSync(p) { - return readSync(this.rid, p); - } + read(p) { + return read(this.rid, p); + } - close() { - core.close(this.rid); - } + readSync(p) { + return readSync(this.rid, p); + } - get readable() { - if (this.#readable === undefined) { - this.#readable = readableStreamForRid(this.rid); - } - return this.#readable; - } + close() { + core.close(this.rid); + } - setRaw(mode, options = {}) { - const cbreak = !!(options.cbreak ?? false); - ops.op_stdin_set_raw(mode, cbreak); + get readable() { + if (this.#readable === undefined) { + this.#readable = readableStreamForRid(this.rid); } + return this.#readable; } - class Stdout { - #writable; - - constructor() { - } + setRaw(mode, options = {}) { + const cbreak = !!(options.cbreak ?? false); + ops.op_stdin_set_raw(mode, cbreak); + } +} - get rid() { - return 1; - } +class Stdout { + #writable; - write(p) { - return write(this.rid, p); - } + constructor() { + } - writeSync(p) { - return writeSync(this.rid, p); - } + get rid() { + return 1; + } - close() { - core.close(this.rid); - } + write(p) { + return write(this.rid, p); + } - get writable() { - if (this.#writable === undefined) { - this.#writable = writableStreamForRid(this.rid); - } - return this.#writable; - } + writeSync(p) { + return writeSync(this.rid, p); } - class Stderr { - #writable; + close() { + core.close(this.rid); + } - constructor() { + get writable() { + if (this.#writable === undefined) { + this.#writable = writableStreamForRid(this.rid); } + return this.#writable; + } +} - get rid() { - return 2; - } +class Stderr { + #writable; - write(p) { - return write(this.rid, p); - } + constructor() { + } - writeSync(p) { - return writeSync(this.rid, p); - } + get rid() { + return 2; + } - close() { - core.close(this.rid); - } + write(p) { + return write(this.rid, p); + } - get writable() { - if (this.#writable === undefined) { - this.#writable = writableStreamForRid(this.rid); - } - return this.#writable; - } + writeSync(p) { + return writeSync(this.rid, p); } - const stdin = new Stdin(); - const stdout = new Stdout(); - const stderr = new Stderr(); + close() { + core.close(this.rid); + } - function checkOpenOptions(options) { - if ( - ArrayPrototypeFilter( - ObjectValues(options), - (val) => val === true, - ).length === 0 - ) { - throw new Error("OpenOptions requires at least one option to be true"); + get writable() { + if (this.#writable === undefined) { + this.#writable = writableStreamForRid(this.rid); } + return this.#writable; + } +} + +const stdin = new Stdin(); +const stdout = new Stdout(); +const stderr = new Stderr(); + +function checkOpenOptions(options) { + if ( + ArrayPrototypeFilter( + ObjectValues(options), + (val) => val === true, + ).length === 0 + ) { + throw new Error("OpenOptions requires at least one option to be true"); + } - if (options.truncate && !options.write) { - throw new Error("'truncate' option requires 'write' option"); - } + if (options.truncate && !options.write) { + throw new Error("'truncate' option requires 'write' option"); + } - const createOrCreateNewWithoutWriteOrAppend = - (options.create || options.createNew) && - !(options.write || options.append); + const createOrCreateNewWithoutWriteOrAppend = + (options.create || options.createNew) && + !(options.write || options.append); - if (createOrCreateNewWithoutWriteOrAppend) { - throw new Error( - "'create' or 'createNew' options require 'write' or 'append' option", - ); - } + if (createOrCreateNewWithoutWriteOrAppend) { + throw new Error( + "'create' or 'createNew' options require 'write' or 'append' option", + ); } - - window.__bootstrap.files = { - stdin, - stdout, - stderr, - File: FsFile, - FsFile, - create, - createSync, - open, - openSync, - seek, - seekSync, - }; -})(this); +} + +const File = FsFile; +export { + create, + createSync, + File, + FsFile, + open, + openSync, + seek, + seekSync, + stderr, + stdin, + stdout, +}; diff --git a/runtime/js/40_fs_events.js b/runtime/js/40_fs_events.js index b410e229909e02..4c2f5fc9a5920d 100644 --- a/runtime/js/40_fs_events.js +++ b/runtime/js/40_fs_events.js @@ -1,70 +1,63 @@ // Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. -"use strict"; -((window) => { - const core = window.Deno.core; - const ops = core.ops; - const { BadResourcePrototype, InterruptedPrototype } = core; - const { - ArrayIsArray, - ObjectPrototypeIsPrototypeOf, - PromiseResolve, - SymbolAsyncIterator, - } = window.__bootstrap.primordials; - class FsWatcher { - #rid = 0; - - constructor(paths, options) { - const { recursive } = options; - this.#rid = ops.op_fs_events_open({ recursive, paths }); - } +const core = globalThis.Deno.core; +const { BadResourcePrototype, InterruptedPrototype, ops } = core; +const primordials = globalThis.__bootstrap.primordials; +const { + ArrayIsArray, + ObjectPrototypeIsPrototypeOf, + PromiseResolve, + SymbolAsyncIterator, +} = primordials; +class FsWatcher { + #rid = 0; + + constructor(paths, options) { + const { recursive } = options; + this.#rid = ops.op_fs_events_open({ recursive, paths }); + } - get rid() { - return this.#rid; - } + get rid() { + return this.#rid; + } - async next() { - try { - const value = await core.opAsync("op_fs_events_poll", this.rid); - return value - ? { value, done: false } - : { value: undefined, done: true }; - } catch (error) { - if (ObjectPrototypeIsPrototypeOf(BadResourcePrototype, error)) { - return { value: undefined, done: true }; - } else if ( - ObjectPrototypeIsPrototypeOf(InterruptedPrototype, error) - ) { - return { value: undefined, done: true }; - } - throw error; + async next() { + try { + const value = await core.opAsync("op_fs_events_poll", this.rid); + return value ? { value, done: false } : { value: undefined, done: true }; + } catch (error) { + if (ObjectPrototypeIsPrototypeOf(BadResourcePrototype, error)) { + return { value: undefined, done: true }; + } else if ( + ObjectPrototypeIsPrototypeOf(InterruptedPrototype, error) + ) { + return { value: undefined, done: true }; } + throw error; } + } - // TODO(kt3k): This is deprecated. Will be removed in v2.0. - // See https://github.com/denoland/deno/issues/10577 for details - return(value) { - core.close(this.rid); - return PromiseResolve({ value, done: true }); - } - - close() { - core.close(this.rid); - } + // TODO(kt3k): This is deprecated. Will be removed in v2.0. + // See https://github.com/denoland/deno/issues/10577 for details + return(value) { + core.close(this.rid); + return PromiseResolve({ value, done: true }); + } - [SymbolAsyncIterator]() { - return this; - } + close() { + core.close(this.rid); } - function watchFs( - paths, - options = { recursive: true }, - ) { - return new FsWatcher(ArrayIsArray(paths) ? paths : [paths], options); + [SymbolAsyncIterator]() { + return this; } +} + +function watchFs( + paths, + options = { recursive: true }, +) { + return new FsWatcher(ArrayIsArray(paths) ? paths : [paths], options); +} - window.__bootstrap.fsEvents = { - watchFs, - }; -})(this); +export { watchFs }; diff --git a/runtime/js/40_http.js b/runtime/js/40_http.js index 22288b5d5452c8..026234f79e92a2 100644 --- a/runtime/js/40_http.js +++ b/runtime/js/40_http.js @@ -1,15 +1,11 @@ // Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. -"use strict"; +const core = globalThis.Deno.core; +const ops = core.ops; +import { HttpConn } from "internal:deno_http/01_http.js"; -((window) => { - const core = window.__bootstrap.core; - const ops = core.ops; - const { HttpConn } = window.__bootstrap.http; +function serveHttp(conn) { + const rid = ops.op_http_start(conn.rid); + return new HttpConn(rid, conn.remoteAddr, conn.localAddr); +} - function serveHttp(conn) { - const rid = ops.op_http_start(conn.rid); - return new HttpConn(rid, conn.remoteAddr, conn.localAddr); - } - - window.__bootstrap.http.serveHttp = serveHttp; -})(globalThis); +export { serveHttp }; diff --git a/runtime/js/40_process.js b/runtime/js/40_process.js index 5ad24c0943cadd..a949e48ed67a36 100644 --- a/runtime/js/40_process.js +++ b/runtime/js/40_process.js @@ -1,139 +1,133 @@ // Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. -"use strict"; - -((window) => { - const core = window.Deno.core; - const ops = core.ops; - const { FsFile } = window.__bootstrap.files; - const { readAll } = window.__bootstrap.io; - const { pathFromURL } = window.__bootstrap.util; - const { assert } = window.__bootstrap.infra; - const { - ArrayPrototypeMap, - ArrayPrototypeSlice, - TypeError, - ObjectEntries, - SafeArrayIterator, - String, - } = window.__bootstrap.primordials; - - function opKill(pid, signo, apiName) { - ops.op_kill(pid, signo, apiName); - } - - function kill(pid, signo = "SIGTERM") { - opKill(pid, signo, "Deno.kill()"); - } - - function opRunStatus(rid) { - return core.opAsync("op_run_status", rid); - } - function opRun(request) { - assert(request.cmd.length > 0); - return ops.op_run(request); +const core = globalThis.Deno.core; +const ops = core.ops; +import { FsFile } from "internal:runtime/js/40_files.js"; +import { readAll } from "internal:runtime/js/12_io.js"; +import { pathFromURL } from "internal:runtime/js/06_util.js"; +import { assert } from "internal:deno_web/00_infra.js"; +const primordials = globalThis.__bootstrap.primordials; +const { + ArrayPrototypeMap, + ArrayPrototypeSlice, + TypeError, + ObjectEntries, + SafeArrayIterator, + String, +} = primordials; + +function opKill(pid, signo, apiName) { + ops.op_kill(pid, signo, apiName); +} + +function kill(pid, signo = "SIGTERM") { + opKill(pid, signo, "Deno.kill()"); +} + +function opRunStatus(rid) { + return core.opAsync("op_run_status", rid); +} + +function opRun(request) { + assert(request.cmd.length > 0); + return ops.op_run(request); +} + +async function runStatus(rid) { + const res = await opRunStatus(rid); + + if (res.gotSignal) { + const signal = res.exitSignal; + return { success: false, code: 128 + signal, signal }; + } else if (res.exitCode != 0) { + return { success: false, code: res.exitCode }; + } else { + return { success: true, code: 0 }; } +} - async function runStatus(rid) { - const res = await opRunStatus(rid); +class Process { + constructor(res) { + this.rid = res.rid; + this.pid = res.pid; - if (res.gotSignal) { - const signal = res.exitSignal; - return { success: false, code: 128 + signal, signal }; - } else if (res.exitCode != 0) { - return { success: false, code: res.exitCode }; - } else { - return { success: true, code: 0 }; + if (res.stdinRid && res.stdinRid > 0) { + this.stdin = new FsFile(res.stdinRid); } - } - - class Process { - constructor(res) { - this.rid = res.rid; - this.pid = res.pid; - - if (res.stdinRid && res.stdinRid > 0) { - this.stdin = new FsFile(res.stdinRid); - } - - if (res.stdoutRid && res.stdoutRid > 0) { - this.stdout = new FsFile(res.stdoutRid); - } - if (res.stderrRid && res.stderrRid > 0) { - this.stderr = new FsFile(res.stderrRid); - } + if (res.stdoutRid && res.stdoutRid > 0) { + this.stdout = new FsFile(res.stdoutRid); } - status() { - return runStatus(this.rid); + if (res.stderrRid && res.stderrRid > 0) { + this.stderr = new FsFile(res.stderrRid); } + } - async output() { - if (!this.stdout) { - throw new TypeError("stdout was not piped"); - } - try { - return await readAll(this.stdout); - } finally { - this.stdout.close(); - } - } + status() { + return runStatus(this.rid); + } - async stderrOutput() { - if (!this.stderr) { - throw new TypeError("stderr was not piped"); - } - try { - return await readAll(this.stderr); - } finally { - this.stderr.close(); - } + async output() { + if (!this.stdout) { + throw new TypeError("stdout was not piped"); } - - close() { - core.close(this.rid); + try { + return await readAll(this.stdout); + } finally { + this.stdout.close(); } + } - kill(signo = "SIGTERM") { - opKill(this.pid, signo, "Deno.Process.kill()"); + async stderrOutput() { + if (!this.stderr) { + throw new TypeError("stderr was not piped"); + } + try { + return await readAll(this.stderr); + } finally { + this.stderr.close(); } } - function run({ - cmd, - cwd = undefined, - clearEnv = false, - env = {}, - gid = undefined, - uid = undefined, - stdout = "inherit", - stderr = "inherit", - stdin = "inherit", - }) { - if (cmd[0] != null) { - cmd = [ - pathFromURL(cmd[0]), - ...new SafeArrayIterator(ArrayPrototypeSlice(cmd, 1)), - ]; - } - const res = opRun({ - cmd: ArrayPrototypeMap(cmd, String), - cwd, - clearEnv, - env: ObjectEntries(env), - gid, - uid, - stdin, - stdout, - stderr, - }); - return new Process(res); + close() { + core.close(this.rid); } - window.__bootstrap.process = { - run, - Process, - kill, - }; -})(this); + kill(signo = "SIGTERM") { + opKill(this.pid, signo, "Deno.Process.kill()"); + } +} + +function run({ + cmd, + cwd = undefined, + clearEnv = false, + env = {}, + gid = undefined, + uid = undefined, + stdout = "inherit", + stderr = "inherit", + stdin = "inherit", +}) { + if (cmd[0] != null) { + cmd = [ + pathFromURL(cmd[0]), + ...new SafeArrayIterator(ArrayPrototypeSlice(cmd, 1)), + ]; + } + const res = opRun({ + cmd: ArrayPrototypeMap(cmd, String), + cwd, + clearEnv, + env: ObjectEntries(env), + gid, + uid, + stdin, + stdout, + stderr, + }); + return new Process(res); +} + +export { kill, Process, run }; diff --git a/runtime/js/40_read_file.js b/runtime/js/40_read_file.js index 7c289890394057..317875f7362bb9 100644 --- a/runtime/js/40_read_file.js +++ b/runtime/js/40_read_file.js @@ -1,78 +1,70 @@ // Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. -"use strict"; -((window) => { - const core = window.Deno.core; - const ops = core.ops; - const { pathFromURL } = window.__bootstrap.util; - const { abortSignal } = window.__bootstrap; +const core = globalThis.Deno.core; +const ops = core.ops; +import { pathFromURL } from "internal:runtime/js/06_util.js"; +import * as abortSignal from "internal:deno_web/03_abort_signal.js"; - function readFileSync(path) { - return ops.op_readfile_sync(pathFromURL(path)); +function readFileSync(path) { + return ops.op_readfile_sync(pathFromURL(path)); +} + +async function readFile(path, options) { + let cancelRid; + let abortHandler; + if (options?.signal) { + options.signal.throwIfAborted(); + cancelRid = ops.op_cancel_handle(); + abortHandler = () => core.tryClose(cancelRid); + options.signal[abortSignal.add](abortHandler); } - async function readFile(path, options) { - let cancelRid; - let abortHandler; + try { + const read = await core.opAsync( + "op_readfile_async", + pathFromURL(path), + cancelRid, + ); + return read; + } finally { if (options?.signal) { - options.signal.throwIfAborted(); - cancelRid = ops.op_cancel_handle(); - abortHandler = () => core.tryClose(cancelRid); - options.signal[abortSignal.add](abortHandler); - } + options.signal[abortSignal.remove](abortHandler); - try { - const read = await core.opAsync( - "op_readfile_async", - pathFromURL(path), - cancelRid, - ); - return read; - } finally { - if (options?.signal) { - options.signal[abortSignal.remove](abortHandler); - - // always throw the abort error when aborted - options.signal.throwIfAborted(); - } + // always throw the abort error when aborted + options.signal.throwIfAborted(); } } +} - function readTextFileSync(path) { - return ops.op_readfile_text_sync(pathFromURL(path)); +function readTextFileSync(path) { + return ops.op_readfile_text_sync(pathFromURL(path)); +} + +async function readTextFile(path, options) { + let cancelRid; + let abortHandler; + if (options?.signal) { + options.signal.throwIfAborted(); + cancelRid = ops.op_cancel_handle(); + abortHandler = () => core.tryClose(cancelRid); + options.signal[abortSignal.add](abortHandler); } - async function readTextFile(path, options) { - let cancelRid; - let abortHandler; + try { + const read = await core.opAsync( + "op_readfile_text_async", + pathFromURL(path), + cancelRid, + ); + return read; + } finally { if (options?.signal) { - options.signal.throwIfAborted(); - cancelRid = ops.op_cancel_handle(); - abortHandler = () => core.tryClose(cancelRid); - options.signal[abortSignal.add](abortHandler); - } + options.signal[abortSignal.remove](abortHandler); - try { - const read = await core.opAsync( - "op_readfile_text_async", - pathFromURL(path), - cancelRid, - ); - return read; - } finally { - if (options?.signal) { - options.signal[abortSignal.remove](abortHandler); - - // always throw the abort error when aborted - options.signal.throwIfAborted(); - } + // always throw the abort error when aborted + options.signal.throwIfAborted(); } } +} - window.__bootstrap.readFile = { - readFile, - readFileSync, - readTextFileSync, - readTextFile, - }; -})(this); +export { readFile, readFileSync, readTextFile, readTextFileSync }; diff --git a/runtime/js/40_signals.js b/runtime/js/40_signals.js index ff1502a5553760..4ae31015153131 100644 --- a/runtime/js/40_signals.js +++ b/runtime/js/40_signals.js @@ -1,88 +1,83 @@ // Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. -"use strict"; -((window) => { - const core = window.Deno.core; - const ops = core.ops; - const { - SafeSetIterator, - Set, - SetPrototypeDelete, - SymbolFor, - TypeError, - } = window.__bootstrap.primordials; +const core = globalThis.Deno.core; +const ops = core.ops; +const primordials = globalThis.__bootstrap.primordials; +const { + SafeSetIterator, + Set, + SetPrototypeDelete, + SymbolFor, + TypeError, +} = primordials; - function bindSignal(signo) { - return ops.op_signal_bind(signo); - } +function bindSignal(signo) { + return ops.op_signal_bind(signo); +} - function pollSignal(rid) { - const promise = core.opAsync("op_signal_poll", rid); - core.unrefOp(promise[SymbolFor("Deno.core.internalPromiseId")]); - return promise; - } +function pollSignal(rid) { + const promise = core.opAsync("op_signal_poll", rid); + core.unrefOp(promise[SymbolFor("Deno.core.internalPromiseId")]); + return promise; +} - function unbindSignal(rid) { - ops.op_signal_unbind(rid); - } +function unbindSignal(rid) { + ops.op_signal_unbind(rid); +} - // Stores signal listeners and resource data. This has type of - // `Record void> }` - const signalData = {}; +// Stores signal listeners and resource data. This has type of +// `Record void> }` +const signalData = {}; - /** Gets the signal handlers and resource data of the given signal */ - function getSignalData(signo) { - return signalData[signo] ?? - (signalData[signo] = { rid: undefined, listeners: new Set() }); - } +/** Gets the signal handlers and resource data of the given signal */ +function getSignalData(signo) { + return signalData[signo] ?? + (signalData[signo] = { rid: undefined, listeners: new Set() }); +} - function checkSignalListenerType(listener) { - if (typeof listener !== "function") { - throw new TypeError( - `Signal listener must be a function. "${typeof listener}" is given.`, - ); - } +function checkSignalListenerType(listener) { + if (typeof listener !== "function") { + throw new TypeError( + `Signal listener must be a function. "${typeof listener}" is given.`, + ); } +} - function addSignalListener(signo, listener) { - checkSignalListenerType(listener); +function addSignalListener(signo, listener) { + checkSignalListenerType(listener); - const sigData = getSignalData(signo); - sigData.listeners.add(listener); + const sigData = getSignalData(signo); + sigData.listeners.add(listener); - if (!sigData.rid) { - // If signal resource doesn't exist, create it. - // The program starts listening to the signal - sigData.rid = bindSignal(signo); - loop(sigData); - } + if (!sigData.rid) { + // If signal resource doesn't exist, create it. + // The program starts listening to the signal + sigData.rid = bindSignal(signo); + loop(sigData); } +} - function removeSignalListener(signo, listener) { - checkSignalListenerType(listener); +function removeSignalListener(signo, listener) { + checkSignalListenerType(listener); - const sigData = getSignalData(signo); - SetPrototypeDelete(sigData.listeners, listener); + const sigData = getSignalData(signo); + SetPrototypeDelete(sigData.listeners, listener); - if (sigData.listeners.size === 0 && sigData.rid) { - unbindSignal(sigData.rid); - sigData.rid = undefined; - } + if (sigData.listeners.size === 0 && sigData.rid) { + unbindSignal(sigData.rid); + sigData.rid = undefined; } +} - async function loop(sigData) { - while (sigData.rid) { - if (await pollSignal(sigData.rid)) { - return; - } - for (const listener of new SafeSetIterator(sigData.listeners)) { - listener(); - } +async function loop(sigData) { + while (sigData.rid) { + if (await pollSignal(sigData.rid)) { + return; + } + for (const listener of new SafeSetIterator(sigData.listeners)) { + listener(); } } +} - window.__bootstrap.signals = { - addSignalListener, - removeSignalListener, - }; -})(this); +export { addSignalListener, removeSignalListener }; diff --git a/runtime/js/40_spawn.js b/runtime/js/40_spawn.js index ecbab52ad94140..f75d83f9ba70ce 100644 --- a/runtime/js/40_spawn.js +++ b/runtime/js/40_spawn.js @@ -1,333 +1,326 @@ // Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. -"use strict"; - -((window) => { - const core = window.Deno.core; - const ops = core.ops; - const { pathFromURL } = window.__bootstrap.util; - const { illegalConstructorKey } = window.__bootstrap.webUtil; - const { add, remove } = window.__bootstrap.abortSignal; - const { - ArrayPrototypeMap, - ObjectEntries, - ObjectPrototypeIsPrototypeOf, - String, - TypeError, - PromisePrototypeThen, - SafePromiseAll, - SymbolFor, - } = window.__bootstrap.primordials; - const { - readableStreamCollectIntoUint8Array, - readableStreamForRidUnrefable, - readableStreamForRidUnrefableRef, - readableStreamForRidUnrefableUnref, - ReadableStreamPrototype, - writableStreamForRid, - } = window.__bootstrap.streams; - - const promiseIdSymbol = SymbolFor("Deno.core.internalPromiseId"); - - function spawnChildInner(opFn, command, apiName, { - args = [], - cwd = undefined, - clearEnv = false, - env = {}, - uid = undefined, - gid = undefined, - stdin = "null", - stdout = "piped", - stderr = "piped", - signal = undefined, - windowsRawArguments = false, - } = {}) { - const child = opFn({ - cmd: pathFromURL(command), - args: ArrayPrototypeMap(args, String), - cwd: pathFromURL(cwd), - clearEnv, - env: ObjectEntries(env), - uid, - gid, - stdin, - stdout, - stderr, - windowsRawArguments, - }, apiName); - return new Child(illegalConstructorKey, { - ...child, - signal, - }); - } - function createSpawnChild(opFn) { - return function spawnChild(command, options = {}) { - return spawnChildInner(opFn, command, "Deno.Command().spawn()", options); - }; +const core = globalThis.Deno.core; +const ops = core.ops; +const primordials = globalThis.__bootstrap.primordials; +import { pathFromURL } from "internal:runtime/js/06_util.js"; +import { add, remove } from "internal:deno_web/03_abort_signal.js"; +const { + ArrayPrototypeMap, + ObjectEntries, + ObjectPrototypeIsPrototypeOf, + String, + TypeError, + PromisePrototypeThen, + SafePromiseAll, + SymbolFor, + Symbol, +} = primordials; +import { + readableStreamCollectIntoUint8Array, + readableStreamForRidUnrefable, + readableStreamForRidUnrefableRef, + readableStreamForRidUnrefableUnref, + ReadableStreamPrototype, + writableStreamForRid, +} from "internal:deno_web/06_streams.js"; + +const illegalConstructorKey = Symbol("illegalConstructorKey"); + +const promiseIdSymbol = SymbolFor("Deno.core.internalPromiseId"); + +function spawnChildInner(opFn, command, apiName, { + args = [], + cwd = undefined, + clearEnv = false, + env = {}, + uid = undefined, + gid = undefined, + stdin = "null", + stdout = "piped", + stderr = "piped", + signal = undefined, + windowsRawArguments = false, +} = {}) { + const child = opFn({ + cmd: pathFromURL(command), + args: ArrayPrototypeMap(args, String), + cwd: pathFromURL(cwd), + clearEnv, + env: ObjectEntries(env), + uid, + gid, + stdin, + stdout, + stderr, + windowsRawArguments, + }, apiName); + return new ChildProcess(illegalConstructorKey, { + ...child, + signal, + }); +} + +function spawnChild(command, options = {}) { + return spawnChildInner( + ops.op_spawn_child, + command, + "Deno.Command().spawn()", + options, + ); +} + +function collectOutput(readableStream) { + if ( + !(ObjectPrototypeIsPrototypeOf(ReadableStreamPrototype, readableStream)) + ) { + return null; } - function collectOutput(readableStream) { - if ( - !(ObjectPrototypeIsPrototypeOf(ReadableStreamPrototype, readableStream)) - ) { - return null; - } + return readableStreamCollectIntoUint8Array(readableStream); +} - return readableStreamCollectIntoUint8Array(readableStream); - } +class ChildProcess { + #rid; + #waitPromiseId; + #unrefed = false; - class Child { - #rid; - #waitPromiseId; - #unrefed = false; + #pid; + get pid() { + return this.#pid; + } - #pid; - get pid() { - return this.#pid; + #stdin = null; + get stdin() { + if (this.#stdin == null) { + throw new TypeError("stdin is not piped"); } + return this.#stdin; + } - #stdin = null; - get stdin() { - if (this.#stdin == null) { - throw new TypeError("stdin is not piped"); - } - return this.#stdin; + #stdoutRid; + #stdout = null; + get stdout() { + if (this.#stdout == null) { + throw new TypeError("stdout is not piped"); } + return this.#stdout; + } - #stdoutPromiseId; - #stdoutRid; - #stdout = null; - get stdout() { - if (this.#stdout == null) { - throw new TypeError("stdout is not piped"); - } - return this.#stdout; + #stderrRid; + #stderr = null; + get stderr() { + if (this.#stderr == null) { + throw new TypeError("stderr is not piped"); } + return this.#stderr; + } - #stderrPromiseId; - #stderrRid; - #stderr = null; - get stderr() { - if (this.#stderr == null) { - throw new TypeError("stderr is not piped"); - } - return this.#stderr; + constructor(key = null, { + signal, + rid, + pid, + stdinRid, + stdoutRid, + stderrRid, + } = null) { + if (key !== illegalConstructorKey) { + throw new TypeError("Illegal constructor."); } - constructor(key = null, { - signal, - rid, - pid, - stdinRid, - stdoutRid, - stderrRid, - } = null) { - if (key !== illegalConstructorKey) { - throw new TypeError("Illegal constructor."); - } + this.#rid = rid; + this.#pid = pid; - this.#rid = rid; - this.#pid = pid; + if (stdinRid !== null) { + this.#stdin = writableStreamForRid(stdinRid); + } - if (stdinRid !== null) { - this.#stdin = writableStreamForRid(stdinRid); - } + if (stdoutRid !== null) { + this.#stdoutRid = stdoutRid; + this.#stdout = readableStreamForRidUnrefable(stdoutRid); + } - if (stdoutRid !== null) { - this.#stdoutRid = stdoutRid; - this.#stdout = readableStreamForRidUnrefable(stdoutRid); - } + if (stderrRid !== null) { + this.#stderrRid = stderrRid; + this.#stderr = readableStreamForRidUnrefable(stderrRid); + } - if (stderrRid !== null) { - this.#stderrRid = stderrRid; - this.#stderr = readableStreamForRidUnrefable(stderrRid); - } + const onAbort = () => this.kill("SIGTERM"); + signal?.[add](onAbort); - const onAbort = () => this.kill("SIGTERM"); - signal?.[add](onAbort); + const waitPromise = core.opAsync("op_spawn_wait", this.#rid); + this.#waitPromiseId = waitPromise[promiseIdSymbol]; + this.#status = PromisePrototypeThen(waitPromise, (res) => { + this.#rid = null; + signal?.[remove](onAbort); + return res; + }); + } - const waitPromise = core.opAsync("op_spawn_wait", this.#rid); - this.#waitPromiseId = waitPromise[promiseIdSymbol]; - this.#status = PromisePrototypeThen(waitPromise, (res) => { - this.#rid = null; - signal?.[remove](onAbort); - return res; - }); - } + #status; + get status() { + return this.#status; + } - #status; - get status() { - return this.#status; + async output() { + if (this.#stdout?.locked) { + throw new TypeError( + "Can't collect output because stdout is locked", + ); } - - async output() { - if (this.#stdout?.locked) { - throw new TypeError( - "Can't collect output because stdout is locked", - ); - } - if (this.#stderr?.locked) { - throw new TypeError( - "Can't collect output because stderr is locked", - ); - } - - const { 0: status, 1: stdout, 2: stderr } = await SafePromiseAll([ - this.#status, - collectOutput(this.#stdout), - collectOutput(this.#stderr), - ]); - - return { - success: status.success, - code: status.code, - signal: status.signal, - get stdout() { - if (stdout == null) { - throw new TypeError("stdout is not piped"); - } - return stdout; - }, - get stderr() { - if (stderr == null) { - throw new TypeError("stderr is not piped"); - } - return stderr; - }, - }; + if (this.#stderr?.locked) { + throw new TypeError( + "Can't collect output because stderr is locked", + ); } - kill(signo = "SIGTERM") { - if (this.#rid === null) { - throw new TypeError("Child process has already terminated."); - } - ops.op_kill(this.#pid, signo, "Deno.Child.kill()"); - } + const { 0: status, 1: stdout, 2: stderr } = await SafePromiseAll([ + this.#status, + collectOutput(this.#stdout), + collectOutput(this.#stderr), + ]); + + return { + success: status.success, + code: status.code, + signal: status.signal, + get stdout() { + if (stdout == null) { + throw new TypeError("stdout is not piped"); + } + return stdout; + }, + get stderr() { + if (stderr == null) { + throw new TypeError("stderr is not piped"); + } + return stderr; + }, + }; + } - ref() { - this.#unrefed = false; - core.refOp(this.#waitPromiseId); - if (this.#stdout) readableStreamForRidUnrefableRef(this.#stdout); - if (this.#stderr) readableStreamForRidUnrefableRef(this.#stderr); + kill(signo = "SIGTERM") { + if (this.#rid === null) { + throw new TypeError("Child process has already terminated."); } + ops.op_kill(this.#pid, signo, "Deno.Child.kill()"); + } - unref() { - this.#unrefed = true; - core.unrefOp(this.#waitPromiseId); - if (this.#stdout) readableStreamForRidUnrefableUnref(this.#stdout); - if (this.#stderr) readableStreamForRidUnrefableUnref(this.#stderr); - } + ref() { + this.#unrefed = false; + core.refOp(this.#waitPromiseId); + if (this.#stdout) readableStreamForRidUnrefableRef(this.#stdout); + if (this.#stderr) readableStreamForRidUnrefableRef(this.#stderr); } - function createSpawn(opFn) { - return function spawn(command, options) { - if (options?.stdin === "piped") { - throw new TypeError( - "Piped stdin is not supported for this function, use 'Deno.Command().spawn()' instead", - ); - } - return spawnChildInner(opFn, command, "Deno.Command().output()", options) - .output(); - }; + unref() { + this.#unrefed = true; + core.unrefOp(this.#waitPromiseId); + if (this.#stdout) readableStreamForRidUnrefableUnref(this.#stdout); + if (this.#stderr) readableStreamForRidUnrefableUnref(this.#stderr); } +} - function createSpawnSync(opFn) { - return function spawnSync(command, { - args = [], - cwd = undefined, - clearEnv = false, - env = {}, - uid = undefined, - gid = undefined, - stdin = "null", - stdout = "piped", - stderr = "piped", - windowsRawArguments = false, - } = {}) { - if (stdin === "piped") { - throw new TypeError( - "Piped stdin is not supported for this function, use 'Deno.Command().spawn()' instead", - ); - } - const result = opFn({ - cmd: pathFromURL(command), - args: ArrayPrototypeMap(args, String), - cwd: pathFromURL(cwd), - clearEnv, - env: ObjectEntries(env), - uid, - gid, - stdin, - stdout, - stderr, - windowsRawArguments, - }); - return { - success: result.status.success, - code: result.status.code, - signal: result.status.signal, - get stdout() { - if (result.stdout == null) { - throw new TypeError("stdout is not piped"); - } - return result.stdout; - }, - get stderr() { - if (result.stderr == null) { - throw new TypeError("stderr is not piped"); - } - return result.stderr; - }, - }; - }; +function spawn(command, options) { + if (options?.stdin === "piped") { + throw new TypeError( + "Piped stdin is not supported for this function, use 'Deno.Command().spawn()' instead", + ); + } + return spawnChildInner( + ops.op_spawn_child, + command, + "Deno.Command().output()", + options, + ) + .output(); +} + +function spawnSync(command, { + args = [], + cwd = undefined, + clearEnv = false, + env = {}, + uid = undefined, + gid = undefined, + stdin = "null", + stdout = "piped", + stderr = "piped", + windowsRawArguments = false, +} = {}) { + if (stdin === "piped") { + throw new TypeError( + "Piped stdin is not supported for this function, use 'Deno.Command().spawn()' instead", + ); } + const result = ops.op_spawn_sync({ + cmd: pathFromURL(command), + args: ArrayPrototypeMap(args, String), + cwd: pathFromURL(cwd), + clearEnv, + env: ObjectEntries(env), + uid, + gid, + stdin, + stdout, + stderr, + windowsRawArguments, + }); + return { + success: result.status.success, + code: result.status.code, + signal: result.status.signal, + get stdout() { + if (result.stdout == null) { + throw new TypeError("stdout is not piped"); + } + return result.stdout; + }, + get stderr() { + if (result.stderr == null) { + throw new TypeError("stderr is not piped"); + } + return result.stderr; + }, + }; +} - function createCommand(spawn, spawnSync, spawnChild) { - return class Command { - #command; - #options; +class Command { + #command; + #options; - constructor(command, options) { - this.#command = command; - this.#options = options; - } + constructor(command, options) { + this.#command = command; + this.#options = options; + } - output() { - if (this.#options?.stdin === "piped") { - throw new TypeError( - "Piped stdin is not supported for this function, use 'Deno.Command.spawn()' instead", - ); - } - return spawn(this.#command, this.#options); - } + output() { + if (this.#options?.stdin === "piped") { + throw new TypeError( + "Piped stdin is not supported for this function, use 'Deno.Command.spawn()' instead", + ); + } + return spawn(this.#command, this.#options); + } - outputSync() { - if (this.#options?.stdin === "piped") { - throw new TypeError( - "Piped stdin is not supported for this function, use 'Deno.Command.spawn()' instead", - ); - } - return spawnSync(this.#command, this.#options); - } + outputSync() { + if (this.#options?.stdin === "piped") { + throw new TypeError( + "Piped stdin is not supported for this function, use 'Deno.Command.spawn()' instead", + ); + } + return spawnSync(this.#command, this.#options); + } - spawn() { - const options = { - ...(this.#options ?? {}), - stdout: this.#options?.stdout ?? "inherit", - stderr: this.#options?.stderr ?? "inherit", - stdin: this.#options?.stdin ?? "inherit", - }; - return spawnChild(this.#command, options); - } + spawn() { + const options = { + ...(this.#options ?? {}), + stdout: this.#options?.stdout ?? "inherit", + stderr: this.#options?.stderr ?? "inherit", + stdin: this.#options?.stdin ?? "inherit", }; + return spawnChild(this.#command, options); } +} - window.__bootstrap.spawn = { - Child, - ChildProcess: Child, - createCommand, - createSpawn, - createSpawnChild, - createSpawnSync, - }; -})(this); +export { ChildProcess, Command }; diff --git a/runtime/js/40_tty.js b/runtime/js/40_tty.js index 3263dc814f65e7..859f492757195e 100644 --- a/runtime/js/40_tty.js +++ b/runtime/js/40_tty.js @@ -1,28 +1,23 @@ // Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. -"use strict"; +const core = globalThis.Deno.core; +const ops = core.ops; +const primordials = globalThis.__bootstrap.primordials; +const { + Uint32Array, + Uint8Array, +} = primordials; -((window) => { - const { - Uint32Array, - Uint8Array, - } = window.__bootstrap.primordials; - const core = window.Deno.core; - const ops = core.ops; +const size = new Uint32Array(2); - const size = new Uint32Array(2); - function consoleSize() { - ops.op_console_size(size); - return { columns: size[0], rows: size[1] }; - } +function consoleSize() { + ops.op_console_size(size); + return { columns: size[0], rows: size[1] }; +} - const isattyBuffer = new Uint8Array(1); - function isatty(rid) { - ops.op_isatty(rid, isattyBuffer); - return !!isattyBuffer[0]; - } +const isattyBuffer = new Uint8Array(1); +function isatty(rid) { + ops.op_isatty(rid, isattyBuffer); + return !!isattyBuffer[0]; +} - window.__bootstrap.tty = { - consoleSize, - isatty, - }; -})(this); +export { consoleSize, isatty }; diff --git a/runtime/js/40_write_file.js b/runtime/js/40_write_file.js index a32ef441bc0a53..255def09f2e549 100644 --- a/runtime/js/40_write_file.js +++ b/runtime/js/40_write_file.js @@ -1,107 +1,100 @@ // Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. -"use strict"; -((window) => { - const core = window.__bootstrap.core; - const ops = core.ops; - const { abortSignal } = window.__bootstrap; - const { pathFromURL } = window.__bootstrap.util; - const { open } = window.__bootstrap.files; - const { ReadableStreamPrototype } = window.__bootstrap.streams; - const { ObjectPrototypeIsPrototypeOf } = window.__bootstrap.primordials; +const core = globalThis.Deno.core; +const ops = core.ops; +const primordials = globalThis.__bootstrap.primordials; +import * as abortSignal from "internal:deno_web/03_abort_signal.js"; +import { pathFromURL } from "internal:runtime/js/06_util.js"; +import { open } from "internal:runtime/js/40_files.js"; +import { ReadableStreamPrototype } from "internal:deno_web/06_streams.js"; +const { ObjectPrototypeIsPrototypeOf } = primordials; - function writeFileSync( - path, +function writeFileSync( + path, + data, + options = {}, +) { + options.signal?.throwIfAborted(); + ops.op_write_file_sync( + pathFromURL(path), + options.mode, + options.append ?? false, + options.create ?? true, + options.createNew ?? false, data, - options = {}, - ) { - options.signal?.throwIfAborted(); - ops.op_write_file_sync( - pathFromURL(path), - options.mode, - options.append ?? false, - options.create ?? true, - options.createNew ?? false, - data, - ); - } + ); +} - async function writeFile( - path, - data, - options = {}, - ) { - let cancelRid; - let abortHandler; - if (options.signal) { - options.signal.throwIfAborted(); - cancelRid = ops.op_cancel_handle(); - abortHandler = () => core.tryClose(cancelRid); - options.signal[abortSignal.add](abortHandler); +async function writeFile( + path, + data, + options = {}, +) { + let cancelRid; + let abortHandler; + if (options.signal) { + options.signal.throwIfAborted(); + cancelRid = ops.op_cancel_handle(); + abortHandler = () => core.tryClose(cancelRid); + options.signal[abortSignal.add](abortHandler); + } + try { + if (ObjectPrototypeIsPrototypeOf(ReadableStreamPrototype, data)) { + const file = await open(path, { + mode: options.mode, + append: options.append ?? false, + create: options.create ?? true, + createNew: options.createNew ?? false, + write: true, + }); + await data.pipeTo(file.writable, { + signal: options.signal, + }); + } else { + await core.opAsync( + "op_write_file_async", + pathFromURL(path), + options.mode, + options.append ?? false, + options.create ?? true, + options.createNew ?? false, + data, + cancelRid, + ); } - try { - if (ObjectPrototypeIsPrototypeOf(ReadableStreamPrototype, data)) { - const file = await open(path, { - mode: options.mode, - append: options.append ?? false, - create: options.create ?? true, - createNew: options.createNew ?? false, - write: true, - }); - await data.pipeTo(file.writable, { - signal: options.signal, - }); - } else { - await core.opAsync( - "op_write_file_async", - pathFromURL(path), - options.mode, - options.append ?? false, - options.create ?? true, - options.createNew ?? false, - data, - cancelRid, - ); - } - } finally { - if (options.signal) { - options.signal[abortSignal.remove](abortHandler); + } finally { + if (options.signal) { + options.signal[abortSignal.remove](abortHandler); - // always throw the abort error when aborted - options.signal.throwIfAborted(); - } + // always throw the abort error when aborted + options.signal.throwIfAborted(); } } +} - function writeTextFileSync( - path, - data, - options = {}, - ) { - const encoder = new TextEncoder(); - return writeFileSync(path, encoder.encode(data), options); - } +function writeTextFileSync( + path, + data, + options = {}, +) { + const encoder = new TextEncoder(); + return writeFileSync(path, encoder.encode(data), options); +} - function writeTextFile( - path, - data, - options = {}, - ) { - if (ObjectPrototypeIsPrototypeOf(ReadableStreamPrototype, data)) { - return writeFile( - path, - data.pipeThrough(new TextEncoderStream()), - options, - ); - } else { - const encoder = new TextEncoder(); - return writeFile(path, encoder.encode(data), options); - } +function writeTextFile( + path, + data, + options = {}, +) { + if (ObjectPrototypeIsPrototypeOf(ReadableStreamPrototype, data)) { + return writeFile( + path, + data.pipeThrough(new TextEncoderStream()), + options, + ); + } else { + const encoder = new TextEncoder(); + return writeFile(path, encoder.encode(data), options); } +} - window.__bootstrap.writeFile = { - writeTextFile, - writeTextFileSync, - writeFile, - writeFileSync, - }; -})(this); +export { writeFile, writeFileSync, writeTextFile, writeTextFileSync }; diff --git a/runtime/js/41_prompt.js b/runtime/js/41_prompt.js index 1d5acc028f54f0..441db9a2f3e517 100644 --- a/runtime/js/41_prompt.js +++ b/runtime/js/41_prompt.js @@ -1,82 +1,76 @@ // Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. -"use strict"; -((window) => { - const { stdin } = window.__bootstrap.files; - const { ArrayPrototypePush, StringPrototypeCharCodeAt, Uint8Array } = - window.__bootstrap.primordials; - const { isatty } = window.__bootstrap.tty; - const LF = StringPrototypeCharCodeAt("\n", 0); - const CR = StringPrototypeCharCodeAt("\r", 0); - const core = window.Deno.core; +const core = globalThis.Deno.core; +const primordials = globalThis.__bootstrap.primordials; +import { isatty } from "internal:runtime/js/40_tty.js"; +import { stdin } from "internal:runtime/js/40_files.js"; +const { ArrayPrototypePush, StringPrototypeCharCodeAt, Uint8Array } = + primordials; +const LF = StringPrototypeCharCodeAt("\n", 0); +const CR = StringPrototypeCharCodeAt("\r", 0); - function alert(message = "Alert") { - if (!isatty(stdin.rid)) { - return; - } - - core.print(`${message} [Enter] `, false); - - readLineFromStdinSync(); +function alert(message = "Alert") { + if (!isatty(stdin.rid)) { + return; } - function confirm(message = "Confirm") { - if (!isatty(stdin.rid)) { - return false; - } - - core.print(`${message} [y/N] `, false); + core.print(`${message} [Enter] `, false); - const answer = readLineFromStdinSync(); + readLineFromStdinSync(); +} - return answer === "Y" || answer === "y"; +function confirm(message = "Confirm") { + if (!isatty(stdin.rid)) { + return false; } - function prompt(message = "Prompt", defaultValue) { - defaultValue ??= null; + core.print(`${message} [y/N] `, false); - if (!isatty(stdin.rid)) { - return null; - } + const answer = readLineFromStdinSync(); - core.print(`${message} `, false); + return answer === "Y" || answer === "y"; +} - if (defaultValue) { - core.print(`[${defaultValue}] `, false); - } +function prompt(message = "Prompt", defaultValue) { + defaultValue ??= null; + + if (!isatty(stdin.rid)) { + return null; + } - return readLineFromStdinSync() || defaultValue; + core.print(`${message} `, false); + + if (defaultValue) { + core.print(`[${defaultValue}] `, false); } - function readLineFromStdinSync() { - const c = new Uint8Array(1); - const buf = []; + return readLineFromStdinSync() || defaultValue; +} + +function readLineFromStdinSync() { + const c = new Uint8Array(1); + const buf = []; - while (true) { + while (true) { + const n = stdin.readSync(c); + if (n === null || n === 0) { + break; + } + if (c[0] === CR) { const n = stdin.readSync(c); - if (n === null || n === 0) { + if (c[0] === LF) { break; } - if (c[0] === CR) { - const n = stdin.readSync(c); - if (c[0] === LF) { - break; - } - ArrayPrototypePush(buf, CR); - if (n === null || n === 0) { - break; - } - } - if (c[0] === LF) { + ArrayPrototypePush(buf, CR); + if (n === null || n === 0) { break; } - ArrayPrototypePush(buf, c[0]); } - return core.decode(new Uint8Array(buf)); + if (c[0] === LF) { + break; + } + ArrayPrototypePush(buf, c[0]); } + return core.decode(new Uint8Array(buf)); +} - window.__bootstrap.prompt = { - alert, - confirm, - prompt, - }; -})(this); +export { alert, confirm, prompt }; diff --git a/runtime/js/90_deno_ns.js b/runtime/js/90_deno_ns.js index 1a1659b75b573d..8ad848025e0074 100644 --- a/runtime/js/90_deno_ns.js +++ b/runtime/js/90_deno_ns.js @@ -1,160 +1,188 @@ // Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. // Copyright 2023 Jo Bates. All rights reserved. MIT license. -"use strict"; -((window) => { - const core = window.Deno.core; - const __bootstrap = window.__bootstrap; +const core = globalThis.Deno.core; +const ops = core.ops; +import * as timers from "internal:deno_web/02_timers.js"; +import * as httpClient from "internal:deno_fetch/22_http_client.js"; +import * as console from "internal:deno_console/02_console.js"; +import * as ffi from "internal:deno_ffi/00_ffi.js"; +import * as net from "internal:deno_net/01_net.js"; +import * as tls from "internal:deno_net/02_tls.js"; +import * as http from "internal:deno_http/01_http.js"; +import * as flash from "internal:deno_flash/01_http.js"; +import * as build from "internal:runtime/js/01_build.js"; +import * as errors from "internal:runtime/js/01_errors.js"; +import * as version from "internal:runtime/js/01_version.ts"; +import * as permissions from "internal:runtime/js/10_permissions.js"; +import * as io from "internal:runtime/js/12_io.js"; +import * as buffer from "internal:runtime/js/13_buffer.js"; +import * as fs from "internal:runtime/js/30_fs.js"; +import * as os from "internal:runtime/js/30_os.js"; +import * as diagnostics from "internal:runtime/js/40_diagnostics.js"; +import * as files from "internal:runtime/js/40_files.js"; +import * as fsEvents from "internal:runtime/js/40_fs_events.js"; +import * as process from "internal:runtime/js/40_process.js"; +import * as readFile from "internal:runtime/js/40_read_file.js"; +import * as signals from "internal:runtime/js/40_signals.js"; +import * as tty from "internal:runtime/js/40_tty.js"; +import * as writeFile from "internal:runtime/js/40_write_file.js"; +import * as spawn from "internal:runtime/js/40_spawn.js"; +// TODO(bartlomieju): this is funky we have two `http` imports +import * as httpRuntime from "internal:runtime/js/40_http.js"; +import * as wsi from "internal:deno_wsi/01_wsi.js"; - __bootstrap.denoNs = { - metrics: core.metrics, - Process: __bootstrap.process.Process, - run: __bootstrap.process.run, - isatty: __bootstrap.tty.isatty, - writeFileSync: __bootstrap.writeFile.writeFileSync, - writeFile: __bootstrap.writeFile.writeFile, - writeTextFileSync: __bootstrap.writeFile.writeTextFileSync, - writeTextFile: __bootstrap.writeFile.writeTextFile, - readTextFile: __bootstrap.readFile.readTextFile, - readTextFileSync: __bootstrap.readFile.readTextFileSync, - readFile: __bootstrap.readFile.readFile, - readFileSync: __bootstrap.readFile.readFileSync, - watchFs: __bootstrap.fsEvents.watchFs, - chmodSync: __bootstrap.fs.chmodSync, - chmod: __bootstrap.fs.chmod, - chown: __bootstrap.fs.chown, - chownSync: __bootstrap.fs.chownSync, - copyFileSync: __bootstrap.fs.copyFileSync, - cwd: __bootstrap.fs.cwd, - makeTempDirSync: __bootstrap.fs.makeTempDirSync, - makeTempDir: __bootstrap.fs.makeTempDir, - makeTempFileSync: __bootstrap.fs.makeTempFileSync, - makeTempFile: __bootstrap.fs.makeTempFile, - memoryUsage: () => core.ops.op_runtime_memory_usage(), - mkdirSync: __bootstrap.fs.mkdirSync, - mkdir: __bootstrap.fs.mkdir, - chdir: __bootstrap.fs.chdir, - copyFile: __bootstrap.fs.copyFile, - readDirSync: __bootstrap.fs.readDirSync, - readDir: __bootstrap.fs.readDir, - readLinkSync: __bootstrap.fs.readLinkSync, - readLink: __bootstrap.fs.readLink, - realPathSync: __bootstrap.fs.realPathSync, - realPath: __bootstrap.fs.realPath, - removeSync: __bootstrap.fs.removeSync, - remove: __bootstrap.fs.remove, - renameSync: __bootstrap.fs.renameSync, - rename: __bootstrap.fs.rename, - version: __bootstrap.version.version, - build: __bootstrap.build.build, - statSync: __bootstrap.fs.statSync, - lstatSync: __bootstrap.fs.lstatSync, - stat: __bootstrap.fs.stat, - lstat: __bootstrap.fs.lstat, - truncateSync: __bootstrap.fs.truncateSync, - truncate: __bootstrap.fs.truncate, - ftruncateSync: __bootstrap.fs.ftruncateSync, - ftruncate: __bootstrap.fs.ftruncate, - futime: __bootstrap.fs.futime, - futimeSync: __bootstrap.fs.futimeSync, - errors: __bootstrap.errors.errors, - // TODO(kt3k): Remove this export at v2 - // See https://github.com/denoland/deno/issues/9294 - customInspect: __bootstrap.console.customInspect, - inspect: __bootstrap.console.inspect, - env: __bootstrap.os.env, - exit: __bootstrap.os.exit, - execPath: __bootstrap.os.execPath, - Buffer: __bootstrap.buffer.Buffer, - readAll: __bootstrap.buffer.readAll, - readAllSync: __bootstrap.buffer.readAllSync, - writeAll: __bootstrap.buffer.writeAll, - writeAllSync: __bootstrap.buffer.writeAllSync, - copy: __bootstrap.io.copy, - iter: __bootstrap.io.iter, - iterSync: __bootstrap.io.iterSync, - SeekMode: __bootstrap.io.SeekMode, - read: __bootstrap.io.read, - readSync: __bootstrap.io.readSync, - write: __bootstrap.io.write, - writeSync: __bootstrap.io.writeSync, - File: __bootstrap.files.File, - FsFile: __bootstrap.files.FsFile, - open: __bootstrap.files.open, - openSync: __bootstrap.files.openSync, - create: __bootstrap.files.create, - createSync: __bootstrap.files.createSync, - stdin: __bootstrap.files.stdin, - stdout: __bootstrap.files.stdout, - stderr: __bootstrap.files.stderr, - seek: __bootstrap.files.seek, - seekSync: __bootstrap.files.seekSync, - connect: __bootstrap.net.connect, - listen: __bootstrap.net.listen, - loadavg: __bootstrap.os.loadavg, - connectTls: __bootstrap.tls.connectTls, - listenTls: __bootstrap.tls.listenTls, - startTls: __bootstrap.tls.startTls, - shutdown: __bootstrap.net.shutdown, - fstatSync: __bootstrap.fs.fstatSync, - fstat: __bootstrap.fs.fstat, - fsyncSync: __bootstrap.fs.fsyncSync, - fsync: __bootstrap.fs.fsync, - fdatasyncSync: __bootstrap.fs.fdatasyncSync, - fdatasync: __bootstrap.fs.fdatasync, - symlink: __bootstrap.fs.symlink, - symlinkSync: __bootstrap.fs.symlinkSync, - link: __bootstrap.fs.link, - linkSync: __bootstrap.fs.linkSync, - permissions: __bootstrap.permissions.permissions, - Permissions: __bootstrap.permissions.Permissions, - PermissionStatus: __bootstrap.permissions.PermissionStatus, - serveHttp: __bootstrap.http.serveHttp, - resolveDns: __bootstrap.net.resolveDns, - upgradeWebSocket: __bootstrap.http.upgradeWebSocket, - utime: __bootstrap.fs.utime, - utimeSync: __bootstrap.fs.utimeSync, - kill: __bootstrap.process.kill, - addSignalListener: __bootstrap.signals.addSignalListener, - removeSignalListener: __bootstrap.signals.removeSignalListener, - refTimer: __bootstrap.timers.refTimer, - unrefTimer: __bootstrap.timers.unrefTimer, - osRelease: __bootstrap.os.osRelease, - osUptime: __bootstrap.os.osUptime, - hostname: __bootstrap.os.hostname, - systemMemoryInfo: __bootstrap.os.systemMemoryInfo, - networkInterfaces: __bootstrap.os.networkInterfaces, - consoleSize: __bootstrap.tty.consoleSize, - gid: __bootstrap.os.gid, - uid: __bootstrap.os.uid, - }; +const denoNs = { + metrics: core.metrics, + Process: process.Process, + run: process.run, + isatty: tty.isatty, + writeFileSync: writeFile.writeFileSync, + writeFile: writeFile.writeFile, + writeTextFileSync: writeFile.writeTextFileSync, + writeTextFile: writeFile.writeTextFile, + readTextFile: readFile.readTextFile, + readTextFileSync: readFile.readTextFileSync, + readFile: readFile.readFile, + readFileSync: readFile.readFileSync, + watchFs: fsEvents.watchFs, + chmodSync: fs.chmodSync, + chmod: fs.chmod, + chown: fs.chown, + chownSync: fs.chownSync, + copyFileSync: fs.copyFileSync, + cwd: fs.cwd, + makeTempDirSync: fs.makeTempDirSync, + makeTempDir: fs.makeTempDir, + makeTempFileSync: fs.makeTempFileSync, + makeTempFile: fs.makeTempFile, + memoryUsage: () => ops.op_runtime_memory_usage(), + mkdirSync: fs.mkdirSync, + mkdir: fs.mkdir, + chdir: fs.chdir, + copyFile: fs.copyFile, + readDirSync: fs.readDirSync, + readDir: fs.readDir, + readLinkSync: fs.readLinkSync, + readLink: fs.readLink, + realPathSync: fs.realPathSync, + realPath: fs.realPath, + removeSync: fs.removeSync, + remove: fs.remove, + renameSync: fs.renameSync, + rename: fs.rename, + version: version.version, + build: build.build, + statSync: fs.statSync, + lstatSync: fs.lstatSync, + stat: fs.stat, + lstat: fs.lstat, + truncateSync: fs.truncateSync, + truncate: fs.truncate, + ftruncateSync: fs.ftruncateSync, + ftruncate: fs.ftruncate, + futime: fs.futime, + futimeSync: fs.futimeSync, + errors: errors.errors, + // TODO(kt3k): Remove this export at v2 + // See https://github.com/denoland/deno/issues/9294 + customInspect: console.customInspect, + inspect: console.inspect, + env: os.env, + exit: os.exit, + execPath: os.execPath, + Buffer: buffer.Buffer, + readAll: buffer.readAll, + readAllSync: buffer.readAllSync, + writeAll: buffer.writeAll, + writeAllSync: buffer.writeAllSync, + copy: io.copy, + iter: io.iter, + iterSync: io.iterSync, + SeekMode: io.SeekMode, + read: io.read, + readSync: io.readSync, + write: io.write, + writeSync: io.writeSync, + File: files.File, + FsFile: files.FsFile, + open: files.open, + openSync: files.openSync, + create: files.create, + createSync: files.createSync, + stdin: files.stdin, + stdout: files.stdout, + stderr: files.stderr, + seek: files.seek, + seekSync: files.seekSync, + connect: net.connect, + listen: net.listen, + loadavg: os.loadavg, + connectTls: tls.connectTls, + listenTls: tls.listenTls, + startTls: tls.startTls, + shutdown: net.shutdown, + fstatSync: fs.fstatSync, + fstat: fs.fstat, + fsyncSync: fs.fsyncSync, + fsync: fs.fsync, + fdatasyncSync: fs.fdatasyncSync, + fdatasync: fs.fdatasync, + symlink: fs.symlink, + symlinkSync: fs.symlinkSync, + link: fs.link, + linkSync: fs.linkSync, + permissions: permissions.permissions, + Permissions: permissions.Permissions, + PermissionStatus: permissions.PermissionStatus, + // TODO(bartlomieju): why is this not in one of extensions? + serveHttp: httpRuntime.serveHttp, + resolveDns: net.resolveDns, + upgradeWebSocket: http.upgradeWebSocket, + utime: fs.utime, + utimeSync: fs.utimeSync, + kill: process.kill, + addSignalListener: signals.addSignalListener, + removeSignalListener: signals.removeSignalListener, + refTimer: timers.refTimer, + unrefTimer: timers.unrefTimer, + osRelease: os.osRelease, + osUptime: os.osUptime, + hostname: os.hostname, + systemMemoryInfo: os.systemMemoryInfo, + networkInterfaces: os.networkInterfaces, + consoleSize: tty.consoleSize, + gid: os.gid, + uid: os.uid, + Command: spawn.Command, + // TODO(bartlomieju): why is this exported? + ChildProcess: spawn.ChildProcess, +}; - __bootstrap.denoNsUnstable = { - DiagnosticCategory: __bootstrap.diagnostics.DiagnosticCategory, - listenDatagram: __bootstrap.net.listenDatagram, - umask: __bootstrap.fs.umask, - HttpClient: __bootstrap.fetch.HttpClient, - createHttpClient: __bootstrap.fetch.createHttpClient, - http: __bootstrap.http, - dlopen: __bootstrap.ffi.dlopen, - UnsafeCallback: __bootstrap.ffi.UnsafeCallback, - UnsafePointer: __bootstrap.ffi.UnsafePointer, - UnsafePointerView: __bootstrap.ffi.UnsafePointerView, - UnsafeFnPointer: __bootstrap.ffi.UnsafeFnPointer, - flock: __bootstrap.fs.flock, - flockSync: __bootstrap.fs.flockSync, - funlock: __bootstrap.fs.funlock, - funlockSync: __bootstrap.fs.funlockSync, - Child: __bootstrap.spawn.Child, - ChildProcess: __bootstrap.spawn.ChildProcess, - Command: __bootstrap.spawn.Command, - serve: __bootstrap.flash.serve, - upgradeHttp: __bootstrap.http.upgradeHttp, - upgradeHttpRaw: __bootstrap.flash.upgradeHttpRaw, - wsi: __bootstrap.wsi.wsi, - WSI: __bootstrap.wsi.WSI, - WSIModifierKey: __bootstrap.wsi.WSIModifierKey, - WSIWindow: __bootstrap.wsi.WSIWindow, - WSIWindowButton: __bootstrap.wsi.WSIWindowButton, - }; -})(this); +const denoNsUnstable = { + DiagnosticCategory: diagnostics.DiagnosticCategory, + listenDatagram: net.listenDatagram, + umask: fs.umask, + HttpClient: httpClient.HttpClient, + createHttpClient: httpClient.createHttpClient, + // TODO(bartlomieju): why is it needed? + http, + dlopen: ffi.dlopen, + UnsafeCallback: ffi.UnsafeCallback, + UnsafePointer: ffi.UnsafePointer, + UnsafePointerView: ffi.UnsafePointerView, + UnsafeFnPointer: ffi.UnsafeFnPointer, + flock: fs.flock, + flockSync: fs.flockSync, + funlock: fs.funlock, + funlockSync: fs.funlockSync, + upgradeHttp: http.upgradeHttp, + upgradeHttpRaw: flash.upgradeHttpRaw, + wsi: wsi.wsi, + WSI: wsi.WSI, + WSIModifierKey: wsi.WSIModifierKey, + WSIWindow: wsi.WSIWindow, + WSIWindowButton: wsi.WSIWindowButton, +}; + +export { denoNs, denoNsUnstable } diff --git a/runtime/js/98_global_scope.js b/runtime/js/98_global_scope.js index 3be66680e3d60f..a608a5b87c9464 100644 --- a/runtime/js/98_global_scope.js +++ b/runtime/js/98_global_scope.js @@ -1,338 +1,337 @@ // Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. // Copyright 2023 Jo Bates. All rights reserved. MIT license. -"use strict"; -((window) => { - const core = Deno.core; - const { - ObjectDefineProperties, - SymbolFor, - } = window.__bootstrap.primordials; +const core = globalThis.Deno.core; +const primordials = globalThis.__bootstrap.primordials; +const { + ObjectDefineProperties, + SymbolFor, +} = primordials; - const util = window.__bootstrap.util; - const location = window.__bootstrap.location; - const event = window.__bootstrap.event; - const eventTarget = window.__bootstrap.eventTarget; - const timers = window.__bootstrap.timers; - const base64 = window.__bootstrap.base64; - const encoding = window.__bootstrap.encoding; - const Console = window.__bootstrap.console.Console; - const caches = window.__bootstrap.caches; - const compression = window.__bootstrap.compression; - const worker = window.__bootstrap.worker; - const performance = window.__bootstrap.performance; - const crypto = window.__bootstrap.crypto; - const url = window.__bootstrap.url; - const urlPattern = window.__bootstrap.urlPattern; - const headers = window.__bootstrap.headers; - const streams = window.__bootstrap.streams; - const fileReader = window.__bootstrap.fileReader; - const webgpu = window.__bootstrap.webgpu; - const webSocket = window.__bootstrap.webSocket; - const broadcastChannel = window.__bootstrap.broadcastChannel; - const file = window.__bootstrap.file; - const formData = window.__bootstrap.formData; - const fetch = window.__bootstrap.fetch; - const messagePort = window.__bootstrap.messagePort; - const webidl = window.__bootstrap.webidl; - const domException = window.__bootstrap.domException; - const abortSignal = window.__bootstrap.abortSignal; - const globalInterfaces = window.__bootstrap.globalInterfaces; - const webStorage = window.__bootstrap.webStorage; - const prompt = window.__bootstrap.prompt; +import * as util from "internal:runtime/js/06_util.js"; +import * as location from "internal:deno_web/12_location.js"; +import * as event from "internal:deno_web/02_event.js"; +import * as timers from "internal:deno_web/02_timers.js"; +import * as base64 from "internal:deno_web/05_base64.js"; +import * as encoding from "internal:deno_web/08_text_encoding.js"; +import * as console from "internal:deno_console/02_console.js"; +import * as caches from "internal:deno_cache/01_cache.js"; +import * as compression from "internal:deno_web/14_compression.js"; +import * as worker from "internal:runtime/js/11_workers.js"; +import * as performance from "internal:deno_web/15_performance.js"; +import * as crypto from "internal:deno_crypto/00_crypto.js"; +import * as url from "internal:deno_url/00_url.js"; +import * as urlPattern from "internal:deno_url/01_urlpattern.js"; +import * as headers from "internal:deno_fetch/20_headers.js"; +import * as streams from "internal:deno_web/06_streams.js"; +import * as fileReader from "internal:deno_web/10_filereader.js"; +import * as webgpu from "internal:deno_webgpu/01_webgpu.js"; +import * as webSocket from "internal:deno_websocket/01_websocket.js"; +import * as webSocketStream from "internal:deno_websocket/02_websocketstream.js"; +import * as broadcastChannel from "internal:deno_broadcast_channel/01_broadcast_channel.js"; +import * as file from "internal:deno_web/09_file.js"; +import * as formData from "internal:deno_fetch/21_formdata.js"; +import * as request from "internal:deno_fetch/23_request.js"; +import * as response from "internal:deno_fetch/23_response.js"; +import * as fetch from "internal:deno_fetch/26_fetch.js"; +import * as messagePort from "internal:deno_web/13_message_port.js"; +import * as webidl from "internal:deno_webidl/00_webidl.js"; +import DOMException from "internal:deno_web/01_dom_exception.js"; +import * as abortSignal from "internal:deno_web/03_abort_signal.js"; +import * as globalInterfaces from "internal:deno_web/04_global_interfaces.js"; +import * as webStorage from "internal:deno_webstorage/01_webstorage.js"; +import * as prompt from "internal:runtime/js/41_prompt.js"; - // https://developer.mozilla.org/en-US/docs/Web/API/WindowOrWorkerGlobalScope - const windowOrWorkerGlobalScope = { - AbortController: util.nonEnumerable(abortSignal.AbortController), - AbortSignal: util.nonEnumerable(abortSignal.AbortSignal), - Blob: util.nonEnumerable(file.Blob), - ByteLengthQueuingStrategy: util.nonEnumerable( - streams.ByteLengthQueuingStrategy, - ), - CloseEvent: util.nonEnumerable(event.CloseEvent), - CompressionStream: util.nonEnumerable(compression.CompressionStream), - CountQueuingStrategy: util.nonEnumerable( - streams.CountQueuingStrategy, - ), - CryptoKey: util.nonEnumerable(crypto.CryptoKey), - CustomEvent: util.nonEnumerable(event.CustomEvent), - DecompressionStream: util.nonEnumerable(compression.DecompressionStream), - DOMException: util.nonEnumerable(domException.DOMException), - ErrorEvent: util.nonEnumerable(event.ErrorEvent), - Event: util.nonEnumerable(event.Event), - EventTarget: util.nonEnumerable(eventTarget.EventTarget), - File: util.nonEnumerable(file.File), - FileReader: util.nonEnumerable(fileReader.FileReader), - FormData: util.nonEnumerable(formData.FormData), - Headers: util.nonEnumerable(headers.Headers), - MessageEvent: util.nonEnumerable(event.MessageEvent), - Performance: util.nonEnumerable(performance.Performance), - PerformanceEntry: util.nonEnumerable(performance.PerformanceEntry), - PerformanceMark: util.nonEnumerable(performance.PerformanceMark), - PerformanceMeasure: util.nonEnumerable(performance.PerformanceMeasure), - PromiseRejectionEvent: util.nonEnumerable(event.PromiseRejectionEvent), - ProgressEvent: util.nonEnumerable(event.ProgressEvent), - ReadableStream: util.nonEnumerable(streams.ReadableStream), - ReadableStreamDefaultReader: util.nonEnumerable( - streams.ReadableStreamDefaultReader, - ), - Request: util.nonEnumerable(fetch.Request), - Response: util.nonEnumerable(fetch.Response), - TextDecoder: util.nonEnumerable(encoding.TextDecoder), - TextEncoder: util.nonEnumerable(encoding.TextEncoder), - TextDecoderStream: util.nonEnumerable(encoding.TextDecoderStream), - TextEncoderStream: util.nonEnumerable(encoding.TextEncoderStream), - TransformStream: util.nonEnumerable(streams.TransformStream), - URL: util.nonEnumerable(url.URL), - URLPattern: util.nonEnumerable(urlPattern.URLPattern), - URLSearchParams: util.nonEnumerable(url.URLSearchParams), - WebSocket: util.nonEnumerable(webSocket.WebSocket), - MessageChannel: util.nonEnumerable(messagePort.MessageChannel), - MessagePort: util.nonEnumerable(messagePort.MessagePort), - Worker: util.nonEnumerable(worker.Worker), - WritableStream: util.nonEnumerable(streams.WritableStream), - WritableStreamDefaultWriter: util.nonEnumerable( - streams.WritableStreamDefaultWriter, - ), - WritableStreamDefaultController: util.nonEnumerable( - streams.WritableStreamDefaultController, - ), - ReadableByteStreamController: util.nonEnumerable( - streams.ReadableByteStreamController, - ), - ReadableStreamBYOBReader: util.nonEnumerable( - streams.ReadableStreamBYOBReader, - ), - ReadableStreamBYOBRequest: util.nonEnumerable( - streams.ReadableStreamBYOBRequest, - ), - ReadableStreamDefaultController: util.nonEnumerable( - streams.ReadableStreamDefaultController, - ), - TransformStreamDefaultController: util.nonEnumerable( - streams.TransformStreamDefaultController, - ), - atob: util.writable(base64.atob), - btoa: util.writable(base64.btoa), - clearInterval: util.writable(timers.clearInterval), - clearTimeout: util.writable(timers.clearTimeout), - caches: { - enumerable: true, - configurable: true, - get: caches.cacheStorage, - }, - CacheStorage: util.nonEnumerable(caches.CacheStorage), - Cache: util.nonEnumerable(caches.Cache), - console: util.nonEnumerable( - new Console((msg, level) => core.print(msg, level > 1)), - ), - crypto: util.readOnly(crypto.crypto), - Crypto: util.nonEnumerable(crypto.Crypto), - SubtleCrypto: util.nonEnumerable(crypto.SubtleCrypto), - fetch: util.writable(fetch.fetch), - performance: util.writable(performance.performance), - reportError: util.writable(event.reportError), - setInterval: util.writable(timers.setInterval), - setTimeout: util.writable(timers.setTimeout), - structuredClone: util.writable(messagePort.structuredClone), - // Branding as a WebIDL object - [webidl.brand]: util.nonEnumerable(webidl.brand), - }; +// https://developer.mozilla.org/en-US/docs/Web/API/WindowOrWorkerGlobalScope +const windowOrWorkerGlobalScope = { + AbortController: util.nonEnumerable(abortSignal.AbortController), + AbortSignal: util.nonEnumerable(abortSignal.AbortSignal), + Blob: util.nonEnumerable(file.Blob), + ByteLengthQueuingStrategy: util.nonEnumerable( + streams.ByteLengthQueuingStrategy, + ), + CloseEvent: util.nonEnumerable(event.CloseEvent), + CompressionStream: util.nonEnumerable(compression.CompressionStream), + CountQueuingStrategy: util.nonEnumerable( + streams.CountQueuingStrategy, + ), + CryptoKey: util.nonEnumerable(crypto.CryptoKey), + CustomEvent: util.nonEnumerable(event.CustomEvent), + DecompressionStream: util.nonEnumerable(compression.DecompressionStream), + DOMException: util.nonEnumerable(DOMException), + ErrorEvent: util.nonEnumerable(event.ErrorEvent), + Event: util.nonEnumerable(event.Event), + EventTarget: util.nonEnumerable(event.EventTarget), + File: util.nonEnumerable(file.File), + FileReader: util.nonEnumerable(fileReader.FileReader), + FormData: util.nonEnumerable(formData.FormData), + Headers: util.nonEnumerable(headers.Headers), + MessageEvent: util.nonEnumerable(event.MessageEvent), + Performance: util.nonEnumerable(performance.Performance), + PerformanceEntry: util.nonEnumerable(performance.PerformanceEntry), + PerformanceMark: util.nonEnumerable(performance.PerformanceMark), + PerformanceMeasure: util.nonEnumerable(performance.PerformanceMeasure), + PromiseRejectionEvent: util.nonEnumerable(event.PromiseRejectionEvent), + ProgressEvent: util.nonEnumerable(event.ProgressEvent), + ReadableStream: util.nonEnumerable(streams.ReadableStream), + ReadableStreamDefaultReader: util.nonEnumerable( + streams.ReadableStreamDefaultReader, + ), + Request: util.nonEnumerable(request.Request), + Response: util.nonEnumerable(response.Response), + TextDecoder: util.nonEnumerable(encoding.TextDecoder), + TextEncoder: util.nonEnumerable(encoding.TextEncoder), + TextDecoderStream: util.nonEnumerable(encoding.TextDecoderStream), + TextEncoderStream: util.nonEnumerable(encoding.TextEncoderStream), + TransformStream: util.nonEnumerable(streams.TransformStream), + URL: util.nonEnumerable(url.URL), + URLPattern: util.nonEnumerable(urlPattern.URLPattern), + URLSearchParams: util.nonEnumerable(url.URLSearchParams), + WebSocket: util.nonEnumerable(webSocket.WebSocket), + MessageChannel: util.nonEnumerable(messagePort.MessageChannel), + MessagePort: util.nonEnumerable(messagePort.MessagePort), + Worker: util.nonEnumerable(worker.Worker), + WritableStream: util.nonEnumerable(streams.WritableStream), + WritableStreamDefaultWriter: util.nonEnumerable( + streams.WritableStreamDefaultWriter, + ), + WritableStreamDefaultController: util.nonEnumerable( + streams.WritableStreamDefaultController, + ), + ReadableByteStreamController: util.nonEnumerable( + streams.ReadableByteStreamController, + ), + ReadableStreamBYOBReader: util.nonEnumerable( + streams.ReadableStreamBYOBReader, + ), + ReadableStreamBYOBRequest: util.nonEnumerable( + streams.ReadableStreamBYOBRequest, + ), + ReadableStreamDefaultController: util.nonEnumerable( + streams.ReadableStreamDefaultController, + ), + TransformStreamDefaultController: util.nonEnumerable( + streams.TransformStreamDefaultController, + ), + atob: util.writable(base64.atob), + btoa: util.writable(base64.btoa), + clearInterval: util.writable(timers.clearInterval), + clearTimeout: util.writable(timers.clearTimeout), + caches: { + enumerable: true, + configurable: true, + get: caches.cacheStorage, + }, + CacheStorage: util.nonEnumerable(caches.CacheStorage), + Cache: util.nonEnumerable(caches.Cache), + console: util.nonEnumerable( + new console.Console((msg, level) => core.print(msg, level > 1)), + ), + crypto: util.readOnly(crypto.crypto), + Crypto: util.nonEnumerable(crypto.Crypto), + SubtleCrypto: util.nonEnumerable(crypto.SubtleCrypto), + fetch: util.writable(fetch.fetch), + performance: util.writable(performance.performance), + reportError: util.writable(event.reportError), + setInterval: util.writable(timers.setInterval), + setTimeout: util.writable(timers.setTimeout), + structuredClone: util.writable(messagePort.structuredClone), + // Branding as a WebIDL object + [webidl.brand]: util.nonEnumerable(webidl.brand), +}; - const unstableWindowOrWorkerGlobalScope = { - BroadcastChannel: util.nonEnumerable(broadcastChannel.BroadcastChannel), - WebSocketStream: util.nonEnumerable(webSocket.WebSocketStream), +const unstableWindowOrWorkerGlobalScope = { + BroadcastChannel: util.nonEnumerable(broadcastChannel.BroadcastChannel), + WebSocketStream: util.nonEnumerable(webSocketStream.WebSocketStream), - GPU: util.nonEnumerable(webgpu.GPU), - GPUAdapter: util.nonEnumerable(webgpu.GPUAdapter), - GPUAdapterInfo: util.nonEnumerable(webgpu.GPUAdapterInfo), - GPUSupportedLimits: util.nonEnumerable(webgpu.GPUSupportedLimits), - GPUSupportedFeatures: util.nonEnumerable(webgpu.GPUSupportedFeatures), - GPUDeviceLostInfo: util.nonEnumerable(webgpu.GPUDeviceLostInfo), - GPUDevice: util.nonEnumerable(webgpu.GPUDevice), - GPUQueue: util.nonEnumerable(webgpu.GPUQueue), - GPUBuffer: util.nonEnumerable(webgpu.GPUBuffer), - GPUBufferUsage: util.nonEnumerable(webgpu.GPUBufferUsage), - GPUMapMode: util.nonEnumerable(webgpu.GPUMapMode), - GPUTexture: util.nonEnumerable(webgpu.GPUTexture), - GPUTextureUsage: util.nonEnumerable(webgpu.GPUTextureUsage), - GPUTextureView: util.nonEnumerable(webgpu.GPUTextureView), - GPUSampler: util.nonEnumerable(webgpu.GPUSampler), - GPUBindGroupLayout: util.nonEnumerable(webgpu.GPUBindGroupLayout), - GPUPipelineLayout: util.nonEnumerable(webgpu.GPUPipelineLayout), - GPUBindGroup: util.nonEnumerable(webgpu.GPUBindGroup), - GPUShaderModule: util.nonEnumerable(webgpu.GPUShaderModule), - GPUShaderStage: util.nonEnumerable(webgpu.GPUShaderStage), - GPUComputePipeline: util.nonEnumerable(webgpu.GPUComputePipeline), - GPURenderPipeline: util.nonEnumerable(webgpu.GPURenderPipeline), - GPUColorWrite: util.nonEnumerable(webgpu.GPUColorWrite), - GPUCommandEncoder: util.nonEnumerable(webgpu.GPUCommandEncoder), - GPURenderPassEncoder: util.nonEnumerable(webgpu.GPURenderPassEncoder), - GPUComputePassEncoder: util.nonEnumerable(webgpu.GPUComputePassEncoder), - GPUCommandBuffer: util.nonEnumerable(webgpu.GPUCommandBuffer), - GPURenderBundleEncoder: util.nonEnumerable(webgpu.GPURenderBundleEncoder), - GPURenderBundle: util.nonEnumerable(webgpu.GPURenderBundle), - GPUQuerySet: util.nonEnumerable(webgpu.GPUQuerySet), - GPUError: util.nonEnumerable(webgpu.GPUError), - GPUOutOfMemoryError: util.nonEnumerable(webgpu.GPUOutOfMemoryError), - GPUValidationError: util.nonEnumerable(webgpu.GPUValidationError), - GPUSurface: util.nonEnumerable(webgpu.GPUSurface), - GPUSurfaceTexture: util.nonEnumerable(webgpu.GPUSurfaceTexture), - }; + GPU: util.nonEnumerable(webgpu.GPU), + GPUAdapter: util.nonEnumerable(webgpu.GPUAdapter), + GPUAdapterInfo: util.nonEnumerable(webgpu.GPUAdapterInfo), + GPUSupportedLimits: util.nonEnumerable(webgpu.GPUSupportedLimits), + GPUSupportedFeatures: util.nonEnumerable(webgpu.GPUSupportedFeatures), + GPUDeviceLostInfo: util.nonEnumerable(webgpu.GPUDeviceLostInfo), + GPUDevice: util.nonEnumerable(webgpu.GPUDevice), + GPUQueue: util.nonEnumerable(webgpu.GPUQueue), + GPUBuffer: util.nonEnumerable(webgpu.GPUBuffer), + GPUBufferUsage: util.nonEnumerable(webgpu.GPUBufferUsage), + GPUMapMode: util.nonEnumerable(webgpu.GPUMapMode), + GPUTexture: util.nonEnumerable(webgpu.GPUTexture), + GPUTextureUsage: util.nonEnumerable(webgpu.GPUTextureUsage), + GPUTextureView: util.nonEnumerable(webgpu.GPUTextureView), + GPUSampler: util.nonEnumerable(webgpu.GPUSampler), + GPUBindGroupLayout: util.nonEnumerable(webgpu.GPUBindGroupLayout), + GPUPipelineLayout: util.nonEnumerable(webgpu.GPUPipelineLayout), + GPUBindGroup: util.nonEnumerable(webgpu.GPUBindGroup), + GPUShaderModule: util.nonEnumerable(webgpu.GPUShaderModule), + GPUShaderStage: util.nonEnumerable(webgpu.GPUShaderStage), + GPUComputePipeline: util.nonEnumerable(webgpu.GPUComputePipeline), + GPURenderPipeline: util.nonEnumerable(webgpu.GPURenderPipeline), + GPUColorWrite: util.nonEnumerable(webgpu.GPUColorWrite), + GPUCommandEncoder: util.nonEnumerable(webgpu.GPUCommandEncoder), + GPURenderPassEncoder: util.nonEnumerable(webgpu.GPURenderPassEncoder), + GPUComputePassEncoder: util.nonEnumerable(webgpu.GPUComputePassEncoder), + GPUCommandBuffer: util.nonEnumerable(webgpu.GPUCommandBuffer), + GPURenderBundleEncoder: util.nonEnumerable(webgpu.GPURenderBundleEncoder), + GPURenderBundle: util.nonEnumerable(webgpu.GPURenderBundle), + GPUQuerySet: util.nonEnumerable(webgpu.GPUQuerySet), + GPUError: util.nonEnumerable(webgpu.GPUError), + GPUOutOfMemoryError: util.nonEnumerable(webgpu.GPUOutOfMemoryError), + GPUValidationError: util.nonEnumerable(webgpu.GPUValidationError), + GPUSurface: util.nonEnumerable(webgpu.GPUSurface), + GPUSurfaceTexture: util.nonEnumerable(webgpu.GPUSurfaceTexture), +}; - class Navigator { - constructor() { - webidl.illegalConstructor(); - } +class Navigator { + constructor() { + webidl.illegalConstructor(); + } - [SymbolFor("Deno.privateCustomInspect")](inspect) { - return `${this.constructor.name} ${inspect({})}`; - } + [SymbolFor("Deno.privateCustomInspect")](inspect) { + return `${this.constructor.name} ${inspect({})}`; } +} - const navigator = webidl.createBranded(Navigator); +const navigator = webidl.createBranded(Navigator); - let numCpus, userAgent, language; +let numCpus, userAgent, language; - function setNumCpus(val) { - numCpus = val; - } +function setNumCpus(val) { + numCpus = val; +} - function setUserAgent(val) { - userAgent = val; - } +function setUserAgent(val) { + userAgent = val; +} - function setLanguage(val) { - language = val; - } +function setLanguage(val) { + language = val; +} - ObjectDefineProperties(Navigator.prototype, { - gpu: { - configurable: true, - enumerable: true, - get() { - webidl.assertBranded(this, NavigatorPrototype); - return webgpu.gpu; - }, +ObjectDefineProperties(Navigator.prototype, { + gpu: { + configurable: true, + enumerable: true, + get() { + webidl.assertBranded(this, NavigatorPrototype); + return webgpu.gpu; }, - hardwareConcurrency: { - configurable: true, - enumerable: true, - get() { - webidl.assertBranded(this, NavigatorPrototype); - return numCpus; - }, + }, + hardwareConcurrency: { + configurable: true, + enumerable: true, + get() { + webidl.assertBranded(this, NavigatorPrototype); + return numCpus; }, - userAgent: { - configurable: true, - enumerable: true, - get() { - webidl.assertBranded(this, NavigatorPrototype); - return userAgent; - }, + }, + userAgent: { + configurable: true, + enumerable: true, + get() { + webidl.assertBranded(this, NavigatorPrototype); + return userAgent; }, - language: { - configurable: true, - enumerable: true, - get() { - webidl.assertBranded(this, NavigatorPrototype); - return language; - }, + }, + language: { + configurable: true, + enumerable: true, + get() { + webidl.assertBranded(this, NavigatorPrototype); + return language; }, - languages: { - configurable: true, - enumerable: true, - get() { - webidl.assertBranded(this, NavigatorPrototype); - return [language]; - }, + }, + languages: { + configurable: true, + enumerable: true, + get() { + webidl.assertBranded(this, NavigatorPrototype); + return [language]; }, - }); - const NavigatorPrototype = Navigator.prototype; + }, +}); +const NavigatorPrototype = Navigator.prototype; - class WorkerNavigator { - constructor() { - webidl.illegalConstructor(); - } +class WorkerNavigator { + constructor() { + webidl.illegalConstructor(); + } - [SymbolFor("Deno.privateCustomInspect")](inspect) { - return `${this.constructor.name} ${inspect({})}`; - } + [SymbolFor("Deno.privateCustomInspect")](inspect) { + return `${this.constructor.name} ${inspect({})}`; } +} - const workerNavigator = webidl.createBranded(WorkerNavigator); +const workerNavigator = webidl.createBranded(WorkerNavigator); - ObjectDefineProperties(WorkerNavigator.prototype, { - gpu: { +ObjectDefineProperties(WorkerNavigator.prototype, { + gpu: { + configurable: true, + enumerable: true, + get() { + webidl.assertBranded(this, WorkerNavigatorPrototype); + return webgpu.gpu; + }, + }, + hardwareConcurrency: { + configurable: true, + enumerable: true, + get() { + webidl.assertBranded(this, WorkerNavigatorPrototype); + return numCpus; + }, + language: { configurable: true, enumerable: true, get() { webidl.assertBranded(this, WorkerNavigatorPrototype); - return webgpu.gpu; + return language; }, }, - hardwareConcurrency: { + languages: { configurable: true, enumerable: true, get() { webidl.assertBranded(this, WorkerNavigatorPrototype); - return numCpus; - }, - language: { - configurable: true, - enumerable: true, - get() { - webidl.assertBranded(this, WorkerNavigatorPrototype); - return language; - }, - }, - languages: { - configurable: true, - enumerable: true, - get() { - webidl.assertBranded(this, WorkerNavigatorPrototype); - return [language]; - }, + return [language]; }, }, - }); - const WorkerNavigatorPrototype = WorkerNavigator.prototype; - - const mainRuntimeGlobalProperties = { - Location: location.locationConstructorDescriptor, - location: location.locationDescriptor, - Window: globalInterfaces.windowConstructorDescriptor, - window: util.getterOnly(() => globalThis), - self: util.getterOnly(() => globalThis), - Navigator: util.nonEnumerable(Navigator), - navigator: util.getterOnly(() => navigator), - alert: util.writable(prompt.alert), - confirm: util.writable(prompt.confirm), - prompt: util.writable(prompt.prompt), - localStorage: util.getterOnly(webStorage.localStorage), - sessionStorage: util.getterOnly(webStorage.sessionStorage), - Storage: util.nonEnumerable(webStorage.Storage), - }; + }, +}); +const WorkerNavigatorPrototype = WorkerNavigator.prototype; - const workerRuntimeGlobalProperties = { - WorkerLocation: location.workerLocationConstructorDescriptor, - location: location.workerLocationDescriptor, - WorkerGlobalScope: globalInterfaces.workerGlobalScopeConstructorDescriptor, - DedicatedWorkerGlobalScope: - globalInterfaces.dedicatedWorkerGlobalScopeConstructorDescriptor, - WorkerNavigator: util.nonEnumerable(WorkerNavigator), - navigator: util.getterOnly(() => workerNavigator), - self: util.getterOnly(() => globalThis), - }; +const mainRuntimeGlobalProperties = { + Location: location.locationConstructorDescriptor, + location: location.locationDescriptor, + Window: globalInterfaces.windowConstructorDescriptor, + window: util.getterOnly(() => globalThis), + self: util.getterOnly(() => globalThis), + Navigator: util.nonEnumerable(Navigator), + navigator: util.getterOnly(() => navigator), + alert: util.writable(prompt.alert), + confirm: util.writable(prompt.confirm), + prompt: util.writable(prompt.prompt), + localStorage: util.getterOnly(webStorage.localStorage), + sessionStorage: util.getterOnly(webStorage.sessionStorage), + Storage: util.nonEnumerable(webStorage.Storage), +}; - window.__bootstrap.globalScope = { - windowOrWorkerGlobalScope, - unstableWindowOrWorkerGlobalScope, - mainRuntimeGlobalProperties, - workerRuntimeGlobalProperties, +const workerRuntimeGlobalProperties = { + WorkerLocation: location.workerLocationConstructorDescriptor, + location: location.workerLocationDescriptor, + WorkerGlobalScope: globalInterfaces.workerGlobalScopeConstructorDescriptor, + DedicatedWorkerGlobalScope: + globalInterfaces.dedicatedWorkerGlobalScopeConstructorDescriptor, + WorkerNavigator: util.nonEnumerable(WorkerNavigator), + navigator: util.getterOnly(() => workerNavigator), + self: util.getterOnly(() => globalThis), +}; - setNumCpus, - setUserAgent, - setLanguage, - }; -})(this); +export { + mainRuntimeGlobalProperties, + setLanguage, + setNumCpus, + setUserAgent, + unstableWindowOrWorkerGlobalScope, + windowOrWorkerGlobalScope, + workerRuntimeGlobalProperties, +}; diff --git a/runtime/js/99_main.js b/runtime/js/99_main.js index e1d089bc5d899d..ffb479c32c8409 100644 --- a/runtime/js/99_main.js +++ b/runtime/js/99_main.js @@ -1,5 +1,4 @@ // Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. -"use strict"; // Removes the `__proto__` for security reasons. // https://tc39.es/ecma262/#sec-get-object.prototype.__proto__ @@ -8,678 +7,640 @@ delete Object.prototype.__proto__; // Remove Intl.v8BreakIterator because it is a non-standard API. delete Intl.v8BreakIterator; -((window) => { - const core = Deno.core; - const ops = core.ops; - const { - ArrayPrototypeIndexOf, - ArrayPrototypePush, - ArrayPrototypeShift, - ArrayPrototypeSplice, - ArrayPrototypeMap, - DateNow, - Error, - ErrorPrototype, - FunctionPrototypeCall, - FunctionPrototypeBind, - ObjectAssign, - ObjectDefineProperty, - ObjectDefineProperties, - ObjectFreeze, - ObjectPrototypeIsPrototypeOf, - ObjectSetPrototypeOf, - PromiseResolve, - Symbol, - SymbolFor, - SymbolIterator, - PromisePrototypeThen, - SafeWeakMap, - TypeError, - WeakMapPrototypeDelete, - WeakMapPrototypeGet, - WeakMapPrototypeSet, - } = window.__bootstrap.primordials; - const util = window.__bootstrap.util; - const event = window.__bootstrap.event; - const eventTarget = window.__bootstrap.eventTarget; - const location = window.__bootstrap.location; - const build = window.__bootstrap.build; - const version = window.__bootstrap.version; - const os = window.__bootstrap.os; - const timers = window.__bootstrap.timers; - const colors = window.__bootstrap.colors; - const inspectArgs = window.__bootstrap.console.inspectArgs; - const quoteString = window.__bootstrap.console.quoteString; - const internals = window.__bootstrap.internals; - const performance = window.__bootstrap.performance; - const url = window.__bootstrap.url; - const fetch = window.__bootstrap.fetch; - const messagePort = window.__bootstrap.messagePort; - const denoNs = window.__bootstrap.denoNs; - const denoNsUnstable = window.__bootstrap.denoNsUnstable; - const errors = window.__bootstrap.errors.errors; - const webidl = window.__bootstrap.webidl; - const domException = window.__bootstrap.domException; - const { defineEventHandler, reportException } = window.__bootstrap.event; - const { deserializeJsMessageData, serializeJsMessageData } = - window.__bootstrap.messagePort; - const { - windowOrWorkerGlobalScope, - unstableWindowOrWorkerGlobalScope, - workerRuntimeGlobalProperties, - mainRuntimeGlobalProperties, - setNumCpus, - setUserAgent, - setLanguage, - } = window.__bootstrap.globalScope; - - let windowIsClosing = false; - - function windowClose() { - if (!windowIsClosing) { - windowIsClosing = true; - // Push a macrotask to exit after a promise resolve. - // This is not perfect, but should be fine for first pass. - PromisePrototypeThen( - PromiseResolve(), - () => - FunctionPrototypeCall(timers.setTimeout, null, () => { - // This should be fine, since only Window/MainWorker has .close() - os.exit(0); - }, 0), - ); - } +const core = globalThis.Deno.core; +const ops = core.ops; +const internals = globalThis.__bootstrap.internals; +const primordials = globalThis.__bootstrap.primordials; +const { + ArrayPrototypeIndexOf, + ArrayPrototypePush, + ArrayPrototypeShift, + ArrayPrototypeSplice, + ArrayPrototypeMap, + DateNow, + Error, + ErrorPrototype, + FunctionPrototypeCall, + FunctionPrototypeBind, + ObjectAssign, + ObjectDefineProperty, + ObjectDefineProperties, + ObjectFreeze, + ObjectPrototypeIsPrototypeOf, + ObjectSetPrototypeOf, + PromiseResolve, + Symbol, + SymbolFor, + SymbolIterator, + PromisePrototypeThen, + SafeWeakMap, + TypeError, + WeakMapPrototypeDelete, + WeakMapPrototypeGet, + WeakMapPrototypeSet, +} = primordials; +import * as util from "internal:runtime/js/06_util.js"; +import * as event from "internal:deno_web/02_event.js"; +import * as location from "internal:deno_web/12_location.js"; +import * as build from "internal:runtime/js/01_build.js"; +import * as version from "internal:runtime/js/01_version.ts"; +import * as os from "internal:runtime/js/30_os.js"; +import * as timers from "internal:deno_web/02_timers.js"; +import * as colors from "internal:deno_console/01_colors.js"; +import * as net from "internal:deno_net/01_net.js"; +import { + inspectArgs, + quoteString, + wrapConsole, +} from "internal:deno_console/02_console.js"; +import * as performance from "internal:deno_web/15_performance.js"; +import * as url from "internal:deno_url/00_url.js"; +import * as fetch from "internal:deno_fetch/26_fetch.js"; +import * as messagePort from "internal:deno_web/13_message_port.js"; +import { denoNs, denoNsUnstable } from "internal:runtime/js/90_deno_ns.js"; +import { errors } from "internal:runtime/js/01_errors.js"; +import * as webidl from "internal:deno_webidl/00_webidl.js"; +import DOMException from "internal:deno_web/01_dom_exception.js"; +import * as flash from "internal:deno_flash/01_http.js"; +import { + mainRuntimeGlobalProperties, + setLanguage, + setNumCpus, + setUserAgent, + unstableWindowOrWorkerGlobalScope, + windowOrWorkerGlobalScope, + workerRuntimeGlobalProperties, +} from "internal:runtime/js/98_global_scope.js"; + +let windowIsClosing = false; +let globalThis_; + +function windowClose() { + if (!windowIsClosing) { + windowIsClosing = true; + // Push a macrotask to exit after a promise resolve. + // This is not perfect, but should be fine for first pass. + PromisePrototypeThen( + PromiseResolve(), + () => + FunctionPrototypeCall(timers.setTimeout, null, () => { + // This should be fine, since only Window/MainWorker has .close() + os.exit(0); + }, 0), + ); } +} - function workerClose() { - if (isClosing) { - return; - } - - isClosing = true; - ops.op_worker_close(); +function workerClose() { + if (isClosing) { + return; } - function postMessage(message, transferOrOptions = {}) { - const prefix = - "Failed to execute 'postMessage' on 'DedicatedWorkerGlobalScope'"; - webidl.requiredArguments(arguments.length, 1, { prefix }); - message = webidl.converters.any(message); - let options; - if ( - webidl.type(transferOrOptions) === "Object" && - transferOrOptions !== undefined && - transferOrOptions[SymbolIterator] !== undefined - ) { - const transfer = webidl.converters["sequence"]( - transferOrOptions, - { prefix, context: "Argument 2" }, - ); - options = { transfer }; - } else { - options = webidl.converters.StructuredSerializeOptions( - transferOrOptions, - { - prefix, - context: "Argument 2", - }, - ); - } - const { transfer } = options; - const data = serializeJsMessageData(message, transfer); - ops.op_worker_post_message(data); + isClosing = true; + ops.op_worker_close(); +} + +function postMessage(message, transferOrOptions = {}) { + const prefix = + "Failed to execute 'postMessage' on 'DedicatedWorkerGlobalScope'"; + webidl.requiredArguments(arguments.length, 1, { prefix }); + message = webidl.converters.any(message); + let options; + if ( + webidl.type(transferOrOptions) === "Object" && + transferOrOptions !== undefined && + transferOrOptions[SymbolIterator] !== undefined + ) { + const transfer = webidl.converters["sequence"]( + transferOrOptions, + { prefix, context: "Argument 2" }, + ); + options = { transfer }; + } else { + options = webidl.converters.StructuredSerializeOptions( + transferOrOptions, + { + prefix, + context: "Argument 2", + }, + ); } + const { transfer } = options; + const data = messagePort.serializeJsMessageData(message, transfer); + ops.op_worker_post_message(data); +} + +let isClosing = false; +let globalDispatchEvent; + +async function pollForMessages() { + if (!globalDispatchEvent) { + globalDispatchEvent = FunctionPrototypeBind( + globalThis.dispatchEvent, + globalThis, + ); + } + while (!isClosing) { + const data = await core.opAsync("op_worker_recv_message"); + if (data === null) break; + const v = messagePort.deserializeJsMessageData(data); + const message = v[0]; + const transferables = v[1]; + + const msgEvent = new event.MessageEvent("message", { + cancelable: false, + data: message, + ports: transferables.filter((t) => + ObjectPrototypeIsPrototypeOf(messagePort.MessagePortPrototype, t) + ), + }); - let isClosing = false; - let globalDispatchEvent; - - async function pollForMessages() { - if (!globalDispatchEvent) { - globalDispatchEvent = FunctionPrototypeBind( - globalThis.dispatchEvent, - globalThis, - ); - } - while (!isClosing) { - const data = await core.opAsync("op_worker_recv_message"); - if (data === null) break; - const v = deserializeJsMessageData(data); - const message = v[0]; - const transferables = v[1]; - - const msgEvent = new event.MessageEvent("message", { - cancelable: false, - data: message, - ports: transferables.filter((t) => - ObjectPrototypeIsPrototypeOf(messagePort.MessagePortPrototype, t) - ), + try { + globalDispatchEvent(msgEvent); + } catch (e) { + const errorEvent = new event.ErrorEvent("error", { + cancelable: true, + message: e.message, + lineno: e.lineNumber ? e.lineNumber + 1 : undefined, + colno: e.columnNumber ? e.columnNumber + 1 : undefined, + filename: e.fileName, + error: e, }); - try { - globalDispatchEvent(msgEvent); - } catch (e) { - const errorEvent = new event.ErrorEvent("error", { - cancelable: true, - message: e.message, - lineno: e.lineNumber ? e.lineNumber + 1 : undefined, - colno: e.columnNumber ? e.columnNumber + 1 : undefined, - filename: e.fileName, - error: e, - }); - - globalDispatchEvent(errorEvent); - if (!errorEvent.defaultPrevented) { - throw e; - } + globalDispatchEvent(errorEvent); + if (!errorEvent.defaultPrevented) { + throw e; } } } +} - let loadedMainWorkerScript = false; - - function importScripts(...urls) { - if (ops.op_worker_get_type() === "module") { - throw new TypeError("Can't import scripts in a module worker."); - } +let loadedMainWorkerScript = false; - const baseUrl = location.getLocationHref(); - const parsedUrls = ArrayPrototypeMap(urls, (scriptUrl) => { - try { - return new url.URL(scriptUrl, baseUrl ?? undefined).href; - } catch { - throw new domException.DOMException( - "Failed to parse URL.", - "SyntaxError", - ); - } - }); +function importScripts(...urls) { + if (ops.op_worker_get_type() === "module") { + throw new TypeError("Can't import scripts in a module worker."); + } - // A classic worker's main script has looser MIME type checks than any - // imported scripts, so we use `loadedMainWorkerScript` to distinguish them. - // TODO(andreubotella) Refactor worker creation so the main script isn't - // loaded with `importScripts()`. - const scripts = ops.op_worker_sync_fetch( - parsedUrls, - !loadedMainWorkerScript, - ); - loadedMainWorkerScript = true; + const baseUrl = location.getLocationHref(); + const parsedUrls = ArrayPrototypeMap(urls, (scriptUrl) => { + try { + return new url.URL(scriptUrl, baseUrl ?? undefined).href; + } catch { + throw new DOMException( + "Failed to parse URL.", + "SyntaxError", + ); + } + }); - for (let i = 0; i < scripts.length; ++i) { - const { url, script } = scripts[i]; - const err = core.evalContext(script, url)[1]; - if (err !== null) { - throw err.thrown; - } + // A classic worker's main script has looser MIME type checks than any + // imported scripts, so we use `loadedMainWorkerScript` to distinguish them. + // TODO(andreubotella) Refactor worker creation so the main script isn't + // loaded with `importScripts()`. + const scripts = ops.op_worker_sync_fetch( + parsedUrls, + !loadedMainWorkerScript, + ); + loadedMainWorkerScript = true; + + for (let i = 0; i < scripts.length; ++i) { + const { url, script } = scripts[i]; + const err = core.evalContext(script, url)[1]; + if (err !== null) { + throw err.thrown; } } - - function opMainModule() { - return ops.op_main_module(); +} + +function opMainModule() { + return ops.op_main_module(); +} + +function formatException(error) { + if (ObjectPrototypeIsPrototypeOf(ErrorPrototype, error)) { + return null; + } else if (typeof error == "string") { + return `Uncaught ${ + inspectArgs([quoteString(error)], { + colors: !colors.getNoColor(), + }) + }`; + } else { + return `Uncaught ${inspectArgs([error], { colors: !colors.getNoColor() })}`; } - - function formatException(error) { - if (ObjectPrototypeIsPrototypeOf(ErrorPrototype, error)) { - return null; - } else if (typeof error == "string") { - return `Uncaught ${ - inspectArgs([quoteString(error)], { - colors: !colors.getNoColor(), - }) - }`; - } else { - return `Uncaught ${ - inspectArgs([error], { colors: !colors.getNoColor() }) - }`; +} + +core.registerErrorClass("NotFound", errors.NotFound); +core.registerErrorClass("PermissionDenied", errors.PermissionDenied); +core.registerErrorClass("ConnectionRefused", errors.ConnectionRefused); +core.registerErrorClass("ConnectionReset", errors.ConnectionReset); +core.registerErrorClass("ConnectionAborted", errors.ConnectionAborted); +core.registerErrorClass("NotConnected", errors.NotConnected); +core.registerErrorClass("AddrInUse", errors.AddrInUse); +core.registerErrorClass("AddrNotAvailable", errors.AddrNotAvailable); +core.registerErrorClass("BrokenPipe", errors.BrokenPipe); +core.registerErrorClass("AlreadyExists", errors.AlreadyExists); +core.registerErrorClass("InvalidData", errors.InvalidData); +core.registerErrorClass("TimedOut", errors.TimedOut); +core.registerErrorClass("Interrupted", errors.Interrupted); +core.registerErrorClass("WouldBlock", errors.WouldBlock); +core.registerErrorClass("WriteZero", errors.WriteZero); +core.registerErrorClass("UnexpectedEof", errors.UnexpectedEof); +core.registerErrorClass("BadResource", errors.BadResource); +core.registerErrorClass("Http", errors.Http); +core.registerErrorClass("Busy", errors.Busy); +core.registerErrorClass("NotSupported", errors.NotSupported); +core.registerErrorBuilder( + "DOMExceptionOperationError", + function DOMExceptionOperationError(msg) { + return new DOMException(msg, "OperationError"); + }, +); +core.registerErrorBuilder( + "DOMExceptionQuotaExceededError", + function DOMExceptionQuotaExceededError(msg) { + return new DOMException(msg, "QuotaExceededError"); + }, +); +core.registerErrorBuilder( + "DOMExceptionNotSupportedError", + function DOMExceptionNotSupportedError(msg) { + return new DOMException(msg, "NotSupported"); + }, +); +core.registerErrorBuilder( + "DOMExceptionNetworkError", + function DOMExceptionNetworkError(msg) { + return new DOMException(msg, "NetworkError"); + }, +); +core.registerErrorBuilder( + "DOMExceptionAbortError", + function DOMExceptionAbortError(msg) { + return new DOMException(msg, "AbortError"); + }, +); +core.registerErrorBuilder( + "DOMExceptionInvalidCharacterError", + function DOMExceptionInvalidCharacterError(msg) { + return new DOMException(msg, "InvalidCharacterError"); + }, +); +core.registerErrorBuilder( + "DOMExceptionDataError", + function DOMExceptionDataError(msg) { + return new DOMException(msg, "DataError"); + }, +); + +function runtimeStart(runtimeOptions, source) { + core.setMacrotaskCallback(timers.handleTimerMacrotask); + core.setMacrotaskCallback(promiseRejectMacrotaskCallback); + core.setWasmStreamingCallback(fetch.handleWasmStreaming); + core.setReportExceptionCallback(event.reportException); + ops.op_set_format_exception_callback(formatException); + version.setVersions( + runtimeOptions.denoVersion, + runtimeOptions.v8Version, + runtimeOptions.tsVersion, + ); + build.setBuildInfo(runtimeOptions.target); + util.setLogDebug(runtimeOptions.debugFlag, source); + colors.setNoColor(runtimeOptions.noColor || !runtimeOptions.isTty); + // deno-lint-ignore prefer-primordials + Error.prepareStackTrace = core.prepareStackTrace; +} + +const pendingRejections = []; +const pendingRejectionsReasons = new SafeWeakMap(); + +function promiseRejectCallback(type, promise, reason) { + switch (type) { + case 0: { + ops.op_store_pending_promise_rejection(promise, reason); + ArrayPrototypePush(pendingRejections, promise); + WeakMapPrototypeSet(pendingRejectionsReasons, promise, reason); + break; + } + case 1: { + ops.op_remove_pending_promise_rejection(promise); + const index = ArrayPrototypeIndexOf(pendingRejections, promise); + if (index > -1) { + ArrayPrototypeSplice(pendingRejections, index, 1); + WeakMapPrototypeDelete(pendingRejectionsReasons, promise); + } + break; } + default: + return false; } - function runtimeStart(runtimeOptions, source) { - core.setMacrotaskCallback(timers.handleTimerMacrotask); - core.setMacrotaskCallback(promiseRejectMacrotaskCallback); - core.setWasmStreamingCallback(fetch.handleWasmStreaming); - core.setReportExceptionCallback(reportException); - ops.op_set_format_exception_callback(formatException); - version.setVersions( - runtimeOptions.denoVersion, - runtimeOptions.v8Version, - runtimeOptions.tsVersion, - ); - build.setBuildInfo(runtimeOptions.target); - util.setLogDebug(runtimeOptions.debugFlag, source); - colors.setNoColor(runtimeOptions.noColor || !runtimeOptions.isTty); - // deno-lint-ignore prefer-primordials - Error.prepareStackTrace = core.prepareStackTrace; - registerErrors(); - } + return !!globalThis_.onunhandledrejection || + event.listenerCount(globalThis_, "unhandledrejection") > 0; +} - function registerErrors() { - core.registerErrorClass("NotFound", errors.NotFound); - core.registerErrorClass("PermissionDenied", errors.PermissionDenied); - core.registerErrorClass("ConnectionRefused", errors.ConnectionRefused); - core.registerErrorClass("ConnectionReset", errors.ConnectionReset); - core.registerErrorClass("ConnectionAborted", errors.ConnectionAborted); - core.registerErrorClass("NotConnected", errors.NotConnected); - core.registerErrorClass("AddrInUse", errors.AddrInUse); - core.registerErrorClass("AddrNotAvailable", errors.AddrNotAvailable); - core.registerErrorClass("BrokenPipe", errors.BrokenPipe); - core.registerErrorClass("AlreadyExists", errors.AlreadyExists); - core.registerErrorClass("InvalidData", errors.InvalidData); - core.registerErrorClass("TimedOut", errors.TimedOut); - core.registerErrorClass("Interrupted", errors.Interrupted); - core.registerErrorClass("WriteZero", errors.WriteZero); - core.registerErrorClass("UnexpectedEof", errors.UnexpectedEof); - core.registerErrorClass("BadResource", errors.BadResource); - core.registerErrorClass("Http", errors.Http); - core.registerErrorClass("Busy", errors.Busy); - core.registerErrorClass("NotSupported", errors.NotSupported); - core.registerErrorBuilder( - "DOMExceptionOperationError", - function DOMExceptionOperationError(msg) { - return new domException.DOMException(msg, "OperationError"); - }, - ); - core.registerErrorBuilder( - "DOMExceptionQuotaExceededError", - function DOMExceptionQuotaExceededError(msg) { - return new domException.DOMException(msg, "QuotaExceededError"); - }, - ); - core.registerErrorBuilder( - "DOMExceptionNotSupportedError", - function DOMExceptionNotSupportedError(msg) { - return new domException.DOMException(msg, "NotSupported"); - }, - ); - core.registerErrorBuilder( - "DOMExceptionNetworkError", - function DOMExceptionNetworkError(msg) { - return new domException.DOMException(msg, "NetworkError"); - }, - ); - core.registerErrorBuilder( - "DOMExceptionAbortError", - function DOMExceptionAbortError(msg) { - return new domException.DOMException(msg, "AbortError"); - }, - ); - core.registerErrorBuilder( - "DOMExceptionInvalidCharacterError", - function DOMExceptionInvalidCharacterError(msg) { - return new domException.DOMException(msg, "InvalidCharacterError"); - }, +function promiseRejectMacrotaskCallback() { + while (pendingRejections.length > 0) { + const promise = ArrayPrototypeShift(pendingRejections); + const hasPendingException = ops.op_has_pending_promise_rejection( + promise, ); - core.registerErrorBuilder( - "DOMExceptionDataError", - function DOMExceptionDataError(msg) { - return new domException.DOMException(msg, "DataError"); + const reason = WeakMapPrototypeGet(pendingRejectionsReasons, promise); + WeakMapPrototypeDelete(pendingRejectionsReasons, promise); + + if (!hasPendingException) { + continue; + } + + const rejectionEvent = new event.PromiseRejectionEvent( + "unhandledrejection", + { + cancelable: true, + promise, + reason, }, ); - } - const pendingRejections = []; - const pendingRejectionsReasons = new SafeWeakMap(); - - function promiseRejectCallback(type, promise, reason) { - switch (type) { - case 0: { - ops.op_store_pending_promise_rejection(promise, reason); - ArrayPrototypePush(pendingRejections, promise); - WeakMapPrototypeSet(pendingRejectionsReasons, promise, reason); - break; - } - case 1: { + const errorEventCb = (event) => { + if (event.error === reason) { ops.op_remove_pending_promise_rejection(promise); - const index = ArrayPrototypeIndexOf(pendingRejections, promise); - if (index > -1) { - ArrayPrototypeSplice(pendingRejections, index, 1); - WeakMapPrototypeDelete(pendingRejectionsReasons, promise); - } - break; } - default: - return false; + }; + // Add a callback for "error" event - it will be dispatched + // if error is thrown during dispatch of "unhandledrejection" + // event. + globalThis_.addEventListener("error", errorEventCb); + globalThis_.dispatchEvent(rejectionEvent); + globalThis_.removeEventListener("error", errorEventCb); + + // If event was not prevented (or "unhandledrejection" listeners didn't + // throw) we will let Rust side handle it. + if (rejectionEvent.defaultPrevented) { + ops.op_remove_pending_promise_rejection(promise); } - - return !!globalThis.onunhandledrejection || - eventTarget.listenerCount(globalThis, "unhandledrejection") > 0; } + return true; +} - function promiseRejectMacrotaskCallback() { - while (pendingRejections.length > 0) { - const promise = ArrayPrototypeShift(pendingRejections); - const hasPendingException = ops.op_has_pending_promise_rejection( - promise, - ); - const reason = WeakMapPrototypeGet(pendingRejectionsReasons, promise); - WeakMapPrototypeDelete(pendingRejectionsReasons, promise); - - if (!hasPendingException) { - continue; - } - - const rejectionEvent = new event.PromiseRejectionEvent( - "unhandledrejection", - { - cancelable: true, - promise, - reason, - }, - ); +let hasBootstrapped = false; - const errorEventCb = (event) => { - if (event.error === reason) { - ops.op_remove_pending_promise_rejection(promise); - } - }; - // Add a callback for "error" event - it will be dispatched - // if error is thrown during dispatch of "unhandledrejection" - // event. - globalThis.addEventListener("error", errorEventCb); - globalThis.dispatchEvent(rejectionEvent); - globalThis.removeEventListener("error", errorEventCb); - - // If event was not prevented (or "unhandledrejection" listeners didn't - // throw) we will let Rust side handle it. - if (rejectionEvent.defaultPrevented) { - ops.op_remove_pending_promise_rejection(promise); - } - } - return true; +function bootstrapMainRuntime(runtimeOptions) { + if (hasBootstrapped) { + throw new Error("Worker runtime already bootstrapped"); } - let hasBootstrapped = false; + performance.setTimeOrigin(DateNow()); + globalThis_ = globalThis; - function bootstrapMainRuntime(runtimeOptions) { - if (hasBootstrapped) { - throw new Error("Worker runtime already bootstrapped"); - } + const consoleFromV8 = globalThis.Deno.core.console; - core.initializeAsyncOps(); - performance.setTimeOrigin(DateNow()); - - const consoleFromV8 = window.Deno.core.console; - const wrapConsole = window.__bootstrap.console.wrapConsole; - - // Remove bootstrapping data from the global scope - const __bootstrap = globalThis.__bootstrap; - delete globalThis.__bootstrap; - delete globalThis.bootstrap; - util.log("bootstrapMainRuntime"); - hasBootstrapped = true; - - // If the `--location` flag isn't set, make `globalThis.location` `undefined` and - // writable, so that they can mock it themselves if they like. If the flag was - // set, define `globalThis.location`, using the provided value. - if (runtimeOptions.location == null) { - mainRuntimeGlobalProperties.location = { - writable: true, - }; - } else { - location.setLocationHref(runtimeOptions.location); - } + // Remove bootstrapping data from the global scope + delete globalThis.__bootstrap; + delete globalThis.bootstrap; + util.log("bootstrapMainRuntime"); + hasBootstrapped = true; - ObjectDefineProperties(globalThis, windowOrWorkerGlobalScope); - if (runtimeOptions.unstableFlag) { - ObjectDefineProperties(globalThis, unstableWindowOrWorkerGlobalScope); - } - ObjectDefineProperties(globalThis, mainRuntimeGlobalProperties); - ObjectDefineProperties(globalThis, { - close: util.writable(windowClose), - closed: util.getterOnly(() => windowIsClosing), - }); - ObjectSetPrototypeOf(globalThis, Window.prototype); + // If the `--location` flag isn't set, make `globalThis.location` `undefined` and + // writable, so that they can mock it themselves if they like. If the flag was + // set, define `globalThis.location`, using the provided value. + if (runtimeOptions.location == null) { + mainRuntimeGlobalProperties.location = { + writable: true, + }; + } else { + location.setLocationHref(runtimeOptions.location); + } - if (runtimeOptions.inspectFlag) { - const consoleFromDeno = globalThis.console; - wrapConsole(consoleFromDeno, consoleFromV8); - } + ObjectDefineProperties(globalThis, windowOrWorkerGlobalScope); + if (runtimeOptions.unstableFlag) { + ObjectDefineProperties(globalThis, unstableWindowOrWorkerGlobalScope); + } + ObjectDefineProperties(globalThis, mainRuntimeGlobalProperties); + ObjectDefineProperties(globalThis, { + close: util.writable(windowClose), + closed: util.getterOnly(() => windowIsClosing), + }); + ObjectSetPrototypeOf(globalThis, Window.prototype); + + if (runtimeOptions.inspectFlag) { + const consoleFromDeno = globalThis.console; + wrapConsole(consoleFromDeno, consoleFromV8); + } - eventTarget.setEventTargetData(globalThis); + event.setEventTargetData(globalThis); + event.saveGlobalThisReference(globalThis); - defineEventHandler(window, "error"); - defineEventHandler(window, "load"); - defineEventHandler(window, "beforeunload"); - defineEventHandler(window, "unload"); - defineEventHandler(window, "unhandledrejection"); + event.defineEventHandler(globalThis, "error"); + event.defineEventHandler(globalThis, "load"); + event.defineEventHandler(globalThis, "beforeunload"); + event.defineEventHandler(globalThis, "unload"); + event.defineEventHandler(globalThis, "unhandledrejection"); - core.setPromiseRejectCallback(promiseRejectCallback); + core.setPromiseRejectCallback(promiseRejectCallback); - const isUnloadDispatched = SymbolFor("isUnloadDispatched"); - // Stores the flag for checking whether unload is dispatched or not. - // This prevents the recursive dispatches of unload events. - // See https://github.com/denoland/deno/issues/9201. - window[isUnloadDispatched] = false; - window.addEventListener("unload", () => { - window[isUnloadDispatched] = true; - }); + const isUnloadDispatched = SymbolFor("isUnloadDispatched"); + // Stores the flag for checking whether unload is dispatched or not. + // This prevents the recursive dispatches of unload events. + // See https://github.com/denoland/deno/issues/9201. + globalThis[isUnloadDispatched] = false; + globalThis.addEventListener("unload", () => { + globalThis_[isUnloadDispatched] = true; + }); - runtimeStart(runtimeOptions); + runtimeStart(runtimeOptions); + + setNumCpus(runtimeOptions.cpuCount); + setUserAgent(runtimeOptions.userAgent); + setLanguage(runtimeOptions.locale); + + const internalSymbol = Symbol("Deno.internal"); + + // These have to initialized here and not in `90_deno_ns.js` because + // the op function that needs to be passed will be invalidated by creating + // a snapshot + ObjectAssign(internals, { + nodeUnstable: { + serve: flash.createServe(ops.op_node_unstable_flash_serve), + upgradeHttpRaw: flash.upgradeHttpRaw, + listenDatagram: net.createListenDatagram( + ops.op_node_unstable_net_listen_udp, + ops.op_node_unstable_net_listen_unixpacket, + ), + }, + }); - setNumCpus(runtimeOptions.cpuCount); - setUserAgent(runtimeOptions.userAgent); - setLanguage(runtimeOptions.locale); + // FIXME(bartlomieju): temporarily add whole `Deno.core` to + // `Deno[Deno.internal]` namespace. It should be removed and only necessary + // methods should be left there. + ObjectAssign(internals, { + core, + }); - const internalSymbol = Symbol("Deno.internal"); + const finalDenoNs = { + internal: internalSymbol, + [internalSymbol]: internals, + resources: core.resources, + close: core.close, + ...denoNs, + }; + ObjectDefineProperties(finalDenoNs, { + pid: util.readOnly(runtimeOptions.pid), + ppid: util.readOnly(runtimeOptions.ppid), + noColor: util.readOnly(runtimeOptions.noColor), + args: util.readOnly(ObjectFreeze(runtimeOptions.args)), + mainModule: util.getterOnly(opMainModule), + }); + if (runtimeOptions.unstableFlag) { + ObjectAssign(finalDenoNs, denoNsUnstable); // These have to initialized here and not in `90_deno_ns.js` because // the op function that needs to be passed will be invalidated by creating // a snapshot - ObjectAssign(internals, { - nodeUnstable: { - Command: __bootstrap.spawn.createCommand( - __bootstrap.spawn.createSpawn(ops.op_node_unstable_spawn_child), - __bootstrap.spawn.createSpawnSync( - ops.op_node_unstable_spawn_sync, - ), - __bootstrap.spawn.createSpawnChild( - ops.op_node_unstable_spawn_child, - ), - ), - serve: __bootstrap.flash.createServe(ops.op_node_unstable_flash_serve), - upgradeHttpRaw: __bootstrap.flash.upgradeHttpRaw, - listenDatagram: __bootstrap.net.createListenDatagram( - ops.op_node_unstable_net_listen_udp, - ops.op_node_unstable_net_listen_unixpacket, - ), - osUptime: __bootstrap.os.createOsUptime(ops.op_node_unstable_os_uptime), - }, + ObjectAssign(finalDenoNs, { + serve: flash.createServe(ops.op_flash_serve), + listenDatagram: net.createListenDatagram( + ops.op_net_listen_udp, + ops.op_net_listen_unixpacket, + ), }); + } - // FIXME(bartlomieju): temporarily add whole `Deno.core` to - // `Deno[Deno.internal]` namespace. It should be removed and only necessary - // methods should be left there. - ObjectAssign(internals, { - core, - }); + // Setup `Deno` global - we're actually overriding already existing global + // `Deno` with `Deno` namespace from "./deno.ts". + ObjectDefineProperty(globalThis, "Deno", util.readOnly(finalDenoNs)); - const finalDenoNs = { - internal: internalSymbol, - [internalSymbol]: internals, - resources: core.resources, - close: core.close, - ...denoNs, - }; - ObjectDefineProperties(finalDenoNs, { - pid: util.readOnly(runtimeOptions.pid), - ppid: util.readOnly(runtimeOptions.ppid), - noColor: util.readOnly(runtimeOptions.noColor), - args: util.readOnly(ObjectFreeze(runtimeOptions.args)), - mainModule: util.getterOnly(opMainModule), - }); + util.log("args", runtimeOptions.args); +} - if (runtimeOptions.unstableFlag) { - ObjectAssign(finalDenoNs, denoNsUnstable); - // These have to initialized here and not in `90_deno_ns.js` because - // the op function that needs to be passed will be invalidated by creating - // a snapshot - ObjectAssign(finalDenoNs, { - Command: __bootstrap.spawn.createCommand( - __bootstrap.spawn.createSpawn(ops.op_spawn_child), - __bootstrap.spawn.createSpawnSync(ops.op_spawn_sync), - __bootstrap.spawn.createSpawnChild(ops.op_spawn_child), - ), - serve: __bootstrap.flash.createServe(ops.op_flash_serve), - listenDatagram: __bootstrap.net.createListenDatagram( - ops.op_net_listen_udp, - ops.op_net_listen_unixpacket, - ), - osUptime: __bootstrap.os.createOsUptime(ops.op_os_uptime), - }); - } +function bootstrapWorkerRuntime( + runtimeOptions, + name, + internalName, +) { + if (hasBootstrapped) { + throw new Error("Worker runtime already bootstrapped"); + } - // Setup `Deno` global - we're actually overriding already existing global - // `Deno` with `Deno` namespace from "./deno.ts". - ObjectDefineProperty(globalThis, "Deno", util.readOnly(finalDenoNs)); + performance.setTimeOrigin(DateNow()); + globalThis_ = globalThis; - util.log("args", runtimeOptions.args); + const consoleFromV8 = globalThis.Deno.core.console; + + // Remove bootstrapping data from the global scope + delete globalThis.__bootstrap; + delete globalThis.bootstrap; + util.log("bootstrapWorkerRuntime"); + hasBootstrapped = true; + ObjectDefineProperties(globalThis, windowOrWorkerGlobalScope); + if (runtimeOptions.unstableFlag) { + ObjectDefineProperties(globalThis, unstableWindowOrWorkerGlobalScope); } + ObjectDefineProperties(globalThis, workerRuntimeGlobalProperties); + ObjectDefineProperties(globalThis, { + name: util.writable(name), + // TODO(bartlomieju): should be readonly? + close: util.nonEnumerable(workerClose), + postMessage: util.writable(postMessage), + }); + if (runtimeOptions.enableTestingFeaturesFlag) { + ObjectDefineProperty( + globalThis, + "importScripts", + util.writable(importScripts), + ); + } + ObjectSetPrototypeOf(globalThis, DedicatedWorkerGlobalScope.prototype); - function bootstrapWorkerRuntime( - runtimeOptions, - name, - internalName, - ) { - if (hasBootstrapped) { - throw new Error("Worker runtime already bootstrapped"); - } + const consoleFromDeno = globalThis.console; + wrapConsole(consoleFromDeno, consoleFromV8); - core.initializeAsyncOps(); - performance.setTimeOrigin(DateNow()); - - const consoleFromV8 = window.Deno.core.console; - const wrapConsole = window.__bootstrap.console.wrapConsole; - - // Remove bootstrapping data from the global scope - const __bootstrap = globalThis.__bootstrap; - delete globalThis.__bootstrap; - delete globalThis.bootstrap; - util.log("bootstrapWorkerRuntime"); - hasBootstrapped = true; - ObjectDefineProperties(globalThis, windowOrWorkerGlobalScope); - if (runtimeOptions.unstableFlag) { - ObjectDefineProperties(globalThis, unstableWindowOrWorkerGlobalScope); - } - ObjectDefineProperties(globalThis, workerRuntimeGlobalProperties); - ObjectDefineProperties(globalThis, { - name: util.writable(name), - // TODO(bartlomieju): should be readonly? - close: util.nonEnumerable(workerClose), - postMessage: util.writable(postMessage), - }); - if (runtimeOptions.enableTestingFeaturesFlag) { - ObjectDefineProperty( - globalThis, - "importScripts", - util.writable(importScripts), - ); - } - ObjectSetPrototypeOf(globalThis, DedicatedWorkerGlobalScope.prototype); + event.setEventTargetData(globalThis); + event.saveGlobalThisReference(globalThis); - const consoleFromDeno = globalThis.console; - wrapConsole(consoleFromDeno, consoleFromV8); + event.defineEventHandler(self, "message"); + event.defineEventHandler(self, "error", undefined, true); + event.defineEventHandler(self, "unhandledrejection"); - eventTarget.setEventTargetData(globalThis); + core.setPromiseRejectCallback(promiseRejectCallback); - defineEventHandler(self, "message"); - defineEventHandler(self, "error", undefined, true); - defineEventHandler(self, "unhandledrejection"); + // `Deno.exit()` is an alias to `self.close()`. Setting and exit + // code using an op in worker context is a no-op. + os.setExitHandler((_exitCode) => { + workerClose(); + }); - core.setPromiseRejectCallback(promiseRejectCallback); + runtimeStart( + runtimeOptions, + internalName ?? name, + ); - // `Deno.exit()` is an alias to `self.close()`. Setting and exit - // code using an op in worker context is a no-op. - os.setExitHandler((_exitCode) => { - workerClose(); - }); + location.setLocationHref(runtimeOptions.location); - runtimeStart( - runtimeOptions, - internalName ?? name, - ); + setNumCpus(runtimeOptions.cpuCount); + setLanguage(runtimeOptions.locale); - location.setLocationHref(runtimeOptions.location); + globalThis.pollForMessages = pollForMessages; - setNumCpus(runtimeOptions.cpuCount); - setLanguage(runtimeOptions.locale); + const internalSymbol = Symbol("Deno.internal"); - globalThis.pollForMessages = pollForMessages; + // These have to initialized here and not in `90_deno_ns.js` because + // the op function that needs to be passed will be invalidated by creating + // a snapshot + ObjectAssign(internals, { + nodeUnstable: { + serve: flash.createServe(ops.op_node_unstable_flash_serve), + upgradeHttpRaw: flash.upgradeHttpRaw, + listenDatagram: net.createListenDatagram( + ops.op_node_unstable_net_listen_udp, + ops.op_node_unstable_net_listen_unixpacket, + ), + }, + }); - const internalSymbol = Symbol("Deno.internal"); + // FIXME(bartlomieju): temporarily add whole `Deno.core` to + // `Deno[Deno.internal]` namespace. It should be removed and only necessary + // methods should be left there. + ObjectAssign(internals, { + core, + }); + const finalDenoNs = { + internal: internalSymbol, + [internalSymbol]: internals, + resources: core.resources, + close: core.close, + ...denoNs, + }; + if (runtimeOptions.unstableFlag) { + ObjectAssign(finalDenoNs, denoNsUnstable); // These have to initialized here and not in `90_deno_ns.js` because // the op function that needs to be passed will be invalidated by creating // a snapshot - ObjectAssign(internals, { - nodeUnstable: { - Command: __bootstrap.spawn.createCommand( - __bootstrap.spawn.createSpawn(ops.op_node_unstable_spawn_child), - __bootstrap.spawn.createSpawnSync( - ops.op_node_unstable_spawn_sync, - ), - __bootstrap.spawn.createSpawnChild( - ops.op_node_unstable_spawn_child, - ), - ), - serve: __bootstrap.flash.createServe(ops.op_node_unstable_flash_serve), - upgradeHttpRaw: __bootstrap.flash.upgradeHttpRaw, - listenDatagram: __bootstrap.net.createListenDatagram( - ops.op_node_unstable_net_listen_udp, - ops.op_node_unstable_net_listen_unixpacket, - ), - osUptime: __bootstrap.os.createOsUptime(ops.op_node_unstable_os_uptime), - }, - }); - - // FIXME(bartlomieju): temporarily add whole `Deno.core` to - // `Deno[Deno.internal]` namespace. It should be removed and only necessary - // methods should be left there. - ObjectAssign(internals, { - core, + ObjectAssign(finalDenoNs, { + serve: flash.createServe(ops.op_flash_serve), + listenDatagram: net.createListenDatagram( + ops.op_net_listen_udp, + ops.op_net_listen_unixpacket, + ), }); - - const finalDenoNs = { - internal: internalSymbol, - [internalSymbol]: internals, - resources: core.resources, - close: core.close, - ...denoNs, - }; - if (runtimeOptions.unstableFlag) { - ObjectAssign(finalDenoNs, denoNsUnstable); - // These have to initialized here and not in `90_deno_ns.js` because - // the op function that needs to be passed will be invalidated by creating - // a snapshot - ObjectAssign(finalDenoNs, { - Command: __bootstrap.spawn.createCommand( - __bootstrap.spawn.createSpawn(ops.op_spawn_child), - __bootstrap.spawn.createSpawnSync(ops.op_spawn_sync), - __bootstrap.spawn.createSpawnChild(ops.op_spawn_child), - ), - serve: __bootstrap.flash.createServe(ops.op_flash_serve), - listenDatagram: __bootstrap.net.createListenDatagram( - ops.op_net_listen_udp, - ops.op_net_listen_unixpacket, - ), - osUptime: __bootstrap.os.createOsUptime(ops.op_os_uptime), - }); - } - ObjectDefineProperties(finalDenoNs, { - pid: util.readOnly(runtimeOptions.pid), - noColor: util.readOnly(runtimeOptions.noColor), - args: util.readOnly(ObjectFreeze(runtimeOptions.args)), - }); - // Setup `Deno` global - we're actually overriding already - // existing global `Deno` with `Deno` namespace from "./deno.ts". - ObjectDefineProperty(globalThis, "Deno", util.readOnly(finalDenoNs)); } - - ObjectDefineProperties(globalThis, { - bootstrap: { - value: { - mainRuntime: bootstrapMainRuntime, - workerRuntime: bootstrapWorkerRuntime, - }, - configurable: true, - }, + ObjectDefineProperties(finalDenoNs, { + pid: util.readOnly(runtimeOptions.pid), + noColor: util.readOnly(runtimeOptions.noColor), + args: util.readOnly(ObjectFreeze(runtimeOptions.args)), }); -})(this); + // Setup `Deno` global - we're actually overriding already + // existing global `Deno` with `Deno` namespace from "./deno.ts". + ObjectDefineProperty(globalThis, "Deno", util.readOnly(finalDenoNs)); +} + +ObjectDefineProperties(globalThis, { + bootstrap: { + value: { + mainRuntime: bootstrapMainRuntime, + workerRuntime: bootstrapWorkerRuntime, + }, + configurable: true, + }, +}); diff --git a/runtime/ops/fs.rs b/runtime/ops/fs.rs index f6b9a58ebfd97c..80aec11aed227b 100644 --- a/runtime/ops/fs.rs +++ b/runtime/ops/fs.rs @@ -107,6 +107,10 @@ pub fn init() -> Extension { .build() } +fn default_err_mapper(err: Error, desc: String) -> Error { + Error::new(err.kind(), format!("{err}, {desc}")) +} + #[derive(Deserialize, Default, Debug)] #[serde(rename_all = "camelCase")] #[serde(default)] @@ -188,7 +192,7 @@ fn op_open_sync( let (path, open_options) = open_helper(state, &path, mode, options.as_ref(), "Deno.openSync()")?; let std_file = open_options.open(&path).map_err(|err| { - Error::new(err.kind(), format!("{}, open '{}'", err, path.display())) + default_err_mapper(err, format!("open '{}'", path.display())) })?; let resource = StdFileResource::fs_file(std_file); let rid = state.resource_table.add(resource); @@ -211,7 +215,7 @@ async fn op_open_async( )?; let std_file = tokio::task::spawn_blocking(move || { open_options.open(&path).map_err(|err| { - Error::new(err.kind(), format!("{}, open '{}'", err, path.display())) + default_err_mapper(err, format!("open '{}'", path.display())) }) }) .await?; @@ -267,14 +271,6 @@ async fn op_write_file_async( data: ZeroCopyBuf, cancel_rid: Option, ) -> Result<(), AnyError> { - let cancel_handle = match cancel_rid { - Some(cancel_rid) => state - .borrow_mut() - .resource_table - .get::(cancel_rid) - .ok(), - None => None, - }; let (path, open_options) = open_helper( &mut state.borrow_mut(), &path, @@ -282,15 +278,30 @@ async fn op_write_file_async( Some(&write_open_options(create, append, create_new)), "Deno.writeFile()", )?; + let write_future = tokio::task::spawn_blocking(move || { write_file(&path, open_options, mode, data) }); + + let cancel_handle = cancel_rid.and_then(|rid| { + state + .borrow_mut() + .resource_table + .get::(rid) + .ok() + }); + if let Some(cancel_handle) = cancel_handle { - write_future.or_cancel(cancel_handle).await???; - } else { - write_future.await??; + let write_future_rv = write_future.or_cancel(cancel_handle).await; + + if let Some(cancel_rid) = cancel_rid { + state.borrow_mut().resource_table.close(cancel_rid).ok(); + }; + + return write_future_rv??; } - Ok(()) + + write_future.await? } fn write_file( @@ -300,7 +311,7 @@ fn write_file( data: ZeroCopyBuf, ) -> Result<(), AnyError> { let mut std_file = open_options.open(path).map_err(|err| { - Error::new(err.kind(), format!("{}, open '{}'", err, path.display())) + default_err_mapper(err, format!("open '{}'", path.display())) })?; // need to chmod the file if it already exists and a mode is specified @@ -308,11 +319,9 @@ fn write_file( if let Some(mode) = _mode { use std::os::unix::fs::PermissionsExt; let permissions = PermissionsExt::from_mode(mode & 0o777); - std_file - .set_permissions(permissions) - .map_err(|err: Error| { - Error::new(err.kind(), format!("{}, chmod '{}'", err, path.display())) - })?; + std_file.set_permissions(permissions).map_err(|err| { + default_err_mapper(err, format!("chmod '{}'", path.display())) + })?; } std_file.write_all(&data)?; @@ -541,9 +550,8 @@ fn op_chdir(state: &mut OpState, directory: String) -> Result<(), AnyError> { state .borrow_mut::() .check_read(&d, "Deno.chdir()")?; - set_current_dir(&d).map_err(|err| { - Error::new(err.kind(), format!("{err}, chdir '{directory}'")) - })?; + set_current_dir(&d) + .map_err(|err| default_err_mapper(err, format!("chdir '{directory}'")))?; Ok(()) } @@ -571,7 +579,7 @@ fn op_mkdir_sync(state: &mut OpState, args: MkdirArgs) -> Result<(), AnyError> { builder.mode(mode); } builder.create(&path).map_err(|err| { - Error::new(err.kind(), format!("{}, mkdir '{}'", err, path.display())) + default_err_mapper(err, format!("mkdir '{}'", path.display())) })?; Ok(()) } @@ -601,7 +609,7 @@ async fn op_mkdir_async( builder.mode(mode); } builder.create(&path).map_err(|err| { - Error::new(err.kind(), format!("{}, mkdir '{}'", err, path.display())) + default_err_mapper(err, format!("mkdir '{}'", path.display())) })?; Ok(()) }) @@ -646,9 +654,8 @@ async fn op_chmod_async( } fn raw_chmod(path: &Path, _raw_mode: u32) -> Result<(), AnyError> { - let err_mapper = |err: Error| { - Error::new(err.kind(), format!("{}, chmod '{}'", err, path.display())) - }; + let err_mapper = + |err| default_err_mapper(err, format!("chmod '{}'", path.display())); #[cfg(unix)] { use std::os::unix::fs::PermissionsExt; @@ -755,9 +762,8 @@ fn op_remove_sync( #[cfg(not(unix))] use std::os::windows::prelude::MetadataExt; - let err_mapper = |err: Error| { - Error::new(err.kind(), format!("{}, remove '{}'", err, path.display())) - }; + let err_mapper = + |err| default_err_mapper(err, format!("remove '{}'", path.display())); let metadata = std::fs::symlink_metadata(&path).map_err(err_mapper)?; let file_type = metadata.file_type(); @@ -804,9 +810,8 @@ async fn op_remove_async( tokio::task::spawn_blocking(move || { #[cfg(not(unix))] use std::os::windows::prelude::MetadataExt; - let err_mapper = |err: Error| { - Error::new(err.kind(), format!("{}, remove '{}'", err, path.display())) - }; + let err_mapper = + |err| default_err_mapper(err, format!("remove '{}'", path.display())); let metadata = std::fs::symlink_metadata(&path).map_err(err_mapper)?; debug!("op_remove_async {} {}", path.display(), recursive); @@ -866,15 +871,10 @@ fn op_copy_file_sync( )); } - let err_mapper = |err: Error| { - Error::new( - err.kind(), - format!( - "{}, copy '{}' -> '{}'", - err, - from_path.display(), - to_path.display() - ), + let err_mapper = |err| { + default_err_mapper( + err, + format!("copy '{}' -> '{}'", from_path.display(), to_path.display()), ) }; @@ -981,15 +981,13 @@ async fn op_copy_file_async( ), )); } - - let err_mapper = |err: Error| { - Error::new( - err.kind(), - format!("{}, copy '{}' -> '{}'", err, from.display(), to.display()), - ) - }; // returns size of from as u64 (we ignore) - std::fs::copy(&from, &to).map_err(err_mapper)?; + std::fs::copy(&from, &to).map_err(|err| { + default_err_mapper( + err, + format!("copy '{}' -> '{}'", from.display(), to.display()), + ) + })?; Ok(()) }) .await @@ -1125,9 +1123,8 @@ fn op_stat_sync( state .borrow_mut::() .check_read(&path, "Deno.statSync()")?; - let err_mapper = |err: Error| { - Error::new(err.kind(), format!("{}, stat '{}'", err, path.display())) - }; + let err_mapper = + |err| default_err_mapper(err, format!("stat '{}'", path.display())); let metadata = if lstat { std::fs::symlink_metadata(&path).map_err(err_mapper)? } else { @@ -1157,9 +1154,8 @@ async fn op_stat_async( tokio::task::spawn_blocking(move || { debug!("op_stat_async {} {}", path.display(), lstat); - let err_mapper = |err: Error| { - Error::new(err.kind(), format!("{}, stat '{}'", err, path.display())) - }; + let err_mapper = + |err| default_err_mapper(err, format!("stat '{}'", path.display())); let metadata = if lstat { std::fs::symlink_metadata(&path).map_err(err_mapper)? } else { @@ -1249,11 +1245,11 @@ fn op_read_dir_sync( .check_read(&path, "Deno.readDirSync()")?; debug!("op_read_dir_sync {}", path.display()); - let err_mapper = |err: Error| { - Error::new(err.kind(), format!("{}, readdir '{}'", err, path.display())) - }; + let entries: Vec<_> = std::fs::read_dir(&path) - .map_err(err_mapper)? + .map_err(|err| { + default_err_mapper(err, format!("readdir '{}'", path.display())) + })? .filter_map(|entry| { let entry = entry.unwrap(); // Not all filenames can be encoded as UTF-8. Skip those for now. @@ -1293,11 +1289,11 @@ async fn op_read_dir_async( } tokio::task::spawn_blocking(move || { debug!("op_read_dir_async {}", path.display()); - let err_mapper = |err: Error| { - Error::new(err.kind(), format!("{}, readdir '{}'", err, path.display())) - }; + let entries: Vec<_> = std::fs::read_dir(&path) - .map_err(err_mapper)? + .map_err(|err| { + default_err_mapper(err, format!("readdir '{}'", path.display())) + })? .filter_map(|entry| { let entry = entry.unwrap(); // Not all filenames can be encoded as UTF-8. Skip those for now. @@ -1340,18 +1336,12 @@ fn op_rename_sync( permissions.check_write(&oldpath, "Deno.renameSync()")?; permissions.check_write(&newpath, "Deno.renameSync()")?; - let err_mapper = |err: Error| { - Error::new( - err.kind(), - format!( - "{}, rename '{}' -> '{}'", - err, - oldpath.display(), - newpath.display() - ), + std::fs::rename(&oldpath, &newpath).map_err(|err| { + default_err_mapper( + err, + format!("rename '{}' -> '{}'", oldpath.display(), newpath.display()), ) - }; - std::fs::rename(&oldpath, &newpath).map_err(err_mapper)?; + })?; Ok(()) } @@ -1370,19 +1360,14 @@ async fn op_rename_async( permissions.check_write(&oldpath, "Deno.rename()")?; permissions.check_write(&newpath, "Deno.rename()")?; } + tokio::task::spawn_blocking(move || { - let err_mapper = |err: Error| { - Error::new( - err.kind(), - format!( - "{}, rename '{}' -> '{}'", - err, - oldpath.display(), - newpath.display() - ), + std::fs::rename(&oldpath, &newpath).map_err(|err| { + default_err_mapper( + err, + format!("rename '{}' -> '{}'", oldpath.display(), newpath.display()), ) - }; - std::fs::rename(&oldpath, &newpath).map_err(err_mapper)?; + })?; Ok(()) }) .await @@ -1404,18 +1389,12 @@ fn op_link_sync( permissions.check_read(&newpath, "Deno.linkSync()")?; permissions.check_write(&newpath, "Deno.linkSync()")?; - let err_mapper = |err: Error| { - Error::new( - err.kind(), - format!( - "{}, link '{}' -> '{}'", - err, - oldpath.display(), - newpath.display() - ), + std::fs::hard_link(&oldpath, &newpath).map_err(|err| { + default_err_mapper( + err, + format!("link '{}' -> '{}'", oldpath.display(), newpath.display()), ) - }; - std::fs::hard_link(&oldpath, &newpath).map_err(err_mapper)?; + })?; Ok(()) } @@ -1438,15 +1417,10 @@ async fn op_link_async( } tokio::task::spawn_blocking(move || { - let err_mapper = |err: Error| { - Error::new( - err.kind(), - format!( - "{}, link '{}' -> '{}'", - err, - oldpath.display(), - newpath.display() - ), + let err_mapper = |err| { + default_err_mapper( + err, + format!("link '{}' -> '{}'", oldpath.display(), newpath.display()), ) }; std::fs::hard_link(&oldpath, &newpath).map_err(err_mapper)?; @@ -1473,17 +1447,13 @@ fn op_symlink_sync( .borrow_mut::() .check_read_all("Deno.symlinkSync()")?; - let err_mapper = |err: Error| { - Error::new( - err.kind(), - format!( - "{}, symlink '{}' -> '{}'", - err, - oldpath.display(), - newpath.display() - ), + let err_mapper = |err| { + default_err_mapper( + err, + format!("symlink '{}' -> '{}'", oldpath.display(), newpath.display()), ) }; + #[cfg(unix)] { use std::os::unix::fs::symlink; @@ -1540,17 +1510,12 @@ async fn op_symlink_async( } tokio::task::spawn_blocking(move || { - let err_mapper = |err: Error| { - Error::new( - err.kind(), - format!( - "{}, symlink '{}' -> '{}'", - err, - oldpath.display(), - newpath.display() - ), - ) - }; + let err_mapper = |err| default_err_mapper(err, format!( + "symlink '{}' -> '{}'", + oldpath.display(), + newpath.display() + )); + #[cfg(unix)] { use std::os::unix::fs::symlink; @@ -1600,14 +1565,10 @@ fn op_read_link_sync( .check_read(&path, "Deno.readLink()")?; debug!("op_read_link_value {}", path.display()); - let err_mapper = |err: Error| { - Error::new( - err.kind(), - format!("{}, readlink '{}'", err, path.display()), - ) - }; let target = std::fs::read_link(&path) - .map_err(err_mapper)? + .map_err(|err| { + default_err_mapper(err, format!("readlink '{}'", path.display())) + })? .into_os_string(); let targetstr = into_string(target)?; Ok(targetstr) @@ -1627,14 +1588,10 @@ async fn op_read_link_async( } tokio::task::spawn_blocking(move || { debug!("op_read_link_async {}", path.display()); - let err_mapper = |err: Error| { - Error::new( - err.kind(), - format!("{}, readlink '{}'", err, path.display()), - ) - }; let target = std::fs::read_link(&path) - .map_err(err_mapper)? + .map_err(|err| { + default_err_mapper(err, format!("readlink '{}'", path.display())) + })? .into_os_string(); let targetstr = into_string(target)?; Ok(targetstr) @@ -1684,12 +1641,8 @@ fn op_truncate_sync( .check_write(&path, "Deno.truncateSync()")?; debug!("op_truncate_sync {} {}", path.display(), len); - let err_mapper = |err: Error| { - Error::new( - err.kind(), - format!("{}, truncate '{}'", err, path.display()), - ) - }; + let err_mapper = + |err| default_err_mapper(err, format!("truncate '{}'", path.display())); let f = std::fs::OpenOptions::new() .write(true) .open(&path) @@ -1714,12 +1667,8 @@ async fn op_truncate_async( } tokio::task::spawn_blocking(move || { debug!("op_truncate_async {} {}", path.display(), len); - let err_mapper = |err: Error| { - Error::new( - err.kind(), - format!("{}, truncate '{}'", err, path.display()), - ) - }; + let err_mapper = + |err| default_err_mapper(err, format!("truncate '{}'", path.display())); let f = std::fs::OpenOptions::new() .write(true) .open(&path) @@ -1966,7 +1915,7 @@ fn op_utime_sync( .borrow_mut::() .check_write(&path, "Deno.utime()")?; filetime::set_file_times(&path, atime, mtime).map_err(|err| { - Error::new(err.kind(), format!("{}, utime '{}'", err, path.display())) + default_err_mapper(err, format!("utime '{}'", path.display())) })?; Ok(()) } @@ -1991,7 +1940,7 @@ async fn op_utime_async( tokio::task::spawn_blocking(move || { filetime::set_file_times(&path, atime, mtime).map_err(|err| { - Error::new(err.kind(), format!("{}, utime '{}'", err, path.display())) + default_err_mapper(err, format!("utime '{}'", path.display())) })?; Ok(()) }) @@ -2018,7 +1967,13 @@ fn op_readfile_sync( state .borrow_mut::() .check_read(path, "Deno.readFileSync()")?; - Ok(std::fs::read(path)?.into()) + Ok( + std::fs::read(path) + .map_err(|err| { + default_err_mapper(err, format!("readfile '{}'", path.display())) + })? + .into(), + ) } #[op] @@ -2030,7 +1985,9 @@ fn op_readfile_text_sync( state .borrow_mut::() .check_read(path, "Deno.readTextFileSync()")?; - Ok(string_from_utf8_lossy(std::fs::read(path)?)) + Ok(string_from_utf8_lossy(std::fs::read(path).map_err( + |err| default_err_mapper(err, format!("readfile '{}'", path.display())), + )?)) } #[op] @@ -2046,20 +2003,33 @@ async fn op_readfile_async( .borrow_mut::() .check_read(path, "Deno.readFile()")?; } - let fut = tokio::task::spawn_blocking(move || { + + let read_future = tokio::task::spawn_blocking(move || { let path = Path::new(&path); - Ok(std::fs::read(path).map(ZeroCopyBuf::from)?) + Ok(std::fs::read(path).map(ZeroCopyBuf::from).map_err(|err| { + default_err_mapper(err, format!("readfile '{}'", path.display())) + })?) }); - if let Some(cancel_rid) = cancel_rid { - let cancel_handle = state + + let cancel_handle = cancel_rid.and_then(|rid| { + state .borrow_mut() .resource_table - .get::(cancel_rid); - if let Ok(cancel_handle) = cancel_handle { - return fut.or_cancel(cancel_handle).await??; - } + .get::(rid) + .ok() + }); + + if let Some(cancel_handle) = cancel_handle { + let read_future_rv = read_future.or_cancel(cancel_handle).await; + + if let Some(cancel_rid) = cancel_rid { + state.borrow_mut().resource_table.close(cancel_rid).ok(); + }; + + return read_future_rv??; } - fut.await? + + read_future.await? } #[op] @@ -2075,20 +2045,33 @@ async fn op_readfile_text_async( .borrow_mut::() .check_read(path, "Deno.readTextFile()")?; } - let fut = tokio::task::spawn_blocking(move || { + + let read_future = tokio::task::spawn_blocking(move || { let path = Path::new(&path); - Ok(string_from_utf8_lossy(std::fs::read(path)?)) + Ok(string_from_utf8_lossy(std::fs::read(path).map_err( + |err| default_err_mapper(err, format!("readfile '{}'", path.display())), + )?)) }); - if let Some(cancel_rid) = cancel_rid { - let cancel_handle = state + + let cancel_handle = cancel_rid.and_then(|rid| { + state .borrow_mut() .resource_table - .get::(cancel_rid); - if let Ok(cancel_handle) = cancel_handle { - return fut.or_cancel(cancel_handle).await??; - } + .get::(rid) + .ok() + }); + + if let Some(cancel_handle) = cancel_handle { + let read_future_rv = read_future.or_cancel(cancel_handle).await; + + if let Some(cancel_rid) = cancel_rid { + state.borrow_mut().resource_table.close(cancel_rid).ok(); + }; + + return read_future_rv??; } - fut.await? + + read_future.await? } // Like String::from_utf8_lossy but operates on owned values diff --git a/runtime/ops/os/mod.rs b/runtime/ops/os/mod.rs index f970c318bc687e..020634c32f2715 100644 --- a/runtime/ops/os/mod.rs +++ b/runtime/ops/os/mod.rs @@ -419,7 +419,6 @@ fn os_uptime(state: &mut OpState) -> Result { #[op] fn op_os_uptime(state: &mut OpState) -> Result { - super::check_unstable(state, "Deno.osUptime"); os_uptime(state) } diff --git a/runtime/ops/runtime.rs b/runtime/ops/runtime.rs index 70814e3b868741..22a481e7765579 100644 --- a/runtime/ops/runtime.rs +++ b/runtime/ops/runtime.rs @@ -1,7 +1,6 @@ // Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. use crate::permissions::PermissionsContainer; -use deno_core::anyhow::Context; use deno_core::error::AnyError; use deno_core::op; use deno_core::Extension; @@ -20,17 +19,15 @@ pub fn init(main_module: ModuleSpecifier) -> Extension { #[op] fn op_main_module(state: &mut OpState) -> Result { - let main = state.borrow::().to_string(); - let main_url = deno_core::resolve_url_or_path(&main)?; + let main_url = state.borrow::(); + let main_path = main_url.to_string(); if main_url.scheme() == "file" { - let main_path = std::env::current_dir() - .context("Failed to get current working directory")? - .join(main_url.to_string()); + let main_path = main_url.to_file_path().unwrap(); state .borrow_mut::() .check_read_blind(&main_path, "main_module", "Deno.mainModule")?; } - Ok(main) + Ok(main_path) } pub fn ppid() -> i64 { diff --git a/runtime/ops/spawn.rs b/runtime/ops/spawn.rs index e5454371e202b2..089a53c238705d 100644 --- a/runtime/ops/spawn.rs +++ b/runtime/ops/spawn.rs @@ -31,10 +31,8 @@ pub fn init() -> Extension { Extension::builder("deno_spawn") .ops(vec![ op_spawn_child::decl(), - op_node_unstable_spawn_child::decl(), op_spawn_wait::decl(), op_spawn_sync::decl(), - op_node_unstable_spawn_sync::decl(), ]) .build() } @@ -125,75 +123,11 @@ pub struct SpawnOutput { stderr: Option, } -fn node_unstable_create_command( - state: &mut OpState, - args: SpawnArgs, - api_name: &str, -) -> Result { - state - .borrow_mut::() - .check_run(&args.cmd, api_name)?; - - let mut command = std::process::Command::new(args.cmd); - - #[cfg(windows)] - if args.windows_raw_arguments { - for arg in args.args.iter() { - command.raw_arg(arg); - } - } else { - command.args(args.args); - } - - #[cfg(not(windows))] - command.args(args.args); - - if let Some(cwd) = args.cwd { - command.current_dir(cwd); - } - - if args.clear_env { - command.env_clear(); - } - command.envs(args.env); - - #[cfg(unix)] - if let Some(gid) = args.gid { - command.gid(gid); - } - #[cfg(unix)] - if let Some(uid) = args.uid { - command.uid(uid); - } - #[cfg(unix)] - // TODO(bartlomieju): - #[allow(clippy::undocumented_unsafe_blocks)] - unsafe { - command.pre_exec(|| { - libc::setgroups(0, std::ptr::null()); - Ok(()) - }); - } - - command.stdin(args.stdio.stdin.as_stdio()); - command.stdout(match args.stdio.stdout { - Stdio::Inherit => StdioOrRid::Rid(1).as_stdio(state)?, - value => value.as_stdio(), - }); - command.stderr(match args.stdio.stderr { - Stdio::Inherit => StdioOrRid::Rid(2).as_stdio(state)?, - value => value.as_stdio(), - }); - - Ok(command) -} - fn create_command( state: &mut OpState, args: SpawnArgs, api_name: &str, ) -> Result { - super::check_unstable(state, "Deno.Command"); state .borrow_mut::() .check_run(&args.cmd, api_name)?; @@ -223,12 +157,10 @@ fn create_command( #[cfg(unix)] if let Some(gid) = args.gid { - super::check_unstable(state, "Deno.CommandOptions.gid"); command.gid(gid); } #[cfg(unix)] if let Some(uid) = args.uid { - super::check_unstable(state, "Deno.CommandOptions.uid"); command.uid(uid); } #[cfg(unix)] @@ -313,16 +245,6 @@ fn op_spawn_child( spawn_child(state, command) } -#[op] -fn op_node_unstable_spawn_child( - state: &mut OpState, - args: SpawnArgs, - api_name: String, -) -> Result { - let command = node_unstable_create_command(state, args, &api_name)?; - spawn_child(state, command) -} - #[op] async fn op_spawn_wait( state: Rc>, @@ -365,29 +287,3 @@ fn op_spawn_sync( }, }) } - -#[op] -fn op_node_unstable_spawn_sync( - state: &mut OpState, - args: SpawnArgs, -) -> Result { - let stdout = matches!(args.stdio.stdout, Stdio::Piped); - let stderr = matches!(args.stdio.stderr, Stdio::Piped); - let output = - node_unstable_create_command(state, args, "Deno.Command().outputSync()")? - .output()?; - - Ok(SpawnOutput { - status: output.status.try_into()?, - stdout: if stdout { - Some(output.stdout.into()) - } else { - None - }, - stderr: if stderr { - Some(output.stderr.into()) - } else { - None - }, - }) -} diff --git a/runtime/permissions/mod.rs b/runtime/permissions/mod.rs index 2981463123ae6e..035c39304f71f4 100644 --- a/runtime/permissions/mod.rs +++ b/runtime/permissions/mod.rs @@ -90,7 +90,7 @@ impl PermissionState { api_name: Option<&str>, info: Option<&str>, prompt: bool, - ) -> (Result<(), AnyError>, bool) { + ) -> (Result<(), AnyError>, bool, bool) { self.check2(name, api_name, || info.map(|s| s.to_string()), prompt) } @@ -101,11 +101,11 @@ impl PermissionState { api_name: Option<&str>, info: impl Fn() -> Option, prompt: bool, - ) -> (Result<(), AnyError>, bool) { + ) -> (Result<(), AnyError>, bool, bool) { match self { PermissionState::Granted => { Self::log_perm_access(name, info); - (Ok(()), false) + (Ok(()), false, false) } PermissionState::Prompt if prompt => { let msg = format!( @@ -113,14 +113,19 @@ impl PermissionState { name, info().map_or(String::new(), |info| { format!(" to {info}") }), ); - if PromptResponse::Allow == permission_prompt(&msg, name, api_name) { - Self::log_perm_access(name, info); - (Ok(()), true) - } else { - (Err(Self::error(name, info)), true) + match permission_prompt(&msg, name, api_name, true) { + PromptResponse::Allow => { + Self::log_perm_access(name, info); + (Ok(()), true, false) + } + PromptResponse::AllowAll => { + Self::log_perm_access(name, info); + (Ok(()), true, true) + } + PromptResponse::Deny => (Err(Self::error(name, info)), true, false), } } - _ => (Err(Self::error(name, info)), false), + _ => (Err(Self::error(name, info)), false, false), } } } @@ -161,6 +166,7 @@ impl UnitPermission { &format!("access to {}", self.description), self.name, Some("Deno.permissions.query()"), + false, ) { self.state = PermissionState::Granted; @@ -179,7 +185,7 @@ impl UnitPermission { } pub fn check(&mut self) -> Result<(), AnyError> { - let (result, prompted) = + let (result, prompted, _is_allow_all) = self.state.check(self.name, None, None, self.prompt); if prompted { if result.is_ok() { @@ -357,19 +363,26 @@ impl UnaryPermission { let (resolved_path, display_path) = resolved_and_display_path(path); let state = self.query(Some(&resolved_path)); if state == PermissionState::Prompt { - if PromptResponse::Allow - == permission_prompt( - &format!("read access to \"{}\"", display_path.display()), - self.name, - Some("Deno.permissions.query()"), - ) - { - self.granted_list.insert(ReadDescriptor(resolved_path)); - PermissionState::Granted - } else { - self.denied_list.insert(ReadDescriptor(resolved_path)); - self.global_state = PermissionState::Denied; - PermissionState::Denied + match permission_prompt( + &format!("read access to \"{}\"", display_path.display()), + self.name, + Some("Deno.permissions.query()"), + true, + ) { + PromptResponse::Allow => { + self.granted_list.insert(ReadDescriptor(resolved_path)); + PermissionState::Granted + } + PromptResponse::Deny => { + self.denied_list.insert(ReadDescriptor(resolved_path)); + self.global_state = PermissionState::Denied; + PermissionState::Denied + } + PromptResponse::AllowAll => { + self.granted_list.clear(); + self.global_state = PermissionState::Granted; + PermissionState::Granted + } } } else if state == PermissionState::Granted { self.granted_list.insert(ReadDescriptor(resolved_path)); @@ -385,6 +398,7 @@ impl UnaryPermission { "read access", self.name, Some("Deno.permissions.query()"), + true, ) { self.granted_list.clear(); @@ -421,7 +435,7 @@ impl UnaryPermission { path: &Path, api_name: Option<&str>, ) -> Result<(), AnyError> { - let (result, prompted) = self.query(Some(path)).check2( + let (result, prompted, is_allow_all) = self.query(Some(path)).check2( self.name, api_name, || Some(format!("\"{}\"", path.to_path_buf().display())), @@ -430,7 +444,12 @@ impl UnaryPermission { if prompted { let resolved_path = resolve_from_cwd(path)?; if result.is_ok() { - self.granted_list.insert(ReadDescriptor(resolved_path)); + if is_allow_all { + self.granted_list.clear(); + self.global_state = PermissionState::Granted; + } else { + self.granted_list.insert(ReadDescriptor(resolved_path)); + } } else { self.denied_list.insert(ReadDescriptor(resolved_path)); self.global_state = PermissionState::Denied; @@ -448,25 +467,33 @@ impl UnaryPermission { api_name: &str, ) -> Result<(), AnyError> { let resolved_path = resolve_from_cwd(path)?; - let (result, prompted) = self.query(Some(&resolved_path)).check( - self.name, - Some(api_name), - Some(&format!("<{display}>")), - self.prompt, - ); + let (result, prompted, is_allow_all) = + self.query(Some(&resolved_path)).check( + self.name, + Some(api_name), + Some(&format!("<{display}>")), + self.prompt, + ); if prompted { if result.is_ok() { - self.granted_list.insert(ReadDescriptor(resolved_path)); + if is_allow_all { + self.granted_list.clear(); + self.global_state = PermissionState::Granted; + } else { + self.granted_list.insert(ReadDescriptor(resolved_path)); + } } else { - self.denied_list.insert(ReadDescriptor(resolved_path)); self.global_state = PermissionState::Denied; + if !is_allow_all { + self.denied_list.insert(ReadDescriptor(resolved_path)); + } } } result } pub fn check_all(&mut self, api_name: Option<&str>) -> Result<(), AnyError> { - let (result, prompted) = + let (result, prompted, _) = self .query(None) .check(self.name, api_name, Some("all"), self.prompt); @@ -530,19 +557,26 @@ impl UnaryPermission { let (resolved_path, display_path) = resolved_and_display_path(path); let state = self.query(Some(&resolved_path)); if state == PermissionState::Prompt { - if PromptResponse::Allow - == permission_prompt( - &format!("write access to \"{}\"", display_path.display()), - self.name, - Some("Deno.permissions.query()"), - ) - { - self.granted_list.insert(WriteDescriptor(resolved_path)); - PermissionState::Granted - } else { - self.denied_list.insert(WriteDescriptor(resolved_path)); - self.global_state = PermissionState::Denied; - PermissionState::Denied + match permission_prompt( + &format!("write access to \"{}\"", display_path.display()), + self.name, + Some("Deno.permissions.query()"), + true, + ) { + PromptResponse::Allow => { + self.granted_list.insert(WriteDescriptor(resolved_path)); + PermissionState::Granted + } + PromptResponse::Deny => { + self.denied_list.insert(WriteDescriptor(resolved_path)); + self.global_state = PermissionState::Denied; + PermissionState::Denied + } + PromptResponse::AllowAll => { + self.granted_list.clear(); + self.global_state = PermissionState::Granted; + PermissionState::Granted + } } } else if state == PermissionState::Granted { self.granted_list.insert(WriteDescriptor(resolved_path)); @@ -558,6 +592,7 @@ impl UnaryPermission { "write access", self.name, Some("Deno.permissions.query()"), + true, ) { self.granted_list.clear(); @@ -594,7 +629,7 @@ impl UnaryPermission { path: &Path, api_name: Option<&str>, ) -> Result<(), AnyError> { - let (result, prompted) = self.query(Some(path)).check2( + let (result, prompted, is_allow_all) = self.query(Some(path)).check2( self.name, api_name, || Some(format!("\"{}\"", path.to_path_buf().display())), @@ -603,7 +638,12 @@ impl UnaryPermission { if prompted { let resolved_path = resolve_from_cwd(path)?; if result.is_ok() { - self.granted_list.insert(WriteDescriptor(resolved_path)); + if is_allow_all { + self.granted_list.clear(); + self.global_state = PermissionState::Granted; + } else { + self.granted_list.insert(WriteDescriptor(resolved_path)); + } } else { self.denied_list.insert(WriteDescriptor(resolved_path)); self.global_state = PermissionState::Denied; @@ -613,7 +653,7 @@ impl UnaryPermission { } pub fn check_all(&mut self, api_name: Option<&str>) -> Result<(), AnyError> { - let (result, prompted) = + let (result, prompted, _) = self .query(None) .check(self.name, api_name, Some("all"), self.prompt); @@ -685,19 +725,26 @@ impl UnaryPermission { let state = self.query(Some(host)); let host = NetDescriptor::new(&host); if state == PermissionState::Prompt { - if PromptResponse::Allow - == permission_prompt( - &format!("network access to \"{host}\""), - self.name, - Some("Deno.permissions.query()"), - ) - { - self.granted_list.insert(host); - PermissionState::Granted - } else { - self.denied_list.insert(host); - self.global_state = PermissionState::Denied; - PermissionState::Denied + match permission_prompt( + &format!("network access to \"{host}\""), + self.name, + Some("Deno.permissions.query()"), + true, + ) { + PromptResponse::Allow => { + self.granted_list.insert(host); + PermissionState::Granted + } + PromptResponse::Deny => { + self.denied_list.insert(host); + self.global_state = PermissionState::Denied; + PermissionState::Denied + } + PromptResponse::AllowAll => { + self.granted_list.clear(); + self.global_state = PermissionState::Granted; + PermissionState::Granted + } } } else if state == PermissionState::Granted { self.granted_list.insert(host); @@ -713,6 +760,7 @@ impl UnaryPermission { "network access", self.name, Some("Deno.permissions.query()"), + true, ) { self.granted_list.clear(); @@ -756,7 +804,7 @@ impl UnaryPermission { api_name: Option<&str>, ) -> Result<(), AnyError> { let new_host = NetDescriptor::new(&host); - let (result, prompted) = self.query(Some(host)).check( + let (result, prompted, is_allow_all) = self.query(Some(host)).check( self.name, api_name, Some(&format!("\"{new_host}\"")), @@ -764,7 +812,12 @@ impl UnaryPermission { ); if prompted { if result.is_ok() { - self.granted_list.insert(new_host); + if is_allow_all { + self.granted_list.clear(); + self.global_state = PermissionState::Granted; + } else { + self.granted_list.insert(new_host); + } } else { self.denied_list.insert(new_host); self.global_state = PermissionState::Denied; @@ -787,7 +840,7 @@ impl UnaryPermission { Some(port) => format!("{hostname}:{port}"), }; let host = &(&hostname, url.port_or_known_default()); - let (result, prompted) = self.query(Some(host)).check( + let (result, prompted, is_allow_all) = self.query(Some(host)).check( self.name, api_name, Some(&format!("\"{display_host}\"")), @@ -795,7 +848,12 @@ impl UnaryPermission { ); if prompted { if result.is_ok() { - self.granted_list.insert(NetDescriptor::new(&host)); + if is_allow_all { + self.granted_list.clear(); + self.global_state = PermissionState::Granted; + } else { + self.granted_list.insert(NetDescriptor::new(&host)); + } } else { self.denied_list.insert(NetDescriptor::new(&host)); self.global_state = PermissionState::Denied; @@ -805,7 +863,7 @@ impl UnaryPermission { } pub fn check_all(&mut self) -> Result<(), AnyError> { - let (result, prompted) = + let (result, prompted, _) = self .query::<&str>(None) .check(self.name, None, Some("all"), self.prompt); @@ -859,19 +917,26 @@ impl UnaryPermission { if let Some(env) = env { let state = self.query(Some(env)); if state == PermissionState::Prompt { - if PromptResponse::Allow - == permission_prompt( - &format!("env access to \"{env}\""), - self.name, - Some("Deno.permissions.query()"), - ) - { - self.granted_list.insert(EnvDescriptor::new(env)); - PermissionState::Granted - } else { - self.denied_list.insert(EnvDescriptor::new(env)); - self.global_state = PermissionState::Denied; - PermissionState::Denied + match permission_prompt( + &format!("env access to \"{env}\""), + self.name, + Some("Deno.permissions.query()"), + true, + ) { + PromptResponse::Allow => { + self.granted_list.insert(EnvDescriptor::new(env)); + PermissionState::Granted + } + PromptResponse::Deny => { + self.denied_list.insert(EnvDescriptor::new(env)); + self.global_state = PermissionState::Denied; + PermissionState::Denied + } + PromptResponse::AllowAll => { + self.granted_list.clear(); + self.global_state = PermissionState::Granted; + PermissionState::Granted + } } } else if state == PermissionState::Granted { self.granted_list.insert(EnvDescriptor::new(env)); @@ -887,6 +952,7 @@ impl UnaryPermission { "env access", self.name, Some("Deno.permissions.query()"), + true, ) { self.granted_list.clear(); @@ -915,7 +981,7 @@ impl UnaryPermission { } pub fn check(&mut self, env: &str) -> Result<(), AnyError> { - let (result, prompted) = self.query(Some(env)).check( + let (result, prompted, is_allow_all) = self.query(Some(env)).check( self.name, None, Some(&format!("\"{env}\"")), @@ -923,7 +989,12 @@ impl UnaryPermission { ); if prompted { if result.is_ok() { - self.granted_list.insert(EnvDescriptor::new(env)); + if is_allow_all { + self.granted_list.clear(); + self.global_state = PermissionState::Granted; + } else { + self.granted_list.insert(EnvDescriptor::new(env)); + } } else { self.denied_list.insert(EnvDescriptor::new(env)); self.global_state = PermissionState::Denied; @@ -933,7 +1004,7 @@ impl UnaryPermission { } pub fn check_all(&mut self) -> Result<(), AnyError> { - let (result, prompted) = + let (result, prompted, _) = self .query(None) .check(self.name, None, Some("all"), self.prompt); @@ -993,19 +1064,26 @@ impl UnaryPermission { } if let Some(kind) = kind { let desc = SysDescriptor(kind.to_string()); - if PromptResponse::Allow - == permission_prompt( - &format!("sys access to \"{kind}\""), - self.name, - Some("Deno.permissions.query()"), - ) - { - self.granted_list.insert(desc); - PermissionState::Granted - } else { - self.denied_list.insert(desc); - self.global_state = PermissionState::Denied; - PermissionState::Denied + match permission_prompt( + &format!("sys access to \"{kind}\""), + self.name, + Some("Deno.permissions.query()"), + true, + ) { + PromptResponse::Allow => { + self.granted_list.insert(desc); + PermissionState::Granted + } + PromptResponse::Deny => { + self.denied_list.insert(desc); + self.global_state = PermissionState::Denied; + PermissionState::Denied + } + PromptResponse::AllowAll => { + self.granted_list.clear(); + self.global_state = PermissionState::Granted; + PermissionState::Granted + } } } else { if PromptResponse::Allow @@ -1013,6 +1091,7 @@ impl UnaryPermission { "sys access", self.name, Some("Deno.permissions.query()"), + true, ) { self.global_state = PermissionState::Granted; @@ -1041,7 +1120,7 @@ impl UnaryPermission { kind: &str, api_name: Option<&str>, ) -> Result<(), AnyError> { - let (result, prompted) = self.query(Some(kind)).check( + let (result, prompted, is_allow_all) = self.query(Some(kind)).check( self.name, api_name, Some(&format!("\"{kind}\"")), @@ -1049,7 +1128,12 @@ impl UnaryPermission { ); if prompted { if result.is_ok() { - self.granted_list.insert(SysDescriptor(kind.to_string())); + if is_allow_all { + self.granted_list.clear(); + self.global_state = PermissionState::Granted; + } else { + self.granted_list.insert(SysDescriptor(kind.to_string())); + } } else { self.denied_list.insert(SysDescriptor(kind.to_string())); self.global_state = PermissionState::Denied; @@ -1059,7 +1143,7 @@ impl UnaryPermission { } pub fn check_all(&mut self) -> Result<(), AnyError> { - let (result, prompted) = + let (result, prompted, _is_allow_all) = self .query(None) .check(self.name, None, Some("all"), self.prompt); @@ -1116,23 +1200,30 @@ impl UnaryPermission { if let Some(cmd) = cmd { let state = self.query(Some(cmd)); if state == PermissionState::Prompt { - if PromptResponse::Allow - == permission_prompt( - &format!("run access to \"{cmd}\""), - self.name, - Some("Deno.permissions.query()"), - ) - { - self - .granted_list - .insert(RunDescriptor::from_str(cmd).unwrap()); - PermissionState::Granted - } else { - self - .denied_list - .insert(RunDescriptor::from_str(cmd).unwrap()); - self.global_state = PermissionState::Denied; - PermissionState::Denied + match permission_prompt( + &format!("run access to \"{cmd}\""), + self.name, + Some("Deno.permissions.query()"), + true, + ) { + PromptResponse::Allow => { + self + .granted_list + .insert(RunDescriptor::from_str(cmd).unwrap()); + PermissionState::Granted + } + PromptResponse::Deny => { + self + .denied_list + .insert(RunDescriptor::from_str(cmd).unwrap()); + self.global_state = PermissionState::Denied; + PermissionState::Denied + } + PromptResponse::AllowAll => { + self.granted_list.clear(); + self.global_state = PermissionState::Granted; + PermissionState::Granted + } } } else if state == PermissionState::Granted { self @@ -1150,6 +1241,7 @@ impl UnaryPermission { "run access", self.name, Some("Deno.permissions.query()"), + true, ) { self.granted_list.clear(); @@ -1184,7 +1276,7 @@ impl UnaryPermission { cmd: &str, api_name: Option<&str>, ) -> Result<(), AnyError> { - let (result, prompted) = self.query(Some(cmd)).check( + let (result, prompted, is_allow_all) = self.query(Some(cmd)).check( self.name, api_name, Some(&format!("\"{cmd}\"")), @@ -1192,9 +1284,14 @@ impl UnaryPermission { ); if prompted { if result.is_ok() { - self - .granted_list - .insert(RunDescriptor::from_str(cmd).unwrap()); + if is_allow_all { + self.granted_list.clear(); + self.global_state = PermissionState::Granted; + } else { + self + .granted_list + .insert(RunDescriptor::from_str(cmd).unwrap()); + } } else { self .denied_list @@ -1206,7 +1303,7 @@ impl UnaryPermission { } pub fn check_all(&mut self, api_name: Option<&str>) -> Result<(), AnyError> { - let (result, prompted) = + let (result, prompted, _) = self .query(None) .check(self.name, api_name, Some("all"), self.prompt); @@ -1267,19 +1364,26 @@ impl UnaryPermission { let (resolved_path, display_path) = resolved_and_display_path(path); let state = self.query(Some(&resolved_path)); if state == PermissionState::Prompt { - if PromptResponse::Allow - == permission_prompt( - &format!("ffi access to \"{}\"", display_path.display()), - self.name, - Some("Deno.permissions.query()"), - ) - { - self.granted_list.insert(FfiDescriptor(resolved_path)); - PermissionState::Granted - } else { - self.denied_list.insert(FfiDescriptor(resolved_path)); - self.global_state = PermissionState::Denied; - PermissionState::Denied + match permission_prompt( + &format!("ffi access to \"{}\"", display_path.display()), + self.name, + Some("Deno.permissions.query()"), + true, + ) { + PromptResponse::Allow => { + self.granted_list.insert(FfiDescriptor(resolved_path)); + PermissionState::Granted + } + PromptResponse::Deny => { + self.denied_list.insert(FfiDescriptor(resolved_path)); + self.global_state = PermissionState::Denied; + PermissionState::Denied + } + PromptResponse::AllowAll => { + self.granted_list.clear(); + self.global_state = PermissionState::Granted; + PermissionState::Granted + } } } else if state == PermissionState::Granted { self.granted_list.insert(FfiDescriptor(resolved_path)); @@ -1295,6 +1399,7 @@ impl UnaryPermission { "ffi access", self.name, Some("Deno.permissions.query()"), + true, ) { self.granted_list.clear(); @@ -1328,16 +1433,22 @@ impl UnaryPermission { pub fn check(&mut self, path: Option<&Path>) -> Result<(), AnyError> { if let Some(path) = path { let (resolved_path, display_path) = resolved_and_display_path(path); - let (result, prompted) = self.query(Some(&resolved_path)).check( - self.name, - None, - Some(&format!("\"{}\"", display_path.display())), - self.prompt, - ); + let (result, prompted, is_allow_all) = + self.query(Some(&resolved_path)).check( + self.name, + None, + Some(&format!("\"{}\"", display_path.display())), + self.prompt, + ); if prompted { if result.is_ok() { - self.granted_list.insert(FfiDescriptor(resolved_path)); + if is_allow_all { + self.granted_list.clear(); + self.global_state = PermissionState::Granted; + } else { + self.granted_list.insert(FfiDescriptor(resolved_path)); + } } else { self.denied_list.insert(FfiDescriptor(resolved_path)); self.global_state = PermissionState::Denied; @@ -1346,7 +1457,7 @@ impl UnaryPermission { result } else { - let (result, prompted) = + let (result, prompted, _) = self.query(None).check(self.name, None, None, self.prompt); if prompted { @@ -1362,7 +1473,7 @@ impl UnaryPermission { } pub fn check_all(&mut self) -> Result<(), AnyError> { - let (result, prompted) = + let (result, prompted, _) = self .query(None) .check(self.name, None, Some("all"), self.prompt); diff --git a/runtime/permissions/prompter.rs b/runtime/permissions/prompter.rs index e311bc9785623d..d148b485e7ac9f 100644 --- a/runtime/permissions/prompter.rs +++ b/runtime/permissions/prompter.rs @@ -11,6 +11,7 @@ pub const PERMISSION_EMOJI: &str = "⚠️"; pub enum PromptResponse { Allow, Deny, + AllowAll, } static PERMISSION_PROMPTER: Lazy>> = @@ -26,11 +27,14 @@ pub fn permission_prompt( message: &str, flag: &str, api_name: Option<&str>, + is_unary: bool, ) -> PromptResponse { if let Some(before_callback) = MAYBE_BEFORE_PROMPT_CALLBACK.lock().as_mut() { before_callback(); } - let r = PERMISSION_PROMPTER.lock().prompt(message, flag, api_name); + let r = PERMISSION_PROMPTER + .lock() + .prompt(message, flag, api_name, is_unary); if let Some(after_callback) = MAYBE_AFTER_PROMPT_CALLBACK.lock().as_mut() { after_callback(); } @@ -53,6 +57,7 @@ pub trait PermissionPrompter: Send + Sync { message: &str, name: &str, api_name: Option<&str>, + is_unary: bool, ) -> PromptResponse; } @@ -64,6 +69,7 @@ impl PermissionPrompter for TtyPrompter { message: &str, name: &str, api_name: Option<&str>, + is_unary: bool, ) -> PromptResponse { if !atty::is(atty::Stream::Stdin) || !atty::is(atty::Stream::Stderr) { return PromptResponse::Deny; @@ -198,18 +204,22 @@ impl PermissionPrompter for TtyPrompter { let _stderr_guard = std::io::stderr().lock(); // print to stderr so that if stdout is piped this is still displayed. - const OPTS: &str = "[y/n] (y = yes, allow; n = no, deny)"; - eprint!("{PERMISSION_EMOJI} ┌ "); + let opts: String = if is_unary { + format!("[y/n/A] (y = yes, allow; n = no, deny; A = allow all {name} permissions)") + } else { + "[y/n] (y = yes, allow; n = no, deny)".to_string() + }; + eprint!("┌ {PERMISSION_EMOJI} "); eprint!("{}", colors::bold("Deno requests ")); eprint!("{}", colors::bold(message)); eprintln!("{}", colors::bold(".")); if let Some(api_name) = api_name { - eprintln!(" ├ Requested by `{api_name}` API"); + eprintln!("├ Requested by `{api_name}` API"); } let msg = format!("Run again with --allow-{name} to bypass this prompt."); - eprintln!(" ├ {}", colors::italic(&msg)); - eprint!(" └ {}", colors::bold("Allow?")); - eprint!(" {OPTS} > "); + eprintln!("├ {}", colors::italic(&msg)); + eprint!("└ {}", colors::bold("Allow?")); + eprint!(" {opts} > "); let value = loop { let mut input = String::new(); let stdin = std::io::stdin(); @@ -221,24 +231,30 @@ impl PermissionPrompter for TtyPrompter { None => break PromptResponse::Deny, Some(v) => v, }; - match ch.to_ascii_lowercase() { - 'y' => { + match ch { + 'y' | 'Y' => { clear_n_lines(if api_name.is_some() { 4 } else { 3 }); let msg = format!("Granted {message}."); eprintln!("✅ {}", colors::bold(&msg)); break PromptResponse::Allow; } - 'n' => { + 'n' | 'N' => { clear_n_lines(if api_name.is_some() { 4 } else { 3 }); let msg = format!("Denied {message}."); eprintln!("❌ {}", colors::bold(&msg)); break PromptResponse::Deny; } + 'A' if is_unary => { + clear_n_lines(if api_name.is_some() { 4 } else { 3 }); + let msg = format!("Granted all {name} access."); + eprintln!("✅ {}", colors::bold(&msg)); + break PromptResponse::AllowAll; + } _ => { // If we don't get a recognized option try again. clear_n_lines(1); - eprint!(" └ {}", colors::bold("Unrecognized option. Allow?")); - eprint!(" {OPTS} > "); + eprint!("└ {}", colors::bold("Unrecognized option. Allow?")); + eprint!(" {opts} > "); } }; }; @@ -264,6 +280,7 @@ pub mod tests { _message: &str, _name: &str, _api_name: Option<&str>, + _is_unary: bool, ) -> PromptResponse { if STUB_PROMPT_VALUE.load(Ordering::SeqCst) { PromptResponse::Allow diff --git a/runtime/web_worker.rs b/runtime/web_worker.rs index 159204620df54a..a26b87e71132bd 100644 --- a/runtime/web_worker.rs +++ b/runtime/web_worker.rs @@ -2,7 +2,6 @@ // Copyright 2023 Jo Bates. All rights reserved. MIT license. use crate::colors; use crate::inspector_server::InspectorServer; -use crate::js; use crate::ops; use crate::ops::io::Stdio; use crate::permissions::PermissionsContainer; @@ -320,6 +319,7 @@ pub struct WebWorker { pub worker_type: WebWorkerType, pub main_module: ModuleSpecifier, poll_for_messages_fn: Option>, + bootstrap_fn_global: Option>, } pub struct WebWorkerOptions { @@ -435,7 +435,8 @@ impl WebWorker { unstable, options.unsafely_ignore_certificate_errors.clone(), ), - deno_napi::init::(unstable), + deno_napi::init::(), + deno_node::init_polyfill(), deno_node::init::(options.npm_resolver), ops::os::init_for_worker(), ops::permissions::init(), @@ -454,13 +455,17 @@ impl WebWorker { // Append exts extensions.extend(std::mem::take(&mut options.extensions)); + #[cfg(not(feature = "dont_create_runtime_snapshot"))] + let startup_snapshot = options + .startup_snapshot + .unwrap_or_else(crate::js::deno_isolate_init); + #[cfg(feature = "dont_create_runtime_snapshot")] + let startup_snapshot = options.startup_snapshot + .expect("deno_runtime startup snapshot is not available with 'create_runtime_snapshot' Cargo feature."); + let mut js_runtime = JsRuntime::new(RuntimeOptions { module_loader: Some(options.module_loader.clone()), - startup_snapshot: Some( - options - .startup_snapshot - .unwrap_or_else(js::deno_isolate_init), - ), + startup_snapshot: Some(startup_snapshot), source_map_getter: options.source_map_getter, get_error_class_fn: options.get_error_class_fn, shared_array_buffer_store: options.shared_array_buffer_store.clone(), @@ -494,6 +499,25 @@ impl WebWorker { (internal_handle, external_handle) }; + let bootstrap_fn_global = { + let context = js_runtime.global_context(); + let scope = &mut js_runtime.handle_scope(); + let context_local = v8::Local::new(scope, context); + let global_obj = context_local.global(scope); + let bootstrap_str = v8::String::new(scope, "bootstrap").unwrap(); + let bootstrap_ns: v8::Local = global_obj + .get(scope, bootstrap_str.into()) + .unwrap() + .try_into() + .unwrap(); + let main_runtime_str = v8::String::new(scope, "workerRuntime").unwrap(); + let bootstrap_fn = + bootstrap_ns.get(scope, main_runtime_str.into()).unwrap(); + let bootstrap_fn = + v8::Local::::try_from(bootstrap_fn).unwrap(); + v8::Global::new(scope, bootstrap_fn) + }; + ( Self { id: worker_id, @@ -503,6 +527,7 @@ impl WebWorker { worker_type: options.worker_type, main_module, poll_for_messages_fn: None, + bootstrap_fn_global: Some(bootstrap_fn_global), }, external_handle, ) @@ -511,15 +536,24 @@ impl WebWorker { pub fn bootstrap(&mut self, options: &BootstrapOptions) { // Instead of using name for log we use `worker-${id}` because // WebWorkers can have empty string as name. - let script = format!( - "bootstrap.workerRuntime({}, \"{}\", \"{}\")", - options.as_json(), - self.name, - self.id - ); - self - .execute_script(&located_script_name!(), &script) - .expect("Failed to execute worker bootstrap script"); + { + let scope = &mut self.js_runtime.handle_scope(); + let options_v8 = + deno_core::serde_v8::to_v8(scope, options.as_json()).unwrap(); + let bootstrap_fn = self.bootstrap_fn_global.take().unwrap(); + let bootstrap_fn = v8::Local::new(scope, bootstrap_fn); + let undefined = v8::undefined(scope); + let name_str: v8::Local = + v8::String::new(scope, &self.name).unwrap().into(); + let id_str: v8::Local = + v8::String::new(scope, &format!("{}", self.id)) + .unwrap() + .into(); + bootstrap_fn + .call(scope, undefined.into(), &[options_v8, name_str, id_str]) + .unwrap(); + } + // TODO(bartlomieju): this could be done using V8 API, without calling `execute_script`. // Save a reference to function that will start polling for messages // from a worker host; it will be called after the user code is loaded. let script = r#" diff --git a/runtime/worker.rs b/runtime/worker.rs index 3a04f63fc35db1..72ee3bb58e29be 100644 --- a/runtime/worker.rs +++ b/runtime/worker.rs @@ -15,7 +15,6 @@ use deno_cache::SqliteBackedCache; use deno_core::error::AnyError; use deno_core::error::JsError; use deno_core::futures::Future; -use deno_core::located_script_name; use deno_core::v8; use deno_core::CompiledWasmModuleStore; use deno_core::Extension; @@ -37,7 +36,6 @@ use deno_wsi::event_loop::WsiEventLoopProxy; use log::debug; use crate::inspector_server::InspectorServer; -use crate::js; use crate::ops; use crate::ops::io::Stdio; use crate::permissions::PermissionsContainer; @@ -69,6 +67,7 @@ pub struct MainWorker { should_break_on_first_statement: bool, should_wait_for_inspector_session: bool, exit_code: ExitCode, + bootstrap_fn_global: Option>, } pub struct WorkerOptions { @@ -270,7 +269,8 @@ impl MainWorker { unstable, options.unsafely_ignore_certificate_errors.clone(), ), - deno_napi::init::(unstable), + deno_napi::init::(), + deno_node::init_polyfill(), deno_node::init::(options.npm_resolver), ops::os::init(exit_code.clone()), ops::permissions::init(), @@ -286,13 +286,17 @@ impl MainWorker { ]; extensions.extend(std::mem::take(&mut options.extensions)); + #[cfg(not(feature = "dont_create_runtime_snapshot"))] + let startup_snapshot = options + .startup_snapshot + .unwrap_or_else(crate::js::deno_isolate_init); + #[cfg(feature = "dont_create_runtime_snapshot")] + let startup_snapshot = options.startup_snapshot + .expect("deno_runtime startup snapshot is not available with 'create_runtime_snapshot' Cargo feature."); + let mut js_runtime = JsRuntime::new(RuntimeOptions { module_loader: Some(options.module_loader.clone()), - startup_snapshot: Some( - options - .startup_snapshot - .unwrap_or_else(js::deno_isolate_init), - ), + startup_snapshot: Some(startup_snapshot), source_map_getter: options.source_map_getter, get_error_class_fn: options.get_error_class_fn, shared_array_buffer_store: options.shared_array_buffer_store.clone(), @@ -319,20 +323,45 @@ impl MainWorker { op_state.borrow_mut().put(inspector); } + let bootstrap_fn_global = { + let context = js_runtime.global_context(); + let scope = &mut js_runtime.handle_scope(); + let context_local = v8::Local::new(scope, context); + let global_obj = context_local.global(scope); + let bootstrap_str = v8::String::new(scope, "bootstrap").unwrap(); + let bootstrap_ns: v8::Local = global_obj + .get(scope, bootstrap_str.into()) + .unwrap() + .try_into() + .unwrap(); + let main_runtime_str = v8::String::new(scope, "mainRuntime").unwrap(); + let bootstrap_fn = + bootstrap_ns.get(scope, main_runtime_str.into()).unwrap(); + let bootstrap_fn = + v8::Local::::try_from(bootstrap_fn).unwrap(); + v8::Global::new(scope, bootstrap_fn) + }; + Self { js_runtime, should_break_on_first_statement: options.should_break_on_first_statement, should_wait_for_inspector_session: options .should_wait_for_inspector_session, exit_code, + bootstrap_fn_global: Some(bootstrap_fn_global), } } pub fn bootstrap(&mut self, options: &BootstrapOptions) { - let script = format!("bootstrap.mainRuntime({})", options.as_json()); - self - .execute_script(&located_script_name!(), &script) - .expect("Failed to execute bootstrap script"); + let scope = &mut self.js_runtime.handle_scope(); + let options_v8 = + deno_core::serde_v8::to_v8(scope, options.as_json()).unwrap(); + let bootstrap_fn = self.bootstrap_fn_global.take().unwrap(); + let bootstrap_fn = v8::Local::new(scope, bootstrap_fn); + let undefined = v8::undefined(scope); + bootstrap_fn + .call(scope, undefined.into(), &[options_v8]) + .unwrap(); } /// See [JsRuntime::execute_script](deno_core::JsRuntime::execute_script) diff --git a/runtime/worker_bootstrap.rs b/runtime/worker_bootstrap.rs index 2f5896f2fe5d49..eeeeff1e44ffae 100644 --- a/runtime/worker_bootstrap.rs +++ b/runtime/worker_bootstrap.rs @@ -58,8 +58,8 @@ impl Default for BootstrapOptions { } impl BootstrapOptions { - pub fn as_json(&self) -> String { - let payload = json!({ + pub fn as_json(&self) -> serde_json::Value { + json!({ // Shared bootstrap args "args": self.args, "cpuCount": self.cpu_count, @@ -80,7 +80,6 @@ impl BootstrapOptions { "v8Version": deno_core::v8_version(), "userAgent": self.user_agent, "inspectFlag": self.inspect, - }); - serde_json::to_string_pretty(&payload).unwrap() + }) } } diff --git a/serde_v8/Cargo.toml b/serde_v8/Cargo.toml index 3d4c850da0eb5c..2fe63ff18af598 100644 --- a/serde_v8/Cargo.toml +++ b/serde_v8/Cargo.toml @@ -2,7 +2,7 @@ [package] name = "serde_v8" -version = "0.82.0" +version = "0.84.0" authors.workspace = true edition.workspace = true license.workspace = true diff --git a/serde_v8/error.rs b/serde_v8/error.rs index 2cd8eab655f512..145524abbe74c1 100644 --- a/serde_v8/error.rs +++ b/serde_v8/error.rs @@ -22,6 +22,7 @@ pub enum Error { ExpectedObject, ExpectedBuffer, ExpectedDetachable, + ExpectedExternal, ExpectedUtf8, ExpectedLatin1, diff --git a/serde_v8/lib.rs b/serde_v8/lib.rs index c15ca715a0ba66..b857acbe812d97 100644 --- a/serde_v8/lib.rs +++ b/serde_v8/lib.rs @@ -20,6 +20,7 @@ pub use magic::bytestring::ByteString; pub use magic::detached_buffer::DetachedBuffer; pub use magic::string_or_buffer::StringOrBuffer; pub use magic::u16string::U16String; +pub use magic::ExternalPointer; pub use magic::Global; pub use magic::Value; pub use ser::to_v8; diff --git a/serde_v8/magic/external_pointer.rs b/serde_v8/magic/external_pointer.rs new file mode 100644 index 00000000000000..fca6028d67193a --- /dev/null +++ b/serde_v8/magic/external_pointer.rs @@ -0,0 +1,56 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. + +use std::ffi::c_void; + +use super::transl8::impl_magic; +use super::transl8::FromV8; +use super::transl8::ToV8; + +pub struct ExternalPointer(*mut c_void); + +// SAFETY: Nonblocking FFI is user controller and we must trust user to have it right. +unsafe impl Send for ExternalPointer {} +// SAFETY: Nonblocking FFI is user controller and we must trust user to have it right. +unsafe impl Sync for ExternalPointer {} + +impl_magic!(ExternalPointer); + +impl ToV8 for ExternalPointer { + fn to_v8<'a>( + &mut self, + scope: &mut v8::HandleScope<'a>, + ) -> Result, crate::Error> { + if self.0.is_null() { + Ok(v8::null(scope).into()) + } else { + Ok(v8::External::new(scope, self.0).into()) + } + } +} + +impl FromV8 for ExternalPointer { + fn from_v8( + _scope: &mut v8::HandleScope, + value: v8::Local, + ) -> Result { + if value.is_null() { + Ok(ExternalPointer(std::ptr::null_mut())) + } else if let Ok(external) = v8::Local::::try_from(value) { + Ok(ExternalPointer(external.value())) + } else { + Err(crate::Error::ExpectedExternal) + } + } +} + +impl From<*mut c_void> for ExternalPointer { + fn from(value: *mut c_void) -> Self { + ExternalPointer(value) + } +} + +impl From<*const c_void> for ExternalPointer { + fn from(value: *const c_void) -> Self { + ExternalPointer(value as *mut c_void) + } +} diff --git a/serde_v8/magic/mod.rs b/serde_v8/magic/mod.rs index fe45776728f49c..f96e422b1617ad 100644 --- a/serde_v8/magic/mod.rs +++ b/serde_v8/magic/mod.rs @@ -2,6 +2,7 @@ pub mod buffer; pub mod bytestring; pub mod detached_buffer; +mod external_pointer; mod global; pub(super) mod rawbytes; pub mod string_or_buffer; @@ -9,5 +10,6 @@ pub mod transl8; pub mod u16string; pub mod v8slice; mod value; +pub use external_pointer::ExternalPointer; pub use global::Global; pub use value::Value; diff --git a/serde_v8/magic/string_or_buffer.rs b/serde_v8/magic/string_or_buffer.rs index b7f6724b8bf50e..04ce08be22d89e 100644 --- a/serde_v8/magic/string_or_buffer.rs +++ b/serde_v8/magic/string_or_buffer.rs @@ -24,6 +24,16 @@ impl Deref for StringOrBuffer { } } +impl<'a> TryFrom<&'a StringOrBuffer> for &'a str { + type Error = std::str::Utf8Error; + fn try_from(value: &'a StringOrBuffer) -> Result { + match value { + StringOrBuffer::String(s) => Ok(s.as_str()), + StringOrBuffer::Buffer(b) => std::str::from_utf8(b.as_ref()), + } + } +} + impl ToV8 for StringOrBuffer { fn to_v8<'a>( &mut self, diff --git a/serde_v8/ser.rs b/serde_v8/ser.rs index 834efa78a9ee8c..fa4cfecde78743 100644 --- a/serde_v8/ser.rs +++ b/serde_v8/ser.rs @@ -16,6 +16,7 @@ use crate::magic::transl8::ToV8; use crate::magic::transl8::MAGIC_FIELD; use crate::ByteString; use crate::DetachedBuffer; +use crate::ExternalPointer; use crate::StringOrBuffer; use crate::U16String; use crate::ZeroCopyBuf; @@ -269,6 +270,7 @@ impl<'a, 'b, 'c, T: MagicType + ToV8> ser::SerializeStruct // Dispatches between magic and regular struct serializers pub enum StructSerializers<'a, 'b, 'c> { + ExternalPointer(MagicalSerializer<'a, 'b, 'c, magic::ExternalPointer>), Magic(MagicalSerializer<'a, 'b, 'c, magic::Value<'a>>), ZeroCopyBuf(MagicalSerializer<'a, 'b, 'c, ZeroCopyBuf>), MagicDetached(MagicalSerializer<'a, 'b, 'c, DetachedBuffer>), @@ -288,6 +290,7 @@ impl<'a, 'b, 'c> ser::SerializeStruct for StructSerializers<'a, 'b, 'c> { value: &T, ) -> Result<()> { match self { + StructSerializers::ExternalPointer(s) => s.serialize_field(key, value), StructSerializers::Magic(s) => s.serialize_field(key, value), StructSerializers::ZeroCopyBuf(s) => s.serialize_field(key, value), StructSerializers::MagicDetached(s) => s.serialize_field(key, value), @@ -302,6 +305,7 @@ impl<'a, 'b, 'c> ser::SerializeStruct for StructSerializers<'a, 'b, 'c> { fn end(self) -> JsResult<'a> { match self { + StructSerializers::ExternalPointer(s) => s.end(), StructSerializers::Magic(s) => s.end(), StructSerializers::ZeroCopyBuf(s) => s.end(), StructSerializers::MagicDetached(s) => s.end(), @@ -385,8 +389,8 @@ macro_rules! forward_to { }; } -const MAX_SAFE_INTEGER: i64 = (1 << 53) - 1; -const MIN_SAFE_INTEGER: i64 = -MAX_SAFE_INTEGER; +pub(crate) const MAX_SAFE_INTEGER: i64 = (1 << 53) - 1; +pub(crate) const MIN_SAFE_INTEGER: i64 = -MAX_SAFE_INTEGER; impl<'a, 'b, 'c> ser::Serializer for Serializer<'a, 'b, 'c> { type Ok = v8::Local<'a, v8::Value>; @@ -564,6 +568,10 @@ impl<'a, 'b, 'c> ser::Serializer for Serializer<'a, 'b, 'c> { len: usize, ) -> Result { match name { + magic::ExternalPointer::MAGIC_NAME => { + let m = MagicalSerializer::::new(self.scope); + Ok(StructSerializers::ExternalPointer(m)) + } ByteString::MAGIC_NAME => { let m = MagicalSerializer::::new(self.scope); Ok(StructSerializers::MagicByteString(m)) diff --git a/test_ffi/tests/event_loop_integration.ts b/test_ffi/tests/event_loop_integration.ts index e44c66ab699536..28152dabf1d14e 100644 --- a/test_ffi/tests/event_loop_integration.ts +++ b/test_ffi/tests/event_loop_integration.ts @@ -26,6 +26,7 @@ const dylib = Deno.dlopen( } as const, ); +let retry = false; const tripleLogCallback = () => { console.log("Sync"); Promise.resolve().then(() => { @@ -35,10 +36,18 @@ const tripleLogCallback = () => { setTimeout(() => { console.log("Timeout"); callback.unref(); + + if (retry) { + // Re-ref and retry the call to make sure re-refing works. + console.log("RETRY THREAD SAFE"); + retry = false; + callback.ref(); + dylib.symbols.call_stored_function_thread_safe_and_log(); + } }, 10); }; -const callback = new Deno.UnsafeCallback( +const callback = Deno.UnsafeCallback.threadSafe( { parameters: [], result: "void", @@ -57,10 +66,11 @@ console.log("STORED_FUNCTION called"); // Wait to make sure synch logging and async logging await new Promise((res) => setTimeout(res, 100)); -// Ref twice to make sure both `Promise.resolve().then()` and `setTimeout()` -// must resolve before isolate exists. -callback.ref(); +// Ref once to make sure both `Promise.resolve().then()` and `setTimeout()` +// must resolve and unref before isolate exists. +// One ref'ing has been done by `threadSafe` constructor. callback.ref(); console.log("THREAD SAFE"); +retry = true; dylib.symbols.call_stored_function_thread_safe_and_log(); diff --git a/test_ffi/tests/ffi_types.ts b/test_ffi/tests/ffi_types.ts index d7c66203c3b5c0..c04c13d7947c6b 100644 --- a/test_ffi/tests/ffi_types.ts +++ b/test_ffi/tests/ffi_types.ts @@ -140,30 +140,31 @@ remote.symbols.method14(0); remote.symbols.method15("foo"); // @ts-expect-error: Invalid argument remote.symbols.method15(new Uint16Array(1)); -remote.symbols.method15(0n); +remote.symbols.method15(null); +remote.symbols.method15({} as Deno.PointerValue); const result = remote.symbols.method16(); // @ts-expect-error: Invalid argument let r_0: string = result; -let r_1: Deno.PointerValue = result; +let r_1: number | bigint = result; const result2 = remote.symbols.method17(); // @ts-expect-error: Invalid argument result2.then((_0: string) => {}); -result2.then((_1: Deno.PointerValue) => {}); +result2.then((_1: number | bigint) => {}); const result3 = remote.symbols.method18(); // @ts-expect-error: Invalid argument let r3_0: Deno.BufferSource = result3; -let r3_1: Deno.UnsafePointer = result3; +let r3_1: null | Deno.UnsafePointer = result3; const result4 = remote.symbols.method19(); // @ts-expect-error: Invalid argument result4.then((_0: Deno.BufferSource) => {}); -result4.then((_1: Deno.UnsafePointer) => {}); +result4.then((_1: null | Deno.UnsafePointer) => {}); const fnptr = new Deno.UnsafeFnPointer( - 0n, + {} as NonNullable, { parameters: ["u32", "pointer"], result: "void", @@ -210,7 +211,7 @@ const unsafe_callback_right1 = new Deno.UnsafeCallback( parameters: ["u8", "u32", "pointer"], result: "void", }, - (_1: number, _2: number, _3: Deno.PointerValue) => {}, + (_1: number, _2: number, _3: null | Deno.PointerValue) => {}, ); const unsafe_callback_right2 = new Deno.UnsafeCallback( { @@ -232,14 +233,14 @@ const unsafe_callback_right4 = new Deno.UnsafeCallback( parameters: ["u8", "u32", "pointer"], result: "u8", }, - (_1: number, _2: number, _3: Deno.PointerValue) => 3, + (_1: number, _2: number, _3: null | Deno.PointerValue) => 3, ); const unsafe_callback_right5 = new Deno.UnsafeCallback( { parameters: ["u8", "i32", "pointer"], result: "void", }, - (_1: number, _2: number, _3: Deno.PointerValue) => {}, + (_1: number, _2: number, _3: null | Deno.PointerValue) => {}, ); // @ts-expect-error: Must pass callback @@ -255,9 +256,9 @@ remote.symbols.method23(new Uint32Array(1)); remote.symbols.method23(new Uint8Array(1)); // @ts-expect-error: Cannot pass pointer values as buffer. -remote.symbols.method23(0); +remote.symbols.method23({}); // @ts-expect-error: Cannot pass pointer values as buffer. -remote.symbols.method23(0n); +remote.symbols.method23({}); remote.symbols.method23(null); // @ts-expect-error: Cannot pass number as bool. @@ -278,16 +279,16 @@ let r42_1: number = remote.symbols.method24(true); // @ts-expect-error: Invalid member type const static1_wrong: null = remote.symbols.static1; -const static1_right: Deno.PointerValue = remote.symbols.static1; +const static1_right: number | bigint = remote.symbols.static1; // @ts-expect-error: Invalid member type const static2_wrong: null = remote.symbols.static2; -const static2_right: Deno.UnsafePointer = remote.symbols.static2; +const static2_right: null | Deno.UnsafePointer = remote.symbols.static2; // @ts-expect-error: Invalid member type const static3_wrong: null = remote.symbols.static3; -const static3_right: Deno.PointerValue = remote.symbols.static3; +const static3_right: number | bigint = remote.symbols.static3; // @ts-expect-error: Invalid member type const static4_wrong: null = remote.symbols.static4; -const static4_right: Deno.PointerValue = remote.symbols.static4; +const static4_right: number | bigint = remote.symbols.static4; // @ts-expect-error: Invalid member type const static5_wrong: null = remote.symbols.static5; const static5_right: number = remote.symbols.static5; @@ -299,7 +300,7 @@ const static7_wrong: null = remote.symbols.static7; const static7_right: number = remote.symbols.static7; // @ts-expect-error: Invalid member type const static8_wrong: null = remote.symbols.static8; -const static8_right: Deno.PointerValue = remote.symbols.static8; +const static8_right: number | bigint = remote.symbols.static8; // @ts-expect-error: Invalid member type const static9_wrong: null = remote.symbols.static9; const static9_right: number = remote.symbols.static9; @@ -311,7 +312,7 @@ const static11_wrong: null = remote.symbols.static11; const static11_right: number = remote.symbols.static11; // @ts-expect-error: Invalid member type const static12_wrong: null = remote.symbols.static12; -const static12_right: Deno.PointerValue = remote.symbols.static12; +const static12_right: number | bigint = remote.symbols.static12; // @ts-expect-error: Invalid member type const static13_wrong: null = remote.symbols.static13; const static13_right: number = remote.symbols.static13; @@ -376,8 +377,8 @@ type __Tests__ = [ symbols: { pushBuf: ( buf: BufferSource | null, - ptr: Deno.PointerValue | null, - func: Deno.PointerValue | null, + ptr: Deno.PointerValue, + func: Deno.PointerValue, ) => Deno.PointerValue; }; close(): void; @@ -395,8 +396,8 @@ type __Tests__ = [ { symbols: { foo: ( - ...args: (Deno.PointerValue | null)[] - ) => Deno.PointerValue; + ...args: (number | Deno.PointerValue | null)[] + ) => number | bigint; }; close(): void; }, diff --git a/test_ffi/tests/integration_tests.rs b/test_ffi/tests/integration_tests.rs index c213f829512089..e850a174a3f7c1 100644 --- a/test_ffi/tests/integration_tests.rs +++ b/test_ffi/tests/integration_tests.rs @@ -97,12 +97,8 @@ fn basic() { -9007199254740992n\n\ 579.9119873046875\n\ 579.912\n\ - After sleep_blocking\n\ - true\n\ Before\n\ - true\n\ After\n\ - true\n\ logCallback\n\ 1 -1 2 -2 3 -3 4 -4 0.5 -0.5 1 2 3 4 5 6 7 8\n\ u8: 8\n\ @@ -119,23 +115,12 @@ fn basic() { 78\n\ STORED_FUNCTION cleared\n\ STORED_FUNCTION_2 cleared\n\ - Thread safe call counter: 0\n\ logCallback\n\ - Thread safe call counter: 1\n\ u8: 8\n\ - Static u32: 42\n\ - Static i64: -1242464576485\n\ - Static ptr: true\n\ - Static ptr value: 42\n\ Rect { x: 10.0, y: 20.0, w: 100.0, h: 200.0 }\n\ Rect { x: 10.0, y: 20.0, w: 100.0, h: 200.0 }\n\ Rect { x: 20.0, y: 20.0, w: 100.0, h: 200.0 }\n\ Mixed { u8: 3, f32: 12.515, rect: Rect { x: 10.0, y: 20.0, w: 100.0, h: 200.0 }, usize: 12456789, array: [8, 32] }\n\ - arrayBuffer.byteLength: 4\n\ - uint32Array.length: 1\n\ - uint32Array[0]: 42\n\ - uint32Array[0] after mutation: 55\n\ - Static ptr value after mutation: 55\n\ 2264956937\n\ 2264956937\n\ Correct number of resources\n"; @@ -240,6 +225,11 @@ fn event_loop_integration() { Sync\n\ Async\n\ STORED_FUNCTION called\n\ + Timeout\n\ + RETRY THREAD SAFE\n\ + Sync\n\ + Async\n\ + STORED_FUNCTION called\n\ Timeout\n"; assert_eq!(stdout, expected); assert_eq!(stderr, ""); diff --git a/test_ffi/tests/test.js b/test_ffi/tests/test.js index e45c5895b351d8..788faa93e90acf 100644 --- a/test_ffi/tests/test.js +++ b/test_ffi/tests/test.js @@ -1,12 +1,15 @@ // Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. // deno-lint-ignore-file -// Run using cargo test or `--v8-options=--allow-natives-syntax` +// Run using cargo test or `--v8-flags=--allow-natives-syntax` -import { assertEquals, assertInstanceOf, assertNotEquals } from "https://deno.land/std@0.149.0/testing/asserts.ts"; import { assertThrows, assert, + assertNotEquals, + assertInstanceOf, + assertEquals, + assertFalse, } from "../../test_util/std/testing/asserts.ts"; const targetDir = Deno.execPath().replace(/[^\/\\]+$/, ""); @@ -368,8 +371,8 @@ assertEquals(isNullBufferDeopt(externalOneBuffer), false, "isNullBufferDeopt(ext // Due to ops macro using `Local->Data()` to get the pointer for the slice that is then used to get // the pointer of an ArrayBuffer / TypedArray, the same effect can be seen where a zero byte length buffer returns // a null pointer as its pointer value. -assertEquals(Deno.UnsafePointer.of(externalZeroBuffer), 0, "Deno.UnsafePointer.of(externalZeroBuffer) !== 0"); -assertNotEquals(Deno.UnsafePointer.of(externalOneBuffer), 0, "Deno.UnsafePointer.of(externalOneBuffer) !== 0"); +assertEquals(Deno.UnsafePointer.of(externalZeroBuffer), null, "Deno.UnsafePointer.of(externalZeroBuffer) !== null"); +assertNotEquals(Deno.UnsafePointer.of(externalOneBuffer), null, "Deno.UnsafePointer.of(externalOneBuffer) === null"); const addU32Ptr = dylib.symbols.get_add_u32_ptr(); const addU32 = new Deno.UnsafeFnPointer(addU32Ptr, { @@ -486,16 +489,15 @@ await promise; let start = performance.now(); dylib.symbols.sleep_blocking(100); -console.log("After sleep_blocking"); -console.log(performance.now() - start >= 100); +assert(performance.now() - start >= 100); start = performance.now(); const promise_2 = dylib.symbols.sleep_nonblocking(100).then(() => { console.log("After"); - console.log(performance.now() - start >= 100); + assert(performance.now() - start >= 100); }); console.log("Before"); -console.log(performance.now() - start < 100); +assert(performance.now() - start < 100); // Await to make sure `sleep_nonblocking` calls and logs before we proceed await promise_2; @@ -532,7 +534,7 @@ const returnU8Callback = new Deno.UnsafeCallback( ); const returnBufferCallback = new Deno.UnsafeCallback({ parameters: [], - result: "pointer", + result: "buffer", }, () => { return buffer; }); @@ -617,27 +619,29 @@ const addToFooCallback = new Deno.UnsafeCallback({ }, () => counter++); // Test thread safe callbacks -console.log("Thread safe call counter:", counter); +assertEquals(counter, 0); addToFooCallback.ref(); await dylib.symbols.call_fn_ptr_thread_safe(addToFooCallback.pointer); addToFooCallback.unref(); logCallback.ref(); await dylib.symbols.call_fn_ptr_thread_safe(logCallback.pointer); logCallback.unref(); -console.log("Thread safe call counter:", counter); +assertEquals(counter, 1); returnU8Callback.ref(); await dylib.symbols.call_fn_ptr_return_u8_thread_safe(returnU8Callback.pointer); // Purposefully do not unref returnU8Callback: Instead use it to test close() unrefing. // Test statics -console.log("Static u32:", dylib.symbols.static_u32); -console.log("Static i64:", dylib.symbols.static_i64); -console.log( - "Static ptr:", - typeof dylib.symbols.static_ptr === "number", +assertEquals(dylib.symbols.static_u32, 42); +assertEquals(dylib.symbols.static_i64, -1242464576485); +assert( + typeof dylib.symbols.static_ptr === "object" +); +assertEquals( + Object.keys(dylib.symbols.static_ptr).length, 0 ); const view = new Deno.UnsafePointerView(dylib.symbols.static_ptr); -console.log("Static ptr value:", view.getUint32()); +assertEquals(view.getUint32(), 42); // Test struct returning const rect_sync = dylib.symbols.make_rect(10, 20, 100, 200); @@ -656,7 +660,7 @@ assertEquals(rect_async.length, 4 * 8); assertEquals(Array.from(new Float64Array(rect_async.buffer)), [10, 20, 100, 200]); // Test complex, mixed struct returning and passing -const mixedStruct = dylib.symbols.create_mixed(3, 12.515000343322754, rect_async, 12456789, new Uint32Array([8, 32])); +const mixedStruct = dylib.symbols.create_mixed(3, 12.515000343322754, rect_async, Deno.UnsafePointer.create(12456789), new Uint32Array([8, 32])); assertEquals(mixedStruct.length, 56); assertEquals(Array.from(mixedStruct.subarray(0, 4)), [3, 0, 0, 0]); assertEquals(new Float32Array(mixedStruct.buffer, 4, 1)[0], 12.515000343322754); @@ -681,12 +685,31 @@ cb.close(); const arrayBuffer = view.getArrayBuffer(4); const uint32Array = new Uint32Array(arrayBuffer); -console.log("arrayBuffer.byteLength:", arrayBuffer.byteLength); -console.log("uint32Array.length:", uint32Array.length); -console.log("uint32Array[0]:", uint32Array[0]); +assertEquals(arrayBuffer.byteLength, 4); +assertEquals(uint32Array.length, 1); +assertEquals(uint32Array[0], 42); uint32Array[0] = 55; // MUTATES! -console.log("uint32Array[0] after mutation:", uint32Array[0]); -console.log("Static ptr value after mutation:", view.getUint32()); +assertEquals(uint32Array[0], 55); +assertEquals(view.getUint32(), 55); + + +{ + // Test UnsafePointer APIs + assertEquals(Deno.UnsafePointer.create(0), null); + const createdPointer = Deno.UnsafePointer.create(1); + assertNotEquals(createdPointer, null); + assertEquals(typeof createdPointer, "object"); + assertEquals(Deno.UnsafePointer.value(null), 0); + assertEquals(Deno.UnsafePointer.value(createdPointer), 1); + assert(Deno.UnsafePointer.equals(null, null)); + assertFalse(Deno.UnsafePointer.equals(null, createdPointer)); + assertFalse(Deno.UnsafePointer.equals(Deno.UnsafePointer.create(2), createdPointer)); + // Do not allow offsetting from null, `create` function should be used instead. + assertThrows(() => Deno.UnsafePointer.offset(null, 5)); + const offsetPointer = Deno.UnsafePointer.offset(createdPointer, 5); + assertEquals(Deno.UnsafePointer.value(offsetPointer), 6); + assertEquals(Deno.UnsafePointer.offset(offsetPointer, -6), null); +} // Test non-UTF-8 characters @@ -699,10 +722,26 @@ assertEquals([...uint8Array], [ 0x00 ]); -try { - assertThrows(() => charView.getCString(), TypeError, "Invalid CString pointer, not valid UTF-8"); -} catch (_err) { - console.log("Invalid UTF-8 characters to `v8::String`:", charView.getCString()); +// Check that `getCString` works equally to `TextDecoder` +assertEquals(charView.getCString(), new TextDecoder().decode(uint8Array.subarray(0, uint8Array.length - 1))); + +// Check a selection of various invalid UTF-8 sequences in C strings and verify +// that the `getCString` API does not cause unexpected behaviour. +for (const charBuffer of [ + Uint8Array.from([0xA0, 0xA1, 0x00]), + Uint8Array.from([0xE2, 0x28, 0xA1, 0x00]), + Uint8Array.from([0xE2, 0x82, 0x28, 0x00]), + Uint8Array.from([0xF0, 0x28, 0x8C, 0xBC, 0x00]), + Uint8Array.from([0xF0, 0x90, 0x28, 0xBC, 0x00]), + Uint8Array.from([0xF0, 0x28, 0x8C, 0x28, 0x00]), + Uint8Array.from([0xF8, 0xA1, 0xA1, 0xA1, 0xA1, 0x00]), + Uint8Array.from([0xFC, 0xA1, 0xA1, 0xA1, 0xA1, 0xA1, 0x00]), +]) { + const charBufferPointer = Deno.UnsafePointer.of(charBuffer); + const charString = Deno.UnsafePointerView.getCString(charBufferPointer); + const charBufferPointerArrayBuffer = new Uint8Array(Deno.UnsafePointerView.getArrayBuffer(charBufferPointer, charBuffer.length - 1)); + assertEquals(charString, new TextDecoder().decode(charBufferPointerArrayBuffer)); + assertEquals([...charBuffer.subarray(0, charBuffer.length - 1)], [...charBufferPointerArrayBuffer]); } diff --git a/test_napi/properties_test.js b/test_napi/properties_test.js index 78268ba15760eb..b5f0c079424ce7 100644 --- a/test_napi/properties_test.js +++ b/test_napi/properties_test.js @@ -5,11 +5,20 @@ import { assertEquals, loadTestLibrary } from "./common.js"; const properties = loadTestLibrary(); Deno.test("napi properties", () => { - properties.test_property_rw = 1; assertEquals(properties.test_property_rw, 1); properties.test_property_rw = 2; assertEquals(properties.test_property_rw, 2); - // assertEquals(properties.test_property_r, 2); - // assertRejects(() => properties.test_property_r = 3); + assertEquals(properties.test_property_r, 1); + + // https://github.com/denoland/deno/issues/17509 + assertEquals(properties.test_simple_property, { + nice: 69, + }); + + assertEquals(properties.key_v8_string, 1); + const symbols = Object.getOwnPropertySymbols(properties); + assertEquals(symbols.length, 1); + assertEquals(symbols[0].description, "key_v8_symbol"); + assertEquals(properties[symbols[0]], 1); }); diff --git a/test_napi/src/lib.rs b/test_napi/src/lib.rs index ed0afb741bc7ab..dba9f65a5cf6f4 100644 --- a/test_napi/src/lib.rs +++ b/test_napi/src/lib.rs @@ -27,7 +27,7 @@ pub mod typedarray; #[macro_export] macro_rules! cstr { ($s: literal) => {{ - std::ffi::CString::new($s).unwrap().as_ptr() + std::ffi::CString::new($s).unwrap().into_raw() }}; } diff --git a/test_napi/src/properties.rs b/test_napi/src/properties.rs index a54738cb571ff9..339699a05e793c 100644 --- a/test_napi/src/properties.rs +++ b/test_napi/src/properties.rs @@ -1,11 +1,28 @@ // Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. use crate::assert_napi_ok; +use crate::cstr; use napi_sys::PropertyAttributes::*; use napi_sys::*; -use std::os::raw::c_char; use std::ptr; +static NICE: i64 = 69; + +fn init_constants(env: napi_env) -> napi_value { + let mut constants: napi_value = ptr::null_mut(); + let mut value: napi_value = ptr::null_mut(); + + assert_napi_ok!(napi_create_object(env, &mut constants)); + assert_napi_ok!(napi_create_int64(env, NICE, &mut value)); + assert_napi_ok!(napi_set_named_property( + env, + constants, + cstr!("nice"), + value + )); + constants +} + pub fn init(env: napi_env, exports: napi_value) { let mut number: napi_value = ptr::null_mut(); assert_napi_ok!(napi_create_double(env, 1.0, &mut number)); @@ -14,7 +31,7 @@ pub fn init(env: napi_env, exports: napi_value) { let mut name_value: napi_value = ptr::null_mut(); assert_napi_ok!(napi_create_string_utf8( env, - "key_v8_string".as_ptr() as *const c_char, + cstr!("key_v8_string"), usize::MAX, &mut name_value, )); @@ -24,7 +41,7 @@ pub fn init(env: napi_env, exports: napi_value) { let mut name_symbol: napi_value = ptr::null_mut(); assert_napi_ok!(napi_create_string_utf8( env, - "key_v8_symbol".as_ptr() as *const c_char, + cstr!("key_v8_symbol"), usize::MAX, &mut symbol_description, )); @@ -36,7 +53,17 @@ pub fn init(env: napi_env, exports: napi_value) { let properties = &[ napi_property_descriptor { - utf8name: "test_property_rw\0".as_ptr() as *const c_char, + utf8name: cstr!("test_simple_property"), + name: ptr::null_mut(), + method: None, + getter: None, + setter: None, + data: ptr::null_mut(), + attributes: enumerable | writable, + value: init_constants(env), + }, + napi_property_descriptor { + utf8name: cstr!("test_property_rw"), name: ptr::null_mut(), method: None, getter: None, @@ -46,7 +73,7 @@ pub fn init(env: napi_env, exports: napi_value) { value: number, }, napi_property_descriptor { - utf8name: "test_property_r\0".as_ptr() as *const c_char, + utf8name: cstr!("test_property_r"), name: ptr::null_mut(), method: None, getter: None, diff --git a/test_napi/tests/napi_tests.rs b/test_napi/tests/napi_tests.rs index cba96079ea74b9..747f6aa276fa93 100644 --- a/test_napi/tests/napi_tests.rs +++ b/test_napi/tests/napi_tests.rs @@ -31,7 +31,6 @@ fn napi_tests() { .arg("--allow-env") .arg("--allow-ffi") .arg("--allow-run") - .arg("--unstable") .spawn() .unwrap() .wait_with_output() diff --git a/test_util/Cargo.toml b/test_util/Cargo.toml index 1f47aa4638cfe9..3a16cdca6ff346 100644 --- a/test_util/Cargo.toml +++ b/test_util/Cargo.toml @@ -30,7 +30,7 @@ regex.workspace = true reqwest.workspace = true ring.workspace = true rustls-pemfile.workspace = true -semver.workspace = true +semver = "=1.0.14" serde.workspace = true serde_json.workspace = true tar.workspace = true diff --git a/test_util/src/lib.rs b/test_util/src/lib.rs index 45008cf1fb1d7c..2e053f85f31465 100644 --- a/test_util/src/lib.rs +++ b/test_util/src/lib.rs @@ -31,6 +31,7 @@ use std::mem::replace; use std::net::SocketAddr; use std::ops::Deref; use std::ops::DerefMut; +use std::path::Path; use std::path::PathBuf; use std::pin::Pin; use std::process::Child; @@ -43,6 +44,7 @@ use std::sync::Mutex; use std::sync::MutexGuard; use std::task::Context; use std::task::Poll; +use std::time::Duration; use tokio::io::AsyncWriteExt; use tokio::net::TcpListener; use tokio::net::TcpStream; @@ -96,7 +98,6 @@ lazy_static! { pub fn env_vars_for_npm_tests_no_sync_download() -> Vec<(String, String)> { vec![ - ("DENO_NODE_COMPAT_URL".to_string(), std_file_url()), ("NPM_CONFIG_REGISTRY".to_string(), npm_registry_url()), ("NO_COLOR".to_string(), "1".to_string()), ] @@ -594,6 +595,28 @@ async fn absolute_redirect( ) -> hyper::Result> { let path = req.uri().path(); + if path == "/" { + // We have to manually extract query params here, + // as `req.uri()` returns `PathAndQuery` only, + // and we cannot use `Url::parse(req.uri()).query_pairs()`, + // as it requires url to have a proper base. + let query_params: HashMap<_, _> = req + .uri() + .query() + .unwrap_or_default() + .split('&') + .filter_map(|s| { + s.split_once('=').map(|t| (t.0.to_owned(), t.1.to_owned())) + }) + .collect(); + + if let Some(url) = query_params.get("redirect_to") { + println!("URL: {url:?}"); + let redirect = redirect_resp(url.to_owned()); + return Ok(redirect); + } + } + if path.starts_with("/REDIRECT") { let url = &req.uri().path()[9..]; println!("URL: {url:?}"); @@ -1036,6 +1059,20 @@ async fn main_server( return Ok(file_resp); } } + } else if let Some(suffix) = req.uri().path().strip_prefix("/deno_std/") { + let mut file_path = std_path(); + file_path.push(suffix); + if let Ok(file) = tokio::fs::read(&file_path).await { + let file_resp = custom_headers(req.uri().path(), file); + return Ok(file_resp); + } + } else if let Some(suffix) = req.uri().path().strip_prefix("/sleep/") { + let duration = suffix.parse::().unwrap(); + tokio::time::sleep(Duration::from_millis(duration)).await; + return Response::builder() + .status(StatusCode::OK) + .header("content-type", "application/typescript") + .body(Body::empty()); } Response::builder() @@ -1902,10 +1939,17 @@ pub struct CheckOutputIntegrationTest<'a> { pub envs: Vec<(String, String)>, pub env_clear: bool, pub temp_cwd: bool, + /// Copies the files at the specified directory in the "testdata" directory + /// to the temp folder and runs the test from there. This is useful when + /// the test creates files in the testdata directory (ex. a node_modules folder) + pub copy_temp_dir: Option<&'a str>, + /// Relative to "testdata" directory + pub cwd: Option<&'a str>, } impl<'a> CheckOutputIntegrationTest<'a> { pub fn run(&self) { + let deno_dir = new_deno_dir(); // keep this alive for the test let args = if self.args_vec.is_empty() { std::borrow::Cow::Owned(self.args.split_whitespace().collect::>()) } else { @@ -1915,7 +1959,15 @@ impl<'a> CheckOutputIntegrationTest<'a> { ); std::borrow::Cow::Borrowed(&self.args_vec) }; - let testdata_dir = testdata_path(); + let testdata_dir = if let Some(temp_copy_dir) = &self.copy_temp_dir { + let test_data_path = testdata_path().join(temp_copy_dir); + let temp_copy_dir = deno_dir.path().join(temp_copy_dir); + std::fs::create_dir_all(&temp_copy_dir).unwrap(); + copy_dir_recursive(&test_data_path, &temp_copy_dir).unwrap(); + deno_dir.path().to_owned() + } else { + testdata_path() + }; let args = args .iter() .map(|arg| arg.replace("$TESTDATA", &testdata_dir.to_string_lossy())) @@ -1930,12 +1982,13 @@ impl<'a> CheckOutputIntegrationTest<'a> { }; let (mut reader, writer) = pipe().unwrap(); - let deno_dir = new_deno_dir(); // keep this alive for the test let mut command = deno_cmd_with_deno_dir(&deno_dir); let cwd = if self.temp_cwd { - deno_dir.path() + deno_dir.path().to_owned() + } else if let Some(cwd_) = &self.cwd { + testdata_dir.join(cwd_) } else { - testdata_dir.as_path() + testdata_dir.clone() }; println!("deno_exe args {}", args.join(" ")); println!("deno_exe cwd {:?}", &cwd); @@ -2303,6 +2356,37 @@ pub fn parse_max_mem(output: &str) -> Option { None } +/// Copies a directory to another directory. +/// +/// Note: Does not handle symlinks. +pub fn copy_dir_recursive(from: &Path, to: &Path) -> Result<(), anyhow::Error> { + use anyhow::Context; + + std::fs::create_dir_all(to) + .with_context(|| format!("Creating {}", to.display()))?; + let read_dir = std::fs::read_dir(from) + .with_context(|| format!("Reading {}", from.display()))?; + + for entry in read_dir { + let entry = entry?; + let file_type = entry.file_type()?; + let new_from = from.join(entry.file_name()); + let new_to = to.join(entry.file_name()); + + if file_type.is_dir() { + copy_dir_recursive(&new_from, &new_to).with_context(|| { + format!("Dir {} to {}", new_from.display(), new_to.display()) + })?; + } else if file_type.is_file() { + std::fs::copy(&new_from, &new_to).with_context(|| { + format!("Copying {} to {}", new_from.display(), new_to.display()) + })?; + } + } + + Ok(()) +} + #[cfg(test)] mod tests { use super::*; diff --git a/test_util/src/lsp.rs b/test_util/src/lsp.rs index 5694287eb71004..c4a81c63c621fe 100644 --- a/test_util/src/lsp.rs +++ b/test_util/src/lsp.rs @@ -1,7 +1,6 @@ // Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. use crate::npm_registry_url; -use crate::std_file_url; use super::new_deno_dir; use super::TempDir; @@ -234,7 +233,6 @@ impl LspClient { let mut command = Command::new(deno_exe); command .env("DENO_DIR", deno_dir.path()) - .env("DENO_NODE_COMPAT_URL", std_file_url()) .env("NPM_CONFIG_REGISTRY", npm_registry_url()) .arg("lsp") .stdin(Stdio::piped()) diff --git a/third_party b/third_party index 17e31cec93aef7..ff2ffb4fd917ca 160000 --- a/third_party +++ b/third_party @@ -1 +1 @@ -Subproject commit 17e31cec93aef7d014dcc46bc58ef1a86c0a9995 +Subproject commit ff2ffb4fd917cabe4441fdb1d8f5e842ce58be9a diff --git a/tools/bench/README.md b/tools/bench/README.md deleted file mode 100644 index 78529d1063837e..00000000000000 --- a/tools/bench/README.md +++ /dev/null @@ -1,33 +0,0 @@ -## Re-bootstrapping - -Re-bootstrapping allows deno devs to bench/profile/test JS-side changes without -doing a full `cargo build --release --bin deno` which takes roughly ~4mn on M1s -more on other machines which significantly slows down iteration & -experimentation. - -## Example - -```js -import { benchSync, rebootstrap } from "./tools/bench/mod.js"; - -const bootstrap = rebootstrap([ - "webidl", - "console", - "url", - "web", - "fetch", -]); - -benchSync("resp_w_h", 1e6, () => - new bootstrap.fetch.Response("yolo", { - status: 200, - headers: { - server: "deno", - "content-type": "text/plain", - }, - })); -``` - -This code can then benched and profiled (using Chrome's DevTools) similar to -regular userland code and the original source files appear in the DevTools as -you would expect. diff --git a/tools/bench/rebench.js b/tools/bench/rebench.js deleted file mode 100644 index 7d77a79cd17563..00000000000000 --- a/tools/bench/rebench.js +++ /dev/null @@ -1,27 +0,0 @@ -// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. - -export function benchSync(name, n, innerLoop) { - const t1 = Date.now(); - for (let i = 0; i < n; i++) { - innerLoop(i); - } - const t2 = Date.now(); - console.log(benchStats(name, n, t1, t2)); -} - -export async function benchAsync(name, n, innerLoop) { - const t1 = Date.now(); - for (let i = 0; i < n; i++) { - await innerLoop(i); - } - const t2 = Date.now(); - console.log(benchStats(name, n, t1, t2)); -} - -function benchStats(name, n, t1, t2) { - const dt = (t2 - t1) / 1e3; - const r = n / dt; - const ns = Math.floor(dt / n * 1e9); - return `${name}:${" ".repeat(20 - name.length)}\t` + - `n = ${n}, dt = ${dt.toFixed(3)}s, r = ${r.toFixed(0)}/s, t = ${ns}ns/op`; -} diff --git a/tools/bench/rebootstrap.js b/tools/bench/rebootstrap.js deleted file mode 100644 index 0e94285d593f46..00000000000000 --- a/tools/bench/rebootstrap.js +++ /dev/null @@ -1,27 +0,0 @@ -// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. - -import { dirname, fromFileUrl, join } from "../../test_util/std/path/mod.ts"; -import { expandGlobSync } from "../../test_util/std/fs/mod.ts"; - -const ROOT_DIR = join(dirname(fromFileUrl(import.meta.url)), "..", ".."); - -export function rebootstrap(exts) { - [ - "core/00_primordials.js", - ...exts.map((e) => `ext/${e}/*.js`), - ] - .map((pattern) => join(ROOT_DIR, pattern)) - .map((pattern) => [...expandGlobSync(pattern)]) - .flat() - .map((entry) => entry.path) - .forEach((file) => { - Deno.core.evalContext(Deno.readTextFileSync(file), file); - }); - const bootstrap = globalThis.__bootstrap; - delete globalThis.__bootstrap; - // Patch dispatchEvent so we don't crash when MainWorker exits via: - // `window.dispatchEvent(new Event('unload'))` - // which fails since symbols are mangled during rebootstrap - globalThis.dispatchEvent = () => {}; - return bootstrap; -} diff --git a/tools/copyright_checker.js b/tools/copyright_checker.js index c8ddcdc9197746..6017e25c4d4afe 100644 --- a/tools/copyright_checker.js +++ b/tools/copyright_checker.js @@ -29,6 +29,8 @@ export async function checkCopyright() { ":!:cli/tsc/compiler.d.ts", ":!:test_util/wpt/**", ":!:cli/tools/init/templates/**", + ":!:cli/tests/unit_node/testdata/**", + ":!:cli/tests/node_compat/test/**", // rust "*.rs", diff --git a/tools/lint.js b/tools/lint.js index 97c2447dfd3fc5..699341692a4f5f 100755 --- a/tools/lint.js +++ b/tools/lint.js @@ -24,7 +24,8 @@ if (Deno.args.includes("--rs")) { if (!didLint) { await dlint(); - await dlintPreferPrimordials(); + // todo(dsherret): re-enable + // await dlintPreferPrimordials(); console.log("copyright checker"); await checkCopyright(); await clippy(); diff --git a/tools/wgpu_sync.js b/tools/wgpu_sync.js index 6edc4c92fc523c..4c374e57436400 100755 --- a/tools/wgpu_sync.js +++ b/tools/wgpu_sync.js @@ -87,7 +87,18 @@ async function patchSrcLib() { (data) => data.replace( `prefix "internal:deno_webgpu",`, - `prefix "internal:ext/webgpu",`, + `prefix "internal:deno_webgpu",`, + ), + ); +} + +async function patchSurface() { + await patchFile( + join(TARGET_DIR, "src", "surface.rs"), + (data) => + data.replace( + `prefix "internal:deno_webgpu",`, + `prefix "internal:deno_webgpu",`, ), ); } @@ -97,6 +108,7 @@ async function main() { await checkoutUpstream(); await patchCargo(); await patchSrcLib(); + await patchSurface(); await bash(join(ROOT_PATH, "tools", "format.js")); }